Skip to content

Commit 81b2d4a

Browse files
committed
support hoisting for comprehensions (old semanal)
1 parent 4a46238 commit 81b2d4a

File tree

2 files changed

+36
-14
lines changed

2 files changed

+36
-14
lines changed

mypy/semanal.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ def __init__(self,
247247
using the Errors instance.
248248
"""
249249
self.locals = [None]
250+
# Whether each nested scope is a comprehension.
251+
self.is_comprehension_stack = [False] # type: List[bool]
250252
self.imports = set()
251253
self.type = None
252254
self.type_stack = []
@@ -931,6 +933,7 @@ def enter_class(self, info: TypeInfo) -> None:
931933
# Remember previous active class
932934
self.type_stack.append(self.type)
933935
self.locals.append(None) # Add class scope
936+
self.is_comprehension_stack.append(False)
934937
self.block_depth.append(-1) # The class body increments this to 0
935938
self.postpone_nested_functions_stack.append(FUNCTION_BOTH_PHASES)
936939
self.type = info
@@ -940,6 +943,7 @@ def leave_class(self) -> None:
940943
self.postpone_nested_functions_stack.pop()
941944
self.block_depth.pop()
942945
self.locals.pop()
946+
self.is_comprehension_stack.pop()
943947
self.type = self.type_stack.pop()
944948

945949
def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
@@ -1781,7 +1785,7 @@ def add_type_alias_deps(self, aliases_used: Iterable[str],
17811785
self.cur_mod_node.alias_deps[target].update(aliases_used)
17821786

17831787
def visit_assignment_expr(self, e: AssignmentExpr) -> None:
1784-
self.analyze_lvalue(e.target)
1788+
self.analyze_lvalue(e.target, escape_comprehension=True)
17851789
e.value.accept(self)
17861790

17871791
def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
@@ -2101,7 +2105,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
21012105
def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
21022106
add_global: bool = False,
21032107
explicit_type: bool = False,
2104-
is_final: bool = False) -> None:
2108+
is_final: bool = False,
2109+
escape_comprehension: bool = False) -> None:
21052110
"""Analyze an lvalue or assignment target.
21062111
21072112
Note that this is used in both pass 1 and 2.
@@ -2111,9 +2116,14 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
21112116
nested: If true, the lvalue is within a tuple or list lvalue expression
21122117
add_global: Add name to globals table only if this is true (used in first pass)
21132118
explicit_type: Assignment has type annotation
2119+
escape_comprehension: If we are inside a comprehension, set the variable
2120+
in the enclosing scope instead. This implements
2121+
https://www.python.org/dev/peps/pep-0572/#scope-of-the-target
21142122
"""
2123+
if escape_comprehension:
2124+
assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr"
21152125
if isinstance(lval, NameExpr):
2116-
self.analyze_name_lvalue(lval, add_global, explicit_type, is_final)
2126+
self.analyze_name_lvalue(lval, add_global, explicit_type, is_final, escape_comprehension)
21172127
elif isinstance(lval, MemberExpr):
21182128
if not add_global:
21192129
self.analyze_member_lvalue(lval, explicit_type, is_final)
@@ -2142,7 +2152,8 @@ def analyze_name_lvalue(self,
21422152
lval: NameExpr,
21432153
add_global: bool,
21442154
explicit_type: bool,
2145-
is_final: bool) -> None:
2155+
is_final: bool,
2156+
escape_comprehension: bool) -> None:
21462157
"""Analyze an lvalue that targets a name expression.
21472158
21482159
Arguments are similar to "analyze_lvalue".
@@ -2179,7 +2190,7 @@ def analyze_name_lvalue(self,
21792190
lval.name not in self.nonlocal_decls[-1]):
21802191
# Define new local name.
21812192
v = self.make_name_lvalue_var(lval, LDEF, not explicit_type)
2182-
self.add_local(v, lval)
2193+
self.add_local(v, lval, escape_comprehension=escape_comprehension)
21832194
if unmangle(lval.name) == '_':
21842195
# Special case for assignment to local named '_': always infer 'Any'.
21852196
typ = AnyType(TypeOfAny.special_form)
@@ -3344,15 +3355,15 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None:
33443355
expr.generator.accept(self)
33453356

33463357
def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
3347-
self.enter()
3358+
self.enter(is_comprehension=True)
33483359
self.analyze_comp_for(expr)
33493360
expr.key.accept(self)
33503361
expr.value.accept(self)
33513362
self.leave()
33523363
self.analyze_comp_for_2(expr)
33533364

33543365
def visit_generator_expr(self, expr: GeneratorExpr) -> None:
3355-
self.enter()
3366+
self.enter(is_comprehension=True)
33563367
self.analyze_comp_for(expr)
33573368
expr.left_expr.accept(self)
33583369
self.leave()
@@ -3658,18 +3669,20 @@ def qualified_name(self, n: str) -> str:
36583669
base = self.cur_mod_id
36593670
return base + '.' + n
36603671

3661-
def enter(self) -> None:
3672+
def enter(self, is_comprehension: bool = False) -> None:
36623673
self.locals.append(SymbolTable())
36633674
self.global_decls.append(set())
36643675
self.nonlocal_decls.append(set())
36653676
# -1 since entering block will increment this to 0.
36663677
self.block_depth.append(-1)
3678+
self.is_comprehension_stack.append(is_comprehension)
36673679

36683680
def leave(self) -> None:
36693681
self.locals.pop()
36703682
self.global_decls.pop()
36713683
self.nonlocal_decls.pop()
36723684
self.block_depth.pop()
3685+
self.is_comprehension_stack.pop()
36733686

36743687
def is_func_scope(self) -> bool:
36753688
return self.locals[-1] is not None
@@ -3731,15 +3744,25 @@ def add_symbol(self, name: str, node: SymbolTableNode,
37313744
return
37323745
self.globals[name] = node
37333746

3734-
def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context) -> None:
3747+
def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context,
3748+
escape_comprehension: bool = False) -> None:
37353749
"""Add local variable or function."""
37363750
assert self.locals[-1] is not None, "Should not add locals outside a function"
3751+
if escape_comprehension:
3752+
for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)):
3753+
if not is_comprehension:
3754+
scope = self.locals[-1 - i]
3755+
break
3756+
else:
3757+
assert False, "Should have at least one non-comprehension scope"
3758+
else:
3759+
scope = self.locals[-1]
37373760
name = node.name()
3738-
if name in self.locals[-1]:
3739-
self.name_already_defined(name, ctx, self.locals[-1][name])
3761+
if name in scope:
3762+
self.name_already_defined(name, ctx, scope[name])
37403763
return
37413764
node._fullname = name
3742-
self.locals[-1][name] = SymbolTableNode(LDEF, node)
3765+
scope[name] = SymbolTableNode(LDEF, node)
37433766

37443767
def add_exports(self, exp_or_exps: Union[Iterable[Expression], Expression]) -> None:
37453768
exps = [exp_or_exps] if isinstance(exp_or_exps, Expression) else exp_or_exps

test-data/unit/check-python38.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,7 @@ def f(x: int = (c := 4)) -> None:
207207

208208
filtered_data = [y3 for x in l if (y3 := a) is not None]
209209
reveal_type(filtered_data) # E: Revealed type is 'builtins.list[builtins.int*]'
210-
# TODO this should be valid: https://www.python.org/dev/peps/pep-0572/#scope-of-the-target
211-
y3 # E: Name 'y3' is not defined
210+
reveal_type(y3) # E: Revealed type is 'builtins.int'
212211

213212
# https://www.python.org/dev/peps/pep-0572/#exceptional-cases
214213
(y4 := 3)

0 commit comments

Comments
 (0)