mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 15:15:33 +00:00
[ty] Add subdiagnostic suggestion to unresolved-reference
diagnostic when variable exists on self
(#18444)
## Summary Closes https://github.com/astral-sh/ty/issues/502. In the following example: ```py class Foo: x: int def method(self): y = x ``` The user may intended to use `y = self.x` in `method`. This is now added as a subdiagnostic in the following form : `info: An attribute with the same name as 'x' is defined, consider using 'self.x'` ## Test Plan Added mdtest with snapshot diagnostics.
This commit is contained in:
parent
f1883d71a4
commit
e658778ced
4 changed files with 146 additions and 2 deletions
|
@ -928,6 +928,42 @@ def _(flag1: bool, flag2: bool):
|
||||||
reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"]
|
reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Invalid access to attribute
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
If a non-declared variable is used and an attribute with the same name is defined and accessible,
|
||||||
|
then we emit a subdiagnostic suggesting the use of `self.`.
|
||||||
|
(`An attribute with the same name as 'x' is defined, consider using 'self.x'` in these cases)
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
def method(self):
|
||||||
|
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
y = x
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo:
|
||||||
|
x: int = 1
|
||||||
|
|
||||||
|
def method(self):
|
||||||
|
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
y = x
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo:
|
||||||
|
def __init__(self):
|
||||||
|
self.x = 1
|
||||||
|
|
||||||
|
def method(self):
|
||||||
|
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
y = x
|
||||||
|
```
|
||||||
|
|
||||||
## Unions of attributes
|
## Unions of attributes
|
||||||
|
|
||||||
If the (meta)class is a union type or if the attribute on the (meta) class has a union type, we
|
If the (meta)class is a union type or if the attribute on the (meta) class has a union type, we
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: attributes.md - Attributes - Invalid access to attribute
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Foo:
|
||||||
|
2 | x: int
|
||||||
|
3 |
|
||||||
|
4 | def method(self):
|
||||||
|
5 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
6 | y = x
|
||||||
|
7 | class Foo:
|
||||||
|
8 | x: int = 1
|
||||||
|
9 |
|
||||||
|
10 | def method(self):
|
||||||
|
11 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
12 | y = x
|
||||||
|
13 | class Foo:
|
||||||
|
14 | def __init__(self):
|
||||||
|
15 | self.x = 1
|
||||||
|
16 |
|
||||||
|
17 | def method(self):
|
||||||
|
18 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
19 | y = x
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-reference]: Name `x` used when not defined
|
||||||
|
--> src/mdtest_snippet.py:6:13
|
||||||
|
|
|
||||||
|
4 | def method(self):
|
||||||
|
5 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
6 | y = x
|
||||||
|
| ^
|
||||||
|
7 | class Foo:
|
||||||
|
8 | x: int = 1
|
||||||
|
|
|
||||||
|
info: An attribute `x` is available, consider using `self.x`
|
||||||
|
info: rule `unresolved-reference` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-reference]: Name `x` used when not defined
|
||||||
|
--> src/mdtest_snippet.py:12:13
|
||||||
|
|
|
||||||
|
10 | def method(self):
|
||||||
|
11 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
12 | y = x
|
||||||
|
| ^
|
||||||
|
13 | class Foo:
|
||||||
|
14 | def __init__(self):
|
||||||
|
|
|
||||||
|
info: An attribute `x` is available, consider using `self.x`
|
||||||
|
info: rule `unresolved-reference` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-reference]: Name `x` used when not defined
|
||||||
|
--> src/mdtest_snippet.py:19:13
|
||||||
|
|
|
||||||
|
17 | def method(self):
|
||||||
|
18 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||||
|
19 | y = x
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
info: An attribute `x` is available, consider using `self.x`
|
||||||
|
info: rule `unresolved-reference` is enabled by default
|
||||||
|
|
||||||
|
```
|
|
@ -1778,7 +1778,11 @@ pub(super) fn report_possibly_unbound_attribute(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) {
|
pub(super) fn report_unresolved_reference(
|
||||||
|
context: &InferContext,
|
||||||
|
expr_name_node: &ast::ExprName,
|
||||||
|
attribute_exists: bool,
|
||||||
|
) {
|
||||||
let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else {
|
let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1795,6 +1799,12 @@ pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node
|
||||||
"resolving types",
|
"resolving types",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attribute_exists {
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"An attribute `{id}` is available, consider using `self.{id}`"
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) {
|
pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) {
|
||||||
|
|
|
@ -1718,6 +1718,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
fn class_context_of_current_method(&self) -> Option<ClassLiteral<'db>> {
|
fn class_context_of_current_method(&self) -> Option<ClassLiteral<'db>> {
|
||||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||||
let current_scope = self.index.scope(current_scope_id);
|
let current_scope = self.index.scope(current_scope_id);
|
||||||
|
if current_scope.kind() != ScopeKind::Function {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let parent_scope_id = current_scope.parent()?;
|
let parent_scope_id = current_scope.parent()?;
|
||||||
let parent_scope = self.index.scope(parent_scope_id);
|
let parent_scope = self.index.scope(parent_scope_id);
|
||||||
|
|
||||||
|
@ -5899,7 +5902,20 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||||
LookupError::Unbound(qualifiers) => {
|
LookupError::Unbound(qualifiers) => {
|
||||||
if self.is_reachable(name_node) {
|
if self.is_reachable(name_node) {
|
||||||
report_unresolved_reference(&self.context, name_node);
|
let attribute_exists =
|
||||||
|
if let Some(class) = self.class_context_of_current_method() {
|
||||||
|
let symbol = Type::instance(db, class.default_specialization(db))
|
||||||
|
.member(db, symbol_name)
|
||||||
|
.symbol;
|
||||||
|
match symbol {
|
||||||
|
Symbol::Type(..) => true,
|
||||||
|
Symbol::Unbound => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
report_unresolved_reference(&self.context, name_node, attribute_exists);
|
||||||
}
|
}
|
||||||
TypeAndQualifiers::new(Type::unknown(), qualifiers)
|
TypeAndQualifiers::new(Type::unknown(), qualifiers)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue