mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 13:15:06 +00:00
[ty] Intersect with a dynamic type when calculating the metaclass of a class if that class has a dynamic type in its MRO
This commit is contained in:
parent
08d8819c8a
commit
2d04d99315
6 changed files with 35 additions and 13 deletions
|
@ -1574,7 +1574,7 @@ class B(Any): ...
|
|||
class C(B, A): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, Any, <class 'A'>, <class 'object'>]
|
||||
reveal_type(C.x) # revealed: Literal[1] & Any
|
||||
reveal_type(C.x) # revealed: @Todo(Type::Intersection.call())
|
||||
```
|
||||
|
||||
## Classes with custom `__getattr__` methods
|
||||
|
|
|
@ -158,15 +158,13 @@ from nonexistent_module import UnknownClass # error: [unresolved-import]
|
|||
|
||||
class C(UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[type] & Unknown`
|
||||
reveal_type(C.__class__) # revealed: <class 'type'>
|
||||
reveal_type(C.__class__) # revealed: type[type] & Unknown
|
||||
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A, UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[M] & Unknown`
|
||||
reveal_type(B.__class__) # revealed: <class 'M'>
|
||||
reveal_type(B.__class__) # revealed: type[M] & Unknown
|
||||
```
|
||||
|
||||
## Duplicate
|
||||
|
@ -176,7 +174,7 @@ class M(type): ...
|
|||
class A(metaclass=M): ...
|
||||
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
|
||||
|
||||
reveal_type(B.__class__) # revealed: <class 'M'>
|
||||
reveal_type(B.__class__) # revealed: type[M] & Unknown
|
||||
```
|
||||
|
||||
## Non-class
|
||||
|
|
|
@ -639,16 +639,16 @@ python-version = "3.13"
|
|||
|
||||
```pyi
|
||||
class C(C.a): ...
|
||||
reveal_type(C.__class__) # revealed: <class 'type'>
|
||||
reveal_type(C.__class__) # revealed: type[type] & Unknown
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
|
||||
|
||||
class D(D.a):
|
||||
a: D
|
||||
reveal_type(D.__class__) # revealed: <class 'type'>
|
||||
reveal_type(D.__class__) # revealed: type[type] & Unknown
|
||||
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, Unknown, <class 'object'>]
|
||||
|
||||
class E[T](E.a): ...
|
||||
reveal_type(E.__class__) # revealed: <class 'type'>
|
||||
reveal_type(E.__class__) # revealed: type[type] & Unknown
|
||||
reveal_type(E.__mro__) # revealed: tuple[<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>]
|
||||
|
||||
class F[T](F(), F): ... # error: [cyclic-class-definition]
|
||||
|
|
|
@ -255,8 +255,14 @@ def test(x: Any):
|
|||
static_assert(is_assignable_to(TypeOf[Bar], type[int]))
|
||||
static_assert(is_assignable_to(TypeOf[Bar], type[Any]))
|
||||
|
||||
static_assert(not is_assignable_to(TypeOf[Foo], int))
|
||||
static_assert(not is_assignable_to(TypeOf[Bar], int))
|
||||
# since the metaclass of `Foo` is `Any`,
|
||||
# and `Foo` is an instance of its metaclass,
|
||||
# and `Any` could materialize to `<class 'int'>`,
|
||||
# the object created by the class definition *could*
|
||||
# theoretically be an `int` instance, in which case
|
||||
# `TypeOf[Foo]` would be a subtype of `int`
|
||||
static_assert(is_assignable_to(TypeOf[Foo], int))
|
||||
static_assert(is_assignable_to(TypeOf[Bar], int))
|
||||
```
|
||||
|
||||
This is because the `Any` element in the MRO could materialize to any subtype of `type`.
|
||||
|
|
|
@ -1246,9 +1246,20 @@ impl<'db> ClassLiteral<'db> {
|
|||
});
|
||||
}
|
||||
|
||||
let metaclass = if let Some(dynamic_element) =
|
||||
self.iter_mro(db, None).find_map(ClassBase::into_dynamic)
|
||||
{
|
||||
IntersectionBuilder::new(db)
|
||||
.add_positive(SubclassOfType::from(db, candidate.metaclass))
|
||||
.add_positive(Type::Dynamic(dynamic_element))
|
||||
.build()
|
||||
} else {
|
||||
Type::from(candidate.metaclass)
|
||||
};
|
||||
|
||||
let (metaclass_literal, _) = candidate.metaclass.class_literal(db);
|
||||
Ok((
|
||||
candidate.metaclass.into(),
|
||||
metaclass,
|
||||
metaclass_literal.dataclass_transformer_params(db),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -233,13 +233,20 @@ impl<'db> ClassBase<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
|
||||
pub(super) const fn into_class(self) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class),
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const fn into_dynamic(self) -> Option<DynamicType> {
|
||||
match self {
|
||||
Self::Dynamic(dynamic) => Some(dynamic),
|
||||
Self::Class(_) | Self::Generic | Self::Protocol => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
match self {
|
||||
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue