mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:14:52 +00:00
[pyflakes
] Fix false positives for __annotate__
(Py3.14+) and __warningregistry__
(F821
) (#20154)
## Summary Fixes #19970 --------- Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
parent
742f8a4ee6
commit
346842f003
7 changed files with 153 additions and 28 deletions
|
@ -0,0 +1,31 @@
|
|||
# `__annotate__` as an implicit global is version-gated (Py3.14+)
|
||||
|
||||
## Absent before 3.14
|
||||
|
||||
`__annotate__` is never present in the global namespace on Python \<3.14.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(__annotate__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Present in 3.14+
|
||||
|
||||
The `__annotate__` global may be present in Python 3.14, but only if at least one global symbol in
|
||||
the module is annotated (e.g. `x: int` or `x: int = 42`). Currently we model `__annotate__` as
|
||||
always being possibly unbound on Python 3.14+.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.14"
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(__annotate__) # revealed: (format: int, /) -> dict[str, Any]
|
||||
```
|
|
@ -16,6 +16,8 @@ reveal_type(__doc__) # revealed: str | None
|
|||
reveal_type(__spec__) # revealed: ModuleSpec | None
|
||||
reveal_type(__path__) # revealed: MutableSequence[str]
|
||||
reveal_type(__builtins__) # revealed: Any
|
||||
# error: [possibly-unresolved-reference] "Name `__warningregistry__` used when possibly not defined"
|
||||
reveal_type(__warningregistry__) # revealed: dict[Any, int]
|
||||
|
||||
import sys
|
||||
|
||||
|
@ -75,6 +77,8 @@ reveal_type(module.__file__) # revealed: Unknown | None
|
|||
reveal_type(module.__path__) # revealed: list[str]
|
||||
reveal_type(module.__doc__) # revealed: Unknown
|
||||
reveal_type(module.__spec__) # revealed: Unknown | ModuleSpec | None
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(module.__warningregistry__) # revealed: Unknown
|
||||
|
||||
def nested_scope():
|
||||
global __loader__
|
||||
|
|
|
@ -1339,12 +1339,15 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
|
|||
|
||||
mod implicit_globals {
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::Program;
|
||||
use crate::db::Db;
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::place::{Boundness, PlaceAndQualifiers};
|
||||
use crate::semantic_index::symbol::Symbol;
|
||||
use crate::semantic_index::{place_table, use_def_map};
|
||||
use crate::types::{KnownClass, Type};
|
||||
use crate::types::{CallableType, KnownClass, Parameter, Parameters, Signature, Type};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::{Place, place_from_declarations};
|
||||
|
||||
|
@ -1392,28 +1395,58 @@ mod implicit_globals {
|
|||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
// We special-case `__file__` here because we know that for an internal implicit global
|
||||
// lookup in a Python module, it is always a string, even though typeshed says `str |
|
||||
// None`.
|
||||
if name == "__file__" {
|
||||
Place::bound(KnownClass::Str.to_instance(db)).into()
|
||||
} else if name == "__builtins__" {
|
||||
Place::bound(Type::any()).into()
|
||||
} else if name == "__debug__" {
|
||||
Place::bound(KnownClass::Bool.to_instance(db)).into()
|
||||
}
|
||||
// In general we wouldn't check to see whether a symbol exists on a class before doing the
|
||||
// `.member()` call on the instance type -- we'd just do the `.member`() call on the instance
|
||||
// type, since it has the same end result. The reason to only call `.member()` on `ModuleType`
|
||||
// when absolutely necessary is that this function is used in a very hot path (name resolution
|
||||
// in `infer.rs`). We use less idiomatic (and much more verbose) code here as a micro-optimisation.
|
||||
else if module_type_symbols(db)
|
||||
.iter()
|
||||
.any(|module_type_member| &**module_type_member == name)
|
||||
{
|
||||
KnownClass::ModuleType.to_instance(db).member(db, name)
|
||||
} else {
|
||||
Place::Unbound.into()
|
||||
match name {
|
||||
// We special-case `__file__` here because we know that for an internal implicit global
|
||||
// lookup in a Python module, it is always a string, even though typeshed says `str |
|
||||
// None`.
|
||||
"__file__" => Place::bound(KnownClass::Str.to_instance(db)).into(),
|
||||
|
||||
"__builtins__" => Place::bound(Type::any()).into(),
|
||||
|
||||
"__debug__" => Place::bound(KnownClass::Bool.to_instance(db)).into(),
|
||||
|
||||
// Created lazily by the warnings machinery; may be absent.
|
||||
// Model as possibly-unbound to avoid false negatives.
|
||||
"__warningregistry__" => Place::Type(
|
||||
KnownClass::Dict
|
||||
.to_specialized_instance(db, [Type::any(), KnownClass::Int.to_instance(db)]),
|
||||
Boundness::PossiblyUnbound,
|
||||
)
|
||||
.into(),
|
||||
|
||||
// Marked as possibly-unbound as it is only present in the module namespace
|
||||
// if at least one global symbol is annotated in the module.
|
||||
"__annotate__" if Program::get(db).python_version(db) >= PythonVersion::PY314 => {
|
||||
let signature = Signature::new(
|
||||
Parameters::new(
|
||||
[Parameter::positional_only(Some(Name::new_static("format")))
|
||||
.with_annotated_type(KnownClass::Int.to_instance(db))],
|
||||
),
|
||||
Some(KnownClass::Dict.to_specialized_instance(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db), Type::any()],
|
||||
)),
|
||||
);
|
||||
Place::Type(
|
||||
CallableType::function_like(db, signature),
|
||||
Boundness::PossiblyUnbound,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
// In general we wouldn't check to see whether a symbol exists on a class before doing the
|
||||
// `.member()` call on the instance type -- we'd just do the `.member`() call on the instance
|
||||
// type, since it has the same end result. The reason to only call `.member()` on `ModuleType`
|
||||
// when absolutely necessary is that this function is used in a very hot path (name resolution
|
||||
// in `infer.rs`). We use less idiomatic (and much more verbose) code here as a micro-optimisation.
|
||||
_ if module_type_symbols(db)
|
||||
.iter()
|
||||
.any(|module_type_member| &**module_type_member == name) =>
|
||||
{
|
||||
KnownClass::ModuleType.to_instance(db).member(db, name)
|
||||
}
|
||||
|
||||
_ => Place::Unbound.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ pub(crate) use self::infer::{
|
|||
infer_expression_types, infer_isolated_expression, infer_scope_types,
|
||||
static_expression_truthiness,
|
||||
};
|
||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||
pub(crate) use self::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{KnownModule, resolve_module};
|
||||
|
@ -64,7 +64,7 @@ pub use crate::types::ide_support::{
|
|||
use crate::types::infer::infer_unpack_types;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
|
||||
use crate::types::signatures::{ParameterForm, walk_signature};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
|
||||
use crate::types::variance::{TypeVarVariance, VarianceInferable};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue