[ty] Simplify signature types, use them in CallableType (#18344)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

There were many fields in `Signature` and friends that really had more
to do with how a signature was being _used_ — how it was looked up,
details about an individual call site, etc. Those fields more properly
belong in `Bindings` and friends.

This is a pure refactoring, and should not affect any tests or ecosystem
projects.

I started on this journey in support of
https://github.com/astral-sh/ty/issues/462. It seemed worth pulling out
as a separate PR.

One major concrete benefit of this refactoring is that we can now use
`CallableSignature` directly in `CallableType`. (We can't use
`CallableSignature` directly in that `Type` variant because signatures
are not currently interned.)
This commit is contained in:
Douglas Creager 2025-05-28 13:11:45 -04:00 committed by GitHub
parent a5ebb3f3a2
commit 452f992fbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 730 additions and 689 deletions

File diff suppressed because it is too large Load diff

View file

@ -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.

View file

@ -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<I>(callable_type: Type<'db>, elements: I) -> Self
where
I: IntoIterator<Item = Bindings<'db>>,
{
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<Self, CallError<'db>> {
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<CallableBinding<'db>> 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<Binding<'db>> 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<Type<'db>>,
/// 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<Item = Signature<'db>>,
) -> 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<ParameterForm>],
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<ParameterForm>],
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);

View file

@ -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

View file

@ -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?

View file

@ -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))
}
},

View file

@ -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)),
),
)),
),
}
}
}

View file

@ -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<I>(signature_type: Type<'db>, elements: I) -> Self
where
I: IntoIterator<Item = Signatures<'db>>,
{
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<Type<'db>>,
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<I>(signature_type: Type<'db>, overloads: I) -> Self
pub(crate) fn from_overloads<I>(overloads: I) -> Self
where
I: IntoIterator<Item = Signature<'db>>,
{
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<TypeVarInstance<'db>>,
) {
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<F>(
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
},
);