mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[red-knot] Assignability for subclasses of Any
and Unknown
(#17557)
## Summary Allow (instances of) subclasses of `Any` and `Unknown` to be assignable to (instances of) other classes, unless they are final. This allows us to get rid of ~1000 false positives, mostly when mock-objects like `unittest.mock.MagicMock` are assigned to various targets. ## Test Plan Adapted and new Markdown tests.
This commit is contained in:
parent
a241321735
commit
99fa850e53
4 changed files with 43 additions and 8 deletions
|
@ -50,10 +50,9 @@ y: Any = "not an Any" # error: [invalid-assignment]
|
|||
|
||||
The spec allows you to define subclasses of `Any`.
|
||||
|
||||
TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The
|
||||
assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment
|
||||
to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore
|
||||
assignable to `int`.
|
||||
`Subclass` has an unknown superclass, which might be `int`. The assignment to `x` should not be
|
||||
allowed, even when the unknown superclass is `int`. The assignment to `y` should be allowed, since
|
||||
`Subclass` might have `int` as a superclass, and is therefore assignable to `int`.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
@ -63,13 +62,33 @@ class Subclass(Any): ...
|
|||
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
|
||||
|
||||
x: Subclass = 1 # error: [invalid-assignment]
|
||||
# TODO: no diagnostic
|
||||
y: int = Subclass() # error: [invalid-assignment]
|
||||
y: int = Subclass()
|
||||
|
||||
def _(s: Subclass):
|
||||
reveal_type(s) # revealed: Subclass
|
||||
```
|
||||
|
||||
`Subclass` should not be assignable to a final class though, because `Subclass` could not possibly
|
||||
be a subclass of `FinalClass`:
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
f: FinalClass = Subclass() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
A use case where this comes up is with mocking libraries, where the mock object should be assignable
|
||||
to any type:
|
||||
|
||||
```py
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
x: int = MagicMock()
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
`Any` cannot be parameterized:
|
||||
|
|
|
@ -22,6 +22,7 @@ We can then place custom stub files in `/typeshed/stdlib`, for example:
|
|||
`/typeshed/stdlib/builtins.pyi`:
|
||||
|
||||
```pyi
|
||||
class object: ...
|
||||
class BuiltinClass: ...
|
||||
|
||||
builtin_symbol: BuiltinClass
|
||||
|
|
|
@ -47,6 +47,13 @@ static_assert(is_assignable_to(Unknown, Literal[1]))
|
|||
static_assert(is_assignable_to(Any, Literal[1]))
|
||||
static_assert(is_assignable_to(Literal[1], Unknown))
|
||||
static_assert(is_assignable_to(Literal[1], Any))
|
||||
|
||||
class SubtypeOfAny(Any): ...
|
||||
|
||||
static_assert(is_assignable_to(SubtypeOfAny, Any))
|
||||
static_assert(is_assignable_to(SubtypeOfAny, int))
|
||||
static_assert(is_assignable_to(Any, SubtypeOfAny))
|
||||
static_assert(not is_assignable_to(int, SubtypeOfAny))
|
||||
```
|
||||
|
||||
## Literal types
|
||||
|
|
|
@ -324,8 +324,6 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
|
||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
|
||||
// participate, so we should not return `True` if we find `Any/Unknown` in the MRO.
|
||||
if self.is_subclass_of(db, other) {
|
||||
return true;
|
||||
}
|
||||
|
@ -341,6 +339,16 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
if self.iter_mro(db).any(|base| {
|
||||
matches!(
|
||||
base,
|
||||
ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown)
|
||||
)
|
||||
}) && !other.is_final(db)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue