mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Introduce a representation for the top/bottom materialization of an invariant generic (#20076)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Part of #994. This adds a new field to the Specialization struct to record when we're dealing with the top or bottom materialization of an invariant generic. It also implements subtyping and assignability for these objects. Next planned steps after this is done are to implement other operations on top/bottom materializations; probably attribute access is an important one. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
af259faed5
commit
18eaa659c1
10 changed files with 696 additions and 191 deletions
|
@ -195,6 +195,34 @@ pub(crate) struct IsEquivalent;
|
|||
pub(crate) type NormalizedVisitor<'db> = TypeTransformer<'db, Normalized>;
|
||||
pub(crate) struct Normalized;
|
||||
|
||||
/// How a generic type has been specialized.
|
||||
///
|
||||
/// This matters only if there is at least one invariant type parameter.
|
||||
/// For example, we represent `Top[list[Any]]` as a `GenericAlias` with
|
||||
/// `MaterializationKind` set to Top, which we denote as `Top[list[Any]]`.
|
||||
/// A type `Top[list[T]]` includes all fully static list types `list[U]` where `U` is
|
||||
/// a supertype of `Bottom[T]` and a subtype of `Top[T]`.
|
||||
///
|
||||
/// Similarly, there is `Bottom[list[Any]]`.
|
||||
/// This type is harder to make sense of in a set-theoretic framework, but
|
||||
/// it is a subtype of all materializations of `list[Any]`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
pub enum MaterializationKind {
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl MaterializationKind {
|
||||
/// Flip the materialization type: `Top` becomes `Bottom` and vice versa.
|
||||
#[must_use]
|
||||
pub const fn flip(self) -> Self {
|
||||
match self {
|
||||
Self::Top => Self::Bottom,
|
||||
Self::Bottom => Self::Top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The descriptor protocol distinguishes two kinds of descriptors. Non-data descriptors
|
||||
/// define a `__get__` method, while data descriptors additionally define a `__set__`
|
||||
/// method or a `__delete__` method. This enum is used to categorize attributes into two
|
||||
|
@ -489,11 +517,13 @@ impl<'db> PropertyInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.getter(db).map(|ty| ty.materialize(db, variance)),
|
||||
self.setter(db).map(|ty| ty.materialize(db, variance)),
|
||||
self.getter(db)
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
self.setter(db)
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -738,14 +768,14 @@ impl<'db> Type<'db> {
|
|||
/// 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)
|
||||
self.materialize(db, MaterializationKind::Top)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
self.materialize(db, MaterializationKind::Bottom)
|
||||
}
|
||||
|
||||
/// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
|
||||
|
@ -780,29 +810,11 @@ impl<'db> Type<'db> {
|
|||
/// - 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> {
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> 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(BoundTypeVarInstance::new(
|
||||
db,
|
||||
TypeVarInstance::new(
|
||||
db,
|
||||
Name::new_static("T_all"),
|
||||
None,
|
||||
None,
|
||||
Some(variance),
|
||||
None,
|
||||
TypeVarKind::Pep695,
|
||||
),
|
||||
BindingContext::Synthetic,
|
||||
)),
|
||||
TypeVarVariance::Covariant => Type::object(db),
|
||||
TypeVarVariance::Contravariant => Type::Never,
|
||||
TypeVarVariance::Bivariant => unreachable!(),
|
||||
Type::Dynamic(_) => match materialization_kind {
|
||||
MaterializationKind::Top => Type::object(db),
|
||||
MaterializationKind::Bottom => Type::Never,
|
||||
},
|
||||
|
||||
Type::Never
|
||||
|
@ -825,7 +837,7 @@ impl<'db> Type<'db> {
|
|||
| Type::BoundSuper(_) => *self,
|
||||
|
||||
Type::PropertyInstance(property_instance) => {
|
||||
Type::PropertyInstance(property_instance.materialize(db, variance))
|
||||
Type::PropertyInstance(property_instance.materialize(db, materialization_kind))
|
||||
}
|
||||
|
||||
Type::FunctionLiteral(_) | Type::BoundMethod(_) => {
|
||||
|
@ -834,14 +846,16 @@ impl<'db> Type<'db> {
|
|||
*self
|
||||
}
|
||||
|
||||
Type::NominalInstance(instance) => instance.materialize(db, variance),
|
||||
Type::NominalInstance(instance) => instance.materialize(db, materialization_kind),
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
Type::GenericAlias(generic_alias.materialize(db, variance))
|
||||
Type::GenericAlias(generic_alias.materialize(db, materialization_kind))
|
||||
}
|
||||
Type::Callable(callable_type) => {
|
||||
Type::Callable(callable_type.materialize(db, variance))
|
||||
Type::Callable(callable_type.materialize(db, materialization_kind))
|
||||
}
|
||||
Type::SubclassOf(subclass_of_type) => {
|
||||
subclass_of_type.materialize(db, materialization_kind)
|
||||
}
|
||||
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:
|
||||
|
@ -850,35 +864,45 @@ impl<'db> Type<'db> {
|
|||
// > 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::ProtocolInstance(protocol_instance_type.materialize(db, materialization_kind))
|
||||
}
|
||||
Type::Union(union_type) => {
|
||||
union_type.map(db, |ty| ty.materialize(db, materialization_kind))
|
||||
}
|
||||
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)),
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
)
|
||||
.negative_elements(
|
||||
intersection_type
|
||||
.negative(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance.flip())),
|
||||
.map(|ty| ty.materialize(db, materialization_kind.flip())),
|
||||
)
|
||||
.build(),
|
||||
Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)),
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar.materialize(db, materialization_kind))
|
||||
}
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
Type::NonInferableTypeVar(bound_typevar.materialize(db, variance))
|
||||
Type::NonInferableTypeVar(bound_typevar.materialize(db, materialization_kind))
|
||||
}
|
||||
Type::TypeIs(type_is) => {
|
||||
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
||||
// TODO(jelle): this seems wrong, should be invariant?
|
||||
type_is.with_type(
|
||||
db,
|
||||
type_is
|
||||
.return_type(db)
|
||||
.materialize(db, materialization_kind),
|
||||
)
|
||||
}
|
||||
Type::TypedDict(_) => {
|
||||
// TODO: Materialization of gradual TypedDicts
|
||||
*self
|
||||
}
|
||||
Type::TypeAlias(alias) => alias.value_type(db).materialize(db, variance),
|
||||
Type::TypeAlias(alias) => alias.value_type(db).materialize(db, materialization_kind),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6637,6 +6661,13 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn materialization_kind(&self, db: &'db dyn Db) -> Option<MaterializationKind> {
|
||||
match self {
|
||||
TypeMapping::Specialization(specialization) => specialization.materialization_kind(db),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Singleton types that are heavily special-cased by ty. Despite its name,
|
||||
|
@ -7321,29 +7352,35 @@ impl<'db> TypeVarInstance<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
self.definition(db),
|
||||
self._bound_or_constraints(db)
|
||||
.and_then(|bound_or_constraints| match bound_or_constraints {
|
||||
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||
Some(bound_or_constraints.materialize(db, variance).into())
|
||||
}
|
||||
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some(
|
||||
bound_or_constraints
|
||||
.materialize(db, materialization_kind)
|
||||
.into(),
|
||||
),
|
||||
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self
|
||||
.lazy_bound(db)
|
||||
.map(|bound| bound.materialize(db, variance).into()),
|
||||
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
|
||||
.lazy_constraints(db)
|
||||
.map(|constraints| constraints.materialize(db, variance).into()),
|
||||
.map(|bound| bound.materialize(db, materialization_kind).into()),
|
||||
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => {
|
||||
self.lazy_constraints(db).map(|constraints| {
|
||||
constraints.materialize(db, materialization_kind).into()
|
||||
})
|
||||
}
|
||||
}),
|
||||
self.explicit_variance(db),
|
||||
self._default(db).and_then(|default| match default {
|
||||
TypeVarDefaultEvaluation::Eager(ty) => Some(ty.materialize(db, variance).into()),
|
||||
TypeVarDefaultEvaluation::Eager(ty) => {
|
||||
Some(ty.materialize(db, materialization_kind).into())
|
||||
}
|
||||
TypeVarDefaultEvaluation::Lazy => self
|
||||
.lazy_default(db)
|
||||
.map(|ty| ty.materialize(db, variance).into()),
|
||||
.map(|ty| ty.materialize(db, materialization_kind).into()),
|
||||
}),
|
||||
self.kind(db),
|
||||
)
|
||||
|
@ -7505,10 +7542,10 @@ impl<'db> BoundTypeVarInstance<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.typevar(db).materialize(db, variance),
|
||||
self.typevar(db).materialize(db, materialization_kind),
|
||||
self.binding_context(db),
|
||||
)
|
||||
}
|
||||
|
@ -7585,10 +7622,10 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
match self {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, variance))
|
||||
TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, materialization_kind))
|
||||
}
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||
|
@ -7596,7 +7633,7 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
|||
constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, variance))
|
||||
.map(|ty| ty.materialize(db, materialization_kind))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
))
|
||||
|
@ -8838,10 +8875,10 @@ impl<'db> CallableType<'db> {
|
|||
))
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
CallableType::new(
|
||||
db,
|
||||
self.signatures(db).materialize(db, variance),
|
||||
self.signatures(db).materialize(db, materialization_kind),
|
||||
self.is_function_like(db),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
|
|||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
||||
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||
KnownInstanceType, ManualPEP695TypeAliasType, NormalizedVisitor, PropertyInstanceType,
|
||||
StringLiteralType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable, declaration_type,
|
||||
infer_definition_types, todo_type,
|
||||
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor,
|
||||
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable,
|
||||
declaration_type, infer_definition_types, todo_type,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxOrderSet, Program,
|
||||
|
@ -272,11 +272,16 @@ impl<'db> GenericAlias<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.origin(db),
|
||||
self.specialization(db).materialize(db, variance),
|
||||
self.specialization(db)
|
||||
.materialize(db, materialization_kind),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -404,10 +409,14 @@ impl<'db> ClassType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
match self {
|
||||
Self::NonGeneric(_) => self,
|
||||
Self::Generic(generic) => Self::Generic(generic.materialize(db, variance)),
|
||||
Self::Generic(generic) => Self::Generic(generic.materialize(db, materialization_kind)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ use crate::types::generics::{GenericContext, Specialization};
|
|||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
use crate::types::{
|
||||
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
||||
SubclassOfInner, Type, UnionType, WrapperDescriptorKind,
|
||||
CallableType, IntersectionType, KnownClass, MaterializationKind, MethodWrapperKind, Protocol,
|
||||
StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind,
|
||||
};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
|
@ -614,14 +614,25 @@ impl Display for DisplayGenericAlias<'_> {
|
|||
if let Some(tuple) = self.specialization.tuple(self.db) {
|
||||
tuple.display_with(self.db, self.settings).fmt(f)
|
||||
} else {
|
||||
let prefix = match self.specialization.materialization_kind(self.db) {
|
||||
None => "",
|
||||
Some(MaterializationKind::Top) => "Top[",
|
||||
Some(MaterializationKind::Bottom) => "Bottom[",
|
||||
};
|
||||
let suffix = match self.specialization.materialization_kind(self.db) {
|
||||
None => "",
|
||||
Some(_) => "]",
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{origin}{specialization}",
|
||||
"{prefix}{origin}{specialization}{suffix}",
|
||||
prefix = prefix,
|
||||
origin = self.origin.name(self.db),
|
||||
specialization = self.specialization.display_short(
|
||||
self.db,
|
||||
TupleSpecialization::from_class(self.db, self.origin)
|
||||
),
|
||||
suffix = suffix,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ use crate::types::signatures::{Parameter, Parameters, Signature};
|
|||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||
KnownClass, KnownInstanceType, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
|
||||
declaration_type,
|
||||
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeMapping,
|
||||
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType,
|
||||
binding_type, declaration_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -244,6 +244,7 @@ impl<'db> GenericContext<'db> {
|
|||
db,
|
||||
self,
|
||||
partial.types(db),
|
||||
None,
|
||||
Some(TupleType::homogeneous(db, Type::unknown())),
|
||||
)
|
||||
} else {
|
||||
|
@ -304,7 +305,7 @@ impl<'db> GenericContext<'db> {
|
|||
types: Box<[Type<'db>]>,
|
||||
) -> Specialization<'db> {
|
||||
assert!(self.variables(db).len() == types.len());
|
||||
Specialization::new(db, self, types, None)
|
||||
Specialization::new(db, self, types, None, None)
|
||||
}
|
||||
|
||||
/// Creates a specialization of this generic context for the `tuple` class.
|
||||
|
@ -314,7 +315,7 @@ impl<'db> GenericContext<'db> {
|
|||
element_type: Type<'db>,
|
||||
tuple: TupleType<'db>,
|
||||
) -> Specialization<'db> {
|
||||
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
|
||||
Specialization::new(db, self, Box::from([element_type]), None, Some(tuple))
|
||||
}
|
||||
|
||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||
|
@ -360,7 +361,7 @@ impl<'db> GenericContext<'db> {
|
|||
expanded[idx] = default;
|
||||
}
|
||||
|
||||
Specialization::new(db, self, expanded.into_boxed_slice(), None)
|
||||
Specialization::new(db, self, expanded.into_boxed_slice(), None, None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
|
@ -407,6 +408,14 @@ pub struct Specialization<'db> {
|
|||
pub(crate) generic_context: GenericContext<'db>,
|
||||
#[returns(deref)]
|
||||
pub(crate) types: Box<[Type<'db>]>,
|
||||
/// The materialization kind of the specialization. For example, given an invariant
|
||||
/// generic type `A`, `Top[A[Any]]` is a supertype of all materializations of `A[Any]`,
|
||||
/// and is represented here with `Some(MaterializationKind::Top)`. Similarly,
|
||||
/// `Bottom[A[Any]]` is a subtype of all materializations of `A[Any]`, and is represented
|
||||
/// with `Some(MaterializationKind::Bottom)`.
|
||||
/// The `materialization_kind` field may be non-`None` only if the specialization contains
|
||||
/// dynamic types in invariant positions.
|
||||
pub(crate) materialization_kind: Option<MaterializationKind>,
|
||||
|
||||
/// For specializations of `tuple`, we also store more detailed information about the tuple's
|
||||
/// elements, above what the class's (single) typevar can represent.
|
||||
|
@ -430,6 +439,114 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si
|
|||
}
|
||||
}
|
||||
|
||||
fn is_subtype_in_invariant_position<'db, C: Constraints<'db>>(
|
||||
db: &'db dyn Db,
|
||||
derived_type: &Type<'db>,
|
||||
derived_materialization: MaterializationKind,
|
||||
base_type: &Type<'db>,
|
||||
base_materialization: MaterializationKind,
|
||||
) -> C {
|
||||
let derived_top = derived_type.top_materialization(db);
|
||||
let derived_bottom = derived_type.bottom_materialization(db);
|
||||
let base_top = base_type.top_materialization(db);
|
||||
let base_bottom = base_type.bottom_materialization(db);
|
||||
match (derived_materialization, base_materialization) {
|
||||
// `Derived` is a subtype of `Base` if the range of materializations covered by `Derived`
|
||||
// is a subset of the range covered by `Base`.
|
||||
(MaterializationKind::Top, MaterializationKind::Top) => C::from_bool(
|
||||
db,
|
||||
base_bottom.is_subtype_of(db, derived_bottom)
|
||||
&& derived_top.is_subtype_of(db, base_top),
|
||||
),
|
||||
// One bottom is a subtype of another if it covers a strictly larger set of materializations.
|
||||
(MaterializationKind::Bottom, MaterializationKind::Bottom) => C::from_bool(
|
||||
db,
|
||||
derived_bottom.is_subtype_of(db, base_bottom)
|
||||
&& base_top.is_subtype_of(db, derived_top),
|
||||
),
|
||||
// The bottom materialization of `Derived` is a subtype of the top materialization
|
||||
// of `Base` if there is some type that is both within the
|
||||
// range of types covered by derived and within the range covered by base, because if such a type
|
||||
// exists, it's a subtype of `Top[base]` and a supertype of `Bottom[derived]`.
|
||||
(MaterializationKind::Bottom, MaterializationKind::Top) => C::from_bool(
|
||||
db,
|
||||
(base_bottom.is_subtype_of(db, derived_bottom)
|
||||
&& derived_bottom.is_subtype_of(db, base_top))
|
||||
|| (base_bottom.is_subtype_of(db, derived_top)
|
||||
&& derived_top.is_subtype_of(db, base_top)
|
||||
|| (base_top.is_subtype_of(db, derived_top)
|
||||
&& derived_bottom.is_subtype_of(db, base_top))),
|
||||
),
|
||||
// A top materialization is a subtype of a bottom materialization only if both original
|
||||
// un-materialized types are the same fully static type.
|
||||
(MaterializationKind::Top, MaterializationKind::Bottom) => C::from_bool(
|
||||
db,
|
||||
derived_top.is_subtype_of(db, base_bottom)
|
||||
&& base_top.is_subtype_of(db, derived_bottom),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether two types encountered in an invariant position
|
||||
/// have a relation (subtyping or assignability), taking into account
|
||||
/// that the two types may come from a top or bottom materialization.
|
||||
fn has_relation_in_invariant_position<'db, C: Constraints<'db>>(
|
||||
db: &'db dyn Db,
|
||||
derived_type: &Type<'db>,
|
||||
derived_materialization: Option<MaterializationKind>,
|
||||
base_type: &Type<'db>,
|
||||
base_materialization: Option<MaterializationKind>,
|
||||
relation: TypeRelation,
|
||||
) -> C {
|
||||
match (derived_materialization, base_materialization, relation) {
|
||||
// Top and bottom materializations are fully static types, so subtyping
|
||||
// is the same as assignability.
|
||||
(Some(derived_mat), Some(base_mat), _) => {
|
||||
is_subtype_in_invariant_position(db, derived_type, derived_mat, base_type, base_mat)
|
||||
}
|
||||
// Subtyping between invariant type parameters without a top/bottom materialization involved
|
||||
// is equivalence
|
||||
(None, None, TypeRelation::Subtyping) => {
|
||||
C::from_bool(db, derived_type.is_equivalent_to(db, *base_type))
|
||||
}
|
||||
(None, None, TypeRelation::Assignability) => C::from_bool(
|
||||
db,
|
||||
derived_type.is_assignable_to(db, *base_type)
|
||||
&& base_type.is_assignable_to(db, *derived_type),
|
||||
),
|
||||
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
||||
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
MaterializationKind::Top,
|
||||
base_type,
|
||||
base_mat,
|
||||
),
|
||||
(Some(derived_mat), None, TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Bottom,
|
||||
),
|
||||
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
||||
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
MaterializationKind::Bottom,
|
||||
base_type,
|
||||
base_mat,
|
||||
),
|
||||
(Some(derived_mat), None, TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Top,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Specialization<'db> {
|
||||
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
|
||||
|
@ -481,15 +598,61 @@ impl<'db> Specialization<'db> {
|
|||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
// TODO it seems like this should be possible to do in a much simpler way in
|
||||
// `Self::apply_specialization`; just apply the type mapping to create the new
|
||||
// specialization, then materialize the new specialization appropriately, if the type
|
||||
// mapping is a materialization. But this doesn't work; see discussion in
|
||||
// https://github.com/astral-sh/ruff/pull/20076
|
||||
let applied_materialization_kind = type_mapping.materialization_kind(db);
|
||||
let mut has_dynamic_invariant_typevar = false;
|
||||
let types: Box<[_]> = self
|
||||
.types(db)
|
||||
.iter()
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
.generic_context(db)
|
||||
.variables(db)
|
||||
.into_iter()
|
||||
.zip(self.types(db))
|
||||
.map(|(bound_typevar, vartype)| {
|
||||
let ty = vartype.apply_type_mapping_impl(db, type_mapping, visitor);
|
||||
match (applied_materialization_kind, bound_typevar.variance(db)) {
|
||||
(None, _) => ty,
|
||||
(Some(_), TypeVarVariance::Bivariant) =>
|
||||
// With bivariance, all specializations are subtypes of each other,
|
||||
// so any materialization is acceptable.
|
||||
{
|
||||
ty.materialize(db, MaterializationKind::Top)
|
||||
}
|
||||
(Some(materialization_kind), TypeVarVariance::Covariant) => {
|
||||
ty.materialize(db, materialization_kind)
|
||||
}
|
||||
(Some(materialization_kind), TypeVarVariance::Contravariant) => {
|
||||
ty.materialize(db, materialization_kind.flip())
|
||||
}
|
||||
(Some(_), TypeVarVariance::Invariant) => {
|
||||
let top_materialization = ty.materialize(db, MaterializationKind::Top);
|
||||
if !ty.is_equivalent_to(db, top_materialization) {
|
||||
has_dynamic_invariant_typevar = true;
|
||||
}
|
||||
ty
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tuple_inner = self
|
||||
.tuple_inner(db)
|
||||
.and_then(|tuple| tuple.apply_type_mapping_impl(db, type_mapping, visitor));
|
||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||
let new_materialization_kind = if has_dynamic_invariant_typevar {
|
||||
self.materialization_kind(db)
|
||||
.or(applied_materialization_kind)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Specialization::new(
|
||||
db,
|
||||
self.generic_context(db),
|
||||
types,
|
||||
new_materialization_kind,
|
||||
tuple_inner,
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies an optional specialization to this specialization.
|
||||
|
@ -527,7 +690,8 @@ impl<'db> Specialization<'db> {
|
|||
})
|
||||
.collect();
|
||||
// TODO: Combine the tuple specs too
|
||||
Specialization::new(db, self.generic_context(db), types, None)
|
||||
// TODO(jelle): specialization type?
|
||||
Specialization::new(db, self.generic_context(db), types, None, None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
|
@ -540,25 +704,68 @@ impl<'db> Specialization<'db> {
|
|||
.tuple_inner(db)
|
||||
.and_then(|tuple| tuple.normalized_impl(db, visitor));
|
||||
let context = self.generic_context(db).normalized_impl(db, visitor);
|
||||
Self::new(db, context, types, tuple_inner)
|
||||
Self::new(
|
||||
db,
|
||||
context,
|
||||
types,
|
||||
self.materialization_kind(db),
|
||||
tuple_inner,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
// The top and bottom materializations are fully static types already, so materializing them
|
||||
// further does nothing.
|
||||
if self.materialization_kind(db).is_some() {
|
||||
return self;
|
||||
}
|
||||
let mut has_dynamic_invariant_typevar = false;
|
||||
let types: Box<[_]> = self
|
||||
.generic_context(db)
|
||||
.variables(db)
|
||||
.into_iter()
|
||||
.zip(self.types(db))
|
||||
.map(|(bound_typevar, vartype)| {
|
||||
let variance = bound_typevar.variance_with_polarity(db, variance);
|
||||
vartype.materialize(db, variance)
|
||||
match bound_typevar.variance(db) {
|
||||
TypeVarVariance::Bivariant => {
|
||||
// With bivariance, all specializations are subtypes of each other,
|
||||
// so any materialization is acceptable.
|
||||
vartype.materialize(db, MaterializationKind::Top)
|
||||
}
|
||||
TypeVarVariance::Covariant => vartype.materialize(db, materialization_kind),
|
||||
TypeVarVariance::Contravariant => {
|
||||
vartype.materialize(db, materialization_kind.flip())
|
||||
}
|
||||
TypeVarVariance::Invariant => {
|
||||
let top_materialization = vartype.materialize(db, MaterializationKind::Top);
|
||||
if !vartype.is_equivalent_to(db, top_materialization) {
|
||||
has_dynamic_invariant_typevar = true;
|
||||
}
|
||||
*vartype
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let tuple_inner = self.tuple_inner(db).and_then(|tuple| {
|
||||
// Tuples are immutable, so tuple element types are always in covariant position.
|
||||
tuple.materialize(db, variance)
|
||||
tuple.materialize(db, materialization_kind)
|
||||
});
|
||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||
let new_materialization_kind = if has_dynamic_invariant_typevar {
|
||||
Some(materialization_kind)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Specialization::new(
|
||||
db,
|
||||
self.generic_context(db),
|
||||
types,
|
||||
new_materialization_kind,
|
||||
tuple_inner,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||
|
@ -578,12 +785,20 @@ impl<'db> Specialization<'db> {
|
|||
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
||||
}
|
||||
|
||||
let self_materialization_kind = self.materialization_kind(db);
|
||||
let other_materialization_kind = other.materialization_kind(db);
|
||||
|
||||
let mut result = C::always_satisfiable(db);
|
||||
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||
.zip(self.types(db))
|
||||
.zip(other.types(db))
|
||||
{
|
||||
if self_type.is_dynamic() || other_type.is_dynamic() {
|
||||
// As an optimization, we can return early if either type is dynamic, unless
|
||||
// we're dealing with a top or bottom materialization.
|
||||
if other_materialization_kind.is_none()
|
||||
&& self_materialization_kind.is_none()
|
||||
&& (self_type.is_dynamic() || other_type.is_dynamic())
|
||||
{
|
||||
match relation {
|
||||
TypeRelation::Assignability => continue,
|
||||
TypeRelation::Subtyping => return C::unsatisfiable(db),
|
||||
|
@ -597,14 +812,14 @@ impl<'db> Specialization<'db> {
|
|||
// - invariant: verify that self_type <: other_type AND other_type <: self_type
|
||||
// - bivariant: skip, can't make subtyping/assignability false
|
||||
let compatible = match bound_typevar.variance(db) {
|
||||
TypeVarVariance::Invariant => match relation {
|
||||
TypeRelation::Subtyping => self_type.when_equivalent_to(db, *other_type),
|
||||
TypeRelation::Assignability => C::from_bool(
|
||||
db,
|
||||
self_type.is_assignable_to(db, *other_type)
|
||||
&& other_type.is_assignable_to(db, *self_type),
|
||||
),
|
||||
},
|
||||
TypeVarVariance::Invariant => has_relation_in_invariant_position(
|
||||
db,
|
||||
self_type,
|
||||
self_materialization_kind,
|
||||
other_type,
|
||||
other_materialization_kind,
|
||||
relation,
|
||||
),
|
||||
TypeVarVariance::Covariant => {
|
||||
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
||||
}
|
||||
|
@ -627,6 +842,9 @@ impl<'db> Specialization<'db> {
|
|||
other: Specialization<'db>,
|
||||
visitor: &IsEquivalentVisitor<'db, C>,
|
||||
) -> C {
|
||||
if self.materialization_kind(db) != other.materialization_kind(db) {
|
||||
return C::unsatisfiable(db);
|
||||
}
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return C::unsatisfiable(db);
|
||||
|
|
|
@ -13,7 +13,8 @@ use crate::types::protocol_class::walk_protocol_interface;
|
|||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
|
||||
IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
|
||||
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation,
|
||||
VarianceInferable,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -259,11 +260,17 @@ impl<'db> NominalInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Type<'db> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => Type::tuple(tuple.materialize(db, variance)),
|
||||
NominalInstanceInner::ExactTuple(tuple) => {
|
||||
Type::tuple(tuple.materialize(db, materialization_kind))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.materialize(db, variance))
|
||||
Type::non_tuple_instance(class.materialize(db, materialization_kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -577,12 +584,16 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> 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))
|
||||
Self::synthesized(synthesized.materialize(db, materialization_kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -668,8 +679,8 @@ mod synthesized_protocol {
|
|||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::protocol_class::ProtocolInterface;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, NormalizedVisitor, TypeMapping,
|
||||
TypeVarVariance, VarianceInferable,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, MaterializationKind, NormalizedVisitor,
|
||||
TypeMapping, TypeVarVariance, VarianceInferable,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -696,8 +707,12 @@ mod synthesized_protocol {
|
|||
Self(interface.normalized_impl(db, visitor))
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self(self.0.materialize(db, variance))
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
Self(self.0.materialize(db, materialization_kind))
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping_impl<'a>(
|
||||
|
|
|
@ -18,8 +18,9 @@ use crate::{
|
|||
semantic_index::{definition::Definition, use_def_map},
|
||||
types::{
|
||||
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, HasRelationToVisitor,
|
||||
IsDisjointVisitor, KnownFunction, NormalizedVisitor, PropertyInstanceType, Signature, Type,
|
||||
TypeMapping, TypeQualifiers, TypeRelation, VarianceInferable,
|
||||
IsDisjointVisitor, KnownFunction, MaterializationKind, NormalizedVisitor,
|
||||
PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, TypeRelation,
|
||||
VarianceInferable,
|
||||
constraints::{Constraints, IteratorConstraintsExtension},
|
||||
signatures::{Parameter, Parameters},
|
||||
},
|
||||
|
@ -255,12 +256,16 @@ impl<'db> ProtocolInterface<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.inner(db)
|
||||
.iter()
|
||||
.map(|(name, data)| (name.clone(), data.materialize(db, variance)))
|
||||
.map(|(name, data)| (name.clone(), data.materialize(db, materialization_kind)))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
)
|
||||
}
|
||||
|
@ -365,9 +370,9 @@ impl<'db> ProtocolMemberData<'db> {
|
|||
.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self {
|
||||
kind: self.kind.materialize(db, variance),
|
||||
kind: self.kind.materialize(db, materialization_kind),
|
||||
qualifiers: self.qualifiers,
|
||||
}
|
||||
}
|
||||
|
@ -470,16 +475,16 @@ impl<'db> ProtocolMemberKind<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
match self {
|
||||
ProtocolMemberKind::Method(callable) => {
|
||||
ProtocolMemberKind::Method(callable.materialize(db, variance))
|
||||
ProtocolMemberKind::Method(callable.materialize(db, materialization_kind))
|
||||
}
|
||||
ProtocolMemberKind::Property(property) => {
|
||||
ProtocolMemberKind::Property(property.materialize(db, variance))
|
||||
ProtocolMemberKind::Property(property.materialize(db, materialization_kind))
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => {
|
||||
ProtocolMemberKind::Other(ty.materialize(db, variance))
|
||||
ProtocolMemberKind::Other(ty.materialize(db, materialization_kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
|||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::{
|
||||
BindingContext, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
|
||||
NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
|
||||
todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
@ -57,11 +58,15 @@ impl<'db> CallableSignature<'db> {
|
|||
self.overloads.iter()
|
||||
}
|
||||
|
||||
pub(super) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(super) fn materialize(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
Self::from_overloads(
|
||||
self.overloads
|
||||
.iter()
|
||||
.map(|signature| signature.materialize(db, variance)),
|
||||
.map(|signature| signature.materialize(db, materialization_kind)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -405,17 +410,17 @@ impl<'db> Signature<'db> {
|
|||
self
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
inherited_generic_context: self.inherited_generic_context,
|
||||
definition: self.definition,
|
||||
// Parameters are at contravariant position, so the variance is flipped.
|
||||
parameters: self.parameters.materialize(db, variance.flip()),
|
||||
parameters: self.parameters.materialize(db, materialization_kind.flip()),
|
||||
return_ty: Some(
|
||||
self.return_ty
|
||||
.unwrap_or(Type::unknown())
|
||||
.materialize(db, variance),
|
||||
.materialize(db, materialization_kind),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1063,13 +1068,13 @@ impl<'db> Parameters<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
if self.is_gradual {
|
||||
Parameters::object(db)
|
||||
} else {
|
||||
Parameters::new(
|
||||
self.iter()
|
||||
.map(|parameter| parameter.materialize(db, variance)),
|
||||
.map(|parameter| parameter.materialize(db, materialization_kind)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1395,12 +1400,12 @@ impl<'db> Parameter<'db> {
|
|||
self
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self {
|
||||
annotated_type: Some(
|
||||
self.annotated_type
|
||||
.unwrap_or(Type::unknown())
|
||||
.materialize(db, variance),
|
||||
.materialize(db, materialization_kind),
|
||||
),
|
||||
kind: self.kind.clone(),
|
||||
form: self.form,
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::constraints::Constraints;
|
||||
use crate::types::variance::VarianceInferable;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType, DynamicType,
|
||||
HasRelationToVisitor, IsDisjointVisitor, KnownClass, MemberLookupPolicy, NormalizedVisitor,
|
||||
SpecialFormType, Type, TypeMapping, TypeRelation, TypeVarInstance,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, HasRelationToVisitor,
|
||||
IsDisjointVisitor, KnownClass, MaterializationKind, MemberLookupPolicy, NormalizedVisitor,
|
||||
SpecialFormType, Type, TypeMapping, TypeRelation,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
use super::{TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance};
|
||||
use super::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, get_size2::GetSize)]
|
||||
|
@ -81,34 +79,15 @@ impl<'db> SubclassOfType<'db> {
|
|||
subclass_of.is_dynamic()
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
||||
pub(super) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> 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::NonInferableTypeVar(BoundTypeVarInstance::new(
|
||||
db,
|
||||
TypeVarInstance::new(
|
||||
db,
|
||||
Name::new_static("T_all"),
|
||||
None,
|
||||
Some(
|
||||
TypeVarBoundOrConstraints::UpperBound(
|
||||
KnownClass::Type.to_instance(db),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
Some(variance),
|
||||
None,
|
||||
TypeVarKind::Pep695,
|
||||
),
|
||||
BindingContext::Synthetic,
|
||||
))
|
||||
}
|
||||
TypeVarVariance::Bivariant => unreachable!(),
|
||||
SubclassOfInner::Dynamic(_) => match materialization_kind {
|
||||
MaterializationKind::Top => KnownClass::Type.to_instance(db),
|
||||
MaterializationKind::Bottom => Type::Never,
|
||||
},
|
||||
SubclassOfInner::Class(_) => Type::SubclassOf(self),
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::types::class::{ClassType, KnownClass};
|
|||
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsDisjointVisitor,
|
||||
IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarVariance,
|
||||
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
||||
UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||
|
@ -228,8 +228,12 @@ impl<'db> TupleType<'db> {
|
|||
TupleType::new(db, &self.tuple(db).normalized_impl(db, visitor))
|
||||
}
|
||||
|
||||
pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Option<Self> {
|
||||
TupleType::new(db, &self.tuple(db).materialize(db, variance))
|
||||
pub(crate) fn materialize(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Option<Self> {
|
||||
TupleType::new(db, &self.tuple(db).materialize(db, materialization_kind))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_type_mapping_impl<'a>(
|
||||
|
@ -389,8 +393,12 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
|||
Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor)))
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::from_elements(self.0.iter().map(|ty| ty.materialize(db, variance)))
|
||||
fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
|
||||
Self::from_elements(
|
||||
self.0
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
|
@ -703,11 +711,19 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
})
|
||||
}
|
||||
|
||||
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> TupleSpec<'db> {
|
||||
fn materialize(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> TupleSpec<'db> {
|
||||
Self::mixed(
|
||||
self.prefix.iter().map(|ty| ty.materialize(db, variance)),
|
||||
self.variable.materialize(db, variance),
|
||||
self.suffix.iter().map(|ty| ty.materialize(db, variance)),
|
||||
self.prefix
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
self.variable.materialize(db, materialization_kind),
|
||||
self.suffix
|
||||
.iter()
|
||||
.map(|ty| ty.materialize(db, materialization_kind)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1058,10 +1074,14 @@ impl<'db> Tuple<Type<'db>> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
pub(crate) fn materialize(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
materialization_kind: MaterializationKind,
|
||||
) -> Self {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.materialize(db, variance)),
|
||||
Tuple::Variable(tuple) => tuple.materialize(db, variance),
|
||||
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.materialize(db, materialization_kind)),
|
||||
Tuple::Variable(tuple) => tuple.materialize(db, materialization_kind),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue