mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-17 02:53:01 +00:00
Add scope and definitions for comprehensions (#12748)
## Summary This PR adds scope and definition for comprehension nodes. This includes the following nodes: * List comprehension * Dictionary comprehension * Set comprehension * Generator expression ### Scope Each expression here adds it's own scope with one caveat - the `iter` expression of the first generator is part of the parent scope. For example, in the following code snippet the `iter1` variable is evaluated in the outer scope. ```py [x for x in iter1] ``` > The iterable expression in the leftmost for clause is evaluated directly in the enclosing scope and then passed as an argument to the implicitly nested scope. > > Reference: https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries There's another special case for assignment expressions: > There is one special case: an assignment expression occurring in a list, set or dict comprehension or in a generator expression (below collectively referred to as “comprehensions”) binds the target in the containing scope, honoring a nonlocal or global declaration for the target in that scope, if one exists. > > Reference: https://peps.python.org/pep-0572/#scope-of-the-target For example, in the following code snippet, the variables `a` and `b` are available after the comprehension while `x` isn't: ```py [a := 1 for x in range(2) if (b := 2)] ``` ### Definition Each comprehension node adds a single definition, the "target" variable (`[_ for target in iter]`). This has been accounted for and a new variant has been added to `DefinitionKind`. ### Type Inference Currently, type inference is limited to a single scope. It doesn't _enter_ in another scope to infer the types of the remaining expressions of a node. To accommodate this, the type inference for a **scope** requires new methods which _doesn't_ infer the type of the `iter` expression of the leftmost outer generator (that's defined in the enclosing scope). The type inference for the scope region is split into two parts: * `infer_generator_expression` (similarly for comprehensions) infers the type of the `iter` expression of the leftmost outer generator * `infer_generator_expression_scope` (similarly for comprehension) infers the type of the remaining expressions except for the one mentioned in the previous point The type inference for the **definition** also needs to account for this special case of leftmost generator. This is done by defining a `first` boolean parameter which indicates whether this comprehension definition occurs first in the enclosing expression. ## Test Plan New test cases were added to validate multiple scenarios. Refer to the documentation for each test case which explains what is being tested.
This commit is contained in:
parent
fb9f0c448f
commit
7027344dfc
6 changed files with 461 additions and 39 deletions
|
|
@ -44,6 +44,7 @@ pub(crate) enum DefinitionNodeRef<'a> {
|
|||
NamedExpression(&'a ast::ExprNamed),
|
||||
Assignment(AssignmentDefinitionNodeRef<'a>),
|
||||
AnnotatedAssignment(&'a ast::StmtAnnAssign),
|
||||
Comprehension(ComprehensionDefinitionNodeRef<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> {
|
||||
|
|
@ -88,6 +89,12 @@ impl<'a> From<AssignmentDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ComprehensionDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
|
||||
fn from(node: ComprehensionDefinitionNodeRef<'a>) -> Self {
|
||||
Self::Comprehension(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ImportFromDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::StmtImportFrom,
|
||||
|
|
@ -100,6 +107,12 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
|
|||
pub(crate) target: &'a ast::ExprName,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ComprehensionDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::Comprehension,
|
||||
pub(crate) first: bool,
|
||||
}
|
||||
|
||||
impl DefinitionNodeRef<'_> {
|
||||
#[allow(unsafe_code)]
|
||||
pub(super) unsafe fn into_owned(self, parsed: ParsedModule) -> DefinitionKind {
|
||||
|
|
@ -131,6 +144,12 @@ impl DefinitionNodeRef<'_> {
|
|||
DefinitionNodeRef::AnnotatedAssignment(assign) => {
|
||||
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
|
||||
}
|
||||
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef { node, first }) => {
|
||||
DefinitionKind::Comprehension(ComprehensionDefinitionKind {
|
||||
node: AstNodeRef::new(parsed, node),
|
||||
first,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +167,7 @@ impl DefinitionNodeRef<'_> {
|
|||
target,
|
||||
}) => target.into(),
|
||||
Self::AnnotatedAssignment(node) => node.into(),
|
||||
Self::Comprehension(ComprehensionDefinitionNodeRef { node, first: _ }) => node.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +181,23 @@ pub enum DefinitionKind {
|
|||
NamedExpression(AstNodeRef<ast::ExprNamed>),
|
||||
Assignment(AssignmentDefinitionKind),
|
||||
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
|
||||
Comprehension(ComprehensionDefinitionKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComprehensionDefinitionKind {
|
||||
node: AstNodeRef<ast::Comprehension>,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl ComprehensionDefinitionKind {
|
||||
pub(crate) fn node(&self) -> &ast::Comprehension {
|
||||
self.node.node()
|
||||
}
|
||||
|
||||
pub(crate) fn is_first(&self) -> bool {
|
||||
self.first
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -230,3 +267,9 @@ impl From<&ast::StmtAnnAssign> for DefinitionNodeKey {
|
|||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::Comprehension> for DefinitionNodeKey {
|
||||
fn from(node: &ast::Comprehension) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue