diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 810ba914fdb28..1e1ac07aabdc0 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -2003,6 +2003,7 @@ python-version = "3.12" ``` ```py +from typing import final from typing_extensions import TypeVar, Self, Protocol from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of @@ -2094,6 +2095,13 @@ class NominalReturningSelfNotGeneric: def g(self) -> "NominalReturningSelfNotGeneric": return self +@final +class Other: ... + +class NominalReturningOtherClass: + def g(self) -> Other: + raise NotImplementedError + # TODO: should pass static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error] @@ -2112,8 +2120,7 @@ static_assert(not is_assignable_to(NominalLegacy, UsesSelf)) static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped)) static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped)) static_assert(is_assignable_to(NominalWithSelf, UsesSelf)) -# TODO: should pass -static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # error: [static-assert-error] +static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # TODO: these should pass static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error] @@ -2126,6 +2133,8 @@ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctio # TODO: should pass static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error] +static_assert(not is_assignable_to(NominalReturningOtherClass, UsesSelf)) + # These test cases are taken from the typing conformance suite: class ShapeProtocolImplicitSelf(Protocol): def set_scale(self, scale: float) -> Self: ... diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 0f22048cccf82..4957a53914315 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -1376,9 +1376,7 @@ mod implicit_globals { use crate::place::{Definedness, PlaceAndQualifiers, TypeOrigin}; use crate::semantic_index::symbol::Symbol; use crate::semantic_index::{place_table, use_def_map}; - use crate::types::{ - CallableType, KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type, - }; + use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type}; use ruff_python_ast::PythonVersion; use super::{Place, place_from_declarations}; @@ -1461,7 +1459,7 @@ mod implicit_globals { )), ); Place::Defined( - CallableType::function_like(db, signature), + Type::function_like_callable(db, signature), TypeOrigin::Inferred, Definedness::PossiblyUndefined, ) diff --git a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs index 9e6d60668f2cd..d7ab0f621cd78 100644 --- a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -208,7 +208,7 @@ use crate::semantic_index::predicate::{ Predicates, ScopedPredicateId, }; use crate::types::{ - IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType, + CallableTypes, IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType, infer_expression_type, static_expression_truthiness, }; @@ -871,12 +871,14 @@ impl ReachabilityConstraints { return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive); } - let overloads_iterator = - if let Some(Type::Callable(callable)) = ty.try_upcast_to_callable(db) { - callable.signatures(db).overloads.iter() - } else { - return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive); - }; + let overloads_iterator = if let Some(callable) = ty + .try_upcast_to_callable(db) + .and_then(CallableTypes::exactly_one) + { + callable.signatures(db).overloads.iter() + } else { + return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive); + }; let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator .fold((true, true), |(none, all), overload| { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a5a2d1c6af617..2ba63d825618a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -16,6 +16,7 @@ use ruff_db::files::File; use ruff_python_ast as ast; use ruff_python_ast::name::Name; use ruff_text_size::{Ranged, TextRange}; +use smallvec::{SmallVec, smallvec}; use type_ordering::union_or_intersection_elements_ordering; @@ -1532,17 +1533,20 @@ impl<'db> Type<'db> { } } - pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option> { + pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option> { match self { - Type::Callable(_) => Some(self), + Type::Callable(callable) => Some(CallableTypes::one(callable)), - Type::Dynamic(_) => Some(CallableType::function_like(db, Signature::dynamic(self))), + Type::Dynamic(_) => Some(CallableTypes::one(CallableType::function_like( + db, + Signature::dynamic(self), + ))), Type::FunctionLiteral(function_literal) => { - Some(Type::Callable(function_literal.into_callable_type(db))) + Some(CallableTypes::one(function_literal.into_callable_type(db))) } Type::BoundMethod(bound_method) => { - Some(Type::Callable(bound_method.into_callable_type(db))) + Some(CallableTypes::one(bound_method.into_callable_type(db))) } Type::NominalInstance(_) | Type::ProtocolInstance(_) => { @@ -1573,13 +1577,22 @@ impl<'db> Type<'db> { // TODO: This is unsound so in future we can consider an opt-in option to disable it. Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() { SubclassOfInner::Class(class) => Some(class.into_callable(db)), - SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single( - db, - Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))), - )), + SubclassOfInner::Dynamic(dynamic) => { + Some(CallableTypes::one(CallableType::single( + db, + Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))), + ))) + } }, - Type::Union(union) => union.try_map(db, |element| element.try_upcast_to_callable(db)), + Type::Union(union) => { + let mut callables = SmallVec::new(); + for element in union.elements(db) { + let element_callable = element.try_upcast_to_callable(db)?; + callables.extend(element_callable.into_inner()); + } + Some(CallableTypes(callables)) + } Type::EnumLiteral(enum_literal) => enum_literal .enum_class_instance(db) @@ -1587,26 +1600,30 @@ impl<'db> Type<'db> { Type::TypeAlias(alias) => alias.value_type(db).try_upcast_to_callable(db), - Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new( + Type::KnownBoundMethod(method) => Some(CallableTypes::one(CallableType::new( db, CallableSignature::from_overloads(method.signatures(db)), false, ))), - Type::WrapperDescriptor(wrapper_descriptor) => Some(Type::Callable(CallableType::new( - db, - CallableSignature::from_overloads(wrapper_descriptor.signatures(db)), - false, - ))), + Type::WrapperDescriptor(wrapper_descriptor) => { + Some(CallableTypes::one(CallableType::new( + db, + CallableSignature::from_overloads(wrapper_descriptor.signatures(db)), + false, + ))) + } - Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Some(CallableType::single( - db, - Signature::new( - Parameters::new([Parameter::positional_only(None) - .with_annotated_type(newtype.base(db).instance_type(db))]), - Some(Type::NewTypeInstance(newtype)), - ), - )), + Type::KnownInstance(KnownInstanceType::NewType(newtype)) => { + Some(CallableTypes::one(CallableType::single( + db, + Signature::new( + Parameters::new([Parameter::positional_only(None) + .with_annotated_type(newtype.base(db).instance_type(db))]), + Some(Type::NewTypeInstance(newtype)), + ), + ))) + } Type::Never | Type::DataclassTransformer(_) @@ -2182,18 +2199,20 @@ impl<'db> Type<'db> { ) }), - (_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || { - self.try_upcast_to_callable(db).when_some_and(|callable| { - callable.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) + (_, Type::Callable(other_callable)) => { + relation_visitor.visit((self, target, relation), || { + self.try_upcast_to_callable(db).when_some_and(|callables| { + callables.has_relation_to_impl( + db, + other_callable, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) }) - }), + } (_, Type::ProtocolInstance(protocol)) => { relation_visitor.visit((self, target, relation), || { @@ -4092,7 +4111,7 @@ impl<'db> Type<'db> { Some((self, AttributeKind::NormalOrNonDataDescriptor)) } else { Some(( - Type::Callable(callable.bind_self(db)), + Type::Callable(callable.bind_self(db, None)), AttributeKind::NormalOrNonDataDescriptor, )) }; @@ -5626,7 +5645,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - CallableType::single(db, getter_signature), + Type::single_callable(db, getter_signature), Type::none(db), ], )) @@ -5635,7 +5654,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - CallableType::single(db, setter_signature), + Type::single_callable(db, setter_signature), Type::none(db), ], )) @@ -5644,7 +5663,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - CallableType::single(db, deleter_signature), + Type::single_callable(db, deleter_signature), Type::none(db), ], )) @@ -7138,6 +7157,15 @@ impl<'db> Type<'db> { tcx: TypeContext<'db>, visitor: &ApplyTypeMappingVisitor<'db>, ) -> Type<'db> { + // If we are binding `typing.Self`, and this type is what we are binding `Self` to, return + // early. This is not just an optimization, it also prevents us from infinitely expanding + // the type, if it's something that can contain a `Self` reference. + if let TypeMapping::BindSelf(self_type) = type_mapping + && self == *self_type + { + return self; + } + match self { Type::TypeVar(bound_typevar) => match type_mapping { TypeMapping::Specialization(specialization) => { @@ -10956,34 +10984,44 @@ pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( // The Salsa heap is tracked separately. impl get_size2::GetSize for CallableType<'_> {} -impl<'db> CallableType<'db> { +impl<'db> Type<'db> { /// Create a callable type with a single non-overloaded signature. - pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { - Type::Callable(CallableType::new( - db, - CallableSignature::single(signature), - false, - )) + pub(crate) fn single_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { + Type::Callable(CallableType::single(db, signature)) } /// Create a non-overloaded, function-like callable type with a single signature. /// /// A function-like callable will bind `self` when accessed as an attribute on an instance. - pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { - Type::Callable(CallableType::new( - db, - CallableSignature::single(signature), - true, - )) + pub(crate) fn function_like_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { + Type::Callable(CallableType::function_like(db, signature)) + } +} + +impl<'db> CallableType<'db> { + pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> { + CallableType::new(db, CallableSignature::single(signature), false) + } + + pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> { + CallableType::new(db, CallableSignature::single(signature), true) } /// Create a callable type which accepts any parameters and returns an `Unknown` type. pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> { - Self::single(db, Signature::unknown()) + Type::Callable(Self::single(db, Signature::unknown())) } - pub(crate) fn bind_self(self, db: &'db dyn Db) -> CallableType<'db> { - CallableType::new(db, self.signatures(db).bind_self(db, None), false) + pub(crate) fn bind_self( + self, + db: &'db dyn Db, + self_type: Option>, + ) -> CallableType<'db> { + CallableType::new(db, self.signatures(db).bind_self(db, self_type), false) + } + + pub(crate) fn apply_self(self, db: &'db dyn Db, self_type: Type<'db>) -> CallableType<'db> { + CallableType::new(db, self.signatures(db).apply_self(db, self_type), false) } /// Create a callable type which represents a fully-static "bottom" callable. @@ -11077,6 +11115,72 @@ impl<'db> CallableType<'db> { } } +/// Converting a type "into a callable" can possibly return a _union_ of callables. Eventually, +/// when coercing that result to a single type, you'll get a `UnionType`. But this lets you handle +/// that result as a list of `CallableType`s before merging them into a `UnionType` should that be +/// helpful. +/// +/// Note that this type is guaranteed to contain at least one callable. If you need to support "no +/// callables" as a possibility, use `Option`. +#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize, salsa::Update)] +pub(crate) struct CallableTypes<'db>(SmallVec<[CallableType<'db>; 1]>); + +impl<'db> CallableTypes<'db> { + pub(crate) fn one(callable: CallableType<'db>) -> Self { + CallableTypes(smallvec![callable]) + } + + pub(crate) fn from_elements(callables: impl IntoIterator>) -> Self { + let callables: SmallVec<_> = callables.into_iter().collect(); + assert!(!callables.is_empty(), "CallableTypes should not be empty"); + CallableTypes(callables) + } + + pub(crate) fn exactly_one(self) -> Option> { + match self.0.as_slice() { + [single] => Some(*single), + _ => None, + } + } + + fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> { + self.0 + } + + pub(crate) fn into_type(self, db: &'db dyn Db) -> Type<'db> { + match self.0.as_slice() { + [] => unreachable!("CallableTypes should not be empty"), + [single] => Type::Callable(*single), + slice => UnionType::from_elements(db, slice.iter().copied().map(Type::Callable)), + } + } + + pub(crate) fn map(self, mut f: impl FnMut(CallableType<'db>) -> CallableType<'db>) -> Self { + Self::from_elements(self.0.iter().map(|element| f(*element))) + } + + pub(crate) fn has_relation_to_impl( + self, + db: &'db dyn Db, + other: CallableType<'db>, + inferable: InferableTypeVars<'_, 'db>, + relation: TypeRelation<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { + self.0.iter().when_all(db, |element| { + element.has_relation_to_impl( + db, + other, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } +} + /// Represents a specific instance of a bound method type for a builtin class. /// /// Unlike bound methods of user-defined classes, these are not generally instances diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index aaec186b9554a..7de3a86263b21 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -32,7 +32,7 @@ use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::typed_dict::typed_dict_params_from_class_def; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::{ - ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DATACLASS_FLAGS, + ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, CallableTypes, DATACLASS_FLAGS, DataclassFlags, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType, @@ -791,7 +791,7 @@ impl<'db> ClassType<'db> { .with_annotated_type(Type::instance(db, self))]); let synthesized_dunder_method = - CallableType::function_like(db, Signature::new(parameters, Some(return_type))); + Type::function_like_callable(db, Signature::new(parameters, Some(return_type))); Member::definitely_declared(synthesized_dunder_method) } @@ -1013,7 +1013,7 @@ impl<'db> ClassType<'db> { iterable_parameter, ]); - let synthesized_dunder = CallableType::function_like( + let synthesized_dunder = Type::function_like_callable( db, Signature::new_generic(inherited_generic_context, parameters, None), ); @@ -1052,7 +1052,7 @@ impl<'db> ClassType<'db> { /// Return a callable type (or union of callable types) that represents the callable /// constructor signature of this class. #[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)] - pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> { + pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> { let self_ty = Type::from(self); let metaclass_dunder_call_function_symbol = self_ty .member_lookup_with_policy( @@ -1070,7 +1070,7 @@ impl<'db> ClassType<'db> { // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable // by always respecting the signature of the metaclass `__call__`, rather than // using a heuristic which makes unwarranted assumptions to sometimes ignore it. - return Type::Callable(metaclass_dunder_call_function.into_callable_type(db)); + return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db)); } let dunder_new_function_symbol = self_ty.lookup_dunder_new(db); @@ -1098,14 +1098,14 @@ impl<'db> ClassType<'db> { }); let instance_ty = Type::instance(db, self); - let dunder_new_bound_method = Type::Callable(CallableType::new( + let dunder_new_bound_method = CallableType::new( db, dunder_new_signature.bind_self(db, Some(instance_ty)), true, - )); + ); if returns_non_subclass { - return dunder_new_bound_method; + return CallableTypes::one(dunder_new_bound_method); } Some(dunder_new_bound_method) } else { @@ -1148,11 +1148,11 @@ impl<'db> ClassType<'db> { signature.overloads.iter().map(synthesized_signature), ); - Some(Type::Callable(CallableType::new( + Some(CallableType::new( db, synthesized_dunder_init_signature, true, - ))) + )) } else { None } @@ -1162,12 +1162,14 @@ impl<'db> ClassType<'db> { match (dunder_new_function, synthesized_dunder_init_callable) { (Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => { - UnionType::from_elements( - db, - vec![dunder_new_function, synthesized_dunder_init_callable], - ) + CallableTypes::from_elements([ + dunder_new_function, + synthesized_dunder_init_callable, + ]) + } + (Some(constructor), None) | (None, Some(constructor)) => { + CallableTypes::one(constructor) } - (Some(constructor), None) | (None, Some(constructor)) => constructor, (None, None) => { // If no `__new__` or `__init__` method is found, then we fall back to looking for // an `object.__new__` method. @@ -1182,17 +1184,17 @@ impl<'db> ClassType<'db> { if let Place::Defined(Type::FunctionLiteral(new_function), _, _) = new_function_symbol { - Type::Callable( + CallableTypes::one( new_function .into_bound_method_type(db, correct_return_type) .into_callable_type(db), ) } else { // Fallback if no `object.__new__` is found. - CallableType::single( + CallableTypes::one(CallableType::single( db, Signature::new(Parameters::empty(), Some(correct_return_type)), - ) + )) } } } @@ -1208,11 +1210,11 @@ impl<'db> ClassType<'db> { } fn into_callable_cycle_initial<'db>( - _db: &'db dyn Db, + db: &'db dyn Db, _id: salsa::Id, _self: ClassType<'db>, -) -> Type<'db> { - Type::Never +) -> CallableTypes<'db> { + CallableTypes::one(CallableType::bottom(db)) } impl<'db> From> for ClassType<'db> { @@ -2156,7 +2158,7 @@ impl<'db> ClassLiteral<'db> { Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]), Some(field.declared_ty), ); - let property_getter = CallableType::single(db, property_getter_signature); + let property_getter = Type::single_callable(db, property_getter_signature); let property = PropertyInstanceType::new(db, Some(property_getter), None); return Member::definitely_declared(Type::PropertyInstance(property)); } @@ -2370,7 +2372,7 @@ impl<'db> ClassLiteral<'db> { ), _ => Signature::new(Parameters::new(parameters), return_ty), }; - Some(CallableType::function_like(db, signature)) + Some(Type::function_like_callable(db, signature)) }; match (field_policy, name) { @@ -2406,7 +2408,7 @@ impl<'db> ClassLiteral<'db> { Some(KnownClass::Bool.to_instance(db)), ); - Some(CallableType::function_like(db, signature)) + Some(Type::function_like_callable(db, signature)) } (CodeGeneratorKind::DataclassLike(_), "__hash__") => { let unsafe_hash = has_dataclass_param(DataclassFlags::UNSAFE_HASH); @@ -2422,7 +2424,7 @@ impl<'db> ClassLiteral<'db> { Some(KnownClass::Int.to_instance(db)), ); - Some(CallableType::function_like(db, signature)) + Some(Type::function_like_callable(db, signature)) } else if eq && !frozen { Some(Type::none(db)) } else { @@ -2509,7 +2511,7 @@ impl<'db> ClassLiteral<'db> { Some(Type::Never), ); - return Some(CallableType::function_like(db, signature)); + return Some(Type::function_like_callable(db, signature)); } None } @@ -2787,7 +2789,7 @@ impl<'db> ClassLiteral<'db> { Some(Type::none(db)), ); - Some(CallableType::function_like(db, signature)) + Some(Type::function_like_callable(db, signature)) } _ => None, } diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index ab21b49bf381e..cf4e2917cca63 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -1790,7 +1790,11 @@ impl<'db> InteriorNode<'db> { /// Returns a sequent map for this BDD, which records the relationships between the constraints /// that appear in the BDD. - #[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)] + #[salsa::tracked( + returns(ref), + cycle_initial=sequent_map_cycle_initial, + heap_size=ruff_memory_usage::heap_size, + )] fn sequent_map(self, db: &'db dyn Db) -> SequentMap<'db> { let mut map = SequentMap::default(); Node::Interior(self).for_each_constraint(db, &mut |constraint| { @@ -2109,6 +2113,14 @@ impl<'db> InteriorNode<'db> { } } +fn sequent_map_cycle_initial<'db>( + _db: &'db dyn Db, + _id: salsa::Id, + _self: InteriorNode<'db>, +) -> SequentMap<'db> { + SequentMap::default() +} + /// An assignment of one BDD variable to either `true` or `false`. (When evaluating a BDD, we /// must provide an assignment for each variable present in the BDD.) #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 72004219c9518..779a34b0fe26e 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -15,7 +15,7 @@ use crate::types::generics::Specialization; use crate::types::signatures::Signature; use crate::types::{CallDunderError, UnionType}; use crate::types::{ - ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext, + CallableTypes, ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext, TypeVarBoundOrConstraints, class::CodeGeneratorKind, }; use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel}; @@ -876,7 +876,10 @@ pub fn definitions_for_keyword_argument<'db>( let mut resolved_definitions = Vec::new(); - if let Some(Type::Callable(callable_type)) = func_type.try_upcast_to_callable(db) { + if let Some(callable_type) = func_type + .try_upcast_to_callable(db) + .and_then(CallableTypes::exactly_one) + { let signatures = callable_type.signatures(db); // For each signature, find the parameter with the matching name @@ -987,7 +990,10 @@ pub fn call_signature_details<'db>( let func_type = call_expr.func.inferred_type(model); // Use into_callable to handle all the complex type conversions - if let Some(callable_type) = func_type.try_upcast_to_callable(db) { + if let Some(callable_type) = func_type + .try_upcast_to_callable(db) + .map(|callables| callables.into_type(db)) + { let call_arguments = CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| { splatted_value.inferred_type(model) @@ -1042,7 +1048,7 @@ pub fn call_type_simplified_by_overloads<'db>( let func_type = call_expr.func.inferred_type(model); // Use into_callable to handle all the complex type conversions - let callable_type = func_type.try_upcast_to_callable(db)?; + let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db); let bindings = callable_type.bindings(db); // If the callable is trivial this analysis is useless, bail out diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 3451bb7638836..5ee5559d01c56 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -101,8 +101,8 @@ use crate::types::typed_dict::{ }; use crate::types::visitor::any_over_type; use crate::types::{ - CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams, - DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass, + CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType, + DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, @@ -2291,7 +2291,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let is_input_function_like = inferred_ty .try_upcast_to_callable(self.db()) - .and_then(Type::as_callable) + .and_then(CallableTypes::exactly_one) .is_some_and(|callable| callable.is_function_like(self.db())); if is_input_function_like @@ -3284,7 +3284,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // specified in this context. match default { ast::Expr::EllipsisLiteral(_) => { - CallableType::single(self.db(), Signature::new(Parameters::gradual_form(), None)) + Type::single_callable(self.db(), Signature::new(Parameters::gradual_form(), None)) } ast::Expr::List(ast::ExprList { elts, .. }) => { let mut parameter_types = Vec::with_capacity(elts.len()); @@ -3312,7 +3312,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { })) }; - CallableType::single(self.db(), Signature::new(parameters, None)) + Type::single_callable(self.db(), Signature::new(parameters, None)) } ast::Expr::Name(name) => { let name_ty = self.infer_name_load(name); @@ -7945,7 +7945,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // TODO: Useful inference of a lambda's return type will require a different approach, // which does the inference of the body expression based on arguments at each call site, // rather than eagerly computing a return type without knowing the argument types. - CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown()))) + Type::function_like_callable(self.db(), Signature::new(parameters, Some(Type::unknown()))) } fn infer_call_expression( diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index eff43e23e83a5..9abb18cbd4192 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -1011,7 +1011,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let callable_type = if let (Some(parameters), Some(return_type), true) = (parameters, return_type, correct_argument_number) { - CallableType::single(db, Signature::new(parameters, Some(return_type))) + Type::single_callable(db, Signature::new(parameters, Some(return_type))) } else { CallableType::unknown(db) }; @@ -1227,7 +1227,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let argument_type = self.infer_expression(&arguments[0], TypeContext::default()); - let Some(callable_type) = argument_type.try_upcast_to_callable(db) else { + let Some(callable_type) = argument_type + .try_upcast_to_callable(db) + .map(|callables| callables.into_type(self.db())) + else { if let Some(builder) = self .context .report_lint(&INVALID_TYPE_FORM, arguments_slice) diff --git a/crates/ty_python_semantic/src/types/liskov.rs b/crates/ty_python_semantic/src/types/liskov.rs index 44ea9a14e7c21..ec0f1765d7b71 100644 --- a/crates/ty_python_semantic/src/types/liskov.rs +++ b/crates/ty_python_semantic/src/types/liskov.rs @@ -126,7 +126,10 @@ fn check_class_declaration<'db>( break; }; - let Some(superclass_type_as_callable) = superclass_type.try_upcast_to_callable(db) else { + let Some(superclass_type_as_callable) = superclass_type + .try_upcast_to_callable(db) + .map(|callables| callables.into_type(db)) + else { continue; }; diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 52136e4046015..58808e48c2490 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -3,8 +3,8 @@ use crate::place::{builtins_symbol, known_module_symbol}; use crate::types::enums::is_single_member_enum; use crate::types::tuple::TupleType; use crate::types::{ - BoundMethodType, CallableType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter, - Parameters, Signature, SpecialFormType, SubclassOfType, Type, UnionType, + BoundMethodType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter, Parameters, + Signature, SpecialFormType, SubclassOfType, Type, UnionType, }; use crate::{Db, module_resolver::KnownModule}; use quickcheck::{Arbitrary, Gen}; @@ -229,7 +229,7 @@ impl Ty { create_bound_method(db, function, builtins_class) } - Ty::Callable { params, returns } => CallableType::single( + Ty::Callable { params, returns } => Type::single_callable( db, Signature::new( params.into_parameters(db), diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 8e3835b386a76..185bb9d2d552e 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -202,7 +202,7 @@ impl<'db> ProtocolInterface<'db> { Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]), Some(ty.normalized(db)), ); - let property_getter = CallableType::single(db, property_getter_signature); + let property_getter = Type::single_callable(db, property_getter_signature); let property = PropertyInstanceType::new(db, Some(property_getter), None); ( Name::new(name), @@ -300,7 +300,7 @@ impl<'db> ProtocolInterface<'db> { .and(db, || { our_type.has_relation_to_impl( db, - Type::Callable(other_type.bind_self(db)), + Type::Callable(other_type.bind_self(db, None)), inferable, relation, relation_visitor, @@ -311,9 +311,9 @@ impl<'db> ProtocolInterface<'db> { ( ProtocolMemberKind::Method(our_method), ProtocolMemberKind::Method(other_method), - ) => our_method.bind_self(db).has_relation_to_impl( + ) => our_method.bind_self(db, None).has_relation_to_impl( db, - other_method.bind_self(db), + other_method.bind_self(db, None), inferable, relation, relation_visitor, @@ -676,10 +676,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { // unfortunately not sufficient to obtain the `Callable` supertypes of these types, due to the // complex interaction between `__new__`, `__init__` and metaclass `__call__`. let attribute_type = if self.name == "__call__" { - let Some(attribute_type) = other.try_upcast_to_callable(db) else { - return ConstraintSet::from(false); - }; - attribute_type + other } else { let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other .invoke_descriptor_protocol( @@ -696,14 +693,32 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { attribute_type }; - attribute_type.has_relation_to_impl( - db, - Type::Callable(method.bind_self(db)), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) + // TODO: Instances of `typing.Self` in the protocol member should specialize to the + // type that we are checking. Without this, we will treat `Self` as an inferable + // typevar, and allow it to match against _any_ type. + // + // It's not very principled, but we also use the literal fallback type, instead of + // `other` directly. This lets us check whether things like `Literal[0]` satisfy a + // protocol that includes methods that have `typing.Self` annotations, without + // overly constraining `Self` to that specific literal. + // + // With the new solver, we should be to replace all of this with an additional + // constraint that enforces what `Self` can specialize to. + let fallback_other = other.literal_fallback_instance(db).unwrap_or(other); + attribute_type + .try_upcast_to_callable(db) + .when_some_and(|callables| { + callables + .map(|callable| callable.apply_self(db, fallback_other)) + .has_relation_to_impl( + db, + method.bind_self(db, Some(fallback_other)), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // TODO: consider the types of the attribute on `other` for property members ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 74fe451e50a76..0460f31f08d3b 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -213,6 +213,19 @@ impl<'db> CallableSignature<'db> { } } + /// Replaces any occurrences of `typing.Self` in the parameter and return annotations with the + /// given type. (Does not bind the `self` parameter; to do that, use + /// [`bind_self`][Self::bind_self].) + pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self { + Self { + overloads: self + .overloads + .iter() + .map(|signature| signature.apply_self(db, self_type)) + .collect(), + } + } + fn is_subtype_of_impl( &self, db: &'db dyn Db, @@ -628,6 +641,28 @@ impl<'db> Signature<'db> { } } + pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self { + let parameters = self.parameters.apply_type_mapping_impl( + db, + &TypeMapping::BindSelf(self_type), + TypeContext::default(), + &ApplyTypeMappingVisitor::default(), + ); + let return_ty = self.return_ty.map(|ty| { + ty.apply_type_mapping( + db, + &TypeMapping::BindSelf(self_type), + TypeContext::default(), + ) + }); + Self { + generic_context: self.generic_context, + definition: self.definition, + parameters, + return_ty, + } + } + fn inferable_typevars(&self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> { match self.generic_context { Some(generic_context) => generic_context.inferable_typevars(db),