mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-16 16:40:19 +00:00
[ty] Generate the top and bottom materialization of a type (#18594)
## Summary This is to support https://github.com/astral-sh/ruff/pull/18607. This PR adds support for generating the top materialization (or upper bound materialization) and the bottom materialization (or lower bound materialization) of a type. This is the most general and the most specific form of the type which is fully static, respectively. More concretely, `T'`, the top materialization of `T`, is the type `T` with all occurrences of dynamic type (`Any`, `Unknown`, `@Todo`) replaced as follows: - In covariant position, it's replaced with `object` - In contravariant position, it's replaced with `Never` - In invariant position, it's replaced with an unresolved type variable (For an invariant position, it should actually be replaced with an existential type, but this is not currently representable in our type system, so we use an unresolved type variable for now instead.) The bottom materialization is implemented in the same way, except we start out in "contravariant" position. ## Test Plan Add test cases for various types.
This commit is contained in:
parent
f74527f4e9
commit
ef4108af2a
12 changed files with 798 additions and 3 deletions
|
@ -615,6 +615,120 @@ impl<'db> Type<'db> {
|
|||
matches!(self, Type::Dynamic(_))
|
||||
}
|
||||
|
||||
/// Returns the top materialization (or upper bound materialization) of this type, which is the
|
||||
/// most general form of the type that is fully static.
|
||||
#[must_use]
|
||||
pub(crate) fn top_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.materialize(db, TypeVarVariance::Covariant)
|
||||
}
|
||||
|
||||
/// Returns the bottom materialization (or lower bound materialization) of this type, which is
|
||||
/// the most specific form of the type that is fully static.
|
||||
#[must_use]
|
||||
pub(crate) fn bottom_materialization(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.materialize(db, TypeVarVariance::Contravariant)
|
||||
}
|
||||
|
||||
/// Returns the materialization of this type depending on the given `variance`.
|
||||
///
|
||||
/// More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of
|
||||
/// the dynamic types (`Any`, `Unknown`, `Todo`) replaced as follows:
|
||||
///
|
||||
/// - In covariant position, it's replaced with `object`
|
||||
/// - In contravariant position, it's replaced with `Never`
|
||||
/// - In invariant position, it's replaced with an unresolved type variable
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
||||
match self {
|
||||
Type::Dynamic(_) => match variance {
|
||||
// TODO: For an invariant position, e.g. `list[Any]`, it should be replaced with an
|
||||
// existential type representing "all lists, containing any type." We currently
|
||||
// represent this by replacing `Any` in invariant position with an unresolved type
|
||||
// variable.
|
||||
TypeVarVariance::Invariant => Type::TypeVar(TypeVarInstance::new(
|
||||
db,
|
||||
Name::new_static("T_all"),
|
||||
None,
|
||||
None,
|
||||
variance,
|
||||
None,
|
||||
TypeVarKind::Pep695,
|
||||
)),
|
||||
TypeVarVariance::Covariant => Type::object(db),
|
||||
TypeVarVariance::Contravariant => Type::Never,
|
||||
TypeVarVariance::Bivariant => unreachable!(),
|
||||
},
|
||||
|
||||
Type::Never
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::BoundSuper(_) => *self,
|
||||
|
||||
Type::FunctionLiteral(_) | Type::BoundMethod(_) => {
|
||||
// TODO: Subtyping between function / methods with a callable accounts for the
|
||||
// signature (parameters and return type), so we might need to do something here
|
||||
*self
|
||||
}
|
||||
|
||||
Type::NominalInstance(nominal_instance_type) => {
|
||||
Type::NominalInstance(nominal_instance_type.materialize(db, variance))
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
Type::GenericAlias(generic_alias.materialize(db, variance))
|
||||
}
|
||||
Type::Callable(callable_type) => {
|
||||
Type::Callable(callable_type.materialize(db, variance))
|
||||
}
|
||||
Type::SubclassOf(subclass_of_type) => subclass_of_type.materialize(db, variance),
|
||||
Type::ProtocolInstance(protocol_instance_type) => {
|
||||
// TODO: Add tests for this once subtyping/assignability is implemented for
|
||||
// protocols. It _might_ require changing the logic here because:
|
||||
//
|
||||
// > Subtyping for protocol instances involves taking account of the fact that
|
||||
// > read-only property members, and method members, on protocols act covariantly;
|
||||
// > write-only property members act contravariantly; and read/write attribute
|
||||
// > members on protocols act invariantly
|
||||
Type::ProtocolInstance(protocol_instance_type.materialize(db, variance))
|
||||
}
|
||||
Type::Union(union_type) => union_type.map(db, |ty| ty.materialize(db, variance)),
|
||||
Type::Intersection(intersection_type) => IntersectionBuilder::new(db)
|
||||
.positive_elements(
|
||||
intersection_type
|
||||
.positive(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance)),
|
||||
)
|
||||
.negative_elements(
|
||||
intersection_type
|
||||
.negative(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance.flip())),
|
||||
)
|
||||
.build(),
|
||||
Type::Tuple(tuple_type) => TupleType::from_elements(
|
||||
db,
|
||||
tuple_type
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance)),
|
||||
),
|
||||
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace references to the class `class` with a self-reference marker. This is currently
|
||||
/// used for recursive protocols, but could probably be extended to self-referential type-
|
||||
/// aliases and similar.
|
||||
|
@ -3634,6 +3748,21 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
.into(),
|
||||
|
||||
Some(KnownFunction::TopMaterialization | KnownFunction::BottomMaterialization) => {
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(Some(Name::new_static(
|
||||
"type",
|
||||
)))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any())]),
|
||||
Some(Type::any()),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
Some(KnownFunction::AssertType) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
|
@ -5984,6 +6113,19 @@ impl<'db> TypeVarInstance<'db> {
|
|||
self.kind(db),
|
||||
)
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
self.definition(db),
|
||||
self.bound_or_constraints(db)
|
||||
.map(|b| b.materialize(db, variance)),
|
||||
self.variance(db),
|
||||
self.default_ty(db),
|
||||
self.kind(db),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
|
||||
|
@ -5994,6 +6136,20 @@ pub enum TypeVarVariance {
|
|||
Bivariant,
|
||||
}
|
||||
|
||||
impl TypeVarVariance {
|
||||
/// Flips the polarity of the variance.
|
||||
///
|
||||
/// Covariant becomes contravariant, contravariant becomes covariant, others remain unchanged.
|
||||
pub(crate) const fn flip(self) -> Self {
|
||||
match self {
|
||||
TypeVarVariance::Invariant => TypeVarVariance::Invariant,
|
||||
TypeVarVariance::Covariant => TypeVarVariance::Contravariant,
|
||||
TypeVarVariance::Contravariant => TypeVarVariance::Covariant,
|
||||
TypeVarVariance::Bivariant => TypeVarVariance::Bivariant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
|
||||
pub enum TypeVarBoundOrConstraints<'db> {
|
||||
UpperBound(Type<'db>),
|
||||
|
@ -6011,6 +6167,25 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
match self {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, variance))
|
||||
}
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||
db,
|
||||
constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned if a type is not (or may not be) a context manager.
|
||||
|
@ -7012,6 +7187,14 @@ impl<'db> CallableType<'db> {
|
|||
))
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
CallableType::new(
|
||||
db,
|
||||
self.signatures(db).materialize(db, variance),
|
||||
self.is_function_like(db),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a callable type which represents a fully-static "bottom" callable.
|
||||
///
|
||||
/// Specifically, this represents a callable type with a single signature:
|
||||
|
|
|
@ -675,6 +675,18 @@ impl<'db> Bindings<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::TopMaterialization) => {
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(ty.top_materialization(db));
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::BottomMaterialization) => {
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(ty.bottom_materialization(db));
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Len) => {
|
||||
if let [Some(first_arg)] = overload.parameter_types() {
|
||||
if let Some(len_ty) = first_arg.len(db) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::hash::BuildHasherDefault;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use super::TypeVarVariance;
|
||||
use super::{
|
||||
IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType,
|
||||
SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type,
|
||||
|
@ -173,6 +174,14 @@ impl<'db> GenericAlias<'db> {
|
|||
Self::new(db, self.origin(db), self.specialization(db).normalized(db))
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.origin(db),
|
||||
self.specialization(db).materialize(db, variance),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
self.origin(db).definition(db)
|
||||
}
|
||||
|
@ -223,6 +232,13 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
match self {
|
||||
Self::NonGeneric(_) => self,
|
||||
Self::Generic(generic) => Self::Generic(generic.materialize(db, variance)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn has_pep_695_type_params(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::NonGeneric(class) => class.has_pep_695_type_params(db),
|
||||
|
|
|
@ -890,6 +890,10 @@ pub enum KnownFunction {
|
|||
DunderAllNames,
|
||||
/// `ty_extensions.all_members`
|
||||
AllMembers,
|
||||
/// `ty_extensions.top_materialization`
|
||||
TopMaterialization,
|
||||
/// `ty_extensions.bottom_materialization`
|
||||
BottomMaterialization,
|
||||
}
|
||||
|
||||
impl KnownFunction {
|
||||
|
@ -947,6 +951,8 @@ impl KnownFunction {
|
|||
| Self::IsSingleValued
|
||||
| Self::IsSingleton
|
||||
| Self::IsSubtypeOf
|
||||
| Self::TopMaterialization
|
||||
| Self::BottomMaterialization
|
||||
| Self::GenericContext
|
||||
| Self::DunderAllNames
|
||||
| Self::StaticAssert
|
||||
|
@ -1007,6 +1013,8 @@ pub(crate) mod tests {
|
|||
| KnownFunction::IsAssignableTo
|
||||
| KnownFunction::IsEquivalentTo
|
||||
| KnownFunction::IsGradualEquivalentTo
|
||||
| KnownFunction::TopMaterialization
|
||||
| KnownFunction::BottomMaterialization
|
||||
| KnownFunction::AllMembers => KnownModule::TyExtensions,
|
||||
};
|
||||
|
||||
|
|
|
@ -358,6 +358,25 @@ impl<'db> Specialization<'db> {
|
|||
Self::new(db, self.generic_context(db), types)
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
let types: Box<[_]> = self
|
||||
.generic_context(db)
|
||||
.variables(db)
|
||||
.into_iter()
|
||||
.zip(self.types(db))
|
||||
.map(|(typevar, vartype)| {
|
||||
let variance = match typevar.variance(db) {
|
||||
TypeVarVariance::Invariant => TypeVarVariance::Invariant,
|
||||
TypeVarVariance::Covariant => variance,
|
||||
TypeVarVariance::Contravariant => variance.flip(),
|
||||
TypeVarVariance::Bivariant => unreachable!(),
|
||||
};
|
||||
vartype.materialize(db, variance)
|
||||
})
|
||||
.collect();
|
||||
Specialization::new(db, self.generic_context(db), types)
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use super::protocol_class::ProtocolInterface;
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||
use crate::place::{Boundness, Place, PlaceAndQualifiers};
|
||||
use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
@ -80,6 +80,10 @@ impl<'db> NominalInstanceType<'db> {
|
|||
Self::from_class(self.class.normalized(db))
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::from_class(self.class.materialize(db, variance))
|
||||
}
|
||||
|
||||
pub(super) fn has_relation_to(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
@ -314,6 +318,16 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
match self.inner {
|
||||
// TODO: This should also materialize via `class.materialize(db, variance)`
|
||||
Protocol::FromClass(class) => Self::from_class(class),
|
||||
Protocol::Synthesized(synthesized) => {
|
||||
Self::synthesized(synthesized.materialize(db, variance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
@ -370,7 +384,7 @@ impl<'db> Protocol<'db> {
|
|||
|
||||
mod synthesized_protocol {
|
||||
use crate::types::protocol_class::ProtocolInterface;
|
||||
use crate::types::{TypeMapping, TypeVarInstance};
|
||||
use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
|
||||
|
@ -390,6 +404,10 @@ mod synthesized_protocol {
|
|||
Self(interface.normalized(db))
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self(self.0.materialize(db, variance))
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
|
@ -303,4 +303,20 @@ mod flaky {
|
|||
negation_reverses_subtype_order, db,
|
||||
forall types s, t. s.is_subtype_of(db, t) => t.negate(db).is_subtype_of(db, s.negate(db))
|
||||
);
|
||||
|
||||
// Both the top and bottom materialization tests are flaky in part due to various failures that
|
||||
// it discovers in the current implementation of assignability of the types.
|
||||
// TODO: Create a issue with some example failures to keep track of it
|
||||
|
||||
// `T'`, the top materialization of `T`, should be assignable to `T`.
|
||||
type_property_test!(
|
||||
top_materialization_of_type_is_assignable_to_type, db,
|
||||
forall types t. t.top_materialization(db).is_assignable_to(db, t)
|
||||
);
|
||||
|
||||
// Similarly, `T'`, the bottom materialization of `T`, should also be assignable to `T`.
|
||||
type_property_test!(
|
||||
bottom_materialization_of_type_is_assigneble_to_type, db,
|
||||
forall types t. t.bottom_materialization(db).is_assignable_to(db, t)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ use crate::{
|
|||
{Db, FxOrderSet},
|
||||
};
|
||||
|
||||
use super::TypeVarVariance;
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
/// Returns `Some` if this is a protocol class, `None` otherwise.
|
||||
pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option<ProtocolClassLiteral<'db>> {
|
||||
|
@ -177,6 +179,28 @@ impl<'db> ProtocolInterface<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
match self {
|
||||
Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new(
|
||||
db,
|
||||
members
|
||||
.inner(db)
|
||||
.iter()
|
||||
.map(|(name, data)| {
|
||||
(
|
||||
name.clone(),
|
||||
ProtocolMemberData {
|
||||
ty: data.ty.materialize(db, variance),
|
||||
qualifiers: data.qualifiers,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
)),
|
||||
Self::SelfReference => Self::SelfReference,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn specialized_and_normalized<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{collections::HashMap, slice::Iter};
|
|||
use itertools::EitherOrBoth;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use super::{DynamicType, Type, definition_expression_type};
|
||||
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::{ClassLiteral, TypeMapping, TypeRelation, TypeVarInstance, todo_type};
|
||||
|
@ -53,6 +53,14 @@ impl<'db> CallableSignature<'db> {
|
|||
self.overloads.iter()
|
||||
}
|
||||
|
||||
pub(super) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::from_overloads(
|
||||
self.overloads
|
||||
.iter()
|
||||
.map(|signature| signature.materialize(db, variance)),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
Self::from_overloads(
|
||||
self.overloads
|
||||
|
@ -353,6 +361,20 @@ impl<'db> Signature<'db> {
|
|||
Self::new(Parameters::object(db), Some(Type::Never))
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
inherited_generic_context: self.inherited_generic_context,
|
||||
// Parameters are at contravariant position, so the variance is flipped.
|
||||
parameters: self.parameters.materialize(db, variance.flip()),
|
||||
return_ty: Some(
|
||||
self.return_ty
|
||||
.unwrap_or(Type::unknown())
|
||||
.materialize(db, variance),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
Self {
|
||||
generic_context: self.generic_context.map(|ctx| ctx.normalized(db)),
|
||||
|
@ -984,6 +1006,17 @@ impl<'db> Parameters<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
if self.is_gradual {
|
||||
Parameters::object(db)
|
||||
} else {
|
||||
Parameters::new(
|
||||
self.iter()
|
||||
.map(|parameter| parameter.materialize(db, variance)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_slice(&self) -> &[Parameter<'db>] {
|
||||
self.value.as_slice()
|
||||
}
|
||||
|
@ -1304,6 +1337,18 @@ impl<'db> Parameter<'db> {
|
|||
self
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self {
|
||||
annotated_type: Some(
|
||||
self.annotated_type
|
||||
.unwrap_or(Type::unknown())
|
||||
.materialize(db, variance),
|
||||
),
|
||||
kind: self.kind.clone(),
|
||||
form: self.form,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
Self {
|
||||
annotated_type: self
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation,
|
||||
|
@ -5,6 +7,8 @@ use crate::types::{
|
|||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
use super::{TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance};
|
||||
|
||||
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub struct SubclassOfType<'db> {
|
||||
|
@ -73,6 +77,32 @@ impl<'db> SubclassOfType<'db> {
|
|||
!self.is_dynamic()
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
||||
match self.subclass_of {
|
||||
SubclassOfInner::Dynamic(_) => match variance {
|
||||
TypeVarVariance::Covariant => KnownClass::Type.to_instance(db),
|
||||
TypeVarVariance::Contravariant => Type::Never,
|
||||
TypeVarVariance::Invariant => {
|
||||
// We need to materialize this to `type[T]` but that isn't representable so
|
||||
// we instead use a type variable with an upper bound of `type`.
|
||||
Type::TypeVar(TypeVarInstance::new(
|
||||
db,
|
||||
Name::new_static("T_all"),
|
||||
None,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(
|
||||
KnownClass::Type.to_instance(db),
|
||||
)),
|
||||
variance,
|
||||
None,
|
||||
TypeVarKind::Pep695,
|
||||
))
|
||||
}
|
||||
TypeVarVariance::Bivariant => unreachable!(),
|
||||
},
|
||||
SubclassOfInner::Class(_) => Type::SubclassOf(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue