mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +00:00
Don't treat annotations as resolved in forward references (#5060)
## Summary This behavior dates back to a Pyflakes commit (5fc37cbd), which was used to allow this test to pass: ```py from __future__ import annotations T: object def f(t: T): pass def g(t: 'T'): pass ``` But, I think this is an error. Mypy and Pyright don't accept it -- you can only use variables as type annotations if they're type aliases (i.e., annotated with `TypeAlias`), in which case, there has to be an assignment on the right-hand side (see: [PEP 613](https://peps.python.org/pep-0613/)).
This commit is contained in:
parent
f9f08d6b03
commit
364bd82aee
2 changed files with 39 additions and 14 deletions
|
@ -3190,6 +3190,20 @@ mod tests {
|
||||||
r#"
|
r#"
|
||||||
T: object
|
T: object
|
||||||
def g(t: 'T'): pass
|
def g(t: 'T'): pass
|
||||||
|
"#,
|
||||||
|
&[Rule::UndefinedName],
|
||||||
|
);
|
||||||
|
flakes(
|
||||||
|
r#"
|
||||||
|
T = object
|
||||||
|
def f(t: T): pass
|
||||||
|
"#,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
flakes(
|
||||||
|
r#"
|
||||||
|
T = object
|
||||||
|
def g(t: 'T'): pass
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
@ -3381,6 +3395,16 @@ mod tests {
|
||||||
T: object
|
T: object
|
||||||
def f(t: T): pass
|
def f(t: T): pass
|
||||||
def g(t: 'T'): pass
|
def g(t: 'T'): pass
|
||||||
|
"#,
|
||||||
|
&[Rule::UndefinedName, Rule::UndefinedName],
|
||||||
|
);
|
||||||
|
|
||||||
|
flakes(
|
||||||
|
r#"
|
||||||
|
from __future__ import annotations
|
||||||
|
T = object
|
||||||
|
def f(t: T): pass
|
||||||
|
def g(t: 'T'): pass
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
|
|
@ -177,6 +177,7 @@ impl<'a> SemanticModel<'a> {
|
||||||
// should prefer it over local resolutions.
|
// should prefer it over local resolutions.
|
||||||
if self.in_forward_reference() {
|
if self.in_forward_reference() {
|
||||||
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
||||||
|
if !self.bindings[binding_id].kind.is_annotation() {
|
||||||
// Mark the binding as used.
|
// Mark the binding as used.
|
||||||
let context = self.execution_context();
|
let context = self.execution_context();
|
||||||
let reference_id = self.references.push(ScopeId::global(), range, context);
|
let reference_id = self.references.push(ScopeId::global(), range, context);
|
||||||
|
@ -193,6 +194,7 @@ impl<'a> SemanticModel<'a> {
|
||||||
return ResolvedRead::Resolved(binding_id);
|
return ResolvedRead::Resolved(binding_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut seen_function = false;
|
let mut seen_function = false;
|
||||||
let mut import_starred = false;
|
let mut import_starred = false;
|
||||||
|
@ -226,8 +228,7 @@ impl<'a> SemanticModel<'a> {
|
||||||
self.bindings[binding_id].references.push(reference_id);
|
self.bindings[binding_id].references.push(reference_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// But if it's a type annotation, don't treat it as resolved, unless we're in a
|
// But if it's a type annotation, don't treat it as resolved. For example, given:
|
||||||
// forward reference. For example, given:
|
|
||||||
//
|
//
|
||||||
// ```python
|
// ```python
|
||||||
// name: str
|
// name: str
|
||||||
|
@ -236,7 +237,7 @@ impl<'a> SemanticModel<'a> {
|
||||||
//
|
//
|
||||||
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
|
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
|
||||||
// `name: str` should be treated as used.
|
// `name: str` should be treated as used.
|
||||||
if !self.in_forward_reference() && self.bindings[binding_id].kind.is_annotation() {
|
if self.bindings[binding_id].kind.is_annotation() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue