Skip to content

Commit a27c60c

Browse files
committed
Fixes nested NamedTuple in incremental mode
Fixes #7281.
1 parent 4a5e311 commit a27c60c

7 files changed

+54
-20
lines changed

mypy/semanal.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1396,15 +1396,15 @@ def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) ->
13961396
# incremental mode and we should avoid it. In general, this logic is too
13971397
# ad-hoc and needs to be removed/refactored.
13981398
if '@' not in defn.info._fullname:
1399-
local_name = defn.info._fullname + '@' + str(defn.line)
1399+
local_name = defn.info.name + '@' + str(defn.line)
14001400
if defn.info.is_named_tuple:
14011401
# Module is already correctly set in _fullname for named tuples.
14021402
defn.info._fullname += '@' + str(defn.line)
14031403
else:
14041404
defn.info._fullname = self.cur_mod_id + '.' + local_name
14051405
else:
14061406
# Preserve name from previous fine-grained incremental run.
1407-
local_name = defn.info._fullname
1407+
local_name = defn.info.name
14081408
defn.fullname = defn.info._fullname
14091409
self.globals[local_name] = SymbolTableNode(GDEF, defn.info)
14101410

@@ -3140,7 +3140,11 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
31403140
self.add_symbol(name, call.analyzed, s)
31413141
return True
31423142

3143-
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo:
3143+
def basic_new_typeinfo(self, name: str,
3144+
basetype_or_fallback: Instance,
3145+
line: int) -> TypeInfo:
3146+
if self.is_func_scope() and not self.type and '@' not in name:
3147+
name += '@' + str(line)
31443148
class_def = ClassDef(name, Block([]))
31453149
if self.is_func_scope() and not self.type:
31463150
# Full names of generated classes should always be prefixed with the module names

mypy/semanal_enum.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ class A(enum.Enum):
6767
items, values, ok = self.parse_enum_call_args(call, fullname.split('.')[-1])
6868
if not ok:
6969
# Error. Construct dummy return value.
70-
info = self.build_enum_call_typeinfo(var_name, [], fullname)
70+
info = self.build_enum_call_typeinfo(var_name, [], fullname, node.line)
7171
else:
7272
name = cast(Union[StrExpr, UnicodeExpr], call.args[0]).value
7373
if name != var_name or is_func_scope:
7474
# Give it a unique name derived from the line number.
7575
name += '@' + str(call.line)
76-
info = self.build_enum_call_typeinfo(name, items, fullname)
76+
info = self.build_enum_call_typeinfo(name, items, fullname, call.line)
7777
# Store generated TypeInfo under both names, see semanal_namedtuple for more details.
7878
if name != var_name or is_func_scope:
7979
self.api.add_symbol_skip_local(name, info)
@@ -82,10 +82,11 @@ class A(enum.Enum):
8282
info.line = node.line
8383
return info
8484

85-
def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str) -> TypeInfo:
85+
def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str,
86+
line: int) -> TypeInfo:
8687
base = self.api.named_type_or_none(fullname)
8788
assert base is not None
88-
info = self.api.basic_new_typeinfo(name, base)
89+
info = self.api.basic_new_typeinfo(name, base, line)
8990
info.metaclass_type = info.calculate_metaclass_type()
9091
info.is_enum = True
9192
for item in items:

mypy/semanal_namedtuple.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def build_namedtuple_typeinfo(self,
382382
iterable_type = self.api.named_type_or_none('typing.Iterable', [implicit_any])
383383
function_type = self.api.named_type('__builtins__.function')
384384

385-
info = self.api.basic_new_typeinfo(name, fallback)
385+
info = self.api.basic_new_typeinfo(name, fallback, line)
386386
info.is_named_tuple = True
387387
tuple_base = TupleType(types, fallback)
388388
info.tuple_type = tuple_base

mypy/semanal_newtype.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,20 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
7272
# Create the corresponding class definition if the aliased type is subtypeable
7373
if isinstance(old_type, TupleType):
7474
newtype_class_info = self.build_newtype_typeinfo(name, old_type,
75-
old_type.partial_fallback)
75+
old_type.partial_fallback, s.line)
7676
newtype_class_info.tuple_type = old_type
7777
elif isinstance(old_type, Instance):
7878
if old_type.type.is_protocol:
7979
self.fail("NewType cannot be used with protocol classes", s)
80-
newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type)
80+
newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type, s.line)
8181
else:
8282
if old_type is not None:
8383
message = "Argument 2 to NewType(...) must be subclassable (got {})"
8484
self.fail(message.format(format_type(old_type)), s, code=codes.VALID_NEWTYPE)
8585
# Otherwise the error was already reported.
8686
old_type = AnyType(TypeOfAny.from_error)
8787
object_type = self.api.named_type('__builtins__.object')
88-
newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type)
88+
newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type, s.line)
8989
newtype_class_info.fallback_to_any = True
9090

9191
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,
181181

182182
return None if has_failed else old_type, should_defer
183183

184-
def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) -> TypeInfo:
185-
info = self.api.basic_new_typeinfo(name, base_type)
184+
def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance,
185+
line: int) -> TypeInfo:
186+
info = self.api.basic_new_typeinfo(name, base_type, line)
186187
info.is_newtype = True
187188

188189
# Add __init__ method

mypy/semanal_shared.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def anal_type(self, t: Type, *,
122122
raise NotImplementedError
123123

124124
@abstractmethod
125-
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo:
125+
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
126126
raise NotImplementedError
127127

128128
@abstractmethod

mypy/semanal_typeddict.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ
6060
fields, types, required_keys = self.analyze_typeddict_classdef_fields(defn)
6161
if fields is None:
6262
return True, None # Defer
63-
info = self.build_typeddict_typeinfo(defn.name, fields, types, required_keys)
63+
info = self.build_typeddict_typeinfo(defn.name, fields, types, required_keys,
64+
defn.line)
6465
defn.analyzed = TypedDictExpr(info)
6566
defn.analyzed.line = defn.line
6667
defn.analyzed.column = defn.column
@@ -97,7 +98,7 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ
9798
keys.extend(new_keys)
9899
types.extend(new_types)
99100
required_keys.update(new_required_keys)
100-
info = self.build_typeddict_typeinfo(defn.name, keys, types, required_keys)
101+
info = self.build_typeddict_typeinfo(defn.name, keys, types, required_keys, defn.line)
101102
defn.analyzed = TypedDictExpr(info)
102103
defn.analyzed.line = defn.line
103104
defn.analyzed.column = defn.column
@@ -196,7 +197,7 @@ def check_typeddict(self,
196197
name, items, types, total, ok = res
197198
if not ok:
198199
# Error. Construct dummy return value.
199-
info = self.build_typeddict_typeinfo('TypedDict', [], [], set())
200+
info = self.build_typeddict_typeinfo('TypedDict', [], [], set(), call.line)
200201
else:
201202
if var_name is not None and name != var_name:
202203
self.fail(
@@ -206,7 +207,7 @@ def check_typeddict(self,
206207
# Give it a unique name derived from the line number.
207208
name += '@' + str(call.line)
208209
required_keys = set(items) if total else set()
209-
info = self.build_typeddict_typeinfo(name, items, types, required_keys)
210+
info = self.build_typeddict_typeinfo(name, items, types, required_keys, call.line)
210211
info.line = node.line
211212
# Store generated TypeInfo under both names, see semanal_namedtuple for more details.
212213
if name != var_name or is_func_scope:
@@ -305,13 +306,14 @@ def fail_typeddict_arg(self, message: str,
305306

306307
def build_typeddict_typeinfo(self, name: str, items: List[str],
307308
types: List[Type],
308-
required_keys: Set[str]) -> TypeInfo:
309+
required_keys: Set[str],
310+
line: int) -> TypeInfo:
309311
# Prefer typing then typing_extensions if available.
310312
fallback = (self.api.named_type_or_none('typing._TypedDict', []) or
311313
self.api.named_type_or_none('typing_extensions._TypedDict', []) or
312314
self.api.named_type_or_none('mypy_extensions._TypedDict', []))
313315
assert fallback is not None
314-
info = self.api.basic_new_typeinfo(name, fallback)
316+
info = self.api.basic_new_typeinfo(name, fallback, line)
315317
info.typeddict_type = TypedDictType(OrderedDict(zip(items, types)), required_keys,
316318
fallback)
317319
return info

test-data/unit/check-incremental.test

+26
Original file line numberDiff line numberDiff line change
@@ -5506,3 +5506,29 @@ class Foo:
55065506
[delete c1.py.2]
55075507
[file c2.py.2]
55085508
class C: pass
5509+
5510+
[case testIncrementalNestedNamedTuple]
5511+
# flags: --python-version 3.6
5512+
import a
5513+
5514+
[file a.py]
5515+
import b
5516+
5517+
[file a.py.2]
5518+
import b # foo
5519+
5520+
[file b.py]
5521+
from typing import NamedTuple
5522+
5523+
def f() -> None:
5524+
class NT(NamedTuple):
5525+
x: int
5526+
5527+
n: NT = NT(x=2)
5528+
5529+
def g() -> None:
5530+
NT = NamedTuple('NT', [('y', str)])
5531+
5532+
n: NT = NT(y='x')
5533+
5534+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)