diff --git a/mypy/semanal.py b/mypy/semanal.py index f8cd97373cff..b4cae218f64e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -671,6 +671,7 @@ def analyze_class_body(self, defn: ClassDef) -> Iterator[bool]: if self.analyze_typeddict_classdef(defn): yield False return + self.setup_class_def_analysis(defn) named_tuple_info = self.analyze_namedtuple_classdef(defn) if named_tuple_info is not None: # Temporarily clear the names dict so we don't get errors about duplicate names @@ -704,7 +705,6 @@ def analyze_class_body(self, defn: ClassDef) -> Iterator[bool]: if key not in named_tuple_info.names or key != '__doc__' }) else: - self.setup_class_def_analysis(defn) self.analyze_base_classes(defn) self.analyze_metaclass(defn) defn.info.is_protocol = is_protocol @@ -956,6 +956,10 @@ def get_all_bases_tvars(self, defn: ClassDef, removed: List[int]) -> TypeVarList tvars.extend(base_tvars) return remove_dups(tvars) + def is_namedtuple_classdef(self, defn: ClassDef) -> bool: + base_exprs = defn.base_type_exprs + return any(getattr(b, 'fullname', None) == 'typing.NamedTuple' for b in base_exprs) + def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]: # special case for NamedTuple for base_expr in defn.base_type_exprs: @@ -964,10 +968,14 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]: if base_expr.fullname == 'typing.NamedTuple': node = self.lookup(defn.name, defn) if node is not None: + if self.type or self.is_func_scope(): + name = defn.name + '@' + str(defn.line) + else: + name = defn.name node.kind = GDEF # TODO in process_namedtuple_definition also applies here items, types, default_items = self.check_namedtuple_classdef(defn) info = self.build_namedtuple_typeinfo( - defn.name, items, types, default_items) + name, items, types, default_items) node.node = info defn.info.replaced = info defn.info = info @@ -1046,7 +1054,11 @@ def setup_class_def_analysis(self, defn: ClassDef) -> None: local_name = defn.info._fullname + '@' + str(defn.line) defn.info._fullname = self.cur_mod_id + '.' + local_name defn.fullname = defn.info._fullname - self.globals[local_name] = node + if self.type and self.is_namedtuple_classdef(defn): + # Special case for NamedTuple. + self.type.names[local_name] = node + else: + self.globals[local_name] = node def analyze_base_classes(self, defn: ClassDef) -> None: """Analyze and set up base classes. diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index fd35ee73d094..c6c8435674cd 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -667,3 +667,11 @@ class HasStaticMethod(NamedTuple): return 4 [builtins fixtures/property.pyi] + +[case testNamedTupleLocalScope] +from typing import NamedTuple, Any, Tuple + +def function() -> Tuple: + class InnerNamedTuple(NamedTuple): + x: int + return InnerNamedTuple(x=0) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 6df0d81a686b..01a6596d3fb3 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1925,6 +1925,21 @@ tmp/crash.py:17: error: Revealed type is 'crash.B@13[builtins.int*]' main:2: error: Revealed type is 'crash.A@5' main:3: error: Revealed type is 'crash.B@13[builtins.int*]' +[case testIncrementalSubclassNamedTuple] +from a import C +reveal_type(C().a) +[file a.py] +from typing import NamedTuple +class C: + def __init__(self) -> None: + class A(NamedTuple): + x: int + self.a = A(1) +[out1] +main:2: error: Revealed type is 'Tuple[builtins.int, fallback=a.C.A@4]' +[out2] +main:2: error: Revealed type is 'Tuple[builtins.int, fallback=a.C.A@4]' + [case testGenericMethodRestoreMetaLevel] from typing import Dict diff --git a/test-data/unit/semanal-namedtuple.test b/test-data/unit/semanal-namedtuple.test index a820a07fe745..8e8cc0ae319d 100644 --- a/test-data/unit/semanal-namedtuple.test +++ b/test-data/unit/semanal-namedtuple.test @@ -132,6 +132,48 @@ MypyFile:1( __main__.N@2) PassStmt:2())) +[case testNamedTupleDirectSubclass] +from typing import NamedTuple +class A(NamedTuple): + x: int + +[out] +MypyFile:1( + ImportFrom:1(typing, [NamedTuple]) + ClassDef:2( + A + TupleType( + Tuple[builtins.int]) + BaseType( + builtins.tuple[builtins.int]) + AssignmentStmt:3( + NameExpr(x [m]) + TempNode:-1( + Any) + builtins.int))) + +[case testLocalNamedTupleDirectSubclass] +from typing import NamedTuple +def foo(): + class A(NamedTuple): + x: int +[out] +MypyFile:1( + ImportFrom:1(typing, [NamedTuple]) + FuncDef:2( + foo + Block:2( + ClassDef:3( + A + TupleType( + Tuple[builtins.int]) + BaseType( + builtins.tuple[builtins.int]) + AssignmentStmt:4( + NameExpr(x [m]) + TempNode:-1( + Any) + builtins.int))))) -- Errors [case testNamedTupleWithTooFewArguments]