mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 14:51:25 +00:00
[ty] Don't add incorrect subdiagnostic for unresolved reference (#18487)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
57bd7d055d
commit
a3c79d8170
5 changed files with 422 additions and 73 deletions
|
@ -946,9 +946,9 @@ def _(flag1: bool, flag2: bool):
|
|||
|
||||
<!-- 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)
|
||||
If an undefined variable is used in a method, and an attribute with the same name is defined and
|
||||
accessible, then we emit a subdiagnostic suggesting the use of `self.`. (These don't appear inline
|
||||
here; see the diagnostic snapshots.)
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
|
@ -978,6 +978,107 @@ class Foo:
|
|||
y = x
|
||||
```
|
||||
|
||||
In a staticmethod, we don't suggest that it might be an attribute.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
self.x = 42
|
||||
|
||||
@staticmethod
|
||||
def static_method():
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
In a classmethod, if the name matches a class attribute, we suggest `cls.`.
|
||||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
class Foo:
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
@classmethod
|
||||
def class_method(cls):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
In a classmethod, if the name matches an instance-only attribute, we don't suggest anything.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
self.x = 42
|
||||
|
||||
@classmethod
|
||||
def class_method(cls):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
We also don't suggest anything if the method is (invalidly) decorated with both `@classmethod` and
|
||||
`@staticmethod`:
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
x: ClassVar[int]
|
||||
|
||||
@classmethod
|
||||
@staticmethod
|
||||
def class_method(cls):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
In an instance method that uses some other parameter name in place of `self`, we use that parameter
|
||||
name in the sub-diagnostic.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
self.x = 42
|
||||
|
||||
def method(other):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
In a classmethod that uses some other parameter name in place of `cls`, we use that parameter name
|
||||
in the sub-diagnostic.
|
||||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
class Foo:
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
@classmethod
|
||||
def class_method(c_other):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
We don't suggest anything if an instance method or a classmethod only has variadic arguments, or if
|
||||
the first parameter is keyword-only:
|
||||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
class Foo:
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
def instance_method(*args, **kwargs):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
print(x)
|
||||
|
||||
@classmethod
|
||||
def class_method(*, cls):
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
```
|
||||
|
||||
## Unions of attributes
|
||||
|
||||
If the (meta)class is a union type or if the attribute on the (meta) class has a union type, we
|
||||
|
|
|
@ -31,6 +31,68 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
|||
17 | def method(self):
|
||||
18 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
19 | y = x
|
||||
20 | class Foo:
|
||||
21 | def __init__(self):
|
||||
22 | self.x = 42
|
||||
23 |
|
||||
24 | @staticmethod
|
||||
25 | def static_method():
|
||||
26 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
27 | y = x
|
||||
28 | from typing import ClassVar
|
||||
29 |
|
||||
30 | class Foo:
|
||||
31 | x: ClassVar[int] = 42
|
||||
32 |
|
||||
33 | @classmethod
|
||||
34 | def class_method(cls):
|
||||
35 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
36 | y = x
|
||||
37 | class Foo:
|
||||
38 | def __init__(self):
|
||||
39 | self.x = 42
|
||||
40 |
|
||||
41 | @classmethod
|
||||
42 | def class_method(cls):
|
||||
43 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
44 | y = x
|
||||
45 | class Foo:
|
||||
46 | x: ClassVar[int]
|
||||
47 |
|
||||
48 | @classmethod
|
||||
49 | @staticmethod
|
||||
50 | def class_method(cls):
|
||||
51 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
52 | y = x
|
||||
53 | class Foo:
|
||||
54 | def __init__(self):
|
||||
55 | self.x = 42
|
||||
56 |
|
||||
57 | def method(other):
|
||||
58 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
59 | y = x
|
||||
60 | from typing import ClassVar
|
||||
61 |
|
||||
62 | class Foo:
|
||||
63 | x: ClassVar[int] = 42
|
||||
64 |
|
||||
65 | @classmethod
|
||||
66 | def class_method(c_other):
|
||||
67 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
68 | y = x
|
||||
69 | from typing import ClassVar
|
||||
70 |
|
||||
71 | class Foo:
|
||||
72 | x: ClassVar[int] = 42
|
||||
73 |
|
||||
74 | def instance_method(*args, **kwargs):
|
||||
75 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
76 | print(x)
|
||||
77 |
|
||||
78 | @classmethod
|
||||
79 | def class_method(*, cls):
|
||||
80 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
81 | y = x
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
@ -75,8 +137,128 @@ error[unresolved-reference]: Name `x` used when not defined
|
|||
18 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
19 | y = x
|
||||
| ^
|
||||
20 | class Foo:
|
||||
21 | 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:27:13
|
||||
|
|
||||
25 | def static_method():
|
||||
26 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
27 | y = x
|
||||
| ^
|
||||
28 | from typing import ClassVar
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:36:13
|
||||
|
|
||||
34 | def class_method(cls):
|
||||
35 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
36 | y = x
|
||||
| ^
|
||||
37 | class Foo:
|
||||
38 | def __init__(self):
|
||||
|
|
||||
info: An attribute `x` is available: consider using `cls.x`
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:44:13
|
||||
|
|
||||
42 | def class_method(cls):
|
||||
43 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
44 | y = x
|
||||
| ^
|
||||
45 | class Foo:
|
||||
46 | x: ClassVar[int]
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:52:13
|
||||
|
|
||||
50 | def class_method(cls):
|
||||
51 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
52 | y = x
|
||||
| ^
|
||||
53 | class Foo:
|
||||
54 | def __init__(self):
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:59:13
|
||||
|
|
||||
57 | def method(other):
|
||||
58 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
59 | y = x
|
||||
| ^
|
||||
60 | from typing import ClassVar
|
||||
|
|
||||
info: An attribute `x` is available: consider using `other.x`
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:68:13
|
||||
|
|
||||
66 | def class_method(c_other):
|
||||
67 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
68 | y = x
|
||||
| ^
|
||||
69 | from typing import ClassVar
|
||||
|
|
||||
info: An attribute `x` is available: consider using `c_other.x`
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:76:15
|
||||
|
|
||||
74 | def instance_method(*args, **kwargs):
|
||||
75 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
76 | print(x)
|
||||
| ^
|
||||
77 |
|
||||
78 | @classmethod
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-reference]: Name `x` used when not defined
|
||||
--> src/mdtest_snippet.py:81:13
|
||||
|
|
||||
79 | def class_method(*, cls):
|
||||
80 | # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
81 | y = x
|
||||
| ^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -581,7 +581,7 @@ pub enum ScopeKind {
|
|||
}
|
||||
|
||||
impl ScopeKind {
|
||||
pub(crate) fn is_eager(self) -> bool {
|
||||
pub(crate) const fn is_eager(self) -> bool {
|
||||
match self {
|
||||
ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => true,
|
||||
ScopeKind::Annotation
|
||||
|
@ -591,7 +591,7 @@ impl ScopeKind {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function_like(self) -> bool {
|
||||
pub(crate) const fn is_function_like(self) -> bool {
|
||||
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
|
||||
// place table also uses the term "function-like" for these scopes.
|
||||
matches!(
|
||||
|
@ -604,13 +604,17 @@ impl ScopeKind {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_class(self) -> bool {
|
||||
pub(crate) const fn is_class(self) -> bool {
|
||||
matches!(self, ScopeKind::Class)
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self) -> bool {
|
||||
pub(crate) const fn is_type_parameter(self) -> bool {
|
||||
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_non_lambda_function(self) -> bool {
|
||||
matches!(self, ScopeKind::Function)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`PlaceExpr`] table for a specific [`Scope`].
|
||||
|
|
|
@ -74,7 +74,8 @@ use crate::types::generics::GenericContext;
|
|||
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, DynamicType, Type, TypeMapping, TypeRelation, TypeVarInstance,
|
||||
BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation,
|
||||
TypeVarInstance,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -116,6 +117,38 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
impl FunctionDecorators {
|
||||
pub(super) fn from_decorator_type(db: &dyn Db, decorator_type: Type) -> Self {
|
||||
match decorator_type {
|
||||
Type::FunctionLiteral(function) => match function.known(db) {
|
||||
Some(KnownFunction::NoTypeCheck) => FunctionDecorators::NO_TYPE_CHECK,
|
||||
Some(KnownFunction::Overload) => FunctionDecorators::OVERLOAD,
|
||||
Some(KnownFunction::AbstractMethod) => FunctionDecorators::ABSTRACT_METHOD,
|
||||
Some(KnownFunction::Final) => FunctionDecorators::FINAL,
|
||||
Some(KnownFunction::Override) => FunctionDecorators::OVERRIDE,
|
||||
_ => FunctionDecorators::empty(),
|
||||
},
|
||||
Type::ClassLiteral(class) => match class.known(db) {
|
||||
Some(KnownClass::Classmethod) => FunctionDecorators::CLASSMETHOD,
|
||||
Some(KnownClass::Staticmethod) => FunctionDecorators::STATICMETHOD,
|
||||
_ => FunctionDecorators::empty(),
|
||||
},
|
||||
_ => FunctionDecorators::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_decorator_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = Type<'db>>,
|
||||
) -> Self {
|
||||
types
|
||||
.into_iter()
|
||||
.fold(FunctionDecorators::empty(), |acc, ty| {
|
||||
acc | FunctionDecorators::from_decorator_type(db, ty)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the
|
||||
/// arguments that were passed in. For the precise meaning of the fields, see [1].
|
||||
|
|
|
@ -1937,29 +1937,42 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
binding_type(self.db(), class_definition).into_class_literal()
|
||||
}
|
||||
|
||||
/// If the current scope is a (non-lambda) function, return that function's AST node.
|
||||
///
|
||||
/// If the current scope is not a function (or it is a lambda function), return `None`.
|
||||
fn current_function_definition(&self) -> Option<&ast::StmtFunctionDef> {
|
||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||
let current_scope = self.index.scope(current_scope_id);
|
||||
if !current_scope.kind().is_non_lambda_function() {
|
||||
return None;
|
||||
}
|
||||
current_scope.node().as_function(self.module())
|
||||
}
|
||||
|
||||
fn function_decorator_types<'a>(
|
||||
&'a self,
|
||||
function: &'a ast::StmtFunctionDef,
|
||||
) -> impl Iterator<Item = Type<'db>> + 'a {
|
||||
let definition = self.index.expect_single_definition(function);
|
||||
let scope = definition.scope(self.db());
|
||||
let definition_types = infer_definition_types(self.db(), definition);
|
||||
|
||||
function.decorator_list.iter().map(move |decorator| {
|
||||
definition_types
|
||||
.expression_type(decorator.expression.scoped_expression_id(self.db(), scope))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the current scope is the function body scope of a function overload (that
|
||||
/// is, the stub declaration decorated with `@overload`, not the implementation), or an
|
||||
/// abstract method (decorated with `@abstractmethod`.)
|
||||
fn in_function_overload_or_abstractmethod(&self) -> bool {
|
||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||
let current_scope = self.index.scope(current_scope_id);
|
||||
|
||||
let function_scope = match current_scope.kind() {
|
||||
ScopeKind::Function => current_scope,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let NodeWithScopeKind::Function(node_ref) = function_scope.node() else {
|
||||
let Some(function) = self.current_function_definition() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
node_ref
|
||||
.node(self.module())
|
||||
.decorator_list
|
||||
.iter()
|
||||
.any(|decorator| {
|
||||
let decorator_type = self.file_expression_type(&decorator.expression);
|
||||
|
||||
self.function_decorator_types(function)
|
||||
.any(|decorator_type| {
|
||||
match decorator_type {
|
||||
Type::FunctionLiteral(function) => matches!(
|
||||
function.known(self.db()),
|
||||
|
@ -2179,55 +2192,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let mut dataclass_transformer_params = None;
|
||||
|
||||
for decorator in decorator_list {
|
||||
let decorator_ty = self.infer_decorator(decorator);
|
||||
let decorator_type = self.infer_decorator(decorator);
|
||||
let decorator_function_decorator =
|
||||
FunctionDecorators::from_decorator_type(self.db(), decorator_type);
|
||||
function_decorators |= decorator_function_decorator;
|
||||
|
||||
match decorator_ty {
|
||||
match decorator_type {
|
||||
Type::FunctionLiteral(function) => {
|
||||
match function.known(self.db()) {
|
||||
Some(KnownFunction::NoTypeCheck) => {
|
||||
if let Some(KnownFunction::NoTypeCheck) = function.known(self.db()) {
|
||||
// If the function is decorated with the `no_type_check` decorator,
|
||||
// we need to suppress any errors that come after the decorators.
|
||||
self.context.set_in_no_type_check(InNoTypeCheck::Yes);
|
||||
function_decorators |= FunctionDecorators::NO_TYPE_CHECK;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::Overload) => {
|
||||
function_decorators |= FunctionDecorators::OVERLOAD;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::AbstractMethod) => {
|
||||
function_decorators |= FunctionDecorators::ABSTRACT_METHOD;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::Final) => {
|
||||
function_decorators |= FunctionDecorators::FINAL;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::Override) => {
|
||||
function_decorators |= FunctionDecorators::OVERRIDE;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Type::ClassLiteral(class) => match class.known(self.db()) {
|
||||
Some(KnownClass::Classmethod) => {
|
||||
function_decorators |= FunctionDecorators::CLASSMETHOD;
|
||||
continue;
|
||||
}
|
||||
Some(KnownClass::Staticmethod) => {
|
||||
function_decorators |= FunctionDecorators::STATICMETHOD;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Type::DataclassTransformer(params) => {
|
||||
dataclass_transformer_params = Some(params);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !decorator_function_decorator.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
decorator_types_and_nodes.push((decorator_ty, decorator));
|
||||
decorator_types_and_nodes.push((decorator_type, decorator));
|
||||
}
|
||||
|
||||
for default in parameters
|
||||
|
@ -5940,6 +5928,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let mut diagnostic =
|
||||
builder.into_diagnostic(format_args!("Name `{id}` used when not defined"));
|
||||
|
||||
// ===
|
||||
// Subdiagnostic (1): check to see if it was added as a builtin in a later version of Python.
|
||||
// ===
|
||||
if let Some(version_added_to_builtins) = version_builtin_was_added(id) {
|
||||
diagnostic.info(format_args!(
|
||||
"`{id}` was added as a builtin in Python 3.{version_added_to_builtins}"
|
||||
|
@ -5951,19 +5942,57 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
);
|
||||
}
|
||||
|
||||
let attribute_exists = self
|
||||
.class_context_of_current_method()
|
||||
.and_then(|class| {
|
||||
Type::instance(self.db(), class.default_specialization(self.db()))
|
||||
// ===
|
||||
// Subdiagnostic (2):
|
||||
// - If it's an instance method, check to see if it's available as an attribute on `self`;
|
||||
// - If it's a classmethod, check to see if it's available as an attribute on `cls`
|
||||
// ===
|
||||
let Some(current_function) = self.current_function_definition() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let function_parameters = &*current_function.parameters;
|
||||
|
||||
// `self`/`cls` can't be a keyword-only parameter.
|
||||
if function_parameters.posonlyargs.is_empty() && function_parameters.args.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(first_parameter) = function_parameters.iter_non_variadic_params().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(class) = self.class_context_of_current_method() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let first_parameter_name = first_parameter.name();
|
||||
|
||||
let function_decorators = FunctionDecorators::from_decorator_types(
|
||||
self.db(),
|
||||
self.function_decorator_types(current_function),
|
||||
);
|
||||
|
||||
let attribute_exists = if function_decorators.contains(FunctionDecorators::CLASSMETHOD) {
|
||||
if function_decorators.contains(FunctionDecorators::STATICMETHOD) {
|
||||
return;
|
||||
}
|
||||
!Type::instance(self.db(), class.default_specialization(self.db()))
|
||||
.class_member(self.db(), id.clone())
|
||||
.place
|
||||
.is_unbound()
|
||||
} else if !function_decorators.contains(FunctionDecorators::STATICMETHOD) {
|
||||
!Type::instance(self.db(), class.default_specialization(self.db()))
|
||||
.member(self.db(), id)
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
})
|
||||
.is_some();
|
||||
.is_unbound()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if attribute_exists {
|
||||
diagnostic.info(format_args!(
|
||||
"An attribute `{id}` is available: consider using `self.{id}`"
|
||||
"An attribute `{id}` is available: consider using `{first_parameter_name}.{id}`"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue