diff --git a/mypy/semanal.py b/mypy/semanal.py index f4702635fb9d..0ca583eadc03 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1396,7 +1396,7 @@ def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) -> # incremental mode and we should avoid it. In general, this logic is too # ad-hoc and needs to be removed/refactored. if '@' not in defn.info._fullname: - local_name = defn.info._fullname + '@' + str(defn.line) + local_name = defn.info.name + '@' + str(defn.line) if defn.info.is_named_tuple: # Module is already correctly set in _fullname for named tuples. defn.info._fullname += '@' + str(defn.line) @@ -1404,7 +1404,7 @@ def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) -> defn.info._fullname = self.cur_mod_id + '.' + local_name else: # Preserve name from previous fine-grained incremental run. - local_name = defn.info._fullname + local_name = defn.info.name defn.fullname = defn.info._fullname self.globals[local_name] = SymbolTableNode(GDEF, defn.info) @@ -3140,7 +3140,11 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: self.add_symbol(name, call.analyzed, s) return True - def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo: + def basic_new_typeinfo(self, name: str, + basetype_or_fallback: Instance, + line: int) -> TypeInfo: + if self.is_func_scope() and not self.type and '@' not in name: + name += '@' + str(line) class_def = ClassDef(name, Block([])) if self.is_func_scope() and not self.type: # Full names of generated classes should always be prefixed with the module names diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index eabd2bcdadea..3bf84217121a 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -67,13 +67,13 @@ class A(enum.Enum): items, values, ok = self.parse_enum_call_args(call, fullname.split('.')[-1]) if not ok: # Error. Construct dummy return value. - info = self.build_enum_call_typeinfo(var_name, [], fullname) + info = self.build_enum_call_typeinfo(var_name, [], fullname, node.line) else: name = cast(Union[StrExpr, UnicodeExpr], call.args[0]).value if name != var_name or is_func_scope: # Give it a unique name derived from the line number. name += '@' + str(call.line) - info = self.build_enum_call_typeinfo(name, items, fullname) + info = self.build_enum_call_typeinfo(name, items, fullname, call.line) # Store generated TypeInfo under both names, see semanal_namedtuple for more details. if name != var_name or is_func_scope: self.api.add_symbol_skip_local(name, info) @@ -82,10 +82,11 @@ class A(enum.Enum): info.line = node.line return info - def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str) -> TypeInfo: + def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str, + line: int) -> TypeInfo: base = self.api.named_type_or_none(fullname) assert base is not None - info = self.api.basic_new_typeinfo(name, base) + info = self.api.basic_new_typeinfo(name, base, line) info.metaclass_type = info.calculate_metaclass_type() info.is_enum = True for item in items: diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 0067fba22322..4adeb56273c6 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -382,7 +382,7 @@ def build_namedtuple_typeinfo(self, iterable_type = self.api.named_type_or_none('typing.Iterable', [implicit_any]) function_type = self.api.named_type('__builtins__.function') - info = self.api.basic_new_typeinfo(name, fallback) + info = self.api.basic_new_typeinfo(name, fallback, line) info.is_named_tuple = True tuple_base = TupleType(types, fallback) info.tuple_type = tuple_base diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index 92372e5ebb82..7af348dd281b 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -72,12 +72,12 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: # Create the corresponding class definition if the aliased type is subtypeable if isinstance(old_type, TupleType): newtype_class_info = self.build_newtype_typeinfo(name, old_type, - old_type.partial_fallback) + old_type.partial_fallback, s.line) newtype_class_info.tuple_type = old_type elif isinstance(old_type, Instance): if old_type.type.is_protocol: self.fail("NewType cannot be used with protocol classes", s) - newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type) + newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type, s.line) else: if old_type is not None: message = "Argument 2 to NewType(...) must be subclassable (got {})" @@ -85,7 +85,7 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: # Otherwise the error was already reported. old_type = AnyType(TypeOfAny.from_error) object_type = self.api.named_type('__builtins__.object') - newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type) + newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type, s.line) newtype_class_info.fallback_to_any = True check_for_explicit_any(old_type, self.options, self.api.is_typeshed_stub_file, self.msg, @@ -181,8 +181,9 @@ def check_newtype_args(self, name: str, call: CallExpr, return None if has_failed else old_type, should_defer - def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) -> TypeInfo: - info = self.api.basic_new_typeinfo(name, base_type) + def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance, + line: int) -> TypeInfo: + info = self.api.basic_new_typeinfo(name, base_type, line) info.is_newtype = True # Add __init__ method diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 87a5d28b4c2c..60bcdb2cb928 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -122,7 +122,7 @@ def anal_type(self, t: Type, *, raise NotImplementedError @abstractmethod - def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo: + def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo: raise NotImplementedError @abstractmethod diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 36eb30620582..0b0891d83fb6 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -60,7 +60,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ fields, types, required_keys = self.analyze_typeddict_classdef_fields(defn) if fields is None: return True, None # Defer - info = self.build_typeddict_typeinfo(defn.name, fields, types, required_keys) + info = self.build_typeddict_typeinfo(defn.name, fields, types, required_keys, + defn.line) defn.analyzed = TypedDictExpr(info) defn.analyzed.line = defn.line defn.analyzed.column = defn.column @@ -97,7 +98,7 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ keys.extend(new_keys) types.extend(new_types) required_keys.update(new_required_keys) - info = self.build_typeddict_typeinfo(defn.name, keys, types, required_keys) + info = self.build_typeddict_typeinfo(defn.name, keys, types, required_keys, defn.line) defn.analyzed = TypedDictExpr(info) defn.analyzed.line = defn.line defn.analyzed.column = defn.column @@ -196,7 +197,7 @@ def check_typeddict(self, name, items, types, total, ok = res if not ok: # Error. Construct dummy return value. - info = self.build_typeddict_typeinfo('TypedDict', [], [], set()) + info = self.build_typeddict_typeinfo('TypedDict', [], [], set(), call.line) else: if var_name is not None and name != var_name: self.fail( @@ -206,7 +207,7 @@ def check_typeddict(self, # Give it a unique name derived from the line number. name += '@' + str(call.line) required_keys = set(items) if total else set() - info = self.build_typeddict_typeinfo(name, items, types, required_keys) + info = self.build_typeddict_typeinfo(name, items, types, required_keys, call.line) info.line = node.line # Store generated TypeInfo under both names, see semanal_namedtuple for more details. if name != var_name or is_func_scope: @@ -305,13 +306,14 @@ def fail_typeddict_arg(self, message: str, def build_typeddict_typeinfo(self, name: str, items: List[str], types: List[Type], - required_keys: Set[str]) -> TypeInfo: + required_keys: Set[str], + line: int) -> TypeInfo: # Prefer typing then typing_extensions if available. fallback = (self.api.named_type_or_none('typing._TypedDict', []) or self.api.named_type_or_none('typing_extensions._TypedDict', []) or self.api.named_type_or_none('mypy_extensions._TypedDict', [])) assert fallback is not None - info = self.api.basic_new_typeinfo(name, fallback) + info = self.api.basic_new_typeinfo(name, fallback, line) info.typeddict_type = TypedDictType(OrderedDict(zip(items, types)), required_keys, fallback) return info diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 4106d6de91a3..372c3e587991 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5506,3 +5506,29 @@ class Foo: [delete c1.py.2] [file c2.py.2] class C: pass + +[case testIncrementalNestedNamedTuple] +# flags: --python-version 3.6 +import a + +[file a.py] +import b + +[file a.py.2] +import b # foo + +[file b.py] +from typing import NamedTuple + +def f() -> None: + class NT(NamedTuple): + x: int + + n: NT = NT(x=2) + +def g() -> None: + NT = NamedTuple('NT', [('y', str)]) + + n: NT = NT(y='x') + +[builtins fixtures/tuple.pyi]