mirror of https://github.com/astral-sh/ruff
[ty] Use the return type of `__get__` for descriptor lookups even when `__get__` is called with incorrect arguments (#21424)
This commit is contained in:
parent
eb1957cd17
commit
cd183c5e1f
|
|
@ -696,8 +696,16 @@ reveal_type(C().instance_access) # revealed: str
|
|||
reveal_type(C.metaclass_access) # revealed: bytes
|
||||
|
||||
# TODO: These should emit a diagnostic
|
||||
reveal_type(C().class_object_access) # revealed: TailoredForClassObjectAccess
|
||||
reveal_type(C.instance_access) # revealed: TailoredForInstanceAccess
|
||||
#
|
||||
# However, we use the return-type of `__get__` as the inferred type anyway:
|
||||
# the way to specify that the descriptor object itself is returned when the
|
||||
# attribute is accessed on the instance or the class is by overloading `__get__`.
|
||||
#
|
||||
# Using the return type of `__get__` even for `__get__` calls that have invalid
|
||||
# arguments passed to them avoids false positives in situations where there are
|
||||
# `__get__` calls that we don't sufficiently understand.
|
||||
reveal_type(C().class_object_access) # revealed: int
|
||||
reveal_type(C.instance_access) # revealed: str
|
||||
```
|
||||
|
||||
### Descriptors with incorrect `__get__` signature
|
||||
|
|
@ -712,10 +720,28 @@ class C:
|
|||
descriptor: Descriptor = Descriptor()
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C.descriptor) # revealed: Descriptor
|
||||
reveal_type(C.descriptor) # revealed: int
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C().descriptor) # revealed: Descriptor
|
||||
reveal_type(C().descriptor) # revealed: int
|
||||
```
|
||||
|
||||
### "Descriptors" with non-callable `__get__` attributes
|
||||
|
||||
If `__get__` is not callable at all, the interpreter will still attempt to call the method at
|
||||
runtime, and this will raise an exception. As such, even for `__get__ = None`, we still "attempt to
|
||||
call `__get__`" on the descriptor object (leading us to infer `Unknown`):
|
||||
|
||||
```py
|
||||
class BrokenDescriptor:
|
||||
__get__: None = None
|
||||
|
||||
class Foo:
|
||||
desc: BrokenDescriptor = BrokenDescriptor()
|
||||
|
||||
# TODO: this raises `TypeError` at runtime due to the implicit call to `__get__`;
|
||||
# we should emit a diagnostic
|
||||
reveal_type(Foo().desc) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Undeclared descriptor arguments
|
||||
|
|
|
|||
|
|
@ -167,10 +167,9 @@ class C:
|
|||
c = C()
|
||||
c.attr = 1
|
||||
|
||||
# TODO: An error should be emitted here, and the type should be `Unknown`
|
||||
# or `Never`. See https://github.com/astral-sh/ruff/issues/16298 for more
|
||||
# details.
|
||||
reveal_type(c.attr) # revealed: Unknown | property
|
||||
# TODO: An error should be emitted here.
|
||||
# See https://github.com/astral-sh/ruff/issues/16298 for more details.
|
||||
reveal_type(c.attr) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Wrong setter signature
|
||||
|
|
|
|||
|
|
@ -3963,7 +3963,9 @@ impl<'db> Type<'db> {
|
|||
UnionType::from_elements(db, [bindings.return_type(db), self])
|
||||
}
|
||||
})
|
||||
.ok()?;
|
||||
// TODO: an error when calling `__get__` will lead to a `TypeError` or similar at runtime;
|
||||
// we should emit a diagnostic here instead of silently ignoring the error.
|
||||
.unwrap_or_else(|CallError(_, bindings)| bindings.return_type(db));
|
||||
|
||||
let descriptor_kind = if self.is_data_descriptor(db) {
|
||||
AttributeKind::DataDescriptor
|
||||
|
|
|
|||
Loading…
Reference in New Issue