-
-
Notifications
You must be signed in to change notification settings - Fork 280
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
Move inference methods to nodes module(s) #2171
Move inference methods to nodes module(s) #2171
Conversation
@@ -111,10 +107,7 @@ | |||
) | |||
from astroid.nodes.utils import Position | |||
|
|||
_BaseContainer = BaseContainer # TODO Remove for astroid 3.0 |
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.
removed!
nodes.Assign.assigned_stmts = assign_assigned_stmts | ||
nodes.AnnAssign.assigned_stmts = assign_annassigned_stmts | ||
nodes.AugAssign.assigned_stmts = assign_assigned_stmts |
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.
yuk!
|
||
# TODO: Move into _base_nodes. Blocked by import of _infer_stmts from bases. |
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.
done!
infer_lhs: ClassVar[InferLHS[AssignName]] | ||
|
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.
yuk!
@@ -1470,6 +1727,88 @@ def last_child(self): | |||
return self.ops[-1][1] | |||
# return self.left | |||
|
|||
# TODO: move to util? |
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.
decision point
def _to_literal(node: SuccessfulInferenceResult) -> Any: | ||
# Can raise SyntaxError or ValueError from ast.literal_eval | ||
# Can raise AttributeError from node.as_string() as not all nodes have a visitor | ||
# Is this the stupidest idea or the simplest idea? |
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.
lol
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.
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.
Left some early review comments.
Benefits
...
π π
Drawbacks
- There are some function-level imports created here that are symptoms of circular imports, but many of them are with the helpers module, and many of those are just for
safe_infer
, which could be extracted into its own helper module (it only needs UninferableBase). I don't think this jeopardizes the design.
Agreed!
However, I think we are missing one major drawback (although not necessarily a drawback of this design).
We still have the issue of these classes being behemoths that (imo) try to do too much. With this change, and possibly the inclusion of protocols.py
on the nodes themselves as well, there is not that much left besides the astroid.nodes
package. I still don't know how I feel about this as it feels we are putting more logic on the nodes
than is completely necessary.
There are also still reference to AstroidManager()
or MANAGER
which makes it impossible to decouple the global state used by astroid
without starting the pass an actual manager
object in these signatures.
Todos:
- Get benchmarks
If we agree on this design I think we shouldn't be held back by performance degradation too much. We can more easily improve performance when we can actually work with the code.
|
||
|
||
class AttributeNode(NodeNG): | ||
expr: NodeNG |
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 feels like this class should also define a postinit
to couple this annotation to something actually setting it.
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.
It's more of a mixin. The actual concrete classes do have postinit methods. Should I document this or raise some sort of TypeError when attempting to instantiate?
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.
Perhaps make them ABC
? However, I don't really like mixins in this way. We actually spend quite some time removing them from pylint
because the way this mixins work don't allow us to guarantee that they function at runtime. We can't enforce that expr
is being set.. Would it be feasible to make them have a postinit
that needs to be called?
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.
Happy to do it, but just for my own understanding, why would a postinit help? When would it ever be called, and how does it help a static type checker? I'm sure there is a reason, I just haven't worked with postinit
in astroid yet.
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.
In this case it should probably be an init
. Sorry for the confusion!
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.
If the mixins need to be robust against unintended instantiation, then they're more trouble then they're worth just for the DRY benefit. I'll just extract helper functions that can be shared.
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 also greatly dislike mixin for the sake of being dry because having to initialise instance in a particular order and not being able to use typecheck (becauseof course you can't guarantee the instanciation particular order and a type checker will rightfully warn you) is order if magnitude worse than whatever design problem mixin are trying to solve.
context.lookupname = self.name | ||
context.constraints[self.name] = get_constraints(self, frame) | ||
|
||
return bases._infer_stmts(stmts, context, frame) |
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 really like these new base classes, but don't really like how long this file is going to be and how many different types of bases nodes are in here. Not sure if this is fixable though.
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.
We could burst some files later on but we have other issues to deal with imo. (Especially cyclic imports that make this harder)
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.
Left some early review comments.
Benefits
...
π π
Drawbacks
- There are some function-level imports created here that are symptoms of circular imports, but many of them are with the helpers module, and many of those are just for
safe_infer
, which could be extracted into its own helper module (it only needs UninferableBase). I don't think this jeopardizes the design.
Agreed!
However, I think we are missing one major drawback (although not necessarily a drawback of this design).
We still have the issue of these classes being behemoths that (imo) try to do too much. With this change, and possibly the inclusion of protocols.py
on the nodes themselves as well, there is not that much left besides the astroid.nodes
package. I still don't know how I feel about this as it feels we are putting more logic on the nodes
than is completely necessary.
There are also still reference to AstroidManager()
or MANAGER
which makes it impossible to decouple the global state used by astroid
without starting the pass an actual manager
object in these signatures.
Todos:
- Get benchmarks
If we agree on this design I think we shouldn't be held back by performance degradation too much. We can more easily improve performance when we can actually work with the code.
Thanks for the early feedback. I'll try to make some time over the next week or so to iron out the kinks. In the meantime, let's avoid merging changes to inference.py, because I've already deleted the file and could easily miss all the merge conflicts unless I check every commit on main. |
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #2171 +/- ##
==========================================
- Coverage 92.93% 92.91% -0.02%
==========================================
Files 95 94 -1
Lines 10921 10926 +5
==========================================
+ Hits 10149 10152 +3
- Misses 772 774 +2
Flags with carried forward coverage won't be shown. Click here to find out more.
|
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.
Still not sure how I feel about this. I think the use of Mixins
and the import issues are not very nice, but you guys seem to agree that this is better so I'm also not sure how productive my opposition to this is π
def _infer( | ||
self, context: InferenceContext | None = None, **kwargs: Any | ||
) -> Generator[objects.Property | FunctionDef, None, InferenceErrorInfo]: | ||
from astroid import objects # pylint: disable=import-outside-toplevel |
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 think this is a case of pretty bad cyclic imports. With this approach we can't avoid this I think which makes me doubtful about whether this is the right approach.
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.
Challenge accepted!
Here is a branch that removes these cyclic imports by moving PartialFunction
and Property
to scoped_nodes
, which makes sense, since they inherit from FunctionDef
anyway (what's the use of a Property being an object, anyway?)
The ease with which I could do this gives me hope that this is a good direction that can enable easier refactors in iterative PRs. What do you think?
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.
If we go with the infer_object
approach, we're freezing the inference/objects/protocols ontologies in place, instead of actually questioning if everything is where it should be, which we should feel free to do during breaking changes season β
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.
See my comment on the other pull request. I don't necessarily think we would be freezing anything in place.
I do think that we should merge your PartialFunction
branch to master
irrespective of the approach. Seems like a very sensible change!
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 bursting the big node files is actually (one of) the way to solve circular imports?
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 one nit, but I don't really mind merging this as is and seeing if there are smaller improvements later on. There is too much going on to make it perfect anyway.
While I still don't prefer this method it seems the discussion doesn't really take off and I don't want to block this any longer. I trust you fixed this and tested that pylint
test suite still passes.
If Pierre still approves of this PR (the code, not the idea) then let's ship it π
Add disables for function-level imports
astroid now better infers self.root() as [Uninferable, Instance of Global] instead of [Uninferable]. But due to control flow, only a Module will be returned, never a Global. astroid has ignore-on-opaque-inference set to No, unlike pylint.
b522ee9
to
76c10af
Compare
Sounds good @DanielNoord I really appreciate the care it took to reach this point, and of course I'm open to I rebased and cleaned up the branch a bit. Future PRs:
I don't know what I fixed other than the monkey-patching π but yes the pylint suite passes. There's plenty to do in pylint, we should cut an astroid alpha ASAP and add 3.12 support and remove future=True everywhere. |
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.
LGTM, amazing refactor there's a lot of work here, and it certainely makes astroid more undrstandable
for result in infer_callable(context): | ||
if isinstance(result, error): | ||
# For the sake of .infer(), we don't care about operation | ||
# errors, which is the job of pylint. So return something |
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.
# errors, which is the job of pylint. So return something | |
# errors, which is the job of a linter. So return something |
Astroid Can be used without pylint :D
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.
Hah, good point! I'll catch this in the next PR.
|
||
|
||
class AttributeNode(NodeNG): | ||
expr: NodeNG |
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 also greatly dislike mixin for the sake of being dry because having to initialise instance in a particular order and not being able to use typecheck (becauseof course you can't guarantee the instanciation particular order and a type checker will rightfully warn you) is order if magnitude worse than whatever design problem mixin are trying to solve.
context.lookupname = self.name | ||
context.constraints[self.name] = get_constraints(self, frame) | ||
|
||
return bases._infer_stmts(stmts, context, frame) |
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.
We could burst some files later on but we have other issues to deal with imo. (Especially cyclic imports that make this harder)
def _infer( | ||
self, context: InferenceContext | None = None, **kwargs: Any | ||
) -> Generator[objects.Property | FunctionDef, None, InferenceErrorInfo]: | ||
from astroid import objects # pylint: disable=import-outside-toplevel |
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 bursting the big node files is actually (one of) the way to solve circular imports?
Thanks so much for the reviews. I'll merge this and then get started on the followup PRs for removing the mixins and some of the circular imports. |
Follow-up to 082774a.
Type of Changes
Description
Move
_infer_()
methods frominference.py
to the nodes themselves (alternative to #2167).Benefits
Drawbacks
safe_infer
, which could be extracted into its own helper module (it only needs UninferableBase). I don't think this jeopardizes the design.Fixes #679