mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-22 08:12:17 +00:00
[ty] Improve error messages for unresolved attribute diagnostics (#20963)
## Summary - Type checkers (and type-checker authors) think in terms of types, but I think most Python users think in terms of values. Rather than saying that a _type_ `X` "has no attribute `foo`" (which I think sounds strange to many users), say that "an object of type `X` has no attribute `foo`" - Special-case certain types so that the diagnostic messages read more like normal English: rather than saying "Type `<class 'Foo'>` has no attribute `bar`" or "Object of type `<class 'Foo'>` has no attribute `bar`", just say "Class `Foo` has no attribute `bar`" ## Test Plan Mdtests and snapshots updated
This commit is contained in:
parent
b6b96d75eb
commit
1f8297cfe6
19 changed files with 102 additions and 63 deletions
|
@ -30,7 +30,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
error[unresolved-attribute]: Type `<module 'sys'>` has no attribute `last_exc`
|
error[unresolved-attribute]: Module `sys` has no member `last_exc`
|
||||||
--> test.py:5:7
|
--> test.py:5:7
|
||||||
|
|
|
|
||||||
4 | # Access `sys.last_exc` that was only added in Python 3.12
|
4 | # Access `sys.last_exc` that was only added in Python 3.12
|
||||||
|
@ -962,7 +962,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
error[unresolved-attribute]: Type `<module 'os'>` has no attribute `grantpt`
|
error[unresolved-attribute]: Module `os` has no member `grantpt`
|
||||||
--> main.py:4:1
|
--> main.py:4:1
|
||||||
|
|
|
|
||||||
2 | import os
|
2 | import os
|
||||||
|
|
|
@ -1112,11 +1112,11 @@ print(sys.last_exc, os.getegid())
|
||||||
assert_eq!(diagnostics.len(), 2);
|
assert_eq!(diagnostics.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diagnostics[0].primary_message(),
|
diagnostics[0].primary_message(),
|
||||||
"Type `<module 'sys'>` has no attribute `last_exc`"
|
"Module `sys` has no member `last_exc`"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diagnostics[1].primary_message(),
|
diagnostics[1].primary_message(),
|
||||||
"Type `<module 'os'>` has no attribute `getegid`"
|
"Module `os` has no member `getegid`"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Change the python version
|
// Change the python version
|
||||||
|
|
|
@ -366,7 +366,7 @@ on function-like callables:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def f_wrong(c: Callable[[], None]):
|
def f_wrong(c: Callable[[], None]):
|
||||||
# error: [unresolved-attribute] "Type `() -> None` has no attribute `__qualname__`"
|
# error: [unresolved-attribute] "Object of type `() -> None` has no attribute `__qualname__`"
|
||||||
c.__qualname__
|
c.__qualname__
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`."
|
# error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`."
|
||||||
|
|
|
@ -1260,13 +1260,13 @@ def _(flag1: bool, flag2: bool):
|
||||||
|
|
||||||
C = C1 if flag1 else C2 if flag2 else C3
|
C = C1 if flag1 else C2 if flag2 else C3
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
|
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||||
C.x = 100
|
C.x = 100
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`"
|
||||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
|
reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
|
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
|
||||||
|
@ -1292,7 +1292,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||||
|
|
||||||
C = C1 if flag1 else C2 if flag2 else C3
|
C = C1 if flag1 else C2 if flag2 else C3
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
|
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
|
||||||
|
|
||||||
# error: [possibly-missing-attribute]
|
# error: [possibly-missing-attribute]
|
||||||
|
@ -1300,7 +1300,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||||
|
|
||||||
# Note: we might want to consider ignoring possibly-missing diagnostics for instance attributes eventually,
|
# Note: we might want to consider ignoring possibly-missing diagnostics for instance attributes eventually,
|
||||||
# see the "Possibly unbound/undeclared instance attribute" section below.
|
# see the "Possibly unbound/undeclared instance attribute" section below.
|
||||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`"
|
||||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
|
reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
|
||||||
|
|
||||||
# error: [possibly-missing-attribute]
|
# error: [possibly-missing-attribute]
|
||||||
|
@ -1433,7 +1433,7 @@ def _(flag: bool):
|
||||||
class C2: ...
|
class C2: ...
|
||||||
C = C1 if flag else C2
|
C = C1 if flag else C2
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<class 'C1'> | <class 'C2'>` has no attribute `x`"
|
# error: [unresolved-attribute] "Object of type `<class 'C1'> | <class 'C2'>` has no attribute `x`"
|
||||||
reveal_type(C.x) # revealed: Unknown
|
reveal_type(C.x) # revealed: Unknown
|
||||||
|
|
||||||
# TODO: This should ideally be a `unresolved-attribute` error. We need better union
|
# TODO: This should ideally be a `unresolved-attribute` error. We need better union
|
||||||
|
@ -1771,7 +1771,7 @@ reveal_type(date.day) # revealed: int
|
||||||
reveal_type(date.month) # revealed: int
|
reveal_type(date.month) # revealed: int
|
||||||
reveal_type(date.year) # revealed: int
|
reveal_type(date.year) # revealed: int
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `Date` has no attribute `century`"
|
# error: [unresolved-attribute] "Object of type `Date` has no attribute `century`"
|
||||||
reveal_type(date.century) # revealed: Unknown
|
reveal_type(date.century) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -311,7 +311,7 @@ reveal_type(C.f(1)) # revealed: str
|
||||||
The method `f` can not be accessed from an instance of the class:
|
The method `f` can not be accessed from an instance of the class:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# error: [unresolved-attribute] "Type `C` has no attribute `f`"
|
# error: [unresolved-attribute] "Object of type `C` has no attribute `f`"
|
||||||
C().f
|
C().f
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -308,7 +308,7 @@ class B(A): ...
|
||||||
|
|
||||||
reveal_type(super(B)) # revealed: super
|
reveal_type(super(B)) # revealed: super
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `super` has no attribute `a`"
|
# error: [unresolved-attribute] "Object of type `super` has no attribute `a`"
|
||||||
super(B).a
|
super(B).a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -436,7 +436,7 @@ def f(x: C | D):
|
||||||
s = super(A, x)
|
s = super(A, x)
|
||||||
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
|
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `b` may be missing on object of type `<super: <class 'A'>, C> | <super: <class 'A'>, D>`"
|
||||||
s.b
|
s.b
|
||||||
|
|
||||||
def f(flag: bool):
|
def f(flag: bool):
|
||||||
|
@ -476,7 +476,7 @@ def f(flag: bool):
|
||||||
reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
|
reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
|
||||||
reveal_type(s.y) # revealed: int | str
|
reveal_type(s.y) # revealed: int | str
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `a` may be missing on object of type `<super: <class 'B'>, B> | <super: <class 'D'>, D>`"
|
||||||
reveal_type(s.a) # revealed: str
|
reveal_type(s.a) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -619,7 +619,7 @@ class B(A):
|
||||||
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error
|
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error
|
||||||
super().a
|
super().a
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'B'>, B>` has no attribute `a`"
|
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
|
||||||
super(B, B(42)).a
|
super(B, B(42)).a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -758,16 +758,16 @@ def _(flag: bool):
|
||||||
non_data: NonDataDescriptor = NonDataDescriptor()
|
non_data: NonDataDescriptor = NonDataDescriptor()
|
||||||
data: DataDescriptor = DataDescriptor()
|
data: DataDescriptor = DataDescriptor()
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `non_data` may be missing on class `PossiblyUnbound`"
|
||||||
reveal_type(PossiblyUnbound.non_data) # revealed: int
|
reveal_type(PossiblyUnbound.non_data) # revealed: int
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `non_data` on type `PossiblyUnbound` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `non_data` may be missing on object of type `PossiblyUnbound`"
|
||||||
reveal_type(PossiblyUnbound().non_data) # revealed: int
|
reveal_type(PossiblyUnbound().non_data) # revealed: int
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `data` may be missing on class `PossiblyUnbound`"
|
||||||
reveal_type(PossiblyUnbound.data) # revealed: int
|
reveal_type(PossiblyUnbound.data) # revealed: int
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `data` on type `PossiblyUnbound` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `data` may be missing on object of type `PossiblyUnbound`"
|
||||||
reveal_type(PossiblyUnbound().data) # revealed: int
|
reveal_type(PossiblyUnbound().data) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ def _(flag: bool):
|
||||||
|
|
||||||
reveal_type(A.union_declared) # revealed: int | str
|
reveal_type(A.union_declared) # revealed: int | str
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `possibly_unbound` on type `<class 'A'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `possibly_unbound` may be missing on class `A`"
|
||||||
reveal_type(A.possibly_unbound) # revealed: str
|
reveal_type(A.possibly_unbound) # revealed: str
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<class 'A'>` has no attribute `non_existent`"
|
# error: [unresolved-attribute] "Class `A` has no attribute `non_existent`"
|
||||||
reveal_type(A.non_existent) # revealed: Unknown
|
reveal_type(A.non_existent) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
|
@ -247,7 +247,7 @@ X: int = 42
|
||||||
from . import foo
|
from . import foo
|
||||||
import package
|
import package
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<module 'package'>` has no attribute `foo`"
|
# error: [unresolved-attribute] "Module `package` has no member `foo`"
|
||||||
reveal_type(package.foo.X) # revealed: Unknown
|
reveal_type(package.foo.X) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,6 @@ def f(x: object):
|
||||||
reveal_type(x.__str__) # revealed: bound method object.__str__() -> str
|
reveal_type(x.__str__) # revealed: bound method object.__str__() -> str
|
||||||
reveal_type(x.__dict__) # revealed: dict[str, Any]
|
reveal_type(x.__dict__) # revealed: dict[str, Any]
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<Protocol with members '__qualname__'>` has no attribute `foo`"
|
# error: [unresolved-attribute] "Object of type `<Protocol with members '__qualname__'>` has no attribute `foo`"
|
||||||
reveal_type(x.foo) # revealed: Unknown
|
reveal_type(x.foo) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
|
@ -16,7 +16,7 @@ class C:
|
||||||
if flag:
|
if flag:
|
||||||
x = 2
|
x = 2
|
||||||
|
|
||||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C'>` may be missing"
|
# error: [possibly-missing-attribute] "Attribute `x` may be missing on class `C`"
|
||||||
reveal_type(C.x) # revealed: Unknown | Literal[2]
|
reveal_type(C.x) # revealed: Unknown | Literal[2]
|
||||||
reveal_type(C.y) # revealed: Unknown | Literal[1]
|
reveal_type(C.y) # revealed: Unknown | Literal[1]
|
||||||
```
|
```
|
||||||
|
|
|
@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
warning[possibly-missing-attribute]: Attribute `attr` on type `<class 'C'>` may be missing
|
warning[possibly-missing-attribute]: Attribute `attr` may be missing on class `C`
|
||||||
--> src/mdtest_snippet.py:6:5
|
--> src/mdtest_snippet.py:6:5
|
||||||
|
|
|
|
||||||
4 | attr: int = 0
|
4 | attr: int = 0
|
||||||
|
@ -41,7 +41,7 @@ info: rule `possibly-missing-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
warning[possibly-missing-attribute]: Attribute `attr` on type `C` may be missing
|
warning[possibly-missing-attribute]: Attribute `attr` may be missing on object of type `C`
|
||||||
--> src/mdtest_snippet.py:9:5
|
--> src/mdtest_snippet.py:9:5
|
||||||
|
|
|
|
||||||
8 | instance = C()
|
8 | instance = C()
|
||||||
|
|
|
@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<module 'datetime'>` has no attribute `UTC`
|
error[unresolved-attribute]: Module `datetime` has no member `UTC`
|
||||||
--> src/main.py:4:13
|
--> src/main.py:4:13
|
||||||
|
|
|
|
||||||
3 | # error: [unresolved-attribute]
|
3 | # error: [unresolved-attribute]
|
||||||
|
@ -38,7 +38,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<module 'datetime'>` has no attribute `fakenotreal`
|
error[unresolved-attribute]: Module `datetime` has no member `fakenotreal`
|
||||||
--> src/main.py:6:13
|
--> src/main.py:6:13
|
||||||
|
|
|
|
||||||
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
||||||
|
|
|
@ -118,7 +118,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
|
error[unresolved-attribute]: Object of type `<super: <class 'C'>, C>` has no attribute `c`
|
||||||
--> src/mdtest_snippet.py:19:1
|
--> src/mdtest_snippet.py:19:1
|
||||||
|
|
|
|
||||||
17 | super(C, C()).a
|
17 | super(C, C()).a
|
||||||
|
@ -133,7 +133,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
|
error[unresolved-attribute]: Object of type `<super: <class 'B'>, C>` has no attribute `b`
|
||||||
--> src/mdtest_snippet.py:22:1
|
--> src/mdtest_snippet.py:22:1
|
||||||
|
|
|
|
||||||
21 | super(B, C()).a
|
21 | super(B, C()).a
|
||||||
|
@ -146,7 +146,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
|
error[unresolved-attribute]: Object of type `<super: <class 'B'>, C>` has no attribute `c`
|
||||||
--> src/mdtest_snippet.py:23:1
|
--> src/mdtest_snippet.py:23:1
|
||||||
|
|
|
|
||||||
21 | super(B, C()).a
|
21 | super(B, C()).a
|
||||||
|
@ -161,7 +161,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
|
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `a`
|
||||||
--> src/mdtest_snippet.py:25:1
|
--> src/mdtest_snippet.py:25:1
|
||||||
|
|
|
|
||||||
23 | super(B, C()).c # error: [unresolved-attribute]
|
23 | super(B, C()).c # error: [unresolved-attribute]
|
||||||
|
@ -176,7 +176,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
|
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `b`
|
||||||
--> src/mdtest_snippet.py:26:1
|
--> src/mdtest_snippet.py:26:1
|
||||||
|
|
|
|
||||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
@ -189,7 +189,7 @@ info: rule `unresolved-attribute` is enabled by default
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
|
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `c`
|
||||||
--> src/mdtest_snippet.py:27:1
|
--> src/mdtest_snippet.py:27:1
|
||||||
|
|
|
|
||||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
|
|
@ -618,7 +618,7 @@ import importlib
|
||||||
from module2 import importlib as other_importlib
|
from module2 import importlib as other_importlib
|
||||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<module 'importlib'>` has no attribute `abc`"
|
# error: [unresolved-attribute] "Module `importlib` has no member `abc`"
|
||||||
reveal_type(importlib.abc) # revealed: Unknown
|
reveal_type(importlib.abc) # revealed: Unknown
|
||||||
|
|
||||||
reveal_type(other_importlib.abc) # revealed: <module 'importlib.abc'>
|
reveal_type(other_importlib.abc) # revealed: <module 'importlib.abc'>
|
||||||
|
|
|
@ -672,18 +672,18 @@ Also, the "attributes" on the class definition can not be accessed. Neither on t
|
||||||
on inhabitants of the type defined by the class:
|
on inhabitants of the type defined by the class:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# error: [unresolved-attribute] "Type `<class 'Person'>` has no attribute `name`"
|
# error: [unresolved-attribute] "Class `Person` has no attribute `name`"
|
||||||
Person.name
|
Person.name
|
||||||
|
|
||||||
def _(P: type[Person]):
|
def _(P: type[Person]):
|
||||||
# error: [unresolved-attribute] "Type `type[Person]` has no attribute `name`"
|
# error: [unresolved-attribute] "Object of type `type[Person]` has no attribute `name`"
|
||||||
P.name
|
P.name
|
||||||
|
|
||||||
def _(p: Person) -> None:
|
def _(p: Person) -> None:
|
||||||
# error: [unresolved-attribute] "Type `Person` has no attribute `name`"
|
# error: [unresolved-attribute] "Object of type `Person` has no attribute `name`"
|
||||||
p.name
|
p.name
|
||||||
|
|
||||||
type(p).name # error: [unresolved-attribute] "Type `<class 'dict[str, object]'>` has no attribute `name`"
|
type(p).name # error: [unresolved-attribute] "Class `dict[str, object]` has no attribute `name`"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Special properties
|
## Special properties
|
||||||
|
|
|
@ -2269,10 +2269,25 @@ pub(super) fn report_possibly_missing_attribute(
|
||||||
let Some(builder) = context.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, target) else {
|
let Some(builder) = context.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, target) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
builder.into_diagnostic(format_args!(
|
let db = context.db();
|
||||||
"Attribute `{attribute}` on type `{}` may be missing",
|
match object_ty {
|
||||||
object_ty.display(context.db()),
|
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
|
||||||
));
|
"Member `{attribute}` may be missing on module `{}`",
|
||||||
|
module.module(db).name(db),
|
||||||
|
)),
|
||||||
|
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
|
||||||
|
"Attribute `{attribute}` may be missing on class `{}`",
|
||||||
|
class.name(db),
|
||||||
|
)),
|
||||||
|
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
|
||||||
|
"Attribute `{attribute}` may be missing on class `{}`",
|
||||||
|
alias.display(db),
|
||||||
|
)),
|
||||||
|
_ => builder.into_diagnostic(format_args!(
|
||||||
|
"Attribute `{attribute}` may be missing on object of type `{}`",
|
||||||
|
object_ty.display(db),
|
||||||
|
)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
|
@ -814,6 +814,10 @@ impl Display for DisplayFunctionType<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> GenericAlias<'db> {
|
impl<'db> GenericAlias<'db> {
|
||||||
|
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> {
|
||||||
|
self.display_with(db, DisplaySettings::default())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn display_with(
|
pub(crate) fn display_with(
|
||||||
&'db self,
|
&'db self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
|
|
@ -7618,25 +7618,45 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.context
|
.context
|
||||||
.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
||||||
{
|
{
|
||||||
if bound_on_instance {
|
if bound_on_instance {
|
||||||
builder.into_diagnostic(
|
builder.into_diagnostic(
|
||||||
format_args!(
|
format_args!(
|
||||||
"Attribute `{}` can only be accessed on instances, \
|
"Attribute `{}` can only be accessed on instances, \
|
||||||
not on the class object `{}` itself.",
|
not on the class object `{}` itself.",
|
||||||
attr.id,
|
attr.id,
|
||||||
value_type.display(db)
|
value_type.display(db)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let diagnostic = builder.into_diagnostic(
|
let diagnostic = match value_type {
|
||||||
format_args!(
|
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
|
||||||
"Type `{}` has no attribute `{}`",
|
"Module `{}` has no member `{}`",
|
||||||
value_type.display(db),
|
module.module(db).name(db),
|
||||||
attr.id
|
&attr.id
|
||||||
),
|
)),
|
||||||
);
|
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
|
||||||
hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr);
|
"Class `{}` has no attribute `{}`",
|
||||||
}
|
class.name(db),
|
||||||
|
&attr.id
|
||||||
|
)),
|
||||||
|
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
|
||||||
|
"Class `{}` has no attribute `{}`",
|
||||||
|
alias.display(db),
|
||||||
|
&attr.id
|
||||||
|
)),
|
||||||
|
Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
|
||||||
|
"Function `{}` has no attribute `{}`",
|
||||||
|
function.name(db),
|
||||||
|
&attr.id
|
||||||
|
)),
|
||||||
|
_ => builder.into_diagnostic(format_args!(
|
||||||
|
"Object of type `{}` has no attribute `{}`",
|
||||||
|
value_type.display(db),
|
||||||
|
&attr.id
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue