Skip to content

Commit 99be8f5

Browse files
committed
Make base optional
1 parent e1b1cf4 commit 99be8f5

5 files changed

Lines changed: 47 additions & 47 deletions

File tree

crates/ty_python_semantic/resources/mdtest/call/type.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ Dynamic classes cannot directly inherit from `Generic`, `Protocol`, or `TypedDic
10101010
forms require class syntax for their semantics to be properly applied:
10111011

10121012
```py
1013-
from typing import Generic, Protocol, TypeVar
1013+
from typing import Generic, NamedTuple, Protocol, TypeVar
10141014
from typing_extensions import TypedDict
10151015

10161016
T = TypeVar("T")
@@ -1023,6 +1023,19 @@ ProtocolClass = type("ProtocolClass", (Protocol,), {})
10231023

10241024
# error: [invalid-base] "Invalid base for class created via `type()`"
10251025
TypedDictClass = type("TypedDictClass", (TypedDict,), {})
1026+
1027+
# error: [invalid-base] "Invalid class base with type `<special-form 'typing.NamedTuple'>`"
1028+
NamedTupleClass = type("NamedTupleClass", (NamedTuple,), {"x": int})
1029+
```
1030+
1031+
`NamedTuple` is also not allowed as a base for dynamic classes, since creating a NamedTuple requires
1032+
class syntax for the field declarations to be properly processed:
1033+
1034+
```py
1035+
from typing import NamedTuple
1036+
1037+
# error: [invalid-base] "Invalid class base with type `<special-form 'typing.NamedTuple'>`"
1038+
NT = type("NT", (NamedTuple,), {})
10261039
```
10271040

10281041
### Protocol bases

crates/ty_python_semantic/src/types/class.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5255,12 +5255,9 @@ impl<'db> DynamicClassLiteral<'db> {
52555255
// Convert Types to ClassBases for metaclass computation.
52565256
// All bases should convert successfully here: `try_mro()` above would have
52575257
// returned `Err(InvalidBases)` if any failed, causing us to return early.
5258-
let placeholder_class: ClassLiteral<'db> =
5259-
KnownClass::Object.try_to_class_literal(db).unwrap().into();
5260-
52615258
let bases: Vec<ClassBase<'db>> = original_bases
52625259
.iter()
5263-
.filter_map(|base_type| ClassBase::try_from_type(db, *base_type, placeholder_class))
5260+
.filter_map(|base_type| ClassBase::try_from_type(db, *base_type, None))
52645261
.collect();
52655262

52665263
// If all bases failed to convert, return type as the metaclass.

crates/ty_python_semantic/src/types/class_base.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl<'db> ClassBase<'db> {
9494
pub(super) fn try_from_type(
9595
db: &'db dyn Db,
9696
ty: Type<'db>,
97-
subclass: ClassLiteral<'db>,
97+
subclass: Option<ClassLiteral<'db>>,
9898
) -> Option<Self> {
9999
match ty {
100100
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
@@ -252,7 +252,7 @@ impl<'db> ClassBase<'db> {
252252
SpecialFormType::Generic => Some(Self::Generic),
253253

254254
SpecialFormType::NamedTuple => {
255-
let class = subclass.as_static()?;
255+
let class = subclass?.as_static()?;
256256
let fields = class.own_fields(db, None, CodeGeneratorKind::NamedTuple);
257257
Self::try_from_type(
258258
db,

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8697,11 +8697,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
86978697
// Get AST nodes for base expressions (for diagnostics).
86988698
let bases_tuple_elts = bases_node.as_tuple_expr().map(|t| t.elts.as_slice());
86998699

8700-
// We use a placeholder class literal for `try_from_type` (the subclass parameter is only
8701-
// used for Protocol/TypedDict detection which doesn't apply here).
8702-
let placeholder_class: ClassLiteral<'db> =
8703-
KnownClass::Object.try_to_class_literal(db).unwrap().into();
8704-
87058700
let mut disjoint_bases = IncompatibleBases::default();
87068701

87078702
// Check each base for special cases that are not allowed for dynamic classes.
@@ -8711,7 +8706,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
87118706
.unwrap_or(bases_node);
87128707

87138708
// Try to convert to ClassBase to check for special cases.
8714-
let Some(class_base) = ClassBase::try_from_type(db, *base, placeholder_class) else {
8709+
let Some(class_base) = ClassBase::try_from_type(db, *base, None) else {
87158710
// Can't convert; will be handled by `InvalidBases` error from `try_mro`.
87168711
continue;
87178712
};

crates/ty_python_semantic/src/types/mro.rs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::Db;
88
use crate::types::class_base::ClassBase;
99
use crate::types::generics::Specialization;
1010
use crate::types::{
11-
ClassLiteral, ClassType, DynamicClassLiteral, KnownClass, KnownInstanceType, SpecialFormType,
11+
ClassLiteral, ClassType, DynamicClassLiteral, KnownInstanceType, SpecialFormType,
1212
StaticClassLiteral, Type,
1313
};
1414

@@ -141,25 +141,29 @@ impl<'db> Mro<'db> {
141141
)
142142
) =>
143143
{
144-
ClassBase::try_from_type(db, *single_base, ClassLiteral::Static(class_literal))
145-
.map_or_else(
146-
|| {
147-
Err(StaticMroErrorKind::InvalidBases(Box::from([(
148-
0,
149-
*single_base,
150-
)])))
151-
},
152-
|single_base| {
153-
if single_base.has_cyclic_mro(db) {
154-
Err(StaticMroErrorKind::InheritanceCycle)
155-
} else {
156-
Ok(std::iter::once(ClassBase::Class(class))
157-
.chain(single_base.mro(db, specialization))
158-
.collect())
159-
}
160-
},
161-
)
162-
.map_err(|err| err.into_mro_error(db, class))
144+
ClassBase::try_from_type(
145+
db,
146+
*single_base,
147+
Some(ClassLiteral::Static(class_literal)),
148+
)
149+
.map_or_else(
150+
|| {
151+
Err(StaticMroErrorKind::InvalidBases(Box::from([(
152+
0,
153+
*single_base,
154+
)])))
155+
},
156+
|single_base| {
157+
if single_base.has_cyclic_mro(db) {
158+
Err(StaticMroErrorKind::InheritanceCycle)
159+
} else {
160+
Ok(std::iter::once(ClassBase::Class(class))
161+
.chain(single_base.mro(db, specialization))
162+
.collect())
163+
}
164+
},
165+
)
166+
.map_err(|err| err.into_mro_error(db, class))
163167
}
164168

165169
// The class has multiple explicit bases.
@@ -186,7 +190,7 @@ impl<'db> Mro<'db> {
186190
match ClassBase::try_from_type(
187191
db,
188192
*base,
189-
ClassLiteral::Static(class_literal),
193+
Some(ClassLiteral::Static(class_literal)),
190194
) {
191195
Some(valid_base) => resolved_bases.push(valid_base),
192196
None => invalid_bases.push((i, *base)),
@@ -261,7 +265,7 @@ impl<'db> Mro<'db> {
261265
let Some(base) = ClassBase::try_from_type(
262266
db,
263267
*base,
264-
ClassLiteral::Static(class_literal),
268+
Some(ClassLiteral::Static(class_literal)),
265269
) else {
266270
continue;
267271
};
@@ -336,17 +340,12 @@ impl<'db> Mro<'db> {
336340
) -> Result<Self, DynamicMroError<'db>> {
337341
let original_bases = dynamic.explicit_bases(db);
338342

339-
// Use a placeholder class literal for try_from_type (the subclass parameter is only
340-
// used for NamedTuple subclasses, which doesn't apply here).
341-
let placeholder_class: ClassLiteral<'db> =
342-
KnownClass::Object.try_to_class_literal(db).unwrap().into();
343-
344343
// Convert Types to ClassBases, tracking any that fail conversion.
345344
let mut resolved_bases = Vec::with_capacity(original_bases.len());
346345
let mut invalid_bases = Vec::new();
347346

348347
for (i, base_type) in original_bases.iter().enumerate() {
349-
match ClassBase::try_from_type(db, *base_type, placeholder_class) {
348+
match ClassBase::try_from_type(db, *base_type, None) {
350349
Some(class_base) => resolved_bases.push(class_base),
351350
None => invalid_bases.push((i, *base_type)),
352351
}
@@ -429,14 +428,10 @@ impl<'db> Mro<'db> {
429428
let mut seen = FxHashSet::default();
430429
seen.insert(self_base);
431430

432-
// Use a placeholder class literal for `try_from_type`.
433-
let placeholder_class: ClassLiteral<'db> =
434-
KnownClass::Object.try_to_class_literal(db).unwrap().into();
435-
436431
for base_type in dynamic.explicit_bases(db) {
437432
// Convert `Type` to `ClassBase`, falling back to `Unknown` if conversion fails.
438-
let base = ClassBase::try_from_type(db, *base_type, placeholder_class)
439-
.unwrap_or_else(ClassBase::unknown);
433+
let base =
434+
ClassBase::try_from_type(db, *base_type, None).unwrap_or_else(ClassBase::unknown);
440435

441436
for item in base.mro(db, None) {
442437
if seen.insert(item) {

0 commit comments

Comments
 (0)