diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9b53980915..4176cb0273 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -29,7 +29,7 @@ pub(crate) use self::infer::{ infer_scope_types, }; pub(crate) use self::narrow::ClassInfoConstraintFunction; -pub(crate) use self::signatures::{CallableSignature, Signature, Signatures}; +pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, file_to_module, resolve_module}; @@ -41,7 +41,7 @@ use crate::suppression::check_suppressions; use crate::symbol::{ Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings, }; -use crate::types::call::{Bindings, CallArgumentTypes, CallableBinding}; +use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; @@ -2768,7 +2768,7 @@ impl<'db> Type<'db> { Some((self, AttributeKind::NormalOrNonDataDescriptor)) } else { Some(( - Type::Callable(callable.bind_self(db)), + callable.bind_self(db), AttributeKind::NormalOrNonDataDescriptor, )) }; @@ -3546,28 +3546,28 @@ impl<'db> Type<'db> { non_negative_int_literal(db, return_ty) } - /// Returns the call signatures of a type. + /// Returns a [`Bindings`] that can be used to analyze a call to this type. You must call + /// [`match_parameters`][Bindings::match_parameters] and [`check_types`][Bindings::check_types] + /// to fully analyze a particular call site. /// - /// Note that all types have a valid [`Signatures`], even if the type is not callable. - /// Moreover, "callable" can be subtle for a union type, since some union elements might be - /// callable and some not. A union is callable if every element type is callable — and even - /// then, the elements might be inconsistent, such that there's no argument list that's valid - /// for all elements. It's usually best to only worry about "callability" relative to a - /// particular argument list, via [`try_call`][Self::try_call] and - /// [`CallErrorKind::NotCallable`]. - fn signatures(self, db: &'db dyn Db) -> Signatures<'db> { + /// Note that we return a [`Bindings`] for all types, even if the type is not callable. + /// "Callable" can be subtle for a union type, since some union elements might be callable and + /// some not. A union is callable if every element type is callable — but even then, the + /// elements might be inconsistent, such that there's no argument list that's valid for all + /// elements. It's usually best to only worry about "callability" relative to a particular + /// argument list, via [`try_call`][Self::try_call] and [`CallErrorKind::NotCallable`]. + fn bindings(self, db: &'db dyn Db) -> Bindings<'db> { match self { - Type::Callable(callable) => Signatures::single(match callable.signatures(db) { - [signature] => CallableSignature::single(self, signature.clone()), - signatures => CallableSignature::from_overloads(self, signatures.iter().cloned()), - }), + Type::Callable(callable) => { + CallableBinding::from_overloads(self, callable.signatures(db).iter().cloned()) + .into() + } Type::BoundMethod(bound_method) => { let signature = bound_method.function(db).signature(db); - Signatures::single( - CallableSignature::from_overloads(self, signature.overloads.iter().cloned()) - .with_bound_type(bound_method.self_instance(db)), - ) + CallableBinding::from_overloads(self, signature.overloads.iter().cloned()) + .with_bound_type(bound_method.self_instance(db)) + .into() } Type::MethodWrapper( @@ -3591,7 +3591,7 @@ impl<'db> Type<'db> { // specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`. let not_none = Type::none(db).negate(db); - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3617,8 +3617,8 @@ impl<'db> Type<'db> { None, ), ], - ); - Signatures::single(signature) + ) + .into() } Type::WrapperDescriptor( @@ -3644,7 +3644,7 @@ impl<'db> Type<'db> { unreachable!("Not part of outer match pattern") } }; - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3674,86 +3674,85 @@ impl<'db> Type<'db> { None, ), ], - ); - Signatures::single(signature) + ) + .into() } - Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(_)) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::object(db)), - ]), - None, - ), - )) - } - Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("self"))) - .with_annotated_type(KnownClass::Property.to_instance(db)), - Parameter::positional_only(Some(Name::new_static("instance"))) - .with_annotated_type(Type::object(db)), - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::object(db)), - ]), - None, - ), - )) - } + Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(_)) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::object(db)), + ]), + None, + ), + ) + .into(), - Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) => { - Signatures::single(CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("prefix"))) - .with_annotated_type(UnionType::from_elements( - db, - [ - KnownClass::Str.to_instance(db), - KnownClass::Tuple.to_specialized_instance( - db, - [KnownClass::Str.to_instance(db)], - ), - ], - )), - Parameter::positional_only(Some(Name::new_static("start"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - Parameter::positional_only(Some(Name::new_static("end"))) - .with_annotated_type(UnionType::from_elements( - db, - [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], - )) - .with_default_type(Type::none(db)), - ]), - Some(KnownClass::Bool.to_instance(db)), - ), - )) - } + Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("self"))) + .with_annotated_type(KnownClass::Property.to_instance(db)), + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::object(db)), + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::object(db)), + ]), + None, + ), + ) + .into(), + + Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("prefix"))) + .with_annotated_type(UnionType::from_elements( + db, + [ + KnownClass::Str.to_instance(db), + KnownClass::Tuple.to_specialized_instance( + db, + [KnownClass::Str.to_instance(db)], + ), + ], + )), + Parameter::positional_only(Some(Name::new_static("start"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("end"))) + .with_annotated_type(UnionType::from_elements( + db, + [KnownClass::SupportsIndex.to_instance(db), Type::none(db)], + )) + .with_default_type(Type::none(db)), + ]), + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), // TODO: We should probably also check the original return type of the function // that was decorated with `@dataclass_transform`, to see if it is consistent with // with what we configure here. - Type::DataclassTransformer(_) => Signatures::single(CallableSignature::single( + Type::DataclassTransformer(_) => Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static("func"))) .with_annotated_type(Type::object(db))]), None, ), - )), + ) + .into(), Type::FunctionLiteral(function_type) => match function_type.known(db) { Some( @@ -3762,62 +3761,54 @@ impl<'db> Type<'db> { | KnownFunction::IsAssignableTo | KnownFunction::IsDisjointFrom | KnownFunction::IsGradualEquivalentTo, - ) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("a"))) - .type_form() - .with_annotated_type(Type::any()), - Parameter::positional_only(Some(Name::new_static("b"))) - .type_form() - .with_annotated_type(Type::any()), - ]), - Some(KnownClass::Bool.to_instance(db)), - ), - ); - Signatures::single(signature) - } + ) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("a"))) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("b"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), Some( KnownFunction::IsFullyStatic | KnownFunction::IsSingleton | KnownFunction::IsSingleValued, - ) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([Parameter::positional_only(Some(Name::new_static( - "a", - ))) + ) => Binding::single( + self, + Signature::new( + Parameters::new([Parameter::positional_only(Some(Name::new_static("a"))) .type_form() .with_annotated_type(Type::any())]), - Some(KnownClass::Bool.to_instance(db)), - ), - ); - Signatures::single(signature) - } + Some(KnownClass::Bool.to_instance(db)), + ), + ) + .into(), - Some(KnownFunction::AssertType) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_only(Some(Name::new_static("value"))) - .with_annotated_type(Type::any()), - Parameter::positional_only(Some(Name::new_static("type"))) - .type_form() - .with_annotated_type(Type::any()), - ]), - Some(Type::none(db)), - ), - ); - Signatures::single(signature) - } + Some(KnownFunction::AssertType) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("type"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(Type::none(db)), + ), + ) + .into(), Some(KnownFunction::AssertNever) => { - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static( @@ -3830,29 +3821,27 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any())]), Some(Type::none(db)), ), - ); - Signatures::single(signature) + ) + .into() } - Some(KnownFunction::Cast) => { - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([ - Parameter::positional_or_keyword(Name::new_static("typ")) - .type_form() - .with_annotated_type(Type::any()), - Parameter::positional_or_keyword(Name::new_static("val")) - .with_annotated_type(Type::any()), - ]), - Some(Type::any()), - ), - ); - Signatures::single(signature) - } + Some(KnownFunction::Cast) => Binding::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_or_keyword(Name::new_static("typ")) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_or_keyword(Name::new_static("val")) + .with_annotated_type(Type::any()), + ]), + Some(Type::any()), + ), + ) + .into(), Some(KnownFunction::Dataclass) => { - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ // def dataclass(cls: None, /) -> Callable[[type[_T]], type[_T]]: ... @@ -3922,12 +3911,15 @@ impl<'db> Type<'db> { None, ), ], - ); - - Signatures::single(signature) + ) + .into() } - _ => Signatures::single(function_type.signature(db).overloads.clone()), + _ => CallableBinding::from_overloads( + self, + function_type.signature(db).overloads.iter().cloned(), + ) + .into(), }, Type::ClassLiteral(class) => match class.known(db) { @@ -3942,7 +3934,7 @@ impl<'db> Type<'db> { // class bool(int): // def __new__(cls, o: object = ..., /) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([Parameter::positional_only(Some(Name::new_static( @@ -3952,8 +3944,8 @@ impl<'db> Type<'db> { .with_default_type(Type::BooleanLiteral(false))]), Some(KnownClass::Bool.to_instance(db)), ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Str) => { @@ -3964,7 +3956,7 @@ impl<'db> Type<'db> { // @overload // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -3997,8 +3989,8 @@ impl<'db> Type<'db> { Some(KnownClass::Str.to_instance(db)), ), ], - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Type) => { @@ -4012,7 +4004,7 @@ impl<'db> Type<'db> { // @overload // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -4042,29 +4034,32 @@ impl<'db> Type<'db> { Some(type_instance), ), ], - ); - Signatures::single(signature) + ) + .into() } + Some(KnownClass::NamedTuple) => { - Signatures::single(CallableSignature::todo("functional `NamedTuple` syntax")) + Binding::single(self, Signature::todo("functional `NamedTuple` syntax")).into() } + Some(KnownClass::Object) => { // ```py // class object: // def __init__(self) -> None: ... // def __new__(cls) -> Self: ... // ``` - let signature = CallableSignature::from_overloads( + Binding::single( self, - [Signature::new( + Signature::new( Parameters::empty(), Some(KnownClass::Object.to_instance(db)), - )], - ); - Signatures::single(signature) + ), + ) + .into() } + Some(KnownClass::Enum) => { - Signatures::single(CallableSignature::todo("functional `Enum` syntax")) + Binding::single(self, Signature::todo("functional `Enum` syntax")).into() } Some(KnownClass::Super) => { @@ -4077,7 +4072,7 @@ impl<'db> Type<'db> { // @overload // def __init__(self) -> None: ... // ``` - let signature = CallableSignature::from_overloads( + CallableBinding::from_overloads( self, [ Signature::new( @@ -4101,8 +4096,8 @@ impl<'db> Type<'db> { Some(KnownClass::Super.to_instance(db)), ), ], - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::TypeVar) => { @@ -4119,7 +4114,7 @@ impl<'db> Type<'db> { // default: Any = ..., // ) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4151,8 +4146,8 @@ impl<'db> Type<'db> { ]), Some(KnownClass::TypeVar.to_instance(db)), ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::TypeAliasType) => { @@ -4165,7 +4160,7 @@ impl<'db> Type<'db> { // type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () // ) -> Self: ... // ``` - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4190,8 +4185,8 @@ impl<'db> Type<'db> { ]), None, ), - ); - Signatures::single(signature) + ) + .into() } Some(KnownClass::Property) => { @@ -4215,7 +4210,7 @@ impl<'db> Type<'db> { Some(Type::any()), ); - let signature = CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4223,10 +4218,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - getter_signature, - )), + CallableType::single(db, getter_signature), Type::none(db), ], )) @@ -4235,10 +4227,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - setter_signature, - )), + CallableType::single(db, setter_signature), Type::none(db), ], )) @@ -4247,10 +4236,7 @@ impl<'db> Type<'db> { .with_annotated_type(UnionType::from_elements( db, [ - Type::Callable(CallableType::single( - db, - deleter_signature, - )), + CallableType::single(db, deleter_signature), Type::none(db), ], )) @@ -4264,8 +4250,8 @@ impl<'db> Type<'db> { ]), None, ), - ); - Signatures::single(signature) + ) + .into() } // Most class literal constructor calls are handled by `try_call_constructor` and @@ -4273,21 +4259,19 @@ impl<'db> Type<'db> { // cases (e.g. evaluating callable subtyping). TODO improve this definition // (intersection of `__new__` and `__init__` signatures? and respect metaclass // `__call__`). - _ => { - let signature = CallableSignature::single( - self, - Signature::new_generic( - class.generic_context(db), - Parameters::gradual_form(), - self.to_instance(db), - ), - ); - Signatures::single(signature) - } + _ => Binding::single( + self, + Signature::new_generic( + class.generic_context(db), + Parameters::gradual_form(), + self.to_instance(db), + ), + ) + .into(), }, Type::KnownInstance(KnownInstanceType::TypedDict) => { - Signatures::single(CallableSignature::single( + Binding::single( self, Signature::new( Parameters::new([ @@ -4305,28 +4289,28 @@ impl<'db> Type<'db> { ]), None, ), - )) + ) + .into() } Type::GenericAlias(_) => { // TODO annotated return type on `__new__` or metaclass `__call__` // TODO check call vs signatures of `__new__` and/or `__init__` - let signature = CallableSignature::single( + Binding::single( self, Signature::new(Parameters::gradual_form(), self.to_instance(db)), - ); - Signatures::single(signature) + ) + .into() } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - SubclassOfInner::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).signatures(db) - } + SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).bindings(db), + // Most type[] constructor calls are handled by `try_call_constructor` and not via // getting the signature here. This signature can still be used in some cases (e.g. // evaluating callable subtyping). TODO improve this definition (intersection of // `__new__` and `__init__` signatures? and respect metaclass `__call__`). - SubclassOfInner::Class(class) => Type::from(class).signatures(db), + SubclassOfInner::Class(class) => Type::from(class).bindings(db), }, Type::NominalInstance(_) | Type::ProtocolInstance(_) => { @@ -4344,39 +4328,43 @@ impl<'db> Type<'db> { .symbol { Symbol::Type(dunder_callable, boundness) => { - let mut signatures = dunder_callable.signatures(db).clone(); - signatures.replace_callable_type(dunder_callable, self); + let mut bindings = dunder_callable.bindings(db); + bindings.replace_callable_type(dunder_callable, self); if boundness == Boundness::PossiblyUnbound { - signatures.set_dunder_call_is_possibly_unbound(); + bindings.set_dunder_call_is_possibly_unbound(); } - signatures + bindings } - Symbol::Unbound => Signatures::not_callable(self), + Symbol::Unbound => CallableBinding::not_callable(self).into(), } } // Dynamic types are callable, and the return type is the same dynamic type. Similarly, // `Never` is always callable and returns `Never`. - Type::Dynamic(_) | Type::Never => Signatures::single(CallableSignature::dynamic(self)), + Type::Dynamic(_) | Type::Never => { + Binding::single(self, Signature::dynamic(self)).into() + } // Note that this correctly returns `None` if none of the union elements are callable. - Type::Union(union) => Signatures::from_union( + Type::Union(union) => Bindings::from_union( self, union .elements(db) .iter() - .map(|element| element.signatures(db)), + .map(|element| element.bindings(db)), ), Type::Intersection(_) => { - Signatures::single(CallableSignature::todo("Type::Intersection.call()")) + Binding::single(self, Signature::todo("Type::Intersection.call()")).into() } // TODO: these are actually callable - Type::MethodWrapper(_) | Type::DataclassDecorator(_) => Signatures::not_callable(self), + Type::MethodWrapper(_) | Type::DataclassDecorator(_) => { + CallableBinding::not_callable(self).into() + } // TODO: some `KnownInstance`s are callable (e.g. TypedDicts) - Type::KnownInstance(_) => Signatures::not_callable(self), + Type::KnownInstance(_) => CallableBinding::not_callable(self).into(), Type::PropertyInstance(_) | Type::AlwaysFalsy @@ -4389,7 +4377,7 @@ impl<'db> Type<'db> { | Type::Tuple(_) | Type::BoundSuper(_) | Type::TypeVar(_) - | Type::ModuleLiteral(_) => Signatures::not_callable(self), + | Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(), } } @@ -4404,8 +4392,9 @@ impl<'db> Type<'db> { db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>, ) -> Result, CallError<'db>> { - let signatures = self.signatures(db); - Bindings::match_parameters(signatures, argument_types).check_types(db, argument_types) + self.bindings(db) + .match_parameters(argument_types) + .check_types(db, argument_types) } /// Look up a dunder method on the meta-type of `self` and call it. @@ -4451,8 +4440,9 @@ impl<'db> Type<'db> { .symbol { Symbol::Type(dunder_callable, boundness) => { - let signatures = dunder_callable.signatures(db); - let bindings = Bindings::match_parameters(signatures, argument_types) + let bindings = dunder_callable + .bindings(db) + .match_parameters(argument_types) .check_types(db, argument_types)?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); @@ -4970,7 +4960,7 @@ impl<'db> Type<'db> { KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), // TODO: Use an opt-in rule for a bare `Callable` - KnownInstanceType::Callable => Ok(Type::Callable(CallableType::unknown(db))), + KnownInstanceType::Callable => Ok(CallableType::unknown(db)), KnownInstanceType::TypingSelf => { let index = semantic_index(db, scope_id.file(db)); @@ -6896,7 +6886,7 @@ impl<'db> FunctionSignature<'db> { /// Returns the "bottom" signature (subtype of all fully-static signatures.) pub(crate) fn bottom(db: &'db dyn Db) -> Self { FunctionSignature { - overloads: CallableSignature::single(Type::any(), Signature::bottom(db)), + overloads: CallableSignature::single(Signature::bottom(db)), implementation: None, } } @@ -6969,9 +6959,9 @@ impl<'db> FunctionType<'db> { /// Convert the `FunctionType` into a [`Type::Callable`]. pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::from_overloads( + Type::Callable(CallableType::new( db, - self.signature(db).overloads.iter().cloned(), + self.signature(db).overloads.clone(), false, )) } @@ -7045,7 +7035,6 @@ impl<'db> FunctionType<'db> { if let Some(overloaded) = self.to_overloaded(db) { FunctionSignature { overloads: CallableSignature::from_overloads( - Type::FunctionLiteral(self), overloaded.overloads.iter().copied().map(|overload| { type_mappings.iter().fold( overload.internal_signature(db, inherited_generic_context), @@ -7062,13 +7051,10 @@ impl<'db> FunctionType<'db> { } } else { FunctionSignature { - overloads: CallableSignature::single( - Type::FunctionLiteral(self), - type_mappings.iter().fold( - self.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ), - ), + overloads: CallableSignature::single(type_mappings.iter().fold( + self.internal_signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + )), implementation: None, } } @@ -7592,13 +7578,15 @@ pub struct BoundMethodType<'db> { impl<'db> BoundMethodType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::from_overloads( + Type::Callable(CallableType::new( db, - self.function(db) - .signature(db) - .overloads - .iter() - .map(signatures::Signature::bind_self), + CallableSignature::from_overloads( + self.function(db) + .signature(db) + .overloads + .iter() + .map(signatures::Signature::bind_self), + ), false, )) } @@ -7662,8 +7650,9 @@ impl<'db> BoundMethodType<'db> { #[salsa::interned(debug)] #[derive(PartialOrd, Ord)] pub struct CallableType<'db> { - #[returns(deref)] - signatures: Box<[Signature<'db>]>, + #[returns(ref)] + signatures: CallableSignature<'db>, + /// We use `CallableType` to represent function-like objects, like the synthesized methods /// of dataclasses or NamedTuples. These callables act like real functions when accessed /// as attributes on instances, i.e. they bind `self`. @@ -7671,49 +7660,37 @@ pub struct CallableType<'db> { } impl<'db> CallableType<'db> { - /// Create a non-overloaded callable type with a single signature. - pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self { - CallableType::new(db, vec![signature].into_boxed_slice(), false) + /// 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, + )) } /// 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>) -> Self { - CallableType::new(db, vec![signature].into_boxed_slice(), true) - } - - /// Create an overloaded callable type with multiple signatures. - /// - /// # Panics - /// - /// Panics if `overloads` is empty. - pub(crate) fn from_overloads(db: &'db dyn Db, overloads: I, is_function_like: bool) -> Self - where - I: IntoIterator>, - { - let overloads = overloads.into_iter().collect::>().into_boxed_slice(); - assert!( - !overloads.is_empty(), - "CallableType must have at least one signature" - ); - CallableType::new(db, overloads, is_function_like) + pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> { + Type::Callable(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) -> Self { - CallableType::single( - db, - Signature::new(Parameters::unknown(), Some(Type::unknown())), - ) + pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> { + Self::single(db, Signature::unknown()) } - pub(crate) fn bind_self(self, db: &'db dyn Db) -> Self { - CallableType::from_overloads( + pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> { + Type::Callable(CallableType::new( db, - self.signatures(db).iter().map(Signature::bind_self), + self.signatures(db).bind_self(), false, - ) + )) } /// Create a callable type which represents a fully-static "bottom" callable. @@ -7722,28 +7699,24 @@ impl<'db> CallableType<'db> { /// `(*args: object, **kwargs: object) -> Never`. #[cfg(test)] pub(crate) fn bottom(db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::single(db, Signature::bottom(db))) + Self::single(db, Signature::bottom(db)) } /// Return a "normalized" version of this `Callable` type. /// /// See [`Type::normalized`] for more details. fn normalized(self, db: &'db dyn Db) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .map(|signature| signature.normalized(db)), + self.signatures(db).normalized(db), self.is_function_like(db), ) } fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .map(|signature| signature.apply_type_mapping(db, type_mapping)), + self.signatures(db).apply_type_mapping(db, type_mapping), self.is_function_like(db), ) } @@ -7753,148 +7726,63 @@ impl<'db> CallableType<'db> { db: &'db dyn Db, typevars: &mut FxOrderSet>, ) { - for signature in self.signatures(db) { - signature.find_legacy_typevars(db, typevars); - } + self.signatures(db).find_legacy_typevars(db, typevars); } /// Check whether this callable type is fully static. /// /// See [`Type::is_fully_static`] for more details. fn is_fully_static(self, db: &'db dyn Db) -> bool { - self.signatures(db) - .iter() - .all(|signature| signature.is_fully_static(db)) + self.signatures(db).is_fully_static(db) } /// Check whether this callable type is a subtype of another callable type. /// /// See [`Type::is_subtype_of`] for more details. fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - self.is_assignable_to_impl(db, other, &|self_signature, other_signature| { - self_signature.is_subtype_of(db, other_signature) - }) + let self_is_function_like = self.is_function_like(db); + let other_is_function_like = other.is_function_like(db); + (self_is_function_like || !other_is_function_like) + && self.signatures(db).is_subtype_of(db, other.signatures(db)) } /// Check whether this callable type is assignable to another callable type. /// /// See [`Type::is_assignable_to`] for more details. fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - self.is_assignable_to_impl(db, other, &|self_signature, other_signature| { - self_signature.is_assignable_to(db, other_signature) - }) - } - - /// Implementation for the various relation checks between two, possible overloaded, callable - /// types. - /// - /// The `check_signature` closure is used to check the relation between two [`Signature`]s. - fn is_assignable_to_impl(self, db: &'db dyn Db, other: Self, check_signature: &F) -> bool - where - F: Fn(&Signature<'db>, &Signature<'db>) -> bool, - { let self_is_function_like = self.is_function_like(db); let other_is_function_like = other.is_function_like(db); - - if !self_is_function_like && other_is_function_like { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - // Base case: both callable types contain a single signature. - check_signature(self_signature, other_signature) - } - - // `self` is possibly overloaded while `other` is definitely not overloaded. - (self_signatures, [other_signature]) => { - let other_callable = CallableType::single(db, other_signature.clone()); - self_signatures - .iter() - .map(|self_signature| CallableType::single(db, self_signature.clone())) - .any(|self_callable| { - self_callable.is_assignable_to_impl(db, other_callable, check_signature) - }) - } - - // `self` is definitely not overloaded while `other` is possibly overloaded. - ([self_signature], other_signatures) => { - let self_callable = CallableType::single(db, self_signature.clone()); - other_signatures - .iter() - .map(|other_signature| CallableType::single(db, other_signature.clone())) - .all(|other_callable| { - self_callable.is_assignable_to_impl(db, other_callable, check_signature) - }) - } - - // `self` is definitely overloaded while `other` is possibly overloaded. - (_, other_signatures) => other_signatures - .iter() - .map(|other_signature| CallableType::single(db, other_signature.clone())) - .all(|other_callable| { - self.is_assignable_to_impl(db, other_callable, check_signature) - }), - } + (self_is_function_like || !other_is_function_like) + && self + .signatures(db) + .is_assignable_to(db, other.signatures(db)) } /// Check whether this callable type is equivalent to another callable type. /// /// See [`Type::is_equivalent_to`] for more details. fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - if self.is_function_like(db) != other.is_function_like(db) { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - // Common case: both callable types contain a single signature, use the custom - // equivalence check instead of delegating it to the subtype check. - self_signature.is_equivalent_to(db, other_signature) - } - (self_signatures, other_signatures) => { - if !self_signatures - .iter() - .chain(other_signatures.iter()) - .all(|signature| signature.is_fully_static(db)) - { - return false; - } - if self == other { - return true; - } - self.is_subtype_of(db, other) && other.is_subtype_of(db, self) - } - } + self.is_function_like(db) == other.is_function_like(db) + && self + .signatures(db) + .is_equivalent_to(db, other.signatures(db)) } /// Check whether this callable type is gradual equivalent to another callable type. /// /// See [`Type::is_gradual_equivalent_to`] for more details. fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - if self.is_function_like(db) != other.is_function_like(db) { - return false; - } - - match (self.signatures(db), other.signatures(db)) { - ([self_signature], [other_signature]) => { - self_signature.is_gradual_equivalent_to(db, other_signature) - } - _ => { - // TODO: overloads - false - } - } + self.is_function_like(db) == other.is_function_like(db) + && self + .signatures(db) + .is_gradual_equivalent_to(db, other.signatures(db)) } /// See [`Type::replace_self_reference`]. fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - CallableType::from_overloads( + CallableType::new( db, - self.signatures(db) - .iter() - .cloned() - .map(|signature| signature.replace_self_reference(db, class)), + self.signatures(db).replace_self_reference(db, class), self.is_function_like(db), ) } diff --git a/crates/ty_python_semantic/src/types/call.rs b/crates/ty_python_semantic/src/types/call.rs index 27c10e5432..bbceeec704 100644 --- a/crates/ty_python_semantic/src/types/call.rs +++ b/crates/ty_python_semantic/src/types/call.rs @@ -1,11 +1,11 @@ use super::context::InferContext; -use super::{CallableSignature, Signature, Signatures, Type}; +use super::{Signature, Type}; use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments}; -pub(super) use bind::{Bindings, CallableBinding}; +pub(super) use bind::{Binding, Bindings, CallableBinding}; /// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 67a8721be0..07888335a1 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,11 +3,11 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. -use smallvec::SmallVec; +use smallvec::{SmallVec, smallvec}; use super::{ - Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, CallableSignature, - InferContext, Signature, Signatures, Type, + Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, InferContext, Signature, + Type, }; use crate::db::Db; use crate::dunder_all::dunder_all_names; @@ -33,7 +33,9 @@ use ruff_python_ast as ast; /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug)] pub(crate) struct Bindings<'db> { - signatures: Signatures<'db>, + /// The type that is (hopefully) callable. + callable_type: Type<'db>, + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union /// type. elements: SmallVec<[CallableBinding<'db>; 1]>, @@ -45,6 +47,40 @@ pub(crate) struct Bindings<'db> { } impl<'db> Bindings<'db> { + /// Creates a new `Bindings` from an iterator of [`Bindings`]s. Panics if the iterator is + /// empty. + pub(crate) fn from_union(callable_type: Type<'db>, elements: I) -> Self + where + I: IntoIterator>, + { + let elements: SmallVec<_> = elements + .into_iter() + .flat_map(|s| s.elements.into_iter()) + .collect(); + assert!(!elements.is_empty()); + Self { + callable_type, + elements, + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } + + pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + for binding in &mut self.elements { + binding.replace_callable_type(before, after); + } + } + + pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) { + for binding in &mut self.elements { + binding.dunder_call_is_possibly_unbound = true; + } + } + /// Match the arguments of a call site against the parameters of a collection of possibly /// unioned, possibly overloaded signatures. /// @@ -55,30 +91,15 @@ impl<'db> Bindings<'db> { /// /// Once you have argument types available, you can call [`check_types`][Self::check_types] to /// verify that each argument type is assignable to the corresponding parameter type. - pub(crate) fn match_parameters( - signatures: Signatures<'db>, - arguments: &CallArguments<'_>, - ) -> Self { + pub(crate) fn match_parameters(mut self, arguments: &CallArguments<'_>) -> Self { let mut argument_forms = vec![None; arguments.len()]; let mut conflicting_forms = vec![false; arguments.len()]; - let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures - .iter() - .map(|signature| { - CallableBinding::match_parameters( - signature, - arguments, - &mut argument_forms, - &mut conflicting_forms, - ) - }) - .collect(); - - Bindings { - signatures, - elements, - argument_forms: argument_forms.into(), - conflicting_forms: conflicting_forms.into(), + for binding in &mut self.elements { + binding.match_parameters(arguments, &mut argument_forms, &mut conflicting_forms); } + self.argument_forms = argument_forms.into(); + self.conflicting_forms = conflicting_forms.into(); + self } /// Verify that the type of each argument is assignable to type of the parameter that it was @@ -95,8 +116,8 @@ impl<'db> Bindings<'db> { db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>, ) -> Result> { - for (signature, element) in self.signatures.iter().zip(&mut self.elements) { - element.check_types(db, signature, argument_types); + for element in &mut self.elements { + element.check_types(db, argument_types); } self.evaluate_known_cases(db); @@ -157,7 +178,7 @@ impl<'db> Bindings<'db> { } pub(crate) fn callable_type(&self) -> Type<'db> { - self.signatures.callable_type + self.callable_type } /// Returns the return type of the call. For successful calls, this is the actual return type. @@ -228,7 +249,7 @@ impl<'db> Bindings<'db> { }; // Each special case listed here should have a corresponding clause in `Type::signatures`. - for (binding, callable_signature) in self.elements.iter_mut().zip(self.signatures.iter()) { + for binding in &mut self.elements { let binding_type = binding.callable_type; for (overload_index, overload) in binding.matching_overloads_mut() { match binding_type { @@ -848,15 +869,12 @@ impl<'db> Bindings<'db> { let mut dataclass_params = DataclassParams::from(params); - if let Some(Some(Type::BooleanLiteral(order))) = - callable_signature.iter().nth(overload_index).and_then( - |signature| { - let (idx, _) = signature - .parameters() - .keyword_by_name("order")?; - overload.parameter_types().get(idx) - }, - ) + if let Some(Some(Type::BooleanLiteral(order))) = overload + .signature + .parameters() + .keyword_by_name("order") + .map(|(idx, _)| idx) + .and_then(|idx| overload.parameter_types().get(idx)) { dataclass_params.set(DataclassParams::ORDER, *order); } @@ -945,6 +963,37 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { } } +impl<'db> From> for Bindings<'db> { + fn from(from: CallableBinding<'db>) -> Bindings<'db> { + Bindings { + callable_type: from.callable_type, + elements: smallvec![from], + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } +} + +impl<'db> From> for Bindings<'db> { + fn from(from: Binding<'db>) -> Bindings<'db> { + let callable_type = from.callable_type; + let signature_type = from.signature_type; + let callable_binding = CallableBinding { + callable_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads: smallvec![from], + }; + Bindings { + callable_type, + elements: smallvec![callable_binding], + argument_forms: Box::from([]), + conflicting_forms: Box::from([]), + } + } +} + /// Binding information for a single callable. If the callable is overloaded, there is a separate /// [`Binding`] for each overload. /// @@ -962,10 +1011,21 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug)] pub(crate) struct CallableBinding<'db> { + /// The type that is (hopefully) callable. pub(crate) callable_type: Type<'db>, + + /// The type we'll use for error messages referring to details of the called signature. For + /// calls to functions this will be the same as `callable_type`; for other callable instances + /// it may be a `__call__` method. pub(crate) signature_type: Type<'db>, + + /// If this is a callable object (i.e. called via a `__call__` method), the boundness of + /// that call method. pub(crate) dunder_call_is_possibly_unbound: bool, + /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. + pub(crate) bound_type: Option>, + /// The bindings of each overload of this callable. Will be empty if the type is not callable. /// /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a @@ -974,15 +1034,56 @@ pub(crate) struct CallableBinding<'db> { } impl<'db> CallableBinding<'db> { + pub(crate) fn from_overloads( + signature_type: Type<'db>, + overloads: impl IntoIterator>, + ) -> Self { + let overloads = overloads + .into_iter() + .map(|signature| Binding::single(signature_type, signature)) + .collect(); + Self { + callable_type: signature_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads, + } + } + + pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { + Self { + callable_type: signature_type, + signature_type, + dunder_call_is_possibly_unbound: false, + bound_type: None, + overloads: smallvec![], + } + } + + pub(crate) fn with_bound_type(mut self, bound_type: Type<'db>) -> Self { + self.bound_type = Some(bound_type); + self + } + + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + for binding in &mut self.overloads { + binding.replace_callable_type(before, after); + } + } + fn match_parameters( - signature: &CallableSignature<'db>, + &mut self, arguments: &CallArguments<'_>, argument_forms: &mut [Option], conflicting_forms: &mut [bool], - ) -> Self { + ) { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let arguments = arguments.with_self(signature.bound_type); + let arguments = arguments.with_self(self.bound_type); // TODO: This checks every overload. In the proposed more detailed call checking spec [1], // arguments are checked for arity first, and are only checked for type assignability against @@ -990,37 +1091,17 @@ impl<'db> CallableBinding<'db> { // two phases. // // [1] https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation - let overloads = signature - .into_iter() - .map(|signature| { - Binding::match_parameters( - signature, - arguments.as_ref(), - argument_forms, - conflicting_forms, - ) - }) - .collect(); - - CallableBinding { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - overloads, + for overload in &mut self.overloads { + overload.match_parameters(arguments.as_ref(), argument_forms, conflicting_forms); } } - fn check_types( - &mut self, - db: &'db dyn Db, - signature: &CallableSignature<'db>, - argument_types: &CallArgumentTypes<'_, 'db>, - ) { + fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let argument_types = argument_types.with_self(signature.bound_type); - for (signature, overload) in signature.iter().zip(&mut self.overloads) { - overload.check_types(db, signature, argument_types.as_ref()); + let argument_types = argument_types.with_self(self.bound_type); + for overload in &mut self.overloads { + overload.check_types(db, argument_types.as_ref()); } } @@ -1215,9 +1296,28 @@ impl<'db> CallableBinding<'db> { } } +impl<'a, 'db> IntoIterator for &'a CallableBinding<'db> { + type Item = &'a Binding<'db>; + type IntoIter = std::slice::Iter<'a, Binding<'db>>; + + fn into_iter(self) -> Self::IntoIter { + self.overloads.iter() + } +} + /// Binding information for one of the overloads of a callable. #[derive(Debug)] pub(crate) struct Binding<'db> { + pub(crate) signature: Signature<'db>, + + /// The type that is (hopefully) callable. + pub(crate) callable_type: Type<'db>, + + /// The type we'll use for error messages referring to details of the called signature. For + /// calls to functions this will be the same as `callable_type`; for other callable instances + /// it may be a `__call__` method. + pub(crate) signature_type: Type<'db>, + /// Return type of the call. return_ty: Type<'db>, @@ -1241,18 +1341,37 @@ pub(crate) struct Binding<'db> { } impl<'db> Binding<'db> { + pub(crate) fn single(signature_type: Type<'db>, signature: Signature<'db>) -> Binding<'db> { + Binding { + signature, + callable_type: signature_type, + signature_type, + return_ty: Type::unknown(), + specialization: None, + inherited_specialization: None, + argument_parameters: Box::from([]), + parameter_tys: Box::from([]), + errors: vec![], + } + } + + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } + } + fn match_parameters( - signature: &Signature<'db>, + &mut self, arguments: &CallArguments<'_>, argument_forms: &mut [Option], conflicting_forms: &mut [bool], - ) -> Self { - let parameters = signature.parameters(); + ) { + let parameters = self.signature.parameters(); // The parameter that each argument is matched with. let mut argument_parameters = vec![None; arguments.len()]; // Whether each parameter has been matched with an argument. let mut parameter_matched = vec![false; parameters.len()]; - let mut errors = vec![]; let mut next_positional = 0; let mut first_excess_positional = None; let mut num_synthetic_args = 0; @@ -1290,7 +1409,7 @@ impl<'db> Binding<'db> { .keyword_by_name(name) .or_else(|| parameters.keyword_variadic()) else { - errors.push(BindingError::UnknownArgument { + self.errors.push(BindingError::UnknownArgument { argument_name: ast::name::Name::new(name), argument_index: get_argument_index(argument_index, num_synthetic_args), }); @@ -1315,7 +1434,7 @@ impl<'db> Binding<'db> { } if parameter_matched[index] { if !parameter.is_variadic() && !parameter.is_keyword_variadic() { - errors.push(BindingError::ParameterAlreadyAssigned { + self.errors.push(BindingError::ParameterAlreadyAssigned { argument_index: get_argument_index(argument_index, num_synthetic_args), parameter: ParameterContext::new(parameter, index, positional), }); @@ -1325,7 +1444,7 @@ impl<'db> Binding<'db> { parameter_matched[index] = true; } if let Some(first_excess_argument_index) = first_excess_positional { - errors.push(BindingError::TooManyPositionalArguments { + self.errors.push(BindingError::TooManyPositionalArguments { first_excess_argument_index: get_argument_index( first_excess_argument_index, num_synthetic_args, @@ -1350,27 +1469,17 @@ impl<'db> Binding<'db> { } if !missing.is_empty() { - errors.push(BindingError::MissingArguments { + self.errors.push(BindingError::MissingArguments { parameters: ParameterContexts(missing), }); } - Self { - return_ty: signature.return_ty.unwrap_or(Type::unknown()), - specialization: None, - inherited_specialization: None, - argument_parameters: argument_parameters.into_boxed_slice(), - parameter_tys: vec![None; parameters.len()].into_boxed_slice(), - errors, - } + self.return_ty = self.signature.return_ty.unwrap_or(Type::unknown()); + self.argument_parameters = argument_parameters.into_boxed_slice(); + self.parameter_tys = vec![None; parameters.len()].into_boxed_slice(); } - fn check_types( - &mut self, - db: &'db dyn Db, - signature: &Signature<'db>, - argument_types: &CallArgumentTypes<'_, 'db>, - ) { + fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) { let mut num_synthetic_args = 0; let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { if argument_index >= num_synthetic_args { @@ -1386,6 +1495,7 @@ impl<'db> Binding<'db> { // If this overload is generic, first see if we can infer a specialization of the function // from the arguments that were passed in. + let signature = &self.signature; let parameters = signature.parameters(); if signature.generic_context.is_some() || signature.inherited_generic_context.is_some() { let mut builder = SpecializationBuilder::new(db); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 1a89063878..176a557673 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -9,10 +9,10 @@ use super::{ use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::signatures::{Parameter, Parameters}; +use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature, - TypeMapping, TypeVarInstance, + CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping, + TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -1229,13 +1229,15 @@ impl<'db> ClassLiteral<'db> { // the `__set__` method can be called. We build a union of all possible options // to account for possible overloads. let mut value_types = UnionBuilder::new(db); - for signature in &dunder_set.signatures(db) { - for overload in signature { - if let Some(value_param) = overload.parameters().get_positional(2) { + for binding in &dunder_set.bindings(db) { + for overload in binding { + if let Some(value_param) = + overload.signature.parameters().get_positional(2) + { value_types = value_types.add( value_param.annotated_type().unwrap_or_else(Type::unknown), ); - } else if overload.parameters().is_gradual() { + } else if overload.signature.parameters().is_gradual() { value_types = value_types.add(Type::unknown()); } } @@ -1267,7 +1269,7 @@ impl<'db> ClassLiteral<'db> { } let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); - Some(Type::Callable(CallableType::function_like(db, signature))) + Some(CallableType::function_like(db, signature)) }; match (field_policy, name) { @@ -1317,7 +1319,7 @@ impl<'db> ClassLiteral<'db> { Some(KnownClass::Bool.to_instance(db)), ); - Some(Type::Callable(CallableType::function_like(db, signature))) + Some(CallableType::function_like(db, signature)) } (CodeGeneratorKind::NamedTuple, name) if name != "__init__" => { KnownClass::NamedTupleFallback diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index d0f3e9a61e..1a12f2291f 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -8,7 +8,7 @@ use ruff_python_literal::escape::AsciiEscape; use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::signatures::{Parameter, Parameters, Signature}; +use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType, @@ -413,13 +413,13 @@ impl<'db> CallableType<'db> { } pub(crate) struct DisplayCallableType<'db> { - signatures: &'db [Signature<'db>], + signatures: &'db CallableSignature<'db>, db: &'db dyn Db, } impl Display for DisplayCallableType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.signatures { + match self.signatures.overloads.as_slice() { [signature] => signature.display(self.db).fmt(f), signatures => { // TODO: How to display overloads? diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 325d0e00db..b60b54a4e5 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -64,7 +64,9 @@ use crate::symbol::{ global_symbol, module_type_implicit_global_declaration, module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, }; -use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; +use crate::types::call::{ + Argument, Binding, Bindings, CallArgumentTypes, CallArguments, CallError, +}; use crate::types::class::{MetaclassErrorKind, SliceLiteral}; use crate::types::diagnostic::{ self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, @@ -81,16 +83,17 @@ use crate::types::diagnostic::{ }; use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; +use crate::types::signatures::{CallableSignature, Signature}; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ - BareTypeAliasType, CallDunderError, CallableSignature, CallableType, ClassLiteral, ClassType, - DataclassParams, DynamicType, FunctionDecorators, FunctionType, GenericAlias, - IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, - MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, - Parameters, Signature, Signatures, StringLiteralType, SubclassOfType, Symbol, - SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, - TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, - TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, + BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, + DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, + IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, + MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, + StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, + TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, + TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, + todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -4847,10 +4850,7 @@ impl<'db> TypeInferenceBuilder<'db> { // 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. - Type::Callable(CallableType::single( - self.db(), - Signature::new(parameters, Some(Type::unknown())), - )) + CallableType::single(self.db(), Signature::new(parameters, Some(Type::unknown()))) } /// Returns the type of the first parameter if the given scope is function-like (i.e. function or lambda). @@ -4964,8 +4964,9 @@ impl<'db> TypeInferenceBuilder<'db> { } } - let signatures = callable_type.signatures(self.db()); - let bindings = Bindings::match_parameters(signatures, &call_arguments); + let bindings = callable_type + .bindings(self.db()) + .match_parameters(&call_arguments); let call_argument_types = self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms); @@ -7282,11 +7283,9 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => CallArgumentTypes::positional([self.infer_type_expression(slice_node)]), }; - let signatures = Signatures::single(CallableSignature::single( - value_ty, - generic_context.signature(self.db()), - )); - let bindings = match Bindings::match_parameters(signatures, &call_argument_types) + let binding = Binding::single(value_ty, generic_context.signature(self.db())); + let bindings = match Bindings::from(binding) + .match_parameters(&call_argument_types) .check_types(self.db(), &call_argument_types) { Ok(bindings) => bindings, @@ -8628,8 +8627,6 @@ impl<'db> TypeInferenceBuilder<'db> { CallableType::unknown(db) }; - let callable_type = Type::Callable(callable_type); - // `Signature` / `Parameters` are not a `Type` variant, so we're storing // the outer callable type on these expressions instead. self.store_expression_type(arguments_slice, callable_type); @@ -8701,20 +8698,20 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => { let argument_type = self.infer_expression(arguments_slice); - let signatures = argument_type.signatures(db); + let bindings = argument_type.bindings(db); - // SAFETY: This is enforced by the constructor methods on `Signatures` even in + // SAFETY: This is enforced by the constructor methods on `Bindings` even in // the case of a non-callable union. - let callable_signature = signatures - .iter() + let callable_binding = bindings + .into_iter() .next() - .expect("`Signatures` should have at least one `CallableSignature`"); + .expect("`Bindings` should have at least one `CallableBinding`"); - let mut signature_iter = callable_signature.iter().map(|signature| { + let mut signature_iter = callable_binding.into_iter().map(|binding| { if argument_type.is_bound_method() { - signature.bind_self() + binding.signature.bind_self() } else { - signature.clone() + binding.signature.clone() } }); @@ -8734,11 +8731,10 @@ impl<'db> TypeInferenceBuilder<'db> { return Type::unknown(); }; - Type::Callable(CallableType::from_overloads( - db, + let signature = CallableSignature::from_overloads( std::iter::once(signature).chain(signature_iter), - false, - )) + ); + Type::Callable(CallableType::new(db, signature, false)) } }, 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 362d603e6b..0850b38745 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 @@ -188,13 +188,13 @@ impl Ty { create_bound_method(db, function, builtins_class) } - Ty::Callable { params, returns } => Type::Callable(CallableType::single( + Ty::Callable { params, returns } => CallableType::single( db, Signature::new( params.into_parameters(db), returns.map(|ty| ty.into_type(db)), ), - )), + ), } } } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 2b68e7c731..24448b661d 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -22,175 +22,33 @@ use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; -/// The signature of a possible union of callables. -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct Signatures<'db> { - /// The type that is (hopefully) callable. - pub(crate) callable_type: Type<'db>, - /// The type we'll use for error messages referring to details of the called signature. For calls to functions this - /// will be the same as `callable_type`; for other callable instances it may be a `__call__` method. - pub(crate) signature_type: Type<'db>, - /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union - /// type. - elements: SmallVec<[CallableSignature<'db>; 1]>, -} - -impl<'db> Signatures<'db> { - pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { - Self { - callable_type: signature_type, - signature_type, - elements: smallvec![CallableSignature::not_callable(signature_type)], - } - } - - pub(crate) fn single(signature: CallableSignature<'db>) -> Self { - Self { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - elements: smallvec![signature], - } - } - - /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is - /// empty. - pub(crate) fn from_union(signature_type: Type<'db>, elements: I) -> Self - where - I: IntoIterator>, - { - let elements: SmallVec<_> = elements - .into_iter() - .flat_map(|s| s.elements.into_iter()) - .collect(); - assert!(!elements.is_empty()); - Self { - callable_type: signature_type, - signature_type, - elements, - } - } - - pub(crate) fn iter(&self) -> std::slice::Iter<'_, CallableSignature<'db>> { - self.elements.iter() - } - - pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { - if self.callable_type == before { - self.callable_type = after; - } - for signature in &mut self.elements { - signature.replace_callable_type(before, after); - } - } - - pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) { - for signature in &mut self.elements { - signature.dunder_call_is_possibly_unbound = true; - } - } -} - -impl<'a, 'db> IntoIterator for &'a Signatures<'db> { - type Item = &'a CallableSignature<'db>; - type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - /// The signature of a single callable. If the callable is overloaded, there is a separate /// [`Signature`] for each overload. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct CallableSignature<'db> { - /// The type that is (hopefully) callable. - pub(crate) callable_type: Type<'db>, - - /// The type we'll use for error messages referring to details of the called signature. For - /// calls to functions this will be the same as `callable_type`; for other callable instances - /// it may be a `__call__` method. - pub(crate) signature_type: Type<'db>, - - /// If this is a callable object (i.e. called via a `__call__` method), the boundness of - /// that call method. - pub(crate) dunder_call_is_possibly_unbound: bool, - - /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. - pub(crate) bound_type: Option>, - +pub struct CallableSignature<'db> { /// The signatures of each overload of this callable. Will be empty if the type is not /// callable. - /// - /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a - /// non-overloaded callable. pub(crate) overloads: SmallVec<[Signature<'db>; 1]>, } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { + pub(crate) fn single(signature: Signature<'db>) -> Self { Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, - overloads: smallvec![], - } - } - - pub(crate) fn single(signature_type: Type<'db>, signature: Signature<'db>) -> Self { - Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, overloads: smallvec![signature], } } /// Creates a new `CallableSignature` from an iterator of [`Signature`]s. Returns a /// non-callable signature if the iterator is empty. - pub(crate) fn from_overloads(signature_type: Type<'db>, overloads: I) -> Self + pub(crate) fn from_overloads(overloads: I) -> Self where I: IntoIterator>, { Self { - callable_type: signature_type, - signature_type, - dunder_call_is_possibly_unbound: false, - bound_type: None, overloads: overloads.into_iter().collect(), } } - /// Return a signature for a dynamic callable - pub(crate) fn dynamic(signature_type: Type<'db>) -> Self { - let signature = Signature { - generic_context: None, - inherited_generic_context: None, - parameters: Parameters::gradual_form(), - return_ty: Some(signature_type), - }; - Self::single(signature_type, signature) - } - - /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo - #[allow(unused_variables)] // 'reason' only unused in debug builds - pub(crate) fn todo(reason: &'static str) -> Self { - let signature_type = todo_type!(reason); - let signature = Signature { - generic_context: None, - inherited_generic_context: None, - parameters: Parameters::todo(), - return_ty: Some(signature_type), - }; - Self::single(signature_type, signature) - } - - pub(crate) fn with_bound_type(mut self, bound_type: Type<'db>) -> Self { - self.bound_type = Some(bound_type); - self - } - pub(crate) fn iter(&self) -> std::slice::Iter<'_, Signature<'db>> { self.overloads.iter() } @@ -199,9 +57,169 @@ impl<'db> CallableSignature<'db> { self.overloads.as_slice() } - fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { - if self.callable_type == before { - self.callable_type = after; + pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + Self::from_overloads( + self.overloads + .iter() + .map(|signature| signature.normalized(db)), + ) + } + + pub(crate) fn apply_type_mapping<'a>( + &self, + db: &'db dyn Db, + type_mapping: &TypeMapping<'a, 'db>, + ) -> Self { + Self::from_overloads( + self.overloads + .iter() + .map(|signature| signature.apply_type_mapping(db, type_mapping)), + ) + } + + pub(crate) fn find_legacy_typevars( + &self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + for signature in &self.overloads { + signature.find_legacy_typevars(db, typevars); + } + } + + pub(crate) fn bind_self(&self) -> Self { + Self { + overloads: self.overloads.iter().map(Signature::bind_self).collect(), + } + } + + /// Check whether this callable type is fully static. + /// + /// See [`Type::is_fully_static`] for more details. + pub(crate) fn is_fully_static(&self, db: &'db dyn Db) -> bool { + self.overloads + .iter() + .all(|signature| signature.is_fully_static(db)) + } + + /// Check whether this callable type is a subtype of another callable type. + /// + /// See [`Type::is_subtype_of`] for more details. + pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { + Self::is_assignable_to_impl( + &self.overloads, + &other.overloads, + &|self_signature, other_signature| self_signature.is_subtype_of(db, other_signature), + ) + } + + /// Check whether this callable type is assignable to another callable type. + /// + /// See [`Type::is_assignable_to`] for more details. + pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool { + Self::is_assignable_to_impl( + &self.overloads, + &other.overloads, + &|self_signature, other_signature| self_signature.is_assignable_to(db, other_signature), + ) + } + + /// Implementation for the various relation checks between two, possible overloaded, callable + /// types. + /// + /// The `check_signature` closure is used to check the relation between two [`Signature`]s. + fn is_assignable_to_impl( + self_signatures: &[Signature<'db>], + other_signatures: &[Signature<'db>], + check_signature: &F, + ) -> bool + where + F: Fn(&Signature<'db>, &Signature<'db>) -> bool, + { + match (self_signatures, other_signatures) { + ([self_signature], [other_signature]) => { + // Base case: both callable types contain a single signature. + check_signature(self_signature, other_signature) + } + + // `self` is possibly overloaded while `other` is definitely not overloaded. + (_, [_]) => self_signatures.iter().any(|self_signature| { + Self::is_assignable_to_impl( + std::slice::from_ref(self_signature), + other_signatures, + check_signature, + ) + }), + + // `self` is definitely not overloaded while `other` is possibly overloaded. + ([_], _) => other_signatures.iter().all(|other_signature| { + Self::is_assignable_to_impl( + self_signatures, + std::slice::from_ref(other_signature), + check_signature, + ) + }), + + // `self` is definitely overloaded while `other` is possibly overloaded. + (_, _) => other_signatures.iter().all(|other_signature| { + Self::is_assignable_to_impl( + self_signatures, + std::slice::from_ref(other_signature), + check_signature, + ) + }), + } + } + + /// Check whether this callable type is equivalent to another callable type. + /// + /// See [`Type::is_equivalent_to`] for more details. + pub(crate) fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool { + match (self.overloads.as_slice(), other.overloads.as_slice()) { + ([self_signature], [other_signature]) => { + // Common case: both callable types contain a single signature, use the custom + // equivalence check instead of delegating it to the subtype check. + self_signature.is_equivalent_to(db, other_signature) + } + (self_signatures, other_signatures) => { + if !self_signatures + .iter() + .chain(other_signatures.iter()) + .all(|signature| signature.is_fully_static(db)) + { + return false; + } + if self == other { + return true; + } + self.is_subtype_of(db, other) && other.is_subtype_of(db, self) + } + } + } + + /// Check whether this callable type is gradual equivalent to another callable type. + /// + /// See [`Type::is_gradual_equivalent_to`] for more details. + pub(crate) fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool { + match (self.overloads.as_slice(), other.overloads.as_slice()) { + ([self_signature], [other_signature]) => { + self_signature.is_gradual_equivalent_to(db, other_signature) + } + _ => { + // TODO: overloads + false + } + } + } + + pub(crate) fn replace_self_reference(&self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { + Self { + overloads: self + .overloads + .iter() + .cloned() + .map(|signature| signature.replace_self_reference(db, class)) + .collect(), } } } @@ -263,6 +281,28 @@ impl<'db> Signature<'db> { } } + /// Return a signature for a dynamic callable + pub(crate) fn dynamic(signature_type: Type<'db>) -> Self { + Signature { + generic_context: None, + inherited_generic_context: None, + parameters: Parameters::gradual_form(), + return_ty: Some(signature_type), + } + } + + /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo + #[allow(unused_variables)] // 'reason' only unused in debug builds + pub(crate) fn todo(reason: &'static str) -> Self { + let signature_type = todo_type!(reason); + Signature { + generic_context: None, + inherited_generic_context: None, + parameters: Parameters::todo(), + return_ty: Some(signature_type), + } + } + /// Return a typed signature from a function definition. pub(super) fn from_function( db: &'db dyn Db, @@ -295,6 +335,11 @@ impl<'db> Signature<'db> { } } + /// Returns the signature which accepts any parameters and returns an `Unknown` type. + pub(crate) fn unknown() -> Self { + Self::new(Parameters::unknown(), Some(Type::unknown())) + } + /// Return the "bottom" signature, subtype of all other fully-static signatures. pub(crate) fn bottom(db: &'db dyn Db) -> Self { Self::new(Parameters::object(db), Some(Type::Never)) @@ -1750,7 +1795,7 @@ mod tests { assert_eq!( func.signature(&db), &FunctionSignature { - overloads: CallableSignature::single(Type::FunctionLiteral(func), expected_sig), + overloads: CallableSignature::single(expected_sig), implementation: None }, );