From 264069e14a6bbe8c2eae3276499b9065765d6f56 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 25 May 2019 10:31:57 -0700 Subject: [PATCH 01/20] make basic things work --- mypy/checkexpr.py | 7 ++++++- mypy/fastparse.py | 10 +++++----- mypy/literals.py | 5 ++++- mypy/newsemanal/semanal.py | 6 +++++- mypy/nodes.py | 11 +++++++++++ mypy/semanal.py | 6 +++++- mypy/semanal_pass1.py | 6 ++++++ mypy/server/subexpr.py | 5 +++++ mypy/stats.py | 7 ++++++- mypy/strconv.py | 3 +++ mypy/traverser.py | 6 +++++- mypy/treetransform.py | 5 ++++- mypy/visitor.py | 7 +++++++ 13 files changed, 72 insertions(+), 12 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4ac9211818b2..7064055ceb91 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -28,7 +28,7 @@ OpExpr, UnaryExpr, IndexExpr, CastExpr, RevealExpr, TypeApplication, ListExpr, TupleExpr, DictExpr, LambdaExpr, SuperExpr, SliceExpr, Context, Expression, ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator, - ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, + ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, AssignmentExpr, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, SymbolNode, PlaceholderNode, @@ -2421,6 +2421,11 @@ def check_list_multiply(self, e: OpExpr) -> Type: e.method_type = method_type return result + def visit_assignment_expr(self, e: AssignmentExpr) -> Type: + value = self.accept(e.value) + self.chk.check_assignment(e.target, e.value) + return value + def visit_unary_expr(self, e: UnaryExpr) -> Type: """Type check an unary operation ('not', '-', '+' or '~').""" operand_type = self.accept(e.expr) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index a0ab4a83b593..2ba1adb90429 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -22,7 +22,7 @@ TupleExpr, GeneratorExpr, ListComprehension, ListExpr, ConditionalExpr, DictExpr, SetExpr, NameExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr, FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr, SliceExpr, OpExpr, - UnaryExpr, LambdaExpr, ComparisonExpr, + UnaryExpr, LambdaExpr, ComparisonExpr, AssignmentExpr, StarExpr, YieldFromExpr, NonlocalDecl, DictionaryComprehension, SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument, AwaitExpr, TempNode, Expression, Statement, @@ -720,10 +720,6 @@ def visit_AugAssign(self, n: ast3.AugAssign) -> OperatorAssignmentStmt: self.visit(n.value)) return self.set_line(s, n) - def visit_NamedExpr(self, n: NamedExpr) -> None: - self.fail("assignment expressions are not yet supported", n.lineno, n.col_offset) - return None - # For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment) def visit_For(self, n: ast3.For) -> ForStmt: if n.type_comment is not None: @@ -885,6 +881,10 @@ def visit_Continue(self, n: ast3.Continue) -> ContinueStmt: # --- expr --- + def visit_NamedExpr(self, n: NamedExpr) -> AssignmentExpr: + s = AssignmentExpr(self.visit(n.target), self.visit(n.value)) + return self.set_line(s, n) + # BoolOp(boolop op, expr* values) def visit_BoolOp(self, n: ast3.BoolOp) -> OpExpr: # mypy translates (1 and 2 and 3) as (1 and (2 and 3)) diff --git a/mypy/literals.py b/mypy/literals.py index 3fb3f10a5551..65d5c7573e4c 100644 --- a/mypy/literals.py +++ b/mypy/literals.py @@ -7,7 +7,7 @@ ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr, TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension, GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr, - TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, + TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, AssignmentExpr, ) from mypy.visitor import ExpressionVisitor @@ -159,6 +159,9 @@ def visit_index_expr(self, e: IndexExpr) -> Optional[Key]: return ('Index', literal_hash(e.base), literal_hash(e.index)) return None + def visit_assignment_expr(self, e: AssignmentExpr) -> None: + return None + def visit_call_expr(self, e: CallExpr) -> None: return None diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 97359f9c39a8..27acdbefd60c 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -73,7 +73,7 @@ PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT, nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions, - EnumCallExpr + EnumCallExpr, AssignmentExpr, ) from mypy.tvar_scope import TypeVarScope from mypy.typevars import fill_typevars @@ -1831,6 +1831,10 @@ def visit_import_all(self, i: ImportAll) -> None: # Assignment # + def visit_assignment_expr(self, s: AssignmentExpr) -> None: + s.value.accept(self) + self.analyze_lvalue(s.target) + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: tag = self.track_incomplete_refs() s.rvalue.accept(self) diff --git a/mypy/nodes.py b/mypy/nodes.py index 25c38eb6c394..60b5d1efc7f2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1581,6 +1581,17 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_unary_expr(self) +class AssignmentExpr(Expression): + """Assignment expressions in Python 3.8+, like "a := 2".""" + def __init__(self, target: Expression, value: Expression) -> None: + super().__init__() + self.target = target + self.value = value + + def accept(self, visitor: ExpressionVisitor[T]) -> T: + return visitor.visit_assignment_expr(self) + + # Map from binary operator id to related method name (in Python 3). op_methods = { '+': '__add__', diff --git a/mypy/semanal.py b/mypy/semanal.py index bdf68e87caee..3cca274bb95b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -48,7 +48,7 @@ GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr, SliceExpr, CastExpr, RevealExpr, TypeApplication, Context, SymbolTable, SymbolTableNode, ListComprehension, GeneratorExpr, - LambdaExpr, MDEF, Decorator, SetExpr, TypeVarExpr, + LambdaExpr, MDEF, Decorator, SetExpr, TypeVarExpr, AssignmentExpr, StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, type_aliases, YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode, @@ -1780,6 +1780,10 @@ def add_type_alias_deps(self, aliases_used: Iterable[str], target = self.scope.current_target() self.cur_mod_node.alias_deps[target].update(aliases_used) + def visit_assignment_expr(self, e: AssignmentExpr) -> None: + self.analyze_lvalue(e.target) + e.value.accept(self) + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: s.is_final_def = self.unwrap_final(s) self.analyze_lvalues(s) diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index 02f6c40f7851..a9b57d72a288 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -154,6 +154,10 @@ def visit_block(self, b: Block) -> None: node.accept(self) self.sem.block_depth[-1] -= 1 + def visit_assignment_expr(self, s: AssignmentStmt) -> None: + if self.sem.is_module_scope(): + self.analyze_lvalue(s.target) + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if self.sem.is_module_scope(): for lval in s.lvalues: @@ -333,6 +337,8 @@ def visit_decorator(self, d: Decorator) -> None: def visit_if_stmt(self, s: IfStmt) -> None: infer_reachability_of_if_statement(s, self.sem.options) + for expr in s.expr: + expr.accept(self) for node in s.body: node.accept(self) if s.else_body: diff --git a/mypy/server/subexpr.py b/mypy/server/subexpr.py index 355681a43bcc..cc645332d9d4 100644 --- a/mypy/server/subexpr.py +++ b/mypy/server/subexpr.py @@ -7,6 +7,7 @@ SliceExpr, CastExpr, RevealExpr, UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr, GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension, ConditionalExpr, TypeApplication, LambdaExpr, StarExpr, BackquoteExpr, AwaitExpr, + AssignmentExpr, ) from mypy.traverser import TraverserVisitor @@ -102,6 +103,10 @@ def visit_reveal_expr(self, e: RevealExpr) -> None: self.add(e) super().visit_reveal_expr(e) + def visit_assignment_expr(self, e: AssignmentExpr) -> None: + self.add(e) + super().visit_assignment_expr(e) + def visit_unary_expr(self, e: UnaryExpr) -> None: self.add(e) super().visit_unary_expr(e) diff --git a/mypy/stats.py b/mypy/stats.py index 87129b311f66..b63c092535ba 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -15,7 +15,8 @@ from mypy import nodes from mypy.nodes import ( Expression, FuncDef, TypeApplication, AssignmentStmt, NameExpr, CallExpr, MypyFile, - MemberExpr, OpExpr, ComparisonExpr, IndexExpr, UnaryExpr, YieldFromExpr, RefExpr, ClassDef + MemberExpr, OpExpr, ComparisonExpr, IndexExpr, UnaryExpr, YieldFromExpr, RefExpr, ClassDef, + AssignmentExpr, ) MYPY = False @@ -167,6 +168,10 @@ def visit_index_expr(self, o: IndexExpr) -> None: self.process_node(o) super().visit_index_expr(o) + def visit_assignment_expr(self, o: AssignmentExpr) -> None: + self.process_node(o) + super().visit_assignment_expr(o) + def visit_unary_expr(self, o: UnaryExpr) -> None: self.process_node(o) super().visit_unary_expr(o) diff --git a/mypy/strconv.py b/mypy/strconv.py index 87f7bd97af71..b9b66c70c413 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -425,6 +425,9 @@ def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> str: # REVEAL_LOCALS return self.dump([o.local_nodes], o) + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> str: + return self.dump([o.target, o.value], o) + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> str: return self.dump([o.op, o.expr], o) diff --git a/mypy/traverser.py b/mypy/traverser.py index b75d356de9a3..b0619872e886 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -6,7 +6,7 @@ ExpressionStmt, AssignmentStmt, OperatorAssignmentStmt, WhileStmt, ForStmt, ReturnStmt, AssertStmt, DelStmt, IfStmt, RaiseStmt, TryStmt, WithStmt, NameExpr, MemberExpr, OpExpr, SliceExpr, CastExpr, RevealExpr, - UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr, + UnaryExpr, ListExpr, TupleExpr, DictExpr, SetExpr, IndexExpr, AssignmentExpr, GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension, ConditionalExpr, TypeApplication, ExecStmt, Import, ImportFrom, LambdaExpr, ComparisonExpr, OverloadedFuncDef, YieldFromExpr, @@ -192,6 +192,10 @@ def visit_reveal_expr(self, o: RevealExpr) -> None: # RevealLocalsExpr doesn't have an inner expression pass + def visit_assignment_expr(self, o: AssignmentExpr) -> None: + o.target.accept(self) + o.value.accept(self) + def visit_unary_expr(self, o: UnaryExpr) -> None: o.expr.accept(self) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index f4bc96e39db6..79802895228d 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -19,7 +19,7 @@ ComparisonExpr, TempNode, StarExpr, Statement, Expression, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension, DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr, - YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, + YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, AssignmentExpr, OverloadPart, EnumCallExpr, REVEAL_TYPE ) from mypy.types import Type, FunctionLike @@ -407,6 +407,9 @@ def visit_super_expr(self, node: SuperExpr) -> SuperExpr: new.info = node.info return new + def visit_assignment_expr(self, node: AssignmentExpr) -> AssignmentExpr: + return AssignmentExpr(node.target, node.value) + def visit_unary_expr(self, node: UnaryExpr) -> UnaryExpr: new = UnaryExpr(node.op, self.expr(node.expr)) new.method_type = self.optional_type(node.method_type) diff --git a/mypy/visitor.py b/mypy/visitor.py index c719f8b297d5..e13986efbade 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -90,6 +90,10 @@ def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: pass + @abstractmethod + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: + pass + @abstractmethod def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: pass @@ -473,6 +477,9 @@ def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: pass + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: + pass + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: pass From e765e326efb5a0abe43f6f1d003494c7bdfe6aba Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 25 May 2019 11:05:51 -0700 Subject: [PATCH 02/20] more tests --- mypy/newsemanal/semanal_pass1.py | 2 ++ mypy/semanal_pass1.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/mypy/newsemanal/semanal_pass1.py b/mypy/newsemanal/semanal_pass1.py index 0e3da779f3eb..76d2e852381d 100644 --- a/mypy/newsemanal/semanal_pass1.py +++ b/mypy/newsemanal/semanal_pass1.py @@ -90,6 +90,8 @@ def visit_import(self, node: Import) -> None: def visit_if_stmt(self, s: IfStmt) -> None: infer_reachability_of_if_statement(s, self.options) + for expr in s.expr: + expr.accept(self) for node in s.body: node.accept(self) if s.else_body: diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index a9b57d72a288..84191d47ebdb 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -174,6 +174,9 @@ def visit_func_def(self, func: FuncDef, decorated: bool = False) -> None: if sem.type is not None: # Don't process methods during pass 1. return + for arg in func.arguments: + if arg.initializer: + arg.initializer.accept(self) func.is_conditional = sem.block_depth[-1] > 0 func._fullname = sem.qualified_name(func.name()) at_module = sem.is_module_scope() and not decorated @@ -309,6 +312,7 @@ def visit_import_all(self, node: ImportAll) -> None: def visit_while_stmt(self, s: WhileStmt) -> None: if self.sem.is_module_scope(): + s.expr.accept(self) s.body.accept(self) if s.else_body: s.else_body.accept(self) From 4a46238f8957a5f49448578f2b858eaf381dab47 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 Jun 2019 10:30:22 -0700 Subject: [PATCH 03/20] add tests back (file got renamed) --- test-data/unit/check-python38.test | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 62c78c616d08..7150a7ec8efc 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -181,3 +181,52 @@ def f(p1: bytes, p2: float, /, p_or_kw: int) -> None: def f(p1: bytes, p2: float, /) -> None: reveal_type(p1) # E: Revealed type is 'builtins.bytes' reveal_type(p2) # E: Revealed type is 'builtins.float' + +[case testWalrus] +if (a := 2): + reveal_type(a) # E: Revealed type is 'builtins.int' + +while (b := "x"): + reveal_type(b) # E: Revealed type is 'builtins.str' + +def f(x: int = (c := 4)) -> None: + if (a := 2): + reveal_type(a) # E: Revealed type is 'builtins.int' + + while (b := "x"): + reveal_type(b) # E: Revealed type is 'builtins.str' + + x = (y := 1) + (z := 2) + reveal_type(x) # E: Revealed type is 'builtins.int' + reveal_type(y) # E: Revealed type is 'builtins.int' + reveal_type(z) # E: Revealed type is 'builtins.int' + + l = [y2 := 1, y2 + 2, y2 + 3] + reveal_type(y2) # E: Revealed type is 'builtins.int' + reveal_type(l) # E: Revealed type is 'builtins.list[builtins.int*]' + + filtered_data = [y3 for x in l if (y3 := a) is not None] + reveal_type(filtered_data) # E: Revealed type is 'builtins.list[builtins.int*]' + # TODO this should be valid: https://www.python.org/dev/peps/pep-0572/#scope-of-the-target + y3 # E: Name 'y3' is not defined + + # https://www.python.org/dev/peps/pep-0572/#exceptional-cases + (y4 := 3) + reveal_type(y4) # E: Revealed type is 'builtins.int' + + y5 = (y6 := 3) + reveal_type(y5) # E: Revealed type is 'builtins.int' + reveal_type(y6) # E: Revealed type is 'builtins.int' + + f(x=(y7 := 3)) + reveal_type(y7) # E: Revealed type is 'builtins.int' + + reveal_type((lambda: (y8 := 3) and y8)()) # E: Revealed type is 'builtins.int' + + y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int") + if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") + pass + +reveal_type(c) # E: Revealed type is 'builtins.int' + +[builtins fixtures/f_string.pyi] From 81b2d4a3f8593e1e2c84a22b371aa2e5bee21fdc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 Jun 2019 10:49:00 -0700 Subject: [PATCH 04/20] support hoisting for comprehensions (old semanal) --- mypy/semanal.py | 47 ++++++++++++++++++++++-------- test-data/unit/check-python38.test | 3 +- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 3cca274bb95b..90a9f75285ac 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -247,6 +247,8 @@ def __init__(self, using the Errors instance. """ self.locals = [None] + # Whether each nested scope is a comprehension. + self.is_comprehension_stack = [False] # type: List[bool] self.imports = set() self.type = None self.type_stack = [] @@ -931,6 +933,7 @@ def enter_class(self, info: TypeInfo) -> None: # Remember previous active class self.type_stack.append(self.type) self.locals.append(None) # Add class scope + self.is_comprehension_stack.append(False) self.block_depth.append(-1) # The class body increments this to 0 self.postpone_nested_functions_stack.append(FUNCTION_BOTH_PHASES) self.type = info @@ -940,6 +943,7 @@ def leave_class(self) -> None: self.postpone_nested_functions_stack.pop() self.block_depth.pop() self.locals.pop() + self.is_comprehension_stack.pop() self.type = self.type_stack.pop() def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None: @@ -1781,7 +1785,7 @@ def add_type_alias_deps(self, aliases_used: Iterable[str], self.cur_mod_node.alias_deps[target].update(aliases_used) def visit_assignment_expr(self, e: AssignmentExpr) -> None: - self.analyze_lvalue(e.target) + self.analyze_lvalue(e.target, escape_comprehension=True) e.value.accept(self) def visit_assignment_stmt(self, s: AssignmentStmt) -> None: @@ -2101,7 +2105,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, explicit_type: bool = False, - is_final: bool = False) -> None: + is_final: bool = False, + escape_comprehension: bool = False) -> None: """Analyze an lvalue or assignment target. Note that this is used in both pass 1 and 2. @@ -2111,9 +2116,14 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, nested: If true, the lvalue is within a tuple or list lvalue expression add_global: Add name to globals table only if this is true (used in first pass) explicit_type: Assignment has type annotation + escape_comprehension: If we are inside a comprehension, set the variable + in the enclosing scope instead. This implements + https://www.python.org/dev/peps/pep-0572/#scope-of-the-target """ + if escape_comprehension: + assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr" if isinstance(lval, NameExpr): - self.analyze_name_lvalue(lval, add_global, explicit_type, is_final) + self.analyze_name_lvalue(lval, add_global, explicit_type, is_final, escape_comprehension) elif isinstance(lval, MemberExpr): if not add_global: self.analyze_member_lvalue(lval, explicit_type, is_final) @@ -2142,7 +2152,8 @@ def analyze_name_lvalue(self, lval: NameExpr, add_global: bool, explicit_type: bool, - is_final: bool) -> None: + is_final: bool, + escape_comprehension: bool) -> None: """Analyze an lvalue that targets a name expression. Arguments are similar to "analyze_lvalue". @@ -2179,7 +2190,7 @@ def analyze_name_lvalue(self, lval.name not in self.nonlocal_decls[-1]): # Define new local name. v = self.make_name_lvalue_var(lval, LDEF, not explicit_type) - self.add_local(v, lval) + self.add_local(v, lval, escape_comprehension=escape_comprehension) if unmangle(lval.name) == '_': # Special case for assignment to local named '_': always infer 'Any'. typ = AnyType(TypeOfAny.special_form) @@ -3344,7 +3355,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(is_comprehension=True) self.analyze_comp_for(expr) expr.key.accept(self) expr.value.accept(self) @@ -3352,7 +3363,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(is_comprehension=True) self.analyze_comp_for(expr) expr.left_expr.accept(self) self.leave() @@ -3658,18 +3669,20 @@ def qualified_name(self, n: str) -> str: base = self.cur_mod_id return base + '.' + n - def enter(self) -> None: + def enter(self, is_comprehension: bool = False) -> None: self.locals.append(SymbolTable()) self.global_decls.append(set()) self.nonlocal_decls.append(set()) # -1 since entering block will increment this to 0. self.block_depth.append(-1) + self.is_comprehension_stack.append(is_comprehension) def leave(self) -> None: self.locals.pop() self.global_decls.pop() self.nonlocal_decls.pop() self.block_depth.pop() + self.is_comprehension_stack.pop() def is_func_scope(self) -> bool: return self.locals[-1] is not None @@ -3731,15 +3744,25 @@ def add_symbol(self, name: str, node: SymbolTableNode, return self.globals[name] = node - def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context) -> None: + def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context, + escape_comprehension: bool = False) -> None: """Add local variable or function.""" assert self.locals[-1] is not None, "Should not add locals outside a function" + if escape_comprehension: + for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)): + if not is_comprehension: + scope = self.locals[-1 - i] + break + else: + assert False, "Should have at least one non-comprehension scope" + else: + scope = self.locals[-1] name = node.name() - if name in self.locals[-1]: - self.name_already_defined(name, ctx, self.locals[-1][name]) + if name in scope: + self.name_already_defined(name, ctx, scope[name]) return node._fullname = name - self.locals[-1][name] = SymbolTableNode(LDEF, node) + scope[name] = SymbolTableNode(LDEF, node) def add_exports(self, exp_or_exps: Union[Iterable[Expression], Expression]) -> None: exps = [exp_or_exps] if isinstance(exp_or_exps, Expression) else exp_or_exps diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 7150a7ec8efc..2132f67897ab 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -207,8 +207,7 @@ def f(x: int = (c := 4)) -> None: filtered_data = [y3 for x in l if (y3 := a) is not None] reveal_type(filtered_data) # E: Revealed type is 'builtins.list[builtins.int*]' - # TODO this should be valid: https://www.python.org/dev/peps/pep-0572/#scope-of-the-target - y3 # E: Name 'y3' is not defined + reveal_type(y3) # E: Revealed type is 'builtins.int' # https://www.python.org/dev/peps/pep-0572/#exceptional-cases (y4 := 3) From 8a08ed2b185d414c060080d1f1b984c567b0ce91 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 Jun 2019 11:18:03 -0700 Subject: [PATCH 05/20] fix under the new semanal --- mypy/newsemanal/semanal.py | 53 +++++++++++++++++++++++------- test-data/unit/check-python38.test | 49 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 27acdbefd60c..923575546f09 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -186,6 +186,8 @@ class NewSemanticAnalyzer(NodeVisitor[None], nonlocal_decls = None # type: List[Set[str]] # Local names of function scopes; None for non-function scopes. locals = None # type: List[Optional[SymbolTable]] + # Whether each scope is a comprehension scope. + is_comprehension_stack = None # type: List[bool] # Nested block depths of scopes block_depth = None # type: List[int] # TypeInfo of directly enclosing class (or None) @@ -240,6 +242,7 @@ def __init__(self, errors: Report analysis errors using this instance """ self.locals = [None] + self.is_comprehension_stack = [False] # Saved namespaces from previous iteration. Every top-level function/method body is # 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 @@ -510,9 +513,14 @@ def file_context(self, # def visit_func_def(self, defn: FuncDef) -> None: + # Visit default values because they may contain assignment expressions. + for arg in defn.arguments: + if arg.initializer: + arg.initializer.accept(self) + defn.is_conditional = self.block_depth[-1] > 0 - # Set full names even for those definitionss that aren't added + # Set full names even for those definitions that aren't added # to a symbol table. For example, for overload items. defn._fullname = self.qualified_name(defn.name()) @@ -1120,6 +1128,7 @@ def enter_class(self, info: TypeInfo) -> None: # Remember previous active class self.type_stack.append(self.type) self.locals.append(None) # Add class scope + self.is_comprehension_stack.append(False) self.block_depth.append(-1) # The class body increments this to 0 self.type = info @@ -1127,6 +1136,7 @@ def leave_class(self) -> None: """ Restore analyzer state. """ self.block_depth.pop() self.locals.pop() + self.is_comprehension_stack.pop() self.type = self.type_stack.pop() def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None: @@ -1833,7 +1843,7 @@ def visit_import_all(self, i: ImportAll) -> None: def visit_assignment_expr(self, s: AssignmentExpr) -> None: s.value.accept(self) - self.analyze_lvalue(s.target) + self.analyze_lvalue(s.target, escape_comprehensions=True) def visit_assignment_stmt(self, s: AssignmentStmt) -> None: tag = self.track_incomplete_refs() @@ -2400,16 +2410,22 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: def analyze_lvalue(self, lval: Lvalue, nested: bool = False, explicit_type: bool = False, - is_final: bool = False) -> None: + is_final: bool = False, + escape_comprehensions: bool = False) -> None: """Analyze an lvalue or assignment target. Args: lval: The target lvalue nested: If true, the lvalue is within a tuple or list lvalue expression explicit_type: Assignment has type annotation + escape_comprehensions: If we are inside a comprehension, set the variable + in the enclosing scope instead. This implements + https://www.python.org/dev/peps/pep-0572/#scope-of-the-target """ + if escape_comprehensions: + assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr" if isinstance(lval, NameExpr): - self.analyze_name_lvalue(lval, explicit_type, is_final) + self.analyze_name_lvalue(lval, explicit_type, is_final, escape_comprehensions) elif isinstance(lval, MemberExpr): self.analyze_member_lvalue(lval, explicit_type, is_final) if explicit_type and not self.is_self_member_ref(lval): @@ -2435,7 +2451,8 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, def analyze_name_lvalue(self, lvalue: NameExpr, explicit_type: bool, - is_final: bool) -> None: + is_final: bool, + escape_comprehensions: bool) -> None: """Analyze an lvalue that targets a name expression. Arguments are similar to "analyze_lvalue". @@ -2459,7 +2476,7 @@ def analyze_name_lvalue(self, if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer: # Define new variable. var = self.make_name_lvalue_var(lvalue, kind, not explicit_type) - added = self.add_symbol(name, var, lvalue) + added = self.add_symbol(name, var, lvalue, escape_comprehensions=escape_comprehensions) # Only bind expression if we successfully added name to symbol table. if added: lvalue.is_new_def = True @@ -3952,7 +3969,8 @@ def add_symbol(self, context: Context, module_public: bool = True, module_hidden: bool = False, - can_defer: bool = True) -> bool: + can_defer: bool = True, + escape_comprehensions: bool = False) -> bool: """Add symbol to the currently active symbol table. Generally additions to symbol table should go through this method or @@ -3974,7 +3992,7 @@ def add_symbol(self, node, module_public=module_public, module_hidden=module_hidden) - return self.add_symbol_table_node(name, symbol, context, can_defer) + return self.add_symbol_table_node(name, symbol, context, can_defer, escape_comprehensions) def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None: """Same as above, but skipping the local namespace. @@ -4002,7 +4020,8 @@ def add_symbol_table_node(self, name: str, symbol: SymbolTableNode, context: Optional[Context] = None, - can_defer: bool = True) -> bool: + can_defer: bool = True, + escape_comprehensions: bool = False) -> bool: """Add symbol table node to the currently active symbol table. Return True if we actually added the symbol, or False if we refused @@ -4021,7 +4040,7 @@ def add_symbol_table_node(self, can_defer: if True, defer current target if adding a placeholder context: error context (see above about None value) """ - names = self.current_symbol_table() + names = self.current_symbol_table(escape_comprehensions=escape_comprehensions) existing = names.get(name) if isinstance(symbol.node, PlaceholderNode) and can_defer: self.defer() @@ -4254,6 +4273,7 @@ def enter(self, function: Union[FuncItem, GeneratorExpr, DictionaryComprehension """Enter a function, generator or comprehension scope.""" names = self.saved_locals.setdefault(function, SymbolTable()) self.locals.append(names) + self.is_comprehension_stack.append(isinstance(function, (GeneratorExpr, DictionaryComprehension))) self.global_decls.append(set()) self.nonlocal_decls.append(set()) # -1 since entering block will increment this to 0. @@ -4261,6 +4281,7 @@ def enter(self, function: Union[FuncItem, GeneratorExpr, DictionaryComprehension def leave(self) -> None: self.locals.pop() + self.is_comprehension_stack.pop() self.global_decls.pop() self.nonlocal_decls.pop() self.block_depth.pop() @@ -4287,10 +4308,18 @@ def current_symbol_kind(self) -> int: kind = GDEF return kind - def current_symbol_table(self) -> SymbolTable: + def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTable: if self.is_func_scope(): assert self.locals[-1] is not None - names = self.locals[-1] + if escape_comprehensions: + for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)): + if not is_comprehension: + names = self.locals[-1 - i] + break + else: + assert False, "Should have at least one non-comprehension scope" + else: + names = self.locals[-1] elif self.type is not None: names = self.type.names else: diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 2132f67897ab..f1da88810a82 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -229,3 +229,52 @@ def f(x: int = (c := 4)) -> None: reveal_type(c) # E: Revealed type is 'builtins.int' [builtins fixtures/f_string.pyi] + +[case testWalrusNewSemanal] +# flags: --new-semantic-analyzer +if (a := 2): + reveal_type(a) # E: Revealed type is 'builtins.int' + +while (b := "x"): + reveal_type(b) # E: Revealed type is 'builtins.str' + +def f(x: int = (c := 4)) -> None: + if (a := 2): + reveal_type(a) # E: Revealed type is 'builtins.int' + + while (b := "x"): + reveal_type(b) # E: Revealed type is 'builtins.str' + + x = (y := 1) + (z := 2) + reveal_type(x) # E: Revealed type is 'builtins.int' + reveal_type(y) # E: Revealed type is 'builtins.int' + reveal_type(z) # E: Revealed type is 'builtins.int' + + l = [y2 := 1, y2 + 2, y2 + 3] + reveal_type(y2) # E: Revealed type is 'builtins.int' + reveal_type(l) # E: Revealed type is 'builtins.list[builtins.int*]' + + filtered_data = [y3 for x in l if (y3 := a) is not None] + reveal_type(filtered_data) # E: Revealed type is 'builtins.list[builtins.int*]' + reveal_type(y3) # E: Revealed type is 'builtins.int' + + # https://www.python.org/dev/peps/pep-0572/#exceptional-cases + (y4 := 3) + reveal_type(y4) # E: Revealed type is 'builtins.int' + + y5 = (y6 := 3) + reveal_type(y5) # E: Revealed type is 'builtins.int' + reveal_type(y6) # E: Revealed type is 'builtins.int' + + f(x=(y7 := 3)) + reveal_type(y7) # E: Revealed type is 'builtins.int' + + reveal_type((lambda: (y8 := 3) and y8)()) # E: Revealed type is 'builtins.int' + + y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int") + if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") + pass + +reveal_type(c) # E: Revealed type is 'builtins.int' + +[builtins fixtures/f_string.pyi] From e3b3ecd9321e9371288062a7061a841e0553224c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 Jun 2019 11:28:14 -0700 Subject: [PATCH 06/20] a few more tests --- test-data/unit/check-python38.test | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index f1da88810a82..d45e529f848f 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -183,13 +183,15 @@ def f(p1: bytes, p2: float, /) -> None: reveal_type(p2) # E: Revealed type is 'builtins.float' [case testWalrus] +from typing import NamedTuple + if (a := 2): reveal_type(a) # E: Revealed type is 'builtins.int' while (b := "x"): reveal_type(b) # E: Revealed type is 'builtins.str' -def f(x: int = (c := 4)) -> None: +def f(x: int = (c := 4)) -> int: if (a := 2): reveal_type(a) # E: Revealed type is 'builtins.int' @@ -226,19 +228,30 @@ def f(x: int = (c := 4)) -> None: if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") pass + # Just make sure we don't crash on this sort of thing. + if (NT := NamedTuple("NT", [("x", int)])): # E: "int" not callable + z2: NT # E: Invalid type "NT" + + if (Alias := int): + z3: Alias # E: Invalid type "Alias" + + return (y8 := 3) + y8 + reveal_type(c) # E: Revealed type is 'builtins.int' [builtins fixtures/f_string.pyi] [case testWalrusNewSemanal] # flags: --new-semantic-analyzer +from typing import NamedTuple + if (a := 2): reveal_type(a) # E: Revealed type is 'builtins.int' while (b := "x"): reveal_type(b) # E: Revealed type is 'builtins.str' -def f(x: int = (c := 4)) -> None: +def f(x: int = (c := 4)) -> int: if (a := 2): reveal_type(a) # E: Revealed type is 'builtins.int' @@ -275,6 +288,15 @@ def f(x: int = (c := 4)) -> None: if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") pass + # Just make sure we don't crash on this sort of thing. + if (NT := NamedTuple("NT", [("x", int)])): # E: "int" not callable + z2: NT # E: Invalid type "NT" + + if (Alias := int): + z3: Alias # E: Invalid type "Alias" + + return (y8 := 3) + y8 + reveal_type(c) # E: Revealed type is 'builtins.int' [builtins fixtures/f_string.pyi] From 6eff366a4ca802fdb5c7e76e50b0f6a9de5761b9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 Jun 2019 19:09:06 -0700 Subject: [PATCH 07/20] fix lint and self-check --- mypy/newsemanal/semanal.py | 4 +++- mypy/semanal.py | 4 +++- mypy/semanal_pass1.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 923575546f09..ad671aacc8ba 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -4273,7 +4273,8 @@ def enter(self, function: Union[FuncItem, GeneratorExpr, DictionaryComprehension """Enter a function, generator or comprehension scope.""" names = self.saved_locals.setdefault(function, SymbolTable()) self.locals.append(names) - self.is_comprehension_stack.append(isinstance(function, (GeneratorExpr, DictionaryComprehension))) + is_comprehension = isinstance(function, (GeneratorExpr, DictionaryComprehension)) + self.is_comprehension_stack.append(is_comprehension) self.global_decls.append(set()) self.nonlocal_decls.append(set()) # -1 since entering block will increment this to 0. @@ -4320,6 +4321,7 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab assert False, "Should have at least one non-comprehension scope" else: names = self.locals[-1] + assert names is not None elif self.type is not None: names = self.type.names else: diff --git a/mypy/semanal.py b/mypy/semanal.py index 90a9f75285ac..e3e35e9d918e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2123,7 +2123,8 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, if escape_comprehension: assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr" if isinstance(lval, NameExpr): - self.analyze_name_lvalue(lval, add_global, explicit_type, is_final, escape_comprehension) + self.analyze_name_lvalue(lval, add_global, explicit_type, is_final, + escape_comprehension=escape_comprehension) elif isinstance(lval, MemberExpr): if not add_global: self.analyze_member_lvalue(lval, explicit_type, is_final) @@ -3755,6 +3756,7 @@ def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context, break else: assert False, "Should have at least one non-comprehension scope" + assert scope is not None else: scope = self.locals[-1] name = node.name() diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index 84191d47ebdb..4cd14e0a3806 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -25,7 +25,7 @@ MypyFile, SymbolTable, SymbolTableNode, Var, Block, AssignmentStmt, FuncDef, Decorator, ClassDef, TypeInfo, ImportFrom, Import, ImportAll, IfStmt, WhileStmt, ForStmt, WithStmt, TryStmt, OverloadedFuncDef, Lvalue, Context, ImportedName, LDEF, GDEF, MDEF, UNBOUND_IMPORTED, - implicit_module_attrs, AssertStmt, + implicit_module_attrs, AssertStmt, AssignmentExpr, ) from mypy.types import Type, UnboundType, UnionType, AnyType, TypeOfAny, NoneType, CallableType from mypy.semanal import SemanticAnalyzerPass2 @@ -154,7 +154,7 @@ def visit_block(self, b: Block) -> None: node.accept(self) self.sem.block_depth[-1] -= 1 - def visit_assignment_expr(self, s: AssignmentStmt) -> None: + def visit_assignment_expr(self, s: AssignmentExpr) -> None: if self.sem.is_module_scope(): self.analyze_lvalue(s.target) From 0f382bb9ceda13d077f73fcf51345873ca9fbf17 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2019 19:03:18 -0700 Subject: [PATCH 08/20] address Jukka's comments --- mypy/newsemanal/semanal.py | 4 +- mypy/semanal.py | 4 +- test-data/unit/check-python38.test | 75 ++++-------------------------- 3 files changed, 12 insertions(+), 71 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index ad671aacc8ba..f852cdc740f3 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2419,8 +2419,8 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, nested: If true, the lvalue is within a tuple or list lvalue expression explicit_type: Assignment has type annotation escape_comprehensions: If we are inside a comprehension, set the variable - in the enclosing scope instead. This implements - https://www.python.org/dev/peps/pep-0572/#scope-of-the-target + in the enclosing scope instead. This implements + https://www.python.org/dev/peps/pep-0572/#scope-of-the-target """ if escape_comprehensions: assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr" diff --git a/mypy/semanal.py b/mypy/semanal.py index e3e35e9d918e..7cb54ea6dfb6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2117,8 +2117,8 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: Add name to globals table only if this is true (used in first pass) explicit_type: Assignment has type annotation escape_comprehension: If we are inside a comprehension, set the variable - in the enclosing scope instead. This implements - https://www.python.org/dev/peps/pep-0572/#scope-of-the-target + in the enclosing scope instead. This implements + https://www.python.org/dev/peps/pep-0572/#scope-of-the-target """ if escape_comprehension: assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr" diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index d45e529f848f..241581c2821a 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -185,17 +185,17 @@ def f(p1: bytes, p2: float, /) -> None: [case testWalrus] from typing import NamedTuple -if (a := 2): +if a := 2: reveal_type(a) # E: Revealed type is 'builtins.int' -while (b := "x"): +while b := "x": reveal_type(b) # E: Revealed type is 'builtins.str' def f(x: int = (c := 4)) -> int: - if (a := 2): + if a := 2: reveal_type(a) # E: Revealed type is 'builtins.int' - while (b := "x"): + while b := "x": reveal_type(b) # E: Revealed type is 'builtins.str' x = (y := 1) + (z := 2) @@ -223,79 +223,20 @@ def f(x: int = (c := 4)) -> int: reveal_type(y7) # E: Revealed type is 'builtins.int' reveal_type((lambda: (y8 := 3) and y8)()) # E: Revealed type is 'builtins.int' + y8 # E: Name 'y8' is not defined y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int") if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") pass # Just make sure we don't crash on this sort of thing. - if (NT := NamedTuple("NT", [("x", int)])): # E: "int" not callable + if NT := NamedTuple("NT", [("x", int)]): # E: "int" not callable z2: NT # E: Invalid type "NT" - if (Alias := int): + if Alias := int: z3: Alias # E: Invalid type "Alias" - return (y8 := 3) + y8 - -reveal_type(c) # E: Revealed type is 'builtins.int' - -[builtins fixtures/f_string.pyi] - -[case testWalrusNewSemanal] -# flags: --new-semantic-analyzer -from typing import NamedTuple - -if (a := 2): - reveal_type(a) # E: Revealed type is 'builtins.int' - -while (b := "x"): - reveal_type(b) # E: Revealed type is 'builtins.str' - -def f(x: int = (c := 4)) -> int: - if (a := 2): - reveal_type(a) # E: Revealed type is 'builtins.int' - - while (b := "x"): - reveal_type(b) # E: Revealed type is 'builtins.str' - - x = (y := 1) + (z := 2) - reveal_type(x) # E: Revealed type is 'builtins.int' - reveal_type(y) # E: Revealed type is 'builtins.int' - reveal_type(z) # E: Revealed type is 'builtins.int' - - l = [y2 := 1, y2 + 2, y2 + 3] - reveal_type(y2) # E: Revealed type is 'builtins.int' - reveal_type(l) # E: Revealed type is 'builtins.list[builtins.int*]' - - filtered_data = [y3 for x in l if (y3 := a) is not None] - reveal_type(filtered_data) # E: Revealed type is 'builtins.list[builtins.int*]' - reveal_type(y3) # E: Revealed type is 'builtins.int' - - # https://www.python.org/dev/peps/pep-0572/#exceptional-cases - (y4 := 3) - reveal_type(y4) # E: Revealed type is 'builtins.int' - - y5 = (y6 := 3) - reveal_type(y5) # E: Revealed type is 'builtins.int' - reveal_type(y6) # E: Revealed type is 'builtins.int' - - f(x=(y7 := 3)) - reveal_type(y7) # E: Revealed type is 'builtins.int' - - reveal_type((lambda: (y8 := 3) and y8)()) # E: Revealed type is 'builtins.int' - - y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int") - if y7 := "x": # E: Incompatible types in assignment (expression has type "str", variable has type "int") - pass - - # Just make sure we don't crash on this sort of thing. - if (NT := NamedTuple("NT", [("x", int)])): # E: "int" not callable - z2: NT # E: Invalid type "NT" - - if (Alias := int): - z3: Alias # E: Invalid type "Alias" - - return (y8 := 3) + y8 + return (y9 := 3) + y9 reveal_type(c) # E: Revealed type is 'builtins.int' From 7c184d2a503e29780ade35fb378d0c227fd782ce Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2019 19:22:19 -0700 Subject: [PATCH 09/20] support Final, check the binder --- mypy/checker.py | 7 +++++-- mypy/checkexpr.py | 1 + test-data/unit/check-python38.test | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 3ad6794c6617..5021c33f19d2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -22,7 +22,7 @@ ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, PromoteExpr, Import, ImportFrom, ImportAll, ImportBase, TypeAlias, ARG_POS, ARG_STAR, LITERAL_TYPE, MDEF, GDEF, - CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, + CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, is_final_node, ) from mypy import nodes @@ -2153,7 +2153,8 @@ def enter_final_context(self, is_final_def: bool) -> Iterator[None]: finally: self._is_final_def = old_ctx - def check_final(self, s: Union[AssignmentStmt, OperatorAssignmentStmt]) -> None: + def check_final(self, + s: Union[AssignmentStmt, OperatorAssignmentStmt, AssignmentExpr]) -> None: """Check if this assignment does not assign to a final attribute. This function performs the check only for name assignments at module @@ -2162,6 +2163,8 @@ def check_final(self, s: Union[AssignmentStmt, OperatorAssignmentStmt]) -> None: """ if isinstance(s, AssignmentStmt): lvs = self.flatten_lvalues(s.lvalues) + elif isinstance(s, AssignmentExpr): + lvs = [s.target] else: lvs = [s.lvalue] is_final_decl = s.is_final_def if isinstance(s, AssignmentStmt) else False diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 7064055ceb91..5573d579b9ed 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2424,6 +2424,7 @@ def check_list_multiply(self, e: OpExpr) -> Type: def visit_assignment_expr(self, e: AssignmentExpr) -> Type: value = self.accept(e.value) self.chk.check_assignment(e.target, e.value) + self.chk.check_final(e) return value def visit_unary_expr(self, e: UnaryExpr) -> Type: diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 241581c2821a..dd323706ca94 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -183,7 +183,8 @@ def f(p1: bytes, p2: float, /) -> None: reveal_type(p2) # E: Revealed type is 'builtins.float' [case testWalrus] -from typing import NamedTuple +from typing import NamedTuple, Optional +from typing_extensions import Final if a := 2: reveal_type(a) # E: Revealed type is 'builtins.int' @@ -240,4 +241,19 @@ def f(x: int = (c := 4)) -> int: reveal_type(c) # E: Revealed type is 'builtins.int' +def check_final() -> None: + x: Final = 3 + + if x := 4: # E: Cannot assign to final name "x" + pass + +def check_binder(x: Optional[int], y: Optional[int]) -> None: + reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' + + (x := 1) + reveal_type(x) # E: Revealed type is 'builtins.int' + + if x or (y := 1): + reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' + [builtins fixtures/f_string.pyi] From 7e2eaf16cc62454445408698ac2d958da569c4b9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2019 19:37:58 -0700 Subject: [PATCH 10/20] partial type test --- 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 dd323706ca94..937001c810ed 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -183,6 +183,7 @@ def f(p1: bytes, p2: float, /) -> None: reveal_type(p2) # E: Revealed type is 'builtins.float' [case testWalrus] +# flags: --strict-optional from typing import NamedTuple, Optional from typing_extensions import Final @@ -256,4 +257,11 @@ def check_binder(x: Optional[int], y: Optional[int]) -> None: if x or (y := 1): reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' +def check_partial() -> None: + x = None + if bool() and (x := 2): + pass + + reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' + [builtins fixtures/f_string.pyi] From 91a87e4b58bc27cc1862b6ec0c43625464899888 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Jun 2019 19:57:25 -0700 Subject: [PATCH 11/20] fix mypyc crash Submitted https://github.com/mypyc/mypyc/issues/637 --- mypy/newsemanal/semanal_shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py index b46e982b23a6..64442285b3c3 100644 --- a/mypy/newsemanal/semanal_shared.py +++ b/mypy/newsemanal/semanal_shared.py @@ -133,7 +133,7 @@ def current_symbol_table(self) -> SymbolTable: @abstractmethod def add_symbol(self, name: str, node: SymbolNode, context: Context, module_public: bool = True, module_hidden: bool = False, - can_defer: bool = True) -> bool: + can_defer: bool = True, escape_comprehensions: bool = False) -> bool: """Add symbol to the current symbol table.""" raise NotImplementedError From 90d2c76d192bad9d8f9f74605c8eb75f02db1cab Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 12 Jun 2019 21:03:01 -0700 Subject: [PATCH 12/20] fix merge conflict for real --- mypy/newsemanal/semanal.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 850738b374c5..5dfada8342b2 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -516,15 +516,12 @@ def file_context(self, # def visit_func_def(self, defn: FuncDef) -> None: -<<<<<<< HEAD # Visit default values because they may contain assignment expressions. for arg in defn.arguments: if arg.initializer: arg.initializer.accept(self) -======= self.statement = defn ->>>>>>> upstream/master defn.is_conditional = self.block_depth[-1] > 0 # Set full names even for those definitions that aren't added From c3c44a193700518b094bb0b25c15705360733964 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 12 Jun 2019 21:03:31 -0700 Subject: [PATCH 13/20] more tests --- test-data/unit/check-python38.test | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index a9e41e9a850c..08d132d24d17 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -255,6 +255,10 @@ def check_binder(x: Optional[int], y: Optional[int]) -> None: if x or (y := 1): reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' + if x and (y := 1): + # TODO should just be int + reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' + def check_partial() -> None: x = None if bool() and (x := 2): @@ -262,4 +266,10 @@ def check_partial() -> None: reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' +def check_partial_list() -> None: + if (x := []): + x.append(3) + + reveal_type(x) # E: Revealed type is 'builtins.list[builtins.int]' + [builtins fixtures/f_string.pyi] From 8f050fea718d4f28472c295922210caf7b44fa42 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 12 Jun 2019 21:42:05 -0700 Subject: [PATCH 14/20] more tests --- test-data/unit/check-python38.test | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 08d132d24d17..13e9445f2c80 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -246,7 +246,8 @@ def check_final() -> None: if x := 4: # E: Cannot assign to final name "x" pass -def check_binder(x: Optional[int], y: Optional[int]) -> None: +def check_binder(x: Optional[int], y: Optional[int], z: Optional[int], a: Optional[int], + b: Optional[int]) -> None: reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' (x := 1) @@ -259,6 +260,15 @@ def check_binder(x: Optional[int], y: Optional[int]) -> None: # TODO should just be int reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' + if (a := 1) and x: + reveal_type(a) # E: Revealed type is 'builtins.int' + + if (b := 1) or x: + reveal_type(b) # E: Revealed type is 'builtins.int' + + if z := 1: + reveal_type(z) # E: Revealed type is 'builtins.int' + def check_partial() -> None: x = None if bool() and (x := 2): From 40d0c4a9cc313b8034d0b18f109cd0a4b82ed93a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 12 Jun 2019 21:43:23 -0700 Subject: [PATCH 15/20] comment on why it's broken --- test-data/unit/check-python38.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 13e9445f2c80..d62b4fa2ccfa 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -258,6 +258,9 @@ def check_binder(x: Optional[int], y: Optional[int], z: Optional[int], a: Option if x and (y := 1): # TODO should just be int + # This is because in check_boolean_op in checkexpr.py we accept the right conditional + # within a binder frame context, so the types assigned in it are lost later. Perhaps + # we need to make find_isinstance_check() walrus-aware. reveal_type(y) # E: Revealed type is 'Union[builtins.int, None]' if (a := 1) and x: From 44bb3ec94d784b4252c9802b5a7be83d6f5a91b8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 4 Jul 2019 09:00:16 -0700 Subject: [PATCH 16/20] fix some reveal_type Notes --- test-data/unit/check-python38.test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 87fcb04bd1c6..b98a6d325cfb 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -264,13 +264,13 @@ def check_binder(x: Optional[int], y: Optional[int], z: Optional[int], a: Option reveal_type(y) # N: Revealed type is 'Union[builtins.int, None]' if (a := 1) and x: - reveal_type(a) # E: Revealed type is 'builtins.int' + reveal_type(a) # N: Revealed type is 'builtins.int' if (b := 1) or x: - reveal_type(b) # E: Revealed type is 'builtins.int' + reveal_type(b) # N: Revealed type is 'builtins.int' if z := 1: - reveal_type(z) # E: Revealed type is 'builtins.int' + reveal_type(z) # N: Revealed type is 'builtins.int' def check_partial() -> None: x = None From d10efb5e8fc3e0ea46033a6bdc828b3a5545e0d3 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 4 Jul 2019 09:01:10 -0700 Subject: [PATCH 17/20] fix test failure Not sure how my branch affects this --- test-data/unit/check-columns.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 17fb8d0695db..aea006bf5441 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -300,7 +300,7 @@ def f(x: T) -> T: y: List[int, str] # E:8: "list" expects 1 type argument, but 2 given del 1[0] # E:5: "int" has no attribute "__delitem__" bb: List[int] = [''] # E:21: List item 0 has incompatible type "str"; expected "int" - aa: List[int] = ['' for x in [1]] # E:22: List comprehension has incompatible type List[str]; expected List[int] + aa: List[int] = ['' for x in [1]] # E:21: List comprehension has incompatible type List[str]; expected List[int] cc = (1).bad # E:11: "int" has no attribute "bad" n: int = '' # E:5: Incompatible types in assignment (expression has type "str", variable has type "int") return x From ca36e907bffa3095f9d4dad7eb65b951eb2a6fbd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 4 Jul 2019 09:12:53 -0700 Subject: [PATCH 18/20] Revert "fix test failure" This reverts commit d10efb5e8fc3e0ea46033a6bdc828b3a5545e0d3. --- test-data/unit/check-columns.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index aea006bf5441..17fb8d0695db 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -300,7 +300,7 @@ def f(x: T) -> T: y: List[int, str] # E:8: "list" expects 1 type argument, but 2 given del 1[0] # E:5: "int" has no attribute "__delitem__" bb: List[int] = [''] # E:21: List item 0 has incompatible type "str"; expected "int" - aa: List[int] = ['' for x in [1]] # E:21: List comprehension has incompatible type List[str]; expected List[int] + aa: List[int] = ['' for x in [1]] # E:22: List comprehension has incompatible type List[str]; expected List[int] cc = (1).bad # E:11: "int" has no attribute "bad" n: int = '' # E:5: Incompatible types in assignment (expression has type "str", variable has type "int") return x From 7ae2f35ea68de18a92d67cc5aa132070fa88f4d4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 4 Jul 2019 12:53:10 -0700 Subject: [PATCH 19/20] fix test failure --- mypy/newsemanal/semanal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index d21091cd5c15..238a94415cd0 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -517,12 +517,13 @@ def file_context(self, # def visit_func_def(self, defn: FuncDef) -> None: + self.statement = defn + # Visit default values because they may contain assignment expressions. for arg in defn.arguments: if arg.initializer: arg.initializer.accept(self) - self.statement = defn defn.is_conditional = self.block_depth[-1] > 0 # Set full names even for those definitions that aren't added From 08c926b7210c9a66ca7cfc84a43ff587bec1ac71 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 10 Aug 2019 09:20:03 -0700 Subject: [PATCH 20/20] fix bad merge --- mypy/semanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8e27323a60d9..846cc3418dbc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4489,7 +4489,7 @@ def name_not_defined(self, name: str, ctx: Context, namespace: Optional[str] = N self.record_incomplete_ref() return message = "Name '{}' is not defined".format(name) - self.fail(message, ctx) + self.fail(message, ctx, code=codes.NAME_DEFINED) if 'builtins.{}'.format(name) in SUGGESTED_TEST_FIXTURES: # The user probably has a missing definition in a test fixture. Let's verify. @@ -4563,8 +4563,8 @@ def is_local_name(self, name: str) -> bool: def fail(self, msg: str, ctx: Context, - *, serious: bool = False, + *, code: Optional[ErrorCode] = None, blocker: bool = False) -> None: if (not serious and