Skip to content

Commit ccd95e6

Browse files
ilevkivskyigvanrossum
authored andcommitted
Fix crashes in synthetic types (#3322)
Fixes #3308 This PR adds better processing of "synthetic" types (``NewType``, ``NamedTuple``, ``TypedDict``) to the third pass and moves some processing from the second to the third pass.
1 parent 7940d76 commit ccd95e6

File tree

4 files changed

+112
-4
lines changed

4 files changed

+112
-4
lines changed

mypy/nodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ class ClassDef(Statement):
731731
info = None # type: TypeInfo # Related TypeInfo
732732
metaclass = '' # type: Optional[str]
733733
decorators = None # type: List[Expression]
734+
analyzed = None # type: Optional[Expression]
734735
has_incompatible_baseclass = False
735736

736737
def __init__(self,
@@ -753,7 +754,7 @@ def is_generic(self) -> bool:
753754
return self.info.is_generic()
754755

755756
def serialize(self) -> JsonDict:
756-
# Not serialized: defs, base_type_exprs, decorators
757+
# Not serialized: defs, base_type_exprs, decorators, analyzed (for named tuples etc.)
757758
return {'.class': 'ClassDef',
758759
'name': self.name,
759760
'fullname': self.fullname,

mypy/semanal.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]:
862862
defn.name, items, types, default_items)
863863
node.node = info
864864
defn.info = info
865+
defn.analyzed = NamedTupleExpr(info)
865866
return info
866867
return None
867868

@@ -1178,7 +1179,9 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
11781179
defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict'):
11791180
# Building a new TypedDict
11801181
fields, types = self.check_typeddict_classdef(defn)
1181-
node.node = self.build_typeddict_typeinfo(defn.name, fields, types)
1182+
info = self.build_typeddict_typeinfo(defn.name, fields, types)
1183+
node.node = info
1184+
defn.analyzed = TypedDictExpr(info)
11821185
return True
11831186
# Extending/merging existing TypedDicts
11841187
if any(not isinstance(expr, RefExpr) or
@@ -1205,7 +1208,9 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
12051208
fields, types = self.check_typeddict_classdef(defn, newfields)
12061209
newfields.extend(fields)
12071210
newtypes.extend(types)
1208-
node.node = self.build_typeddict_typeinfo(defn.name, newfields, newtypes)
1211+
info = self.build_typeddict_typeinfo(defn.name, newfields, newtypes)
1212+
node.node = info
1213+
defn.analyzed = TypedDictExpr(info)
12091214
return True
12101215
return False
12111216

@@ -3681,6 +3686,11 @@ def visit_class_def(self, tdef: ClassDef) -> None:
36813686
if tdef.info.mro:
36823687
tdef.info.mro = [] # Force recomputation
36833688
calculate_class_mro(tdef, self.fail_blocker)
3689+
if tdef.analyzed is not None:
3690+
if isinstance(tdef.analyzed, TypedDictExpr):
3691+
self.analyze(tdef.analyzed.info.typeddict_type)
3692+
elif isinstance(tdef.analyzed, NamedTupleExpr):
3693+
self.analyze(tdef.analyzed.info.tuple_type)
36843694
super().visit_class_def(tdef)
36853695

36863696
def visit_decorator(self, dec: Decorator) -> None:
@@ -3737,6 +3747,13 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
37373747
self.analyze(s.type)
37383748
if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr):
37393749
self.analyze(s.rvalue.analyzed.type)
3750+
if isinstance(s.rvalue, CallExpr):
3751+
if isinstance(s.rvalue.analyzed, NewTypeExpr):
3752+
self.analyze(s.rvalue.analyzed.old_type)
3753+
if isinstance(s.rvalue.analyzed, TypedDictExpr):
3754+
self.analyze(s.rvalue.analyzed.info.typeddict_type)
3755+
if isinstance(s.rvalue.analyzed, NamedTupleExpr):
3756+
self.analyze(s.rvalue.analyzed.info.tuple_type)
37403757
super().visit_assignment_stmt(s)
37413758

37423759
def visit_cast_expr(self, e: CastExpr) -> None:
@@ -3753,7 +3770,7 @@ def visit_type_application(self, e: TypeApplication) -> None:
37533770

37543771
# Helpers
37553772

3756-
def analyze(self, type: Type) -> None:
3773+
def analyze(self, type: Optional[Type]) -> None:
37573774
if type:
37583775
analyzer = TypeAnalyserPass3(self.fail)
37593776
type.accept(analyzer)

mypy/typeanal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ def visit_instance(self, t: Instance) -> None:
610610
arg, info.name(), tvar.upper_bound), t)
611611
for arg in t.args:
612612
arg.accept(self)
613+
if info.is_newtype:
614+
for base in info.bases:
615+
base.accept(self)
613616

