-
-
Notifications
You must be signed in to change notification settings - Fork 276
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
Handle inference of ComprehensionScope
better
#1475
Conversation
astroid/nodes/scoped_nodes/mixin.py
Outdated
generators: List["nodes.Comprehension"] | ||
"""The generators that are looped through.""" | ||
|
||
def qname(self): |
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.
This is another instance of #278. We could also change qname()
on LocalsDictNodeNG
not to use name()
if it doesn't exist.
I don't know what is better.
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.
def qname(self): | |
def qname(self) -> str: |
Pull Request Test Coverage Report for Build 2268818330
💛 - Coveralls |
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 have some questions, but this looks pretty good !
Forgot to point out that the coverage decreased by more than 0.05% and coverall did not fail. I must have set it up wrong. |
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.
The coverage decreased a lot, is this coverall acting up again ?
Hmm I'll have a good look. I think this might actually fix some Part of it is also the merge of |
@Pierre-Sassoulas Can we merge this and backport this to the upcoming patch release? Would be good to fix the coverage in |
Sure, but do you mean astroid patch release or pylint's patch release, or both ? |
|
def qname(self) -> str: | ||
"""Get the 'qualified' name of the node.""" | ||
return self.pytype() |
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.
@cdce8p do you have thoughts about this? In the past I believe you were a vote against, see #1284 (review).
Moving this to |
def _infer( | ||
self: _T, context: Optional["InferenceContext"] = None, **kwargs: Any | ||
) -> Iterator[_T]: | ||
yield self |
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.
What do we get by returning self
, i.e. the AST node, here?
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.
Do you mean return self
instead of yield self
?
dict_comp = next(module["dict_comp"].infer()) | ||
self.assertEqual(dict_comp, util.Uninferable) | ||
assert isinstance(dict_comp, nodes.DictComp) | ||
set_comp = next(module["set_comp"].infer()) | ||
self.assertEqual(set_comp, util.Uninferable) | ||
assert isinstance(set_comp, nodes.SetComp) | ||
list_comp = next(module["list_comp"].infer()) | ||
self.assertEqual(list_comp, util.Uninferable) | ||
assert isinstance(list_comp, nodes.ListComp) |
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.
Maybe I'm misunderstanding the concept of inference here. If so, please correct me.
However, the way I see it inference means we look at the AST node and tell the caller what the Python result will be without actually executing the code. To give an example
lst = [x for x in range(3)]
The inference result for lst
shouldn't be the list comprehension itself. Instead it should be the AST node for a list with
[0, 1, 2]
Same for generator expressions, dict and set comprehensions.
That is much much harder to do which is likely we it wasn't done already.
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.
Hm, yeah I didn't think of it like that. That would decouple the value of lst
from the actual ast
tree though.
For example, we would then have a ListComp
node in nodes.Module.body
but a List
node for lst
? If we create that List
when calling _infer
on ListComp
what would then be the lineno
etc.?
Wouldn't it be better to say that lst
is a ListComp
and let downstream users decide if they want to handle ListComp
and List
similarly? Like we often do with FunctionDef
and AsyncFunctionDef
.
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.
That would decouple the value of
lst
from the actualast
tree though.
Yeah, it's an inference result after all.
If we create that
List
when calling_infer
onListComp
what would then be thelineno
etc.?
None
? Both lineno
and col_offset
attributes are typed as optional.
Or the same one as the original ListComp
.
Wouldn't it be better to say that
lst
is aListComp
and let downstream users decide if they want to handleListComp
andList
similarly? Like we often do withFunctionDef
andAsyncFunctionDef
.
The downstream user still has full control over it. If he once the "raw" data, he can just use the node itself. No need to call infer
for that. Regarding FunctionDef
, the infer method can actually return a Property
if the function is decorated. Similarly, inferring a DictUnpack
will return a single dict or a comparison just a bool constant
{1: 'A', **{2: 'B'}}
True == True
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 guess we should probably handle this comprehension per comprehension then. Instead of one larger PR.
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 guess we should probably handle this comprehension per comprehension then. Instead of one larger PR.
Yeah. There are multiple parts to be aware of: evaluating the iterator, matching the loop var(s) with optional sequence unpacking, checking any if statements and doing late assignments via :=
, evaluating the loop expression. Likely I'm forgetting something.
[a + c for a, b in func() if (c := b * 2)]
If you, or someone else wants to go down that route, it's probably best to start with a fairly limited set constraints and only one comprehension type. Once that is down extending it should be fairly easy in comparison.
Going to close this PR since we would need a different approach for inference. |
Steps
Description
This makes it so that
ComprehensionScopes
are actually inferred. This requires some changes inpylint
as we rely on the fact that these don't get inferred currently.I have made a related PR to
pylint
which shows what these changes would be (pylint-dev/pylint#5923). I think it will be useful to review this simultaneously, as they show how this impactsastroid's
output. That PR has now been merged.One problem I couldn't overcome: this will breakPre-commit will be fixed once we havepre-commit
for the project that merges this first. The change in behaviour changes various checks and we need to resolve an order in which these get merged.pylint
2.13 available here.Let me know what you guys think!
We might want to add some additional tests.
Type of Changes
Related Issue
Closes #135, closes #1404