mirror of https://github.com/astral-sh/ruff
[ty] now that inference sees nested bindings, allow unresolved globals
This commit is contained in:
parent
a6569ed960
commit
5ffed301ef
|
|
@ -1299,15 +1299,12 @@ reveal_type(Nope) # revealed: Unknown
|
|||
## `global` statements in non-global scopes
|
||||
|
||||
Python allows `global` statements in function bodies to add new variables to the global scope, but
|
||||
we require a matching global binding or declaration. We lint on unresolved `global` statements, and
|
||||
we don't include the symbols they might define in `*` imports:
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
def f():
|
||||
# error: [unresolved-global] "Invalid global declaration of `g`: `g` has no declarations or bindings in the global scope"
|
||||
# error: [unresolved-global] "Invalid global declaration of `h`: `h` has no declarations or bindings in the global scope"
|
||||
global g, h
|
||||
|
||||
g = True
|
||||
|
|
|
|||
|
|
@ -241,26 +241,32 @@ def f():
|
|||
# TODO: reveal_type(x) # revealed: Unknown | Literal["1"]
|
||||
```
|
||||
|
||||
## Global variables need an explicit definition in the global scope
|
||||
## Global variables don't need an explicit definition in the global scope
|
||||
|
||||
You're allowed to use the `global` keyword to define new global variables that don't have any
|
||||
explicit definition in the global scope, but we consider that fishy and prefer to lint on it:
|
||||
explicit definition in the global scope:
|
||||
|
||||
```py
|
||||
x = 1
|
||||
y: int
|
||||
# z is neither bound nor declared in the global scope
|
||||
|
||||
def f():
|
||||
global x, y, z # error: [unresolved-global] "Invalid global declaration of `z`: `z` has no declarations or bindings in the global scope"
|
||||
global x
|
||||
x = 42
|
||||
|
||||
def g():
|
||||
print(x) # allowed, resolves to the global `x` defined by `f`
|
||||
|
||||
def h():
|
||||
print(y) # error: [unresolved-reference]
|
||||
```
|
||||
|
||||
You don't need a definition for implicit globals, but you do for built-ins:
|
||||
However, this only affects the "public" type of the global. It's still considered unbound when
|
||||
module-scope code refers to it locally.
|
||||
|
||||
```py
|
||||
def f():
|
||||
global __file__ # allowed, implicit global
|
||||
global int # error: [unresolved-global] "Invalid global declaration of `int`: `int` has no declarations or bindings in the global scope"
|
||||
global x
|
||||
x = 42
|
||||
|
||||
print(x) # error: [unresolved-reference]
|
||||
```
|
||||
|
||||
## References to variables before they are defined within a class scope are considered global
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ use crate::types::diagnostic::{
|
|||
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT,
|
||||
TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
|
||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type,
|
||||
TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type,
|
||||
report_instance_layout_conflict, report_invalid_argument_number_to_special_form,
|
||||
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
|
||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||
|
|
@ -2547,8 +2547,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::Stmt::Raise(raise) => self.infer_raise_statement(raise),
|
||||
ast::Stmt::Return(ret) => self.infer_return_statement(ret),
|
||||
ast::Stmt::Delete(delete) => self.infer_delete_statement(delete),
|
||||
ast::Stmt::Global(global) => self.infer_global_statement(global),
|
||||
ast::Stmt::Nonlocal(_)
|
||||
ast::Stmt::Global(_)
|
||||
| ast::Stmt::Nonlocal(_)
|
||||
| ast::Stmt::Break(_)
|
||||
| ast::Stmt::Continue(_)
|
||||
| ast::Stmt::Pass(_)
|
||||
|
|
@ -5299,61 +5299,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
fn infer_global_statement(&mut self, global: &ast::StmtGlobal) {
|
||||
// CPython allows examples like this, where a global variable is never explicitly defined
|
||||
// in the global scope:
|
||||
//
|
||||
// ```py
|
||||
// def f():
|
||||
// global x
|
||||
// x = 1
|
||||
// def g():
|
||||
// print(x)
|
||||
// ```
|
||||
//
|
||||
// However, allowing this pattern would make it hard for us to guarantee
|
||||
// accurate analysis about the types and boundness of global-scope symbols,
|
||||
// so we require the variable to be explicitly defined (either bound or declared)
|
||||
// in the global scope.
|
||||
let ast::StmtGlobal {
|
||||
node_index: _,
|
||||
range: _,
|
||||
names,
|
||||
} = global;
|
||||
let global_place_table = self.index.place_table(FileScopeId::global());
|
||||
for name in names {
|
||||
if let Some(symbol_id) = global_place_table.symbol_id(name) {
|
||||
let symbol = global_place_table.symbol(symbol_id);
|
||||
if symbol.is_bound() || symbol.is_declared() {
|
||||
// This name is explicitly defined in the global scope (not just in function
|
||||
// bodies that mark it `global`).
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !module_type_implicit_global_symbol(self.db(), name)
|
||||
.place
|
||||
.is_unbound()
|
||||
{
|
||||
// This name is an implicit global like `__file__` (but not a built-in like `int`).
|
||||
continue;
|
||||
}
|
||||
// This variable isn't explicitly defined in the global scope, nor is it an
|
||||
// implicit global from `types.ModuleType`, so we consider this `global` statement invalid.
|
||||
let Some(builder) = self.context.report_lint(&UNRESOLVED_GLOBAL, name) else {
|
||||
return;
|
||||
};
|
||||
let mut diag =
|
||||
builder.into_diagnostic(format_args!("Invalid global declaration of `{name}`"));
|
||||
diag.set_primary_message(format_args!(
|
||||
"`{name}` has no declarations or bindings in the global scope"
|
||||
));
|
||||
diag.info("This limits ty's ability to make accurate inferences about the boundness and types of global-scope symbols");
|
||||
diag.info(format_args!(
|
||||
"Consider adding a declaration to the global scope, e.g. `{name}: int`"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn module_type_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
|
||||
resolve_module(self.db(), module_name)
|
||||
.map(|module| Type::module_literal(self.db(), self.file(), module))
|
||||
|
|
|
|||
Loading…
Reference in New Issue