614617
def check_type_var_values(self, type: TypeInfo, actuals: List[Type],
615618
valids: List[Type], arg_number: int, context: Context) -> None:

test-data/unit/check-classes.test

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,93 @@ class M(type):
31373137
class A(metaclass=M): pass
31383138
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
31393139

3140+
-- Synthetic types crashes
3141+
-- -----------------------
3142+
3143+
[case testCrashInvalidArgsSyntheticClassSyntax]
3144+
from typing import List, NamedTuple
3145+
from mypy_extensions import TypedDict
3146+
class TD(TypedDict):
3147+
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
3148+
class NM(NamedTuple):
3149+
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
3150+
3151+
# These two should never crash, reveals are in the next test
3152+
TD({'x': []})
3153+
NM(x=[])
3154+
[builtins fixtures/dict.pyi]
3155+
[out]
3156+
3157+
[case testCrashInvalidArgsSyntheticClassSyntaxReveals]
3158+
from typing import List, NamedTuple
3159+
from mypy_extensions import TypedDict
3160+
class TD(TypedDict):
3161+
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
3162+
class NM(NamedTuple):
3163+
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
3164+
3165+
x: TD
3166+
x1 = TD({'x': []})
3167+
y: NM
3168+
y1 = NM(x=[])
3169+
reveal_type(x) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=__main__.TD)'
3170+
reveal_type(x1) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=typing.Mapping[builtins.str, builtins.list[Any]])'
3171+
reveal_type(y) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
3172+
reveal_type(y1) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
3173+
[builtins fixtures/dict.pyi]
3174+
[out]
3175+
3176+
[case testCrashInvalidArgsSyntheticFunctionSyntax]
3177+
from typing import List, NewType, NamedTuple
3178+
from mypy_extensions import TypedDict
3179+
TD = TypedDict('TD', {'x': List[int, str]}) # E: "list" expects 1 type argument, but 2 given
3180+
NM = NamedTuple('NM', [('x', List[int, str])]) # E: "list" expects 1 type argument, but 2 given
3181+
NT = NewType('NT', List[int, str]) # E: "list" expects 1 type argument, but 2 given
3182+
3183+
# These three should not crash
3184+
TD({'x': []})
3185+
NM(x=[])
3186+
NT([])
3187+
[builtins fixtures/dict.pyi]
3188+
[out]
3189+
3190+
-- The two tests below will not crash after
3191+
-- https://github.com/python/mypy/issues/3319 is fixed
3192+
[case testCrashForwardSyntheticClassSyntax-skip]
3193+
from typing import NamedTuple
3194+
from mypy_extensions import TypedDict
3195+
class A1(NamedTuple):
3196+
b: 'B'
3197+
x: int
3198+
class A2(TypedDict):
3199+
b: 'B'
3200+
x: int
3201+
class B:
3202+
pass
3203+
x: A1
3204+
y: A2
3205+
reveal_type(x.b) # E: Revealed type is '__main__.B'
3206+
reveal_type(y['b']) # E: Revealed type is '__main__.B'
3207+
[builtins fixtures/dict.pyi]
3208+
[out]
3209+
3210+
[case testCrashForwardSyntheticFunctionSyntax-skip]
3211+
from typing import NamedTuple
3212+
from mypy_extensions import TypedDict
3213+
A1 = NamedTuple('A1', [('b', 'B'), ('x', int)])
3214+
A2 = TypedDict('A2', {'b': 'B', 'x': int})
3215+
class B:
3216+
pass
3217+
x: A1
3218+
y: A2
3219+
reveal_type(x.b) # E: Revealed type is '__main__.B'
3220+
reveal_type(y['b']) # E: Revealed type is '__main__.B'
3221+
[builtins fixtures/dict.pyi]
3222+
[out]
3223+
3224+
-- Special support for six
3225+
-- -----------------------
3226+
31403227
[case testSixWithMetaclass]
31413228
import six
31423229
class M(type):

0 commit comments

Comments
 (0)