diff --git a/mypy/semanal.py b/mypy/semanal.py index 6aa5977c110f..60d4f1bde9f8 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3464,8 +3464,9 @@ def record_special_form_lvalue(self, s: AssignmentStmt) -> None: def analyze_enum_assign(self, s: AssignmentStmt) -> bool: """Check if s defines an Enum.""" if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, EnumCallExpr): - # Already analyzed enum -- nothing to do here. - return True + # This is an analyzed enum definition. + # It is valid iff it can be stored correctly, failures were already reported. + return self._is_single_name_assignment(s) return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope()) def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: @@ -3474,7 +3475,9 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: if s.rvalue.analyzed.info.tuple_type and not has_placeholder( s.rvalue.analyzed.info.tuple_type ): - return True # This is a valid and analyzed named tuple definition, nothing to do here. + # This is an analyzed named tuple definition. + # It is valid iff it can be stored correctly, failures were already reported. + return self._is_single_name_assignment(s) if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] @@ -3515,8 +3518,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: if s.rvalue.analyzed.info.typeddict_type and not has_placeholder( s.rvalue.analyzed.info.typeddict_type ): - # This is a valid and analyzed typed dict definition, nothing to do here. - return True + # This is an analyzed typed dict definition. + # It is valid iff it can be stored correctly, failures were already reported. + return self._is_single_name_assignment(s) if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] @@ -3540,6 +3544,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: self.setup_alias_type_vars(defn) return True + def _is_single_name_assignment(self, s: AssignmentStmt) -> bool: + return len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) + def analyze_lvalues(self, s: AssignmentStmt) -> None: # We cannot use s.type, because analyze_simple_literal_type() will set it. explicit = s.unanalyzed_type is not None diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 72e22f2fae94..cc9048db18dc 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2512,3 +2512,15 @@ def list_vals(e: Type[T]) -> list[T]: reveal_type(list_vals(Choices)) # N: Revealed type is "builtins.list[__main__.Choices]" [builtins fixtures/enum.pyi] + +[case testEnumAsClassMemberNoCrash] +# https://github.com/python/mypy/issues/18736 +from enum import Enum + +class Base: + def __init__(self, namespace: tuple[str, ...]) -> None: + # Not a bug: trigger defer + names = [name for name in namespace if fail] # E: Name "fail" is not defined + self.o = Enum("o", names) # E: Enum type as attribute is not supported \ + # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 3ac669eb93a3..13f977a1e463 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1519,3 +1519,14 @@ class C(T): c: Union[C, Any] reveal_type(c.f()) # N: Revealed type is "Union[builtins.bool, Any]" [builtins fixtures/tuple.pyi] + +[case testNamedTupleAsClassMemberNoCrash] +# https://github.com/python/mypy/issues/18736 +from collections import namedtuple + +class Base: + def __init__(self, namespace: tuple[str, ...]) -> None: + # Not a bug: trigger defer + names = [name for name in namespace if fail] # E: Name "fail" is not defined + self.n = namedtuple("n", names) # E: NamedTuple type as an attribute is not supported +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c5ebed57bbcd..48bfa4bdba49 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -4138,3 +4138,16 @@ Derived.Params(name="Robert") DerivedOverride.Params(name="Robert") [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testEnumAsClassMemberNoCrash] +# https://github.com/python/mypy/issues/18736 +from typing import TypedDict + +class Base: + def __init__(self, namespace: dict[str, str]) -> None: + # Not a bug: trigger defer + names = {n: n for n in namespace if fail} # E: Name "fail" is not defined + self.d = TypedDict("d", names) # E: TypedDict type as attribute is not supported \ + # E: TypedDict() expects a dictionary literal as the second argument +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi]