diff --git a/mypy/nodes.py b/mypy/nodes.py index e662dd2dfb2f..a5cb96007750 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -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] has_incompatible_baseclass = False def __init__(self, @@ -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, diff --git a/mypy/semanal.py b/mypy/semanal.py index 024bb7960277..e79c80a9b1c5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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 @@ -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 @@ -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 @@ -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: @@ -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: @@ -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) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2a506b7a5378..3b90464c3367 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -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: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 3e9ab9924809..f86b7efffab2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -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]' +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 +[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):