-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[red-knot] Handle multiple comprehension targets #13213
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -402,7 +402,8 @@ impl<'db> TypeInferenceBuilder<'db> { | |
} | ||
DefinitionKind::Comprehension(comprehension) => { | ||
self.infer_comprehension_definition( | ||
comprehension.node(), | ||
comprehension.iterable(), | ||
comprehension.target(), | ||
comprehension.is_first(), | ||
definition, | ||
); | ||
|
@@ -1544,11 +1545,11 @@ impl<'db> TypeInferenceBuilder<'db> { | |
|
||
/// Infer the type of the `iter` expression of the first comprehension. | ||
fn infer_first_comprehension_iter(&mut self, comprehensions: &[ast::Comprehension]) { | ||
let mut generators_iter = comprehensions.iter(); | ||
let Some(first_generator) = generators_iter.next() else { | ||
let mut comprehensions_iter = comprehensions.iter(); | ||
let Some(first_comprehension) = comprehensions_iter.next() else { | ||
unreachable!("Comprehension must contain at least one generator"); | ||
}; | ||
self.infer_expression(&first_generator.iter); | ||
self.infer_expression(&first_comprehension.iter); | ||
} | ||
|
||
fn infer_generator_expression(&mut self, generator: &ast::ExprGenerator) -> Type<'db> { | ||
|
@@ -1614,9 +1615,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |
} = generator; | ||
|
||
self.infer_expression(elt); | ||
for comprehension in generators { | ||
self.infer_comprehension(comprehension); | ||
} | ||
self.infer_comprehensions_scope(generators); | ||
} | ||
|
||
fn infer_list_comprehension_expression_scope(&mut self, listcomp: &ast::ExprListComp) { | ||
|
@@ -1627,9 +1626,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |
} = listcomp; | ||
|
||
self.infer_expression(elt); | ||
for comprehension in generators { | ||
self.infer_comprehension(comprehension); | ||
} | ||
self.infer_comprehensions_scope(generators); | ||
} | ||
|
||
fn infer_dict_comprehension_expression_scope(&mut self, dictcomp: &ast::ExprDictComp) { | ||
|
@@ -1642,9 +1639,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |
|
||
self.infer_expression(key); | ||
self.infer_expression(value); | ||
for comprehension in generators { | ||
self.infer_comprehension(comprehension); | ||
} | ||
self.infer_comprehensions_scope(generators); | ||
} | ||
|
||
fn infer_set_comprehension_expression_scope(&mut self, setcomp: &ast::ExprSetComp) { | ||
|
@@ -1655,37 +1650,68 @@ impl<'db> TypeInferenceBuilder<'db> { | |
} = setcomp; | ||
|
||
self.infer_expression(elt); | ||
for comprehension in generators { | ||
self.infer_comprehension(comprehension); | ||
} | ||
self.infer_comprehensions_scope(generators); | ||
} | ||
|
||
fn infer_comprehension(&mut self, comprehension: &ast::Comprehension) { | ||
self.infer_definition(comprehension); | ||
for expr in &comprehension.ifs { | ||
self.infer_expression(expr); | ||
fn infer_comprehensions_scope(&mut self, comprehensions: &[ast::Comprehension]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We've inherited some awfully confusing naming from the CPython grammar and AST :( If we have (To be clear, this is not a critique of this PR, just a lament for the state of naming in this part of the AST. We could fix this in our AST, but then we'd be inventing our own naming scheme that doesn't match the CPython AST, and that doesn't seem great either.) One way to maybe clarify this naming a bit would be to switch from In any case, I don't like the names If we go with my suggestion above to rename e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with the sentiment, thanks for sharing that.
I think, if we want, we can change the names in our AST as we've done so in the past as well (#8064, #6379, #6253, etc.). There are other recommendations here: #6183
From my perspective, both
Makes sense. I can change this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've not renamed the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, mostly what my suggestion was aiming for was to hew very closely to the AST naming, in hopes that this would at least give some guideposts to readers. But I'm OK with your approach and relying on |
||
let mut generators_iter = comprehensions.iter(); | ||
let Some(first_generator) = generators_iter.next() else { | ||
unreachable!("Comprehension must contain at least one generator"); | ||
}; | ||
self.infer_comprehension_scope(first_generator, true); | ||
for generator in generators_iter { | ||
self.infer_comprehension_scope(generator, false); | ||
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
fn infer_comprehension_definition( | ||
&mut self, | ||
comprehension: &ast::Comprehension, | ||
is_first: bool, | ||
definition: Definition<'db>, | ||
) { | ||
fn infer_comprehension_scope(&mut self, comprehension: &ast::Comprehension, is_first: bool) { | ||
let ast::Comprehension { | ||
range: _, | ||
target, | ||
iter, | ||
ifs: _, | ||
ifs, | ||
is_async: _, | ||
} = comprehension; | ||
|
||
if !is_first { | ||
self.infer_expression(iter); | ||
} | ||
// TODO(dhruvmanila): The target type should be inferred based on the iter type instead. | ||
let target_ty = self.infer_expression(target); | ||
// TODO more complex assignment targets | ||
if let ast::Expr::Name(name) = target { | ||
self.infer_definition(name); | ||
} else { | ||
self.infer_expression(target); | ||
} | ||
for expr in ifs { | ||
self.infer_expression(expr); | ||
} | ||
} | ||
|
||
fn infer_comprehension_definition( | ||
&mut self, | ||
iterable: &ast::Expr, | ||
target: &ast::ExprName, | ||
is_first: bool, | ||
definition: Definition<'db>, | ||
) { | ||
if !is_first { | ||
let expression = self.index.expression(iterable); | ||
let result = infer_expression_types(self.db, expression); | ||
self.extend(result); | ||
let _iterable_ty = self | ||
.types | ||
.expression_ty(iterable.scoped_ast_id(self.db, self.scope)); | ||
} | ||
// TODO(dhruvmanila): The iter type for the first comprehension is coming from the | ||
// enclosing scope. | ||
|
||
// TODO(dhruvmanila): The target type should be inferred based on the iter type instead, | ||
// similar to how it's done in `infer_for_statement_definition`. | ||
let target_ty = Type::Unknown; | ||
|
||
self.types | ||
.expressions | ||
.insert(target.scoped_ast_id(self.db, self.scope), target_ty); | ||
self.types.definitions.insert(definition, target_ty); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering if we'll also need access to the
if
portion of the generator here, but I think we won't; that expression should be handled separately as aConstraint
, it's not part of theDefinition
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's what I thought as well which is the reason to limit this. It can easily be reverted in the future if there's a need.