Skip to content

Fix crashes in synthetic types #3322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 24, 2017
3 changes: 2 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@ class ClassDef(Statement):
info = None # type: TypeInfo # Related TypeInfo
metaclass = '' # type: Optional[str]
decorators = None # type: List[Expression]
analyzed = None # type: Optional[Expression]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need an update to serialize() (perhaps in the comment)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to serialize this (since it is needed only for semantic analysis). I updated the comment.

has_incompatible_baseclass = False

def __init__(self,
Expand All @@ -753,7 +754,7 @@ def is_generic(self) -> bool:
return self.info.is_generic()

def serialize(self) -> JsonDict:
# Not serialized: defs, base_type_exprs, decorators
# Not serialized: defs, base_type_exprs, decorators, analyzed (for named tuples etc.)
return {'.class': 'ClassDef',
'name': self.name,
'fullname': self.fullname,
Expand Down
23 changes: 20 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]:
defn.name, items, types, default_items)
node.node = info
defn.info = info
defn.analyzed = NamedTupleExpr(info)
return info
return None

Expand Down Expand Up @@ -1178,7 +1179,9 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict'):
# Building a new TypedDict
fields, types = self.check_typeddict_classdef(defn)
node.node = self.build_typeddict_typeinfo(defn.name, fields, types)
info = self.build_typeddict_typeinfo(defn.name, fields, types)
node.node = info
defn.analyzed = TypedDictExpr(info)
return True
# Extending/merging existing TypedDicts
if any(not isinstance(expr, RefExpr) or
Expand All @@ -1205,7 +1208,9 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
fields, types = self.check_typeddict_classdef(defn, newfields)
newfields.extend(fields)
newtypes.extend(types)
node.node = self.build_typeddict_typeinfo(defn.name, newfields, newtypes)
info = self.build_typeddict_typeinfo(defn.name, newfields, newtypes)
node.node = info
defn.analyzed = TypedDictExpr(info)
return True
return False

Expand Down Expand Up @@ -3686,6 +3691,11 @@ def visit_class_def(self, tdef: ClassDef) -> None:
if tdef.info.mro:
tdef.info.mro = [] # Force recomputation
calculate_class_mro(tdef, self.fail_blocker)
if tdef.analyzed is not None:
if isinstance(tdef.analyzed, TypedDictExpr):
self.analyze(tdef.analyzed.info.typeddict_type)
elif isinstance(tdef.analyzed, NamedTupleExpr):
self.analyze(tdef.analyzed.info.tuple_type)
super().visit_class_def(tdef)

def visit_decorator(self, dec: Decorator) -> None:
Expand Down Expand Up @@ -3742,6 +3752,13 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
self.analyze(s.type)
if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr):
self.analyze(s.rvalue.analyzed.type)
if isinstance(s.rvalue, CallExpr):
if isinstance(s.rvalue.analyzed, NewTypeExpr):
self.analyze(s.rvalue.analyzed.old_type)
if isinstance(s.rvalue.analyzed, TypedDictExpr):
self.analyze(s.rvalue.analyzed.info.typeddict_type)
if isinstance(s.rvalue.analyzed, NamedTupleExpr):
self.analyze(s.rvalue.analyzed.info.tuple_type)
super().visit_assignment_stmt(s)

def visit_cast_expr(self, e: CastExpr) -> None:
Expand All @@ -3758,7 +3775,7 @@ def visit_type_application(self, e: TypeApplication) -> None:

# Helpers

def analyze(self, type: Type) -> None:
def analyze(self, type: Optional[Type]) -> None:
if type:
analyzer = TypeAnalyserPass3(self.fail)
type.accept(analyzer)
Expand Down
3 changes: 3 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,9 @@ def visit_instance(self, t: Instance) -> None:
arg, info.name(), tvar.upper_bound), t)
for arg in t.args:
arg.accept(self)
if info.is_newtype:
for base in info.bases:
base.accept(self)

def check_type_var_values(self, type: TypeInfo, actuals: List[Type],
valids: List[Type], arg_number: int, context: Context) -> None:
Expand Down
87 changes: 87 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -3137,6 +3137,93 @@ class M(type):
class A(metaclass=M): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'

-- Synthetic types crashes
-- -----------------------

[case testCrashInvalidArgsSyntheticClassSyntax]
from typing import List, NamedTuple
from mypy_extensions import TypedDict
class TD(TypedDict):
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
class NM(NamedTuple):
x: List[int, str] # E: "list" expects 1 type argument, but 2 given

# These two should never crash, reveals are in the next test
TD({'x': []})
NM(x=[])
[builtins fixtures/dict.pyi]
[out]

[case testCrashInvalidArgsSyntheticClassSyntaxReveals]
from typing import List, NamedTuple
from mypy_extensions import TypedDict
class TD(TypedDict):
x: List[int, str] # E: "list" expects 1 type argument, but 2 given
class NM(NamedTuple):
x: List[int, str] # E: "list" expects 1 type argument, but 2 given

x: TD
x1 = TD({'x': []})
y: NM
y1 = NM(x=[])
reveal_type(x) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=__main__.TD)'
reveal_type(x1) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=typing.Mapping[builtins.str, builtins.list[Any]])'
reveal_type(y) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reveal for y1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it.

reveal_type(y1) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
[builtins fixtures/dict.pyi]
[out]

[case testCrashInvalidArgsSyntheticFunctionSyntax]
from typing import List, NewType, NamedTuple
from mypy_extensions import TypedDict
TD = TypedDict('TD', {'x': List[int, str]}) # E: "list" expects 1 type argument, but 2 given
NM = NamedTuple('NM', [('x', List[int, str])]) # E: "list" expects 1 type argument, but 2 given
NT = NewType('NT', List[int, str]) # E: "list" expects 1 type argument, but 2 given

# These three should not crash
TD({'x': []})
NM(x=[])
NT([])
[builtins fixtures/dict.pyi]
[out]

-- The two tests below will not crash after
-- https://github.com/python/mypy/issues/3319 is fixed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remember to remove the -skip then.

[case testCrashForwardSyntheticClassSyntax-skip]
from typing import NamedTuple
from mypy_extensions import TypedDict
class A1(NamedTuple):
b: 'B'
x: int
class A2(TypedDict):
b: 'B'
x: int
class B:
pass
x: A1
y: A2
reveal_type(x.b) # E: Revealed type is '__main__.B'
reveal_type(y['b']) # E: Revealed type is '__main__.B'
[builtins fixtures/dict.pyi]
[out]

[case testCrashForwardSyntheticFunctionSyntax-skip]
from typing import NamedTuple
from mypy_extensions import TypedDict
A1 = NamedTuple('A1', [('b', 'B'), ('x', int)])
A2 = TypedDict('A2', {'b': 'B', 'x': int})
class B:
pass
x: A1
y: A2
reveal_type(x.b) # E: Revealed type is '__main__.B'
reveal_type(y['b']) # E: Revealed type is '__main__.B'
[builtins fixtures/dict.pyi]
[out]

-- Special support for six
-- -----------------------

[case testSixWithMetaclass]
import six
class M(type):
Expand Down