diff --git a/ChangeLog b/ChangeLog index 70bb91cac..771e49491 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,9 @@ What's New in astroid 2.12.9? ============================= Release date: TBA +* Fixed a crash on ``namedtuples`` that use ``typename`` to specify their name. + + Closes PyCQA/pylint#7429 diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index acc20c516..aa2ff3cec 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -538,10 +538,22 @@ def _get_namedtuple_fields(node: nodes.Call) -> str: extract a node from them later on. """ names = [] + container = None try: container = next(node.args[1].infer()) except (InferenceError, StopIteration) as exc: raise UseInferenceDefault from exc + # We pass on IndexError as we'll try to infer 'field_names' from the keywords + except IndexError: + pass + if not container: + for keyword_node in node.keywords: + if keyword_node.arg == "field_names": + try: + container = next(keyword_node.value.infer()) + except (InferenceError, StopIteration) as exc: + raise UseInferenceDefault from exc + break if not isinstance(container, nodes.BaseContainer): raise UseInferenceDefault for elt in container.elts: diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py index bcd92c02d..07d17c4ce 100644 --- a/tests/unittest_brain.py +++ b/tests/unittest_brain.py @@ -434,6 +434,23 @@ def __str__(self): inferred = next(node.infer()) self.assertIs(util.Uninferable, inferred) + def test_name_as_typename(self) -> None: + """Reported in https://github.com/PyCQA/pylint/issues/7429 as a crash.""" + good_node, good_node_two, bad_node = builder.extract_node( + """ + import collections + collections.namedtuple(typename="MyTuple", field_names=["birth_date", "city"]) #@ + collections.namedtuple("MyTuple", field_names=["birth_date", "city"]) #@ + collections.namedtuple(["birth_date", "city"], typename="MyTuple") #@ + """ + ) + good_inferred = next(good_node.infer()) + assert isinstance(good_inferred, nodes.ClassDef) + good_node_two_inferred = next(good_node_two.infer()) + assert isinstance(good_node_two_inferred, nodes.ClassDef) + bad_node_inferred = next(bad_node.infer()) + assert bad_node_inferred == util.Uninferable + class DefaultDictTest(unittest.TestCase): def test_1(self) -> None: