From 3a149869ef7f81552e0bf5eb1663855ad1c15161 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Fri, 20 Jan 2023 18:05:14 -0800 Subject: [PATCH 1/2] [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 2/2] 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