Skip to content

Commit 7e9b0df

Browse files
authored
[ty] Allow classes to inherit from type[Any] or type[Unknown] (#18060)
1 parent 41fa082 commit 7e9b0df

File tree

5 files changed

+40
-20
lines changed

5 files changed

+40
-20
lines changed

crates/ty_python_semantic/resources/mdtest/mro.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,23 @@ if not isinstance(DoesNotExist, type):
177177
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
178178
```
179179

180+
## Inheritance from `type[Any]` and `type[Unknown]`
181+
182+
Inheritance from `type[Any]` and `type[Unknown]` is also permitted, in keeping with the gradual
183+
guarantee:
184+
185+
```py
186+
from typing import Any
187+
from ty_extensions import Unknown, Intersection
188+
189+
def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
190+
class Foo(x): ...
191+
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
192+
193+
class Bar(y): ...
194+
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
195+
```
196+
180197
## `__bases__` lists that cause errors at runtime
181198

182199
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class

crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ info[revealed-type]: Revealed type
141141
```
142142
143143
```
144-
error[duplicate-base]: Duplicate base class `Spam`
144+
error[duplicate-base]: Duplicate base class `Eggs`
145145
--> src/mdtest_snippet.py:16:7
146146
|
147147
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
@@ -160,26 +160,25 @@ error[duplicate-base]: Duplicate base class `Spam`
160160
25 | # fmt: on
161161
|
162162
info: The definition of class `Ham` will raise `TypeError` at runtime
163-
--> src/mdtest_snippet.py:17:5
163+
--> src/mdtest_snippet.py:18:5
164164
|
165-
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
166165
16 | class Ham(
167166
17 | Spam,
168-
| ---- Class `Spam` first included in bases list here
169167
18 | Eggs,
168+
| ---- Class `Eggs` first included in bases list here
170169
19 | Bar,
171170
20 | Baz,
172171
21 | Spam,
173-
| ^^^^ Class `Spam` later repeated here
174172
22 | Eggs,
173+
| ^^^^ Class `Eggs` later repeated here
175174
23 | ): ...
176175
|
177176
info: rule `duplicate-base` is enabled by default
178177
179178
```
180179
181180
```
182-
error[duplicate-base]: Duplicate base class `Eggs`
181+
error[duplicate-base]: Duplicate base class `Spam`
183182
--> src/mdtest_snippet.py:16:7
184183
|
185184
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
@@ -198,17 +197,18 @@ error[duplicate-base]: Duplicate base class `Eggs`
198197
25 | # fmt: on
199198
|
200199
info: The definition of class `Ham` will raise `TypeError` at runtime
201-
--> src/mdtest_snippet.py:18:5
200+
--> src/mdtest_snippet.py:17:5
202201
|
202+
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
203203
16 | class Ham(
204204
17 | Spam,
205+
| ---- Class `Spam` first included in bases list here
205206
18 | Eggs,
206-
| ---- Class `Eggs` first included in bases list here
207207
19 | Bar,
208208
20 | Baz,
209209
21 | Spam,
210+
| ^^^^ Class `Spam` later repeated here
210211
22 | Eggs,
211-
| ^^^^ Class `Eggs` later repeated here
212212
23 | ): ...
213213
|
214214
info: rule `duplicate-base` is enabled by default

crates/ty_python_semantic/src/types.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -543,13 +543,6 @@ impl<'db> Type<'db> {
543543
Self::Dynamic(DynamicType::Unknown)
544544
}
545545

546-
pub(crate) fn into_dynamic(self) -> Option<DynamicType> {
547-
match self {
548-
Type::Dynamic(dynamic_type) => Some(dynamic_type),
549-
_ => None,
550-
}
551-
}
552-
553546
pub fn object(db: &'db dyn Db) -> Self {
554547
KnownClass::Object.to_instance(db)
555548
}

crates/ty_python_semantic/src/types/class_base.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,20 @@ impl<'db> ClassBase<'db> {
107107
{
108108
Self::try_from_type(db, todo_type!("GenericAlias instance"))
109109
}
110+
Type::SubclassOf(subclass_of) => subclass_of
111+
.subclass_of()
112+
.into_dynamic()
113+
.map(ClassBase::Dynamic),
110114
Type::Intersection(inter) => {
111-
let dynamic_element = inter
115+
let valid_element = inter
112116
.positive(db)
113117
.iter()
114-
.find_map(|elem| elem.into_dynamic())?;
118+
.find_map(|elem| ClassBase::try_from_type(db, *elem))?;
115119

116120
if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) {
117121
None
118122
} else {
119-
Some(ClassBase::Dynamic(dynamic_element))
123+
Some(valid_element)
120124
}
121125
}
122126
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
@@ -137,7 +141,6 @@ impl<'db> ClassBase<'db> {
137141
| Type::LiteralString
138142
| Type::Tuple(_)
139143
| Type::ModuleLiteral(_)
140-
| Type::SubclassOf(_)
141144
| Type::TypeVar(_)
142145
| Type::BoundSuper(_)
143146
| Type::ProtocolInstance(_)

crates/ty_python_semantic/src/types/subclass_of.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ impl<'db> SubclassOfInner<'db> {
138138
}
139139
}
140140

141+
pub(crate) const fn into_dynamic(self) -> Option<DynamicType> {
142+
match self {
143+
Self::Class(_) => None,
144+
Self::Dynamic(dynamic) => Some(dynamic),
145+
}
146+
}
147+
141148
pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
142149
match ty {
143150
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),

0 commit comments

Comments
 (0)