Repository: python/mypy (Python)

Issue:
Used before def false positive when aliasing builtin definition
The first error is a false positive, since `range` is available in builtins:
```py
_range = range  # error: Name "range" is used before definition

_C = C  # error: Name "C" is used before definition

class range:
    pass

class C:
    pass
```
This is not reported consistently. In this case there is no error:
```py
_range = range  # No error!

class range:
    pass
```

I assume the error is only generated when we perform two or more semantic analysis passes.

This is a regression introduced in #14166.


Hints:

Patch:
diff --git a/mypy/build.py b/mypy/build.py
--- 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
--- 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 = 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()
@@ -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.


Test patch:
diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test
--- 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 `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.