Skip to content

Commit

Permalink
[used before def] rework builtin handling (#14483)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ilinum authored Jan 24, 2023
1 parent eee3a2a commit 8b30913
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 8 deletions.
5 changes: 4 additions & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
)

Expand Down
20 changes: 13 additions & 7 deletions mypy/partially_defined.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
ListExpr,
Lvalue,
MatchStmt,
MypyFile,
NameExpr,
NonlocalDecl,
RaiseStmt,
RefExpr,
ReturnStmt,
StarExpr,
SymbolTable,
TryStmt,
TupleExpr,
WhileStmt,
Expand Down Expand Up @@ -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
Expand All @@ -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 = 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()
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-possibly-undefined.test
Original file line number Diff line number Diff line change
Expand Up @@ -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 `type` 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.
Expand Down

0 comments on commit 8b30913

Please sign in to comment.