mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +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#"
|
||||
T: object
|
||||
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
|
||||
def f(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,20 +177,22 @@ impl<'a> SemanticModel<'a> {
|
|||
// should prefer it over local resolutions.
|
||||
if self.in_forward_reference() {
|
||||
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
||||
// Mark the binding as used.
|
||||
let context = self.execution_context();
|
||||
let reference_id = self.references.push(ScopeId::global(), range, context);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
|
||||
// Mark any submodule aliases as used.
|
||||
if let Some(binding_id) =
|
||||
self.resolve_submodule(symbol, ScopeId::global(), binding_id)
|
||||
{
|
||||
if !self.bindings[binding_id].kind.is_annotation() {
|
||||
// Mark the binding as used.
|
||||
let context = self.execution_context();
|
||||
let reference_id = self.references.push(ScopeId::global(), range, context);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
return ResolvedRead::Resolved(binding_id);
|
||||
// Mark any submodule aliases as used.
|
||||
if let Some(binding_id) =
|
||||
self.resolve_submodule(symbol, ScopeId::global(), binding_id)
|
||||
{
|
||||
let reference_id = self.references.push(ScopeId::global(), range, context);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
return ResolvedRead::Resolved(binding_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,8 +228,7 @@ impl<'a> SemanticModel<'a> {
|
|||
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
|
||||
// forward reference. For example, given:
|
||||
// But if it's a type annotation, don't treat it as resolved. For example, given:
|
||||
//
|
||||
// ```python
|
||||
// name: str
|
||||
|
@ -236,7 +237,7 @@ impl<'a> SemanticModel<'a> {
|
|||
//
|
||||
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
|
||||
// `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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue