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

Require build class parent #2557

Merged
merged 2 commits into from
Sep 13, 2024

Conversation

temyurchenko
Copy link
Contributor

A part of getting rid of non-Module roots, see #2536

Copy link

codecov bot commented Sep 9, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 93.05%. Comparing base (b4ac0e2) to head (b4b7947).
Report is 3 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #2557   +/-   ##
=======================================
  Coverage   93.05%   93.05%           
=======================================
  Files          93       93           
  Lines       11050    11050           
=======================================
  Hits        10283    10283           
  Misses        767      767           
Flag Coverage Δ
linux 92.94% <100.00%> (ø)
pypy 93.05% <100.00%> (ø)
windows 93.04% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
astroid/helpers.py 95.33% <100.00%> (-0.04%) ⬇️
astroid/nodes/scoped_nodes/scoped_nodes.py 93.13% <100.00%> (+0.02%) ⬆️
astroid/raw_building.py 94.92% <100.00%> (-0.07%) ⬇️

Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

Awesome!

@@ -1705,7 +1705,8 @@ def test_infer_dict_from_keys() -> None:
)
for node in bad_nodes:
with pytest.raises(InferenceError):
next(node.infer())
if isinstance(next(node.infer()), util.UninferableBase):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For this one, I don't know exactly, but my intuition was the following. Before, when we had Unknown for a parent, when inference encountered Unknown, it would raise (InferenceError). Now, there is no Unknown, but the calls still aren't correct. So, we just fail to infer and yield Uninferable.

However, both responses kind of seem to make sense, so I opted to allow either of the responses (InferenceError or Uninferable) to pass the tests.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we be certain that it is either of the two? So, just assert that it is always Uniferable or always the exception? If so, I'd prefer just testing for that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, right now it's always Uninferable. I'll change to test specifically for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Copy link
Contributor Author

@temyurchenko temyurchenko Sep 11, 2024

Choose a reason for hiding this comment

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

Ah, one test fails on pypy with InferenceError. I'd rather go with the initial pytest.raises clause then

astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
@@ -178,6 +178,13 @@ def function_to_method(n, klass):
return n


def attach_to_parent(node: NodeNG, name: str, parent: NodeNG):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def attach_to_parent(node: NodeNG, name: str, parent: NodeNG):
def _attach_to_parent(node: NodeNG, name: str, parent: NodeNG):

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

frame = parent.frame()
frame.set_local(name, node)
if frame is parent:
frame._append_node(node)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels like a crucial change that we don't have a test for. Should we add one?

Copy link
Contributor Author

@temyurchenko temyurchenko Sep 11, 2024

Choose a reason for hiding this comment

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

So, usually classes get added to the body of the parent in the positinit constructor. There is only one exception to my knowledge: raw_building._add_dunder_class. It was added solely to support some pyqt inference and it is creating (and attaching) adhoc classes for the purposes of being later detected by the transform process.

That situation is already tested by the three qt tests (which fail without this change). In other situations it's useless, afaics.

Overall, the process described above seems very perverse, the "dunder" class shouldn't be recreated within a function each time. It is its own class that is defined outside. We should rather consult the type annotation in the transform process. However, it is a question for another PR.

@temyurchenko temyurchenko force-pushed the require-build_class-parent branch 3 times, most recently from 4b2d28a to 4f9e72f Compare September 11, 2024 18:20
We also remove `add_local_node` to avoid redundancy. Instead we do the
   attachment to the parent scope in the constructor of `ClassDef`.

We append a node to the body of the frame when it is also the
   parent. If it's not a parent, then the node should belong to the
   "body" of the parent if it existed. An example is a definition
   within an "if", where the parent is the If node, but the frame is
   the whole module.

it's a part of the campaign to get rid of non-module roots
@DanielNoord DanielNoord merged commit a99967e into pylint-dev:main Sep 13, 2024
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants