From 3a149869ef7f81552e0bf5eb1663855ad1c15161 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Fri, 20 Jan 2023 18:05:14 -0800 Subject: [PATCH 01/10] [used before def] rework builtin handling --- mypy/build.py | 5 ++++- mypy/partially_defined.py | 20 +++++++++++++------- test-data/unit/check-possibly-undefined.test | 10 ++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 1747c4518c63..a4817d1866c7 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2359,7 +2359,10 @@ def detect_possibly_undefined_vars(self) -> None: ) or manager.errors.is_error_code_enabled(codes.USED_BEFORE_DEF): self.tree.accept( PossiblyUndefinedVariableVisitor( - MessageBuilder(manager.errors, manager.modules), self.type_map(), self.options + MessageBuilder(manager.errors, manager.modules), + self.type_map(), + self.options, + self.tree.names, ) ) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 9a58df04371f..1b25c4055b96 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -27,12 +27,13 @@ ListExpr, Lvalue, MatchStmt, + MypyFile, NameExpr, NonlocalDecl, RaiseStmt, - RefExpr, ReturnStmt, StarExpr, + SymbolTable, TryStmt, TupleExpr, WhileStmt, @@ -286,10 +287,6 @@ def is_undefined(self, name: str) -> bool: return self._scope().branch_stmts[-1].is_undefined(name) -def refers_to_builtin(o: RefExpr) -> bool: - return o.fullname.startswith("builtins.") - - class Loop: def __init__(self) -> None: self.has_break = False @@ -314,11 +311,20 @@ class PossiblyUndefinedVariableVisitor(ExtendedTraverserVisitor): """ def __init__( - self, msg: MessageBuilder, type_map: dict[Expression, Type], options: Options + self, + msg: MessageBuilder, + type_map: dict[Expression, Type], + options: Options, + names: SymbolTable, ) -> None: self.msg = msg self.type_map = type_map self.options = options + self.builtins: set[str] = set() + builtins_mod = names.get("__builtins__", None) + if builtins_mod: + assert isinstance(builtins_mod.node, MypyFile) + self.builtins = set(builtins_mod.node.names.keys()) self.loops: list[Loop] = [] self.try_depth = 0 self.tracker = DefinedVariableTracker() @@ -597,7 +603,7 @@ def visit_starred_pattern(self, o: StarredPattern) -> None: super().visit_starred_pattern(o) def visit_name_expr(self, o: NameExpr) -> None: - if refers_to_builtin(o): + if o.name in self.builtins: return if self.tracker.is_possibly_undefined(o.name): # A variable is only defined in some branches. diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 802635c30b35..fcdf091e7c82 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -909,6 +909,16 @@ def f0() -> None: type = "abc" a = type +[case testUsedBeforeDefBuiltinsMultipass] +# flags: --enable-error-code used-before-def + +# When doing multiple passes, mypy resolves references slightly differently. +# In this case, it would refer the earlier `range` call to the range class defined below. +_type = type # No error +_C = C # E: Name "C" is used before definition +class type: pass +class C: pass + [case testUsedBeforeDefImplicitModuleAttrs] # flags: --enable-error-code used-before-def a = __name__ # No error. From 5415d4ef97b7e10216dd451d1f8e74a847e90a29 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 23 Jan 2023 07:43:07 -0800 Subject: [PATCH 02/10] store SymbolTable instance + update comment --- mypy/partially_defined.py | 4 ++-- test-data/unit/check-possibly-undefined.test | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 1b25c4055b96..af09493c9cae 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -320,11 +320,11 @@ def __init__( self.msg = msg self.type_map = type_map self.options = options - self.builtins: set[str] = set() + self.builtins = SymbolTable() builtins_mod = names.get("__builtins__", None) if builtins_mod: assert isinstance(builtins_mod.node, MypyFile) - self.builtins = set(builtins_mod.node.names.keys()) + self.builtins = builtins_mod.node.names self.loops: list[Loop] = [] self.try_depth = 0 self.tracker = DefinedVariableTracker() diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index fcdf091e7c82..29c4868e97af 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -913,7 +913,7 @@ def f0() -> None: # flags: --enable-error-code used-before-def # When doing multiple passes, mypy resolves references slightly differently. -# In this case, it would refer the earlier `range` call to the range class defined below. +# In this case, it would refer the earlier `type` call to the range class defined below. _type = type # No error _C = C # E: Name "C" is used before definition class type: pass From 342c05dc7c34a3496d4901379cd474092ef31715 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 23 Jan 2023 14:05:09 -0800 Subject: [PATCH 03/10] rework scope inheritance in defs --- mypy/partially_defined.py | 24 ++-- test-data/unit/check-possibly-undefined.test | 117 ++++++++++++------- 2 files changed, 88 insertions(+), 53 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index af09493c9cae..71c7b699e7b6 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -216,9 +216,11 @@ def _scope(self) -> Scope: def enter_scope(self, scope_type: ScopeType) -> None: assert len(self._scope().branch_stmts) > 0 - self.scopes.append( - Scope([BranchStatement(self._scope().branch_stmts[-1].branches[-1])], scope_type) - ) + initial_state = self._scope().branch_stmts[-1].branches[-1] + if scope_type == ScopeType.Func: + # Empty branch state. + initial_state = BranchState() + self.scopes.append(Scope([BranchStatement(initial_state)], scope_type)) def exit_scope(self) -> None: self.scopes.pop() @@ -320,14 +322,14 @@ def __init__( self.msg = msg self.type_map = type_map self.options = options - self.builtins = SymbolTable() - builtins_mod = names.get("__builtins__", None) - if builtins_mod: - assert isinstance(builtins_mod.node, MypyFile) - self.builtins = builtins_mod.node.names self.loops: list[Loop] = [] self.try_depth = 0 self.tracker = DefinedVariableTracker() + builtins_mod = names.get("__builtins__", None) + if builtins_mod: + assert isinstance(builtins_mod.node, MypyFile) + for name in builtins_mod.node.names: + self.tracker.record_definition(name) for name in implicit_module_attrs: self.tracker.record_definition(name) @@ -342,13 +344,15 @@ def variable_may_be_undefined(self, name: str, context: Context) -> None: def process_definition(self, name: str) -> None: # Was this name previously used? If yes, it's a used-before-definition error. if not self.tracker.in_scope(ScopeType.Class): - # Errors in class scopes are caught by the semantic analyzer. refs = self.tracker.pop_undefined_ref(name) for ref in refs: if self.loops: self.variable_may_be_undefined(name, ref) else: self.var_used_before_def(name, ref) + else: + # Errors in class scopes are caught by the semantic analyzer. + pass self.tracker.record_definition(name) def visit_global_decl(self, o: GlobalDecl) -> None: @@ -603,8 +607,6 @@ def visit_starred_pattern(self, o: StarredPattern) -> None: super().visit_starred_pattern(o) def visit_name_expr(self, o: NameExpr) -> None: - if o.name in self.builtins: - return if self.tracker.is_possibly_undefined(o.name): # A variable is only defined in some branches. self.variable_may_be_undefined(o.name, o) diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 29c4868e97af..c38ec3a9b606 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -210,7 +210,6 @@ def f0() -> None: y = x x = 1 # No error. - [case testGlobalDeclarationAfterUsage] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def f0() -> None: @@ -219,6 +218,7 @@ def f0() -> None: x = 1 # No error. x = 2 + [case testVarDefinedInOuterScope] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def f0() -> None: @@ -227,6 +227,7 @@ def f0() -> None: f0() x = 1 + [case testDefinedInOuterScopeNoError] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def foo() -> None: @@ -234,6 +235,49 @@ def foo() -> None: def bar() -> None: foo() + +[case testClassFromOuterScopeRedefined] +# flags: --enable-error-code possibly-undefined --enable-error-code used-before-def +class c: pass + +def f0() -> None: + s = c() # E: Name "c" is used before definition + class c: pass + + +def f1() -> None: + s = c() # No error. + + +def f2() -> None: + s = c() # E: Name "c" is used before definition + if int(): + class c: pass + +[case testVarFromOuterScopeRedefined] +# flags: --enable-error-code possibly-undefined --enable-error-code used-before-def +x = 0 + +def f0() -> None: + y = x # E: Name "x" is used before definition + x = 0 + +def f1() -> None: + y = x # No error. + +def f2() -> None: + y = x # E: Name "x" is used before definition + global x + +def f3() -> None: + global x + y = x # No error. + +def f4() -> None: + if int(): + x = 0 + y = x # E: Name "x" may be undefined + [case testFuncParams] # flags: --enable-error-code possibly-undefined def foo(a: int) -> None: @@ -829,67 +873,56 @@ def f4() -> None: x = z # E: Name "z" is used before definition z: int = 2 -[case testUsedBeforeDefImportsBasic] -# flags: --enable-error-code used-before-def +[case testUsedBeforeDefImportsBasicImportNoError] +# flags: --enable-error-code used-before-def --enable-error-code possibly-undefined --disable-error-code no-redef import foo # type: ignore -import x.y # type: ignore -def f0() -> None: - a = foo # No error. - foo: int = 1 +a = foo # No error. +foo: int = 1 -def f1() -> None: - a = y # E: Name "y" is used before definition - y: int = 1 +[case testUsedBeforeDefImportsDotImport] +# flags: --enable-error-code used-before-def --enable-error-code possibly-undefined --disable-error-code no-redef +import x.y # type: ignore -def f2() -> None: - a = x # No error. - x: int = 1 +a = y # E: Name "y" is used before definition +y: int = 1 -def f3() -> None: - a = x.y # No error. - x: int = 1 +b = x # No error. +x: int = 1 + +c = x.y # No error. +x: int = 1 [case testUsedBeforeDefImportBasicRename] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code=no-redef import x.y as z # type: ignore from typing import Any -def f0() -> None: - a = z # No error. - z: int = 1 +a = z # No error. +z: int = 1 -def f1() -> None: - a = x # E: Name "x" is used before definition - x: int = 1 +a = x # E: Name "x" is used before definition +x: int = 1 -def f2() -> None: - a = x.y # E: Name "x" is used before definition - x: Any = 1 - -def f3() -> None: - a = y # E: Name "y" is used before definition - y: int = 1 +a = y # E: Name "y" is used before definition +y: int = 1 [case testUsedBeforeDefImportFrom] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code no-redef from foo import x # type: ignore -def f0() -> None: - a = x # No error. - x: int = 1 +a = x # No error. +x: int = 1 [case testUsedBeforeDefImportFromRename] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code no-redef from foo import x as y # type: ignore -def f0() -> None: - a = y # No error. - y: int = 1 +a = y # No error. +y: int = 1 -def f1() -> None: - a = x # E: Name "x" is used before definition - x: int = 1 +a = x # E: Name "x" is used before definition +x: int = 1 [case testUsedBeforeDefFunctionDeclarations] # flags: --enable-error-code used-before-def @@ -905,7 +938,7 @@ def f0() -> None: # flags: --enable-error-code used-before-def def f0() -> None: - s = type(123) + s = type(123) # E: Name "type" is used before definition type = "abc" a = type From ddc44188bcb0e9dee30d7eb2b104e960eb04e4fa Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 23 Jan 2023 14:32:14 -0800 Subject: [PATCH 04/10] extend fixes to all non-generator scopes --- mypy/partially_defined.py | 12 +++++++----- test-data/unit/check-possibly-undefined.test | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 71c7b699e7b6..ece594130f48 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -79,7 +79,9 @@ def copy(self) -> BranchState: class BranchStatement: - def __init__(self, initial_state: BranchState) -> None: + def __init__(self, initial_state: BranchState | None = None) -> None: + if initial_state is None: + initial_state = BranchState() self.initial_state = initial_state self.branches: list[BranchState] = [ BranchState( @@ -171,7 +173,7 @@ class ScopeType(Enum): Global = 1 Class = 2 Func = 3 - Generator = 3 + Generator = 4 class Scope: @@ -199,7 +201,7 @@ class DefinedVariableTracker: def __init__(self) -> None: # There's always at least one scope. Within each scope, there's at least one "global" BranchingStatement. - self.scopes: list[Scope] = [Scope([BranchStatement(BranchState())], ScopeType.Global)] + self.scopes: list[Scope] = [Scope([BranchStatement()], ScopeType.Global)] # disable_branch_skip is used to disable skipping a branch due to a return/raise/etc. This is useful # in things like try/except/finally statements. self.disable_branch_skip = False @@ -217,8 +219,8 @@ def _scope(self) -> Scope: def enter_scope(self, scope_type: ScopeType) -> None: assert len(self._scope().branch_stmts) > 0 initial_state = self._scope().branch_stmts[-1].branches[-1] - if scope_type == ScopeType.Func: - # Empty branch state. + if scope_type != ScopeType.Generator: + # Generators are special in that they inherit the outer scope. initial_state = BranchState() self.scopes.append(Scope([BranchStatement(initial_state)], scope_type)) diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index c38ec3a9b606..39c33173eaaf 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -942,6 +942,9 @@ def f0() -> None: type = "abc" a = type +def f1() -> None: + s = type(123) + [case testUsedBeforeDefBuiltinsMultipass] # flags: --enable-error-code used-before-def From 8e69cd4994a9b45770e1b53532e50d1d860f930a Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 23 Jan 2023 14:43:04 -0800 Subject: [PATCH 05/10] minor refactor --- mypy/partially_defined.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index ece594130f48..d19bf69cd910 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -218,10 +218,10 @@ def _scope(self) -> Scope: def enter_scope(self, scope_type: ScopeType) -> None: assert len(self._scope().branch_stmts) > 0 - initial_state = self._scope().branch_stmts[-1].branches[-1] - if scope_type != ScopeType.Generator: - # Generators are special in that they inherit the outer scope. - initial_state = BranchState() + initial_state = None + if scope_type == ScopeType.Generator: + # Generators are special because they inherit the outer scope. + initial_state = self._scope().branch_stmts[-1].branches[-1] self.scopes.append(Scope([BranchStatement(initial_state)], scope_type)) def exit_scope(self) -> None: From 33561c2bc3a2255198fd77e5d16164efc92cfb92 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Tue, 24 Jan 2023 09:48:55 -0600 Subject: [PATCH 06/10] test whitespace --- test-data/unit/check-possibly-undefined.test | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 39c33173eaaf..198fa1cf9a1a 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -244,11 +244,9 @@ def f0() -> None: s = c() # E: Name "c" is used before definition class c: pass - def f1() -> None: s = c() # No error. - def f2() -> None: s = c() # E: Name "c" is used before definition if int(): From 43ddea4c838278b90af2af91ba12b5c03f8908de Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Wed, 8 Feb 2023 09:09:26 -0800 Subject: [PATCH 07/10] fix mypyc test --- mypyc/test-data/run-sets.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/run-sets.test b/mypyc/test-data/run-sets.test index 56c946933fac..8d178d03a75b 100644 --- a/mypyc/test-data/run-sets.test +++ b/mypyc/test-data/run-sets.test @@ -141,8 +141,8 @@ def test_in_set() -> None: assert main_set(item), f"{item!r} should be in set_main" assert not main_negated_set(item), item - assert non_final_name_set(non_const) global non_const + assert non_final_name_set(non_const) non_const = "updated" assert non_final_name_set("updated") From b5a02a4eef3d9c4734a6ddce77ab54f9b2487716 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Wed, 8 Feb 2023 09:23:43 -0800 Subject: [PATCH 08/10] fix default args --- mypy/partially_defined.py | 19 +++++++++++++------ test-data/unit/check-possibly-undefined.test | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 3c85aae40616..22385a902ddc 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -426,17 +426,24 @@ def visit_match_stmt(self, o: MatchStmt) -> None: def visit_func_def(self, o: FuncDef) -> None: self.process_definition(o.name) - self.tracker.enter_scope(ScopeType.Func) super().visit_func_def(o) - self.tracker.exit_scope() def visit_func(self, o: FuncItem) -> None: if o.is_dynamic() and not self.options.check_untyped_defs: return - if o.arguments is not None: - for arg in o.arguments: - self.tracker.record_definition(arg.variable.name) - super().visit_func(o) + + args = o.arguments or [] + # Process initializers (defaults) outside the function scope. + for arg in args: + if arg.initializer is not None: + arg.initializer.accept(self) + + self.tracker.enter_scope(ScopeType.Func) + for arg in args: + self.process_definition(arg.variable.name) + super().visit_var(arg.variable) + o.body.accept(self) + self.tracker.exit_scope() def visit_generator_expr(self, o: GeneratorExpr) -> None: self.tracker.enter_scope(ScopeType.Generator) diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 198fa1cf9a1a..d4f1b6a2b9fd 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -252,6 +252,10 @@ def f2() -> None: if int(): class c: pass +glob = c() +def f3(x: c = glob) -> None: + glob = 123 + [case testVarFromOuterScopeRedefined] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def x = 0 From 78b0f552b28dafd700f5f0bf62bcbba72b0aee9a Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Wed, 8 Feb 2023 10:00:11 -0800 Subject: [PATCH 09/10] fix tests --- test-data/unit/check-functions.test | 39 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c23bbb77f643..b76abd31e3dc 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -491,62 +491,61 @@ if int(): [case testDefaultArgumentExpressions] import typing +class B: pass +class A: pass + def f(x: 'A' = A()) -> None: b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") a = x # type: A - -class B: pass -class A: pass [out] [case testDefaultArgumentExpressions2] import typing -def f(x: 'A' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") - b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") - a = x # type: A - class B: pass class A: pass +def f(x: 'A' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") + b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") + a = x # type: A [case testDefaultArgumentExpressionsGeneric] from typing import TypeVar T = TypeVar('T', bound='A') -def f(x: T = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "T") - b = x # type: B # E: Incompatible types in assignment (expression has type "T", variable has type "B") - a = x # type: A class B: pass class A: pass +def f(x: T = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "T") + b = x # type: B # E: Incompatible types in assignment (expression has type "T", variable has type "B") + a = x # type: A [case testDefaultArgumentsWithSubtypes] import typing +class A: pass +class B(A): pass + def f(x: 'B' = A()) -> None: # E: Incompatible default for argument "x" (default has type "A", argument has type "B") pass def g(x: 'A' = B()) -> None: pass - -class A: pass -class B(A): pass [out] [case testMultipleDefaultArgumentExpressions] import typing +class A: pass +class B: pass + def f(x: 'A' = B(), y: 'B' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") pass def h(x: 'A' = A(), y: 'B' = B()) -> None: pass - -class A: pass -class B: pass [out] [case testMultipleDefaultArgumentExpressions2] import typing -def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible default for argument "y" (default has type "A", argument has type "B") - pass - class A: pass class B: pass + +def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible default for argument "y" (default has type "A", argument has type "B") + pass [out] [case testDefaultArgumentsAndSignatureAsComment] @@ -2612,7 +2611,7 @@ def f() -> int: ... [case testLambdaDefaultTypeErrors] lambda a=(1 + 'asdf'): a # E: Unsupported operand types for + ("int" and "str") lambda a=nonsense: a # E: Name "nonsense" is not defined -def f(x: int = i): # E: Name "i" is not defined # E: Name "i" is used before definition +def f(x: int = i): # E: Name "i" is not defined i = 42 [case testRevealTypeOfCallExpressionReturningNoneWorks] From 39c22d55a43766f3590eb4a2176c62d2a33baba1 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Wed, 1 Mar 2023 07:45:29 -0800 Subject: [PATCH 10/10] more efficient builtin handling --- mypy/partially_defined.py | 7 ++----- test-data/unit/check-possibly-undefined.test | 22 +++++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index a1c2d2dc12b6..085384989705 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -332,11 +332,6 @@ def __init__( self.loops: list[Loop] = [] self.try_depth = 0 self.tracker = DefinedVariableTracker() - builtins_mod = names.get("__builtins__", None) - if builtins_mod: - assert isinstance(builtins_mod.node, MypyFile) - for name in builtins_mod.node.names: - self.tracker.record_definition(name) for name in implicit_module_attrs: self.tracker.record_definition(name) @@ -621,6 +616,8 @@ def visit_starred_pattern(self, o: StarredPattern) -> None: super().visit_starred_pattern(o) def visit_name_expr(self, o: NameExpr) -> None: + if o.name in self.builtins and self.tracker.in_scope(ScopeType.Global): + return if self.tracker.is_possibly_undefined(o.name): # A variable is only defined in some branches. self.variable_may_be_undefined(o.name, o) diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index d4f1b6a2b9fd..ebceef88b537 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -936,7 +936,7 @@ def f0() -> None: inner() # No error. inner = lambda: None -[case testUsedBeforeDefBuiltins] +[case testUsedBeforeDefBuiltinsFunc] # flags: --enable-error-code used-before-def def f0() -> None: @@ -947,6 +947,26 @@ def f0() -> None: def f1() -> None: s = type(123) +[case testUsedBeforeDefBuiltinsGlobal] +# flags: --enable-error-code used-before-def + +s = type(123) +type = "abc" +a = type + +[case testUsedBeforeDefBuiltinsClass] +# flags: --enable-error-code used-before-def + +class C: + s = type + type = s + +[case testUsedBeforeDefBuiltinsGenerator] +# flags: --enable-error-code used-before-def + +def f0() -> None: + _ = [type for type in [type("a"), type(1)]] + [case testUsedBeforeDefBuiltinsMultipass] # flags: --enable-error-code used-before-def