Skip to content
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

Change frame and scope of NamedExpr for certain parents #1221

Merged
merged 7 commits into from
Oct 24, 2021

Conversation

DanielNoord
Copy link
Collaborator

@DanielNoord DanielNoord commented Oct 24, 2021

Steps

  • Write a good description on what the PR does.

Description

/CC @cdce8p

This changes the frame and scope methods of NamedExpr whenever they occur in FunctionDef or ClassDef.

I have tested against current main of pylint and did not experience any regressions.

Type of Changes

Type
🐛 Bug fix

Related Issue

@DanielNoord DanielNoord added this to the 2.8.4 milestone Oct 24, 2021
@DanielNoord
Copy link
Collaborator Author

Keep forgetting that assignment expressions are >3.8... Oops. Will fix!

@cdce8p cdce8p self-requested a review October 24, 2021 12:45
@cdce8p cdce8p added the pylint-tested PRs that don't cause major regressions with pylint label Oct 24, 2021
Copy link
Member

@cdce8p cdce8p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the change against HomeAssistant and didn't notice any regression. Seems ok.

Could you add one more test case for NamedExpr in Comprehensions?

[y for i in (1, 2) if (y := i ** 2)]

astroid/nodes/node_classes.py Show resolved Hide resolved
tests/unittest_nodes.py Outdated Show resolved Hide resolved
tests/unittest_nodes.py Outdated Show resolved Hide resolved
tests/unittest_nodes.py Outdated Show resolved Hide resolved
@DanielNoord
Copy link
Collaborator Author

I'll the extra test tonight.

Btw, do we consider this breaking? Do we expect users to be dependent on the fact that this wrong previously?

@cdce8p
Copy link
Member

cdce8p commented Oct 24, 2021

I'll the extra test tonight.

👍🏻

Btw, do we consider this breaking? Do we expect users to be dependent on the fact that this wrong previously?

Do you mean at what point does a bug become a feature? ;)
We should mention the fix in the changelog, but besides that I would think it's ok.

@DanielNoord
Copy link
Collaborator Author

😃

I'll add a changelog entry then as well!

@DanielNoord
Copy link
Collaborator Author

We didn't actually catch the Comprehension example. You might want to retest with HomeAssistant.

Copy link
Member

@cdce8p cdce8p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did test again. Seems to work still. Left some minor last comments. Let me know what you think.

ChangeLog Outdated Show resolved Hide resolved
tests/unittest_nodes.py Show resolved Hide resolved
Co-authored-by: Marc Mueller <[email protected]>
ChangeLog Outdated Show resolved Hide resolved
Copy link
Member

@cdce8p cdce8p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks, @DanielNoord 🐬

ChangeLog Outdated Show resolved Hide resolved
Co-authored-by: Marc Mueller <[email protected]>
@DanielNoord
Copy link
Collaborator Author

DanielNoord commented Oct 24, 2021

Thanks for the help @cdce8p!

Btw, if you ever get the time to do it I would appreciate it if you could take a look at the typing of frame. I tried to do it before but didn't know how such cases should be handled normally.

The problem is as follows.

  1. Normally frame returns self.parent.frame()
  2. Unless self == LocalsDictNodeNG, which returns self
  3. Unless self == ComprehensionScope which returns self.parent.frame() again. ComprehensionScope inherits from LocalsDictNodeNG

Adding -> Union[Module, FunctionDef, ClassDef] to the first case seems most sensical.
Then for 2 I would think we would something similar as we did for scope() with def frame(self: T) -> T:.
However, then for 3 we would need -> Union[Module, FunctionDef, ClassDef] again, which raises a method not same as base class error.

So we have BaseClass, Inheritor and SubInheritor where the return type of BaseClass and SubInheritor should be similar but Inheritor and some of its other inheritors (Module, FunctionDef and ClassDef) have a different annotation. Is this something that can be easily/cleanly fixed with annotation?

I could also open an issue to track this. I think it would be helpful in pylint if we could add typing to frame.

ChangeLog Outdated Show resolved Hide resolved
Co-authored-by: Marc Mueller <[email protected]>
@@ -4225,6 +4225,46 @@ def postinit(self, target: NodeNG, value: NodeNG) -> None:
self.target = target
self.value = value

