Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[used before def] rework builtin handling #14483

Merged
merged 3 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this should only affect references to global definitions. Can you add a test case to ensure that a local variable named type is still processed? A potential fix would be to check if fullname starts with builtins..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It think that you can also check that the kind of SymbolTableNode is GDEF.

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