From d3995cc06d6e05aab2295a38bd5e09b16d79d0ec Mon Sep 17 00:00:00 2001 From: Artem Yurchenko Date: Mon, 9 Sep 2024 13:05:50 -0700 Subject: [PATCH] set proper parents for namedtuple's and enum's it's a part of the campaign to get rid of non-module roots --- astroid/brain/brain_namedtuple_enum.py | 49 +++++++++++++------------- tests/brain/test_named_tuple.py | 13 ------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index 72a07c1187..add92af3df 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -74,7 +74,9 @@ def _extract_namedtuple_arg_or_keyword( # pylint: disable=inconsistent-return-s def infer_func_form( node: nodes.Call, - base_type: list[nodes.NodeNG], + base_type: nodes.NodeNG, + *, + parent: nodes.NodeNG, context: InferenceContext | None = None, enum: bool = False, ) -> tuple[nodes.ClassDef, str, list[str]]: @@ -147,15 +149,11 @@ def infer_func_form( col_offset=node.col_offset, end_lineno=node.end_lineno, end_col_offset=node.end_col_offset, - parent=nodes.Unknown(), + parent=parent, ) - # A typical ClassDef automatically adds its name to the parent scope, - # but doing so causes problems, so defer setting parent until after init - # see: https://github.com/pylint-dev/pylint/issues/5982 - class_node.parent = node.parent class_node.postinit( # set base class=tuple - bases=base_type, + bases=[base_type], body=[], decorators=None, ) @@ -195,25 +193,18 @@ def infer_named_tuple( node: nodes.Call, context: InferenceContext | None = None ) -> Iterator[nodes.ClassDef]: """Specific inference function for namedtuple Call node.""" - tuple_base_name: list[nodes.NodeNG] = [ - nodes.Name( - name="tuple", - parent=node.root(), - lineno=0, - col_offset=0, - end_lineno=None, - end_col_offset=None, - ) - ] + tuple_base = util.safe_infer(_extract_single_node("tuple")) + assert isinstance(tuple_base, nodes.NodeNG) + class_node, name, attributes = infer_func_form( - node, tuple_base_name, context=context + node, tuple_base, parent=AstroidManager().adhoc_module, context=context ) + call_site = arguments.CallSite.from_call(node, context=context) - node = extract_node("import collections; collections.namedtuple") - try: - func = next(node.infer()) - except StopIteration as e: - raise InferenceError(node=node) from e + func = util.safe_infer( + _extract_single_node("import collections; collections.namedtuple") + ) + assert isinstance(func, nodes.NodeNG) try: rename = next( call_site.infer_argument(func, "rename", context or InferenceContext()) @@ -364,7 +355,17 @@ def value(self): __members__ = [''] """ ) - class_node = infer_func_form(node, [enum_meta], context=context, enum=True)[0] + + # FIXME arguably, the base here shouldn't be the EnumMeta class definition + # itself, but a reference (Name) to it. Otherwise, the invariant that all + # children of a node have that node as their parent is broken. + class_node = infer_func_form( + node, + enum_meta, + parent=AstroidManager().adhoc_module, + context=context, + enum=True, + )[0] return iter([class_node.instantiate_class()]) diff --git a/tests/brain/test_named_tuple.py b/tests/brain/test_named_tuple.py index 40a96c7cee..c802982e71 100644 --- a/tests/brain/test_named_tuple.py +++ b/tests/brain/test_named_tuple.py @@ -175,19 +175,6 @@ def test_namedtuple_func_form_args_and_kwargs(self) -> None: self.assertIn("b", inferred.locals) self.assertIn("c", inferred.locals) - def test_namedtuple_bases_are_actually_names_not_nodes(self) -> None: - node = builder.extract_node( - """ - from collections import namedtuple - Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE) - Tuple #@ - """ - ) - inferred = next(node.infer()) - self.assertIsInstance(inferred, astroid.ClassDef) - self.assertIsInstance(inferred.bases[0], astroid.Name) - self.assertEqual(inferred.bases[0].name, "tuple") - def test_invalid_label_does_not_crash_inference(self) -> None: code = """ import collections