def frame(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it was discussed with Marc (sorry I did not read all the comment), and adding typing here should be done later and the docstring is enough for now ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the docstring is enough for now. I will need some guidance from @cdce8p to actually fully type frame().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, typing frame is a bit complicated due to the current class / mixin structure. We should tackle that separately.

of parent nodes.
"""
module = builder.parse(
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can create a fixture for the example? It seems the same example is used for frame and scope.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, although that might also make these tests less flexible in the future. I can explore this, but personally I'm fine with keeping it like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is the same. I'm just not sure it does make much sense to create a fixture for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it that way.

@cdce8p
Copy link
Member

cdce8p commented Oct 24, 2021

Btw, if you ever get the time to do it I would appreciate it if you could take a look at the typing of frame. I tried to do it before but didn't know how such cases should be handled normally.

@DanielNoord This is indeed a bit complicated. At the moment I can only think of two options

  1. A large refactoring. frame should move to a new mixin class. That will however create other typing issues.
  2. Or adding type: ignore for it.

I guess 2 would be much easier to do.

@DanielNoord
Copy link
Collaborator Author

DanielNoord commented Oct 24, 2021

Is was thinking of removing frame from LocalsDictNodeNG and adding it to Module, FunctionDef and ClassDef separately.
Although this would create some duplicate code it might also make the code more maintainable and would remove the need for a type: ignore. Currently it is not immediately apparent which node actually return self because Comprehension is inherited by other classes itself.

Is that something you would consider?

@cdce8p
Copy link
Member

cdce8p commented Oct 24, 2021

Is that something you would consider?

By removing it from LocalsDictNodeNG, you should be aware that any function that currently returns LocalsDictNodeNG can't access frame() afterwards. That's mostly scope() if I'm not mistaken, so might or might not be an issue.

Just an idea:

  • Create a new class FrameNode which inherits from LocalsDictNodeNG
  • Move frame method to FrameNode class
  • Module, ClassDef, and FunctionDef should inherit from from FrameNode instead
  • scope() should return a union of FrameNode, ComprehensionScope, Lambda, ... (?)

@Pierre-Sassoulas
Copy link
Member

Maybe we can create an issue with the current frame typing discussion so it's not lost after the merge ?

@DanielNoord
Copy link
Collaborator Author

DanielNoord commented Oct 24, 2021

Is that something you would consider?

By removing it from LocalsDictNodeNG, you should be aware that any function that currently returns LocalsDictNodeNG can't access frame() afterwards. That's mostly scope() if I'm not mistaken, so might or might not be an issue.

LocalsDictNG inherits from NodeNG:
https://github.com/PyCQA/astroid/blob/1620fb33e852e3d36244244cd99ee2d91aedfc5a/astroid/nodes/scoped_nodes.py#L206

Which has a frame method defined:
https://github.com/PyCQA/astroid/blob/1620fb33e852e3d36244244cd99ee2d91aedfc5a/astroid/nodes/node_ng.py#L261-L270

So, I would assume LocalsDictNG can still access frame right? Or am I missing something in the inheritance structure here (probably 😅 )

Just an idea:

  • Create a new class FrameNode which inherits from LocalsDictNodeNG
  • Move frame method to FrameNode class
  • Module, ClassDef, and FunctionDef should inherit from from FrameNode instead
  • scope() should return a union of FrameNode, ComprehensionScope, Lambda, ... (?)

I like this idea as well. I am happy to create a PR that implements this, but is that necessary with the knowledge that LocalsDictNG does have access to NodeNG's frame?

@cdce8p
Copy link
Member

cdce8p commented Oct 24, 2021

I like this idea as well. I am happy to create a PR that implements this, but is that necessary with the knowledge that LocalsDictNG does have access to NodeNG's frame?

Good point. Don't know about typing though. If you like, try to see if you can make it work. Open a WIP PR and I can take a look.

@DanielNoord DanielNoord merged commit b90714e into pylint-dev:main Oct 24, 2021
@DanielNoord DanielNoord deleted the scope branch October 24, 2021 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🪳 pylint-tested PRs that don't cause major regressions with pylint
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants