From 27f8b4f7282093e66d76db354c9cb2b7becf2586 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 3 Jul 2019 14:35:58 +0100 Subject: [PATCH 1/2] New semantic analyzer: fix issues with 'X = X' assignments Fixes #6404. --- mypy/newsemanal/semanal.py | 45 ++++++++++++++ test-data/unit/check-newsemanal.test | 82 ++++++++++++++++++++++++++ test-data/unit/check-type-aliases.test | 1 - 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 260cd5cb3dfbc..5f4657346aeaa 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -1824,6 +1824,11 @@ def visit_import_all(self, i: ImportAll) -> None: def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.statement = s + + # Special case assignment like X = X. + if self.analyze_identity_global_assignment(s): + return + tag = self.track_incomplete_refs() s.rvalue.accept(self) if self.found_incomplete_ref(tag) or self.should_wait_rhs(s.rvalue): @@ -1868,6 +1873,46 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.process_module_assignment(s.lvalues, s.rvalue, s) self.process__all__(s) + def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool: + """Special case 'X = X' in global scope. + + This allows supporting some important use cases. + + Return true if special casing was applied. + """ + if not isinstance(s.rvalue, NameExpr) or len(s.lvalues) != 1: + # Not of form 'X = X' + return False + lvalue = s.lvalues[0] + if not isinstance(lvalue, NameExpr) or s.rvalue.name != lvalue.name: + # Not of form 'X = X' + return False + if self.type is not None or self.is_func_scope(): + # Not in global scope + return False + # It's an assignment like 'X = X' in the global scope. + name = lvalue.name + sym = self.lookup(name, s) + if sym is None: + if self.final_iteration: + # Fall back to normal assignment analysis. + return False + else: + self.defer() + else: + if sym.node is None: + # Something special -- fall back to normal assignment analysis. + return False + if name not in self.globals: + # The name is from builtins. Add an alias to the current module. + self.add_symbol(name, sym.node, s) + if not isinstance(sym.node, PlaceholderNode): + for node in s.rvalue, lvalue: + node.node = sym.node + node.kind = GDEF + node.fullname = sym.node.fullname() + return True + def should_wait_rhs(self, rv: Expression) -> bool: """Can we already classify this r.h.s. of an assignment or should we wait? diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 5314210706a30..29c7100e8f683 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2886,3 +2886,85 @@ def f() -> None: x, (y, *z) = t reveal_type(z) # N: Revealed type is 'builtins.list[builtins.str*]' [builtins fixtures/list.pyi] + +[case testNewAnalyzerIdentityAssignment1] +from foo import * + +try: + X = X +except: + class X: # E: Name 'X' already defined (possibly by an import) + pass + +reveal_type(X()) # N: Revealed type is 'foo.X' + +[file foo.py] +class X: pass + +[case testNewAnalyzerIdentityAssignment2] +try: + int = int + reveal_type(int()) # N: Revealed type is 'builtins.int' +except: + class int: # E: Name 'int' already defined (possibly by an import) + pass + +reveal_type(int()) # N: Revealed type is 'builtins.int' + +[case testNewAnalyzerIdentityAssignment3] +forwardref: C + +try: + int = int + reveal_type(int()) # N: Revealed type is 'builtins.int' +except: + class int: # E: Name 'int' already defined (possibly by an import) + pass + +reveal_type(int()) # N: Revealed type is 'builtins.int' + +class C: pass + +[case testNewAnalyzerIdentityAssignment4] +try: + C = C + C +except: + class C: + pass + +reveal_type(C()) # N: Revealed type is '__main__.C' + +[case testNewAnalyzerIdentityAssignment5] +forwardref: D + +try: + C = C + C +except: + class C: + pass + +class D: pass + +reveal_type(C()) # N: Revealed type is '__main__.C' + +[case testNewAnalyzerIdentityAssignment6] +x: C +class C: + pass +C = C + +reveal_type(C()) # N: Revealed type is '__main__.C' +reveal_type(x) # N: Revealed type is '__main__.C' + +[case testNewAnalyzerIdentityAssignment7] +C = C # E: Name 'C' is not defined + +reveal_type(C) # N: Revealed type is 'Any' \ + # E: Name 'C' is not defined + +[case testNewAnalyzerIdentityAssignment8] +from typing import Final +x: Final = 0 +x = x # E: Cannot assign to final name "x" diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 41434cd43c646..ae3ef301686aa 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -589,7 +589,6 @@ def foo(x: Bogus[int]) -> None: [builtins fixtures/dict.pyi] [case testOverrideByIdemAliasCorrectType] -# flags: --no-new-semantic-analyzer C = C class C: # type: ignore pass From 790b9a67e84afa74c748e6baf5b08d21fa18bba7 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 4 Jul 2019 10:03:16 +0100 Subject: [PATCH 2/2] Update based on feedback --- 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 5f4657346aeaa..94ec97a7eb73c 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -1899,6 +1899,7 @@ def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool: return False else: self.defer() + return True else: if sym.node is None: # Something special -- fall back to normal assignment analysis. @@ -1911,7 +1912,7 @@ def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool: node.node = sym.node node.kind = GDEF node.fullname = sym.node.fullname() - return True + return True def should_wait_rhs(self, rv: Expression) -> bool: """Can we already classify this r.h.s. of an assignment or should we wait?