Repository: python/mypy

Issue:
Synthetic types are not processed in third pass
While working on #3305 I discovered that "synthetic" types are not always properly processed in third pass (also there can be additional problems with them in ``fixup.py``). This leads to crashes like these:
```python
from typing import List, NewType, NamedTuple
from mypy_extensions import TypedDict

NT = NewType('NT', List[int, str])

class NM(NamedTuple):
    x: List[int, str]

class TD(TypedDict):
    x: List[int, str]

# crashers: any of these crashes with "IndexError: list index out of range"

TD({'x': []})
NM(x=[])
NT([])
```


Discussion:

Resolving patch:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- 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
--- 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
--- 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:


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- 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):