Skip to content

Commit 46d6758

Browse files
committed
Various minor fixes
1 parent de1cc57 commit 46d6758

File tree

3 files changed

+32
-63
lines changed

3 files changed

+32
-63
lines changed

crates/red_knot_python_semantic/resources/mdtest/annotations/any.md

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,64 +46,59 @@ def f():
4646
y: Any = "not an Any" # error: [invalid-assignment]
4747
```
4848

49-
## Subclass
49+
## Subclasses if `Any`
5050

5151
The spec allows you to define subclasses of `Any`.
5252

53-
`Subclass` has an unknown superclass, which might be `int`. The assignment to `x` should not be
53+
`SubclassOfAny` has an unknown superclass, which might be `int`. The assignment to `x` should not be
5454
allowed, even when the unknown superclass is `int`. The assignment to `y` should be allowed, since
5555
`Subclass` might have `int` as a superclass, and is therefore assignable to `int`.
5656

5757
```py
5858
from typing import Any
5959

60-
class Subclass(Any): ...
60+
class SubclassOfAny(Any): ...
6161

62-
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
62+
reveal_type(SubclassOfAny.__mro__) # revealed: tuple[Literal[SubclassOfAny], Any, Literal[object]]
6363

64-
x: Subclass = 1 # error: [invalid-assignment]
65-
y: int = Subclass()
66-
67-
def _(s: Subclass):
68-
reveal_type(s) # revealed: Subclass
64+
x: SubclassOfAny = 1 # error: [invalid-assignment]
65+
y: int = SubclassOfAny()
6966
```
7067

71-
`Subclass` should not be assignable to a final class though, because `Subclass` could not possibly
72-
be a subclass of `FinalClass`:
68+
`SubclassOfAny` should not be assignable to a final class though, because `SubclassOfAny` could not
69+
possibly be a subclass of `FinalClass`:
7370

7471
```py
7572
from typing import final
7673

7774
@final
7875
class FinalClass: ...
7976

80-
f: FinalClass = Subclass() # error: [invalid-assignment]
81-
```
82-
83-
A use case where this comes up is with mocking libraries, where the mock object should be assignable
84-
to any type:
77+
f: FinalClass = SubclassOfAny() # error: [invalid-assignment]
8578

86-
```py
87-
from unittest.mock import MagicMock
79+
@final
80+
class OtherFinalClass: ...
8881

89-
x: int = MagicMock()
82+
f: FinalClass | OtherFinalClass = SubclassOfAny() # error: [invalid-assignment]
9083
```
9184

92-
A subclass of `Any` can be assigned to a `Callable` and called.
85+
A subclass of `Any` can also be assigned to arbitrary `Callable` types:
9386

9487
```py
9588
from typing import Callable, Any
9689

97-
class MockCallable(Any):
98-
pass
99-
100-
def takes_callable(f: Callable):
90+
def takes_callable1(f: Callable):
10191
f()
10292

103-
takes_callable(MockCallable())
93+
takes_callable1(SubclassOfAny())
94+
95+
def takes_callable2(f: Callable[[int], None]):
96+
f(1)
97+
98+
takes_callable2(SubclassOfAny())
10499
```
105100

106-
But `Any` cannot be assigned to `Final` or `Literal`
101+
A subclass of `Any` cannot be assigned to literal types, since those can not be subclassed:
107102

108103
```py
109104
from typing import Any, Literal
@@ -114,22 +109,15 @@ class MockAny(Any):
114109
x: Literal[1] = MockAny() # error: [invalid-assignment]
115110
```
116111

112+
A use case where subclasses of `Any` come up is in mocking libraries, where the mock object should
113+
be assignable to (almost) any type:
117114

118115
```py
119-
from typing import final, Any
120-
121-
@final
122-
class FinalA: ...
123-
124-
@final
125-
class FinalB: ...
126-
127-
class MockAny(Any): ...
116+
from unittest.mock import MagicMock
128117

129-
value: FinalA | FinalB = MockAny() # error: [invalid-assignment]
118+
x: int = MagicMock()
130119
```
131120

132-
133121
## Invalid
134122

135123
`Any` cannot be parameterized:

crates/red_knot_python_semantic/src/types.rs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,25 +1354,6 @@ impl<'db> Type<'db> {
13541354
.iter()
13551355
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
13561356

1357-
// Subclasses of Any are assignable to non-final, non-literal types,
1358-
// as Any can materialize to any such type, but not to final or literal types.
1359-
(Type::Instance(instance), target)
1360-
if instance.class().is_subclass_of_any_or_unknown(db) =>
1361-
{
1362-
match target {
1363-
Type::IntLiteral(_)
1364-
| Type::BooleanLiteral(_)
1365-
| Type::StringLiteral(_)
1366-
| Type::BytesLiteral(_)
1367-
| Type::LiteralString
1368-
| Type::SliceLiteral(_) => false,
1369-
Type::Instance(target_instance) if target_instance.class().is_final(db) => {
1370-
false
1371-
}
1372-
_ => true,
1373-
}
1374-
}
1375-
13761357
// If the typevar is constrained, there must be multiple constraints, and the typevar
13771358
// might be specialized to any one of them. However, the constraints do not have to be
13781359
// disjoint, which means an lhs type might be assignable to all of the constraints.
@@ -1496,6 +1477,12 @@ impl<'db> Type<'db> {
14961477
self_callable.is_assignable_to(db, target_callable)
14971478
}
14981479

1480+
(Type::NominalInstance(instance), Type::Callable(_))
1481+
if instance.class().is_subclass_of_any_or_unknown(db) =>
1482+
{
1483+
true
1484+
}
1485+
14991486
(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
15001487
let call_symbol = self.member(db, "__call__").symbol;
15011488
match call_symbol {

crates/red_knot_python_semantic/src/types/class.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,7 @@ impl<'db> ClassType<'db> {
284284
}
285285
}
286286

287-
if self.iter_mro(db).any(|base| {
288-
matches!(
289-
base,
290-
ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown)
291-
)
292-
}) && !other.is_final(db)
293-
{
287+
if self.is_subclass_of_any_or_unknown(db) && !other.is_final(db) {
294288
return true;
295289
}
296290

0 commit comments

Comments
 (0)