diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md index 65eeea44a0..01f3402c74 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md @@ -125,11 +125,11 @@ def _( top_meth: Top[TypeOf[A().method]], bottom_meth: Bottom[TypeOf[A().method]], ): - reveal_type(top_func) # revealed: def function(x: Any) -> None - reveal_type(bottom_func) # revealed: def function(x: Any) -> None + reveal_type(top_func) # revealed: def function(x: Never) -> None + reveal_type(bottom_func) # revealed: def function(x: object) -> None - reveal_type(top_meth) # revealed: bound method A.method(x: Any) -> None - reveal_type(bottom_meth) # revealed: bound method A.method(x: Any) -> None + reveal_type(top_meth) # revealed: bound method A.method(x: Never) -> None + reveal_type(bottom_meth) # revealed: bound method A.method(x: object) -> None ``` ## Callable @@ -698,3 +698,35 @@ static_assert(is_assignable_to(InvariantChild[Any], CovariantBase[A])) static_assert(not is_assignable_to(Top[InvariantChild[Any]], CovariantBase[A])) ``` + +## Attributes + +Attributes on top and bottom materializations are specialized on access. + +```toml +[environment] +python-version = "3.12" +``` + +```py +from ty_extensions import Top, Bottom +from typing import Any + +class Invariant[T]: + def get(self) -> T: + raise NotImplementedError + + def push(self, obj: T) -> None: ... + + attr: T + +def capybara(top: Top[Invariant[Any]], bottom: Bottom[Invariant[Any]]) -> None: + reveal_type(top.get) # revealed: bound method Top[Invariant[Any]].get() -> object + reveal_type(top.push) # revealed: bound method Top[Invariant[Any]].push(obj: Never) -> None + + reveal_type(bottom.get) # revealed: bound method Bottom[Invariant[Any]].get() -> Never + reveal_type(bottom.push) # revealed: bound method Bottom[Invariant[Any]].push(obj: object) -> None + + reveal_type(top.attr) # revealed: object + reveal_type(bottom.attr) # revealed: Never +``` diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 764fdc1a5d..06c81c093f 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -10,8 +10,9 @@ use crate::semantic_index::{ }; use crate::semantic_index::{DeclarationWithConstraint, global_scope, use_def_map}; use crate::types::{ - DynamicType, KnownClass, Truthiness, Type, TypeAndQualifiers, TypeQualifiers, UnionBuilder, - UnionType, binding_type, declaration_type, todo_type, + ApplyTypeMappingVisitor, DynamicType, KnownClass, MaterializationKind, Truthiness, Type, + TypeAndQualifiers, TypeQualifiers, UnionBuilder, UnionType, binding_type, declaration_type, + todo_type, }; use crate::{Db, FxOrderSet, Program, resolve_module}; @@ -614,6 +615,15 @@ impl<'db> PlaceAndQualifiers<'db> { } } + pub(crate) fn materialize( + self, + db: &'db dyn Db, + materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, + ) -> PlaceAndQualifiers<'db> { + self.map_type(|ty| ty.materialize(db, materialization_kind, visitor)) + } + /// Transform place and qualifiers into a [`LookupResult`], /// a [`Result`] type in which the `Ok` variant represents a definitely bound place /// and the `Err` variant represents a place that is either definitely or possibly unbound. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ece4c38ab1..bdb74d90ea 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -526,16 +526,6 @@ impl<'db> PropertyInstanceType<'db> { ty.find_legacy_typevars_impl(db, binding_context, typevars, visitor); } } - - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { - Self::new( - db, - self.getter(db) - .map(|ty| ty.materialize(db, materialization_kind)), - self.setter(db) - .map(|ty| ty.materialize(db, materialization_kind)), - ) - } } bitflags! { @@ -778,14 +768,22 @@ 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, MaterializationKind::Top) + self.materialize( + db, + MaterializationKind::Top, + &ApplyTypeMappingVisitor::default(), + ) } /// 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, MaterializationKind::Bottom) + self.materialize( + db, + MaterializationKind::Bottom, + &ApplyTypeMappingVisitor::default(), + ) } /// If this type is an instance type where the class has a tuple spec, returns the tuple spec. @@ -817,103 +815,25 @@ impl<'db> Type<'db> { /// 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 covariant position, it's replaced with `object` (TODO: it should be the `TypeVar`'s upper + /// bound, if any) /// - 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, materialization_kind: MaterializationKind) -> Type<'db> { - match self { - Type::Dynamic(_) => match materialization_kind { - MaterializationKind::Top => Type::object(db), - MaterializationKind::Bottom => Type::Never, - }, - - Type::Never - | Type::WrapperDescriptor(_) - | Type::MethodWrapper(_) - | Type::DataclassDecorator(_) - | Type::DataclassTransformer(_) - | Type::ModuleLiteral(_) - | Type::IntLiteral(_) - | Type::BooleanLiteral(_) - | Type::StringLiteral(_) - | Type::LiteralString - | Type::BytesLiteral(_) - | Type::EnumLiteral(_) - | Type::SpecialForm(_) - | Type::KnownInstance(_) - | Type::AlwaysFalsy - | Type::AlwaysTruthy - | Type::ClassLiteral(_) - | Type::BoundSuper(_) => *self, - - Type::PropertyInstance(property_instance) => { - Type::PropertyInstance(property_instance.materialize(db, materialization_kind)) - } - - 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(instance) => instance.materialize(db, materialization_kind), - Type::GenericAlias(generic_alias) => { - Type::GenericAlias(generic_alias.materialize(db, materialization_kind)) - } - Type::Callable(callable_type) => { - Type::Callable(callable_type.materialize(db, materialization_kind)) - } - Type::SubclassOf(subclass_of_type) => { - subclass_of_type.materialize(db, materialization_kind) - } - 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, materialization_kind)) - } - Type::Union(union_type) => { - union_type.map(db, |ty| ty.materialize(db, materialization_kind)) - } - Type::Intersection(intersection_type) => IntersectionBuilder::new(db) - .positive_elements( - intersection_type - .positive(db) - .iter() - .map(|ty| ty.materialize(db, materialization_kind)), - ) - .negative_elements( - intersection_type - .negative(db) - .iter() - .map(|ty| ty.materialize(db, materialization_kind.flip())), - ) - .build(), - Type::TypeVar(bound_typevar) => { - Type::TypeVar(bound_typevar.materialize(db, materialization_kind)) - } - Type::NonInferableTypeVar(bound_typevar) => { - Type::NonInferableTypeVar(bound_typevar.materialize(db, materialization_kind)) - } - Type::TypeIs(type_is) => { - // 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, materialization_kind), - } + /// - In invariant position, we replace the object with a special form recording that it's the top + /// or bottom materialization. + /// + /// This is implemented as a type mapping. Some specific objects have `materialize()` or + /// `materialize_impl()` methods. The rule of thumb is: + /// + /// - `materialize()` calls `apply_type_mapping()` (or `apply_type_mapping_impl()`) + /// - `materialize_impl()` gets called from `apply_type_mapping()` or from another + /// `materialize_impl()` + pub(crate) fn materialize( + &self, + db: &'db dyn Db, + materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, + ) -> Type<'db> { + self.apply_type_mapping_impl(db, &TypeMapping::Materialize(materialization_kind), visitor) } pub(crate) const fn into_class_literal(self) -> Option> { @@ -2792,7 +2712,17 @@ impl<'db> Type<'db> { } Type::GenericAlias(alias) => { - Some(ClassType::from(*alias).class_member(db, name, policy)) + let attr = Some(ClassType::from(*alias).class_member(db, name, policy)); + match alias.specialization(db).materialization_kind(db) { + None => attr, + Some(materialization_kind) => attr.map(|attr| { + attr.materialize( + db, + materialization_kind, + &ApplyTypeMappingVisitor::default(), + ) + }), + } } Type::SubclassOf(subclass_of_ty) => { @@ -6011,7 +5941,11 @@ impl<'db> Type<'db> { self.apply_type_mapping(db, &TypeMapping::Specialization(specialization)); match specialization.materialization_kind(db) { None => new_specialization, - Some(materialization_kind) => new_specialization.materialize(db, materialization_kind), + Some(materialization_kind) => new_specialization.materialize( + db, + materialization_kind, + &ApplyTypeMappingVisitor::default(), + ), } } @@ -6046,6 +5980,9 @@ impl<'db> Type<'db> { } TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) | TypeMapping::MarkTypeVarsInferable(_) => self, + TypeMapping::Materialize(materialization_kind) => { + Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor)) + } } Type::NonInferableTypeVar(bound_typevar) => match type_mapping { @@ -6066,6 +6003,8 @@ impl<'db> Type<'db> { TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindSelf(_) => self, + TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor)) + } Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping { @@ -6076,7 +6015,8 @@ impl<'db> Type<'db> { TypeMapping::PartialSpecialization(_) | TypeMapping::PromoteLiterals | TypeMapping::BindSelf(_) | - TypeMapping::MarkTypeVarsInferable(_) => self, + TypeMapping::MarkTypeVarsInferable(_) | + TypeMapping::Materialize(_) => self, } Type::FunctionLiteral(function) => { @@ -6093,6 +6033,13 @@ impl<'db> Type<'db> { instance.apply_type_mapping_impl(db, type_mapping, visitor), Type::ProtocolInstance(instance) => { + // TODO: Add tests for materialization 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(instance.apply_type_mapping_impl(db, type_mapping, visitor)) } @@ -6132,9 +6079,7 @@ impl<'db> Type<'db> { Type::TypedDict(typed_dict.apply_type_mapping_impl(db, type_mapping, visitor)) } - Type::SubclassOf(subclass_of) => Type::SubclassOf( - subclass_of.apply_type_mapping_impl(db, type_mapping, visitor), - ), + Type::SubclassOf(subclass_of) => subclass_of.apply_type_mapping_impl(db, type_mapping, visitor), Type::PropertyInstance(property) => { Type::PropertyInstance(property.apply_type_mapping_impl(db, type_mapping, visitor)) @@ -6149,13 +6094,18 @@ impl<'db> Type<'db> { builder = builder.add_positive(positive.apply_type_mapping_impl(db, type_mapping, visitor)); } + let flipped_mapping = match type_mapping { + TypeMapping::Materialize(materialization_kind) => &TypeMapping::Materialize(materialization_kind.flip()), + _ => type_mapping, + }; for negative in intersection.negative(db) { builder = - builder.add_negative(negative.apply_type_mapping_impl(db, type_mapping, visitor)); + builder.add_negative(negative.apply_type_mapping_impl(db, flipped_mapping, visitor)); } builder.build() } + // TODO(jelle): Materialize should be handled differently, since TypeIs is invariant Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)), Type::TypeAlias(alias) => { @@ -6173,13 +6123,26 @@ impl<'db> Type<'db> { TypeMapping::PartialSpecialization(_) | TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindSelf(_) | - TypeMapping::MarkTypeVarsInferable(_) => self, + TypeMapping::MarkTypeVarsInferable(_) | + TypeMapping::Materialize(_) => self, TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) .expect("literal type should have fallback instance type"), } - Type::Dynamic(_) - | Type::Never + Type::Dynamic(_) => match type_mapping { + TypeMapping::Specialization(_) | + TypeMapping::PartialSpecialization(_) | + TypeMapping::BindLegacyTypevars(_) | + TypeMapping::BindSelf(_) | + TypeMapping::MarkTypeVarsInferable(_) | + TypeMapping::PromoteLiterals => self, + TypeMapping::Materialize(materialization_kind) => match materialization_kind { + MaterializationKind::Top => Type::object(db), + MaterializationKind::Bottom => Type::Never, + } + } + + Type::Never | Type::AlwaysTruthy | Type::AlwaysFalsy | Type::WrapperDescriptor(_) @@ -6692,6 +6655,8 @@ pub enum TypeMapping<'a, 'db> { BindSelf(Type<'db>), /// Marks the typevars that are bound by a generic class or function as inferable. MarkTypeVarsInferable(BindingContext<'db>), + /// Create the top or bottom materialization of a type. + Materialize(MaterializationKind), } fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( @@ -6711,7 +6676,8 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( } TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) - | TypeMapping::MarkTypeVarsInferable(_) => {} + | TypeMapping::MarkTypeVarsInferable(_) + | TypeMapping::Materialize(_) => {} } } @@ -6732,6 +6698,9 @@ impl<'db> TypeMapping<'_, 'db> { TypeMapping::MarkTypeVarsInferable(binding_context) => { TypeMapping::MarkTypeVarsInferable(*binding_context) } + TypeMapping::Materialize(materialization_kind) => { + TypeMapping::Materialize(*materialization_kind) + } } } @@ -6753,6 +6722,9 @@ impl<'db> TypeMapping<'_, 'db> { TypeMapping::MarkTypeVarsInferable(binding_context) => { TypeMapping::MarkTypeVarsInferable(*binding_context) } + TypeMapping::Materialize(materialization_kind) => { + TypeMapping::Materialize(*materialization_kind) + } } } } @@ -7443,7 +7415,12 @@ impl<'db> TypeVarInstance<'db> { ) } - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { + fn materialize_impl( + self, + db: &'db dyn Db, + materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, + ) -> Self { Self::new( db, self.name(db), @@ -7452,26 +7429,32 @@ impl<'db> TypeVarInstance<'db> { .and_then(|bound_or_constraints| match bound_or_constraints { TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some( bound_or_constraints - .materialize(db, materialization_kind) + .materialize_impl(db, materialization_kind, visitor) .into(), ), - TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self - .lazy_bound(db) - .map(|bound| bound.materialize(db, materialization_kind).into()), + TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => { + self.lazy_bound(db).map(|bound| { + bound + .materialize_impl(db, materialization_kind, visitor) + .into() + }) + } TypeVarBoundOrConstraintsEvaluation::LazyConstraints => { self.lazy_constraints(db).map(|constraints| { - constraints.materialize(db, materialization_kind).into() + constraints + .materialize_impl(db, materialization_kind, visitor) + .into() }) } }), self.explicit_variance(db), self._default(db).and_then(|default| match default { TypeVarDefaultEvaluation::Eager(ty) => { - Some(ty.materialize(db, materialization_kind).into()) + Some(ty.materialize(db, materialization_kind, visitor).into()) } TypeVarDefaultEvaluation::Lazy => self .lazy_default(db) - .map(|ty| ty.materialize(db, materialization_kind).into()), + .map(|ty| ty.materialize(db, materialization_kind, visitor).into()), }), self.kind(db), ) @@ -7655,10 +7638,16 @@ impl<'db> BoundTypeVarInstance<'db> { ) } - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { + fn materialize_impl( + self, + db: &'db dyn Db, + materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, + ) -> Self { Self::new( db, - self.typevar(db).materialize(db, materialization_kind), + self.typevar(db) + .materialize_impl(db, materialization_kind, visitor), self.binding_context(db), ) } @@ -7745,18 +7734,23 @@ impl<'db> TypeVarBoundOrConstraints<'db> { } } - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { + fn materialize_impl( + self, + db: &'db dyn Db, + materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, + ) -> Self { match self { - TypeVarBoundOrConstraints::UpperBound(bound) => { - TypeVarBoundOrConstraints::UpperBound(bound.materialize(db, materialization_kind)) - } + TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound( + bound.materialize(db, materialization_kind, visitor), + ), TypeVarBoundOrConstraints::Constraints(constraints) => { TypeVarBoundOrConstraints::Constraints(UnionType::new( db, constraints .elements(db) .iter() - .map(|ty| ty.materialize(db, materialization_kind)) + .map(|ty| ty.materialize(db, materialization_kind, visitor)) .collect::>() .into_boxed_slice(), )) @@ -8987,14 +8981,6 @@ impl<'db> CallableType<'db> { )) } - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { - CallableType::new( - db, - self.signatures(db).materialize(db, materialization_kind), - 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: diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 1bdb85fa50..97bcf20f9b 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -802,17 +802,6 @@ impl<'db> IntersectionBuilder<'db> { self } - pub(crate) fn negative_elements(mut self, elements: I) -> Self - where - I: IntoIterator, - T: Into>, - { - for element in elements { - self = self.add_negative(element.into()); - } - self - } - pub(crate) fn build(mut self) -> Type<'db> { // Avoid allocating the UnionBuilder unnecessarily if we have just one intersection: if self.intersections.len() == 1 { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 2a8f113287..60ab729d32 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -29,10 +29,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def; use crate::types::{ ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, - NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, - TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, - UnionBuilder, VarianceInferable, declaration_type, infer_definition_types, + IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, NormalizedVisitor, + PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation, + TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder, + VarianceInferable, declaration_type, infer_definition_types, }; use crate::{ Db, FxIndexMap, FxOrderSet, Program, @@ -270,19 +270,6 @@ impl<'db> GenericAlias<'db> { ) } - pub(super) fn materialize( - self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Self { - Self::new( - db, - self.origin(db), - self.specialization(db) - .materialize(db, materialization_kind), - ) - } - pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { self.origin(db).definition(db) } @@ -408,17 +395,6 @@ impl<'db> ClassType<'db> { } } - 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, materialization_kind)), - } - } - 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), @@ -769,7 +745,17 @@ impl<'db> ClassType<'db> { let fallback_member_lookup = || { class_literal .own_class_member(db, inherited_generic_context, specialization, name) - .map_type(|ty| ty.apply_optional_specialization(db, specialization)) + .map_type(|ty| { + let ty = ty.apply_optional_specialization(db, specialization); + match specialization.map(|spec| spec.materialization_kind(db)) { + Some(Some(materialization_kind)) => ty.materialize( + db, + materialization_kind, + &ApplyTypeMappingVisitor::default(), + ), + _ => ty, + } + }) }; let synthesize_simple_tuple_method = |return_type| { diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index d794821d31..28dddc32e5 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -302,13 +302,11 @@ impl<'db> ClassBase<'db> { } fn materialize(self, db: &'db dyn Db, kind: MaterializationKind) -> Self { - match self { - ClassBase::Class(class) => Self::Class(class.materialize(db, kind)), - ClassBase::Dynamic(_) - | ClassBase::Generic - | ClassBase::Protocol - | ClassBase::TypedDict => self, - } + self.apply_type_mapping_impl( + db, + &TypeMapping::Materialize(kind), + &ApplyTypeMappingVisitor::default(), + ) } pub(super) fn has_cyclic_mro(self, db: &'db dyn Db) -> bool { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 7bc10a1496..26c90fccb8 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -590,7 +590,11 @@ impl<'db> Specialization<'db> { let new_specialization = self.apply_type_mapping(db, &TypeMapping::Specialization(other)); match other.materialization_kind(db) { None => new_specialization, - Some(materialization_kind) => new_specialization.materialize(db, materialization_kind), + Some(materialization_kind) => new_specialization.materialize_impl( + db, + materialization_kind, + &ApplyTypeMappingVisitor::default(), + ), } } @@ -608,6 +612,9 @@ impl<'db> Specialization<'db> { type_mapping: &TypeMapping<'a, 'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { + if let TypeMapping::Materialize(materialization_kind) = type_mapping { + return self.materialize_impl(db, *materialization_kind, visitor); + } let types: Box<[_]> = self .types(db) .iter() @@ -684,10 +691,11 @@ impl<'db> Specialization<'db> { ) } - pub(super) fn materialize( + pub(super) fn materialize_impl( self, db: &'db dyn Db, materialization_kind: MaterializationKind, + visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { // The top and bottom materializations are fully static types already, so materializing them // further does nothing. @@ -705,14 +713,17 @@ impl<'db> Specialization<'db> { TypeVarVariance::Bivariant => { // With bivariance, all specializations are subtypes of each other, // so any materialization is acceptable. - vartype.materialize(db, MaterializationKind::Top) + vartype.materialize(db, MaterializationKind::Top, visitor) + } + TypeVarVariance::Covariant => { + vartype.materialize(db, materialization_kind, visitor) } - TypeVarVariance::Covariant => vartype.materialize(db, materialization_kind), TypeVarVariance::Contravariant => { - vartype.materialize(db, materialization_kind.flip()) + vartype.materialize(db, materialization_kind.flip(), visitor) } TypeVarVariance::Invariant => { - let top_materialization = vartype.materialize(db, MaterializationKind::Top); + let top_materialization = + vartype.materialize(db, MaterializationKind::Top, visitor); if !vartype.is_equivalent_to(db, top_materialization) { has_dynamic_invariant_typevar = true; } @@ -723,7 +734,11 @@ impl<'db> Specialization<'db> { .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, materialization_kind) + tuple.apply_type_mapping_impl( + db, + &TypeMapping::Materialize(materialization_kind), + visitor, + ) }); let new_materialization_kind = if has_dynamic_invariant_typevar { Some(materialization_kind) diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 51c406ccc2..8714c824aa 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -13,8 +13,8 @@ use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ ApplyTypeMappingVisitor, ClassBase, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsDisjointVisitor, IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, - TypeRelation, VarianceInferable, + IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, + VarianceInferable, }; use crate::{Db, FxOrderSet}; @@ -260,21 +260,6 @@ impl<'db> NominalInstanceType<'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, materialization_kind)) - } - NominalInstanceInner::NonTuple(class) => { - Type::non_tuple_instance(class.materialize(db, materialization_kind)) - } - } - } - pub(super) fn has_relation_to_impl>( self, db: &'db dyn Db, @@ -588,20 +573,6 @@ impl<'db> ProtocolInstanceType<'db> { } } - 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, materialization_kind)) - } - } - } - pub(super) fn apply_type_mapping_impl<'a>( self, db: &'db dyn Db, @@ -685,7 +656,7 @@ mod synthesized_protocol { use crate::types::protocol_class::ProtocolInterface; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, - MaterializationKind, NormalizedVisitor, TypeMapping, TypeVarVariance, VarianceInferable, + NormalizedVisitor, TypeMapping, TypeVarVariance, VarianceInferable, }; use crate::{Db, FxOrderSet}; @@ -712,14 +683,6 @@ mod synthesized_protocol { Self(interface.normalized_impl(db, visitor)) } - 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>( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index f375885512..147187db90 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -19,8 +19,8 @@ use crate::{ types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction, - MaterializationKind, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, - TypeQualifiers, TypeRelation, VarianceInferable, + NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, + TypeRelation, VarianceInferable, constraints::{Constraints, IteratorConstraintsExtension}, signatures::{Parameter, Parameters}, }, @@ -256,20 +256,6 @@ impl<'db> ProtocolInterface<'db> { ) } - 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, materialization_kind))) - .collect::>(), - ) - } - pub(super) fn specialized_and_normalized<'a>( self, db: &'db dyn Db, @@ -382,13 +368,6 @@ impl<'db> ProtocolMemberData<'db> { .find_legacy_typevars_impl(db, binding_context, typevars, visitor); } - fn materialize(&self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { - Self { - kind: self.kind.materialize(db, materialization_kind), - qualifiers: self.qualifiers, - } - } - fn display(&self, db: &'db dyn Db) -> impl std::fmt::Display { struct ProtocolMemberDataDisplay<'db> { db: &'db dyn Db, @@ -492,20 +471,6 @@ impl<'db> ProtocolMemberKind<'db> { } } } - - fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self { - match self { - ProtocolMemberKind::Method(callable) => { - ProtocolMemberKind::Method(callable.materialize(db, materialization_kind)) - } - ProtocolMemberKind::Property(property) => { - ProtocolMemberKind::Property(property.materialize(db, materialization_kind)) - } - ProtocolMemberKind::Other(ty) => { - ProtocolMemberKind::Other(ty.materialize(db, materialization_kind)) - } - } - } } /// A single member of a protocol interface. diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 70c1641a0c..029869a0b2 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -58,18 +58,6 @@ impl<'db> CallableSignature<'db> { self.overloads.iter() } - pub(super) fn materialize( - &self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Self { - Self::from_overloads( - self.overloads - .iter() - .map(|signature| signature.materialize(db, materialization_kind)), - ) - } - pub(crate) fn normalized_impl( &self, db: &'db dyn Db, @@ -414,21 +402,6 @@ impl<'db> Signature<'db> { 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, materialization_kind.flip()), - return_ty: Some( - self.return_ty - .unwrap_or(Type::unknown()) - .materialize(db, materialization_kind), - ), - } - } - pub(crate) fn normalized_impl( &self, db: &'db dyn Db, @@ -469,13 +442,19 @@ impl<'db> Signature<'db> { type_mapping: &TypeMapping<'a, 'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { + let flipped_mapping = match type_mapping { + TypeMapping::Materialize(materialization_kind) => { + &TypeMapping::Materialize(materialization_kind.flip()) + } + _ => type_mapping, + }; Self { generic_context: self.generic_context, inherited_generic_context: self.inherited_generic_context, definition: self.definition, parameters: self .parameters - .apply_type_mapping_impl(db, type_mapping, visitor), + .apply_type_mapping_impl(db, flipped_mapping, visitor), return_ty: self .return_ty .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)), @@ -1088,17 +1067,6 @@ impl<'db> Parameters<'db> { } } - 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, materialization_kind)), - ) - } - } - pub(crate) fn as_slice(&self) -> &[Parameter<'db>] { self.value.as_slice() } @@ -1254,13 +1222,27 @@ impl<'db> Parameters<'db> { type_mapping: &TypeMapping<'a, 'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { - Self { - value: self - .value - .iter() - .map(|param| param.apply_type_mapping_impl(db, type_mapping, visitor)) - .collect(), - is_gradual: self.is_gradual, + match type_mapping { + // Note that we've already flipped the materialization in Signature.apply_type_mapping_impl(), + // so the "top" materialization here is the bottom materialization of the whole Signature. + // It might make sense to flip the materialization here instead. + TypeMapping::Materialize(MaterializationKind::Top) if self.is_gradual => { + Parameters::object(db) + } + // TODO: This is wrong, the empty Parameters is not a subtype of all materializations. + // The bottom materialization is not currently representable and implementing it + // properly requires extending the Parameters struct. + TypeMapping::Materialize(MaterializationKind::Bottom) if self.is_gradual => { + Parameters::empty() + } + _ => Self { + value: self + .value + .iter() + .map(|param| param.apply_type_mapping_impl(db, type_mapping, visitor)) + .collect(), + is_gradual: self.is_gradual, + }, } } @@ -1425,18 +1407,6 @@ impl<'db> Parameter<'db> { 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, materialization_kind), - ), - kind: self.kind.clone(), - form: self.form, - } - } - fn apply_type_mapping_impl<'a>( &self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 43f01dee22..e27799210f 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -80,35 +80,27 @@ impl<'db> SubclassOfType<'db> { subclass_of.is_dynamic() } - pub(super) fn materialize( - self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Type<'db> { - match self.subclass_of { - SubclassOfInner::Dynamic(_) => match materialization_kind { - MaterializationKind::Top => KnownClass::Type.to_instance(db), - MaterializationKind::Bottom => Type::Never, - }, - SubclassOfInner::Class(_) => Type::SubclassOf(self), - } - } - pub(super) fn apply_type_mapping_impl<'a>( self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>, visitor: &ApplyTypeMappingVisitor<'db>, - ) -> Self { + ) -> Type<'db> { match self.subclass_of { - SubclassOfInner::Class(class) => Self { + SubclassOfInner::Class(class) => Type::SubclassOf(Self { subclass_of: SubclassOfInner::Class(class.apply_type_mapping_impl( db, type_mapping, visitor, )), + }), + SubclassOfInner::Dynamic(_) => match type_mapping { + TypeMapping::Materialize(materialization_kind) => match materialization_kind { + MaterializationKind::Top => KnownClass::Type.to_instance(db), + MaterializationKind::Bottom => Type::Never, + }, + _ => Type::SubclassOf(self), }, - SubclassOfInner::Dynamic(_) => self, } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index f74d0ae0d9..cb947d2695 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -27,8 +27,8 @@ use crate::types::class::{ClassType, KnownClass}; use crate::types::constraints::{Constraints, IteratorConstraintsExtension}; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsDisjointVisitor, IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, Type, - TypeMapping, TypeRelation, UnionBuilder, UnionType, + IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, + UnionBuilder, UnionType, }; use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError}; use crate::{Db, FxOrderSet, Program}; @@ -228,14 +228,6 @@ impl<'db> TupleType<'db> { TupleType::new(db, &self.tuple(db).normalized_impl(db, visitor)) } - pub(crate) fn materialize( - self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Option { - TupleType::new(db, &self.tuple(db).materialize(db, materialization_kind)) - } - pub(crate) fn apply_type_mapping_impl<'a>( self, db: &'db dyn Db, @@ -394,14 +386,6 @@ impl<'db> FixedLengthTuple> { Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor))) } - 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>( &self, db: &'db dyn Db, @@ -713,22 +697,6 @@ impl<'db> VariableLengthTuple> { }) } - fn materialize( - &self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> TupleSpec<'db> { - Self::mixed( - 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)), - ) - } - fn apply_type_mapping_impl<'a>( &self, db: &'db dyn Db, @@ -1077,17 +1045,6 @@ impl<'db> Tuple> { } } - pub(crate) fn materialize( - &self, - db: &'db dyn Db, - materialization_kind: MaterializationKind, - ) -> Self { - match self { - Tuple::Fixed(tuple) => Tuple::Fixed(tuple.materialize(db, materialization_kind)), - Tuple::Variable(tuple) => tuple.materialize(db, materialization_kind), - } - } - pub(crate) fn apply_type_mapping_impl<'a>( &self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/typed_dict.rs b/crates/ty_python_semantic/src/types/typed_dict.rs index f6b03a225a..8fe8f500bc 100644 --- a/crates/ty_python_semantic/src/types/typed_dict.rs +++ b/crates/ty_python_semantic/src/types/typed_dict.rs @@ -61,6 +61,7 @@ impl<'db> TypedDictType<'db> { type_mapping: &TypeMapping<'a, 'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Self { + // TODO: Materialization of gradual TypedDicts needs more logic Self { defining_class: self .defining_class