mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +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
|
@ -56,7 +56,7 @@ use ruff_python_semantic::{
|
|||
Import, Module, ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel,
|
||||
SemanticModelFlags, StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{MAGIC_GLOBALS, python_builtins};
|
||||
use ruff_python_stdlib::builtins::{python_builtins, python_magic_globals};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{OneIndexed, SourceFile, SourceFileBuilder, SourceRow};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
@ -2550,7 +2550,7 @@ impl<'a> Checker<'a> {
|
|||
for builtin in standard_builtins {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in MAGIC_GLOBALS {
|
||||
for builtin in python_magic_globals(target_version.minor) {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in &settings.builtins {
|
||||
|
|
|
@ -920,6 +920,42 @@ mod tests {
|
|||
flakes("__annotations__", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_warningregistry() {
|
||||
// Using __warningregistry__ should not be considered undefined.
|
||||
flakes("__warningregistry__", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_annotate_py314_available() {
|
||||
// __annotate__ is available starting in Python 3.14.
|
||||
let diagnostics = crate::test::test_snippet(
|
||||
"__annotate__",
|
||||
&crate::settings::LinterSettings {
|
||||
unresolved_target_version: ruff_python_ast::PythonVersion::PY314.into(),
|
||||
..crate::settings::LinterSettings::for_rules(vec![
|
||||
crate::codes::Rule::UndefinedName,
|
||||
])
|
||||
},
|
||||
);
|
||||
assert!(diagnostics.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_annotate_pre_py314_undefined() {
|
||||
// __annotate__ is not available before Python 3.14.
|
||||
let diagnostics = crate::test::test_snippet(
|
||||
"__annotate__",
|
||||
&crate::settings::LinterSettings {
|
||||
unresolved_target_version: ruff_python_ast::PythonVersion::PY313.into(),
|
||||
..crate::settings::LinterSettings::for_rules(vec![
|
||||
crate::codes::Rule::UndefinedName,
|
||||
])
|
||||
},
|
||||
);
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn magic_globals_file() {
|
||||
// Use of the C{__file__} magic global should not emit an undefined name
|
||||
|
|
|
@ -20,9 +20,15 @@ pub const MAGIC_GLOBALS: &[&str] = &[
|
|||
"__annotations__",
|
||||
"__builtins__",
|
||||
"__cached__",
|
||||
"__warningregistry__",
|
||||
"__file__",
|
||||
];
|
||||
|
||||
/// Magic globals that are only available starting in specific Python versions.
|
||||
///
|
||||
/// `__annotate__` was introduced in Python 3.14.
|
||||
static PY314_PLUS_MAGIC_GLOBALS: &[&str] = &["__annotate__"];
|
||||
|
||||
static ALWAYS_AVAILABLE_BUILTINS: &[&str] = &[
|
||||
"ArithmeticError",
|
||||
"AssertionError",
|
||||
|
@ -216,6 +222,21 @@ pub fn python_builtins(minor_version: u8, is_notebook: bool) -> impl Iterator<It
|
|||
.copied()
|
||||
}
|
||||
|
||||
/// Return the list of magic globals for the given Python minor version.
|
||||
pub fn python_magic_globals(minor_version: u8) -> impl Iterator<Item = &'static str> {
|
||||
let py314_magic_globals = if minor_version >= 14 {
|
||||
Some(PY314_PLUS_MAGIC_GLOBALS)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
py314_magic_globals
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(MAGIC_GLOBALS)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns `true` if the given name is that of a Python builtin.
|
||||
///
|
||||
/// Intended to be kept in sync with [`python_builtins`].
|
||||
|
|
|
@ -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> {
|
||||
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`.
|
||||
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()
|
||||
"__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.
|
||||
else if module_type_symbols(db)
|
||||
_ if module_type_symbols(db)
|
||||
.iter()
|
||||
.any(|module_type_member| &**module_type_member == name)
|
||||
.any(|module_type_member| &**module_type_member == name) =>
|
||||
{
|
||||
KnownClass::ModuleType.to_instance(db).member(db, name)
|
||||
} else {
|
||||
Place::Unbound.into()
|
||||
}
|
||||
|
||||
_ => 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