Repository: python/mypy

Issue:
New semantic analyzer: support comprehensions
This is one of the blockers for mypy self-check.


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
@@ -244,7 +244,8 @@ def __init__(self,
         # analyzed in several iterations until all names are resolved. We need to save
         # the local namespaces for the top level function and all nested functions between
         # these iterations. See also semanal_main.process_top_level_function().
-        self.saved_locals = {}  # type: Dict[FuncItem, SymbolTable]
+        self.saved_locals = {} \
+            # type: Dict[Union[FuncItem, GeneratorExpr, DictionaryComprehension], SymbolTable]
         self.imports = set()
         self.type = None
         self.type_stack = []
@@ -3491,7 +3492,7 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None:
         expr.generator.accept(self)
 
     def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
-        self.enter()
+        self.enter(expr)
         self.analyze_comp_for(expr)
         expr.key.accept(self)
         expr.value.accept(self)
@@ -3499,7 +3500,7 @@ def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
         self.analyze_comp_for_2(expr)
 
     def visit_generator_expr(self, expr: GeneratorExpr) -> None:
-        self.enter()
+        self.enter(expr)
         self.analyze_comp_for(expr)
         expr.left_expr.accept(self)
         self.leave()
@@ -3851,16 +3852,9 @@ def qualified_name(self, n: str) -> str:
             base = self.cur_mod_id
         return base + '.' + n
 
-    def enter(self, function: Optional[FuncItem] = None) -> None:
-        """Enter the function scope.
-
-        The argument can be omitted for temporary scopes (like comprehensions
-        and generator expressions) that can't have incomplete definitions.
-        """
-        if function:
-            names = self.saved_locals.setdefault(function, SymbolTable())
-        else:
-            names = SymbolTable()
+    def enter(self, function: Union[FuncItem, GeneratorExpr, DictionaryComprehension]) -> None:
+        """Enter a function, generator or comprehension scope."""
+        names = self.saved_locals.setdefault(function, SymbolTable())
         self.locals.append(names)
         self.global_decls.append(set())
         self.nonlocal_decls.append(set())


Test patch:
diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test
--- a/test-data/unit/check-newsemanal.test
+++ b/test-data/unit/check-newsemanal.test
@@ -1055,6 +1055,27 @@ class Test:
     def __init__(self) -> None:
         some_module = self.a
 
+[case testNewAnalyzerListComprehension]
+from typing import List
+a: List[A]
+a = [x for x in a]
+b: List[B] = [x for x in a] # E: List comprehension has incompatible type List[A]; expected List[B]
+class A: pass
+class B: pass
+[builtins fixtures/for.pyi]
+
+[case testNewAnalyzerDictionaryComprehension]
+from typing import Dict, List, Tuple
+abd: Dict[A, B]
+abl: List[Tuple[A, B]]
+abd = {a: b for a, b in abl}
+x: Dict[B, A] = {a: b for a, b in abl} # E: Key expression in dictionary comprehension has incompatible type "A"; expected type "B" \
+  # E: Value expression in dictionary comprehension has incompatible type "B"; expected type "A"
+y: A = {a: b for a, b in abl} # E: Incompatible types in assignment (expression has type "Dict[A, B]", variable has type "A")
+class A: pass
+class B: pass
+[builtins fixtures/dict.pyi]
+
 [case testNewAnalyzerTypeArgBoundCheck]
 from typing import TypeVar, Generic