44
44
PRIORITY_FALLBACKS ,
45
45
SemanticAnalyzerInterface ,
46
46
calculate_tuple_fallback ,
47
+ has_placeholder ,
47
48
set_callable_name ,
48
49
)
49
50
from mypy .types import (
@@ -109,8 +110,11 @@ def analyze_namedtuple_classdef(
109
110
items , types , default_items = result
110
111
if is_func_scope and "@" not in defn .name :
111
112
defn .name += "@" + str (defn .line )
113
+ existing_info = None
114
+ if isinstance (defn .analyzed , NamedTupleExpr ):
115
+ existing_info = defn .analyzed .info
112
116
info = self .build_namedtuple_typeinfo (
113
- defn .name , items , types , default_items , defn .line
117
+ defn .name , items , types , default_items , defn .line , existing_info
114
118
)
115
119
defn .info = info
116
120
defn .analyzed = NamedTupleExpr (info , is_typed = True )
@@ -164,7 +168,14 @@ def check_namedtuple_classdef(
164
168
if stmt .type is None :
165
169
types .append (AnyType (TypeOfAny .unannotated ))
166
170
else :
167
- analyzed = self .api .anal_type (stmt .type )
171
+ # We never allow recursive types at function scope. Although it is
172
+ # possible to support this for named tuples, it is still tricky, and
173
+ # it would be inconsistent with type aliases.
174
+ analyzed = self .api .anal_type (
175
+ stmt .type ,
176
+ allow_placeholder = self .options .enable_recursive_aliases
177
+ and not self .api .is_func_scope (),
178
+ )
168
179
if analyzed is None :
169
180
# Something is incomplete. We need to defer this named tuple.
170
181
return None
@@ -226,7 +237,7 @@ def check_namedtuple(
226
237
name += "@" + str (call .line )
227
238
else :
228
239
name = var_name = "namedtuple@" + str (call .line )
229
- info = self .build_namedtuple_typeinfo (name , [], [], {}, node .line )
240
+ info = self .build_namedtuple_typeinfo (name , [], [], {}, node .line , None )
230
241
self .store_namedtuple_info (info , var_name , call , is_typed )
231
242
if name != var_name or is_func_scope :
232
243
# NOTE: we skip local namespaces since they are not serialized.
@@ -262,12 +273,22 @@ def check_namedtuple(
262
273
}
263
274
else :
264
275
default_items = {}
265
- info = self .build_namedtuple_typeinfo (name , items , types , default_items , node .line )
276
+
277
+ existing_info = None
278
+ if isinstance (node .analyzed , NamedTupleExpr ):
279
+ existing_info = node .analyzed .info
280
+ info = self .build_namedtuple_typeinfo (
281
+ name , items , types , default_items , node .line , existing_info
282
+ )
283
+
266
284
# If var_name is not None (i.e. this is not a base class expression), we always
267
285
# store the generated TypeInfo under var_name in the current scope, so that
268
286
# other definitions can use it.
269
287
if var_name :
270
288
self .store_namedtuple_info (info , var_name , call , is_typed )
289
+ else :
290
+ call .analyzed = NamedTupleExpr (info , is_typed = is_typed )
291
+ call .analyzed .set_line (call )
271
292
# There are three cases where we need to store the generated TypeInfo
272
293
# second time (for the purpose of serialization):
273
294
# * If there is a name mismatch like One = NamedTuple('Other', [...])
@@ -408,7 +429,12 @@ def parse_namedtuple_fields_with_types(
408
429
except TypeTranslationError :
409
430
self .fail ("Invalid field type" , type_node )
410
431
return None
411
- analyzed = self .api .anal_type (type )
432
+ # We never allow recursive types at function scope.
433
+ analyzed = self .api .anal_type (
434
+ type ,
435
+ allow_placeholder = self .options .enable_recursive_aliases
436
+ and not self .api .is_func_scope (),
437
+ )
412
438
# Workaround #4987 and avoid introducing a bogus UnboundType
413
439
if isinstance (analyzed , UnboundType ):
414
440
analyzed = AnyType (TypeOfAny .from_error )
@@ -428,6 +454,7 @@ def build_namedtuple_typeinfo(
428
454
types : List [Type ],
429
455
default_items : Mapping [str , Expression ],
430
456
line : int ,
457
+ existing_info : Optional [TypeInfo ],
431
458
) -> TypeInfo :
432
459
strtype = self .api .named_type ("builtins.str" )
433
460
implicit_any = AnyType (TypeOfAny .special_form )
@@ -448,18 +475,23 @@ def build_namedtuple_typeinfo(
448
475
literals : List [Type ] = [LiteralType (item , strtype ) for item in items ]
449
476
match_args_type = TupleType (literals , basetuple_type )
450
477
451
- info = self .api .basic_new_typeinfo (name , fallback , line )
478
+ info = existing_info or self .api .basic_new_typeinfo (name , fallback , line )
452
479
info .is_named_tuple = True
453
480
tuple_base = TupleType (types , fallback )
454
- info .tuple_type = tuple_base
481
+ if info .tuple_alias and has_placeholder (info .tuple_alias .target ):
482
+ self .api .defer (force_progress = True )
483
+ info .update_tuple_type (tuple_base )
455
484
info .line = line
456
485
# For use by mypyc.
457
486
info .metadata ["namedtuple" ] = {"fields" : items .copy ()}
458
487
459
488
# We can't calculate the complete fallback type until after semantic
460
489
# analysis, since otherwise base classes might be incomplete. Postpone a
461
490
# callback function that patches the fallback.
462
- self .api .schedule_patch (PRIORITY_FALLBACKS , lambda : calculate_tuple_fallback (tuple_base ))
491
+ if not has_placeholder (tuple_base ):
492
+ self .api .schedule_patch (
493
+ PRIORITY_FALLBACKS , lambda : calculate_tuple_fallback (tuple_base )
494
+ )
463
495
464
496
def add_field (
465
497
var : Var , is_initialized_in_class : bool = False , is_property : bool = False
@@ -489,6 +521,7 @@ def add_field(
489
521
if self .options .python_version >= (3 , 10 ):
490
522
add_field (Var ("__match_args__" , match_args_type ), is_initialized_in_class = True )
491
523
524
+ assert info .tuple_type is not None # Set by update_tuple_type() above.
492
525
tvd = TypeVarType (
493
526
SELF_TVAR_NAME , info .fullname + "." + SELF_TVAR_NAME , - 1 , [], info .tuple_type
494
527
)
0 commit comments