Skip to content

Commit 4b2d28a

Browse files
committed
enforce a non-None parent in build_class
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
1 parent 867e12a commit 4b2d28a

File tree

6 files changed

+27
-20
lines changed

6 files changed

+27
-20
lines changed

astroid/helpers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ def safe_infer(
3737

3838

3939
def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef:
40-
proxy = raw_building.build_class(cls_name)
41-
proxy.parent = builtins
40+
proxy = raw_building.build_class(cls_name, builtins)
4241
return proxy
4342

4443

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ def function_to_method(n, klass):
178178
return n
179179

180180

181+
def attach_to_parent(node: NodeNG, name: str, parent: NodeNG):
182+
frame = parent.frame()
183+
frame.set_local(name, node)
184+
if frame is parent:
185+
frame._append_node(node)
186+
187+
181188
class Module(LocalsDictNodeNG):
182189
"""Class representing an :class:`ast.Module` node.
183190
@@ -1936,7 +1943,7 @@ def __init__(
19361943
parent=parent,
19371944
)
19381945
if parent and not isinstance(parent, Unknown):
1939-
parent.frame().set_local(name, self)
1946+
attach_to_parent(self, name, parent)
19401947

19411948
for local_name, node in self.implicit_locals():
19421949
self.add_local_node(node, local_name)

astroid/raw_building.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ def _attach_local_node(parent, node, name: str) -> None:
4949
parent.add_local_node(node)
5050

5151

52-
def _add_dunder_class(func, member) -> None:
52+
def _add_dunder_class(func, parent: nodes.NodeNG, member) -> None:
5353
"""Add a __class__ member to the given func node, if we can determine it."""
5454
python_cls = member.__class__
5555
cls_name = getattr(python_cls, "__name__", None)
5656
if not cls_name:
5757
return
5858
cls_bases = [ancestor.__name__ for ancestor in python_cls.__bases__]
5959
doc = python_cls.__doc__ if isinstance(python_cls.__doc__, str) else None
60-
ast_klass = build_class(cls_name, cls_bases, doc)
60+
ast_klass = build_class(cls_name, parent, cls_bases, doc)
6161
func.instance_attrs["__class__"] = [ast_klass]
6262

6363

@@ -97,7 +97,10 @@ def build_module(name: str, doc: str | None = None) -> nodes.Module:
9797

9898

9999
def build_class(
100-
name: str, basenames: Iterable[str] = (), doc: str | None = None
100+
name: str,
101+
parent: nodes.NodeNG,
102+
basenames: Iterable[str] = (),
103+
doc: str | None = None,
101104
) -> nodes.ClassDef:
102105
"""Create and initialize an astroid ClassDef node."""
103106
node = nodes.ClassDef(
@@ -106,7 +109,7 @@ def build_class(
106109
col_offset=0,
107110
end_lineno=0,
108111
end_col_offset=0,
109-
parent=nodes.Unknown(),
112+
parent=parent,
110113
)
111114
node.postinit(
112115
bases=[
@@ -343,7 +346,7 @@ def object_build_methoddescriptor(
343346
getattr(member, "__name__", None) or localname, doc=member.__doc__
344347
)
345348
node.add_local_node(func, localname)
346-
_add_dunder_class(func, member)
349+
_add_dunder_class(func, node, member)
347350

348351

349352
def _base_class_object_build(
@@ -359,9 +362,8 @@ def _base_class_object_build(
359362
class_name = name or getattr(member, "__name__", None) or localname
360363
assert isinstance(class_name, str)
361364
doc = member.__doc__ if isinstance(member.__doc__, str) else None
362-
klass = build_class(class_name, basenames, doc)
365+
klass = build_class(class_name, node, basenames, doc)
363366
klass._newstyle = isinstance(member, type)
364-
node.add_local_node(klass, localname)
365367
try:
366368
# limit the instantiation trick since it's too dangerous
367369
# (such as infinite test execution...)
@@ -603,14 +605,11 @@ def _astroid_bootstrapping() -> None:
603605

604606
for cls, node_cls in node_classes.CONST_CLS.items():
605607
if cls is TYPE_NONE:
606-
proxy = build_class("NoneType")
607-
proxy.parent = astroid_builtin
608+
proxy = build_class("NoneType", astroid_builtin)
608609
elif cls is TYPE_NOTIMPLEMENTED:
609-
proxy = build_class("NotImplementedType")
610-
proxy.parent = astroid_builtin
610+
proxy = build_class("NotImplementedType", astroid_builtin)
611611
elif cls is TYPE_ELLIPSIS:
612-
proxy = build_class("Ellipsis")
613-
proxy.parent = astroid_builtin
612+
proxy = build_class("Ellipsis", astroid_builtin)
614613
else:
615614
proxy = astroid_builtin.getattr(cls.__name__)[0]
616615
assert isinstance(proxy, nodes.ClassDef)

tests/brain/test_brain.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1705,7 +1705,8 @@ def test_infer_dict_from_keys() -> None:
17051705
)
17061706
for node in bad_nodes:
17071707
with pytest.raises(InferenceError):
1708-
next(node.infer())
1708+
if isinstance(next(node.infer()), util.UninferableBase):
1709+
raise InferenceError
17091710

17101711
# Test uninferable values
17111712
good_nodes = astroid.extract_node(

tests/test_helpers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ def _extract(self, obj_name: str) -> ClassDef:
2626
return self.builtins.getattr(obj_name)[0]
2727

2828
def _build_custom_builtin(self, obj_name: str) -> ClassDef:
29-
proxy = raw_building.build_class(obj_name)
30-
proxy.parent = self.builtins
29+
proxy = raw_building.build_class(obj_name, self.builtins)
3130
return proxy
3231

3332
def assert_classes_equal(self, cls: ClassDef, other: ClassDef) -> None:

tests/test_raw_building.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
build_module,
3434
)
3535

36+
DUMMY_MOD = build_module("DUMMY")
37+
3638

3739
class RawBuildingTC(unittest.TestCase):
3840
def test_attach_dummy_node(self) -> None:
@@ -48,7 +50,7 @@ def test_build_module(self) -> None:
4850
self.assertEqual(node.parent, None)
4951

5052
def test_build_class(self) -> None:
51-
node = build_class("MyClass")
53+
node = build_class("MyClass", DUMMY_MOD)
5254
self.assertEqual(node.name, "MyClass")
5355
self.assertEqual(node.doc_node, None)
5456

0 commit comments

Comments
 (0)