-
-
Notifications
You must be signed in to change notification settings - Fork 305
Move object dunders from FunctionModel
to ObjectModel
#2847
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
aeedd50
50a56f8
b691e28
1bc57c5
2762ac9
6c4d29f
c4f01a1
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 |
---|---|---|
|
@@ -1618,6 +1618,16 @@ def infer_call_result( | |
yield node_classes.Const(None) | ||
return | ||
|
||
# Builtin dunder methods have empty bodies, return Uninferable. | ||
if ( | ||
len(self.body) == 0 | ||
and self.name.startswith("__") | ||
and self.name.endswith("__") | ||
and self.root().qname() == "builtins" | ||
): | ||
yield util.Uninferable | ||
return | ||
|
||
raise InferenceError("The function does not have any return statements") | ||
|
||
for returnnode in itertools.chain((first_return,), returns): | ||
|
@@ -2346,8 +2356,12 @@ def getattr( | |
values += classnode.locals.get(name, []) | ||
|
||
if name in self.special_attributes and class_context and not values: | ||
result = [self.special_attributes.lookup(name)] | ||
return result | ||
special_attr = self.special_attributes.lookup(name) | ||
if not isinstance( | ||
special_attr, (util.UninferableBase, node_classes.Unknown) | ||
): | ||
result = [special_attr] | ||
return result | ||
Comment on lines
2359
to
2362
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. What is the effect of this? What will we eventually return if 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. This block is a short-circuit used when there are no concrete locals/ancestor definitions: originally it always returned 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.
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. Yes, good idea! |
||
|
||
if class_context: | ||
values += self._metaclass_lookup_attribute(name, context) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,7 +78,10 @@ def attach_const_node(node, name: str, value) -> None: | |
"""create a Const node and register it in the locals of the given | ||
node with the specified name | ||
""" | ||
if name not in node.special_attributes: | ||
# Special case: __hash__ = None overrides ObjectModel for unhashable types. | ||
if name == "__hash__" and value is None: | ||
_attach_local_node(node, nodes.const_factory(value), name) | ||
Comment on lines
81
to
84
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. Should this be the case for all attributes where 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 believe only for 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. Perhaps we can link that documentation here? It would prevent me from wondering why this special case is here the next time. And thanks for the link, TIL about something I knew implicitly but now know where to find the documentation for that feature π Appreciated! 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. Good idea - added the documentation link to both places π |
||
elif name not in node.special_attributes: | ||
_attach_local_node(node, nodes.const_factory(value), name) | ||
|
||
|
||
|
@@ -507,7 +510,10 @@ def object_build( | |
elif inspect.isdatadescriptor(member): | ||
child = object_build_datadescriptor(node, member) | ||
elif isinstance(member, tuple(node_classes.CONST_CLS)): | ||
if alias in node.special_attributes: | ||
# Special case: __hash__ = None overrides ObjectModel for unhashable types. | ||
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. Same question as above 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. Same reasoning as the previous hash question - |
||
if alias in node.special_attributes and not ( | ||
alias == "__hash__" and member is None | ||
): | ||
continue | ||
child = nodes.const_factory(member) | ||
elif inspect.isroutine(member): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5618,8 +5618,9 @@ def test_cannot_infer_call_result_for_builtin_methods() -> None: | |
) | ||
inferred = next(node.infer()) | ||
lenmeth = next(inferred.igetattr("__len__")) | ||
with pytest.raises(InferenceError): | ||
next(lenmeth.infer_call_result(None, None)) | ||
# Builtin dunder methods now return Uninferable instead of raising InferenceError | ||
result = next(lenmeth.infer_call_result(None, None)) | ||
assert result is util.Uninferable | ||
Comment on lines
+5621
to
+5623
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. Ah, I think the changes above now make more sense to me.
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. Yes - |
||
|
||
|
||
def test_unpack_dicts_in_assignment() -> None: | ||
|
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.
Similar question as above
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.
Same pattern as the
ClassDef.getattr()
check - whenspecial_attr
isUnknown
orUninferable
, we skip the early return and continue to the if lookupclass: block which searches the class for the attribute. This ensures we find actual implementations in classes rather than stopping at theUnknown
placeholder.