mirror of https://github.com/astral-sh/ruff
Try adding a specialization to non-generic classes
This commit is contained in:
parent
a4531bf865
commit
b59c6d4fc4
|
|
@ -56,6 +56,15 @@ class D(C[T]): ...
|
||||||
|
|
||||||
(Examples `E` and `F` from above do not have analogues in the legacy syntax.)
|
(Examples `E` and `F` from above do not have analogues in the legacy syntax.)
|
||||||
|
|
||||||
|
## fwomp
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C[T]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
reveal_type(C[int]()) # revealed: C[int]
|
||||||
|
```
|
||||||
|
|
||||||
## Specializing generic classes explicitly
|
## Specializing generic classes explicitly
|
||||||
|
|
||||||
The type parameter can be specified explicitly:
|
The type parameter can be specified explicitly:
|
||||||
|
|
@ -158,7 +167,7 @@ If the type of a constructor parameter is a class typevar, we can use that to in
|
||||||
parameter. The types inferred from a type context and from a constructor parameter must be
|
parameter. The types inferred from a type context and from a constructor parameter must be
|
||||||
consistent with each other.
|
consistent with each other.
|
||||||
|
|
||||||
## `__new__` only
|
### `__new__` only
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class C[T]:
|
class C[T]:
|
||||||
|
|
@ -171,7 +180,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||||
wrong_innards: C[int] = C("five")
|
wrong_innards: C[int] = C("five")
|
||||||
```
|
```
|
||||||
|
|
||||||
## `__init__` only
|
### `__init__` only
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class C[T]:
|
class C[T]:
|
||||||
|
|
@ -183,7 +192,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||||
wrong_innards: C[int] = C("five")
|
wrong_innards: C[int] = C("five")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Identical `__new__` and `__init__` signatures
|
### Identical `__new__` and `__init__` signatures
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class C[T]:
|
class C[T]:
|
||||||
|
|
@ -198,7 +207,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||||
wrong_innards: C[int] = C("five")
|
wrong_innards: C[int] = C("five")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatible `__new__` and `__init__` signatures
|
### Compatible `__new__` and `__init__` signatures
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class C[T]:
|
class C[T]:
|
||||||
|
|
@ -224,7 +233,7 @@ reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||||
wrong_innards: D[int] = D("five")
|
wrong_innards: D[int] = D("five")
|
||||||
```
|
```
|
||||||
|
|
||||||
## `__init__` is itself generic
|
### `__init__` is itself generic
|
||||||
|
|
||||||
TODO: These do not currently work yet, because we don't correctly model the nested generic contexts.
|
TODO: These do not currently work yet, because we don't correctly model the nested generic contexts.
|
||||||
|
|
||||||
|
|
@ -285,6 +294,33 @@ c: C[int] = C[int]()
|
||||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
reveal_type(c.method("string")) # revealed: Literal["string"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Nested classes
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C[T]:
|
||||||
|
class D:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
class E[U]:
|
||||||
|
x: T
|
||||||
|
y: U
|
||||||
|
|
||||||
|
def method1(self) -> "D":
|
||||||
|
return self.D()
|
||||||
|
|
||||||
|
def method2(self) -> "E[str]":
|
||||||
|
return self.E[str]()
|
||||||
|
|
||||||
|
reveal_type(C[int]().method1()) # revealed: D
|
||||||
|
# TODO: revealed: int
|
||||||
|
reveal_type(C[int]().method1().x) # revealed: T
|
||||||
|
|
||||||
|
reveal_type(C[int]().method2()) # revealed: E[str]
|
||||||
|
# TODO: revealed: int
|
||||||
|
reveal_type(C[int]().method2().x) # revealed: T
|
||||||
|
reveal_type(C[int]().method2().y) # revealed: str
|
||||||
|
```
|
||||||
|
|
||||||
## Cyclic class definition
|
## Cyclic class definition
|
||||||
|
|
||||||
A class can use itself as the type parameter of one of its superclasses. (This is also known as the
|
A class can use itself as the type parameter of one of its superclasses. (This is also known as the
|
||||||
|
|
|
||||||
|
|
@ -4651,6 +4651,12 @@ impl<'db> Type<'db> {
|
||||||
Type::Callable(callable.apply_specialization(db, specialization))
|
Type::Callable(callable.apply_specialization(db, specialization))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type::ClassLiteral(ClassLiteralType::NonGeneric(class)) => {
|
||||||
|
Type::from(class.apply_specialization(db, specialization))
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::ClassLiteral(ClassLiteralType::Generic(_)) => self,
|
||||||
|
|
||||||
Type::GenericAlias(generic) => {
|
Type::GenericAlias(generic) => {
|
||||||
let specialization = generic
|
let specialization = generic
|
||||||
.specialization(db)
|
.specialization(db)
|
||||||
|
|
@ -4692,10 +4698,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::MethodWrapper(MethodWrapperKind::StrStartswith(_))
|
| Type::MethodWrapper(MethodWrapperKind::StrStartswith(_))
|
||||||
| Type::DataclassDecorator(_)
|
| Type::DataclassDecorator(_)
|
||||||
| Type::ModuleLiteral(_)
|
| Type::ModuleLiteral(_)
|
||||||
// A non-generic class never needs to be specialized. A generic class is specialized
|
|
||||||
// explicitly (via a subscript expression) or implicitly (via a call), and not because
|
|
||||||
// some other generic context's specialization is applied to it.
|
|
||||||
| Type::ClassLiteral(_)
|
|
||||||
// SubclassOf contains a ClassType, which has already been specialized if needed, like
|
// SubclassOf contains a ClassType, which has already been specialized if needed, like
|
||||||
// above with BoundMethod's self_instance.
|
// above with BoundMethod's self_instance.
|
||||||
| Type::SubclassOf(_)
|
| Type::SubclassOf(_)
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,23 @@ impl<'db> Class<'db> {
|
||||||
pub struct NonGenericClass<'db> {
|
pub struct NonGenericClass<'db> {
|
||||||
#[return_ref]
|
#[return_ref]
|
||||||
pub(crate) class: Class<'db>,
|
pub(crate) class: Class<'db>,
|
||||||
|
|
||||||
|
pub(crate) specialization: Option<Specialization<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> NonGenericClass<'db> {
|
||||||
|
pub(crate) fn apply_specialization(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
specialization: Specialization<'db>,
|
||||||
|
) -> Self {
|
||||||
|
eprintln!(
|
||||||
|
"==> specialize {} with {}",
|
||||||
|
Type::from(self).display(db),
|
||||||
|
specialization.display(db)
|
||||||
|
);
|
||||||
|
NonGenericClass::new(db, self.class(db), Some(specialization))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<NonGenericClass<'db>> for Type<'db> {
|
impl<'db> From<NonGenericClass<'db>> for Type<'db> {
|
||||||
|
|
@ -223,7 +240,13 @@ impl<'db> ClassType<'db> {
|
||||||
|
|
||||||
fn specialize_type(self, db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
|
fn specialize_type(self, db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Self::NonGeneric(_) => ty,
|
Self::NonGeneric(non_generic) => {
|
||||||
|
if let Some(specialization) = non_generic.specialization(db) {
|
||||||
|
ty.apply_specialization(db, specialization)
|
||||||
|
} else {
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Generic(generic) => ty.apply_specialization(db, generic.specialization(db)),
|
Self::Generic(generic) => ty.apply_specialization(db, generic.specialization(db)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1795,7 +1795,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Some(generic_context) => {
|
Some(generic_context) => {
|
||||||
ClassLiteralType::Generic(GenericClass::new(self.db(), class, generic_context))
|
ClassLiteralType::Generic(GenericClass::new(self.db(), class, generic_context))
|
||||||
}
|
}
|
||||||
None => ClassLiteralType::NonGeneric(NonGenericClass::new(self.db(), class)),
|
None => {
|
||||||
|
let specialization = None;
|
||||||
|
ClassLiteralType::NonGeneric(NonGenericClass::new(self.db(), class, specialization))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let class_ty = Type::from(class_literal);
|
let class_ty = Type::from(class_literal);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue