mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-21 15:52:34 +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
|
@ -366,7 +366,7 @@ on function-like callables:
|
|||
|
||||
```py
|
||||
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__
|
||||
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
|
||||
# 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,
|
||||
# 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]
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
|
@ -1433,7 +1433,7 @@ def _(flag: bool):
|
|||
class 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
|
||||
|
||||
# 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.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
|
||||
```
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ reveal_type(C.f(1)) # revealed: str
|
|||
The method `f` can not be accessed from an instance of the class:
|
||||
|
||||
```py
|
||||
# error: [unresolved-attribute] "Type `C` has no attribute `f`"
|
||||
# error: [unresolved-attribute] "Object of type `C` has no attribute `f`"
|
||||
C().f
|
||||
```
|
||||
|
||||
|
|
|
@ -308,7 +308,7 @@ class B(A): ...
|
|||
|
||||
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
|
||||
```
|
||||
|
||||
|
@ -436,7 +436,7 @@ def f(x: C | D):
|
|||
s = super(A, x)
|
||||
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
|
||||
|
||||
def f(flag: bool):
|
||||
|
@ -476,7 +476,7 @@ def f(flag: bool):
|
|||
reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
|
||||
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
|
||||
```
|
||||
|
||||
|
@ -619,7 +619,7 @@ class B(A):
|
|||
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
@ -758,16 +758,16 @@ def _(flag: bool):
|
|||
non_data: NonDataDescriptor = NonDataDescriptor()
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ def _(flag: bool):
|
|||
|
||||
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
|
||||
|
||||
# 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
|
||||
```
|
||||
|
|
|
@ -247,7 +247,7 @@ X: int = 42
|
|||
from . import foo
|
||||
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
|
||||
```
|
||||
|
||||
|
|
|
@ -95,6 +95,6 @@ def f(x: object):
|
|||
reveal_type(x.__str__) # revealed: bound method object.__str__() -> str
|
||||
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
|
||||
```
|
||||
|
|
|
@ -16,7 +16,7 @@ class C:
|
|||
if flag:
|
||||
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.y) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
|
|
@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
|
|||
# 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
|
||||
|
|
||||
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
|
||||
|
|
||||
8 | instance = C()
|
||||
|
|
|
@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
|||
# 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
|
||||
|
|
||||
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
|
||||
|
|
||||
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
||||
|
|
|
@ -118,7 +118,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
|||
# 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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
|
|
|
@ -618,7 +618,7 @@ import importlib
|
|||
from module2 import importlib as other_importlib
|
||||
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(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:
|
||||
|
||||
```py
|
||||
# error: [unresolved-attribute] "Type `<class 'Person'>` has no attribute `name`"
|
||||
# error: [unresolved-attribute] "Class `Person` has no attribute `name`"
|
||||
Person.name
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue