Skip to content

Commit 36d2d45

Browse files
committed
Return BoundMethods instead of the result of those methods
1 parent b4e4a78 commit 36d2d45

File tree

3 files changed

+72
-46
lines changed

3 files changed

+72
-46
lines changed

astroid/interpreter/objectmodel.py

+38-34
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131
from typing import TYPE_CHECKING
3232

3333
import astroid
34-
from astroid import nodes, util
34+
from astroid import bases, nodes, util
3535
from astroid.context import InferenceContext, copy_context
3636
from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
3737
from astroid.manager import AstroidManager
3838
from astroid.nodes import node_classes
3939

4040
objects = util.lazy_import("objects")
41+
builder = util.lazy_import("builder")
4142

4243
if TYPE_CHECKING:
44+
from astroid import builder
4345
from astroid.objects import Property
4446

4547
IMPL_PREFIX = "attr_"
@@ -119,21 +121,41 @@ def lookup(self, name):
119121
raise AttributeInferenceError(target=self._instance, attribute=name)
120122

121123
@property
122-
def attr___new__(self):
123-
"""Calling cls.__new__(cls) on an object returns an instance of that object.
124+
def attr___new__(self) -> bases.BoundMethod:
125+
"""Calling cls.__new__(type) on an object returns an instance of 'type'."""
126+
node: nodes.FunctionDef = builder.extract_node(
127+
"""def __new__(self, cls): return cls()"""
128+
)
129+
# We set the parent as being the ClassDef of 'object' as that
130+
# triggers correct inference as a call to __new__ in bases.py
131+
node.parent: nodes.ClassDef = AstroidManager().builtins_module["object"]
124132

125-
Instance is either an instance or a class definition of the instance to be
126-
created.
127-
"""
128133
# TODO: Use isinstance instead of try ... except after _instance has typing
129134
try:
130-
return self._instance._proxied.instantiate_class()
135+
bound = self._instance._proxied
131136
except AttributeError:
132-
return self._instance.instantiate_class()
137+
bound = self._instance
138+
return bases.BoundMethod(proxy=node, bound=bound)
133139

134140
@property
135-
def attr___init__(self) -> nodes.Const:
136-
return nodes.Const(None)
141+
def attr___init__(self) -> bases.BoundMethod:
142+
"""Calling cls.__init__() normally returns None."""
143+
# The *args and **kwargs are necessary not too trigger warnings about missing
144+
# or extra parameters for '__init__' methods we don't infer correctly.
145+
# This BoundMethod is the fallback value for those.
146+
node: nodes.FunctionDef = builder.extract_node(
147+
"""def __init__(self, *args, **kwargs): return None"""
148+
)
149+
# We set the parent as being the ClassDef of 'object' as that
150+
# is where this method originally comes from
151+
node.parent: nodes.ClassDef = AstroidManager().builtins_module["object"]
152+
153+
# TODO: Use isinstance instead of try ... except after _instance has typing
154+
try:
155+
bound = self._instance._proxied
156+
except AttributeError:
157+
bound = self._instance
158+
return bases.BoundMethod(proxy=node, bound=bound)
137159

138160

139161
class ModuleModel(ObjectModel):
@@ -304,9 +326,6 @@ def attr___module__(self):
304326

305327
@property
306328
def attr___get__(self):
307-
# pylint: disable=import-outside-toplevel; circular import
308-
from astroid import bases
309-
310329
func = self._instance
311330

312331
class DescriptorBoundMethod(bases.BoundMethod):
@@ -458,9 +477,6 @@ def attr_mro(self):
458477
if not self._instance.newstyle:
459478
raise AttributeInferenceError(target=self._instance, attribute="mro")
460479

461-
# pylint: disable=import-outside-toplevel; circular import
462-
from astroid import bases
463-
464480
other_self = self
465481

466482
# Cls.mro is a method and we need to return one in order to have a proper inference.
@@ -483,7 +499,7 @@ def attr___bases__(self):
483499

484500
@property
485501
def attr___class__(self):
486-
# pylint: disable=import-outside-toplevel; circular import
502+
# pylint: disable=import-outside-toplevel; circular importdd
487503
from astroid import helpers
488504

489505
return helpers.object_type(self._instance)
@@ -495,10 +511,6 @@ def attr___subclasses__(self):
495511
This looks only in the current module for retrieving the subclasses,
496512
thus it might miss a couple of them.
497513
"""
498-
# pylint: disable=import-outside-toplevel; circular import
499-
from astroid import bases
500-
from astroid.nodes import scoped_nodes
501-
502514
if not self._instance.newstyle:
503515
raise AttributeInferenceError(
504516
target=self._instance, attribute="__subclasses__"
@@ -508,7 +520,7 @@ def attr___subclasses__(self):
508520
root = self._instance.root()
509521
classes = [
510522
cls
511-
for cls in root.nodes_of_class(scoped_nodes.ClassDef)
523+
for cls in root.nodes_of_class(nodes.ClassDef)
512524
if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
513525
]
514526

@@ -781,12 +793,8 @@ def attr_values(self):
781793
class PropertyModel(ObjectModel):
782794
"""Model for a builtin property"""
783795

784-
# pylint: disable=import-outside-toplevel
785796
def _init_function(self, name):
786-
from astroid.nodes.node_classes import Arguments
787-
from astroid.nodes.scoped_nodes import FunctionDef
788-
789-
args = Arguments()
797+
args = nodes.Arguments()
790798
args.postinit(
791799
args=[],
792800
defaults=[],
@@ -798,18 +806,16 @@ def _init_function(self, name):
798806
kwonlyargs_annotations=[],
799807
)
800808

801-
function = FunctionDef(name=name, parent=self._instance)
809+
function = nodes.FunctionDef(name=name, parent=self._instance)
802810

803811
function.postinit(args=args, body=[])
804812
return function
805813

806814
@property
807815
def attr_fget(self):
808-
from astroid.nodes.scoped_nodes import FunctionDef
809-
810816
func = self._instance
811817

812-
class PropertyFuncAccessor(FunctionDef):
818+
class PropertyFuncAccessor(nodes.FunctionDef):
813819
def infer_call_result(self, caller=None, context=None):
814820
nonlocal func
815821
if caller and len(caller.args) != 1:
@@ -827,8 +833,6 @@ def infer_call_result(self, caller=None, context=None):
827833

828834
@property
829835
def attr_fset(self):
830-
from astroid.nodes.scoped_nodes import FunctionDef
831-
832836
func = self._instance
833837

834838
def find_setter(func: Property) -> astroid.FunctionDef | None:
@@ -852,7 +856,7 @@ def find_setter(func: Property) -> astroid.FunctionDef | None:
852856
f"Unable to find the setter of property {func.function.name}"
853857
)
854858

855-
class PropertyFuncAccessor(FunctionDef):
859+
class PropertyFuncAccessor(nodes.FunctionDef):
856860
def infer_call_result(self, caller=None, context=None):
857861
nonlocal func_setter
858862
if caller and len(caller.args) != 2:

tests/unittest_object_model.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99

1010
import astroid
11-
from astroid import builder, nodes, objects, test_utils, util
11+
from astroid import bases, builder, nodes, objects, test_utils, util
1212
from astroid.const import PY311_PLUS
1313
from astroid.exceptions import InferenceError
1414

@@ -203,9 +203,9 @@ class C(A): pass
203203
called_mro = next(ast_nodes[5].infer())
204204
self.assertEqual(called_mro.elts, mro.elts)
205205

206-
bases = next(ast_nodes[6].infer())
207-
self.assertIsInstance(bases, astroid.Tuple)
208-
self.assertEqual([cls.name for cls in bases.elts], ["object"])
206+
base_nodes = next(ast_nodes[6].infer())
207+
self.assertIsInstance(base_nodes, astroid.Tuple)
208+
self.assertEqual([cls.name for cls in base_nodes.elts], ["object"])
209209

210210
cls = next(ast_nodes[7].infer())
211211
self.assertIsInstance(cls, astroid.ClassDef)
@@ -306,12 +306,13 @@ def test_module_model(self) -> None:
306306
self.assertIsInstance(dict_, astroid.Dict)
307307

308308
init_ = next(ast_nodes[9].infer())
309-
assert isinstance(init_, nodes.Const)
310-
assert init_.value is None
309+
assert isinstance(init_, bases.BoundMethod)
310+
init_result = next(init_.infer_call_result(nodes.Call()))
311+
assert isinstance(init_result, nodes.Const)
312+
assert init_result.value is None
311313

312314
new_ = next(ast_nodes[10].infer())
313-
assert isinstance(new_, nodes.Module)
314-
assert new_.name == "xml"
315+
assert isinstance(new_, bases.BoundMethod)
315316

316317
# The following nodes are just here for theoretical completeness,
317318
# and they either return Uninferable or raise InferenceError.
@@ -484,12 +485,13 @@ def func(a=1, b=2):
484485
self.assertIs(next(ast_node.infer()), astroid.Uninferable)
485486

486487
init_ = next(ast_nodes[9].infer())
487-
assert isinstance(init_, nodes.Const)
488-
assert init_.value is None
488+
assert isinstance(init_, bases.BoundMethod)
489+
init_result = next(init_.infer_call_result(nodes.Call()))
490+
assert isinstance(init_result, nodes.Const)
491+
assert init_result.value is None
489492

490493
new_ = next(ast_nodes[10].infer())
491-
assert isinstance(new_, nodes.FunctionDef)
492-
assert new_.name == "func"
494+
assert isinstance(new_, bases.BoundMethod)
493495

494496
# The following nodes are just here for theoretical completeness,
495497
# and they either return Uninferable or raise InferenceError.

tests/unittest_objects.py

+20
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,26 @@ def __new__(metacls, classname, bases, classdict, **kwds):
568568
isinstance(i, (nodes.NodeNG, type(util.Uninferable))) for i in inferred
569569
)
570570

571+
def test_super_init_call(self) -> None:
572+
"""Test that __init__ is still callable."""
573+
init_node: nodes.Attribute = builder.extract_node(
574+
"""
575+
class SuperUsingClass:
576+
@staticmethod
577+
def test():
578+
super(object, 1).__new__ #@
579+
super(object, 1).__init__ #@
580+
class A:
581+
pass
582+
A().__new__ #@
583+
A().__init__ #@
584+
"""
585+
)
586+
assert isinstance(next(init_node[0].infer()), bases.BoundMethod)
587+
assert isinstance(next(init_node[1].infer()), bases.BoundMethod)
588+
assert isinstance(next(init_node[2].infer()), bases.BoundMethod)
589+
assert isinstance(next(init_node[3].infer()), bases.BoundMethod)
590+
571591

572592
if __name__ == "__main__":
573593
unittest.main()

0 commit comments

Comments
 (0)