diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 260cd5cb3dfbc..94ec97a7eb73c 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,47 @@ 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() + return True + 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