Repository: python/mypy

Issue:
New semantic analyzer: remove redundant pass 3 / patching code
Much of the code of pass 3 is not required, but there may be checks that should be preserved. Some of the checks can probably be moved to the main pass.


Discussion:

Resolving patch:
diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py
--- a/mypy/newsemanal/semanal.py
+++ b/mypy/newsemanal/semanal.py
@@ -1764,8 +1764,7 @@ def type_analyzer(self, *,
                       allow_tuple_literal: bool = False,
                       allow_unbound_tvars: bool = False,
                       allow_placeholder: bool = False,
-                      report_invalid_types: bool = True,
-                      third_pass: bool = False) -> TypeAnalyser:
+                      report_invalid_types: bool = True) -> TypeAnalyser:
         if tvar_scope is None:
             tvar_scope = self.tvar_scope
         tpan = TypeAnalyser(self,
@@ -1777,8 +1776,7 @@ def type_analyzer(self, *,
                             allow_tuple_literal=allow_tuple_literal,
                             report_invalid_types=report_invalid_types,
                             allow_unnormalized=self.is_stub_file,
-                            allow_placeholder=allow_placeholder,
-                            third_pass=third_pass)
+                            allow_placeholder=allow_placeholder)
         tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic())
         tpan.global_scope = not self.type and not self.function_stack
         return tpan
@@ -1800,8 +1798,7 @@ def anal_type(self, t: Type, *,
                                allow_unbound_tvars=allow_unbound_tvars,
                                allow_tuple_literal=allow_tuple_literal,
                                allow_placeholder=allow_placeholder,
-                               report_invalid_types=report_invalid_types,
-                               third_pass=third_pass)
+                               report_invalid_types=report_invalid_types)
         tag = self.track_incomplete_refs()
         typ = t.accept(a)
         if self.found_incomplete_ref(tag):
diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py
--- a/mypy/newsemanal/semanal_shared.py
+++ b/mypy/newsemanal/semanal_shared.py
@@ -106,8 +106,7 @@ def anal_type(self, t: Type, *,
                   tvar_scope: Optional[TypeVarScope] = None,
                   allow_tuple_literal: bool = False,
                   allow_unbound_tvars: bool = False,
-                  report_invalid_types: bool = True,
-                  third_pass: bool = False) -> Optional[Type]:
+                  report_invalid_types: bool = True) -> Optional[Type]:
         raise NotImplementedError
 
     @abstractmethod
diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py
--- a/mypy/newsemanal/typeanal.py
+++ b/mypy/newsemanal/typeanal.py
@@ -110,7 +110,7 @@ class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface):
 
     def __init__(self,
                  api: SemanticAnalyzerCoreInterface,
-                 tvar_scope: Optional[TypeVarScope],
+                 tvar_scope: TypeVarScope,
                  plugin: Plugin,
                  options: Options,
                  is_typeshed_stub: bool, *,
@@ -119,8 +119,7 @@ def __init__(self,
                  allow_unnormalized: bool = False,
                  allow_unbound_tvars: bool = False,
                  allow_placeholder: bool = False,
-                 report_invalid_types: bool = True,
-                 third_pass: bool = False) -> None:
+                 report_invalid_types: bool = True) -> None:
         self.api = api
         self.lookup_qualified = api.lookup_qualified
         self.lookup_fqn_func = api.lookup_fully_qualified
@@ -147,7 +146,6 @@ def __init__(self,
         self.plugin = plugin
         self.options = options
         self.is_typeshed_stub = is_typeshed_stub
-        self.third_pass = third_pass
         # Names of type aliases encountered while analysing a type will be collected here.
         self.aliases_used = set()  # type: Set[str]
 
@@ -160,7 +158,7 @@ def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) ->
         return typ
 
     def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type:
-        sym = self.lookup_qualified(t.name, t, suppress_errors=self.third_pass)
+        sym = self.lookup_qualified(t.name, t)
         if sym is not None:
             node = sym.node
             if isinstance(node, PlaceholderNode):
@@ -187,10 +185,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
                     not self.allow_unnormalized):
                 self.fail(no_subscript_builtin_alias(fullname,
                                                      propose_alt=not self.defining_alias), t)
-            if self.tvar_scope is not None:
-                tvar_def = self.tvar_scope.get_binding(sym)
-            else:
-                tvar_def = None
+            tvar_def = self.tvar_scope.get_binding(sym)
             if isinstance(sym.node, TypeVarExpr) and tvar_def is not None and self.defining_alias:
                 self.fail('Can\'t use bound type variable "{}"'
                           ' to define generic alias'.format(t.name), t)
@@ -219,9 +214,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
             else:
                 return self.analyze_unbound_type_without_type_info(t, sym, defining_literal)
         else:  # sym is None
-            if self.third_pass:
-                self.fail('Invalid type "{}"'.format(t.name), t)
-                return AnyType(TypeOfAny.from_error)
             return AnyType(TypeOfAny.special_form)
 
     def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Optional[Type]:
@@ -370,8 +362,8 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
         # Unbound type variable. Currently these may be still valid,
         # for example when defining a generic type alias.
         unbound_tvar = (isinstance(sym.node, TypeVarExpr) and
-                        (not self.tvar_scope or self.tvar_scope.get_binding(sym) is None))
-        if self.allow_unbound_tvars and unbound_tvar and not self.third_pass:
+                        self.tvar_scope.get_binding(sym) is None)
+        if self.allow_unbound_tvars and unbound_tvar:
             return t
 
         # Option 3:
@@ -401,9 +393,6 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
         # None of the above options worked, we give up.
         self.fail('Invalid type "{}"'.format(name), t)
 
-        if self.third_pass and isinstance(sym.node, TypeVarExpr):
-            self.note_func("Forward references to type variables are prohibited", t)
-            return AnyType(TypeOfAny.from_error)
         # TODO: Would it be better to always return Any instead of UnboundType
         # in case of an error? On one hand, UnboundType has a name so error messages
         # are more detailed, on the other hand, some of them may be bogus.
@@ -726,18 +715,13 @@ def fail(self, msg: str, ctx: Context) -> None:
     @contextmanager
     def tvar_scope_frame(self) -> Iterator[None]:
         old_scope = self.tvar_scope
-        if self.tvar_scope:
-            self.tvar_scope = self.tvar_scope.method_frame()
-        else:
-            assert self.third_pass, "Internal error: type variable scope not given"
+        self.tvar_scope = self.tvar_scope.method_frame()
         yield
         self.tvar_scope = old_scope
 
     def infer_type_variables(self,
                              type: CallableType) -> List[Tuple[str, TypeVarExpr]]:
         """Return list of unique type variables referred to in a callable."""
-        if not self.tvar_scope:
-            return []  # We are in third pass, nothing new here
         names = []  # type: List[str]
         tvars = []  # type: List[TypeVarExpr]
         for arg in type.arg_types:
@@ -761,8 +745,6 @@ def infer_type_variables(self,
     def bind_function_type_variables(self,
                                      fun_type: CallableType, defn: Context) -> List[TypeVarDef]:
         """Find the type variables of the function type and bind them in our tvar_scope"""
-        if not self.tvar_scope:
-            return []  # We are in third pass, nothing new here
         if fun_type.variables:
             for var in fun_type.variables:
                 var_node = self.lookup_qualified(var.name, var)
@@ -787,8 +769,6 @@ def bind_function_type_variables(self,
         return defs
 
     def is_defined_type_var(self, tvar: str, context: Context) -> bool:
-        if self.tvar_scope is None:
-            return False
         tvar_node = self.lookup_qualified(tvar, context)
         if not tvar_node:
             return False


Test patch: