Skip to content

Commit 8b30913

Browse files
authored
[used before def] rework builtin handling (#14483)
When doing multiple passes, in the example below, `range` will refer to current's module range. When doing a single pass, `range` will refer to `builtins.range`: ```python _range = range _C = C # error: Name "C" is used before definition class range: pass class C: pass ``` Instead of looking at the output of semanal to check if a variable is resolving to a `builtins` package, we can just check if it's part of builtins module. Fixes #14476.
1 parent eee3a2a commit 8b30913

File tree

3 files changed

+27
-8
lines changed

3 files changed

+27
-8
lines changed

mypy/build.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2359,7 +2359,10 @@ def detect_possibly_undefined_vars(self) -> None:
23592359
) or manager.errors.is_error_code_enabled(codes.USED_BEFORE_DEF):
23602360
self.tree.accept(
23612361
PossiblyUndefinedVariableVisitor(
2362-
MessageBuilder(manager.errors, manager.modules), self.type_map(), self.options
2362+
MessageBuilder(manager.errors, manager.modules),
2363+
self.type_map(),
2364+
self.options,
2365+
self.tree.names,
23632366
)
23642367
)
23652368

mypy/partially_defined.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
ListExpr,
2828
Lvalue,
2929
MatchStmt,
30+
MypyFile,
3031
NameExpr,
3132
NonlocalDecl,
3233
RaiseStmt,
33-
RefExpr,
3434
ReturnStmt,
3535
StarExpr,
36+
SymbolTable,
3637
TryStmt,
3738
TupleExpr,
3839
WhileStmt,
@@ -286,10 +287,6 @@ def is_undefined(self, name: str) -> bool:
286287
return self._scope().branch_stmts[-1].is_undefined(name)
287288

288289

289-
def refers_to_builtin(o: RefExpr) -> bool:
290-
return o.fullname.startswith("builtins.")
291-
292-
293290
class Loop:
294291
def __init__(self) -> None:
295292
self.has_break = False
@@ -314,11 +311,20 @@ class PossiblyUndefinedVariableVisitor(ExtendedTraverserVisitor):
314311
"""
315312

316313
def __init__(
317-
self, msg: MessageBuilder, type_map: dict[Expression, Type], options: Options
314+
self,
315+
msg: MessageBuilder,
316+
type_map: dict[Expression, Type],
317+
options: Options,
318+
names: SymbolTable,
318319
) -> None:
319320
self.msg = msg
320321
self.type_map = type_map
321322
self.options = options
323+
self.builtins = SymbolTable()
324+
builtins_mod = names.get("__builtins__", None)
325+
if builtins_mod:
326+
assert isinstance(builtins_mod.node, MypyFile)
327+
self.builtins = builtins_mod.node.names
322328
self.loops: list[Loop] = []
323329
self.try_depth = 0
324330
self.tracker = DefinedVariableTracker()
@@ -597,7 +603,7 @@ def visit_starred_pattern(self, o: StarredPattern) -> None:
597603
super().visit_starred_pattern(o)
598604

599605
def visit_name_expr(self, o: NameExpr) -> None:
600-
if refers_to_builtin(o):
606+
if o.name in self.builtins:
601607
return
602608
if self.tracker.is_possibly_undefined(o.name):
603609
# A variable is only defined in some branches.

test-data/unit/check-possibly-undefined.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,16 @@ def f0() -> None:
909909
type = "abc"
910910
a = type
911911

912+
[case testUsedBeforeDefBuiltinsMultipass]
913+
# flags: --enable-error-code used-before-def
914+
915+
# When doing multiple passes, mypy resolves references slightly differently.
916+
# In this case, it would refer the earlier `type` call to the range class defined below.
917+
_type = type # No error
918+
_C = C # E: Name "C" is used before definition
919+
class type: pass
920+
class C: pass
921+
912922
[case testUsedBeforeDefImplicitModuleAttrs]
913923
# flags: --enable-error-code used-before-def
914924
a = __name__ # No error.

0 commit comments

Comments
 (0)