Skip to content

Commit

Permalink
set proper parents for namedtuple's and enum's
Browse files Browse the repository at this point in the history
it's a part of the campaign to get rid of non-module roots
  • Loading branch information
temyurchenko committed Sep 9, 2024
1 parent dcf081c commit d3995cc
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 37 deletions.
49 changes: 25 additions & 24 deletions astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]:
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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()])


Expand Down
13 changes: 0 additions & 13 deletions tests/brain/test_named_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d3995cc

Please sign in to comment.