From e66891f80f2632f843ff229bf4513e90974eb71c Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 28 Jun 2020 16:29:47 +1000 Subject: [PATCH 1/6] Add tests for dicts using assignment expression --- test-data/unit/check-python38.test | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 12a060525820..d892e181fdd1 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -218,6 +218,19 @@ def f(x: int = (c := 4)) -> int: reveal_type(filtered_data) # N: Revealed type is 'builtins.list[builtins.int*]' reveal_type(y3) # N: Revealed type is 'builtins.int' + d = {'a': (a2 := 1), 'b': a2 + 1, 'c': a2 + 2} + reveal_type(d) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' + reveal_type(a2) # N: Revealed type is 'builtins.int' + + d2 = {(prefix := 'key_') + 'a': (start_val := 1), prefix + 'b': start_val + 1, prefix + 'c': start_val + 2} + reveal_type(d2) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' + reveal_type(prefix) # N: Revealed type is 'builtins.str' + reveal_type(start_val) # N: Revealed type is 'builtins.int' + + filtered_dict = {k: new_v for k, v in [('a', 1), ('b', 2), ('c', 3)] if (new_v := v + 1) == 2} + reveal_type(filtered_dict) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' + reveal_type(new_v) # N: Revealed type is 'builtins.int' + # https://www.python.org/dev/peps/pep-0572/#exceptional-cases (y4 := 3) reveal_type(y4) # N: Revealed type is 'builtins.int' From c0f633f655e497938e16a3776ae44bdb6a9947c0 Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 28 Jun 2020 16:30:35 +1000 Subject: [PATCH 2/6] Duplicate walrus dict/list tests to global scope (passing) --- test-data/unit/check-python38.test | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index d892e181fdd1..f51ed3226094 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -198,6 +198,19 @@ if a := 2: while b := "x": reveal_type(b) # N: Revealed type is 'builtins.str' +l = [y2 := 1, y2 + 2, y2 + 3] +reveal_type(y2) # N: Revealed type is 'builtins.int' +reveal_type(l) # N: Revealed type is 'builtins.list[builtins.int*]' + +d = {'a': (a2 := 1), 'b': a2 + 1, 'c': a2 + 2} +reveal_type(d) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' +reveal_type(a2) # N: Revealed type is 'builtins.int' + +d2 = {(prefix := 'key_') + 'a': (start_val := 1), prefix + 'b': start_val + 1, prefix + 'c': start_val + 2} +reveal_type(d2) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' +reveal_type(prefix) # N: Revealed type is 'builtins.str' +reveal_type(start_val) # N: Revealed type is 'builtins.int' + def f(x: int = (c := 4)) -> int: if a := 2: reveal_type(a) # N: Revealed type is 'builtins.int' From 822813dad1f30118d5c7f5e69708177beef7021d Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 28 Jun 2020 16:31:31 +1000 Subject: [PATCH 3/6] Duplicate walrus comprehension tests to global scope (now failing) --- test-data/unit/check-python38.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index f51ed3226094..41eb6e829796 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -201,6 +201,10 @@ while b := "x": l = [y2 := 1, y2 + 2, y2 + 3] reveal_type(y2) # N: Revealed type is 'builtins.int' reveal_type(l) # N: Revealed type is 'builtins.list[builtins.int*]' + +filtered_data = [y3 for x in l if (y3 := a) is not None] +reveal_type(filtered_data) # N: Revealed type is 'builtins.list[builtins.int*]' +reveal_type(y3) # N: Revealed type is 'builtins.int' d = {'a': (a2 := 1), 'b': a2 + 1, 'c': a2 + 2} reveal_type(d) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' @@ -211,6 +215,10 @@ reveal_type(d2) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.in reveal_type(prefix) # N: Revealed type is 'builtins.str' reveal_type(start_val) # N: Revealed type is 'builtins.int' +filtered_dict = {k: new_v for k, v in [('a', 1), ('b', 2), ('c', 3)] if (new_v := v + 1) == 2} +reveal_type(filtered_dict) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.int*]' +reveal_type(new_v) # N: Revealed type is 'builtins.int' + def f(x: int = (c := 4)) -> int: if a := 2: reveal_type(a) # N: Revealed type is 'builtins.int' From ed9ba4f87fadefa9de335246bf2c12135dec735c Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 28 Jun 2020 16:32:10 +1000 Subject: [PATCH 4/6] Use global symbol table if 1st non-comprehension scope --- mypy/semanal.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 24c9cb7a9e5f..dc20c279bb50 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4542,9 +4542,15 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab if self.is_func_scope(): assert self.locals[-1] is not None if escape_comprehensions: + assert len(self.locals) == len(self.is_comprehension_stack) + # Retrieve the symbol table from the enclosing non-comprehension scope. for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)): if not is_comprehension: - names = self.locals[-1 - i] + if i == len(self.locals) - 1: # The last iteration. + # The caller of the comprehension is in the global space. + names = self.globals + else: + names = cast(SymbolTable, self.locals[-1 - i]) break else: assert False, "Should have at least one non-comprehension scope" From a68bd68958cfa37bfc1a4d9d9946c07b3a63e518 Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 5 Jul 2020 19:43:36 +1000 Subject: [PATCH 5/6] Confirm the symbol table retrieved is not None It may be None in the case of a list comprehension being declared in a class scope (not in a function of a class). This, however, is not valid python syntax. --- mypy/semanal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index dc20c279bb50..c3b9f8e91817 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4550,7 +4550,10 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab # The caller of the comprehension is in the global space. names = self.globals else: - names = cast(SymbolTable, self.locals[-1 - i]) + names_candidate = self.locals[-1 - i] + assert names_candidate is not None, \ + "Escaping comprehension from invalid scope" + names = names_candidate break else: assert False, "Should have at least one non-comprehension scope" From 8585a4f11a929b56b5f83379fc6192a62f9a09bd Mon Sep 17 00:00:00 2001 From: dhood Date: Sun, 5 Jul 2020 19:44:58 +1000 Subject: [PATCH 6/6] Add tests for assignment expression in class scope --- test-data/unit/check-python38.test | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 41eb6e829796..78d62ae43ba4 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -338,6 +338,28 @@ def check_narrow(x: Optional[int], s: List[int]) -> None: if isinstance((y := x), int): reveal_type(y) # N: Revealed type is 'builtins.int' +class AssignmentExpressionsClass: + x = (y := 1) + (z := 2) + reveal_type(z) # N: Revealed type is 'builtins.int' + + l = [x2 := 1, 2, 3] + reveal_type(x2) # N: Revealed type is 'builtins.int' + + def __init__(self) -> None: + reveal_type(self.z) # N: Revealed type is 'builtins.int' + + l = [z2 := 1, z2 + 2, z2 + 3] + reveal_type(z2) # N: Revealed type is 'builtins.int' + reveal_type(l) # N: Revealed type is 'builtins.list[builtins.int*]' + + filtered_data = [z3 for x in l if (z3 := 1) is not None] + reveal_type(filtered_data) # N: Revealed type is 'builtins.list[builtins.int*]' + reveal_type(z3) # N: Revealed type is 'builtins.int' + +# Assignment expressions from inside the class should not escape the class scope. +reveal_type(x2) # E: Name 'x2' is not defined # N: Revealed type is 'Any' +reveal_type(z2) # E: Name 'z2' is not defined # N: Revealed type is 'Any' + [builtins fixtures/isinstancelist.pyi] [case testWalrusPartialTypes]