From 4e5994cf67a2d84ad8cefa80ade1d263e952b386 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 11 Mar 2025 15:14:06 -0400 Subject: [PATCH 01/57] Rename bindings types --- crates/red_knot_python_semantic/src/types.rs | 8 +-- .../src/types/call.rs | 30 +++++----- .../src/types/call/bind.rs | 55 ++++++++++--------- .../src/types/infer.rs | 2 +- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 8eb90bab3b767..5da7367dba550 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -30,7 +30,7 @@ use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; use crate::symbol::{imported_symbol, Boundness, Symbol, SymbolAndQualifiers}; -use crate::types::call::{bind_call, CallArguments, CallOutcome, UnionCallError}; +use crate::types::call::{bind_call, Bindings, CallArguments, UnionCallError}; use crate::types::class_base::ClassBase; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::infer::infer_unpack_types; @@ -2321,7 +2321,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, - ) -> Result, CallError<'db>> { + ) -> Result, CallError<'db>> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let instance = bound_method.self_instance(db); @@ -2894,7 +2894,7 @@ impl<'db> Type<'db> { } Type::Union(union) => { - CallOutcome::try_call_union(db, union, |element| element.try_call(db, arguments)) + Bindings::try_call_union(db, union, |element| element.try_call(db, arguments)) } Type::Intersection(_) => { @@ -2918,7 +2918,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, name: &str, arguments: &CallArguments<'_, 'db>, - ) -> Result, CallDunderError<'db>> { + ) -> Result, CallDunderError<'db>> { match self .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) .symbol diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 475326f95792c..9bd07bb44c9cc 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -6,21 +6,21 @@ use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArguments}; -pub(super) use bind::{bind_call, CallBinding}; +pub(super) use bind::{bind_call, CallableBinding}; /// A successfully bound call where all arguments are valid. /// /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum CallOutcome<'db> { +pub(super) enum Bindings<'db> { /// The call resolves to exactly one binding. - Single(CallBinding<'db>), + Single(CallableBinding<'db>), /// The call resolves to multiple bindings. - Union(Box<[CallBinding<'db>]>), + Union(Box<[CallableBinding<'db>]>), } -impl<'db> CallOutcome<'db> { +impl<'db> Bindings<'db> { /// Calls each union element using the provided `call` function. /// /// Returns `Ok` if all variants can be called without error according to the callback and `Err` otherwise. @@ -39,8 +39,8 @@ impl<'db> CallOutcome<'db> { for element in elements { match call(*element) { - Ok(CallOutcome::Single(binding)) => bindings.push(binding), - Ok(CallOutcome::Union(inner_bindings)) => { + Ok(Bindings::Single(binding)) => bindings.push(binding), + Ok(Bindings::Union(inner_bindings)) => { bindings.extend(inner_bindings); } Err(error) => { @@ -51,7 +51,7 @@ impl<'db> CallOutcome<'db> { } if errors.is_empty() { - Ok(CallOutcome::Union(bindings.into())) + Ok(Bindings::Union(bindings.into())) } else if bindings.is_empty() && all_errors_not_callable { Err(CallError::NotCallable { not_callable_type: Type::Union(union), @@ -70,12 +70,12 @@ impl<'db> CallOutcome<'db> { match self { Self::Single(binding) => binding.return_type(), Self::Union(bindings) => { - UnionType::from_elements(db, bindings.iter().map(CallBinding::return_type)) + UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) } } } - pub(super) fn bindings(&self) -> &[CallBinding<'db>] { + pub(super) fn bindings(&self) -> &[CallableBinding<'db>] { match self { Self::Single(binding) => std::slice::from_ref(binding), Self::Union(bindings) => bindings, @@ -101,11 +101,11 @@ pub(super) enum CallError<'db> { /// The type has a `__call__` method but it isn't always bound. PossiblyUnboundDunderCall { called_type: Type<'db>, - outcome: Box>, + outcome: Box>, }, /// The type is callable but not with the given arguments. - BindingError { binding: CallBinding<'db> }, + BindingError { binding: CallableBinding<'db> }, } impl<'db> CallError<'db> { @@ -123,7 +123,7 @@ impl<'db> CallError<'db> { db, bindings .iter() - .map(CallBinding::return_type) + .map(CallableBinding::return_type) .chain(errors.iter().map(|err| err.fallback_return_type(db))), )), Self::PossiblyUnboundDunderCall { outcome, .. } => Some(outcome.return_type(db)), @@ -166,7 +166,7 @@ pub(super) struct UnionCallError<'db> { pub(super) errors: Box<[CallError<'db>]>, /// The bindings for the callable variants (that have no binding errors). - pub(super) bindings: Box<[CallBinding<'db>]>, + pub(super) bindings: Box<[CallableBinding<'db>]>, /// The union type that we tried calling. pub(super) called_type: Type<'db>, @@ -204,7 +204,7 @@ pub(super) enum CallDunderError<'db> { /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors /// but it is possibly unbound. - PossiblyUnbound(CallOutcome<'db>), + PossiblyUnbound(Bindings<'db>), /// The dunder method with the specified name is missing. MethodNotAvailable, diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 20ff424ac354e..6527c2c7e786d 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -1,6 +1,10 @@ +//! When analyzing a call site, we create _bindings_, which match and type-check the actual +//! arguments against the parameters of the callable. Like with +//! [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 super::{ - Argument, CallArguments, CallError, CallOutcome, CallableSignature, InferContext, Signature, - Type, + Argument, Bindings, CallArguments, CallError, CallableSignature, InferContext, Signature, Type, }; use crate::db::Db; use crate::types::diagnostic::{ @@ -15,14 +19,14 @@ use ruff_text_size::Ranged; /// Bind a [`CallArguments`] against a [`CallableSignature`]. /// -/// The returned [`CallBinding`] provides the return type of the call, the bound types for all +/// The returned [`CallableBinding`] provides the return type of the call, the bound types for all /// parameters, and any errors resulting from binding the call. pub(crate) fn bind_call<'db>( db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, overloads: &CallableSignature<'db>, callable_ty: Type<'db>, -) -> CallBinding<'db> { +) -> CallableBinding<'db> { // 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 // the matching overloads. Make sure to implement that as part of separating call binding into @@ -34,7 +38,7 @@ pub(crate) fn bind_call<'db>( .map(|signature| bind_overload(db, arguments, signature)) .collect::>() .into_boxed_slice(); - CallBinding { + CallableBinding { callable_ty, overloads, } @@ -44,7 +48,7 @@ fn bind_overload<'db>( db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, signature: &Signature<'db>, -) -> OverloadBinding<'db> { +) -> Binding<'db> { let parameters = signature.parameters(); // The type assigned to each parameter at this call site. let mut parameter_tys = vec![None; parameters.len()]; @@ -86,7 +90,7 @@ fn bind_overload<'db>( .keyword_by_name(name) .or_else(|| parameters.keyword_variadic()) else { - errors.push(CallBindingError::UnknownArgument { + errors.push(CallableBindingError::UnknownArgument { argument_name: ast::name::Name::new(name), argument_index: get_argument_index(argument_index, num_synthetic_args), }); @@ -102,7 +106,7 @@ fn bind_overload<'db>( }; if let Some(expected_ty) = parameter.annotated_type() { if !argument_ty.is_assignable_to(db, expected_ty) { - errors.push(CallBindingError::InvalidArgumentType { + errors.push(CallableBindingError::InvalidArgumentType { parameter: ParameterContext::new(parameter, index, positional), argument_index: get_argument_index(argument_index, num_synthetic_args), expected_ty, @@ -115,7 +119,7 @@ fn bind_overload<'db>( let union = UnionType::from_elements(db, [existing, *argument_ty]); parameter_tys[index].replace(union); } else { - errors.push(CallBindingError::ParameterAlreadyAssigned { + errors.push(CallableBindingError::ParameterAlreadyAssigned { argument_index: get_argument_index(argument_index, num_synthetic_args), parameter: ParameterContext::new(parameter, index, positional), }); @@ -123,7 +127,7 @@ fn bind_overload<'db>( } } if let Some(first_excess_argument_index) = first_excess_positional { - errors.push(CallBindingError::TooManyPositionalArguments { + errors.push(CallableBindingError::TooManyPositionalArguments { first_excess_argument_index: get_argument_index( first_excess_argument_index, num_synthetic_args, @@ -146,12 +150,12 @@ fn bind_overload<'db>( } if !missing.is_empty() { - errors.push(CallBindingError::MissingArguments { + errors.push(CallableBindingError::MissingArguments { parameters: ParameterContexts(missing), }); } - OverloadBinding { + Binding { return_ty: signature.return_ty.unwrap_or(Type::unknown()), parameter_tys: parameter_tys .into_iter() @@ -168,7 +172,8 @@ pub(crate) struct CallableDescriptor<'a> { kind: &'a str, } -/// Binding information for a call site. +/// Binding information for a single callable. If the callable is overloaded, there is a separate +/// [`Binding`] for each overload. /// /// For a successful binding, each argument is mapped to one of the callable's formal parameters. /// If the callable has multiple overloads, the first one that matches is used as the overall @@ -183,19 +188,19 @@ pub(crate) struct CallableDescriptor<'a> { /// /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct CallBinding<'db> { +pub(crate) struct CallableBinding<'db> { /// Type of the callable object (function, class...) callable_ty: Type<'db>, - overloads: Box<[OverloadBinding<'db>]>, + overloads: Box<[Binding<'db>]>, } -impl<'db> CallBinding<'db> { - pub(crate) fn into_outcome(self) -> Result, CallError<'db>> { +impl<'db> CallableBinding<'db> { + pub(crate) fn into_outcome(self) -> Result, CallError<'db>> { if self.has_binding_errors() { return Err(CallError::BindingError { binding: self }); } - Ok(CallOutcome::Single(self)) + Ok(Bindings::Single(self)) } pub(crate) fn callable_type(&self) -> Type<'db> { @@ -210,7 +215,7 @@ impl<'db> CallBinding<'db> { /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. - pub(crate) fn matching_overload(&self) -> Option<(usize, &OverloadBinding<'db>)> { + pub(crate) fn matching_overload(&self) -> Option<(usize, &Binding<'db>)> { self.overloads .iter() .enumerate() @@ -219,7 +224,7 @@ impl<'db> CallBinding<'db> { /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. - pub(crate) fn matching_overload_mut(&mut self) -> Option<(usize, &mut OverloadBinding<'db>)> { + pub(crate) fn matching_overload_mut(&mut self) -> Option<(usize, &mut Binding<'db>)> { self.overloads .iter_mut() .enumerate() @@ -304,7 +309,7 @@ impl<'db> CallBinding<'db> { /// Binding information for one of the overloads of a callable. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct OverloadBinding<'db> { +pub(crate) struct Binding<'db> { /// Return type of the call. return_ty: Type<'db>, @@ -312,10 +317,10 @@ pub(crate) struct OverloadBinding<'db> { parameter_tys: Box<[Type<'db>]>, /// Call binding errors, if any. - errors: Vec>, + errors: Vec>, } -impl<'db> OverloadBinding<'db> { +impl<'db> Binding<'db> { pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) { self.return_ty = return_ty; } @@ -399,7 +404,7 @@ impl std::fmt::Display for ParameterContexts { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum CallBindingError<'db> { +pub(crate) enum CallableBindingError<'db> { /// The type of an argument is not assignable to the annotated type of its corresponding /// parameter. InvalidArgumentType { @@ -428,7 +433,7 @@ pub(crate) enum CallBindingError<'db> { }, } -impl<'db> CallBindingError<'db> { +impl<'db> CallableBindingError<'db> { fn parameter_span_from_index( db: &'db dyn Db, callable_ty: Type<'db>, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 5bcac004dfadb..ba67ef4894334 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4404,7 +4404,7 @@ impl<'db> TypeInferenceBuilder<'db> { let reflected_dunder = op.reflected_dunder(); let rhs_reflected = right_class.member(self.db(), reflected_dunder).symbol; // TODO: if `rhs_reflected` is possibly unbound, we should union the two possible - // CallOutcomes together + // Bindings together if !rhs_reflected.is_unbound() && rhs_reflected != left_class.member(self.db(), reflected_dunder).symbol { From 90e4b9b6d15e786552f4326e2eaa8a56f2db18cc Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 11 Mar 2025 15:20:08 -0400 Subject: [PATCH 02/57] Move Bindings into bind module --- .../src/types/call.rs | 77 +--------------- .../src/types/call/bind.rs | 89 +++++++++++++++++-- 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 9bd07bb44c9cc..f940017aa12a1 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -6,82 +6,7 @@ use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArguments}; -pub(super) use bind::{bind_call, CallableBinding}; - -/// A successfully bound call where all arguments are valid. -/// -/// It's guaranteed that the wrapped bindings have no errors. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum Bindings<'db> { - /// The call resolves to exactly one binding. - Single(CallableBinding<'db>), - - /// The call resolves to multiple bindings. - Union(Box<[CallableBinding<'db>]>), -} - -impl<'db> Bindings<'db> { - /// Calls each union element using the provided `call` function. - /// - /// Returns `Ok` if all variants can be called without error according to the callback and `Err` otherwise. - pub(super) fn try_call_union( - db: &'db dyn Db, - union: UnionType<'db>, - call: F, - ) -> Result> - where - F: Fn(Type<'db>) -> Result>, - { - let elements = union.elements(db); - let mut bindings = Vec::with_capacity(elements.len()); - let mut errors = Vec::new(); - let mut all_errors_not_callable = true; - - for element in elements { - match call(*element) { - Ok(Bindings::Single(binding)) => bindings.push(binding), - Ok(Bindings::Union(inner_bindings)) => { - bindings.extend(inner_bindings); - } - Err(error) => { - all_errors_not_callable &= error.is_not_callable(); - errors.push(error); - } - } - } - - if errors.is_empty() { - Ok(Bindings::Union(bindings.into())) - } else if bindings.is_empty() && all_errors_not_callable { - Err(CallError::NotCallable { - not_callable_type: Type::Union(union), - }) - } else { - Err(CallError::Union(UnionCallError { - errors: errors.into(), - bindings: bindings.into(), - called_type: Type::Union(union), - })) - } - } - - /// The type returned by this call. - pub(super) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { - match self { - Self::Single(binding) => binding.return_type(), - Self::Union(bindings) => { - UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) - } - } - } - - pub(super) fn bindings(&self) -> &[CallableBinding<'db>] { - match self { - Self::Single(binding) => std::slice::from_ref(binding), - Self::Union(bindings) => bindings, - } - } -} +pub(super) use bind::{bind_call, Bindings, CallableBinding}; /// The reason why calling a type failed. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 6527c2c7e786d..41995b61064eb 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -4,7 +4,8 @@ //! union of types, each of which might contain multiple overloads. use super::{ - Argument, Bindings, CallArguments, CallError, CallableSignature, InferContext, Signature, Type, + Argument, CallArguments, CallError, CallableSignature, InferContext, Signature, Type, + UnionCallError, }; use crate::db::Db; use crate::types::diagnostic::{ @@ -165,11 +166,80 @@ fn bind_overload<'db>( } } -/// Describes a callable for the purposes of diagnostics. -#[derive(Debug)] -pub(crate) struct CallableDescriptor<'a> { - name: &'a str, - kind: &'a str, +/// Binding information for a possible union of callables. At a call site, the arguments must be +/// compatible with _all_ of the types in the union for the call to be valid. +/// +/// It's guaranteed that the wrapped bindings have no errors. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Bindings<'db> { + /// The call resolves to exactly one binding. + Single(CallableBinding<'db>), + + /// The call resolves to multiple bindings. + Union(Box<[CallableBinding<'db>]>), +} + +impl<'db> Bindings<'db> { + /// Calls each union element using the provided `call` function. + /// + /// Returns `Ok` if all variants can be called without error according to the callback and `Err` otherwise. + pub(crate) fn try_call_union( + db: &'db dyn Db, + union: UnionType<'db>, + call: F, + ) -> Result> + where + F: Fn(Type<'db>) -> Result>, + { + let elements = union.elements(db); + let mut bindings = Vec::with_capacity(elements.len()); + let mut errors = Vec::new(); + let mut all_errors_not_callable = true; + + for element in elements { + match call(*element) { + Ok(Bindings::Single(binding)) => bindings.push(binding), + Ok(Bindings::Union(inner_bindings)) => { + bindings.extend(inner_bindings); + } + Err(error) => { + all_errors_not_callable &= error.is_not_callable(); + errors.push(error); + } + } + } + + if errors.is_empty() { + Ok(Bindings::Union(bindings.into())) + } else if bindings.is_empty() && all_errors_not_callable { + Err(CallError::NotCallable { + not_callable_type: Type::Union(union), + }) + } else { + Err(CallError::Union(UnionCallError { + errors: errors.into(), + bindings: bindings.into(), + called_type: Type::Union(union), + })) + } + } + + /// The type returned by this call. + pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { + match self { + Self::Single(binding) => binding.return_type(), + Self::Union(bindings) => { + UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) + } + } + } + + pub(crate) fn bindings(&self) -> &[CallableBinding<'db>] { + match self { + Self::Single(binding) => std::slice::from_ref(binding), + Self::Union(bindings) => bindings, + } + } } /// Binding information for a single callable. If the callable is overloaded, there is a separate @@ -350,6 +420,13 @@ impl<'db> Binding<'db> { } } +/// Describes a callable for the purposes of diagnostics. +#[derive(Debug)] +pub(crate) struct CallableDescriptor<'a> { + name: &'a str, + kind: &'a str, +} + /// Information needed to emit a diagnostic regarding a parameter. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct ParameterContext { From adede59813d348dbd84d2a2dbf0884a8ef7583b4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 11 Mar 2025 15:44:57 -0400 Subject: [PATCH 03/57] Make bind_call/overload constructor methods --- crates/red_knot_python_semantic/src/types.rs | 23 +- .../src/types/call.rs | 2 +- .../src/types/call/bind.rs | 298 +++++++++--------- 3 files changed, 163 insertions(+), 160 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 5da7367dba550..bf03820d10a49 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -30,7 +30,7 @@ use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; use crate::symbol::{imported_symbol, Boundness, Symbol, SymbolAndQualifiers}; -use crate::types::call::{bind_call, Bindings, CallArguments, UnionCallError}; +use crate::types::call::{Bindings, CallArguments, CallableBinding, UnionCallError}; use crate::types::class_base::ClassBase; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::infer::infer_unpack_types; @@ -2326,7 +2326,7 @@ impl<'db> Type<'db> { Type::Callable(CallableType::BoundMethod(bound_method)) => { let instance = bound_method.self_instance(db); let arguments = arguments.with_self(instance); - let binding = bind_call( + let binding = CallableBinding::bind( db, &arguments, bound_method.function(db).signature(db), @@ -2390,7 +2390,7 @@ impl<'db> Type<'db> { ]) } - let mut binding = bind_call(db, arguments, overloads(db), self); + let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); let Some((_, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2480,7 +2480,7 @@ impl<'db> Type<'db> { ]) } - let mut binding = bind_call(db, arguments, overloads(db), self); + let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); let Some((_, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2550,7 +2550,8 @@ impl<'db> Type<'db> { binding.into_outcome() } Type::FunctionLiteral(function_type) => { - let mut binding = bind_call(db, arguments, function_type.signature(db), self); + let mut binding = + CallableBinding::bind(db, arguments, function_type.signature(db), self); let Some((_, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2702,7 +2703,7 @@ impl<'db> Type<'db> { .into() } - let mut binding = bind_call(db, arguments, overloads(db), self); + let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); let Some((_, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2761,7 +2762,7 @@ impl<'db> Type<'db> { ]) } - let mut binding = bind_call(db, arguments, overloads(db), self); + let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); let Some((index, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2820,7 +2821,7 @@ impl<'db> Type<'db> { ]) } - let mut binding = bind_call(db, arguments, overloads(db), self); + let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); let Some((index, overload)) = binding.matching_overload_mut() else { return Err(CallError::BindingError { binding }); }; @@ -2836,7 +2837,7 @@ impl<'db> Type<'db> { // TODO check call vs signatures of `__new__` and/or `__init__` Type::ClassLiteral(ClassLiteralType { .. }) => { let signature = Signature::new(Parameters::gradual_form(), self.to_instance(db)); - let binding = bind_call(db, arguments, &signature.into(), self); + let binding = CallableBinding::bind(db, arguments, &signature.into(), self); binding.into_outcome() } @@ -2889,7 +2890,7 @@ impl<'db> Type<'db> { // `Never` is always callable and returns `Never`. Type::Dynamic(_) | Type::Never => { let overloads = CallableSignature::dynamic(self); - let binding = bind_call(db, arguments, &overloads, self); + let binding = CallableBinding::bind(db, arguments, &overloads, self); binding.into_outcome() } @@ -2899,7 +2900,7 @@ impl<'db> Type<'db> { Type::Intersection(_) => { let overloads = CallableSignature::todo("Type::Intersection.call()"); - let binding = bind_call(db, arguments, &overloads, self); + let binding = CallableBinding::bind(db, arguments, &overloads, self); binding.into_outcome() } diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index f940017aa12a1..d26009aaed7ce 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -6,7 +6,7 @@ use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArguments}; -pub(super) use bind::{bind_call, Bindings, CallableBinding}; +pub(super) use bind::{Bindings, CallableBinding}; /// The reason why calling a type failed. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 41995b61064eb..f68914a4de5a2 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -18,154 +18,6 @@ use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span}; use ruff_python_ast as ast; use ruff_text_size::Ranged; -/// Bind a [`CallArguments`] against a [`CallableSignature`]. -/// -/// The returned [`CallableBinding`] provides the return type of the call, the bound types for all -/// parameters, and any errors resulting from binding the call. -pub(crate) fn bind_call<'db>( - db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, - overloads: &CallableSignature<'db>, - callable_ty: Type<'db>, -) -> CallableBinding<'db> { - // 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 - // the matching overloads. Make sure to implement that as part of separating call binding into - // two phases. - // - // [1] https://github.com/python/typing/pull/1839 - let overloads = overloads - .iter() - .map(|signature| bind_overload(db, arguments, signature)) - .collect::>() - .into_boxed_slice(); - CallableBinding { - callable_ty, - overloads, - } -} - -fn bind_overload<'db>( - db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, - signature: &Signature<'db>, -) -> Binding<'db> { - let parameters = signature.parameters(); - // The type assigned to each parameter at this call site. - let mut parameter_tys = vec![None; parameters.len()]; - let mut errors = vec![]; - let mut next_positional = 0; - let mut first_excess_positional = None; - let mut num_synthetic_args = 0; - let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { - if argument_index >= num_synthetic_args { - // Adjust the argument index to skip synthetic args, which don't appear at the call - // site and thus won't be in the Call node arguments list. - Some(argument_index - num_synthetic_args) - } else { - // we are erroring on a synthetic argument, we'll just emit the diagnostic on the - // entire Call node, since there's no argument node for this argument at the call site - None - } - }; - for (argument_index, argument) in arguments.iter().enumerate() { - let (index, parameter, argument_ty, positional) = match argument { - Argument::Positional(ty) | Argument::Synthetic(ty) => { - if matches!(argument, Argument::Synthetic(_)) { - num_synthetic_args += 1; - } - let Some((index, parameter)) = parameters - .get_positional(next_positional) - .map(|param| (next_positional, param)) - .or_else(|| parameters.variadic()) - else { - first_excess_positional.get_or_insert(argument_index); - next_positional += 1; - continue; - }; - next_positional += 1; - (index, parameter, ty, !parameter.is_variadic()) - } - Argument::Keyword { name, ty } => { - let Some((index, parameter)) = parameters - .keyword_by_name(name) - .or_else(|| parameters.keyword_variadic()) - else { - errors.push(CallableBindingError::UnknownArgument { - argument_name: ast::name::Name::new(name), - argument_index: get_argument_index(argument_index, num_synthetic_args), - }); - continue; - }; - (index, parameter, ty, false) - } - - Argument::Variadic(_) | Argument::Keywords(_) => { - // TODO - continue; - } - }; - if let Some(expected_ty) = parameter.annotated_type() { - if !argument_ty.is_assignable_to(db, expected_ty) { - errors.push(CallableBindingError::InvalidArgumentType { - parameter: ParameterContext::new(parameter, index, positional), - argument_index: get_argument_index(argument_index, num_synthetic_args), - expected_ty, - provided_ty: *argument_ty, - }); - } - } - if let Some(existing) = parameter_tys[index].replace(*argument_ty) { - if parameter.is_variadic() || parameter.is_keyword_variadic() { - let union = UnionType::from_elements(db, [existing, *argument_ty]); - parameter_tys[index].replace(union); - } else { - errors.push(CallableBindingError::ParameterAlreadyAssigned { - argument_index: get_argument_index(argument_index, num_synthetic_args), - parameter: ParameterContext::new(parameter, index, positional), - }); - } - } - } - if let Some(first_excess_argument_index) = first_excess_positional { - errors.push(CallableBindingError::TooManyPositionalArguments { - first_excess_argument_index: get_argument_index( - first_excess_argument_index, - num_synthetic_args, - ), - expected_positional_count: parameters.positional().count(), - provided_positional_count: next_positional, - }); - } - let mut missing = vec![]; - for (index, bound_ty) in parameter_tys.iter().enumerate() { - if bound_ty.is_none() { - let param = ¶meters[index]; - if param.is_variadic() || param.is_keyword_variadic() || param.default_type().is_some() - { - // variadic/keywords and defaulted arguments are not required - continue; - } - missing.push(ParameterContext::new(param, index, false)); - } - } - - if !missing.is_empty() { - errors.push(CallableBindingError::MissingArguments { - parameters: ParameterContexts(missing), - }); - } - - Binding { - return_ty: signature.return_ty.unwrap_or(Type::unknown()), - parameter_tys: parameter_tys - .into_iter() - .map(|opt_ty| opt_ty.unwrap_or(Type::unknown())) - .collect(), - errors, - } -} - /// Binding information for a possible union of callables. At a call site, the arguments must be /// compatible with _all_ of the types in the union for the call to be valid. /// @@ -266,6 +118,33 @@ pub(crate) struct CallableBinding<'db> { } impl<'db> CallableBinding<'db> { + /// Bind a [`CallArguments`] against a [`CallableSignature`]. + /// + /// The returned [`CallableBinding`] provides the return type of the call, the bound types for all + /// parameters, and any errors resulting from binding the call. + pub(crate) fn bind( + db: &'db dyn Db, + arguments: &CallArguments<'_, 'db>, + overloads: &CallableSignature<'db>, + callable_ty: Type<'db>, + ) -> Self { + // 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 + // the matching overloads. Make sure to implement that as part of separating call binding into + // two phases. + // + // [1] https://github.com/python/typing/pull/1839 + let overloads = overloads + .iter() + .map(|signature| Binding::bind(db, arguments, signature)) + .collect::>() + .into_boxed_slice(); + CallableBinding { + callable_ty, + overloads, + } + } + pub(crate) fn into_outcome(self) -> Result, CallError<'db>> { if self.has_binding_errors() { return Err(CallError::BindingError { binding: self }); @@ -391,6 +270,129 @@ pub(crate) struct Binding<'db> { } impl<'db> Binding<'db> { + fn bind( + db: &'db dyn Db, + arguments: &CallArguments<'_, 'db>, + signature: &Signature<'db>, + ) -> Self { + let parameters = signature.parameters(); + // The type assigned to each parameter at this call site. + let mut parameter_tys = vec![None; parameters.len()]; + let mut errors = vec![]; + let mut next_positional = 0; + let mut first_excess_positional = None; + let mut num_synthetic_args = 0; + let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { + if argument_index >= num_synthetic_args { + // Adjust the argument index to skip synthetic args, which don't appear at the call + // site and thus won't be in the Call node arguments list. + Some(argument_index - num_synthetic_args) + } else { + // we are erroring on a synthetic argument, we'll just emit the diagnostic on the + // entire Call node, since there's no argument node for this argument at the call site + None + } + }; + for (argument_index, argument) in arguments.iter().enumerate() { + let (index, parameter, argument_ty, positional) = match argument { + Argument::Positional(ty) | Argument::Synthetic(ty) => { + if matches!(argument, Argument::Synthetic(_)) { + num_synthetic_args += 1; + } + let Some((index, parameter)) = parameters + .get_positional(next_positional) + .map(|param| (next_positional, param)) + .or_else(|| parameters.variadic()) + else { + first_excess_positional.get_or_insert(argument_index); + next_positional += 1; + continue; + }; + next_positional += 1; + (index, parameter, ty, !parameter.is_variadic()) + } + Argument::Keyword { name, ty } => { + let Some((index, parameter)) = parameters + .keyword_by_name(name) + .or_else(|| parameters.keyword_variadic()) + else { + errors.push(CallableBindingError::UnknownArgument { + argument_name: ast::name::Name::new(name), + argument_index: get_argument_index(argument_index, num_synthetic_args), + }); + continue; + }; + (index, parameter, ty, false) + } + + Argument::Variadic(_) | Argument::Keywords(_) => { + // TODO + continue; + } + }; + if let Some(expected_ty) = parameter.annotated_type() { + if !argument_ty.is_assignable_to(db, expected_ty) { + errors.push(CallableBindingError::InvalidArgumentType { + parameter: ParameterContext::new(parameter, index, positional), + argument_index: get_argument_index(argument_index, num_synthetic_args), + expected_ty, + provided_ty: *argument_ty, + }); + } + } + if let Some(existing) = parameter_tys[index].replace(*argument_ty) { + if parameter.is_variadic() || parameter.is_keyword_variadic() { + let union = UnionType::from_elements(db, [existing, *argument_ty]); + parameter_tys[index].replace(union); + } else { + errors.push(CallableBindingError::ParameterAlreadyAssigned { + argument_index: get_argument_index(argument_index, num_synthetic_args), + parameter: ParameterContext::new(parameter, index, positional), + }); + } + } + } + if let Some(first_excess_argument_index) = first_excess_positional { + errors.push(CallableBindingError::TooManyPositionalArguments { + first_excess_argument_index: get_argument_index( + first_excess_argument_index, + num_synthetic_args, + ), + expected_positional_count: parameters.positional().count(), + provided_positional_count: next_positional, + }); + } + let mut missing = vec![]; + for (index, bound_ty) in parameter_tys.iter().enumerate() { + if bound_ty.is_none() { + let param = ¶meters[index]; + if param.is_variadic() + || param.is_keyword_variadic() + || param.default_type().is_some() + { + // variadic/keywords and defaulted arguments are not required + continue; + } + missing.push(ParameterContext::new(param, index, false)); + } + } + + if !missing.is_empty() { + errors.push(CallableBindingError::MissingArguments { + parameters: ParameterContexts(missing), + }); + } + + Self { + return_ty: signature.return_ty.unwrap_or(Type::unknown()), + parameter_tys: parameter_tys + .into_iter() + .map(|opt_ty| opt_ty.unwrap_or(Type::unknown())) + .collect(), + errors, + } + } + pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) { self.return_ty = return_ty; } From 57b400c2d2dea2d012ab40526e4ab412b9eec33c Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 11 Mar 2025 16:16:34 -0400 Subject: [PATCH 04/57] Add dunder_call to callable signature --- crates/red_knot_python_semantic/src/symbol.rs | 4 +- .../src/types/signatures.rs | 37 +++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/crates/red_knot_python_semantic/src/symbol.rs b/crates/red_knot_python_semantic/src/symbol.rs index 3ba9bc36920de..ba1b509f47053 100644 --- a/crates/red_knot_python_semantic/src/symbol.rs +++ b/crates/red_knot_python_semantic/src/symbol.rs @@ -15,8 +15,8 @@ use crate::{resolve_module, Db, KnownModule, Module, Program}; pub(crate) use implicit_globals::module_type_implicit_global_symbol; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum Boundness { +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Boundness { Bound, PossiblyUnbound, } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 9d5fa2ba4fbd8..eb9105a768d3e 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -11,16 +11,28 @@ //! arguments must match _at least one_ overload. use super::{definition_expression_type, DynamicType, Type}; +use crate::semantic_index::definition::Definition; +use crate::symbol::Boundness; +use crate::types::todo_type; use crate::Db; -use crate::{semantic_index::definition::Definition, types::todo_type}; use ruff_python_ast::{self as ast, name::Name}; /// 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 enum CallableSignature<'db> { - Single(Signature<'db>), - Overloaded(Box<[Signature<'db>]>), + Single { + signature: Signature<'db>, + /// If this is a callable object (i.e. called via a `__call__` method), the boundness of + /// that call method. + dunder_call: Option, + }, + Overloaded { + overloads: Box<[Signature<'db>]>, + /// If this is a callable object (i.e. called via a `__call__` method), the boundness of + /// that call method. + dunder_call: Option, + }, } impl<'db> CallableSignature<'db> { @@ -34,17 +46,23 @@ impl<'db> CallableSignature<'db> { let mut iter = overloads.into_iter(); let first_overload = iter.next().expect("overloads should not be empty"); let Some(second_overload) = iter.next() else { - return CallableSignature::Single(first_overload); + return CallableSignature::Single { + signature: first_overload, + dunder_call: None, + }; }; let mut overloads = vec![first_overload, second_overload]; overloads.extend(iter); - CallableSignature::Overloaded(overloads.into()) + CallableSignature::Overloaded { + overloads: overloads.into(), + dunder_call: None, + } } pub(crate) fn iter(&self) -> std::slice::Iter> { match self { - CallableSignature::Single(signature) => std::slice::from_ref(signature).iter(), - CallableSignature::Overloaded(signatures) => signatures.iter(), + CallableSignature::Single { signature, .. } => std::slice::from_ref(signature).iter(), + CallableSignature::Overloaded { overloads, .. } => overloads.iter(), } } @@ -70,7 +88,10 @@ impl<'db> CallableSignature<'db> { impl<'db> From> for CallableSignature<'db> { fn from(signature: Signature<'db>) -> Self { - CallableSignature::Single(signature) + CallableSignature::Single { + signature, + dunder_call: None, + } } } From e611d6fa1c43ab6d1ffa3eac849755a3f701dc43 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 12 Mar 2025 19:07:13 -0400 Subject: [PATCH 05/57] Mostly working --- .../resources/mdtest/binary/instances.md | 2 +- .../mdtest/call/callable_instance.md | 4 +- .../comparison/instances/membership_test.md | 2 +- .../comparison/instances/rich_comparison.md | 2 +- .../resources/mdtest/comparison/tuples.md | 4 +- .../mdtest/conditional/if_expression.md | 2 +- .../mdtest/conditional/if_statement.md | 2 +- .../resources/mdtest/conditional/match.md | 2 +- .../resources/mdtest/expression/assert.md | 2 +- .../resources/mdtest/expression/boolean.md | 8 +- .../resources/mdtest/loops/while_loop.md | 2 +- ...types_with_invalid_`__bool__`_methods.snap | 2 +- ...oesn't_implement_`__bool__`_correctly.snap | 2 +- ...hat_implements_`__bool__`_incorrectly.snap | 2 +- ..._don't_implement_`__bool__`_correctly.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- ...that_incorrectly_implement_`__bool__`.snap | 2 +- .../resources/mdtest/unary/not.md | 2 +- crates/red_knot_python_semantic/src/symbol.rs | 2 +- crates/red_knot_python_semantic/src/types.rs | 923 +++++++++--------- .../src/types/call.rs | 114 +-- .../src/types/call/bind.rs | 455 ++++++--- .../src/types/class.rs | 52 +- .../src/types/infer.rs | 59 +- .../src/types/signatures.rs | 235 ++++- 25 files changed, 1051 insertions(+), 835 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md index 5c701b22a6484..40fddeecddf7b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md @@ -363,7 +363,7 @@ reveal_type(X() + Y()) # revealed: int ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 a = NotBoolable() diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 5b6bb368797bb..72506fab6979d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -85,7 +85,7 @@ class C: c = C() -# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 2 (`x`) of bound method `__call__`; expected type `int`" +# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 2 (`x`); expected type `int`" reveal_type(c("foo")) # revealed: int ``` @@ -99,7 +99,7 @@ class C: c = C() -# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`) of bound method `__call__`; expected type `int`" +# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`); expected type `int`" reveal_type(c()) # revealed: int ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/membership_test.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/membership_test.md index 4b1617a979fcf..b28d0d04fa9bf 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/membership_test.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/membership_test.md @@ -191,7 +191,7 @@ It may also be more appropriate to use `unsupported-operator` as the error code. ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 class WithContains: def __contains__(self, item) -> NotBoolable: diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md index a0c6680c610c3..f6fda97032a89 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md @@ -355,7 +355,7 @@ element) of a chained comparison. ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 class Comparable: def __lt__(self, item) -> NotBoolable: diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md index 557791790d511..d4cd80765c7e1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md @@ -355,7 +355,7 @@ def compute_chained_comparison(): ```py class NotBoolable: - __bool__ = 5 + __bool__: int = 5 class Comparable: def __lt__(self, other) -> NotBoolable: @@ -387,7 +387,7 @@ class A: return NotBoolable() class NotBoolable: - __bool__ = None + __bool__: None = None # error: [unsupported-bool-conversion] (A(),) == (A(),) diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md index 47696c065840b..b14d358ea04bd 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md @@ -40,7 +40,7 @@ def _(flag: bool): ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" 3 if NotBoolable() else 4 diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md index fff101842773f..9a3fc4f8f4c89 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md @@ -152,7 +152,7 @@ def _(flag: bool): ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" if NotBoolable(): diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md index 3fe4956b3468a..2f0ad24fe1e3e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md @@ -48,7 +48,7 @@ def _(target: int): ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 def _(target: int, flag: NotBoolable): y = 1 diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/assert.md b/crates/red_knot_python_semantic/resources/mdtest/expression/assert.md index f7e1715246a6d..54073f9170fe3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/assert.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/assert.md @@ -2,7 +2,7 @@ ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" assert NotBoolable() diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md b/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md index ccedbac6f4b06..ce3363636d740 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md @@ -121,7 +121,7 @@ if NotBoolable(): ```py class NotBoolable: - __bool__ = None + __bool__: None = None # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" if NotBoolable(): @@ -133,9 +133,9 @@ if NotBoolable(): ```py def test(cond: bool): class NotBoolable: - __bool__ = None if cond else 3 + __bool__: int | None = None if cond else 3 - # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; it incorrectly implements `__bool__`" + # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" if NotBoolable(): ... ``` @@ -145,7 +145,7 @@ def test(cond: bool): ```py def test(cond: bool): class NotBoolable: - __bool__ = None + __bool__: None = None a = 10 if cond else NotBoolable() diff --git a/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md b/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md index c3da62e064ec4..397a06b742dac 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md @@ -121,7 +121,7 @@ def _(flag: bool, flag2: bool): ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 # error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable" while NotBoolable(): diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap index cd7e6f94b9c9f..cebcc8765538f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/instances.md_-_Binary_operations_on_instances_-_Operations_involving_types_with_invalid_`__bool__`_methods.snap @@ -13,7 +13,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/binary/instances.m ``` 1 | class NotBoolable: -2 | __bool__ = 3 +2 | __bool__: int = 3 3 | 4 | a = NotBoolable() 5 | diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap index d714fd2c18a00..c811afea2fa0d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/membership_test.md_-_Comparison___Membership_Test_-_Return_type_that_doesn't_implement_`__bool__`_correctly.snap @@ -13,7 +13,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc ``` 1 | class NotBoolable: - 2 | __bool__ = 3 + 2 | __bool__: int = 3 3 | 4 | class WithContains: 5 | def __contains__(self, item) -> NotBoolable: diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap index 8471ca5c59e0f..3482463acd311 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/not.md_-_Unary_not_-_Object_that_implements_`__bool__`_incorrectly.snap @@ -13,7 +13,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/unary/not.md ``` 1 | class NotBoolable: -2 | __bool__ = 3 +2 | __bool__: int = 3 3 | 4 | # error: [unsupported-bool-conversion] 5 | not NotBoolable() diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap index 87779b02dc3db..c0004ad58d0f0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/rich_comparison.md_-_Comparison___Rich_Comparison_-_Chained_comparisons_with_objects_that_don't_implement_`__bool__`_correctly.snap @@ -13,7 +13,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc ``` 1 | class NotBoolable: - 2 | __bool__ = 3 + 2 | __bool__: int = 3 3 | 4 | class Comparable: 5 | def __lt__(self, item) -> NotBoolable: diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap index f0694a0fdace6..b741702c18837 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Chained_comparisons_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -13,7 +13,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples. ``` 1 | class NotBoolable: - 2 | __bool__ = 5 + 2 | __bool__: int = 5 3 | 4 | class Comparable: 5 | def __lt__(self, other) -> NotBoolable: diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap index 386af0045668d..55e6c8fa67789 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Equality_with_elements_that_incorrectly_implement_`__bool__`.snap @@ -17,7 +17,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples. 3 | return NotBoolable() 4 | 5 | class NotBoolable: -6 | __bool__ = None +6 | __bool__: None = None 7 | 8 | # error: [unsupported-bool-conversion] 9 | (A(),) == (A(),) diff --git a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md index b3b75678f9da9..82f589517af48 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md +++ b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md @@ -210,7 +210,7 @@ reveal_type(not PossiblyUnboundBool()) ```py class NotBoolable: - __bool__ = 3 + __bool__: int = 3 # error: [unsupported-bool-conversion] not NotBoolable() diff --git a/crates/red_knot_python_semantic/src/symbol.rs b/crates/red_knot_python_semantic/src/symbol.rs index ba1b509f47053..53b2c88b15591 100644 --- a/crates/red_knot_python_semantic/src/symbol.rs +++ b/crates/red_knot_python_semantic/src/symbol.rs @@ -16,7 +16,7 @@ use crate::{resolve_module, Db, KnownModule, Module, Program}; pub(crate) use implicit_globals::module_type_implicit_global_symbol; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Boundness { +pub(crate) enum Boundness { Bound, PossiblyUnbound, } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index bf03820d10a49..9b1de74ee4b81 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -20,7 +20,7 @@ pub(crate) use self::infer::{ infer_scope_types, }; pub use self::narrow::KnownConstraintFunction; -pub(crate) use self::signatures::{CallableSignature, Signature}; +pub(crate) use self::signatures::{CallableSignature, Signature, Signatures}; pub use self::subclass_of::SubclassOfType; use crate::module_name::ModuleName; use crate::module_resolver::{file_to_module, resolve_module, KnownModule}; @@ -30,7 +30,7 @@ use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; use crate::symbol::{imported_symbol, Boundness, Symbol, SymbolAndQualifiers}; -use crate::types::call::{Bindings, CallArguments, CallableBinding, UnionCallError}; +use crate::types::call::{Bindings, CallArguments}; use crate::types::class_base::ClassBase; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::infer::infer_unpack_types; @@ -2170,48 +2170,25 @@ impl<'db> Type<'db> { } } Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, - Err(CallDunderError::Call(err)) => { - let err = match err { - // Unwrap call errors where only a single variant isn't callable. - // E.g. in the case of `Unknown & T` - // TODO: Improve handling of unions. While this improves messages overall, - // it still results in loosing information. Or should the information - // be recomputed when rendering the diagnostic? - CallError::Union(union_error) => { - if let Type::Union(_) = union_error.called_type { - if union_error.errors.len() == 1 { - union_error.errors.into_vec().pop().unwrap() - } else { - CallError::Union(union_error) - } - } else { - CallError::Union(union_error) - } - } - err => err, - }; - - match err { - CallError::BindingError { binding } => { - return Err(BoolError::IncorrectArguments { - truthiness: type_to_truthiness(binding.return_type()), - not_boolable_type: *instance_ty, - }); - } - CallError::NotCallable { .. } => { - return Err(BoolError::NotCallable { - not_boolable_type: *instance_ty, - }); - } + Err(CallDunderError::Call(err)) => match err { + CallError::BindingError(binding) => { + return Err(BoolError::IncorrectArguments { + truthiness: type_to_truthiness(binding.return_type(db)), + not_boolable_type: *instance_ty, + }); + } + CallError::NotCallable(_) => { + return Err(BoolError::NotCallable { + not_boolable_type: *instance_ty, + }); + } - CallError::PossiblyUnboundDunderCall { .. } - | CallError::Union(..) => { - return Err(BoolError::Other { - not_boolable_type: *self, - }) - } + CallError::PossiblyNotCallable(_) => { + return Err(BoolError::Other { + not_boolable_type: *self, + }) } - } + }, } } }, @@ -2314,27 +2291,18 @@ impl<'db> Type<'db> { non_negative_int_literal(db, return_ty) } - /// Calls `self` - /// - /// Returns `Ok` if the call with the given arguments is successful and `Err` otherwise. - fn try_call( - self, - db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, - ) -> Result, CallError<'db>> { + /// Returns the (possibly unioned, possibly overloaded) signatures of a callable type. Returns + /// [`Signatures::not_callable`] if the type is not callable. + fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { - let instance = bound_method.self_instance(db); - let arguments = arguments.with_self(instance); - let binding = CallableBinding::bind( - db, - &arguments, - bound_method.function(db).signature(db), - self, - ); - binding.into_outcome() + let signature = bound_method.function(db).signature(db); + let mut signature = CallableSignature::new(callable_ty, signature.clone()); + signature.bound_type = Some(bound_method.self_instance(db)); + Signatures::single(signature.into()) } - Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { + + Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { // Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`. // This is required because we need to return more precise types than what the signature in // typeshed provides: @@ -2348,10 +2316,10 @@ impl<'db> Type<'db> { // def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ... // ``` - #[salsa::tracked(return_ref)] - fn overloads<'db>(db: &'db dyn Db) -> CallableSignature<'db> { - let not_none = Type::none(db).negate(db); - CallableSignature::from_overloads([ + let not_none = Type::none(db).negate(db); + let signature = CallableSignature::from_overloads( + callable_ty, + [ Signature::new( Parameters::new([ Parameter::new( @@ -2387,40 +2355,11 @@ impl<'db> Type<'db> { ]), None, ), - ]) - } - - let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); - let Some((_, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - - if function.has_known_class_decorator(db, KnownClass::Classmethod) - && function.decorators(db).len() == 1 - { - if let Some(owner) = arguments.second_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, owner), - ))); - } else if let Some(instance) = arguments.first_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, instance.to_meta_type(db)), - ))); - } - } else { - if let Some(first) = arguments.first_argument() { - if first.is_none(db) { - overload.set_return_type(Type::FunctionLiteral(function)); - } else { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, first), - ))); - } - } - } - - binding.into_outcome() + ], + ); + Signatures::single(signature) } + Type::Callable(CallableType::WrapperDescriptorDunderGet) => { // Here, we also model `types.FunctionType.__get__`, but now we consider a call to // this as a function, i.e. we also expect the `self` argument to be passed in. @@ -2428,10 +2367,10 @@ impl<'db> Type<'db> { // TODO: Consider merging this signature with the one in the previous match clause, // since the previous one is just this signature with the `self` parameters // removed. - #[salsa::tracked(return_ref)] - fn overloads<'db>(db: &'db dyn Db) -> CallableSignature<'db> { - let not_none = Type::none(db).negate(db); - CallableSignature::from_overloads([ + let not_none = Type::none(db).negate(db); + let signature = CallableSignature::from_overloads( + callable_ty, + [ Signature::new( Parameters::new([ Parameter::new( @@ -2477,209 +2416,15 @@ impl<'db> Type<'db> { ]), None, ), - ]) - } - - let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); - let Some((_, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - - if let Some(function_ty @ Type::FunctionLiteral(function)) = - arguments.first_argument() - { - if function.has_known_class_decorator(db, KnownClass::Classmethod) - && function.decorators(db).len() == 1 - { - if let Some(owner) = arguments.third_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, owner), - ))); - } else if let Some(instance) = arguments.second_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, instance.to_meta_type(db)), - ))); - } - } else { - match (arguments.second_argument(), arguments.third_argument()) { - (Some(instance), _) if instance.is_none(db) => { - overload.set_return_type(function_ty); - } - - ( - Some(Type::KnownInstance(KnownInstanceType::TypeAliasType( - type_alias, - ))), - Some(Type::ClassLiteral(ClassLiteralType { class })), - ) if class.is_known(db, KnownClass::TypeAliasType) - && function.name(db) == "__name__" => - { - overload - .set_return_type(Type::string_literal(db, type_alias.name(db))); - } - - ( - Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), - Some(Type::ClassLiteral(ClassLiteralType { class })), - ) if class.is_known(db, KnownClass::TypeVar) - && function.name(db) == "__name__" => - { - overload - .set_return_type(Type::string_literal(db, typevar.name(db))); - } - - (Some(_), _) - if function.has_known_class_decorator(db, KnownClass::Property) => - { - overload.set_return_type(todo_type!("@property")); - } - - (Some(instance), _) => { - overload.set_return_type(Type::Callable( - CallableType::BoundMethod(BoundMethodType::new( - db, function, instance, - )), - )); - } - - (None, _) => {} - } - } - } - - binding.into_outcome() + ], + ); + Signatures::single(signature) } - Type::FunctionLiteral(function_type) => { - let mut binding = - CallableBinding::bind(db, arguments, function_type.signature(db), self); - let Some((_, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - match function_type.known(db) { - Some(KnownFunction::IsEquivalentTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_equivalent_to(db, *ty_b), - )); - } - } - Some(KnownFunction::IsSubtypeOf) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_subtype_of(db, *ty_b), - )); - } - } - Some(KnownFunction::IsAssignableTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_assignable_to(db, *ty_b), - )); - } - } - Some(KnownFunction::IsDisjointFrom) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_disjoint_from(db, *ty_b), - )); - } - } - Some(KnownFunction::IsGradualEquivalentTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_gradual_equivalent_to(db, *ty_b), - )); - } - } - Some(KnownFunction::IsFullyStatic) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); - } - } - Some(KnownFunction::IsSingleton) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); - } - } - Some(KnownFunction::IsSingleValued) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); - } - } - - Some(KnownFunction::Len) => { - if let [first_arg] = overload.parameter_types() { - if let Some(len_ty) = first_arg.len(db) { - overload.set_return_type(len_ty); - } - }; - } - - Some(KnownFunction::Repr) => { - if let [first_arg] = overload.parameter_types() { - overload.set_return_type(first_arg.repr(db)); - }; - } - - Some(KnownFunction::Cast) => { - // TODO: Use `.parameter_types()` exclusively when overloads are supported. - if let Some(casted_ty) = arguments.first_argument() { - if let [_, _] = overload.parameter_types() { - overload.set_return_type(casted_ty); - } - }; - } - - Some(KnownFunction::Overload) => { - overload.set_return_type(todo_type!("overload(..) return type")); - } - - Some(KnownFunction::GetattrStatic) => { - let [instance_ty, attr_name, default] = overload.parameter_types() else { - return binding.into_outcome(); - }; - - let Some(attr_name) = attr_name.into_string_literal() else { - return binding.into_outcome(); - }; - - let default = if default.is_unknown() { - Type::Never - } else { - *default - }; - - let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); - - // TODO: we could emit a diagnostic here (if default is not set) - overload.set_return_type( - match instance_ty.static_member(db, attr_name.value(db)) { - Symbol::Type(ty, Boundness::Bound) => { - if instance_ty.is_fully_static(db) { - ty - } else { - // Here, we attempt to model the fact that an attribute lookup on - // a non-fully static type could fail. This is an approximation, - // as there are gradual types like `tuple[Any]`, on which a lookup - // of (e.g. of the `index` method) would always succeed. - - union_with_default(ty) - } - } - Symbol::Type(ty, Boundness::PossiblyUnbound) => { - union_with_default(ty) - } - Symbol::Unbound => default, - }, - ); - } - - _ => {} - }; - - binding.into_outcome() - } + Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::new( + callable_ty, + function_type.signature(db).clone(), + )), Type::ClassLiteral(ClassLiteralType { class }) if class.is_known(db, KnownClass::Bool) => @@ -2688,8 +2433,8 @@ impl<'db> Type<'db> { // class bool(int): // def __new__(cls, o: object = ..., /) -> Self: ... // ``` - #[salsa::tracked(return_ref)] - fn overloads<'db>(db: &'db dyn Db) -> CallableSignature<'db> { + let signature = CallableSignature::new( + callable_ty, Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2699,21 +2444,9 @@ impl<'db> Type<'db> { }, )]), Some(KnownClass::Bool.to_instance(db)), - ) - .into() - } - - let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); - let Some((_, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.bool(db).into_type(db)) - .unwrap_or(Type::BooleanLiteral(false)), + ), ); - binding.into_outcome() + Signatures::single(signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2726,9 +2459,9 @@ impl<'db> Type<'db> { // @overload // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` - #[salsa::tracked(return_ref)] - fn overloads<'db>(db: &'db dyn Db) -> CallableSignature<'db> { - CallableSignature::from_overloads([ + let signature = CallableSignature::from_overloads( + callable_ty, + [ Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2759,22 +2492,9 @@ impl<'db> Type<'db> { ]), Some(KnownClass::Str.to_instance(db)), ), - ]) - } - - let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); - let Some((index, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - if index == 0 { - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.str(db)) - .unwrap_or_else(|| Type::string_literal(db, "")), - ); - } - binding.into_outcome() + ], + ); + Signatures::single(signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2787,9 +2507,9 @@ impl<'db> Type<'db> { // @overload // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` - #[salsa::tracked(return_ref)] - fn overloads<'db>(db: &'db dyn Db) -> CallableSignature<'db> { - CallableSignature::from_overloads([ + let signature = CallableSignature::from_overloads( + callable_ty, + [ Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2818,95 +2538,387 @@ impl<'db> Type<'db> { ]), Some(KnownClass::Type.to_instance(db)), ), - ]) - } - - let mut binding = CallableBinding::bind(db, arguments, overloads(db), self); - let Some((index, overload)) = binding.matching_overload_mut() else { - return Err(CallError::BindingError { binding }); - }; - if index == 0 { - if let Some(arg) = arguments.first_argument() { - overload.set_return_type(arg.to_meta_type(db)); - } - } - binding.into_outcome() + ], + ); + Signatures::single(signature) } // TODO annotated return type on `__new__` or metaclass `__call__` // TODO check call vs signatures of `__new__` and/or `__init__` Type::ClassLiteral(ClassLiteralType { .. }) => { - let signature = Signature::new(Parameters::gradual_form(), self.to_instance(db)); - let binding = CallableBinding::bind(db, arguments, &signature.into(), self); - binding.into_outcome() + let signature = CallableSignature::new( + callable_ty, + Signature::new(Parameters::gradual_form(), self.to_instance(db)), + ); + Signatures::single(signature) } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { ClassBase::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).try_call(db, arguments) + Type::Dynamic(dynamic_type).signatures(db, callable_ty) } - ClassBase::Class(class) => Type::class_literal(class).try_call(db, arguments), + ClassBase::Class(class) => Type::class_literal(class).signatures(db, callable_ty), }, - instance_ty @ Type::Instance(_) => { - instance_ty - .try_call_dunder(db, "__call__", arguments) - .map_err(|err| match err { - CallDunderError::Call(CallError::NotCallable { .. }) => { - // Turn "`` not callable" into - // "`X` not callable" - CallError::NotCallable { - not_callable_type: self, - } + Type::Instance(_) => { + let Some((mut signatures, boundness)) = + self.dunder_signature(db, Some(callable_ty), "__call__") + else { + return Signatures::not_callable(self); + }; + signatures.set_dunder_call_boundness(boundness); + signatures + } + + // 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)), + + // Note that this correctly returns `None` if none of the union elements are callable. + Type::Union(union) => Signatures::from_union( + callable_ty, + union + .elements(db) + .iter() + .map(|element| element.signatures(db, *element)), + ), + + Type::Intersection(_) => { + Signatures::single(CallableSignature::todo("Type::Intersection.call()")) + } + + _ => Signatures::not_callable(self), + } + } + + /// Calls `self` + fn try_call( + self, + db: &'db dyn Db, + arguments: &CallArguments<'_, 'db>, + ) -> Result, CallError>> { + // Note that for objects that are callable via a `__call__` method, we will get the + // signature of the dunder method, but will pass in the type of the object as the "callable + // type". That ensures that we get errors like "`X` is not callable" instead of "`` is not callable". + let signatures = self.signatures(db, self); + let mut bindings = Bindings::bind(db, &signatures, arguments).into_result()?; + + for binding in bindings.bindings_mut() { + // For certain known callables, we have special case logic to determine the return type + // in a way that isn't directly expressible in the type system. Each special case + // listed here should have a corresponding clause above in `signatures`. + let (overload_index, overload) = binding + .matching_overload_mut() + .expect("bindings with no error should have a matching overload"); + + match self { + Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { + if function.has_known_class_decorator(db, KnownClass::Classmethod) + && function.decorators(db).len() == 1 + { + if let Some(owner) = arguments.second_argument() { + overload.set_return_type(Type::Callable(CallableType::BoundMethod( + BoundMethodType::new(db, function, owner), + ))); + } else if let Some(instance) = arguments.first_argument() { + overload.set_return_type(Type::Callable(CallableType::BoundMethod( + BoundMethodType::new(db, function, instance.to_meta_type(db)), + ))); } - CallDunderError::Call(CallError::Union(UnionCallError { - called_type: _, - bindings, - errors, - })) => CallError::Union(UnionCallError { - called_type: self, - bindings, - errors, - }), - CallDunderError::Call(error) => error, - // Turn "possibly unbound object of type `Literal['__call__']`" - // into "`X` not callable (possibly unbound `__call__` method)" - CallDunderError::PossiblyUnbound(outcome) => { - CallError::PossiblyUnboundDunderCall { - called_type: self, - outcome: Box::new(outcome), + } else { + if let Some(first) = arguments.first_argument() { + if first.is_none(db) { + overload.set_return_type(Type::FunctionLiteral(function)); + } else { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, function, first, + )), + )); } } - CallDunderError::MethodNotAvailable => { - // Turn "`X.__call__` unbound" into "`X` not callable" - CallError::NotCallable { - not_callable_type: self, + } + } + + Type::Callable(CallableType::WrapperDescriptorDunderGet) => { + if let Some(function_ty @ Type::FunctionLiteral(function)) = + arguments.first_argument() + { + if function.has_known_class_decorator(db, KnownClass::Classmethod) + && function.decorators(db).len() == 1 + { + if let Some(owner) = arguments.third_argument() { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, function, owner, + )), + )); + } else if let Some(instance) = arguments.second_argument() { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, + function, + instance.to_meta_type(db), + )), + )); + } + } else { + match (arguments.second_argument(), arguments.third_argument()) { + (Some(instance), _) if instance.is_none(db) => { + overload.set_return_type(function_ty); + } + + ( + Some(Type::KnownInstance(KnownInstanceType::TypeAliasType( + type_alias, + ))), + Some(Type::ClassLiteral(ClassLiteralType { class })), + ) if class.is_known(db, KnownClass::TypeAliasType) + && function.name(db) == "__name__" => + { + overload.set_return_type(Type::string_literal( + db, + type_alias.name(db), + )); + } + + ( + Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), + Some(Type::ClassLiteral(ClassLiteralType { class })), + ) if class.is_known(db, KnownClass::TypeVar) + && function.name(db) == "__name__" => + { + overload.set_return_type(Type::string_literal( + db, + typevar.name(db), + )); + } + + (Some(_), _) + if function + .has_known_class_decorator(db, KnownClass::Property) => + { + overload.set_return_type(todo_type!("@property")); + } + + (Some(instance), _) => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, function, instance, + )), + )); + } + + (None, _) => {} } } - }) - } + } + } - // 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 => { - let overloads = CallableSignature::dynamic(self); - let binding = CallableBinding::bind(db, arguments, &overloads, self); - binding.into_outcome() - } + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsEquivalentTo) => + { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_equivalent_to(db, *ty_b), + )); + } + } - Type::Union(union) => { - Bindings::try_call_union(db, union, |element| element.try_call(db, arguments)) - } + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsSubtypeOf) => + { + if let [ty_a, ty_b] = overload.parameter_types() { + overload + .set_return_type(Type::BooleanLiteral(ty_a.is_subtype_of(db, *ty_b))); + } + } - Type::Intersection(_) => { - let overloads = CallableSignature::todo("Type::Intersection.call()"); - let binding = CallableBinding::bind(db, arguments, &overloads, self); - binding.into_outcome() + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsAssignableTo) => + { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_assignable_to(db, *ty_b), + )); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsDisjointFrom) => + { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_disjoint_from(db, *ty_b), + )); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsGradualEquivalentTo) => + { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_gradual_equivalent_to(db, *ty_b), + )); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsFullyStatic) => + { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsSingleton) => + { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::IsSingleValued) => + { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); + } + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::Len) => + { + if let [first_arg] = overload.parameter_types() { + if let Some(len_ty) = first_arg.len(db) { + overload.set_return_type(len_ty); + } + }; + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::Repr) => + { + if let [first_arg] = overload.parameter_types() { + overload.set_return_type(first_arg.repr(db)); + }; + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::Cast) => + { + // TODO: Use `.parameter_types()` exclusively when overloads are supported. + if let Some(casted_ty) = arguments.first_argument() { + if let [_, _] = overload.parameter_types() { + overload.set_return_type(casted_ty); + } + }; + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::Overload) => + { + overload.set_return_type(todo_type!("overload(..) return type")); + } + + Type::FunctionLiteral(function_type) + if function_type.is_known(db, KnownFunction::GetattrStatic) => + { + let [instance_ty, attr_name, default] = overload.parameter_types() else { + continue; + }; + + let Some(attr_name) = attr_name.into_string_literal() else { + continue; + }; + + let default = if default.is_unknown() { + Type::Never + } else { + *default + }; + + let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); + + // TODO: we could emit a diagnostic here (if default is not set) + overload.set_return_type( + match instance_ty.static_member(db, attr_name.value(db)) { + Symbol::Type(ty, Boundness::Bound) => { + if instance_ty.is_fully_static(db) { + ty + } else { + // Here, we attempt to model the fact that an attribute lookup on + // a non-fully static type could fail. This is an approximation, + // as there are gradual types like `tuple[Any]`, on which a lookup + // of (e.g. of the `index` method) would always succeed. + + union_with_default(ty) + } + } + Symbol::Type(ty, Boundness::PossiblyUnbound) => union_with_default(ty), + Symbol::Unbound => default, + }, + ); + } + + Type::ClassLiteral(ClassLiteralType { class }) + if class.is_known(db, KnownClass::Bool) => + { + overload.set_return_type( + arguments + .first_argument() + .map(|arg| arg.bool(db).into_type(db)) + .unwrap_or(Type::BooleanLiteral(false)), + ); + } + + Type::ClassLiteral(ClassLiteralType { class }) + if class.is_known(db, KnownClass::Str) && overload_index == 0 => + { + overload.set_return_type( + arguments + .first_argument() + .map(|arg| arg.str(db)) + .unwrap_or_else(|| Type::string_literal(db, "")), + ); + } + + Type::ClassLiteral(ClassLiteralType { class }) + if class.is_known(db, KnownClass::Type) && overload_index == 0 => + { + if let Some(arg) = arguments.first_argument() { + overload.set_return_type(arg.to_meta_type(db)); + } + } + + // Not a special case + _ => {} } + } - _ => Err(CallError::NotCallable { - not_callable_type: self, - }), + Ok(bindings) + } + + /// Looks up a dunder method on the meta-type of `self` and returns its signature and + /// boundness. Returns `None` if the meta-type does not contain the dunder method. + fn dunder_signature( + self, + db: &'db dyn Db, + callable_ty: Option>, + name: &str, + ) -> Option<(Signatures<'db>, Boundness)> { + match self + .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) + .symbol + { + Symbol::Type(dunder_callable, boundness) => { + let callable_ty = callable_ty.unwrap_or(dunder_callable); + Some((dunder_callable.signatures(db, callable_ty), boundness)) + } + Symbol::Unbound => None, } } @@ -2920,21 +2932,14 @@ impl<'db> Type<'db> { name: &str, arguments: &CallArguments<'_, 'db>, ) -> Result, CallDunderError<'db>> { - match self - .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) - .symbol - { - Symbol::Type(dunder_callbable, boundness) => { - let result = dunder_callbable.try_call(db, arguments)?; - - if boundness == Boundness::Bound { - Ok(result) - } else { - Err(CallDunderError::PossiblyUnbound(result)) - } - } - Symbol::Unbound => Err(CallDunderError::MethodNotAvailable), + let (signature, boundness) = self + .dunder_signature(db, None, name) + .ok_or(CallDunderError::MethodNotAvailable)?; + let bindings = Bindings::bind(db, &signature, arguments).into_result()?; + if boundness == Boundness::PossiblyUnbound { + return Err(CallDunderError::PossiblyUnbound(bindings)); } + Ok(bindings) } /// Returns the element type when iterating over `self`. @@ -3793,7 +3798,7 @@ impl<'db> IterationError<'db> { enum IterationErrorKind<'db> { /// The object being iterated over has a bound `__iter__` method, /// but calling it with the expected arguments results in an error. - IterCallError(CallError<'db>), + IterCallError(CallError>), /// The object being iterated over has a bound `__iter__` method that can be called /// with the expected types, but it returns an object that is not a valid iterator. @@ -3886,43 +3891,44 @@ impl<'db> IterationErrorKind<'db> { match self { Self::IterCallError(dunder_iter_call_error) => match dunder_iter_call_error { - CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!( + CallError::NotCallable(bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_iter_type = not_callable_type.display(db), + dunder_iter_type = bindings.ty.display(db), )), - CallError::PossiblyUnboundDunderCall { called_type, .. } => { + CallError::PossiblyNotCallable(bindings) if bindings.is_single() => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = called_type.display(db), + dunder_iter_type = bindings.ty.display(db), )); } - CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => { + CallError::PossiblyNotCallable(bindings) => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = union_call_error.called_type.display(db), + dunder_iter_type = bindings.ty.display(db), )); } - CallError::BindingError { .. } => report_not_iterable(format_args!( + CallError::BindingError(bindings) + if bindings.len() == 1 => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method has an invalid signature \ (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), )), - CallError::Union(UnionCallError { called_type, .. }) => report_not_iterable(format_args!( + CallError::BindingError(bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), - dunder_iter_type = called_type.display(db), + dunder_iter_type = bindings.ty.display(db), )), } @@ -3945,37 +3951,28 @@ impl<'db> IterationErrorKind<'db> { iterator_type = iterator.display(db), )), CallDunderError::Call(dunder_next_call_error) => match dunder_next_call_error { - CallError::NotCallable { .. } => report_not_iterable(format_args!( + CallError::NotCallable(_) => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that is not callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!( + CallError::PossiblyNotCallable(_) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that may not be callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => { - report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` method returns an object of type `{iterator_type}`, \ - which has a `__next__` attribute that may not be callable", - iterable_type = iterable_type.display(db), - iterator_type = iterator.display(db), - )); - } - CallError::BindingError { .. } => report_not_iterable(format_args!( + CallError::BindingError(bindings) if bindings.len() == 1 => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has an invalid `__next__` method (expected `def __next__(self): ...`)", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::Union(_) => report_not_iterable(format_args!( + CallError::BindingError(_) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which may have an invalid `__next__` method (expected `def __next__(self): ...`)", @@ -4000,31 +3997,31 @@ impl<'db> IterationErrorKind<'db> { iterable_type.display(db) )), CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!( + CallError::NotCallable(bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = not_callable_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!( + CallError::PossiblyNotCallable(bindings) if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute may not be callable", iterable_type = iterable_type.display(db), )), - CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => { + CallError::PossiblyNotCallable(bindings) => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = union_call_error.called_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError { .. } => report_not_iterable(format_args!( + CallError::BindingError(bindings) if bindings.len() == 1 => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method has an incorrect signature \ @@ -4033,7 +4030,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::Union(UnionCallError {called_type, ..})=> report_not_iterable(format_args!( + CallError::BindingError(bindings)=> report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method (with type `{dunder_getitem_type}`) @@ -4041,7 +4038,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = called_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )), } } @@ -4058,30 +4055,30 @@ impl<'db> IterationErrorKind<'db> { iterable_type.display(db) )), CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable { not_callable_type } => report_not_iterable(format_args!( + CallError::NotCallable(bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = not_callable_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyUnboundDunderCall { .. } => report_not_iterable(format_args!( + CallError::PossiblyNotCallable(bindings) if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ may not be callable", iterable_type = iterable_type.display(db), )), - CallError::Union(union_call_error) if union_call_error.indicates_type_possibly_not_callable() => { + CallError::PossiblyNotCallable(bindings) => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = union_call_error.called_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError { .. } => report_not_iterable(format_args!( + CallError::BindingError(bindings) if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` method has an incorrect signature \ @@ -4090,7 +4087,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::Union(UnionCallError { called_type, .. }) => report_not_iterable(format_args!( + CallError::BindingError(bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and \ its `__getitem__` method (with type `{dunder_getitem_type}`) \ @@ -4098,7 +4095,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = called_type.display(db), + dunder_getitem_type = bindings.ty.display(db), )), } } @@ -4340,8 +4337,8 @@ impl<'db> FunctionType<'db> { /// Were this not a salsa query, then the calling query /// would depend on the function's AST and rerun for every change in that file. #[salsa::tracked(return_ref)] - pub fn signature(self, db: &'db dyn Db) -> CallableSignature<'db> { - let internal_signature = self.internal_signature(db).into(); + pub fn signature(self, db: &'db dyn Db) -> Signature<'db> { + let internal_signature = self.internal_signature(db); let decorators = self.decorators(db); let mut decorators = decorators.iter(); @@ -4353,7 +4350,7 @@ impl<'db> FunctionType<'db> { { internal_signature } else { - CallableSignature::todo("return type of decorated function") + Signature::todo("return type of decorated function") } } else { internal_signature diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index d26009aaed7ce..c1ea557225a37 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -1,59 +1,44 @@ use super::context::InferContext; -use super::{CallableSignature, Signature, Type}; -use crate::types::UnionType; +use super::{CallableSignature, Signature, Signatures, Type}; use crate::Db; mod arguments; mod bind; pub(super) use arguments::{Argument, CallArguments}; -pub(super) use bind::{Bindings, CallableBinding}; +pub(super) use bind::Bindings; /// The reason why calling a type failed. #[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum CallError<'db> { +pub(super) enum CallError { /// The type is not callable. - NotCallable { - /// The type that can't be called. - not_callable_type: Type<'db>, - }, - - /// A call to a union failed because at least one variant - /// can't be called with the given arguments. - /// - /// A union where all variants are not callable is represented as a `NotCallable` error. - Union(UnionCallError<'db>), - - /// The type has a `__call__` method but it isn't always bound. - PossiblyUnboundDunderCall { - called_type: Type<'db>, - outcome: Box>, - }, + NotCallable(T), /// The type is callable but not with the given arguments. - BindingError { binding: CallableBinding<'db> }, + BindingError(T), + + /// The type is possibly not callable, but there are no binding errors in the situations where + /// it is callable. + PossiblyNotCallable(T), } -impl<'db> CallError<'db> { +impl<'db> CallError> { + pub(super) fn bindings(&self) -> &Bindings<'db> { + match self { + CallError::NotCallable(bindings) + | CallError::BindingError(bindings) + | CallError::PossiblyNotCallable(bindings) => bindings, + } + } + /// Returns a fallback return type to use that best approximates the return type of the call. /// /// Returns `None` if the type isn't callable. pub(super) fn return_type(&self, db: &'db dyn Db) -> Option> { - match self { - CallError::NotCallable { .. } => None, - // If some variants are callable, and some are not, return the union of the return types of the callable variants - // combined with `Type::Unknown` - CallError::Union(UnionCallError { - bindings, errors, .. - }) => Some(UnionType::from_elements( - db, - bindings - .iter() - .map(CallableBinding::return_type) - .chain(errors.iter().map(|err| err.fallback_return_type(db))), - )), - Self::PossiblyUnboundDunderCall { outcome, .. } => Some(outcome.return_type(db)), - Self::BindingError { binding } => Some(binding.return_type()), + let bindings = self.bindings(); + if bindings.is_not_callable() { + return None; } + Some(bindings.return_type(db)) } /// Returns the return type of the call or a fallback that @@ -62,7 +47,7 @@ impl<'db> CallError<'db> { /// /// If the type is not callable, returns `Type::Unknown`. pub(super) fn fallback_return_type(&self, db: &'db dyn Db) -> Type<'db> { - self.return_type(db).unwrap_or(Type::unknown()) + self.bindings().return_type(db) } /// The resolved type that was not callable. @@ -70,52 +55,7 @@ impl<'db> CallError<'db> { /// For unions, returns the union type itself, which may contain a mix of callable and /// non-callable types. pub(super) fn called_type(&self) -> Type<'db> { - match self { - Self::NotCallable { - not_callable_type, .. - } => *not_callable_type, - Self::Union(UnionCallError { called_type, .. }) - | Self::PossiblyUnboundDunderCall { called_type, .. } => *called_type, - Self::BindingError { binding } => binding.callable_type(), - } - } - - pub(super) const fn is_not_callable(&self) -> bool { - matches!(self, Self::NotCallable { .. }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) struct UnionCallError<'db> { - /// The variants that can't be called with the given arguments. - pub(super) errors: Box<[CallError<'db>]>, - - /// The bindings for the callable variants (that have no binding errors). - pub(super) bindings: Box<[CallableBinding<'db>]>, - - /// The union type that we tried calling. - pub(super) called_type: Type<'db>, -} - -impl UnionCallError<'_> { - /// Return `true` if this `UnionCallError` indicates that the union might not be callable at all. - /// Otherwise, return `false`. - /// - /// For example, the union type `Callable[[int], int] | None` may not be callable at all, - /// because the `None` element in this union has no `__call__` method. Calling an object that - /// inhabited this union type would lead to a `UnionCallError` that would indicate that the - /// union might not be callable at all. - /// - /// On the other hand, the union type `Callable[[int], int] | Callable[[str], str]` is always - /// *callable*, but it would still lead to a `UnionCallError` if an inhabitant of this type was - /// called with a single `int` argument passed in. That's because the second element in the - /// union doesn't accept an `int` when it's called: it only accepts a `str`. - pub(crate) fn indicates_type_possibly_not_callable(&self) -> bool { - self.errors.iter().any(|error| match error { - CallError::BindingError { .. } => false, - CallError::NotCallable { .. } | CallError::PossiblyUnboundDunderCall { .. } => true, - CallError::Union(union_error) => union_error.indicates_type_possibly_not_callable(), - }) + self.bindings().ty } } @@ -124,7 +64,7 @@ pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// /// This includes non-callable dunder attributes that are possibly unbound. - Call(CallError<'db>), + Call(CallError>), /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors @@ -149,8 +89,8 @@ impl<'db> CallDunderError<'db> { } } -impl<'db> From> for CallDunderError<'db> { - fn from(error: CallError<'db>) -> Self { +impl<'db> From>> for CallDunderError<'db> { + fn from(error: CallError>) -> Self { Self::Call(error) } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index f68914a4de5a2..ceac2a7861cc3 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -3,17 +3,20 @@ //! [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 std::borrow::Cow; + use super::{ - Argument, CallArguments, CallError, CallableSignature, InferContext, Signature, Type, - UnionCallError, + Argument, CallArguments, CallError, CallableSignature, InferContext, Signature, Signatures, + Type, }; use crate::db::Db; +use crate::symbol::Boundness; use crate::types::diagnostic::{ - INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, - TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, + CALL_NON_CALLABLE, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, + PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, }; use crate::types::signatures::Parameter; -use crate::types::{CallableType, UnionType}; +use crate::types::{CallableType, UnionBuilder, UnionType}; use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -23,7 +26,13 @@ use ruff_text_size::Ranged; /// /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum Bindings<'db> { +pub(crate) struct Bindings<'db> { + pub(crate) ty: Type<'db>, + inner: BindingsInner<'db>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum BindingsInner<'db> { /// The call resolves to exactly one binding. Single(CallableBinding<'db>), @@ -32,64 +41,184 @@ pub(crate) enum Bindings<'db> { } impl<'db> Bindings<'db> { - /// Calls each union element using the provided `call` function. + /// Binds the arguments of a call site against a signature. /// - /// Returns `Ok` if all variants can be called without error according to the callback and `Err` otherwise. - pub(crate) fn try_call_union( + /// The returned bindings provide the return type of the call, the bound types for all + /// parameters, and any errors resulting from binding the call, all for each union element and + /// overload (if any). + pub(crate) fn bind( db: &'db dyn Db, - union: UnionType<'db>, - call: F, - ) -> Result> - where - F: Fn(Type<'db>) -> Result>, - { - let elements = union.elements(db); - let mut bindings = Vec::with_capacity(elements.len()); - let mut errors = Vec::new(); - let mut all_errors_not_callable = true; - - for element in elements { - match call(*element) { - Ok(Bindings::Single(binding)) => bindings.push(binding), - Ok(Bindings::Union(inner_bindings)) => { - bindings.extend(inner_bindings); + signatures: &Signatures<'db>, + arguments: &CallArguments<'_, 'db>, + ) -> Self { + if let Some(signature) = signatures.as_single() { + return Bindings { + ty: signatures.ty, + inner: BindingsInner::Single(CallableBinding::bind(db, signature, arguments)), + }; + } + + let bindings = signatures + .iter() + .map(|signature| CallableBinding::bind(db, signature, arguments)) + .collect::>() + .into_boxed_slice(); + Bindings { + ty: signatures.ty, + inner: BindingsInner::Union(bindings), + } + } + + pub(crate) fn is_single(&self) -> bool { + matches!(&self.inner, BindingsInner::Single(_)) + } + + /// The type returned by this call. + pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { + match &self.inner { + BindingsInner::Single(binding) => binding.return_type(), + BindingsInner::Union(bindings) => { + // The return types from successfully bound elements should come first, then the + // fallback return types for any bindings with errors. + let mut builder = UnionBuilder::new(db); + for binding in bindings.iter() { + if !binding.has_binding_errors() { + builder = builder.add(binding.return_type()); + } } - Err(error) => { - all_errors_not_callable &= error.is_not_callable(); - errors.push(error); + for binding in bindings.iter() { + if binding.has_binding_errors() { + builder = builder.add(binding.return_type()); + } } + builder.build() } } + } + + /// Returns whether all bindings were successful, or an error describing why some bindings were + /// unsuccessful. + pub(crate) fn as_result(&self) -> Result<(), CallError<()>> { + // In order of precedence: + // + // - If every union element is Ok, then the union is too. + // - If any element has a BindingError, the union has a BindingError. + // - If every element is NotCallable, then the union is also NotCallable. + // - Otherwise, the elements are some mixture of Ok, NotCallable, and PossiblyNotCallable. + // The union as a whole is PossiblyNotCallable. + // + // For example, the union type `Callable[[int], int] | None` may not be callable at all, + // because the `None` element in this union has no `__call__` method. + // + // On the other hand, the union type `Callable[[int], int] | Callable[[str], str]` is + // always *callable*, but it would produce a `BindingError` if an inhabitant of this type + // was called with a single `int` argument passed in. That's because the second element in + // the union doesn't accept an `int` when it's called: it only accepts a `str`. + let mut all_ok = true; + let mut any_binding_error = false; + let mut all_not_callable = true; + for binding in self.bindings() { + let result = binding.as_result(); + all_ok &= matches!(result, Ok(_)); + any_binding_error |= matches!(result, Err(CallError::BindingError(()))); + all_not_callable &= matches!(result, Err(CallError::NotCallable(()))); + } - if errors.is_empty() { - Ok(Bindings::Union(bindings.into())) - } else if bindings.is_empty() && all_errors_not_callable { - Err(CallError::NotCallable { - not_callable_type: Type::Union(union), - }) + if all_ok { + Ok(()) + } else if any_binding_error { + Err(CallError::BindingError(())) + } else if all_not_callable { + Err(CallError::NotCallable(())) } else { - Err(CallError::Union(UnionCallError { - errors: errors.into(), - bindings: bindings.into(), - called_type: Type::Union(union), - })) + Err(CallError::PossiblyNotCallable(())) } } - /// The type returned by this call. - pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { - match self { - Self::Single(binding) => binding.return_type(), - Self::Union(bindings) => { - UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) - } + /// Wraps a successful binding in `Ok`, or returns an error describing why the binding was + /// unsuccessful. + pub(crate) fn into_result(self) -> Result> { + match self.as_result() { + Ok(()) => Ok(self), + Err(CallError::NotCallable(())) => Err(CallError::NotCallable(self)), + Err(CallError::BindingError(())) => Err(CallError::BindingError(self)), + Err(CallError::PossiblyNotCallable(())) => Err(CallError::PossiblyNotCallable(self)), } } + pub(crate) fn len(&self) -> usize { + match &self.inner { + BindingsInner::Single(_) => 1, + BindingsInner::Union(bindings) => bindings.len(), + } + } + + /// Returns whether this binding is callable. A union type is callable if _all_ of its elements + /// are callable. + pub(crate) fn is_not_callable(&self) -> bool { + self.bindings().iter().all(|b| !b.is_callable()) + } + + /// Returns whether there were any errors binding this call site. If the callable is a union, + /// an error for _any_ element causes the call to fail. + pub(crate) fn has_binding_errors(&self) -> bool { + self.bindings() + .iter() + .any(CallableBinding::has_binding_errors) + } + + /// Returns whether any binding is for an object that is callable via a `__call__` method that + /// is possibly unbound. + pub(crate) fn any_dunder_is_possibly_unbound(&self) -> bool { + self.bindings() + .iter() + .any(CallableBinding::dunder_is_possibly_unbound) + } + pub(crate) fn bindings(&self) -> &[CallableBinding<'db>] { - match self { - Self::Single(binding) => std::slice::from_ref(binding), - Self::Union(bindings) => bindings, + match &self.inner { + BindingsInner::Single(binding) => std::slice::from_ref(binding), + BindingsInner::Union(bindings) => bindings, + } + } + + pub(crate) fn bindings_mut(&mut self) -> &mut [CallableBinding<'db>] { + match &mut self.inner { + BindingsInner::Single(binding) => std::slice::from_mut(binding), + BindingsInner::Union(bindings) => bindings, + } + } + + /// Report diagnostics for all of the errors that occurred when trying to match actual + /// arguments to formal parameters. If the callable is a union, or has multiple overloads, we + /// report a single diagnostic if we couldn't match any union element or overload. + /// TODO: Update this to add subdiagnostics about how we failed to match each union element and + /// overload. + pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { + // If all union elements are not callable, report that the union as a whole is not + // callable. + if self.bindings().iter().all(|b| !b.is_callable()) { + context.report_lint( + &CALL_NON_CALLABLE, + node, + format_args!( + "Object of type `{}` is not callable", + self.ty.display(context.db()) + ), + ); + return; + } + + // TODO: We currently only report errors for the first union element. Ideally, we'd report + // an error saying that the union type can't be called, followed by subdiagnostics + // explaining why. + if let Some(first) = self + .bindings() + .iter() + .filter(|b| b.as_result().is_err()) + .next() + { + first.report_diagnostics(context, node); } } } @@ -111,49 +240,105 @@ impl<'db> Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallableBinding<'db> { - /// Type of the callable object (function, class...) - callable_ty: Type<'db>, + pub(crate) ty: Type<'db>, + pub(crate) dunder_call_boundness: Option, + inner: CallableBindingInner<'db>, +} - overloads: Box<[Binding<'db>]>, +#[derive(Debug, Clone, PartialEq, Eq)] +enum CallableBindingInner<'db> { + NotCallable, + Single(Binding<'db>), + Overloaded(Box<[Binding<'db>]>), } impl<'db> CallableBinding<'db> { /// Bind a [`CallArguments`] against a [`CallableSignature`]. /// - /// The returned [`CallableBinding`] provides the return type of the call, the bound types for all - /// parameters, and any errors resulting from binding the call. - pub(crate) fn bind( + /// The returned [`CallableBinding`] provides the return type of the call, the bound types for + /// all parameters, and any errors resulting from binding the call. + fn bind( db: &'db dyn Db, + signature: &CallableSignature<'db>, arguments: &CallArguments<'_, 'db>, - overloads: &CallableSignature<'db>, - callable_ty: Type<'db>, ) -> Self { + if !signature.is_callable() { + return CallableBinding { + ty: signature.ty, + dunder_call_boundness: signature.dunder_call_boundness, + inner: CallableBindingInner::NotCallable, + }; + } + + // If this callable is a bound method, prepend the self instance onto the arguments list + // before checking. + let arguments = if let Some(bound_type) = signature.bound_type { + Cow::Owned(arguments.with_self(bound_type)) + } else { + Cow::Borrowed(arguments) + }; + + if let Some(single) = signature.as_single() { + let binding = Binding::bind(db, single, arguments.as_ref()); + return CallableBinding { + ty: signature.ty, + dunder_call_boundness: signature.dunder_call_boundness, + inner: CallableBindingInner::Single(binding), + }; + } + // 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 // the matching overloads. Make sure to implement that as part of separating call binding into // two phases. // // [1] https://github.com/python/typing/pull/1839 - let overloads = overloads + let overloads = signature .iter() - .map(|signature| Binding::bind(db, arguments, signature)) + .map(|signature| Binding::bind(db, signature, arguments.as_ref())) .collect::>() .into_boxed_slice(); CallableBinding { - callable_ty, - overloads, + ty: signature.ty, + dunder_call_boundness: signature.dunder_call_boundness, + inner: CallableBindingInner::Overloaded(overloads), + } + } + + fn overloads(&self) -> &[Binding<'db>] { + match &self.inner { + CallableBindingInner::NotCallable => &[], + CallableBindingInner::Single(binding) => std::slice::from_ref(binding), + CallableBindingInner::Overloaded(bindings) => bindings, + } + } + + fn overloads_mut(&mut self) -> &mut [Binding<'db>] { + match &mut self.inner { + CallableBindingInner::NotCallable => &mut [], + CallableBindingInner::Single(binding) => std::slice::from_mut(binding), + CallableBindingInner::Overloaded(bindings) => bindings, } } - pub(crate) fn into_outcome(self) -> Result, CallError<'db>> { + fn as_result(&self) -> Result<(), CallError<()>> { + if matches!(self.inner, CallableBindingInner::NotCallable) { + return Err(CallError::NotCallable(())); + } + if self.has_binding_errors() { - return Err(CallError::BindingError { binding: self }); + return Err(CallError::BindingError(())); + } + + if self.dunder_is_possibly_unbound() { + return Err(CallError::PossiblyNotCallable(())); } - Ok(Bindings::Single(self)) + + Ok(()) } - pub(crate) fn callable_type(&self) -> Type<'db> { - self.callable_ty + fn is_callable(&self) -> bool { + !matches!(&self.inner, CallableBindingInner::NotCallable) } /// Returns whether there were any errors binding this call site. If the callable has multiple @@ -162,22 +347,28 @@ impl<'db> CallableBinding<'db> { self.matching_overload().is_none() } + /// Returns whether this binding is for an object that is callable via a `__call__` method that + /// is possibly unbound. + pub(crate) fn dunder_is_possibly_unbound(&self) -> bool { + matches!(self.dunder_call_boundness, Some(Boundness::PossiblyUnbound)) + } + /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. pub(crate) fn matching_overload(&self) -> Option<(usize, &Binding<'db>)> { - self.overloads + self.overloads() .iter() .enumerate() - .find(|(_, overload)| !overload.has_binding_errors()) + .find(|(_, overload)| overload.as_result().is_ok()) } /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. pub(crate) fn matching_overload_mut(&mut self) -> Option<(usize, &mut Binding<'db>)> { - self.overloads + self.overloads_mut() .iter_mut() .enumerate() - .find(|(_, overload)| !overload.has_binding_errors()) + .find(|(_, overload)| overload.as_result().is_ok()) } /// Returns the return type of this call. For a valid call, this is the return type of the @@ -189,47 +380,39 @@ impl<'db> CallableBinding<'db> { if let Some((_, overload)) = self.matching_overload() { return overload.return_type(); } - if let [overload] = self.overloads.as_ref() { + if let [overload] = self.overloads().as_ref() { return overload.return_type(); } Type::unknown() } - fn callable_descriptor(&self, db: &'db dyn Db) -> Option { - match self.callable_ty { - Type::FunctionLiteral(function) => Some(CallableDescriptor { - kind: "function", - name: function.name(db), - }), - Type::ClassLiteral(class_type) => Some(CallableDescriptor { - kind: "class", - name: class_type.class().name(db), - }), - Type::Callable(CallableType::BoundMethod(bound_method)) => Some(CallableDescriptor { - kind: "bound method", - name: bound_method.function(db).name(db), - }), - Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { - Some(CallableDescriptor { - kind: "method wrapper `__get__` of function", - name: function.name(db), - }) - } - Type::Callable(CallableType::WrapperDescriptorDunderGet) => Some(CallableDescriptor { - kind: "wrapper descriptor", - name: "FunctionType.__get__", - }), - _ => None, + fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { + if !self.is_callable() { + context.report_lint( + &CALL_NON_CALLABLE, + node, + format_args!( + "Object of type `{}` is not callable", + self.ty.display(context.db()), + ), + ); + return; } - } - /// Report diagnostics for all of the errors that occurred when trying to match actual - /// arguments to formal parameters. If the callable has multiple overloads, we report a single - /// diagnostic that we couldn't match any overload. - /// TODO: Update this to add subdiagnostics about how we failed to match each overload. - pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { - let callable_descriptor = self.callable_descriptor(context.db()); - if self.overloads.len() > 1 { + if self.dunder_is_possibly_unbound() { + context.report_lint( + &CALL_NON_CALLABLE, + node, + format_args!( + "Object of type `{}` is not callable (possibly unbound `__call__` method)", + self.ty.display(context.db()), + ), + ); + return; + } + + let callable_descriptor = CallableDescriptor::new(context.db(), self.ty); + if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, node, @@ -245,13 +428,8 @@ impl<'db> CallableBinding<'db> { return; } - for overload in &self.overloads { - overload.report_diagnostics( - context, - node, - self.callable_ty, - callable_descriptor.as_ref(), - ); + for overload in self.overloads() { + overload.report_diagnostics(context, node, self.ty, callable_descriptor.as_ref()); } } } @@ -266,14 +444,14 @@ pub(crate) struct Binding<'db> { parameter_tys: Box<[Type<'db>]>, /// Call binding errors, if any. - errors: Vec>, + errors: Vec>, } impl<'db> Binding<'db> { fn bind( db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, signature: &Signature<'db>, + arguments: &CallArguments<'_, 'db>, ) -> Self { let parameters = signature.parameters(); // The type assigned to each parameter at this call site. @@ -316,7 +494,7 @@ impl<'db> Binding<'db> { .keyword_by_name(name) .or_else(|| parameters.keyword_variadic()) else { - errors.push(CallableBindingError::UnknownArgument { + errors.push(BindingError::UnknownArgument { argument_name: ast::name::Name::new(name), argument_index: get_argument_index(argument_index, num_synthetic_args), }); @@ -332,7 +510,7 @@ impl<'db> Binding<'db> { }; if let Some(expected_ty) = parameter.annotated_type() { if !argument_ty.is_assignable_to(db, expected_ty) { - errors.push(CallableBindingError::InvalidArgumentType { + errors.push(BindingError::InvalidArgumentType { parameter: ParameterContext::new(parameter, index, positional), argument_index: get_argument_index(argument_index, num_synthetic_args), expected_ty, @@ -345,7 +523,7 @@ impl<'db> Binding<'db> { let union = UnionType::from_elements(db, [existing, *argument_ty]); parameter_tys[index].replace(union); } else { - errors.push(CallableBindingError::ParameterAlreadyAssigned { + errors.push(BindingError::ParameterAlreadyAssigned { argument_index: get_argument_index(argument_index, num_synthetic_args), parameter: ParameterContext::new(parameter, index, positional), }); @@ -353,7 +531,7 @@ impl<'db> Binding<'db> { } } if let Some(first_excess_argument_index) = first_excess_positional { - errors.push(CallableBindingError::TooManyPositionalArguments { + errors.push(BindingError::TooManyPositionalArguments { first_excess_argument_index: get_argument_index( first_excess_argument_index, num_synthetic_args, @@ -378,7 +556,7 @@ impl<'db> Binding<'db> { } if !missing.is_empty() { - errors.push(CallableBindingError::MissingArguments { + errors.push(BindingError::MissingArguments { parameters: ParameterContexts(missing), }); } @@ -417,8 +595,11 @@ impl<'db> Binding<'db> { } } - pub(crate) fn has_binding_errors(&self) -> bool { - !self.errors.is_empty() + fn as_result(&self) -> Result<(), CallError<()>> { + if !self.errors.is_empty() { + return Err(CallError::BindingError(())); + } + Ok(()) } } @@ -429,6 +610,36 @@ pub(crate) struct CallableDescriptor<'a> { kind: &'a str, } +impl<'db> CallableDescriptor<'db> { + fn new(db: &'db dyn Db, ty: Type<'db>) -> Option> { + match ty { + Type::FunctionLiteral(function) => Some(CallableDescriptor { + kind: "function", + name: function.name(db), + }), + Type::ClassLiteral(class_type) => Some(CallableDescriptor { + kind: "class", + name: class_type.class().name(db), + }), + Type::Callable(CallableType::BoundMethod(bound_method)) => Some(CallableDescriptor { + kind: "bound method", + name: bound_method.function(db).name(db), + }), + Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { + Some(CallableDescriptor { + kind: "method wrapper `__get__` of function", + name: function.name(db), + }) + } + Type::Callable(CallableType::WrapperDescriptorDunderGet) => Some(CallableDescriptor { + kind: "wrapper descriptor", + name: "FunctionType.__get__", + }), + _ => None, + } + } +} + /// Information needed to emit a diagnostic regarding a parameter. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct ParameterContext { @@ -483,7 +694,7 @@ impl std::fmt::Display for ParameterContexts { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum CallableBindingError<'db> { +pub(crate) enum BindingError<'db> { /// The type of an argument is not assignable to the annotated type of its corresponding /// parameter. InvalidArgumentType { @@ -512,7 +723,7 @@ pub(crate) enum CallableBindingError<'db> { }, } -impl<'db> CallableBindingError<'db> { +impl<'db> BindingError<'db> { fn parameter_span_from_index( db: &'db dyn Db, callable_ty: Type<'db>, diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 97bb9e6373630..a7b40be6857a8 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -12,7 +12,7 @@ use crate::{ }, types::{ definition_expression_type, CallArguments, CallError, DynamicType, MetaclassCandidate, - TupleType, UnionBuilder, UnionCallError, UnionType, + TupleType, UnionBuilder, UnionType, }, Db, KnownModule, Program, }; @@ -235,55 +235,17 @@ impl<'db> Class<'db> { let return_ty_result = match metaclass.try_call(db, &arguments) { Ok(outcome) => Ok(outcome.return_type(db)), - Err(CallError::NotCallable { not_callable_type }) => Err(MetaclassError { - kind: MetaclassErrorKind::NotCallable(not_callable_type), + Err(CallError::NotCallable(bindings)) => Err(MetaclassError { + kind: MetaclassErrorKind::NotCallable(bindings.ty), }), - Err(CallError::Union(UnionCallError { - called_type, - errors, - bindings, - })) => { - let mut partly_not_callable = false; - - let return_ty = errors - .iter() - .fold(None, |acc, error| { - let ty = error.return_type(db); - - match (acc, ty) { - (acc, None) => { - partly_not_callable = true; - acc - } - (None, Some(ty)) => Some(UnionBuilder::new(db).add(ty)), - (Some(builder), Some(ty)) => Some(builder.add(ty)), - } - }) - .map(|mut builder| { - for binding in bindings { - builder = builder.add(binding.return_type()); - } - - builder.build() - }); - - if partly_not_callable { - Err(MetaclassError { - kind: MetaclassErrorKind::PartlyNotCallable(called_type), - }) - } else { - Ok(return_ty.unwrap_or(Type::unknown())) - } - } + // TODO we should also check for binding errors that would indicate the metaclass + // does not accept the right arguments + Err(CallError::BindingError(bindings)) => Ok(bindings.return_type(db)), - Err(CallError::PossiblyUnboundDunderCall { .. }) => Err(MetaclassError { + Err(CallError::PossiblyNotCallable(_)) => Err(MetaclassError { kind: MetaclassErrorKind::PartlyNotCallable(metaclass), }), - - // TODO we should also check for binding errors that would indicate the metaclass - // does not accept the right arguments - Err(CallError::BindingError { binding }) => Ok(binding.return_type()), }; return return_ty_result.map(|ty| ty.to_meta_type(db)); diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index ba67ef4894334..7e2d14ad5d1c4 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -61,7 +61,7 @@ use crate::symbol::{ module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, Boundness, LookupError, }; -use crate::types::call::{Argument, CallArguments, UnionCallError}; +use crate::types::call::{Argument, CallArguments}; use crate::types::diagnostic::{ report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, @@ -88,7 +88,6 @@ use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; use crate::Db; -use super::call::CallError; use super::class_base::ClassBase; use super::context::{InNoTypeCheck, InferContext, WithDiagnostics}; use super::diagnostic::{ @@ -3551,7 +3550,7 @@ impl<'db> TypeInferenceBuilder<'db> { Ok(outcome) => { for binding in outcome.bindings() { let Some(known_function) = binding - .callable_type() + .ty .into_function_literal() .and_then(|function_type| function_type.known(self.db())) else { @@ -3659,57 +3658,9 @@ impl<'db> TypeInferenceBuilder<'db> { outcome.return_type(self.db()) } Err(err) => { - // TODO: We currently only report the first error. Ideally, we'd report - // an error saying that the union type can't be called, followed by a sub - // diagnostic explaining why. - fn report_call_error( - context: &InferContext, - err: CallError, - call_expression: &ast::ExprCall, - ) { - match err { - CallError::NotCallable { not_callable_type } => { - context.report_lint( - &CALL_NON_CALLABLE, - call_expression, - format_args!( - "Object of type `{}` is not callable", - not_callable_type.display(context.db()) - ), - ); - } - - CallError::Union(UnionCallError { errors, .. }) => { - if let Some(first) = IntoIterator::into_iter(errors).next() { - report_call_error(context, first, call_expression); - } else { - debug_assert!( - false, - "Expected `CalLError::Union` to at least have one error" - ); - } - } - - CallError::PossiblyUnboundDunderCall { called_type, .. } => { - context.report_lint( - &CALL_NON_CALLABLE, - call_expression, - format_args!( - "Object of type `{}` is not callable (possibly unbound `__call__` method)", - called_type.display(context.db()) - ), - ); - } - CallError::BindingError { binding, .. } => { - binding.report_diagnostics(context, call_expression.into()); - } - } - } - - let return_type = err.fallback_return_type(self.db()); - report_call_error(&self.context, err, call_expression); - - return_type + err.bindings() + .report_diagnostics(&self.context, call_expression.into()); + err.fallback_return_type(self.db()) } } } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index eb9105a768d3e..35a8099fbcbf2 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -17,28 +17,158 @@ use crate::types::todo_type; use crate::Db; 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> { + pub(crate) ty: Type<'db>, + inner: SignaturesInner<'db>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] +enum SignaturesInner<'db> { + Single(CallableSignature<'db>), + Union(Box<[CallableSignature<'db>]>), +} + +impl<'db> Signatures<'db> { + pub(crate) fn not_callable(ty: Type<'db>) -> Self { + Self { + ty, + inner: SignaturesInner::Single(CallableSignature::not_callable(ty)), + } + } + + pub(crate) fn single(signature: CallableSignature<'db>) -> Self { + Self { + ty: signature.ty, + inner: SignaturesInner::Single(signature), + } + } + + /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is + /// empty. + pub(crate) fn from_union(ty: Type<'db>, elements: I) -> Self + where + I: IntoIterator, + I::IntoIter: Iterator>, + { + let mut signatures = Vec::new(); + for element in elements { + match element.inner { + SignaturesInner::Single(signature) => signatures.push(signature), + SignaturesInner::Union(union) => signatures.extend(union), + } + } + if signatures.len() == 1 { + let first_signature = signatures.pop().expect("signatures sould have one element"); + return Self { + ty, + inner: SignaturesInner::Single(first_signature), + }; + } + + Self { + ty, + inner: SignaturesInner::Union(signatures.into()), + } + } + + /// Replaces one of the callable types that part of this signature applies to. This is used, + /// for instance, with `__call__` methods, where the signature is for the method, but we want + /// to report errors for the containing object. + pub(crate) fn replace_type(&mut self, before: Type<'db>, after: Type<'db>) { + match &mut self.inner { + SignaturesInner::Single(signature) => signature.replace_type(before, after), + SignaturesInner::Union(signatures) => { + for signature in signatures { + signature.replace_type(before, after); + } + } + } + } + + pub(crate) fn as_single(&self) -> Option<&CallableSignature<'db>> { + match &self.inner { + SignaturesInner::Single(signature) => Some(signature), + SignaturesInner::Union(_) => None, + } + } + + pub(crate) fn iter(&self) -> std::slice::Iter> { + match &self.inner { + SignaturesInner::Single(signature) => std::slice::from_ref(signature).iter(), + SignaturesInner::Union(signatures) => signatures.iter(), + } + } + + /// Returns whether this signature is callable. A union type is callable if _all_ of its + /// elements are callable. + pub(crate) fn is_callable(&self) -> bool { + self.iter().all(CallableSignature::is_callable) + } + + pub(crate) fn set_dunder_call_boundness(&mut self, boundness: Boundness) { + match &mut self.inner { + SignaturesInner::Single(signature) => { + signature.dunder_call_boundness = Some(boundness); + } + SignaturesInner::Union(signatures) => { + for signature in signatures { + signature.dunder_call_boundness = Some(boundness); + } + } + } + } +} + /// 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 enum CallableSignature<'db> { - Single { - signature: Signature<'db>, - /// If this is a callable object (i.e. called via a `__call__` method), the boundness of - /// that call method. - dunder_call: Option, - }, - Overloaded { - overloads: Box<[Signature<'db>]>, - /// If this is a callable object (i.e. called via a `__call__` method), the boundness of - /// that call method. - dunder_call: Option, - }, +pub struct CallableSignature<'db> { + /// Type of the object (function, class...). If the object is callable via a `__call__` method, + /// this is the type of the object, not of the dunder method. + pub(crate) ty: 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_boundness: Option, + + /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. + pub(crate) bound_type: Option>, + + inner: CallableSignatureInner<'db>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] +enum CallableSignatureInner<'db> { + /// The type being called is not callable + NotCallable, + Single(Signature<'db>), + Overloaded(Box<[Signature<'db>]>), } impl<'db> CallableSignature<'db> { - /// Creates a new `CallableSignature` from an non-empty iterator of [`Signature`]s. - /// Panics if the iterator is empty. - pub(crate) fn from_overloads(overloads: I) -> Self + pub(crate) fn not_callable(ty: Type<'db>) -> Self { + Self { + ty, + dunder_call_boundness: None, + bound_type: None, + inner: CallableSignatureInner::NotCallable, + } + } + + pub(crate) fn new(ty: Type<'db>, signature: Signature<'db>) -> Self { + Self { + ty, + dunder_call_boundness: None, + bound_type: None, + inner: CallableSignatureInner::Single(signature), + } + } + + /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if + /// the iterator is empty. + pub(crate) fn from_overloads(ty: Type<'db>, overloads: I) -> Self where I: IntoIterator, I::IntoIter: Iterator>, @@ -46,23 +176,20 @@ impl<'db> CallableSignature<'db> { let mut iter = overloads.into_iter(); let first_overload = iter.next().expect("overloads should not be empty"); let Some(second_overload) = iter.next() else { - return CallableSignature::Single { - signature: first_overload, - dunder_call: None, + return Self { + ty, + dunder_call_boundness: None, + bound_type: None, + inner: CallableSignatureInner::Single(first_overload), }; }; let mut overloads = vec![first_overload, second_overload]; overloads.extend(iter); - CallableSignature::Overloaded { - overloads: overloads.into(), - dunder_call: None, - } - } - - pub(crate) fn iter(&self) -> std::slice::Iter> { - match self { - CallableSignature::Single { signature, .. } => std::slice::from_ref(signature).iter(), - CallableSignature::Overloaded { overloads, .. } => overloads.iter(), + Self { + ty, + dunder_call_boundness: None, + bound_type: None, + inner: CallableSignatureInner::Overloaded(overloads.into()), } } @@ -72,25 +199,44 @@ impl<'db> CallableSignature<'db> { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - signature.into() + Self::new(ty, 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 ty = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), - return_ty: Some(todo_type!(reason)), + return_ty: Some(ty), }; - signature.into() + Self::new(ty, signature) + } + + fn replace_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.ty == before { + self.ty = after; + } + } + + pub(crate) fn iter(&self) -> std::slice::Iter> { + match &self.inner { + CallableSignatureInner::NotCallable => [].iter(), + CallableSignatureInner::Single(signature) => std::slice::from_ref(signature).iter(), + CallableSignatureInner::Overloaded(signatures) => signatures.iter(), + } } -} -impl<'db> From> for CallableSignature<'db> { - fn from(signature: Signature<'db>) -> Self { - CallableSignature::Single { - signature, - dunder_call: None, + /// Returns whether this signature is callable. + pub(crate) fn is_callable(&self) -> bool { + !matches!(&self.inner, CallableSignatureInner::NotCallable) + } + + pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { + match &self.inner { + CallableSignatureInner::NotCallable => None, + CallableSignatureInner::Single(signature) => Some(signature), + CallableSignatureInner::Overloaded(_) => None, } } } @@ -120,6 +266,15 @@ impl<'db> Signature<'db> { } } + /// 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 { + Signature { + parameters: Parameters::todo(), + return_ty: Some(todo_type!(reason)), + } + } + /// Return a typed signature from a function definition. pub(super) fn from_function( db: &'db dyn Db, @@ -805,7 +960,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let expected_sig = func.internal_signature(&db).into(); + let expected_sig = func.internal_signature(&db); // With no decorators, internal and external signature are the same assert_eq!(func.signature(&db), &expected_sig); @@ -826,7 +981,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let expected_sig = CallableSignature::todo("return type of decorated function"); + let expected_sig = Signature::todo("return type of decorated function"); // With no decorators, internal and external signature are the same assert_eq!(func.signature(&db), &expected_sig); From c3a365967817206e4ab2fd04dac7e0e658e352f4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 15:01:50 -0400 Subject: [PATCH 06/57] There we go --- .../mdtest/call/callable_instance.md | 4 +- crates/red_knot_python_semantic/src/types.rs | 14 ++++- .../src/types/call/bind.rs | 12 +++- .../src/types/signatures.rs | 55 +++++++++---------- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 72506fab6979d..5b6bb368797bb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -85,7 +85,7 @@ class C: c = C() -# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 2 (`x`); expected type `int`" +# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 2 (`x`) of bound method `__call__`; expected type `int`" reveal_type(c("foo")) # revealed: int ``` @@ -99,7 +99,7 @@ class C: c = C() -# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`); expected type `int`" +# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`) of bound method `__call__`; expected type `int`" reveal_type(c()) # revealed: int ``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 9b1de74ee4b81..f0b323ac0f839 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2297,7 +2297,7 @@ impl<'db> Type<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let mut signature = CallableSignature::new(callable_ty, signature.clone()); + let mut signature = CallableSignature::new(callable_ty, self, signature.clone()); signature.bound_type = Some(bound_method.self_instance(db)); Signatures::single(signature.into()) } @@ -2319,6 +2319,7 @@ impl<'db> Type<'db> { let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( callable_ty, + self, [ Signature::new( Parameters::new([ @@ -2370,6 +2371,7 @@ impl<'db> Type<'db> { let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( callable_ty, + self, [ Signature::new( Parameters::new([ @@ -2423,6 +2425,7 @@ impl<'db> Type<'db> { Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::new( callable_ty, + self, function_type.signature(db).clone(), )), @@ -2435,6 +2438,7 @@ impl<'db> Type<'db> { // ``` let signature = CallableSignature::new( callable_ty, + self, Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2461,6 +2465,7 @@ impl<'db> Type<'db> { // ``` let signature = CallableSignature::from_overloads( callable_ty, + self, [ Signature::new( Parameters::new([Parameter::new( @@ -2509,6 +2514,7 @@ impl<'db> Type<'db> { // ``` let signature = CallableSignature::from_overloads( callable_ty, + self, [ Signature::new( Parameters::new([Parameter::new( @@ -2548,6 +2554,7 @@ impl<'db> Type<'db> { Type::ClassLiteral(ClassLiteralType { .. }) => { let signature = CallableSignature::new( callable_ty, + self, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); Signatures::single(signature) @@ -2564,7 +2571,7 @@ impl<'db> Type<'db> { let Some((mut signatures, boundness)) = self.dunder_signature(db, Some(callable_ty), "__call__") else { - return Signatures::not_callable(self); + return Signatures::not_callable(callable_ty, self); }; signatures.set_dunder_call_boundness(boundness); signatures @@ -2577,6 +2584,7 @@ impl<'db> Type<'db> { // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( callable_ty, + self, union .elements(db) .iter() @@ -2587,7 +2595,7 @@ impl<'db> Type<'db> { Signatures::single(CallableSignature::todo("Type::Intersection.call()")) } - _ => Signatures::not_callable(self), + _ => Signatures::not_callable(callable_ty, self), } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index ceac2a7861cc3..1fbbfbc619e91 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -241,6 +241,7 @@ impl<'db> Bindings<'db> { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallableBinding<'db> { pub(crate) ty: Type<'db>, + pub(crate) signature_ty: Type<'db>, pub(crate) dunder_call_boundness: Option, inner: CallableBindingInner<'db>, } @@ -265,6 +266,7 @@ impl<'db> CallableBinding<'db> { if !signature.is_callable() { return CallableBinding { ty: signature.ty, + signature_ty: signature.signature_ty, dunder_call_boundness: signature.dunder_call_boundness, inner: CallableBindingInner::NotCallable, }; @@ -282,6 +284,7 @@ impl<'db> CallableBinding<'db> { let binding = Binding::bind(db, single, arguments.as_ref()); return CallableBinding { ty: signature.ty, + signature_ty: signature.signature_ty, dunder_call_boundness: signature.dunder_call_boundness, inner: CallableBindingInner::Single(binding), }; @@ -300,6 +303,7 @@ impl<'db> CallableBinding<'db> { .into_boxed_slice(); CallableBinding { ty: signature.ty, + signature_ty: signature.signature_ty, dunder_call_boundness: signature.dunder_call_boundness, inner: CallableBindingInner::Overloaded(overloads), } @@ -428,8 +432,14 @@ impl<'db> CallableBinding<'db> { return; } + let callable_descriptor = CallableDescriptor::new(context.db(), self.signature_ty); for overload in self.overloads() { - overload.report_diagnostics(context, node, self.ty, callable_descriptor.as_ref()); + overload.report_diagnostics( + context, + node, + self.signature_ty, + callable_descriptor.as_ref(), + ); } } } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 35a8099fbcbf2..681ab416fbc98 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -20,7 +20,11 @@ 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) ty: Type<'db>, + /// For an object that's callable via a `__call__` method, the type of that method. For an + /// object that is directly callable, the type of the object. + pub(crate) signature_ty: Type<'db>, inner: SignaturesInner<'db>, } @@ -31,23 +35,25 @@ enum SignaturesInner<'db> { } impl<'db> Signatures<'db> { - pub(crate) fn not_callable(ty: Type<'db>) -> Self { + pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { Self { ty, - inner: SignaturesInner::Single(CallableSignature::not_callable(ty)), + signature_ty: ty, + inner: SignaturesInner::Single(CallableSignature::not_callable(ty, signature_ty)), } } pub(crate) fn single(signature: CallableSignature<'db>) -> Self { Self { ty: signature.ty, + signature_ty: signature.signature_ty, inner: SignaturesInner::Single(signature), } } /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is /// empty. - pub(crate) fn from_union(ty: Type<'db>, elements: I) -> Self + pub(crate) fn from_union(ty: Type<'db>, signature_ty: Type<'db>, elements: I) -> Self where I: IntoIterator, I::IntoIter: Iterator>, @@ -63,30 +69,18 @@ impl<'db> Signatures<'db> { let first_signature = signatures.pop().expect("signatures sould have one element"); return Self { ty, + signature_ty, inner: SignaturesInner::Single(first_signature), }; } Self { ty, + signature_ty, inner: SignaturesInner::Union(signatures.into()), } } - /// Replaces one of the callable types that part of this signature applies to. This is used, - /// for instance, with `__call__` methods, where the signature is for the method, but we want - /// to report errors for the containing object. - pub(crate) fn replace_type(&mut self, before: Type<'db>, after: Type<'db>) { - match &mut self.inner { - SignaturesInner::Single(signature) => signature.replace_type(before, after), - SignaturesInner::Union(signatures) => { - for signature in signatures { - signature.replace_type(before, after); - } - } - } - } - pub(crate) fn as_single(&self) -> Option<&CallableSignature<'db>> { match &self.inner { SignaturesInner::Single(signature) => Some(signature), @@ -125,10 +119,13 @@ impl<'db> Signatures<'db> { /// [`Signature`] for each overload. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub struct CallableSignature<'db> { - /// Type of the object (function, class...). If the object is callable via a `__call__` method, - /// this is the type of the object, not of the dunder method. + /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, + /// For an object that's callable via a `__call__` method, the type of that method. For an + /// object that is directly callable, the type of the object. + pub(crate) signature_ty: 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_boundness: Option, @@ -148,18 +145,20 @@ enum CallableSignatureInner<'db> { } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(ty: Type<'db>) -> Self { + pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { Self { ty, + signature_ty, dunder_call_boundness: None, bound_type: None, inner: CallableSignatureInner::NotCallable, } } - pub(crate) fn new(ty: Type<'db>, signature: Signature<'db>) -> Self { + pub(crate) fn new(ty: Type<'db>, signature_ty: Type<'db>, signature: Signature<'db>) -> Self { Self { ty, + signature_ty, dunder_call_boundness: None, bound_type: None, inner: CallableSignatureInner::Single(signature), @@ -168,7 +167,7 @@ impl<'db> CallableSignature<'db> { /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if /// the iterator is empty. - pub(crate) fn from_overloads(ty: Type<'db>, overloads: I) -> Self + pub(crate) fn from_overloads(ty: Type<'db>, signature_ty: Type<'db>, overloads: I) -> Self where I: IntoIterator, I::IntoIter: Iterator>, @@ -178,6 +177,7 @@ impl<'db> CallableSignature<'db> { let Some(second_overload) = iter.next() else { return Self { ty, + signature_ty, dunder_call_boundness: None, bound_type: None, inner: CallableSignatureInner::Single(first_overload), @@ -187,6 +187,7 @@ impl<'db> CallableSignature<'db> { overloads.extend(iter); Self { ty, + signature_ty, dunder_call_boundness: None, bound_type: None, inner: CallableSignatureInner::Overloaded(overloads.into()), @@ -199,7 +200,7 @@ impl<'db> CallableSignature<'db> { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::new(ty, signature) + Self::new(ty, ty, signature) } /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo @@ -210,13 +211,7 @@ impl<'db> CallableSignature<'db> { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::new(ty, signature) - } - - fn replace_type(&mut self, before: Type<'db>, after: Type<'db>) { - if self.ty == before { - self.ty = after; - } + Self::new(ty, ty, signature) } pub(crate) fn iter(&self) -> std::slice::Iter> { From 453dbfa5e93aca8b398f7df7b2469f63bdc87cb7 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 15:02:32 -0400 Subject: [PATCH 07/57] Remove unused stuff --- .../src/types/call/bind.rs | 16 ---------------- .../src/types/signatures.rs | 8 +------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 1fbbfbc619e91..319a9a20dad5c 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -159,22 +159,6 @@ impl<'db> Bindings<'db> { self.bindings().iter().all(|b| !b.is_callable()) } - /// Returns whether there were any errors binding this call site. If the callable is a union, - /// an error for _any_ element causes the call to fail. - pub(crate) fn has_binding_errors(&self) -> bool { - self.bindings() - .iter() - .any(CallableBinding::has_binding_errors) - } - - /// Returns whether any binding is for an object that is callable via a `__call__` method that - /// is possibly unbound. - pub(crate) fn any_dunder_is_possibly_unbound(&self) -> bool { - self.bindings() - .iter() - .any(CallableBinding::dunder_is_possibly_unbound) - } - pub(crate) fn bindings(&self) -> &[CallableBinding<'db>] { match &self.inner { BindingsInner::Single(binding) => std::slice::from_ref(binding), diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 681ab416fbc98..6db22a515eb6a 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -95,12 +95,6 @@ impl<'db> Signatures<'db> { } } - /// Returns whether this signature is callable. A union type is callable if _all_ of its - /// elements are callable. - pub(crate) fn is_callable(&self) -> bool { - self.iter().all(CallableSignature::is_callable) - } - pub(crate) fn set_dunder_call_boundness(&mut self, boundness: Boundness) { match &mut self.inner { SignaturesInner::Single(signature) => { @@ -118,7 +112,7 @@ impl<'db> Signatures<'db> { /// 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 struct CallableSignature<'db> { +pub(crate) struct CallableSignature<'db> { /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, From 61a1cea12fe47658f22bcc6b62e359380be46e3d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 15:06:57 -0400 Subject: [PATCH 08/57] clippy --- crates/red_knot_python_semantic/src/types.rs | 2 +- .../src/types/call/bind.rs | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index f0b323ac0f839..ce722dca990dd 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2299,7 +2299,7 @@ impl<'db> Type<'db> { let signature = bound_method.function(db).signature(db); let mut signature = CallableSignature::new(callable_ty, self, signature.clone()); signature.bound_type = Some(bound_method.self_instance(db)); - Signatures::single(signature.into()) + Signatures::single(signature) } Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 319a9a20dad5c..a3f99a08ea9fd 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -81,12 +81,12 @@ impl<'db> Bindings<'db> { // The return types from successfully bound elements should come first, then the // fallback return types for any bindings with errors. let mut builder = UnionBuilder::new(db); - for binding in bindings.iter() { + for binding in bindings { if !binding.has_binding_errors() { builder = builder.add(binding.return_type()); } } - for binding in bindings.iter() { + for binding in bindings { if binding.has_binding_errors() { builder = builder.add(binding.return_type()); } @@ -119,7 +119,7 @@ impl<'db> Bindings<'db> { let mut all_not_callable = true; for binding in self.bindings() { let result = binding.as_result(); - all_ok &= matches!(result, Ok(_)); + all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallError::BindingError(()))); all_not_callable &= matches!(result, Err(CallError::NotCallable(()))); } @@ -196,12 +196,7 @@ impl<'db> Bindings<'db> { // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. - if let Some(first) = self - .bindings() - .iter() - .filter(|b| b.as_result().is_err()) - .next() - { + if let Some(first) = self.bindings().iter().find(|b| b.as_result().is_err()) { first.report_diagnostics(context, node); } } @@ -368,7 +363,7 @@ impl<'db> CallableBinding<'db> { if let Some((_, overload)) = self.matching_overload() { return overload.return_type(); } - if let [overload] = self.overloads().as_ref() { + if let [overload] = self.overloads() { return overload.return_type(); } Type::unknown() From 1266d0f79e0188440ce16340362ffec7ae9ebc23 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 15:07:42 -0400 Subject: [PATCH 09/57] lint --- crates/red_knot_python_semantic/src/types/signatures.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 6db22a515eb6a..330bddc81863f 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -66,7 +66,9 @@ impl<'db> Signatures<'db> { } } if signatures.len() == 1 { - let first_signature = signatures.pop().expect("signatures sould have one element"); + let first_signature = signatures + .pop() + .expect("signatures should have one element"); return Self { ty, signature_ty, From 0bf32e3c6ef5764b41a8c18720e524555cd7f2e1 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 16:33:09 -0400 Subject: [PATCH 10/57] Clean up error types more --- crates/red_knot_python_semantic/src/types.rs | 171 +++++++++--------- .../src/types/call.rs | 61 +------ .../src/types/call/bind.rs | 52 ++---- .../src/types/class.rs | 11 +- .../src/types/infer.rs | 106 +++++------ 5 files changed, 163 insertions(+), 238 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index ce722dca990dd..d8d559fea2486 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1676,13 +1676,14 @@ impl<'db> Type<'db> { let descr_get = self.class_member(db, "__get__".into()).symbol; if let Symbol::Type(descr_get, descr_get_boundness) = descr_get { - let return_ty = descr_get - .try_call(db, &CallArguments::positional([self, instance, owner])) - .map(|outcome| { + let bindings = descr_get.call(db, &CallArguments::positional([self, instance, owner])); + let return_ty = bindings + .as_result() + .map(|()| { if descr_get_boundness == Boundness::Bound { - outcome.return_type(db) + bindings.return_type(db) } else { - UnionType::from_elements(db, [outcome.return_type(db), self]) + UnionType::from_elements(db, [bindings.return_type(db), self]) } }) .ok()?; @@ -2169,26 +2170,24 @@ impl<'db> Type<'db> { Truthiness::Ambiguous } } - Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, - Err(CallDunderError::Call(err)) => match err { - CallError::BindingError(binding) => { - return Err(BoolError::IncorrectArguments { - truthiness: type_to_truthiness(binding.return_type(db)), - not_boolable_type: *instance_ty, - }); - } - CallError::NotCallable(_) => { - return Err(BoolError::NotCallable { - not_boolable_type: *instance_ty, - }); - } - CallError::PossiblyNotCallable(_) => { - return Err(BoolError::Other { - not_boolable_type: *self, - }) - } - }, + Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, + Err(CallDunderError::Call(binding, CallError::BindingError)) => { + return Err(BoolError::IncorrectArguments { + truthiness: type_to_truthiness(binding.return_type(db)), + not_boolable_type: *instance_ty, + }); + } + Err(CallDunderError::Call(_, CallError::NotCallable)) => { + return Err(BoolError::NotCallable { + not_boolable_type: *instance_ty, + }); + } + Err(CallDunderError::Call(_, CallError::PossiblyNotCallable)) => { + return Err(BoolError::Other { + not_boolable_type: *self, + }) + } } } }, @@ -2282,10 +2281,13 @@ impl<'db> Type<'db> { } let return_ty = match self.try_call_dunder(db, "__len__", &CallArguments::none()) { - Ok(outcome) | Err(CallDunderError::PossiblyUnbound(outcome)) => outcome.return_type(db), + Ok(bindings) | Err(CallDunderError::PossiblyUnbound(bindings)) => { + bindings.return_type(db) + } // TODO: emit a diagnostic - Err(err) => err.return_type(db)?, + Err(CallDunderError::MethodNotAvailable) => return None, + Err(CallDunderError::Call(bindings, _)) => bindings.return_type(db), }; non_negative_int_literal(db, return_ty) @@ -2568,6 +2570,10 @@ impl<'db> Type<'db> { }, Type::Instance(_) => { + // Note that for objects that are callable via a `__call__` method, we will get the + // signature of the dunder method, but will pass in the type of the object as the + // "callable type". That ensures that we get errors like "`X` is not callable" + // instead of "`` is not callable". let Some((mut signatures, boundness)) = self.dunder_signature(db, Some(callable_ty), "__call__") else { @@ -2600,25 +2606,16 @@ impl<'db> Type<'db> { } /// Calls `self` - fn try_call( - self, - db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, - ) -> Result, CallError>> { - // Note that for objects that are callable via a `__call__` method, we will get the - // signature of the dunder method, but will pass in the type of the object as the "callable - // type". That ensures that we get errors like "`X` is not callable" instead of "`` is not callable". + fn call(self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>) -> Bindings<'db> { let signatures = self.signatures(db, self); - let mut bindings = Bindings::bind(db, &signatures, arguments).into_result()?; - + let mut bindings = Bindings::bind(db, &signatures, arguments); for binding in bindings.bindings_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case // listed here should have a corresponding clause above in `signatures`. - let (overload_index, overload) = binding - .matching_overload_mut() - .expect("bindings with no error should have a matching overload"); + let Some((overload_index, overload)) = binding.matching_overload_mut() else { + continue; + }; match self { Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { @@ -2907,7 +2904,7 @@ impl<'db> Type<'db> { } } - Ok(bindings) + bindings } /// Looks up a dunder method on the meta-type of `self` and returns its signature and @@ -2940,10 +2937,13 @@ impl<'db> Type<'db> { name: &str, arguments: &CallArguments<'_, 'db>, ) -> Result, CallDunderError<'db>> { - let (signature, boundness) = self - .dunder_signature(db, None, name) - .ok_or(CallDunderError::MethodNotAvailable)?; - let bindings = Bindings::bind(db, &signature, arguments).into_result()?; + let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { + return Err(CallDunderError::MethodNotAvailable); + }; + let bindings = Bindings::bind(db, &signature, arguments); + if let Err(err) = bindings.as_result() { + return Err(CallDunderError::Call(bindings, err)); + } if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(bindings)); } @@ -3039,9 +3039,9 @@ impl<'db> Type<'db> { } // `__iter__` is definitely bound but it can't be called with the expected arguments - Err(CallDunderError::Call(dunder_iter_call_error)) => { - Err(IterationErrorKind::IterCallError(dunder_iter_call_error)) - } + Err(CallDunderError::Call(bindings, dunder_iter_call_error)) => Err( + IterationErrorKind::IterCallError(bindings, dunder_iter_call_error), + ), // There's no `__iter__` method. Try `__getitem__` instead... Err(CallDunderError::MethodNotAvailable) => { @@ -3703,7 +3703,8 @@ impl<'db> ContextManagerErrorKind<'db> { CallDunderError::PossiblyUnbound(call_outcome) => { Some(call_outcome.return_type(db)) } - CallDunderError::Call(call_error) => call_error.return_type(db), + CallDunderError::Call(_, CallError::NotCallable) => None, + CallDunderError::Call(bindings, _) => Some(bindings.return_type(db)), CallDunderError::MethodNotAvailable => None, }, } @@ -3724,7 +3725,7 @@ impl<'db> ContextManagerErrorKind<'db> { // TODO: Use more specific error messages for the different error cases. // E.g. hint toward the union variant that doesn't correctly implement enter, // distinguish between a not callable `__enter__` attribute and a wrong signature. - CallDunderError::Call(_) => format!("it does not correctly implement `{name}`"), + CallDunderError::Call(_, _) => format!("it does not correctly implement `{name}`"), } }; @@ -3739,7 +3740,7 @@ impl<'db> ContextManagerErrorKind<'db> { (CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => { format!("it does not implement `{name_a}` and `{name_b}`") } - (CallDunderError::Call(_), CallDunderError::Call(_)) => { + (CallDunderError::Call(_, _), CallDunderError::Call(_, _)) => { format!("it does not correctly implement `{name_a}` or `{name_b}`") } (_, _) => format!( @@ -3806,7 +3807,7 @@ impl<'db> IterationError<'db> { enum IterationErrorKind<'db> { /// The object being iterated over has a bound `__iter__` method, /// but calling it with the expected arguments results in an error. - IterCallError(CallError>), + IterCallError(Bindings<'db>, CallError), /// The object being iterated over has a bound `__iter__` method that can be called /// with the expected types, but it returns an object that is not a valid iterator. @@ -3846,8 +3847,8 @@ impl<'db> IterationErrorKind<'db> { dunder_next_error, .. } => dunder_next_error.return_type(db), - Self::IterCallError(dunder_iter_call_error) => dunder_iter_call_error - .fallback_return_type(db) + Self::IterCallError(dunder_iter_bindings, _) => dunder_iter_bindings + .return_type(db) .try_call_dunder(db, "__next__", &CallArguments::none()) .map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db))) .unwrap_or_else(|dunder_next_call_error| dunder_next_call_error.return_type(db)), @@ -3863,15 +3864,12 @@ impl<'db> IterationErrorKind<'db> { [*dunder_next_return, dunder_getitem_outcome.return_type(db)], )) } - CallDunderError::Call(dunder_getitem_call_error) => Some( - dunder_getitem_call_error - .return_type(db) - .map(|dunder_getitem_return| { - let elements = [*dunder_next_return, dunder_getitem_return]; - UnionType::from_elements(db, elements) - }) - .unwrap_or(*dunder_next_return), - ), + CallDunderError::Call(_, CallError::NotCallable) => Some(*dunder_next_return), + CallDunderError::Call(dunder_getitem_bindings, _) => { + let dunder_getitem_return = dunder_getitem_bindings.return_type(db); + let elements = [*dunder_next_return, dunder_getitem_return]; + Some(UnionType::from_elements(db, elements)) + } }, Self::UnboundIterAndGetitemError { @@ -3898,15 +3896,15 @@ impl<'db> IterationErrorKind<'db> { // or similar, rather than as part of the same sentence as the error message. match self { - Self::IterCallError(dunder_iter_call_error) => match dunder_iter_call_error { - CallError::NotCallable(bindings) => report_not_iterable(format_args!( + Self::IterCallError(bindings, dunder_iter_call_error) => match dunder_iter_call_error { + CallError::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), dunder_iter_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable(bindings) if bindings.is_single() => { + CallError::PossiblyNotCallable if bindings.is_single() => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ @@ -3915,7 +3913,7 @@ impl<'db> IterationErrorKind<'db> { dunder_iter_type = bindings.ty.display(db), )); } - CallError::PossiblyNotCallable(bindings) => { + CallError::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ @@ -3924,14 +3922,13 @@ impl<'db> IterationErrorKind<'db> { dunder_iter_type = bindings.ty.display(db), )); } - CallError::BindingError(bindings) - if bindings.len() == 1 => report_not_iterable(format_args!( + CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method has an invalid signature \ (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError(bindings) => report_not_iterable(format_args!( + CallError::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", @@ -3958,29 +3955,29 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallDunderError::Call(dunder_next_call_error) => match dunder_next_call_error { - CallError::NotCallable(_) => report_not_iterable(format_args!( + CallDunderError::Call(bindings, dunder_next_call_error) => match dunder_next_call_error { + CallError::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that is not callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::PossiblyNotCallable(_) => report_not_iterable(format_args!( + CallError::PossiblyNotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that may not be callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::BindingError(bindings) if bindings.len() == 1 => report_not_iterable(format_args!( + CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has an invalid `__next__` method (expected `def __next__(self): ...`)", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::BindingError(_) => report_not_iterable(format_args!( + CallError::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which may have an invalid `__next__` method (expected `def __next__(self): ...`)", @@ -4004,8 +4001,8 @@ impl<'db> IterationErrorKind<'db> { because it may not have an `__iter__` method or a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable(bindings) => report_not_iterable(format_args!( + CallDunderError::Call(bindings, dunder_getitem_call_error) => match dunder_getitem_call_error { + CallError::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ @@ -4013,13 +4010,13 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable(bindings) if bindings.is_single() => report_not_iterable(format_args!( + CallError::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute may not be callable", iterable_type = iterable_type.display(db), )), - CallError::PossiblyNotCallable(bindings) => { + CallError::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ @@ -4029,7 +4026,7 @@ impl<'db> IterationErrorKind<'db> { dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError(bindings) if bindings.len() == 1 => report_not_iterable(format_args!( + CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method has an incorrect signature \ @@ -4038,7 +4035,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError(bindings)=> report_not_iterable(format_args!( + CallError::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method (with type `{dunder_getitem_type}`) @@ -4062,8 +4059,8 @@ impl<'db> IterationErrorKind<'db> { and it may not have a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable(bindings) => report_not_iterable(format_args!( + CallDunderError::Call(bindings, dunder_getitem_call_error) => match dunder_getitem_call_error { + CallError::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` attribute has type `{dunder_getitem_type}`, \ @@ -4071,13 +4068,13 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable(bindings) if bindings.is_single() => report_not_iterable(format_args!( + CallError::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ may not be callable", iterable_type = iterable_type.display(db), )), - CallError::PossiblyNotCallable(bindings) => { + CallError::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ @@ -4086,7 +4083,7 @@ impl<'db> IterationErrorKind<'db> { dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError(bindings) if bindings.is_single() => report_not_iterable(format_args!( + CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` method has an incorrect signature \ @@ -4095,7 +4092,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError(bindings) => report_not_iterable(format_args!( + CallError::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and \ its `__getitem__` method (with type `{dunder_getitem_type}`) \ diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index c1ea557225a37..678efc891d3ce 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -9,54 +9,16 @@ pub(super) use bind::Bindings; /// The reason why calling a type failed. #[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum CallError { +pub(super) enum CallError { /// The type is not callable. - NotCallable(T), + NotCallable, /// The type is callable but not with the given arguments. - BindingError(T), + BindingError, /// The type is possibly not callable, but there are no binding errors in the situations where /// it is callable. - PossiblyNotCallable(T), -} - -impl<'db> CallError> { - pub(super) fn bindings(&self) -> &Bindings<'db> { - match self { - CallError::NotCallable(bindings) - | CallError::BindingError(bindings) - | CallError::PossiblyNotCallable(bindings) => bindings, - } - } - - /// Returns a fallback return type to use that best approximates the return type of the call. - /// - /// Returns `None` if the type isn't callable. - pub(super) fn return_type(&self, db: &'db dyn Db) -> Option> { - let bindings = self.bindings(); - if bindings.is_not_callable() { - return None; - } - Some(bindings.return_type(db)) - } - - /// Returns the return type of the call or a fallback that - /// represents the best guess of the return type (e.g. the actual return type even if the - /// dunder is possibly unbound). - /// - /// If the type is not callable, returns `Type::Unknown`. - pub(super) fn fallback_return_type(&self, db: &'db dyn Db) -> Type<'db> { - self.bindings().return_type(db) - } - - /// The resolved type that was not callable. - /// - /// For unions, returns the union type itself, which may contain a mix of callable and - /// non-callable types. - pub(super) fn called_type(&self) -> Type<'db> { - self.bindings().ty - } + PossiblyNotCallable, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -64,7 +26,7 @@ pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// /// This includes non-callable dunder attributes that are possibly unbound. - Call(CallError>), + Call(Bindings<'db>, CallError), /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors @@ -78,9 +40,10 @@ pub(super) enum CallDunderError<'db> { impl<'db> CallDunderError<'db> { pub(super) fn return_type(&self, db: &'db dyn Db) -> Option> { match self { - Self::Call(error) => error.return_type(db), - Self::PossiblyUnbound(call_outcome) => Some(call_outcome.return_type(db)), - Self::MethodNotAvailable => None, + Self::MethodNotAvailable | Self::Call(_, CallError::NotCallable) => None, + Self::Call(bindings, _) | Self::PossiblyUnbound(bindings) => { + Some(bindings.return_type(db)) + } } } @@ -88,9 +51,3 @@ impl<'db> CallDunderError<'db> { self.return_type(db).unwrap_or(Type::unknown()) } } - -impl<'db> From>> for CallDunderError<'db> { - fn from(error: CallError>) -> Self { - Self::Call(error) - } -} diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index a3f99a08ea9fd..21ab224888759 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -73,7 +73,9 @@ impl<'db> Bindings<'db> { matches!(&self.inner, BindingsInner::Single(_)) } - /// The type returned by this call. + /// Returns the return type of the call. For successful calls, this is the actual return type. + /// For calls with binding errors, this is a type that best approximates the return type. For + /// types that are not callable, returns `Type::Unknown`. pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { match &self.inner { BindingsInner::Single(binding) => binding.return_type(), @@ -98,7 +100,7 @@ impl<'db> Bindings<'db> { /// Returns whether all bindings were successful, or an error describing why some bindings were /// unsuccessful. - pub(crate) fn as_result(&self) -> Result<(), CallError<()>> { + pub(crate) fn as_result(&self) -> Result<(), CallError> { // In order of precedence: // // - If every union element is Ok, then the union is too. @@ -120,45 +122,21 @@ impl<'db> Bindings<'db> { for binding in self.bindings() { let result = binding.as_result(); all_ok &= result.is_ok(); - any_binding_error |= matches!(result, Err(CallError::BindingError(()))); - all_not_callable &= matches!(result, Err(CallError::NotCallable(()))); + any_binding_error |= matches!(result, Err(CallError::BindingError)); + all_not_callable &= matches!(result, Err(CallError::NotCallable)); } if all_ok { Ok(()) } else if any_binding_error { - Err(CallError::BindingError(())) + Err(CallError::BindingError) } else if all_not_callable { - Err(CallError::NotCallable(())) + Err(CallError::NotCallable) } else { - Err(CallError::PossiblyNotCallable(())) + Err(CallError::PossiblyNotCallable) } } - /// Wraps a successful binding in `Ok`, or returns an error describing why the binding was - /// unsuccessful. - pub(crate) fn into_result(self) -> Result> { - match self.as_result() { - Ok(()) => Ok(self), - Err(CallError::NotCallable(())) => Err(CallError::NotCallable(self)), - Err(CallError::BindingError(())) => Err(CallError::BindingError(self)), - Err(CallError::PossiblyNotCallable(())) => Err(CallError::PossiblyNotCallable(self)), - } - } - - pub(crate) fn len(&self) -> usize { - match &self.inner { - BindingsInner::Single(_) => 1, - BindingsInner::Union(bindings) => bindings.len(), - } - } - - /// Returns whether this binding is callable. A union type is callable if _all_ of its elements - /// are callable. - pub(crate) fn is_not_callable(&self) -> bool { - self.bindings().iter().all(|b| !b.is_callable()) - } - pub(crate) fn bindings(&self) -> &[CallableBinding<'db>] { match &self.inner { BindingsInner::Single(binding) => std::slice::from_ref(binding), @@ -304,17 +282,17 @@ impl<'db> CallableBinding<'db> { } } - fn as_result(&self) -> Result<(), CallError<()>> { + fn as_result(&self) -> Result<(), CallError> { if matches!(self.inner, CallableBindingInner::NotCallable) { - return Err(CallError::NotCallable(())); + return Err(CallError::NotCallable); } if self.has_binding_errors() { - return Err(CallError::BindingError(())); + return Err(CallError::BindingError); } if self.dunder_is_possibly_unbound() { - return Err(CallError::PossiblyNotCallable(())); + return Err(CallError::PossiblyNotCallable); } Ok(()) @@ -584,9 +562,9 @@ impl<'db> Binding<'db> { } } - fn as_result(&self) -> Result<(), CallError<()>> { + fn as_result(&self) -> Result<(), CallError> { if !self.errors.is_empty() { - return Err(CallError::BindingError(())); + return Err(CallError::BindingError); } Ok(()) } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index a7b40be6857a8..8e1d4554943f0 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -232,18 +232,19 @@ impl<'db> Class<'db> { // TODO: Other keyword arguments? let arguments = CallArguments::positional([name, bases, namespace]); - let return_ty_result = match metaclass.try_call(db, &arguments) { - Ok(outcome) => Ok(outcome.return_type(db)), + let bindings = metaclass.call(db, &arguments); + let return_ty_result = match bindings.as_result() { + Ok(()) => Ok(bindings.return_type(db)), - Err(CallError::NotCallable(bindings)) => Err(MetaclassError { + Err(CallError::NotCallable) => Err(MetaclassError { kind: MetaclassErrorKind::NotCallable(bindings.ty), }), // TODO we should also check for binding errors that would indicate the metaclass // does not accept the right arguments - Err(CallError::BindingError(bindings)) => Ok(bindings.return_type(db)), + Err(CallError::BindingError) => Ok(bindings.return_type(db)), - Err(CallError::PossiblyNotCallable(_)) => Err(MetaclassError { + Err(CallError::PossiblyNotCallable) => Err(MetaclassError { kind: MetaclassErrorKind::PartlyNotCallable(metaclass), }), }; diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 7e2d14ad5d1c4..58d1b7865ef26 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -2415,25 +2415,22 @@ impl<'db> TypeInferenceBuilder<'db> { .class_member(self.db(), op.in_place_dunder()) .symbol { - let call = class_member.try_call( + let bindings = class_member.call( self.db(), &CallArguments::positional([target_type, value_type]), ); - let augmented_return_ty = match call { - Ok(t) => t.return_type(self.db()), - Err(e) => { - self.context.report_lint( - &UNSUPPORTED_OPERATOR, - assignment, - format_args!( - "Operator `{op}=` is unsupported between objects of type `{}` and `{}`", - target_type.display(self.db()), - value_type.display(self.db()) - ), - ); - e.fallback_return_type(self.db()) - } + if bindings.as_result().is_err() { + self.context.report_lint( + &UNSUPPORTED_OPERATOR, + assignment, + format_args!( + "Operator `{op}=` is unsupported between objects of type `{}` and `{}`", + target_type.display(self.db()), + value_type.display(self.db()) + ), + ); }; + let augmented_return_ty = bindings.return_type(self.db()); return match boundness { Boundness::Bound => augmented_return_ty, @@ -3544,11 +3541,11 @@ impl<'db> TypeInferenceBuilder<'db> { .unwrap_or_default(); let call_arguments = self.infer_arguments(arguments, parameter_expectations); - let call = function_type.try_call(self.db(), &call_arguments); + let bindings = function_type.call(self.db(), &call_arguments); - match call { - Ok(outcome) => { - for binding in outcome.bindings() { + match bindings.as_result() { + Ok(()) => { + for binding in bindings.bindings() { let Some(known_function) = binding .ty .into_function_literal() @@ -3654,15 +3651,13 @@ impl<'db> TypeInferenceBuilder<'db> { _ => {} } } - - outcome.return_type(self.db()) } - Err(err) => { - err.bindings() - .report_diagnostics(&self.context, call_expression.into()); - err.fallback_return_type(self.db()) + Err(_) => { + bindings.report_diagnostics(&self.context, call_expression.into()); } } + + bindings.return_type(self.db()) } fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> { @@ -5093,13 +5088,11 @@ impl<'db> TypeInferenceBuilder<'db> { let compare_result_opt = match contains_dunder { Symbol::Type(contains_dunder, Boundness::Bound) => { // If `__contains__` is available, it is used directly for the membership test. - contains_dunder - .try_call( - db, - &CallArguments::positional([Type::Instance(right), Type::Instance(left)]), - ) - .map(|outcome| outcome.return_type(db)) - .ok() + let bindings = contains_dunder.call( + db, + &CallArguments::positional([Type::Instance(right), Type::Instance(left)]), + ); + bindings.as_result().map(|()| bindings.return_type(db)).ok() } _ => { // iteration-based membership test @@ -5382,18 +5375,18 @@ impl<'db> TypeInferenceBuilder<'db> { return err.fallback_return_type(self.db()); } - Err(CallDunderError::Call(err)) => { + Err(CallDunderError::Call(bindings, _)) => { self.context.report_lint( - &CALL_NON_CALLABLE, - value_node, - format_args!( - "Method `__getitem__` of type `{}` is not callable on object of type `{}`", - err.called_type().display(self.db()), - value_ty.display(self.db()), - ), - ); + &CALL_NON_CALLABLE, + value_node, + format_args!( + "Method `__getitem__` of type `{}` is not callable on object of type `{}`", + bindings.ty.display(self.db()), + value_ty.display(self.db()), + ), + ); - return err.fallback_return_type(self.db()); + return bindings.return_type(self.db()); } Err(CallDunderError::MethodNotAvailable) => { // try `__class_getitem__` @@ -5427,21 +5420,20 @@ impl<'db> TypeInferenceBuilder<'db> { ); } - return ty - .try_call(self.db(), &CallArguments::positional([value_ty, slice_ty])) - .map(|outcome| outcome.return_type(self.db())) - .unwrap_or_else(|err| { - self.context.report_lint( - &CALL_NON_CALLABLE, - value_node, - format_args!( - "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - err.called_type().display(self.db()), - value_ty.display(self.db()), - ), - ); - err.fallback_return_type(self.db()) - }); + let bindings = ty + .call(self.db(), &CallArguments::positional([value_ty, slice_ty])); + if bindings.as_result().is_err() { + self.context.report_lint( + &CALL_NON_CALLABLE, + value_node, + format_args!( + "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", + bindings.ty.display(self.db()), + value_ty.display(self.db()), + ), + ); + } + return bindings.return_type(self.db()); } } From a85b6d56776ccd6a375aeffe454ed10fd9c46fbc Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 21:07:15 -0400 Subject: [PATCH 11/57] Fix merge conflicts --- crates/red_knot_python_semantic/src/types.rs | 2 +- .../src/types/signatures.rs | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 79bf5fe7440c9..a0bf50e3ed604 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -4349,7 +4349,7 @@ impl<'db> FunctionType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Option> { // TODO: Add support for overloaded callables; return `Type`, not `Option`. Some(Type::Callable(CallableType::General( - GeneralCallableType::new(db, self.signature(db).as_single()?.clone()), + GeneralCallableType::new(db, self.signature(db).clone()), ))) } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index a812c348042c4..3d0bffb54893a 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -212,9 +212,10 @@ impl<'db> CallableSignature<'db> { /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { - match self { - CallableSignature::Single(signature) => Some(signature), - CallableSignature::Overloaded(_) => None, + match &self.inner { + CallableSignatureInner::NotCallable => None, + CallableSignatureInner::Single(signature) => Some(signature), + CallableSignatureInner::Overloaded(_) => None, } } @@ -230,14 +231,6 @@ impl<'db> CallableSignature<'db> { pub(crate) fn is_callable(&self) -> bool { !matches!(&self.inner, CallableSignatureInner::NotCallable) } - - pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { - match &self.inner { - CallableSignatureInner::NotCallable => None, - CallableSignatureInner::Single(signature) => Some(signature), - CallableSignatureInner::Overloaded(_) => None, - } - } } /// The signature of one of the overloads of a callable. From 88ad36fd1c128531d54193b933e0986674b3824b Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 21:22:14 -0400 Subject: [PATCH 12/57] clippy --- crates/red_knot_python_semantic/src/types.rs | 44 +++++++++++-------- .../src/types/call.rs | 4 +- .../src/types/infer.rs | 11 +---- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a0bf50e3ed604..071fd15084431 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2164,24 +2164,32 @@ impl<'db> Type<'db> { }; match self.try_call_dunder(db, "__bool__", &CallArguments::none()) { - ref result @ (Ok(ref outcome) - | Err(CallDunderError::PossiblyUnbound(ref outcome))) => { + Ok(outcome) => { let return_type = outcome.return_type(db); + if !return_type.is_assignable_to(db, KnownClass::Bool.to_instance(db)) { + // The type has a `__bool__` method, but it doesn't return a + // boolean. + return Err(BoolError::IncorrectReturnType { + return_type, + not_boolable_type: *instance_ty, + }); + } + type_to_truthiness(return_type) + } - // The type has a `__bool__` method, but it doesn't return a boolean. + Err(CallDunderError::PossiblyUnbound(outcome)) => { + let return_type = outcome.return_type(db); if !return_type.is_assignable_to(db, KnownClass::Bool.to_instance(db)) { + // The type has a `__bool__` method, but it doesn't return a + // boolean. return Err(BoolError::IncorrectReturnType { return_type: outcome.return_type(db), not_boolable_type: *instance_ty, }); } - if result.is_ok() { - type_to_truthiness(return_type) - } else { - // Don't trust possibly unbound `__bool__` method. - Truthiness::Ambiguous - } + // Don't trust possibly unbound `__bool__` method. + Truthiness::Ambiguous } Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, @@ -2294,9 +2302,8 @@ impl<'db> Type<'db> { } let return_ty = match self.try_call_dunder(db, "__len__", &CallArguments::none()) { - Ok(bindings) | Err(CallDunderError::PossiblyUnbound(bindings)) => { - bindings.return_type(db) - } + Ok(bindings) => bindings.return_type(db), + Err(CallDunderError::PossiblyUnbound(bindings)) => bindings.return_type(db), // TODO: emit a diagnostic Err(CallDunderError::MethodNotAvailable) => return None, @@ -2955,10 +2962,10 @@ impl<'db> Type<'db> { }; let bindings = Bindings::bind(db, &signature, arguments); if let Err(err) = bindings.as_result() { - return Err(CallDunderError::Call(bindings, err)); + return Err(CallDunderError::Call(Box::new(bindings), err)); } if boundness == Boundness::PossiblyUnbound { - return Err(CallDunderError::PossiblyUnbound(bindings)); + return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } Ok(bindings) } @@ -3820,7 +3827,7 @@ impl<'db> IterationError<'db> { enum IterationErrorKind<'db> { /// The object being iterated over has a bound `__iter__` method, /// but calling it with the expected arguments results in an error. - IterCallError(Bindings<'db>, CallError), + IterCallError(Box>, CallError), /// The object being iterated over has a bound `__iter__` method that can be called /// with the expected types, but it returns an object that is not a valid iterator. @@ -4346,10 +4353,11 @@ impl<'db> FunctionType<'db> { /// /// Returns `None` if the function is overloaded. This powers the `CallableTypeFromFunction` /// special form from the `knot_extensions` module. - pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Option> { + pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { // TODO: Add support for overloaded callables; return `Type`, not `Option`. - Some(Type::Callable(CallableType::General( - GeneralCallableType::new(db, self.signature(db).clone()), + Type::Callable(CallableType::General(GeneralCallableType::new( + db, + self.signature(db).clone(), ))) } diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 678efc891d3ce..0594da5ae050e 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -26,12 +26,12 @@ pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// /// This includes non-callable dunder attributes that are possibly unbound. - Call(Bindings<'db>, CallError), + Call(Box>, CallError), /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors /// but it is possibly unbound. - PossiblyUnbound(Bindings<'db>), + PossiblyUnbound(Box>), /// The dunder method with the specified name is missing. MethodNotAvailable, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 02a808da79ad7..ba8f96bb85eff 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -6329,16 +6329,7 @@ impl<'db> TypeInferenceBuilder<'db> { ); return Type::unknown(); }; - function_type - .into_callable_type(self.db()) - .unwrap_or_else(|| { - self.context.report_lint( - &INVALID_TYPE_FORM, - arguments_slice, - format_args!("Overloaded function literal is not yet supported"), - ); - Type::unknown() - }) + function_type.into_callable_type(self.db()) } }, From 903bfdea6be7982ab133fa03f8129d4ea67d361b Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 13 Mar 2025 21:28:17 -0400 Subject: [PATCH 13/57] Track call signatures --- crates/red_knot_python_semantic/src/types.rs | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 071fd15084431..369531f20235d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2315,6 +2315,7 @@ impl<'db> Type<'db> { /// Returns the (possibly unioned, possibly overloaded) signatures of a callable type. Returns /// [`Signatures::not_callable`] if the type is not callable. + #[salsa::tracked(return_ref)] fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { @@ -2583,10 +2584,12 @@ impl<'db> Type<'db> { } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - ClassBase::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).signatures(db, callable_ty) - } - ClassBase::Class(class) => Type::class_literal(class).signatures(db, callable_ty), + ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) + .signatures(db, callable_ty) + .clone(), + ClassBase::Class(class) => Type::class_literal(class) + .signatures(db, callable_ty) + .clone(), }, Type::Instance(_) => { @@ -2594,11 +2597,12 @@ impl<'db> Type<'db> { // signature of the dunder method, but will pass in the type of the object as the // "callable type". That ensures that we get errors like "`X` is not callable" // instead of "`` is not callable". - let Some((mut signatures, boundness)) = + let Some((signatures, boundness)) = self.dunder_signature(db, Some(callable_ty), "__call__") else { return Signatures::not_callable(callable_ty, self); }; + let mut signatures = signatures.clone(); signatures.set_dunder_call_boundness(boundness); signatures } @@ -2614,7 +2618,7 @@ impl<'db> Type<'db> { union .elements(db) .iter() - .map(|element| element.signatures(db, *element)), + .map(|element| element.signatures(db, *element).clone()), ), Type::Intersection(_) => { @@ -2628,7 +2632,7 @@ impl<'db> Type<'db> { /// Calls `self` fn call(self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>) -> Bindings<'db> { let signatures = self.signatures(db, self); - let mut bindings = Bindings::bind(db, &signatures, arguments); + let mut bindings = Bindings::bind(db, signatures, arguments); for binding in bindings.bindings_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -2934,7 +2938,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, callable_ty: Option>, name: &str, - ) -> Option<(Signatures<'db>, Boundness)> { + ) -> Option<(&'db Signatures<'db>, Boundness)> { match self .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) .symbol @@ -2960,7 +2964,7 @@ impl<'db> Type<'db> { let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { return Err(CallDunderError::MethodNotAvailable); }; - let bindings = Bindings::bind(db, &signature, arguments); + let bindings = Bindings::bind(db, signature, arguments); if let Err(err) = bindings.as_result() { return Err(CallDunderError::Call(Box::new(bindings), err)); } From 71fe9ff6e81013e0a6cd0215370253214d13eb71 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 08:35:53 -0400 Subject: [PATCH 14/57] Back to `try_call` --- crates/red_knot_python_semantic/src/types.rs | 116 ++++++++++-------- .../src/types/call.rs | 24 ++-- .../src/types/call/bind.rs | 33 ++--- .../src/types/class.rs | 17 +-- .../src/types/infer.rs | 87 +++++++------ 5 files changed, 155 insertions(+), 122 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 369531f20235d..1531c2f73bc3a 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use std::str::FromStr; use bitflags::bitflags; -use call::{CallDunderError, CallError}; +use call::{CallDunderError, CallError, CallErrorKind}; use context::InferContext; use diagnostic::{INVALID_CONTEXT_MANAGER, NOT_ITERABLE}; use ruff_db::files::File; @@ -1688,10 +1688,9 @@ impl<'db> Type<'db> { let descr_get = self.class_member(db, "__get__".into()).symbol; if let Symbol::Type(descr_get, descr_get_boundness) = descr_get { - let bindings = descr_get.call(db, &CallArguments::positional([self, instance, owner])); - let return_ty = bindings - .as_result() - .map(|()| { + let return_ty = descr_get + .try_call(db, &CallArguments::positional([self, instance, owner])) + .map(|bindings| { if descr_get_boundness == Boundness::Bound { bindings.return_type(db) } else { @@ -2193,18 +2192,24 @@ impl<'db> Type<'db> { } Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, - Err(CallDunderError::Call(binding, CallError::BindingError)) => { + Err(CallDunderError::Call(CallError( + CallErrorKind::BindingError, + bindings, + ))) => { return Err(BoolError::IncorrectArguments { - truthiness: type_to_truthiness(binding.return_type(db)), + truthiness: type_to_truthiness(bindings.return_type(db)), not_boolable_type: *instance_ty, }); } - Err(CallDunderError::Call(_, CallError::NotCallable)) => { + Err(CallDunderError::Call(CallError(CallErrorKind::NotCallable, _))) => { return Err(BoolError::NotCallable { not_boolable_type: *instance_ty, }); } - Err(CallDunderError::Call(_, CallError::PossiblyNotCallable)) => { + Err(CallDunderError::Call(CallError( + CallErrorKind::PossiblyNotCallable, + _, + ))) => { return Err(BoolError::Other { not_boolable_type: *self, }) @@ -2307,7 +2312,7 @@ impl<'db> Type<'db> { // TODO: emit a diagnostic Err(CallDunderError::MethodNotAvailable) => return None, - Err(CallDunderError::Call(bindings, _)) => bindings.return_type(db), + Err(CallDunderError::Call(CallError(_, bindings))) => bindings.return_type(db), }; non_negative_int_literal(db, return_ty) @@ -2629,10 +2634,18 @@ impl<'db> Type<'db> { } } - /// Calls `self` - fn call(self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>) -> Bindings<'db> { + /// Calls `self`. Returns a [`CallError`] if `self` is (always or possibly) not callable, or if + /// the arguments are not compatible with the formal parameters. You get back a [`Bindings`] + /// for both successful and unsuccessful calls. It contains information about which formal + /// parameters each argument was matched to, and about any errors matching arguments and + /// parameters. + fn try_call( + self, + db: &'db dyn Db, + arguments: &CallArguments<'_, 'db>, + ) -> Result, CallError<'db>> { let signatures = self.signatures(db, self); - let mut bindings = Bindings::bind(db, signatures, arguments); + let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.bindings_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -2928,7 +2941,7 @@ impl<'db> Type<'db> { } } - bindings + Ok(bindings) } /// Looks up a dunder method on the meta-type of `self` and returns its signature and @@ -2964,10 +2977,7 @@ impl<'db> Type<'db> { let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { return Err(CallDunderError::MethodNotAvailable); }; - let bindings = Bindings::bind(db, signature, arguments); - if let Err(err) = bindings.as_result() { - return Err(CallDunderError::Call(Box::new(bindings), err)); - } + let bindings = Bindings::bind(db, signature, arguments).into_result()?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } @@ -3063,9 +3073,9 @@ impl<'db> Type<'db> { } // `__iter__` is definitely bound but it can't be called with the expected arguments - Err(CallDunderError::Call(bindings, dunder_iter_call_error)) => Err( - IterationErrorKind::IterCallError(bindings, dunder_iter_call_error), - ), + Err(CallDunderError::Call(dunder_iter_call_error)) => { + Err(IterationErrorKind::IterCallError(dunder_iter_call_error)) + } // There's no `__iter__` method. Try `__getitem__` instead... Err(CallDunderError::MethodNotAvailable) => { @@ -3727,8 +3737,8 @@ impl<'db> ContextManagerErrorKind<'db> { CallDunderError::PossiblyUnbound(call_outcome) => { Some(call_outcome.return_type(db)) } - CallDunderError::Call(_, CallError::NotCallable) => None, - CallDunderError::Call(bindings, _) => Some(bindings.return_type(db)), + CallDunderError::Call(CallError(CallErrorKind::NotCallable, _)) => None, + CallDunderError::Call(CallError(_, bindings)) => Some(bindings.return_type(db)), CallDunderError::MethodNotAvailable => None, }, } @@ -3749,7 +3759,7 @@ impl<'db> ContextManagerErrorKind<'db> { // TODO: Use more specific error messages for the different error cases. // E.g. hint toward the union variant that doesn't correctly implement enter, // distinguish between a not callable `__enter__` attribute and a wrong signature. - CallDunderError::Call(_, _) => format!("it does not correctly implement `{name}`"), + CallDunderError::Call(_) => format!("it does not correctly implement `{name}`"), } }; @@ -3764,7 +3774,7 @@ impl<'db> ContextManagerErrorKind<'db> { (CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => { format!("it does not implement `{name_a}` and `{name_b}`") } - (CallDunderError::Call(_, _), CallDunderError::Call(_, _)) => { + (CallDunderError::Call(_), CallDunderError::Call(_)) => { format!("it does not correctly implement `{name_a}` or `{name_b}`") } (_, _) => format!( @@ -3831,7 +3841,7 @@ impl<'db> IterationError<'db> { enum IterationErrorKind<'db> { /// The object being iterated over has a bound `__iter__` method, /// but calling it with the expected arguments results in an error. - IterCallError(Box>, CallError), + IterCallError(CallError<'db>), /// The object being iterated over has a bound `__iter__` method that can be called /// with the expected types, but it returns an object that is not a valid iterator. @@ -3871,7 +3881,7 @@ impl<'db> IterationErrorKind<'db> { dunder_next_error, .. } => dunder_next_error.return_type(db), - Self::IterCallError(dunder_iter_bindings, _) => dunder_iter_bindings + Self::IterCallError(CallError(_, dunder_iter_bindings)) => dunder_iter_bindings .return_type(db) .try_call_dunder(db, "__next__", &CallArguments::none()) .map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db))) @@ -3888,8 +3898,10 @@ impl<'db> IterationErrorKind<'db> { [*dunder_next_return, dunder_getitem_outcome.return_type(db)], )) } - CallDunderError::Call(_, CallError::NotCallable) => Some(*dunder_next_return), - CallDunderError::Call(dunder_getitem_bindings, _) => { + CallDunderError::Call(CallError(CallErrorKind::NotCallable, _)) => { + Some(*dunder_next_return) + } + CallDunderError::Call(CallError(_, dunder_getitem_bindings)) => { let dunder_getitem_return = dunder_getitem_bindings.return_type(db); let elements = [*dunder_next_return, dunder_getitem_return]; Some(UnionType::from_elements(db, elements)) @@ -3920,15 +3932,15 @@ impl<'db> IterationErrorKind<'db> { // or similar, rather than as part of the same sentence as the error message. match self { - Self::IterCallError(bindings, dunder_iter_call_error) => match dunder_iter_call_error { - CallError::NotCallable => report_not_iterable(format_args!( + Self::IterCallError(CallError(dunder_iter_call_error, bindings)) => match dunder_iter_call_error { + CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), dunder_iter_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable if bindings.is_single() => { + CallErrorKind::PossiblyNotCallable if bindings.is_single() => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ @@ -3937,7 +3949,7 @@ impl<'db> IterationErrorKind<'db> { dunder_iter_type = bindings.ty.display(db), )); } - CallError::PossiblyNotCallable => { + CallErrorKind::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` attribute (with type `{dunder_iter_type}`) \ @@ -3946,13 +3958,13 @@ impl<'db> IterationErrorKind<'db> { dunder_iter_type = bindings.ty.display(db), )); } - CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method has an invalid signature \ (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError => report_not_iterable(format_args!( + CallErrorKind::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", @@ -3979,29 +3991,29 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallDunderError::Call(bindings, dunder_next_call_error) => match dunder_next_call_error { - CallError::NotCallable => report_not_iterable(format_args!( + CallDunderError::Call(CallError(dunder_next_call_error, bindings)) => match dunder_next_call_error { + CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that is not callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::PossiblyNotCallable => report_not_iterable(format_args!( + CallErrorKind::PossiblyNotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has a `__next__` attribute that may not be callable", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which has an invalid `__next__` method (expected `def __next__(self): ...`)", iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallError::BindingError => report_not_iterable(format_args!( + CallErrorKind::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ which may have an invalid `__next__` method (expected `def __next__(self): ...`)", @@ -4025,8 +4037,8 @@ impl<'db> IterationErrorKind<'db> { because it may not have an `__iter__` method or a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(bindings, dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable => report_not_iterable(format_args!( + CallDunderError::Call(CallError(dunder_getitem_call_error, bindings)) => match dunder_getitem_call_error { + CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ @@ -4034,13 +4046,13 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` attribute may not be callable", iterable_type = iterable_type.display(db), )), - CallError::PossiblyNotCallable => { + CallErrorKind::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ @@ -4050,7 +4062,7 @@ impl<'db> IterationErrorKind<'db> { dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method has an incorrect signature \ @@ -4059,7 +4071,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError => report_not_iterable(format_args!( + CallErrorKind::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ and its `__getitem__` method (with type `{dunder_getitem_type}`) @@ -4083,8 +4095,8 @@ impl<'db> IterationErrorKind<'db> { and it may not have a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(bindings, dunder_getitem_call_error) => match dunder_getitem_call_error { - CallError::NotCallable => report_not_iterable(format_args!( + CallDunderError::Call(CallError(dunder_getitem_call_error, bindings)) => match dunder_getitem_call_error { + CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` attribute has type `{dunder_getitem_type}`, \ @@ -4092,13 +4104,13 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.ty.display(db), )), - CallError::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ may not be callable", iterable_type = iterable_type.display(db), )), - CallError::PossiblyNotCallable => { + CallErrorKind::PossiblyNotCallable => { report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ @@ -4107,7 +4119,7 @@ impl<'db> IterationErrorKind<'db> { dunder_getitem_type = bindings.ty.display(db), )); } - CallError::BindingError if bindings.is_single() => report_not_iterable(format_args!( + CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ its `__getitem__` method has an incorrect signature \ @@ -4116,7 +4128,7 @@ impl<'db> IterationErrorKind<'db> { `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), )), - CallError::BindingError => report_not_iterable(format_args!( + CallErrorKind::BindingError => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and \ its `__getitem__` method (with type `{dunder_getitem_type}`) \ diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 0594da5ae050e..aae08eff58111 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -7,9 +7,14 @@ mod bind; pub(super) use arguments::{Argument, CallArguments}; pub(super) use bind::Bindings; -/// The reason why calling a type failed. +/// Wraps a [`CallBindings`] for an unsuccessful call with information about why the call was +/// unsuccessful. #[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum CallError { +pub(super) struct CallError<'db>(pub(super) CallErrorKind, pub(super) Box>); + +/// The reason why calling a type failed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum CallErrorKind { /// The type is not callable. NotCallable, @@ -26,7 +31,7 @@ pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// /// This includes non-callable dunder attributes that are possibly unbound. - Call(Box>, CallError), + Call(CallError<'db>), /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors @@ -40,10 +45,9 @@ pub(super) enum CallDunderError<'db> { impl<'db> CallDunderError<'db> { pub(super) fn return_type(&self, db: &'db dyn Db) -> Option> { match self { - Self::MethodNotAvailable | Self::Call(_, CallError::NotCallable) => None, - Self::Call(bindings, _) | Self::PossiblyUnbound(bindings) => { - Some(bindings.return_type(db)) - } + Self::MethodNotAvailable | Self::Call(CallError(CallErrorKind::NotCallable, _)) => None, + Self::Call(CallError(_, bindings)) => Some(bindings.return_type(db)), + Self::PossiblyUnbound(bindings) => Some(bindings.return_type(db)), } } @@ -51,3 +55,9 @@ impl<'db> CallDunderError<'db> { self.return_type(db).unwrap_or(Type::unknown()) } } + +impl<'db> From> for CallDunderError<'db> { + fn from(error: CallError<'db>) -> Self { + Self::Call(error) + } +} diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 21ab224888759..984210e7692b9 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -6,8 +6,8 @@ use std::borrow::Cow; use super::{ - Argument, CallArguments, CallError, CallableSignature, InferContext, Signature, Signatures, - Type, + Argument, CallArguments, CallError, CallErrorKind, CallableSignature, InferContext, Signature, + Signatures, Type, }; use crate::db::Db; use crate::symbol::Boundness; @@ -100,7 +100,7 @@ impl<'db> Bindings<'db> { /// Returns whether all bindings were successful, or an error describing why some bindings were /// unsuccessful. - pub(crate) fn as_result(&self) -> Result<(), CallError> { + pub(crate) fn into_result(self) -> Result> { // In order of precedence: // // - If every union element is Ok, then the union is too. @@ -122,18 +122,21 @@ impl<'db> Bindings<'db> { for binding in self.bindings() { let result = binding.as_result(); all_ok &= result.is_ok(); - any_binding_error |= matches!(result, Err(CallError::BindingError)); - all_not_callable &= matches!(result, Err(CallError::NotCallable)); + any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); + all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable)); } if all_ok { - Ok(()) + Ok(self) } else if any_binding_error { - Err(CallError::BindingError) + Err(CallError(CallErrorKind::BindingError, Box::new(self))) } else if all_not_callable { - Err(CallError::NotCallable) + Err(CallError(CallErrorKind::NotCallable, Box::new(self))) } else { - Err(CallError::PossiblyNotCallable) + Err(CallError( + CallErrorKind::PossiblyNotCallable, + Box::new(self), + )) } } @@ -282,17 +285,17 @@ impl<'db> CallableBinding<'db> { } } - fn as_result(&self) -> Result<(), CallError> { + fn as_result(&self) -> Result<(), CallErrorKind> { if matches!(self.inner, CallableBindingInner::NotCallable) { - return Err(CallError::NotCallable); + return Err(CallErrorKind::NotCallable); } if self.has_binding_errors() { - return Err(CallError::BindingError); + return Err(CallErrorKind::BindingError); } if self.dunder_is_possibly_unbound() { - return Err(CallError::PossiblyNotCallable); + return Err(CallErrorKind::PossiblyNotCallable); } Ok(()) @@ -562,9 +565,9 @@ impl<'db> Binding<'db> { } } - fn as_result(&self) -> Result<(), CallError> { + fn as_result(&self) -> Result<(), CallErrorKind> { if !self.errors.is_empty() { - return Err(CallError::BindingError); + return Err(CallErrorKind::BindingError); } Ok(()) } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index a98d7ce976377..d0839aa4515d2 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -11,8 +11,8 @@ use crate::{ Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers, }, types::{ - definition_expression_type, CallArguments, CallError, DynamicType, MetaclassCandidate, - TupleType, UnionBuilder, UnionType, + definition_expression_type, CallArguments, CallError, CallErrorKind, DynamicType, + MetaclassCandidate, TupleType, UnionBuilder, UnionType, }, Db, KnownModule, Program, }; @@ -281,19 +281,20 @@ impl<'db> Class<'db> { // TODO: Other keyword arguments? let arguments = CallArguments::positional([name, bases, namespace]); - let bindings = metaclass.call(db, &arguments); - let return_ty_result = match bindings.as_result() { - Ok(()) => Ok(bindings.return_type(db)), + let return_ty_result = match metaclass.try_call(db, &arguments) { + Ok(bindings) => Ok(bindings.return_type(db)), - Err(CallError::NotCallable) => Err(MetaclassError { + Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError { kind: MetaclassErrorKind::NotCallable(bindings.ty), }), // TODO we should also check for binding errors that would indicate the metaclass // does not accept the right arguments - Err(CallError::BindingError) => Ok(bindings.return_type(db)), + Err(CallError(CallErrorKind::BindingError, bindings)) => { + Ok(bindings.return_type(db)) + } - Err(CallError::PossiblyNotCallable) => Err(MetaclassError { + Err(CallError(CallErrorKind::PossiblyNotCallable, _)) => Err(MetaclassError { kind: MetaclassErrorKind::PartlyNotCallable(metaclass), }), }; diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index ba8f96bb85eff..8b14af7dce9d6 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -61,7 +61,7 @@ use crate::symbol::{ module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, Boundness, LookupError, }; -use crate::types::call::{Argument, CallArguments}; +use crate::types::call::{Argument, CallArguments, CallError}; use crate::types::diagnostic::{ report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, @@ -2415,22 +2415,24 @@ impl<'db> TypeInferenceBuilder<'db> { .class_member(self.db(), op.in_place_dunder()) .symbol { - let bindings = class_member.call( + let augmented_return_ty = match class_member.try_call( self.db(), &CallArguments::positional([target_type, value_type]), - ); - if bindings.as_result().is_err() { - self.context.report_lint( - &UNSUPPORTED_OPERATOR, - assignment, - format_args!( - "Operator `{op}=` is unsupported between objects of type `{}` and `{}`", - target_type.display(self.db()), - value_type.display(self.db()) - ), - ); + ) { + Ok(bindings) => bindings.return_type(self.db()), + Err(CallError(_, bindings)) => { + self.context.report_lint( + &UNSUPPORTED_OPERATOR, + assignment, + format_args!( + "Operator `{op}=` is unsupported between objects of type `{}` and `{}`", + target_type.display(self.db()), + value_type.display(self.db()) + ), + ); + bindings.return_type(self.db()) + } }; - let augmented_return_ty = bindings.return_type(self.db()); return match boundness { Boundness::Bound => augmented_return_ty, @@ -3541,10 +3543,8 @@ impl<'db> TypeInferenceBuilder<'db> { .unwrap_or_default(); let call_arguments = self.infer_arguments(arguments, parameter_expectations); - let bindings = function_type.call(self.db(), &call_arguments); - - match bindings.as_result() { - Ok(()) => { + match function_type.try_call(self.db(), &call_arguments) { + Ok(bindings) => { for binding in bindings.bindings() { let Some(known_function) = binding .ty @@ -3651,13 +3651,14 @@ impl<'db> TypeInferenceBuilder<'db> { _ => {} } } + bindings.return_type(self.db()) } - Err(_) => { + + Err(CallError(_, bindings)) => { bindings.report_diagnostics(&self.context, call_expression.into()); + bindings.return_type(self.db()) } } - - bindings.return_type(self.db()) } fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> { @@ -5088,11 +5089,13 @@ impl<'db> TypeInferenceBuilder<'db> { let compare_result_opt = match contains_dunder { Symbol::Type(contains_dunder, Boundness::Bound) => { // If `__contains__` is available, it is used directly for the membership test. - let bindings = contains_dunder.call( - db, - &CallArguments::positional([Type::Instance(right), Type::Instance(left)]), - ); - bindings.as_result().map(|()| bindings.return_type(db)).ok() + contains_dunder + .try_call( + db, + &CallArguments::positional([Type::Instance(right), Type::Instance(left)]), + ) + .map(|bindings| bindings.return_type(db)) + .ok() } _ => { // iteration-based membership test @@ -5375,7 +5378,7 @@ impl<'db> TypeInferenceBuilder<'db> { return err.fallback_return_type(self.db()); } - Err(CallDunderError::Call(bindings, _)) => { + Err(CallDunderError::Call(CallError(_, bindings))) => { self.context.report_lint( &CALL_NON_CALLABLE, value_node, @@ -5420,20 +5423,24 @@ impl<'db> TypeInferenceBuilder<'db> { ); } - let bindings = ty - .call(self.db(), &CallArguments::positional([value_ty, slice_ty])); - if bindings.as_result().is_err() { - self.context.report_lint( - &CALL_NON_CALLABLE, - value_node, - format_args!( - "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - bindings.ty.display(self.db()), - value_ty.display(self.db()), - ), - ); + match ty.try_call( + self.db(), + &CallArguments::positional([value_ty, slice_ty]), + ) { + Ok(bindings) => return bindings.return_type(self.db()), + Err(CallError(_, bindings)) => { + self.context.report_lint( + &CALL_NON_CALLABLE, + value_node, + format_args!( + "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", + bindings.ty.display(self.db()), + value_ty.display(self.db()), + ), + ); + return bindings.return_type(self.db()); + } } - return bindings.return_type(self.db()); } } From a1596da644878ec47881c1de20208d6d369f239f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 08:45:22 -0400 Subject: [PATCH 15/57] Better comment for `Type::signatures` --- crates/red_knot_python_semantic/src/types.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 1531c2f73bc3a..224a36b993afb 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2318,8 +2318,15 @@ impl<'db> Type<'db> { non_negative_int_literal(db, return_ty) } - /// Returns the (possibly unioned, possibly overloaded) signatures of a callable type. Returns - /// [`Signatures::not_callable`] if the type is not callable. + /// Returns the call signatures of a type. + /// + /// Note that all types have a valid [`Signatures`], even if the type is not callable. If you + /// need to determine if a type is callable, use [`Signatures::is_callable`]. Though note that + /// "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 inconsisent, 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`] and [`CallErrorKind::NotCallable`]. #[salsa::tracked(return_ref)] fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { match self { From da5eef30c6938dcd475380d115188bd455f5c07d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 09:13:41 -0400 Subject: [PATCH 16/57] Remove all mut use of Signatures --- crates/red_knot_python_semantic/src/types.rs | 121 ++++++++++-------- .../src/types/signatures.rs | 73 ++++++----- 2 files changed, 114 insertions(+), 80 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 224a36b993afb..b6d7a263b12cc 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2328,12 +2328,22 @@ impl<'db> Type<'db> { /// elements. It's usually best to only worry about "callability" relative to a particular /// argument list, via [`try_call`] and [`CallErrorKind::NotCallable`]. #[salsa::tracked(return_ref)] - fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { + fn signatures( + self, + db: &'db dyn Db, + callable_ty: Type<'db>, + dunder_call_boundness: Option, + ) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let mut signature = CallableSignature::new(callable_ty, self, signature.clone()); - signature.bound_type = Some(bound_method.self_instance(db)); + let signature = CallableSignature::new( + callable_ty, + self, + dunder_call_boundness, + Some(bound_method.self_instance(db)), + signature.clone(), + ); Signatures::single(signature) } @@ -2355,6 +2365,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, + dunder_call_boundness, + None, [ Signature::new( Parameters::new([ @@ -2407,6 +2419,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, + dunder_call_boundness, + None, [ Signature::new( Parameters::new([ @@ -2461,6 +2475,8 @@ impl<'db> Type<'db> { Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::new( callable_ty, self, + dunder_call_boundness, + None, function_type.signature(db).clone(), )), @@ -2474,6 +2490,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::new( callable_ty, self, + dunder_call_boundness, + None, Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2501,6 +2519,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, + dunder_call_boundness, + None, [ Signature::new( Parameters::new([Parameter::new( @@ -2550,6 +2570,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, + dunder_call_boundness, + None, [ Signature::new( Parameters::new([Parameter::new( @@ -2590,6 +2612,8 @@ impl<'db> Type<'db> { let signature = CallableSignature::new( callable_ty, self, + dunder_call_boundness, + None, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); Signatures::single(signature) @@ -2597,10 +2621,10 @@ impl<'db> Type<'db> { Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) - .signatures(db, callable_ty) + .signatures(db, callable_ty, dunder_call_boundness) .clone(), ClassBase::Class(class) => Type::class_literal(class) - .signatures(db, callable_ty) + .signatures(db, callable_ty, dunder_call_boundness) .clone(), }, @@ -2609,35 +2633,46 @@ impl<'db> Type<'db> { // signature of the dunder method, but will pass in the type of the object as the // "callable type". That ensures that we get errors like "`X` is not callable" // instead of "`` is not callable". - let Some((signatures, boundness)) = - self.dunder_signature(db, Some(callable_ty), "__call__") - else { - return Signatures::not_callable(callable_ty, self); - }; - let mut signatures = signatures.clone(); - signatures.set_dunder_call_boundness(boundness); - signatures + match self + .member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NoInstanceFallback, + ) + .symbol + { + Symbol::Type(dunder_callable, boundness) => dunder_callable + .signatures(db, callable_ty, Some(boundness)) + .clone(), + Symbol::Unbound => { + Signatures::not_callable(callable_ty, self, dunder_call_boundness) + } + } } // 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 => { + Signatures::single(CallableSignature::dynamic(self, dunder_call_boundness)) + } // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( callable_ty, self, - union - .elements(db) - .iter() - .map(|element| element.signatures(db, *element).clone()), + union.elements(db).iter().map(|element| { + element + .signatures(db, *element, dunder_call_boundness) + .clone() + }), ), - Type::Intersection(_) => { - Signatures::single(CallableSignature::todo("Type::Intersection.call()")) - } + Type::Intersection(_) => Signatures::single(CallableSignature::todo( + "Type::Intersection.call()", + dunder_call_boundness, + )), - _ => Signatures::not_callable(callable_ty, self), + _ => Signatures::not_callable(callable_ty, self, dunder_call_boundness), } } @@ -2651,7 +2686,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { - let signatures = self.signatures(db, self); + let signatures = self.signatures(db, self, None); let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.bindings_mut() { // For certain known callables, we have special case logic to determine the return type @@ -2951,26 +2986,6 @@ impl<'db> Type<'db> { Ok(bindings) } - /// Looks up a dunder method on the meta-type of `self` and returns its signature and - /// boundness. Returns `None` if the meta-type does not contain the dunder method. - fn dunder_signature( - self, - db: &'db dyn Db, - callable_ty: Option>, - name: &str, - ) -> Option<(&'db Signatures<'db>, Boundness)> { - match self - .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) - .symbol - { - Symbol::Type(dunder_callable, boundness) => { - let callable_ty = callable_ty.unwrap_or(dunder_callable); - Some((dunder_callable.signatures(db, callable_ty), boundness)) - } - Symbol::Unbound => None, - } - } - /// Look up a dunder method on the meta-type of `self` and call it. /// /// Returns an `Err` if the dunder method can't be called, @@ -2981,14 +2996,20 @@ impl<'db> Type<'db> { name: &str, arguments: &CallArguments<'_, 'db>, ) -> Result, CallDunderError<'db>> { - let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { - return Err(CallDunderError::MethodNotAvailable); - }; - let bindings = Bindings::bind(db, signature, arguments).into_result()?; - if boundness == Boundness::PossiblyUnbound { - return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); + match self + .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) + .symbol + { + Symbol::Type(dunder_callable, boundness) => { + let signatures = dunder_callable.signatures(db, dunder_callable, None); + let bindings = Bindings::bind(db, signatures, arguments).into_result()?; + if boundness == Boundness::PossiblyUnbound { + return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); + } + Ok(bindings) + } + Symbol::Unbound => Err(CallDunderError::MethodNotAvailable), } - Ok(bindings) } /// Returns the element type when iterating over `self`. diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 3d0bffb54893a..c4eac65bcd545 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -35,11 +35,20 @@ enum SignaturesInner<'db> { } impl<'db> Signatures<'db> { - pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { + pub(crate) fn not_callable( + ty: Type<'db>, + signature_ty: Type<'db>, + dunder_call_boundness: Option, + ) -> Self { Self { ty, signature_ty: ty, - inner: SignaturesInner::Single(CallableSignature::not_callable(ty, signature_ty)), + inner: SignaturesInner::Single(CallableSignature::not_callable( + ty, + signature_ty, + dunder_call_boundness, + None, + )), } } @@ -96,19 +105,6 @@ impl<'db> Signatures<'db> { SignaturesInner::Union(signatures) => signatures.iter(), } } - - pub(crate) fn set_dunder_call_boundness(&mut self, boundness: Boundness) { - match &mut self.inner { - SignaturesInner::Single(signature) => { - signature.dunder_call_boundness = Some(boundness); - } - SignaturesInner::Union(signatures) => { - for signature in signatures { - signature.dunder_call_boundness = Some(boundness); - } - } - } - } } /// The signature of a single callable. If the callable is overloaded, there is a separate @@ -141,29 +137,46 @@ enum CallableSignatureInner<'db> { } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { + pub(crate) fn not_callable( + ty: Type<'db>, + signature_ty: Type<'db>, + dunder_call_boundness: Option, + bound_type: Option>, + ) -> Self { Self { ty, signature_ty, - dunder_call_boundness: None, - bound_type: None, + dunder_call_boundness, + bound_type, inner: CallableSignatureInner::NotCallable, } } - pub(crate) fn new(ty: Type<'db>, signature_ty: Type<'db>, signature: Signature<'db>) -> Self { + pub(crate) fn new( + ty: Type<'db>, + signature_ty: Type<'db>, + dunder_call_boundness: Option, + bound_type: Option>, + signature: Signature<'db>, + ) -> Self { Self { ty, signature_ty, - dunder_call_boundness: None, - bound_type: None, + dunder_call_boundness, + bound_type, inner: CallableSignatureInner::Single(signature), } } /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if /// the iterator is empty. - pub(crate) fn from_overloads(ty: Type<'db>, signature_ty: Type<'db>, overloads: I) -> Self + pub(crate) fn from_overloads( + ty: Type<'db>, + signature_ty: Type<'db>, + dunder_call_boundness: Option, + bound_type: Option>, + overloads: I, + ) -> Self where I: IntoIterator, I::IntoIter: Iterator>, @@ -174,8 +187,8 @@ impl<'db> CallableSignature<'db> { return Self { ty, signature_ty, - dunder_call_boundness: None, - bound_type: None, + dunder_call_boundness, + bound_type, inner: CallableSignatureInner::Single(first_overload), }; }; @@ -184,30 +197,30 @@ impl<'db> CallableSignature<'db> { Self { ty, signature_ty, - dunder_call_boundness: None, - bound_type: None, + dunder_call_boundness, + bound_type, inner: CallableSignatureInner::Overloaded(overloads.into()), } } /// Return a signature for a dynamic callable - pub(crate) fn dynamic(ty: Type<'db>) -> Self { + pub(crate) fn dynamic(ty: Type<'db>, dunder_call_boundness: Option) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::new(ty, ty, signature) + Self::new(ty, ty, dunder_call_boundness, None, 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 { + pub(crate) fn todo(reason: &'static str, dunder_call_boundness: Option) -> Self { let ty = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::new(ty, ty, signature) + Self::new(ty, ty, dunder_call_boundness, None, signature) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. From fb9fd6e70f3ba099773ae18016ed0b0cdaca17c5 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 10:22:02 -0400 Subject: [PATCH 17/57] Track signatures structs --- crates/red_knot_python_semantic/src/types.rs | 113 +++++++------ .../src/types/call/bind.rs | 53 ++++--- .../src/types/class.rs | 2 +- .../src/types/infer.rs | 6 +- .../src/types/signatures.rs | 150 +++++++++++------- 5 files changed, 193 insertions(+), 131 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index b6d7a263b12cc..f2b9dd5155ec7 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2327,7 +2327,6 @@ impl<'db> Type<'db> { /// elements might be inconsisent, 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`] and [`CallErrorKind::NotCallable`]. - #[salsa::tracked(return_ref)] fn signatures( self, db: &'db dyn Db, @@ -2337,14 +2336,15 @@ impl<'db> Type<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let signature = CallableSignature::new( + let signature = CallableSignature::single( + db, callable_ty, self, dunder_call_boundness, Some(bound_method.self_instance(db)), signature.clone(), ); - Signatures::single(signature) + Signatures::single(db, signature) } Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { @@ -2363,6 +2363,7 @@ impl<'db> Type<'db> { let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( + db, callable_ty, self, dunder_call_boundness, @@ -2405,7 +2406,7 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(signature) + Signatures::single(db, signature) } Type::Callable(CallableType::WrapperDescriptorDunderGet) => { @@ -2417,6 +2418,7 @@ impl<'db> Type<'db> { // removed. let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( + db, callable_ty, self, dunder_call_boundness, @@ -2469,16 +2471,20 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(signature) + Signatures::single(db, signature) } - Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::new( - callable_ty, - self, - dunder_call_boundness, - None, - function_type.signature(db).clone(), - )), + Type::FunctionLiteral(function_type) => Signatures::single( + db, + CallableSignature::single( + db, + callable_ty, + self, + dunder_call_boundness, + None, + function_type.signature(db).clone(), + ), + ), Type::ClassLiteral(ClassLiteralType { class }) if class.is_known(db, KnownClass::Bool) => @@ -2487,7 +2493,8 @@ impl<'db> Type<'db> { // class bool(int): // def __new__(cls, o: object = ..., /) -> Self: ... // ``` - let signature = CallableSignature::new( + let signature = CallableSignature::single( + db, callable_ty, self, dunder_call_boundness, @@ -2503,7 +2510,7 @@ impl<'db> Type<'db> { Some(KnownClass::Bool.to_instance(db)), ), ); - Signatures::single(signature) + Signatures::single(db, signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2517,6 +2524,7 @@ impl<'db> Type<'db> { // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` let signature = CallableSignature::from_overloads( + db, callable_ty, self, dunder_call_boundness, @@ -2554,7 +2562,7 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(signature) + Signatures::single(db, signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2568,6 +2576,7 @@ impl<'db> Type<'db> { // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` let signature = CallableSignature::from_overloads( + db, callable_ty, self, dunder_call_boundness, @@ -2603,29 +2612,30 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(signature) + Signatures::single(db, signature) } // TODO annotated return type on `__new__` or metaclass `__call__` // TODO check call vs signatures of `__new__` and/or `__init__` Type::ClassLiteral(ClassLiteralType { .. }) => { - let signature = CallableSignature::new( + let signature = CallableSignature::single( + db, callable_ty, self, dunder_call_boundness, None, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); - Signatures::single(signature) + Signatures::single(db, signature) } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) - .signatures(db, callable_ty, dunder_call_boundness) - .clone(), - ClassBase::Class(class) => Type::class_literal(class) - .signatures(db, callable_ty, dunder_call_boundness) - .clone(), + ClassBase::Dynamic(dynamic_type) => { + Type::Dynamic(dynamic_type).signatures(db, callable_ty, dunder_call_boundness) + } + ClassBase::Class(class) => { + Type::class_literal(class).signatures(db, callable_ty, dunder_call_boundness) + } }, Type::Instance(_) => { @@ -2641,38 +2651,39 @@ impl<'db> Type<'db> { ) .symbol { - Symbol::Type(dunder_callable, boundness) => dunder_callable - .signatures(db, callable_ty, Some(boundness)) - .clone(), + Symbol::Type(dunder_callable, boundness) => { + dunder_callable.signatures(db, callable_ty, Some(boundness)) + } Symbol::Unbound => { - Signatures::not_callable(callable_ty, self, dunder_call_boundness) + Signatures::not_callable(db, callable_ty, self, dunder_call_boundness) } } } // 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, dunder_call_boundness)) - } + Type::Dynamic(_) | Type::Never => Signatures::single( + db, + CallableSignature::dynamic(db, self, dunder_call_boundness), + ), // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( + db, callable_ty, self, - union.elements(db).iter().map(|element| { - element - .signatures(db, *element, dunder_call_boundness) - .clone() - }), + union + .elements(db) + .iter() + .map(|element| element.signatures(db, *element, dunder_call_boundness)), ), - Type::Intersection(_) => Signatures::single(CallableSignature::todo( - "Type::Intersection.call()", - dunder_call_boundness, - )), + Type::Intersection(_) => Signatures::single( + db, + CallableSignature::todo(db, "Type::Intersection.call()", dunder_call_boundness), + ), - _ => Signatures::not_callable(callable_ty, self, dunder_call_boundness), + _ => Signatures::not_callable(db, callable_ty, self, dunder_call_boundness), } } @@ -3966,7 +3977,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.ty.display(db), + dunder_iter_type = bindings.callable_type(db).display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => { report_not_iterable(format_args!( @@ -3974,7 +3985,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.ty.display(db), + dunder_iter_type = bindings.callable_type(db).display(db), )); } CallErrorKind::PossiblyNotCallable => { @@ -3983,7 +3994,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.ty.display(db), + dunder_iter_type = bindings.callable_type(db).display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -3997,7 +4008,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.ty.display(db), + dunder_iter_type = bindings.callable_type(db).display(db), )), } @@ -4072,7 +4083,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4087,7 +4098,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4107,7 +4118,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )), } } @@ -4130,7 +4141,7 @@ impl<'db> IterationErrorKind<'db> { its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4144,7 +4155,7 @@ impl<'db> IterationErrorKind<'db> { because it has no `__iter__` method and its `__getitem__` attribute \ (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4164,7 +4175,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.ty.display(db), + dunder_getitem_type = bindings.callable_type(db).display(db), )), } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 984210e7692b9..5711036b97956 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -27,7 +27,7 @@ use ruff_text_size::Ranged; /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Bindings<'db> { - pub(crate) ty: Type<'db>, + pub(crate) signatures: Signatures<'db>, inner: BindingsInner<'db>, } @@ -48,23 +48,23 @@ impl<'db> Bindings<'db> { /// overload (if any). pub(crate) fn bind( db: &'db dyn Db, - signatures: &Signatures<'db>, + signatures: Signatures<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if let Some(signature) = signatures.as_single() { + if let Some(signature) = signatures.as_single(db) { return Bindings { - ty: signatures.ty, + signatures, inner: BindingsInner::Single(CallableBinding::bind(db, signature, arguments)), }; } let bindings = signatures - .iter() + .iter(db) .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect::>() .into_boxed_slice(); Bindings { - ty: signatures.ty, + signatures, inner: BindingsInner::Union(bindings), } } @@ -73,6 +73,10 @@ impl<'db> Bindings<'db> { matches!(&self.inner, BindingsInner::Single(_)) } + pub(crate) fn callable_type(&self, db: &'db dyn Db) -> Type<'db> { + self.signatures.ty(db) + } + /// Returns the return type of the call. For successful calls, this is the actual return type. /// For calls with binding errors, this is a type that best approximates the return type. For /// types that are not callable, returns `Type::Unknown`. @@ -159,7 +163,12 @@ impl<'db> Bindings<'db> { /// report a single diagnostic if we couldn't match any union element or overload. /// TODO: Update this to add subdiagnostics about how we failed to match each union element and /// overload. - pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { + pub(crate) fn report_diagnostics( + &self, + db: &'db dyn Db, + context: &InferContext<'db>, + node: ast::AnyNodeRef, + ) { // If all union elements are not callable, report that the union as a whole is not // callable. if self.bindings().iter().all(|b| !b.is_callable()) { @@ -168,7 +177,7 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.ty.display(context.db()) + self.signatures.ty(db).display(context.db()) ), ); return; @@ -220,32 +229,32 @@ impl<'db> CallableBinding<'db> { /// all parameters, and any errors resulting from binding the call. fn bind( db: &'db dyn Db, - signature: &CallableSignature<'db>, + signature: CallableSignature<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if !signature.is_callable() { + if !signature.is_callable(db) { return CallableBinding { - ty: signature.ty, - signature_ty: signature.signature_ty, - dunder_call_boundness: signature.dunder_call_boundness, + ty: signature.ty(db), + signature_ty: signature.signature_ty(db), + dunder_call_boundness: signature.dunder_call_boundness(db), inner: CallableBindingInner::NotCallable, }; } // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let arguments = if let Some(bound_type) = signature.bound_type { + let arguments = if let Some(bound_type) = signature.bound_type(db) { Cow::Owned(arguments.with_self(bound_type)) } else { Cow::Borrowed(arguments) }; - if let Some(single) = signature.as_single() { + if let Some(single) = signature.as_single(db) { let binding = Binding::bind(db, single, arguments.as_ref()); return CallableBinding { - ty: signature.ty, - signature_ty: signature.signature_ty, - dunder_call_boundness: signature.dunder_call_boundness, + ty: signature.ty(db), + signature_ty: signature.signature_ty(db), + dunder_call_boundness: signature.dunder_call_boundness(db), inner: CallableBindingInner::Single(binding), }; } @@ -257,14 +266,14 @@ impl<'db> CallableBinding<'db> { // // [1] https://github.com/python/typing/pull/1839 let overloads = signature - .iter() + .iter(db) .map(|signature| Binding::bind(db, signature, arguments.as_ref())) .collect::>() .into_boxed_slice(); CallableBinding { - ty: signature.ty, - signature_ty: signature.signature_ty, - dunder_call_boundness: signature.dunder_call_boundness, + ty: signature.ty(db), + signature_ty: signature.signature_ty(db), + dunder_call_boundness: signature.dunder_call_boundness(db), inner: CallableBindingInner::Overloaded(overloads), } } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index d0839aa4515d2..80e368b8e75f0 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -285,7 +285,7 @@ impl<'db> Class<'db> { Ok(bindings) => Ok(bindings.return_type(db)), Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError { - kind: MetaclassErrorKind::NotCallable(bindings.ty), + kind: MetaclassErrorKind::NotCallable(bindings.callable_type(db)), }), // TODO we should also check for binding errors that would indicate the metaclass diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 8b14af7dce9d6..d337d42087812 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3655,7 +3655,7 @@ impl<'db> TypeInferenceBuilder<'db> { } Err(CallError(_, bindings)) => { - bindings.report_diagnostics(&self.context, call_expression.into()); + bindings.report_diagnostics(self.db(), &self.context, call_expression.into()); bindings.return_type(self.db()) } } @@ -5384,7 +5384,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__getitem__` of type `{}` is not callable on object of type `{}`", - bindings.ty.display(self.db()), + bindings.callable_type(self.db()).display(self.db()), value_ty.display(self.db()), ), ); @@ -5434,7 +5434,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - bindings.ty.display(self.db()), + bindings.callable_type(self.db()).display(self.db()), value_ty.display(self.db()), ), ); diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index c4eac65bcd545..1fca1880d7acf 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -18,98 +18,110 @@ use crate::Db; 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)] +#[salsa::tracked] pub(crate) struct Signatures<'db> { /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, /// For an object that's callable via a `__call__` method, the type of that method. For an /// object that is directly callable, the type of the object. pub(crate) signature_ty: Type<'db>, + #[return_ref] inner: SignaturesInner<'db>, } #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -enum SignaturesInner<'db> { +pub(crate) enum SignaturesInner<'db> { Single(CallableSignature<'db>), Union(Box<[CallableSignature<'db>]>), } impl<'db> Signatures<'db> { pub(crate) fn not_callable( + db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, ) -> Self { - Self { + Self::new( + db, ty, - signature_ty: ty, - inner: SignaturesInner::Single(CallableSignature::not_callable( + ty, + SignaturesInner::Single(CallableSignature::not_callable( + db, ty, signature_ty, dunder_call_boundness, None, )), - } + ) } - pub(crate) fn single(signature: CallableSignature<'db>) -> Self { - Self { - ty: signature.ty, - signature_ty: signature.signature_ty, - inner: SignaturesInner::Single(signature), - } + pub(crate) fn single(db: &'db dyn Db, signature: CallableSignature<'db>) -> Self { + Self::new( + db, + signature.ty(db), + signature.signature_ty(db), + SignaturesInner::Single(signature), + ) } /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is /// empty. - pub(crate) fn from_union(ty: Type<'db>, signature_ty: Type<'db>, elements: I) -> Self + pub(crate) fn from_union( + db: &'db dyn Db, + ty: Type<'db>, + signature_ty: Type<'db>, + elements: I, + ) -> Self where I: IntoIterator, I::IntoIter: Iterator>, { let mut signatures = Vec::new(); for element in elements { - match element.inner { - SignaturesInner::Single(signature) => signatures.push(signature), - SignaturesInner::Union(union) => signatures.extend(union), + match element.inner(db) { + SignaturesInner::Single(signature) => signatures.push(*signature), + SignaturesInner::Union(union) => signatures.extend(union.into_iter().copied()), } } if signatures.len() == 1 { let first_signature = signatures .pop() .expect("signatures should have one element"); - return Self { + return Self::new( + db, ty, signature_ty, - inner: SignaturesInner::Single(first_signature), - }; + SignaturesInner::Single(first_signature), + ); } - Self { + Self::new( + db, ty, signature_ty, - inner: SignaturesInner::Union(signatures.into()), - } + SignaturesInner::Union(signatures.into()), + ) } - pub(crate) fn as_single(&self) -> Option<&CallableSignature<'db>> { - match &self.inner { - SignaturesInner::Single(signature) => Some(signature), + pub(crate) fn as_single(self, db: &'db dyn Db) -> Option> { + match self.inner(db) { + SignaturesInner::Single(signature) => Some(*signature), SignaturesInner::Union(_) => None, } } - pub(crate) fn iter(&self) -> std::slice::Iter> { - match &self.inner { - SignaturesInner::Single(signature) => std::slice::from_ref(signature).iter(), - SignaturesInner::Union(signatures) => signatures.iter(), + pub(crate) fn iter(self, db: &'db dyn Db) -> impl Iterator> { + match self.inner(db) { + SignaturesInner::Single(signature) => std::slice::from_ref(signature).iter().copied(), + SignaturesInner::Union(signatures) => signatures.iter().copied(), } } } /// 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)] +#[salsa::tracked] pub(crate) struct CallableSignature<'db> { /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, @@ -125,11 +137,12 @@ pub(crate) struct CallableSignature<'db> { /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. pub(crate) bound_type: Option>, + #[return_ref] inner: CallableSignatureInner<'db>, } #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -enum CallableSignatureInner<'db> { +pub(crate) enum CallableSignatureInner<'db> { /// The type being called is not callable NotCallable, Single(Signature<'db>), @@ -138,39 +151,44 @@ enum CallableSignatureInner<'db> { impl<'db> CallableSignature<'db> { pub(crate) fn not_callable( + db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, bound_type: Option>, ) -> Self { - Self { + Self::new( + db, ty, signature_ty, dunder_call_boundness, bound_type, - inner: CallableSignatureInner::NotCallable, - } + CallableSignatureInner::NotCallable, + ) } - pub(crate) fn new( + pub(crate) fn single( + db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, bound_type: Option>, signature: Signature<'db>, ) -> Self { - Self { + Self::new( + db, ty, signature_ty, dunder_call_boundness, bound_type, - inner: CallableSignatureInner::Single(signature), - } + CallableSignatureInner::Single(signature), + ) } /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if /// the iterator is empty. pub(crate) fn from_overloads( + db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, @@ -184,56 +202,80 @@ impl<'db> CallableSignature<'db> { let mut iter = overloads.into_iter(); let first_overload = iter.next().expect("overloads should not be empty"); let Some(second_overload) = iter.next() else { - return Self { + return Self::new( + db, ty, signature_ty, dunder_call_boundness, bound_type, - inner: CallableSignatureInner::Single(first_overload), - }; + CallableSignatureInner::Single(first_overload), + ); }; let mut overloads = vec![first_overload, second_overload]; overloads.extend(iter); - Self { + Self::new( + db, ty, signature_ty, dunder_call_boundness, bound_type, - inner: CallableSignatureInner::Overloaded(overloads.into()), - } + CallableSignatureInner::Overloaded(overloads.into()), + ) } /// Return a signature for a dynamic callable - pub(crate) fn dynamic(ty: Type<'db>, dunder_call_boundness: Option) -> Self { + pub(crate) fn dynamic( + db: &'db dyn Db, + ty: Type<'db>, + dunder_call_boundness: Option, + ) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::new(ty, ty, dunder_call_boundness, None, signature) + Self::new( + db, + ty, + ty, + dunder_call_boundness, + None, + CallableSignatureInner::Single(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, dunder_call_boundness: Option) -> Self { + pub(crate) fn todo( + db: &'db dyn Db, + reason: &'static str, + dunder_call_boundness: Option, + ) -> Self { let ty = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::new(ty, ty, dunder_call_boundness, None, signature) + Self::new( + db, + ty, + ty, + dunder_call_boundness, + None, + CallableSignatureInner::Single(signature), + ) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. - pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { - match &self.inner { + pub(crate) fn as_single(&self, db: &'db dyn Db) -> Option<&Signature<'db>> { + match self.inner(db) { CallableSignatureInner::NotCallable => None, CallableSignatureInner::Single(signature) => Some(signature), CallableSignatureInner::Overloaded(_) => None, } } - pub(crate) fn iter(&self) -> std::slice::Iter> { - match &self.inner { + pub(crate) fn iter(&self, db: &'db dyn Db) -> std::slice::Iter> { + match self.inner(db) { CallableSignatureInner::NotCallable => [].iter(), CallableSignatureInner::Single(signature) => std::slice::from_ref(signature).iter(), CallableSignatureInner::Overloaded(signatures) => signatures.iter(), @@ -241,8 +283,8 @@ impl<'db> CallableSignature<'db> { } /// Returns whether this signature is callable. - pub(crate) fn is_callable(&self) -> bool { - !matches!(&self.inner, CallableSignatureInner::NotCallable) + pub(crate) fn is_callable(self, db: &'db dyn Db) -> bool { + !matches!(self.inner(db), CallableSignatureInner::NotCallable) } } From d0a07fc93d7588aa6b6e27f167b2eda4e6263f33 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 10:40:04 -0400 Subject: [PATCH 18/57] Simplify signatures a bit --- .../src/types/signatures.rs | 124 ++++-------------- 1 file changed, 25 insertions(+), 99 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 1fca1880d7acf..b3994caf9a32b 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -26,13 +26,7 @@ pub(crate) struct Signatures<'db> { /// object that is directly callable, the type of the object. pub(crate) signature_ty: Type<'db>, #[return_ref] - inner: SignaturesInner<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) enum SignaturesInner<'db> { - Single(CallableSignature<'db>), - Union(Box<[CallableSignature<'db>]>), + signatures: Vec>, } impl<'db> Signatures<'db> { @@ -46,13 +40,13 @@ impl<'db> Signatures<'db> { db, ty, ty, - SignaturesInner::Single(CallableSignature::not_callable( + vec![CallableSignature::not_callable( db, ty, signature_ty, dunder_call_boundness, None, - )), + )], ) } @@ -61,7 +55,7 @@ impl<'db> Signatures<'db> { db, signature.ty(db), signature.signature_ty(db), - SignaturesInner::Single(signature), + vec![signature], ) } @@ -74,48 +68,21 @@ impl<'db> Signatures<'db> { elements: I, ) -> Self where - I: IntoIterator, - I::IntoIter: Iterator>, + I: IntoIterator>, { - let mut signatures = Vec::new(); - for element in elements { - match element.inner(db) { - SignaturesInner::Single(signature) => signatures.push(*signature), - SignaturesInner::Union(union) => signatures.extend(union.into_iter().copied()), - } - } - if signatures.len() == 1 { - let first_signature = signatures - .pop() - .expect("signatures should have one element"); - return Self::new( - db, - ty, - signature_ty, - SignaturesInner::Single(first_signature), - ); - } - - Self::new( - db, - ty, - signature_ty, - SignaturesInner::Union(signatures.into()), - ) + let signatures = elements.into_iter().flat_map(|s| s.iter(db)).collect(); + Self::new(db, ty, signature_ty, signatures) } pub(crate) fn as_single(self, db: &'db dyn Db) -> Option> { - match self.inner(db) { - SignaturesInner::Single(signature) => Some(*signature), - SignaturesInner::Union(_) => None, + match self.signatures(db).as_slice() { + [signature] => Some(*signature), + _ => None, } } pub(crate) fn iter(self, db: &'db dyn Db) -> impl Iterator> { - match self.inner(db) { - SignaturesInner::Single(signature) => std::slice::from_ref(signature).iter().copied(), - SignaturesInner::Union(signatures) => signatures.iter().copied(), - } + self.signatures(db).iter().copied() } } @@ -138,15 +105,7 @@ pub(crate) struct CallableSignature<'db> { pub(crate) bound_type: Option>, #[return_ref] - inner: CallableSignatureInner<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) enum CallableSignatureInner<'db> { - /// The type being called is not callable - NotCallable, - Single(Signature<'db>), - Overloaded(Box<[Signature<'db>]>), + overloads: Vec>, } impl<'db> CallableSignature<'db> { @@ -163,7 +122,7 @@ impl<'db> CallableSignature<'db> { signature_ty, dunder_call_boundness, bound_type, - CallableSignatureInner::NotCallable, + vec![], ) } @@ -181,7 +140,7 @@ impl<'db> CallableSignature<'db> { signature_ty, dunder_call_boundness, bound_type, - CallableSignatureInner::Single(signature), + vec![signature], ) } @@ -196,30 +155,16 @@ impl<'db> CallableSignature<'db> { overloads: I, ) -> Self where - I: IntoIterator, - I::IntoIter: Iterator>, + I: IntoIterator>, { - let mut iter = overloads.into_iter(); - let first_overload = iter.next().expect("overloads should not be empty"); - let Some(second_overload) = iter.next() else { - return Self::new( - db, - ty, - signature_ty, - dunder_call_boundness, - bound_type, - CallableSignatureInner::Single(first_overload), - ); - }; - let mut overloads = vec![first_overload, second_overload]; - overloads.extend(iter); + let overloads = overloads.into_iter().collect(); Self::new( db, ty, signature_ty, dunder_call_boundness, bound_type, - CallableSignatureInner::Overloaded(overloads.into()), + overloads, ) } @@ -233,14 +178,7 @@ impl<'db> CallableSignature<'db> { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::new( - db, - ty, - ty, - dunder_call_boundness, - None, - CallableSignatureInner::Single(signature), - ) + Self::new(db, ty, ty, dunder_call_boundness, None, vec![signature]) } /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo @@ -255,36 +193,24 @@ impl<'db> CallableSignature<'db> { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::new( - db, - ty, - ty, - dunder_call_boundness, - None, - CallableSignatureInner::Single(signature), - ) + Self::new(db, ty, ty, dunder_call_boundness, None, vec![signature]) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. pub(crate) fn as_single(&self, db: &'db dyn Db) -> Option<&Signature<'db>> { - match self.inner(db) { - CallableSignatureInner::NotCallable => None, - CallableSignatureInner::Single(signature) => Some(signature), - CallableSignatureInner::Overloaded(_) => None, + match self.overloads(db).as_slice() { + [signature] => Some(signature), + _ => None, } } - pub(crate) fn iter(&self, db: &'db dyn Db) -> std::slice::Iter> { - match self.inner(db) { - CallableSignatureInner::NotCallable => [].iter(), - CallableSignatureInner::Single(signature) => std::slice::from_ref(signature).iter(), - CallableSignatureInner::Overloaded(signatures) => signatures.iter(), - } + pub(crate) fn iter(&self, db: &'db dyn Db) -> impl Iterator> { + self.overloads(db).iter() } /// Returns whether this signature is callable. pub(crate) fn is_callable(self, db: &'db dyn Db) -> bool { - !matches!(self.inner(db), CallableSignatureInner::NotCallable) + !self.overloads(db).is_empty() } } From 9d525c7c25e3c3f9f1aeef0d8bbc25315e999890 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 10:48:43 -0400 Subject: [PATCH 19/57] Store signature in callable binding too --- crates/red_knot_python_semantic/src/types.rs | 4 +- .../src/types/call/bind.rs | 60 +++++++++---------- .../src/types/infer.rs | 5 +- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index f2b9dd5155ec7..4bf0ef03cbc4d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2698,7 +2698,7 @@ impl<'db> Type<'db> { arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { let signatures = self.signatures(db, self, None); - let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; + let mut bindings = Bindings::bind(db, signatures, arguments).into_result(db)?; for binding in bindings.bindings_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -3013,7 +3013,7 @@ impl<'db> Type<'db> { { Symbol::Type(dunder_callable, boundness) => { let signatures = dunder_callable.signatures(db, dunder_callable, None); - let bindings = Bindings::bind(db, signatures, arguments).into_result()?; + let bindings = Bindings::bind(db, signatures, arguments).into_result(db)?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 5711036b97956..063f026b70161 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -104,7 +104,7 @@ impl<'db> Bindings<'db> { /// Returns whether all bindings were successful, or an error describing why some bindings were /// unsuccessful. - pub(crate) fn into_result(self) -> Result> { + pub(crate) fn into_result(self, db: &'db dyn Db) -> Result> { // In order of precedence: // // - If every union element is Ok, then the union is too. @@ -124,7 +124,7 @@ impl<'db> Bindings<'db> { let mut any_binding_error = false; let mut all_not_callable = true; for binding in self.bindings() { - let result = binding.as_result(); + let result = binding.as_result(db); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable)); @@ -163,12 +163,7 @@ impl<'db> Bindings<'db> { /// report a single diagnostic if we couldn't match any union element or overload. /// TODO: Update this to add subdiagnostics about how we failed to match each union element and /// overload. - pub(crate) fn report_diagnostics( - &self, - db: &'db dyn Db, - context: &InferContext<'db>, - node: ast::AnyNodeRef, - ) { + pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { // If all union elements are not callable, report that the union as a whole is not // callable. if self.bindings().iter().all(|b| !b.is_callable()) { @@ -177,7 +172,7 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signatures.ty(db).display(context.db()) + self.signatures.ty(context.db()).display(context.db()) ), ); return; @@ -186,7 +181,11 @@ impl<'db> Bindings<'db> { // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. - if let Some(first) = self.bindings().iter().find(|b| b.as_result().is_err()) { + if let Some(first) = self + .bindings() + .iter() + .find(|b| b.as_result(context.db()).is_err()) + { first.report_diagnostics(context, node); } } @@ -209,9 +208,7 @@ impl<'db> Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallableBinding<'db> { - pub(crate) ty: Type<'db>, - pub(crate) signature_ty: Type<'db>, - pub(crate) dunder_call_boundness: Option, + pub(crate) signature: CallableSignature<'db>, inner: CallableBindingInner<'db>, } @@ -234,9 +231,7 @@ impl<'db> CallableBinding<'db> { ) -> Self { if !signature.is_callable(db) { return CallableBinding { - ty: signature.ty(db), - signature_ty: signature.signature_ty(db), - dunder_call_boundness: signature.dunder_call_boundness(db), + signature, inner: CallableBindingInner::NotCallable, }; } @@ -252,9 +247,7 @@ impl<'db> CallableBinding<'db> { if let Some(single) = signature.as_single(db) { let binding = Binding::bind(db, single, arguments.as_ref()); return CallableBinding { - ty: signature.ty(db), - signature_ty: signature.signature_ty(db), - dunder_call_boundness: signature.dunder_call_boundness(db), + signature, inner: CallableBindingInner::Single(binding), }; } @@ -271,9 +264,7 @@ impl<'db> CallableBinding<'db> { .collect::>() .into_boxed_slice(); CallableBinding { - ty: signature.ty(db), - signature_ty: signature.signature_ty(db), - dunder_call_boundness: signature.dunder_call_boundness(db), + signature, inner: CallableBindingInner::Overloaded(overloads), } } @@ -294,7 +285,7 @@ impl<'db> CallableBinding<'db> { } } - fn as_result(&self) -> Result<(), CallErrorKind> { + fn as_result(&self, db: &'db dyn Db) -> Result<(), CallErrorKind> { if matches!(self.inner, CallableBindingInner::NotCallable) { return Err(CallErrorKind::NotCallable); } @@ -303,7 +294,7 @@ impl<'db> CallableBinding<'db> { return Err(CallErrorKind::BindingError); } - if self.dunder_is_possibly_unbound() { + if self.dunder_is_possibly_unbound(db) { return Err(CallErrorKind::PossiblyNotCallable); } @@ -322,8 +313,11 @@ impl<'db> CallableBinding<'db> { /// Returns whether this binding is for an object that is callable via a `__call__` method that /// is possibly unbound. - pub(crate) fn dunder_is_possibly_unbound(&self) -> bool { - matches!(self.dunder_call_boundness, Some(Boundness::PossiblyUnbound)) + pub(crate) fn dunder_is_possibly_unbound(&self, db: &'db dyn Db) -> bool { + matches!( + self.signature.dunder_call_boundness(db), + Some(Boundness::PossiblyUnbound) + ) } /// Returns the overload that matched for this call binding. Returns `None` if none of the @@ -366,25 +360,26 @@ impl<'db> CallableBinding<'db> { node, format_args!( "Object of type `{}` is not callable", - self.ty.display(context.db()), + self.signature.ty(context.db()).display(context.db()), ), ); return; } - if self.dunder_is_possibly_unbound() { + if self.dunder_is_possibly_unbound(context.db()) { context.report_lint( &CALL_NON_CALLABLE, node, format_args!( "Object of type `{}` is not callable (possibly unbound `__call__` method)", - self.ty.display(context.db()), + self.signature.ty(context.db()).display(context.db()), ), ); return; } - let callable_descriptor = CallableDescriptor::new(context.db(), self.ty); + let callable_descriptor = + CallableDescriptor::new(context.db(), self.signature.ty(context.db())); if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, @@ -401,12 +396,13 @@ impl<'db> CallableBinding<'db> { return; } - let callable_descriptor = CallableDescriptor::new(context.db(), self.signature_ty); + let callable_descriptor = + CallableDescriptor::new(context.db(), self.signature.signature_ty(context.db())); for overload in self.overloads() { overload.report_diagnostics( context, node, - self.signature_ty, + self.signature.signature_ty(context.db()), callable_descriptor.as_ref(), ); } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index d337d42087812..a4f8fcb2504f7 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3547,7 +3547,8 @@ impl<'db> TypeInferenceBuilder<'db> { Ok(bindings) => { for binding in bindings.bindings() { let Some(known_function) = binding - .ty + .signature + .ty(self.db()) .into_function_literal() .and_then(|function_type| function_type.known(self.db())) else { @@ -3655,7 +3656,7 @@ impl<'db> TypeInferenceBuilder<'db> { } Err(CallError(_, bindings)) => { - bindings.report_diagnostics(self.db(), &self.context, call_expression.into()); + bindings.report_diagnostics(&self.context, call_expression.into()); bindings.return_type(self.db()) } } From b6af68362fabc42a6e8e4c552f27015fd11d6a29 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 10:56:01 -0400 Subject: [PATCH 20/57] Change to iter/iter_mut --- crates/red_knot_python_semantic/src/types.rs | 2 +- .../src/types/call/bind.rs | 22 ++++++++----------- .../src/types/infer.rs | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4bf0ef03cbc4d..821e26b06a053 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2699,7 +2699,7 @@ impl<'db> Type<'db> { ) -> Result, CallError<'db>> { let signatures = self.signatures(db, self, None); let mut bindings = Bindings::bind(db, signatures, arguments).into_result(db)?; - for binding in bindings.bindings_mut() { + for binding in bindings.iter_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case // listed here should have a corresponding clause above in `signatures`. diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 063f026b70161..e44c03807e579 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -123,7 +123,7 @@ impl<'db> Bindings<'db> { let mut all_ok = true; let mut any_binding_error = false; let mut all_not_callable = true; - for binding in self.bindings() { + for binding in self.iter() { let result = binding.as_result(db); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); @@ -144,17 +144,17 @@ impl<'db> Bindings<'db> { } } - pub(crate) fn bindings(&self) -> &[CallableBinding<'db>] { + pub(crate) fn iter(&self) -> impl Iterator> + '_ { match &self.inner { - BindingsInner::Single(binding) => std::slice::from_ref(binding), - BindingsInner::Union(bindings) => bindings, + BindingsInner::Single(binding) => std::slice::from_ref(binding).iter(), + BindingsInner::Union(bindings) => bindings.iter(), } } - pub(crate) fn bindings_mut(&mut self) -> &mut [CallableBinding<'db>] { + pub(crate) fn iter_mut(&mut self) -> impl Iterator> + '_ { match &mut self.inner { - BindingsInner::Single(binding) => std::slice::from_mut(binding), - BindingsInner::Union(bindings) => bindings, + BindingsInner::Single(binding) => std::slice::from_mut(binding).iter_mut(), + BindingsInner::Union(bindings) => bindings.iter_mut(), } } @@ -166,7 +166,7 @@ impl<'db> Bindings<'db> { pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { // If all union elements are not callable, report that the union as a whole is not // callable. - if self.bindings().iter().all(|b| !b.is_callable()) { + if self.iter().all(|b| !b.is_callable()) { context.report_lint( &CALL_NON_CALLABLE, node, @@ -181,11 +181,7 @@ impl<'db> Bindings<'db> { // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. - if let Some(first) = self - .bindings() - .iter() - .find(|b| b.as_result(context.db()).is_err()) - { + if let Some(first) = self.iter().find(|b| b.as_result(context.db()).is_err()) { first.report_diagnostics(context, node); } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index a4f8fcb2504f7..c07ba858b6cac 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3545,7 +3545,7 @@ impl<'db> TypeInferenceBuilder<'db> { let call_arguments = self.infer_arguments(arguments, parameter_expectations); match function_type.try_call(self.db(), &call_arguments) { Ok(bindings) => { - for binding in bindings.bindings() { + for binding in bindings.iter() { let Some(known_function) = binding .signature .ty(self.db()) From 834d4aa7d3261a2cc4115ced6f86f09af6a2e2f0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 11:10:25 -0400 Subject: [PATCH 21/57] fix doc links --- crates/red_knot_python_semantic/src/types.rs | 14 +++++++------- crates/red_knot_python_semantic/src/types/call.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 821e26b06a053..891a79b22d185 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2320,13 +2320,13 @@ impl<'db> Type<'db> { /// Returns the call signatures of a type. /// - /// Note that all types have a valid [`Signatures`], even if the type is not callable. If you - /// need to determine if a type is callable, use [`Signatures::is_callable`]. Though note that - /// "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 inconsisent, 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`] and [`CallErrorKind::NotCallable`]. + /// 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 inconsisent, 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, diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index aae08eff58111..4706b91a68190 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -7,7 +7,7 @@ mod bind; pub(super) use arguments::{Argument, CallArguments}; pub(super) use bind::Bindings; -/// Wraps a [`CallBindings`] for an unsuccessful call with information about why the call was +/// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. #[derive(Debug, Clone, PartialEq, Eq)] pub(super) struct CallError<'db>(pub(super) CallErrorKind, pub(super) Box>); From 675e496820355c375734213ca77f65e9a6a71b57 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 11:28:29 -0400 Subject: [PATCH 22/57] lint --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 891a79b22d185..3c95ce253f318 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2323,7 +2323,7 @@ impl<'db> Type<'db> { /// 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 inconsisent, such that there's no argument list that's valid + /// 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`]. From 19a1e537aae7d5f608e1473fdb55c4e94a5a01b3 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 11:52:43 -0400 Subject: [PATCH 23/57] Untrack signatures --- crates/red_knot_python_semantic/src/types.rs | 102 +++++++--------- .../src/types/call/bind.rs | 53 ++++---- .../src/types/class.rs | 2 +- .../src/types/infer.rs | 6 +- .../src/types/signatures.rs | 115 ++++++++---------- 5 files changed, 123 insertions(+), 155 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 3c95ce253f318..a14e8a7f11242 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2327,6 +2327,7 @@ impl<'db> Type<'db> { /// 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`]. + #[salsa::tracked(return_ref)] fn signatures( self, db: &'db dyn Db, @@ -2337,14 +2338,13 @@ impl<'db> Type<'db> { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); let signature = CallableSignature::single( - db, callable_ty, self, dunder_call_boundness, Some(bound_method.self_instance(db)), signature.clone(), ); - Signatures::single(db, signature) + Signatures::single(signature) } Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { @@ -2363,7 +2363,6 @@ impl<'db> Type<'db> { let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( - db, callable_ty, self, dunder_call_boundness, @@ -2406,7 +2405,7 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(db, signature) + Signatures::single(signature) } Type::Callable(CallableType::WrapperDescriptorDunderGet) => { @@ -2418,7 +2417,6 @@ impl<'db> Type<'db> { // removed. let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( - db, callable_ty, self, dunder_call_boundness, @@ -2471,20 +2469,16 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(db, signature) + Signatures::single(signature) } - Type::FunctionLiteral(function_type) => Signatures::single( - db, - CallableSignature::single( - db, - callable_ty, - self, - dunder_call_boundness, - None, - function_type.signature(db).clone(), - ), - ), + Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::single( + callable_ty, + self, + dunder_call_boundness, + None, + function_type.signature(db).clone(), + )), Type::ClassLiteral(ClassLiteralType { class }) if class.is_known(db, KnownClass::Bool) => @@ -2494,7 +2488,6 @@ impl<'db> Type<'db> { // def __new__(cls, o: object = ..., /) -> Self: ... // ``` let signature = CallableSignature::single( - db, callable_ty, self, dunder_call_boundness, @@ -2510,7 +2503,7 @@ impl<'db> Type<'db> { Some(KnownClass::Bool.to_instance(db)), ), ); - Signatures::single(db, signature) + Signatures::single(signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2524,7 +2517,6 @@ impl<'db> Type<'db> { // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` let signature = CallableSignature::from_overloads( - db, callable_ty, self, dunder_call_boundness, @@ -2562,7 +2554,7 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(db, signature) + Signatures::single(signature) } Type::ClassLiteral(ClassLiteralType { class }) @@ -2576,7 +2568,6 @@ impl<'db> Type<'db> { // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` let signature = CallableSignature::from_overloads( - db, callable_ty, self, dunder_call_boundness, @@ -2612,30 +2603,29 @@ impl<'db> Type<'db> { ), ], ); - Signatures::single(db, signature) + Signatures::single(signature) } // TODO annotated return type on `__new__` or metaclass `__call__` // TODO check call vs signatures of `__new__` and/or `__init__` Type::ClassLiteral(ClassLiteralType { .. }) => { let signature = CallableSignature::single( - db, callable_ty, self, dunder_call_boundness, None, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); - Signatures::single(db, signature) + Signatures::single(signature) } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - ClassBase::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).signatures(db, callable_ty, dunder_call_boundness) - } - ClassBase::Class(class) => { - Type::class_literal(class).signatures(db, callable_ty, dunder_call_boundness) - } + ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) + .signatures(db, callable_ty, dunder_call_boundness) + .clone(), + ClassBase::Class(class) => Type::class_literal(class) + .signatures(db, callable_ty, dunder_call_boundness) + .clone(), }, Type::Instance(_) => { @@ -2651,25 +2641,23 @@ impl<'db> Type<'db> { ) .symbol { - Symbol::Type(dunder_callable, boundness) => { - dunder_callable.signatures(db, callable_ty, Some(boundness)) - } + Symbol::Type(dunder_callable, boundness) => dunder_callable + .signatures(db, callable_ty, Some(boundness)) + .clone(), Symbol::Unbound => { - Signatures::not_callable(db, callable_ty, self, dunder_call_boundness) + Signatures::not_callable(callable_ty, self, dunder_call_boundness) } } } // 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( - db, - CallableSignature::dynamic(db, self, dunder_call_boundness), - ), + Type::Dynamic(_) | Type::Never => { + Signatures::single(CallableSignature::dynamic(self, dunder_call_boundness)) + } // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( - db, callable_ty, self, union @@ -2678,12 +2666,12 @@ impl<'db> Type<'db> { .map(|element| element.signatures(db, *element, dunder_call_boundness)), ), - Type::Intersection(_) => Signatures::single( - db, - CallableSignature::todo(db, "Type::Intersection.call()", dunder_call_boundness), - ), + Type::Intersection(_) => Signatures::single(CallableSignature::todo( + "Type::Intersection.call()", + dunder_call_boundness, + )), - _ => Signatures::not_callable(db, callable_ty, self, dunder_call_boundness), + _ => Signatures::not_callable(callable_ty, self, dunder_call_boundness), } } @@ -2698,7 +2686,7 @@ impl<'db> Type<'db> { arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { let signatures = self.signatures(db, self, None); - let mut bindings = Bindings::bind(db, signatures, arguments).into_result(db)?; + let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.iter_mut() { // For certain known callables, we have special case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -3013,7 +3001,7 @@ impl<'db> Type<'db> { { Symbol::Type(dunder_callable, boundness) => { let signatures = dunder_callable.signatures(db, dunder_callable, None); - let bindings = Bindings::bind(db, signatures, arguments).into_result(db)?; + let bindings = Bindings::bind(db, signatures, arguments).into_result()?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } @@ -3977,7 +3965,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type(db).display(db), + dunder_iter_type = bindings.callable_type().display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => { report_not_iterable(format_args!( @@ -3985,7 +3973,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type(db).display(db), + dunder_iter_type = bindings.callable_type().display(db), )); } CallErrorKind::PossiblyNotCallable => { @@ -3994,7 +3982,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type(db).display(db), + dunder_iter_type = bindings.callable_type().display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4008,7 +3996,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type(db).display(db), + dunder_iter_type = bindings.callable_type().display(db), )), } @@ -4083,7 +4071,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4098,7 +4086,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4118,7 +4106,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), } } @@ -4141,7 +4129,7 @@ impl<'db> IterationErrorKind<'db> { its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4155,7 +4143,7 @@ impl<'db> IterationErrorKind<'db> { because it has no `__iter__` method and its `__getitem__` attribute \ (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4175,7 +4163,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type(db).display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index e44c03807e579..34dc92921fb99 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -27,7 +27,7 @@ use ruff_text_size::Ranged; /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Bindings<'db> { - pub(crate) signatures: Signatures<'db>, + pub(crate) signatures: &'db Signatures<'db>, inner: BindingsInner<'db>, } @@ -48,10 +48,10 @@ impl<'db> Bindings<'db> { /// overload (if any). pub(crate) fn bind( db: &'db dyn Db, - signatures: Signatures<'db>, + signatures: &'db Signatures<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if let Some(signature) = signatures.as_single(db) { + if let Some(signature) = signatures.as_single() { return Bindings { signatures, inner: BindingsInner::Single(CallableBinding::bind(db, signature, arguments)), @@ -59,7 +59,7 @@ impl<'db> Bindings<'db> { } let bindings = signatures - .iter(db) + .iter() .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect::>() .into_boxed_slice(); @@ -73,8 +73,8 @@ impl<'db> Bindings<'db> { matches!(&self.inner, BindingsInner::Single(_)) } - pub(crate) fn callable_type(&self, db: &'db dyn Db) -> Type<'db> { - self.signatures.ty(db) + pub(crate) fn callable_type(&self) -> Type<'db> { + self.signatures.ty } /// Returns the return type of the call. For successful calls, this is the actual return type. @@ -104,7 +104,7 @@ impl<'db> Bindings<'db> { /// Returns whether all bindings were successful, or an error describing why some bindings were /// unsuccessful. - pub(crate) fn into_result(self, db: &'db dyn Db) -> Result> { + pub(crate) fn into_result(self) -> Result> { // In order of precedence: // // - If every union element is Ok, then the union is too. @@ -124,7 +124,7 @@ impl<'db> Bindings<'db> { let mut any_binding_error = false; let mut all_not_callable = true; for binding in self.iter() { - let result = binding.as_result(db); + let result = binding.as_result(); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable)); @@ -172,7 +172,7 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signatures.ty(context.db()).display(context.db()) + self.signatures.ty.display(context.db()) ), ); return; @@ -181,7 +181,7 @@ impl<'db> Bindings<'db> { // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. - if let Some(first) = self.iter().find(|b| b.as_result(context.db()).is_err()) { + if let Some(first) = self.iter().find(|b| b.as_result().is_err()) { first.report_diagnostics(context, node); } } @@ -204,7 +204,7 @@ impl<'db> Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallableBinding<'db> { - pub(crate) signature: CallableSignature<'db>, + pub(crate) signature: &'db CallableSignature<'db>, inner: CallableBindingInner<'db>, } @@ -222,10 +222,10 @@ impl<'db> CallableBinding<'db> { /// all parameters, and any errors resulting from binding the call. fn bind( db: &'db dyn Db, - signature: CallableSignature<'db>, + signature: &'db CallableSignature<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if !signature.is_callable(db) { + if !signature.is_callable() { return CallableBinding { signature, inner: CallableBindingInner::NotCallable, @@ -234,13 +234,13 @@ impl<'db> CallableBinding<'db> { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let arguments = if let Some(bound_type) = signature.bound_type(db) { + let arguments = if let Some(bound_type) = signature.bound_type { Cow::Owned(arguments.with_self(bound_type)) } else { Cow::Borrowed(arguments) }; - if let Some(single) = signature.as_single(db) { + if let Some(single) = signature.as_single() { let binding = Binding::bind(db, single, arguments.as_ref()); return CallableBinding { signature, @@ -255,7 +255,7 @@ impl<'db> CallableBinding<'db> { // // [1] https://github.com/python/typing/pull/1839 let overloads = signature - .iter(db) + .iter() .map(|signature| Binding::bind(db, signature, arguments.as_ref())) .collect::>() .into_boxed_slice(); @@ -281,7 +281,7 @@ impl<'db> CallableBinding<'db> { } } - fn as_result(&self, db: &'db dyn Db) -> Result<(), CallErrorKind> { + fn as_result(&self) -> Result<(), CallErrorKind> { if matches!(self.inner, CallableBindingInner::NotCallable) { return Err(CallErrorKind::NotCallable); } @@ -290,7 +290,7 @@ impl<'db> CallableBinding<'db> { return Err(CallErrorKind::BindingError); } - if self.dunder_is_possibly_unbound(db) { + if self.dunder_is_possibly_unbound() { return Err(CallErrorKind::PossiblyNotCallable); } @@ -309,9 +309,9 @@ impl<'db> CallableBinding<'db> { /// Returns whether this binding is for an object that is callable via a `__call__` method that /// is possibly unbound. - pub(crate) fn dunder_is_possibly_unbound(&self, db: &'db dyn Db) -> bool { + pub(crate) fn dunder_is_possibly_unbound(&self) -> bool { matches!( - self.signature.dunder_call_boundness(db), + self.signature.dunder_call_boundness, Some(Boundness::PossiblyUnbound) ) } @@ -356,26 +356,25 @@ impl<'db> CallableBinding<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signature.ty(context.db()).display(context.db()), + self.signature.ty.display(context.db()), ), ); return; } - if self.dunder_is_possibly_unbound(context.db()) { + if self.dunder_is_possibly_unbound() { context.report_lint( &CALL_NON_CALLABLE, node, format_args!( "Object of type `{}` is not callable (possibly unbound `__call__` method)", - self.signature.ty(context.db()).display(context.db()), + self.signature.ty.display(context.db()), ), ); return; } - let callable_descriptor = - CallableDescriptor::new(context.db(), self.signature.ty(context.db())); + let callable_descriptor = CallableDescriptor::new(context.db(), self.signature.ty); if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, @@ -393,12 +392,12 @@ impl<'db> CallableBinding<'db> { } let callable_descriptor = - CallableDescriptor::new(context.db(), self.signature.signature_ty(context.db())); + CallableDescriptor::new(context.db(), self.signature.signature_ty); for overload in self.overloads() { overload.report_diagnostics( context, node, - self.signature.signature_ty(context.db()), + self.signature.signature_ty, callable_descriptor.as_ref(), ); } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 80e368b8e75f0..55c34948d510d 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -285,7 +285,7 @@ impl<'db> Class<'db> { Ok(bindings) => Ok(bindings.return_type(db)), Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError { - kind: MetaclassErrorKind::NotCallable(bindings.callable_type(db)), + kind: MetaclassErrorKind::NotCallable(bindings.callable_type()), }), // TODO we should also check for binding errors that would indicate the metaclass diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index c07ba858b6cac..439fcb207ab21 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3548,7 +3548,7 @@ impl<'db> TypeInferenceBuilder<'db> { for binding in bindings.iter() { let Some(known_function) = binding .signature - .ty(self.db()) + .ty .into_function_literal() .and_then(|function_type| function_type.known(self.db())) else { @@ -5385,7 +5385,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type(self.db()).display(self.db()), + bindings.callable_type().display(self.db()), value_ty.display(self.db()), ), ); @@ -5435,7 +5435,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type(self.db()).display(self.db()), + bindings.callable_type().display(self.db()), value_ty.display(self.db()), ), ); diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index b3994caf9a32b..3cb711038b2be 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -18,77 +18,74 @@ use crate::Db; use ruff_python_ast::{self as ast, name::Name}; /// The signature of a possible union of callables. -#[salsa::tracked] +#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct Signatures<'db> { /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, /// For an object that's callable via a `__call__` method, the type of that method. For an /// object that is directly callable, the type of the object. pub(crate) signature_ty: Type<'db>, - #[return_ref] - signatures: Vec>, + elements: Vec>, } impl<'db> Signatures<'db> { pub(crate) fn not_callable( - db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, ) -> Self { - Self::new( - db, - ty, + Self { ty, - vec![CallableSignature::not_callable( - db, + signature_ty, + elements: vec![CallableSignature::not_callable( ty, signature_ty, dunder_call_boundness, None, )], - ) + } } - pub(crate) fn single(db: &'db dyn Db, signature: CallableSignature<'db>) -> Self { - Self::new( - db, - signature.ty(db), - signature.signature_ty(db), - vec![signature], - ) + pub(crate) fn single(signature: CallableSignature<'db>) -> Self { + Self { + ty: signature.ty, + signature_ty: signature.signature_ty, + elements: vec![signature], + } } /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is /// empty. - pub(crate) fn from_union( - db: &'db dyn Db, - ty: Type<'db>, - signature_ty: Type<'db>, - elements: I, - ) -> Self + pub(crate) fn from_union(ty: Type<'db>, signature_ty: Type<'db>, elements: I) -> Self where - I: IntoIterator>, + I: IntoIterator>, { - let signatures = elements.into_iter().flat_map(|s| s.iter(db)).collect(); - Self::new(db, ty, signature_ty, signatures) + let elements = elements + .into_iter() + .flat_map(|s| s.elements.iter().cloned()) + .collect(); + Self { + ty, + signature_ty, + elements, + } } - pub(crate) fn as_single(self, db: &'db dyn Db) -> Option> { - match self.signatures(db).as_slice() { - [signature] => Some(*signature), + pub(crate) fn as_single(&self) -> Option<&CallableSignature<'db>> { + match self.elements.as_slice() { + [signature] => Some(signature), _ => None, } } - pub(crate) fn iter(self, db: &'db dyn Db) -> impl Iterator> { - self.signatures(db).iter().copied() + pub(crate) fn iter(&self) -> impl Iterator> { + self.elements.iter() } } /// The signature of a single callable. If the callable is overloaded, there is a separate /// [`Signature`] for each overload. -#[salsa::tracked] +#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct CallableSignature<'db> { /// The type that is (hopefully) callable. pub(crate) ty: Type<'db>, @@ -104,50 +101,44 @@ pub(crate) struct CallableSignature<'db> { /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. pub(crate) bound_type: Option>, - #[return_ref] overloads: Vec>, } impl<'db> CallableSignature<'db> { pub(crate) fn not_callable( - db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, bound_type: Option>, ) -> Self { - Self::new( - db, + Self { ty, signature_ty, dunder_call_boundness, bound_type, - vec![], - ) + overloads: vec![], + } } pub(crate) fn single( - db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, bound_type: Option>, signature: Signature<'db>, ) -> Self { - Self::new( - db, + Self { ty, signature_ty, dunder_call_boundness, bound_type, - vec![signature], - ) + overloads: vec![signature], + } } /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if /// the iterator is empty. pub(crate) fn from_overloads( - db: &'db dyn Db, ty: Type<'db>, signature_ty: Type<'db>, dunder_call_boundness: Option, @@ -157,60 +148,50 @@ impl<'db> CallableSignature<'db> { where I: IntoIterator>, { - let overloads = overloads.into_iter().collect(); - Self::new( - db, + Self { ty, signature_ty, dunder_call_boundness, bound_type, - overloads, - ) + overloads: overloads.into_iter().collect(), + } } /// Return a signature for a dynamic callable - pub(crate) fn dynamic( - db: &'db dyn Db, - ty: Type<'db>, - dunder_call_boundness: Option, - ) -> Self { + pub(crate) fn dynamic(ty: Type<'db>, dunder_call_boundness: Option) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::new(db, ty, ty, dunder_call_boundness, None, vec![signature]) + Self::single(ty, ty, dunder_call_boundness, None, signature) } /// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo #[allow(unused_variables)] // 'reason' only unused in debug builds - pub(crate) fn todo( - db: &'db dyn Db, - reason: &'static str, - dunder_call_boundness: Option, - ) -> Self { + pub(crate) fn todo(reason: &'static str, dunder_call_boundness: Option) -> Self { let ty = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::new(db, ty, ty, dunder_call_boundness, None, vec![signature]) + Self::single(ty, ty, dunder_call_boundness, None, signature) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. - pub(crate) fn as_single(&self, db: &'db dyn Db) -> Option<&Signature<'db>> { - match self.overloads(db).as_slice() { + pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { + match self.overloads.as_slice() { [signature] => Some(signature), _ => None, } } - pub(crate) fn iter(&self, db: &'db dyn Db) -> impl Iterator> { - self.overloads(db).iter() + pub(crate) fn iter(&self) -> impl Iterator> { + self.overloads.iter() } /// Returns whether this signature is callable. - pub(crate) fn is_callable(self, db: &'db dyn Db) -> bool { - !self.overloads(db).is_empty() + pub(crate) fn is_callable(&self) -> bool { + !self.overloads.is_empty() } } From b2b943c5a79c361eea3f23a1ff9bf0990cee2175 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 11:56:42 -0400 Subject: [PATCH 24/57] Back to mut --- crates/red_knot_python_semantic/src/types.rs | 114 ++++++++---------- .../src/types/signatures.rs | 56 +++------ 2 files changed, 67 insertions(+), 103 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a14e8a7f11242..5a400a08b86e0 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2328,22 +2328,12 @@ impl<'db> Type<'db> { /// particular argument list, via [`try_call`][Self::try_call] and /// [`CallErrorKind::NotCallable`]. #[salsa::tracked(return_ref)] - fn signatures( - self, - db: &'db dyn Db, - callable_ty: Type<'db>, - dunder_call_boundness: Option, - ) -> Signatures<'db> { + fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let signature = CallableSignature::single( - callable_ty, - self, - dunder_call_boundness, - Some(bound_method.self_instance(db)), - signature.clone(), - ); + let mut signature = CallableSignature::single(callable_ty, self, signature.clone()); + signature.bound_type = Some(bound_method.self_instance(db)); Signatures::single(signature) } @@ -2365,8 +2355,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, - dunder_call_boundness, - None, [ Signature::new( Parameters::new([ @@ -2419,8 +2407,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, - dunder_call_boundness, - None, [ Signature::new( Parameters::new([ @@ -2475,8 +2461,6 @@ impl<'db> Type<'db> { Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::single( callable_ty, self, - dunder_call_boundness, - None, function_type.signature(db).clone(), )), @@ -2490,8 +2474,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::single( callable_ty, self, - dunder_call_boundness, - None, Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), @@ -2519,8 +2501,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, - dunder_call_boundness, - None, [ Signature::new( Parameters::new([Parameter::new( @@ -2570,8 +2550,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::from_overloads( callable_ty, self, - dunder_call_boundness, - None, [ Signature::new( Parameters::new([Parameter::new( @@ -2612,8 +2590,6 @@ impl<'db> Type<'db> { let signature = CallableSignature::single( callable_ty, self, - dunder_call_boundness, - None, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); Signatures::single(signature) @@ -2621,10 +2597,10 @@ impl<'db> Type<'db> { Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) - .signatures(db, callable_ty, dunder_call_boundness) + .signatures(db, callable_ty) .clone(), ClassBase::Class(class) => Type::class_literal(class) - .signatures(db, callable_ty, dunder_call_boundness) + .signatures(db, callable_ty) .clone(), }, @@ -2633,28 +2609,19 @@ impl<'db> Type<'db> { // signature of the dunder method, but will pass in the type of the object as the // "callable type". That ensures that we get errors like "`X` is not callable" // instead of "`` is not callable". - match self - .member_lookup_with_policy( - db, - Name::new_static("__call__"), - MemberLookupPolicy::NoInstanceFallback, - ) - .symbol - { - Symbol::Type(dunder_callable, boundness) => dunder_callable - .signatures(db, callable_ty, Some(boundness)) - .clone(), - Symbol::Unbound => { - Signatures::not_callable(callable_ty, self, dunder_call_boundness) - } - } + let Some((signatures, boundness)) = + self.dunder_signature(db, Some(callable_ty), "__call__") + else { + return Signatures::not_callable(callable_ty, self); + }; + let mut signatures = signatures.clone(); + signatures.set_dunder_call_boundness(boundness); + signatures } // 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, dunder_call_boundness)) - } + Type::Dynamic(_) | Type::Never => Signatures::single(CallableSignature::dynamic(self)), // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( @@ -2663,15 +2630,14 @@ impl<'db> Type<'db> { union .elements(db) .iter() - .map(|element| element.signatures(db, *element, dunder_call_boundness)), + .map(|element| element.signatures(db, *element)), ), - Type::Intersection(_) => Signatures::single(CallableSignature::todo( - "Type::Intersection.call()", - dunder_call_boundness, - )), + Type::Intersection(_) => { + Signatures::single(CallableSignature::todo("Type::Intersection.call()")) + } - _ => Signatures::not_callable(callable_ty, self, dunder_call_boundness), + _ => Signatures::not_callable(callable_ty, self), } } @@ -2685,7 +2651,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { - let signatures = self.signatures(db, self, None); + let signatures = self.signatures(db, self); let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.iter_mut() { // For certain known callables, we have special case logic to determine the return type @@ -2985,6 +2951,26 @@ impl<'db> Type<'db> { Ok(bindings) } + /// Looks up a dunder method on the meta-type of `self` and returns its signature and + /// boundness. Returns `None` if the meta-type does not contain the dunder method. + fn dunder_signature( + self, + db: &'db dyn Db, + callable_ty: Option>, + name: &str, + ) -> Option<(&'db Signatures<'db>, Boundness)> { + match self + .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) + .symbol + { + Symbol::Type(dunder_callable, boundness) => { + let callable_ty = callable_ty.unwrap_or(dunder_callable); + Some((dunder_callable.signatures(db, callable_ty), boundness)) + } + Symbol::Unbound => None, + } + } + /// Look up a dunder method on the meta-type of `self` and call it. /// /// Returns an `Err` if the dunder method can't be called, @@ -2995,20 +2981,14 @@ impl<'db> Type<'db> { name: &str, arguments: &CallArguments<'_, 'db>, ) -> Result, CallDunderError<'db>> { - match self - .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) - .symbol - { - Symbol::Type(dunder_callable, boundness) => { - let signatures = dunder_callable.signatures(db, dunder_callable, None); - let bindings = Bindings::bind(db, signatures, arguments).into_result()?; - if boundness == Boundness::PossiblyUnbound { - return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); - } - Ok(bindings) - } - Symbol::Unbound => Err(CallDunderError::MethodNotAvailable), + let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { + return Err(CallDunderError::MethodNotAvailable); + }; + let bindings = Bindings::bind(db, signature, arguments).into_result()?; + if boundness == Boundness::PossiblyUnbound { + return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } + Ok(bindings) } /// Returns the element type when iterating over `self`. diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 3cb711038b2be..366ab74317866 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -29,20 +29,11 @@ pub(crate) struct Signatures<'db> { } impl<'db> Signatures<'db> { - pub(crate) fn not_callable( - ty: Type<'db>, - signature_ty: Type<'db>, - dunder_call_boundness: Option, - ) -> Self { + pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { Self { ty, signature_ty, - elements: vec![CallableSignature::not_callable( - ty, - signature_ty, - dunder_call_boundness, - None, - )], + elements: vec![CallableSignature::not_callable(ty, signature_ty)], } } @@ -81,6 +72,12 @@ impl<'db> Signatures<'db> { pub(crate) fn iter(&self) -> impl Iterator> { self.elements.iter() } + + pub(crate) fn set_dunder_call_boundness(&mut self, boundness: Boundness) { + for signature in &mut self.elements { + signature.dunder_call_boundness = Some(boundness); + } + } } /// The signature of a single callable. If the callable is overloaded, there is a separate @@ -105,17 +102,12 @@ pub(crate) struct CallableSignature<'db> { } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable( - ty: Type<'db>, - signature_ty: Type<'db>, - dunder_call_boundness: Option, - bound_type: Option>, - ) -> Self { + pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { Self { ty, signature_ty, - dunder_call_boundness, - bound_type, + dunder_call_boundness: None, + bound_type: None, overloads: vec![], } } @@ -123,58 +115,50 @@ impl<'db> CallableSignature<'db> { pub(crate) fn single( ty: Type<'db>, signature_ty: Type<'db>, - dunder_call_boundness: Option, - bound_type: Option>, signature: Signature<'db>, ) -> Self { Self { ty, signature_ty, - dunder_call_boundness, - bound_type, + dunder_call_boundness: None, + bound_type: None, overloads: vec![signature], } } /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if /// the iterator is empty. - pub(crate) fn from_overloads( - ty: Type<'db>, - signature_ty: Type<'db>, - dunder_call_boundness: Option, - bound_type: Option>, - overloads: I, - ) -> Self + pub(crate) fn from_overloads(ty: Type<'db>, signature_ty: Type<'db>, overloads: I) -> Self where I: IntoIterator>, { Self { ty, signature_ty, - dunder_call_boundness, - bound_type, + dunder_call_boundness: None, + bound_type: None, overloads: overloads.into_iter().collect(), } } /// Return a signature for a dynamic callable - pub(crate) fn dynamic(ty: Type<'db>, dunder_call_boundness: Option) -> Self { + pub(crate) fn dynamic(ty: Type<'db>) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), return_ty: Some(ty), }; - Self::single(ty, ty, dunder_call_boundness, None, signature) + Self::single(ty, ty, 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, dunder_call_boundness: Option) -> Self { + pub(crate) fn todo(reason: &'static str) -> Self { let ty = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), return_ty: Some(ty), }; - Self::single(ty, ty, dunder_call_boundness, None, signature) + Self::single(ty, ty, signature) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. From fd3d5798194b52a39b0bf32a625f39a4878c2869 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 12:07:23 -0400 Subject: [PATCH 25/57] Remove (some) stutter from call errors --- crates/red_knot_python_semantic/src/types.rs | 44 +++++++++---------- .../src/types/call.rs | 10 ++--- .../src/types/infer.rs | 2 +- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 5a400a08b86e0..4f730277bad7c 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2192,24 +2192,18 @@ impl<'db> Type<'db> { } Err(CallDunderError::MethodNotAvailable) => Truthiness::Ambiguous, - Err(CallDunderError::Call(CallError( - CallErrorKind::BindingError, - bindings, - ))) => { + Err(CallDunderError::CallError(CallErrorKind::BindingError, bindings)) => { return Err(BoolError::IncorrectArguments { truthiness: type_to_truthiness(bindings.return_type(db)), not_boolable_type: *instance_ty, }); } - Err(CallDunderError::Call(CallError(CallErrorKind::NotCallable, _))) => { + Err(CallDunderError::CallError(CallErrorKind::NotCallable, _)) => { return Err(BoolError::NotCallable { not_boolable_type: *instance_ty, }); } - Err(CallDunderError::Call(CallError( - CallErrorKind::PossiblyNotCallable, - _, - ))) => { + Err(CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _)) => { return Err(BoolError::Other { not_boolable_type: *self, }) @@ -2312,7 +2306,7 @@ impl<'db> Type<'db> { // TODO: emit a diagnostic Err(CallDunderError::MethodNotAvailable) => return None, - Err(CallDunderError::Call(CallError(_, bindings))) => bindings.return_type(db), + Err(CallDunderError::CallError(_, bindings)) => bindings.return_type(db), }; non_negative_int_literal(db, return_ty) @@ -3080,8 +3074,8 @@ impl<'db> Type<'db> { } // `__iter__` is definitely bound but it can't be called with the expected arguments - Err(CallDunderError::Call(dunder_iter_call_error)) => { - Err(IterationErrorKind::IterCallError(dunder_iter_call_error)) + Err(CallDunderError::CallError(kind, bindings)) => { + Err(IterationErrorKind::IterCallError(kind, bindings)) } // There's no `__iter__` method. Try `__getitem__` instead... @@ -3744,8 +3738,8 @@ impl<'db> ContextManagerErrorKind<'db> { CallDunderError::PossiblyUnbound(call_outcome) => { Some(call_outcome.return_type(db)) } - CallDunderError::Call(CallError(CallErrorKind::NotCallable, _)) => None, - CallDunderError::Call(CallError(_, bindings)) => Some(bindings.return_type(db)), + CallDunderError::CallError(CallErrorKind::NotCallable, _) => None, + CallDunderError::CallError(_, bindings) => Some(bindings.return_type(db)), CallDunderError::MethodNotAvailable => None, }, } @@ -3766,7 +3760,9 @@ impl<'db> ContextManagerErrorKind<'db> { // TODO: Use more specific error messages for the different error cases. // E.g. hint toward the union variant that doesn't correctly implement enter, // distinguish between a not callable `__enter__` attribute and a wrong signature. - CallDunderError::Call(_) => format!("it does not correctly implement `{name}`"), + CallDunderError::CallError(_, _) => { + format!("it does not correctly implement `{name}`") + } } }; @@ -3781,7 +3777,7 @@ impl<'db> ContextManagerErrorKind<'db> { (CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => { format!("it does not implement `{name_a}` and `{name_b}`") } - (CallDunderError::Call(_), CallDunderError::Call(_)) => { + (CallDunderError::CallError(_, _), CallDunderError::CallError(_, _)) => { format!("it does not correctly implement `{name_a}` or `{name_b}`") } (_, _) => format!( @@ -3848,7 +3844,7 @@ impl<'db> IterationError<'db> { enum IterationErrorKind<'db> { /// The object being iterated over has a bound `__iter__` method, /// but calling it with the expected arguments results in an error. - IterCallError(CallError<'db>), + IterCallError(CallErrorKind, Box>), /// The object being iterated over has a bound `__iter__` method that can be called /// with the expected types, but it returns an object that is not a valid iterator. @@ -3888,7 +3884,7 @@ impl<'db> IterationErrorKind<'db> { dunder_next_error, .. } => dunder_next_error.return_type(db), - Self::IterCallError(CallError(_, dunder_iter_bindings)) => dunder_iter_bindings + Self::IterCallError(_, dunder_iter_bindings) => dunder_iter_bindings .return_type(db) .try_call_dunder(db, "__next__", &CallArguments::none()) .map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db))) @@ -3905,10 +3901,10 @@ impl<'db> IterationErrorKind<'db> { [*dunder_next_return, dunder_getitem_outcome.return_type(db)], )) } - CallDunderError::Call(CallError(CallErrorKind::NotCallable, _)) => { + CallDunderError::CallError(CallErrorKind::NotCallable, _) => { Some(*dunder_next_return) } - CallDunderError::Call(CallError(_, dunder_getitem_bindings)) => { + CallDunderError::CallError(_, dunder_getitem_bindings) => { let dunder_getitem_return = dunder_getitem_bindings.return_type(db); let elements = [*dunder_next_return, dunder_getitem_return]; Some(UnionType::from_elements(db, elements)) @@ -3939,7 +3935,7 @@ impl<'db> IterationErrorKind<'db> { // or similar, rather than as part of the same sentence as the error message. match self { - Self::IterCallError(CallError(dunder_iter_call_error, bindings)) => match dunder_iter_call_error { + Self::IterCallError(dunder_iter_call_error, bindings) => match dunder_iter_call_error { CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` attribute has type `{dunder_iter_type}`, \ @@ -3998,7 +3994,7 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallDunderError::Call(CallError(dunder_next_call_error, bindings)) => match dunder_next_call_error { + CallDunderError::CallError(dunder_next_call_error, bindings) => match dunder_next_call_error { CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because its `__iter__` method returns an object of type `{iterator_type}`, \ @@ -4044,7 +4040,7 @@ impl<'db> IterationErrorKind<'db> { because it may not have an `__iter__` method or a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(CallError(dunder_getitem_call_error, bindings)) => match dunder_getitem_call_error { + CallDunderError::CallError(dunder_getitem_call_error, bindings) => match dunder_getitem_call_error { CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ @@ -4102,7 +4098,7 @@ impl<'db> IterationErrorKind<'db> { and it may not have a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::Call(CallError(dunder_getitem_call_error, bindings)) => match dunder_getitem_call_error { + CallDunderError::CallError(dunder_getitem_call_error, bindings) => match dunder_getitem_call_error { CallErrorKind::NotCallable => report_not_iterable(format_args!( "Object of type `{iterable_type}` is not iterable \ because it has no `__iter__` method and \ diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 4706b91a68190..7d82d449efd90 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -31,7 +31,7 @@ pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// /// This includes non-callable dunder attributes that are possibly unbound. - Call(CallError<'db>), + CallError(CallErrorKind, Box>), /// The type has the specified dunder method and it is callable /// with the specified arguments without any binding errors @@ -45,8 +45,8 @@ pub(super) enum CallDunderError<'db> { impl<'db> CallDunderError<'db> { pub(super) fn return_type(&self, db: &'db dyn Db) -> Option> { match self { - Self::MethodNotAvailable | Self::Call(CallError(CallErrorKind::NotCallable, _)) => None, - Self::Call(CallError(_, bindings)) => Some(bindings.return_type(db)), + Self::MethodNotAvailable | Self::CallError(CallErrorKind::NotCallable, _) => None, + Self::CallError(_, bindings) => Some(bindings.return_type(db)), Self::PossiblyUnbound(bindings) => Some(bindings.return_type(db)), } } @@ -57,7 +57,7 @@ impl<'db> CallDunderError<'db> { } impl<'db> From> for CallDunderError<'db> { - fn from(error: CallError<'db>) -> Self { - Self::Call(error) + fn from(CallError(kind, bindings): CallError<'db>) -> Self { + Self::CallError(kind, bindings) } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 439fcb207ab21..0e1a0bb8a0045 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -5379,7 +5379,7 @@ impl<'db> TypeInferenceBuilder<'db> { return err.fallback_return_type(self.db()); } - Err(CallDunderError::Call(CallError(_, bindings))) => { + Err(CallDunderError::CallError(_, bindings)) => { self.context.report_lint( &CALL_NON_CALLABLE, value_node, From d279145a5d285138473600252d1e4e976f10ef31 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:39:59 -0400 Subject: [PATCH 26/57] Apply suggestions from code review Co-authored-by: Alex Waygood --- crates/red_knot_python_semantic/src/types/call/bind.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 34dc92921fb99..9618710a3bf66 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -69,7 +69,7 @@ impl<'db> Bindings<'db> { } } - pub(crate) fn is_single(&self) -> bool { + pub(crate) const fn is_single(&self) -> bool { matches!(&self.inner, BindingsInner::Single(_)) } @@ -297,7 +297,7 @@ impl<'db> CallableBinding<'db> { Ok(()) } - fn is_callable(&self) -> bool { + const fn is_callable(&self) -> bool { !matches!(&self.inner, CallableBindingInner::NotCallable) } @@ -309,7 +309,7 @@ impl<'db> CallableBinding<'db> { /// Returns whether this binding is for an object that is callable via a `__call__` method that /// is possibly unbound. - pub(crate) fn dunder_is_possibly_unbound(&self) -> bool { + pub(crate) const fn dunder_is_possibly_unbound(&self) -> bool { matches!( self.signature.dunder_call_boundness, Some(Boundness::PossiblyUnbound) From dda63db3b2aefff261cfd1684d463f1c9cf68d9a Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:43:43 -0400 Subject: [PATCH 27/57] Fewer is_known calls --- crates/red_knot_python_semantic/src/types.rs | 277 +++++++++---------- 1 file changed, 128 insertions(+), 149 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4f730277bad7c..adad3f1897a06 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2759,183 +2759,162 @@ impl<'db> Type<'db> { } } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsEquivalentTo) => - { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_equivalent_to(db, *ty_b), - )); - } - } - - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsSubtypeOf) => - { - if let [ty_a, ty_b] = overload.parameter_types() { - overload - .set_return_type(Type::BooleanLiteral(ty_a.is_subtype_of(db, *ty_b))); + Type::FunctionLiteral(function_type) => match function_type.known(db) { + Some(KnownFunction::IsEquivalentTo) => { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_equivalent_to(db, *ty_b), + )); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsAssignableTo) => - { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_assignable_to(db, *ty_b), - )); + Some(KnownFunction::IsSubtypeOf) => { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_subtype_of(db, *ty_b), + )); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsDisjointFrom) => - { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_disjoint_from(db, *ty_b), - )); + Some(KnownFunction::IsAssignableTo) => { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_assignable_to(db, *ty_b), + )); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsGradualEquivalentTo) => - { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_gradual_equivalent_to(db, *ty_b), - )); - } - } - - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsFullyStatic) => - { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + Some(KnownFunction::IsDisjointFrom) => { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_disjoint_from(db, *ty_b), + )); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsSingleton) => - { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); + Some(KnownFunction::IsGradualEquivalentTo) => { + if let [ty_a, ty_b] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_gradual_equivalent_to(db, *ty_b), + )); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::IsSingleValued) => - { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); + Some(KnownFunction::IsFullyStatic) => { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + } } - } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::Len) => - { - if let [first_arg] = overload.parameter_types() { - if let Some(len_ty) = first_arg.len(db) { - overload.set_return_type(len_ty); + Some(KnownFunction::IsSingleton) => { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); } - }; - } - - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::Repr) => - { - if let [first_arg] = overload.parameter_types() { - overload.set_return_type(first_arg.repr(db)); - }; - } + } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::Cast) => - { - // TODO: Use `.parameter_types()` exclusively when overloads are supported. - if let Some(casted_ty) = arguments.first_argument() { - if let [_, _] = overload.parameter_types() { - overload.set_return_type(casted_ty); + Some(KnownFunction::IsSingleValued) => { + if let [ty] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); } - }; - } + } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::Overload) => - { - overload.set_return_type(todo_type!("overload(..) return type")); - } + Some(KnownFunction::Len) => { + if let [first_arg] = overload.parameter_types() { + if let Some(len_ty) = first_arg.len(db) { + overload.set_return_type(len_ty); + } + }; + } - Type::FunctionLiteral(function_type) - if function_type.is_known(db, KnownFunction::GetattrStatic) => - { - let [instance_ty, attr_name, default] = overload.parameter_types() else { - continue; - }; + Some(KnownFunction::Repr) => { + if let [first_arg] = overload.parameter_types() { + overload.set_return_type(first_arg.repr(db)); + }; + } - let Some(attr_name) = attr_name.into_string_literal() else { - continue; - }; + Some(KnownFunction::Cast) => { + // TODO: Use `.parameter_types()` exclusively when overloads are supported. + if let Some(casted_ty) = arguments.first_argument() { + if let [_, _] = overload.parameter_types() { + overload.set_return_type(casted_ty); + } + }; + } - let default = if default.is_unknown() { - Type::Never - } else { - *default - }; + Some(KnownFunction::Overload) => { + overload.set_return_type(todo_type!("overload(..) return type")); + } - let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); + Some(KnownFunction::GetattrStatic) => { + let [instance_ty, attr_name, default] = overload.parameter_types() else { + continue; + }; - // TODO: we could emit a diagnostic here (if default is not set) - overload.set_return_type( - match instance_ty.static_member(db, attr_name.value(db)) { - Symbol::Type(ty, Boundness::Bound) => { - if instance_ty.is_fully_static(db) { - ty - } else { - // Here, we attempt to model the fact that an attribute lookup on - // a non-fully static type could fail. This is an approximation, - // as there are gradual types like `tuple[Any]`, on which a lookup - // of (e.g. of the `index` method) would always succeed. + let Some(attr_name) = attr_name.into_string_literal() else { + continue; + }; + let default = if default.is_unknown() { + Type::Never + } else { + *default + }; + + let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); + + // TODO: we could emit a diagnostic here (if default is not set) + overload.set_return_type( + match instance_ty.static_member(db, attr_name.value(db)) { + Symbol::Type(ty, Boundness::Bound) => { + if instance_ty.is_fully_static(db) { + ty + } else { + // Here, we attempt to model the fact that an attribute lookup on + // a non-fully static type could fail. This is an approximation, + // as there are gradual types like `tuple[Any]`, on which a lookup + // of (e.g. of the `index` method) would always succeed. + + union_with_default(ty) + } + } + Symbol::Type(ty, Boundness::PossiblyUnbound) => { union_with_default(ty) } - } - Symbol::Type(ty, Boundness::PossiblyUnbound) => union_with_default(ty), - Symbol::Unbound => default, - }, - ); - } + Symbol::Unbound => default, + }, + ); + } - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Bool) => - { - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.bool(db).into_type(db)) - .unwrap_or(Type::BooleanLiteral(false)), - ); - } + _ => {} + }, - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Str) && overload_index == 0 => - { - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.str(db)) - .unwrap_or_else(|| Type::string_literal(db, "")), - ); - } + Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) { + Some(KnownClass::Bool) => { + overload.set_return_type( + arguments + .first_argument() + .map(|arg| arg.bool(db).into_type(db)) + .unwrap_or(Type::BooleanLiteral(false)), + ); + } - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Type) && overload_index == 0 => - { - if let Some(arg) = arguments.first_argument() { - overload.set_return_type(arg.to_meta_type(db)); + Some(KnownClass::Str) if overload_index == 0 => { + overload.set_return_type( + arguments + .first_argument() + .map(|arg| arg.str(db)) + .unwrap_or_else(|| Type::string_literal(db, "")), + ); } - } + + Some(KnownClass::Type) if overload_index == 0 => { + if let Some(arg) = arguments.first_argument() { + overload.set_return_type(arg.to_meta_type(db)); + } + } + + _ => {} + }, // Not a special case _ => {} From d9979440cefb1cc9e022f5ac5c733f602fe9fbf1 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:45:04 -0400 Subject: [PATCH 28/57] Only iterate errors if there are any --- .../red_knot_python_semantic/src/types/call/bind.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 9618710a3bf66..d39271293079d 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -87,14 +87,19 @@ impl<'db> Bindings<'db> { // The return types from successfully bound elements should come first, then the // fallback return types for any bindings with errors. let mut builder = UnionBuilder::new(db); + let mut any_errors = false; for binding in bindings { - if !binding.has_binding_errors() { + if binding.has_binding_errors() { + any_errors = true; + } else { builder = builder.add(binding.return_type()); } } - for binding in bindings { - if binding.has_binding_errors() { - builder = builder.add(binding.return_type()); + if any_errors { + for binding in bindings { + if binding.has_binding_errors() { + builder = builder.add(binding.return_type()); + } } } builder.build() From b4103abf8caba09e43473d414fb5195302357ffb Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:46:42 -0400 Subject: [PATCH 29/57] =?UTF-8?q?CallableDescriptor=20=E2=86=92=20Descript?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/types/call/bind.rs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index d39271293079d..fa6b98b5ba354 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -379,14 +379,14 @@ impl<'db> CallableBinding<'db> { return; } - let callable_descriptor = CallableDescriptor::new(context.db(), self.signature.ty); + let callable_descriptor = CallableDescription::new(context.db(), self.signature.ty); if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, node, format_args!( "No overload{} matches arguments", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" of {kind} `{name}`") } else { String::new() @@ -397,7 +397,7 @@ impl<'db> CallableBinding<'db> { } let callable_descriptor = - CallableDescriptor::new(context.db(), self.signature.signature_ty); + CallableDescription::new(context.db(), self.signature.signature_ty); for overload in self.overloads() { overload.report_diagnostics( context, @@ -563,7 +563,7 @@ impl<'db> Binding<'db> { context: &InferContext<'db>, node: ast::AnyNodeRef, callable_ty: Type<'db>, - callable_descriptor: Option<&CallableDescriptor>, + callable_descriptor: Option<&CallableDescription>, ) { for error in &self.errors { error.report_diagnostic(context, node, callable_ty, callable_descriptor); @@ -580,33 +580,33 @@ impl<'db> Binding<'db> { /// Describes a callable for the purposes of diagnostics. #[derive(Debug)] -pub(crate) struct CallableDescriptor<'a> { +pub(crate) struct CallableDescription<'a> { name: &'a str, kind: &'a str, } -impl<'db> CallableDescriptor<'db> { - fn new(db: &'db dyn Db, ty: Type<'db>) -> Option> { +impl<'db> CallableDescription<'db> { + fn new(db: &'db dyn Db, ty: Type<'db>) -> Option> { match ty { - Type::FunctionLiteral(function) => Some(CallableDescriptor { + Type::FunctionLiteral(function) => Some(CallableDescription { kind: "function", name: function.name(db), }), - Type::ClassLiteral(class_type) => Some(CallableDescriptor { + Type::ClassLiteral(class_type) => Some(CallableDescription { kind: "class", name: class_type.class().name(db), }), - Type::Callable(CallableType::BoundMethod(bound_method)) => Some(CallableDescriptor { + Type::Callable(CallableType::BoundMethod(bound_method)) => Some(CallableDescription { kind: "bound method", name: bound_method.function(db).name(db), }), Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { - Some(CallableDescriptor { + Some(CallableDescription { kind: "method wrapper `__get__` of function", name: function.name(db), }) } - Type::Callable(CallableType::WrapperDescriptorDunderGet) => Some(CallableDescriptor { + Type::Callable(CallableType::WrapperDescriptorDunderGet) => Some(CallableDescription { kind: "wrapper descriptor", name: "FunctionType.__get__", }), @@ -738,7 +738,7 @@ impl<'db> BindingError<'db> { context: &InferContext<'db>, node: ast::AnyNodeRef, callable_ty: Type<'db>, - callable_descriptor: Option<&CallableDescriptor>, + callable_descriptor: Option<&CallableDescription>, ) { match self { Self::InvalidArgumentType { @@ -765,7 +765,7 @@ impl<'db> BindingError<'db> { format_args!( "Object of type `{provided_ty_display}` cannot be assigned to \ parameter {parameter}{}; expected type `{expected_ty_display}`", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" of {kind} `{name}`") } else { String::new() @@ -786,7 +786,7 @@ impl<'db> BindingError<'db> { format_args!( "Too many positional arguments{}: expected \ {expected_positional_count}, got {provided_positional_count}", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" to {kind} `{name}`") } else { String::new() @@ -802,7 +802,7 @@ impl<'db> BindingError<'db> { node, format_args!( "No argument{s} provided for required parameter{s} {parameters}{}", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" of {kind} `{name}`") } else { String::new() @@ -820,7 +820,7 @@ impl<'db> BindingError<'db> { Self::get_node(node, *argument_index), format_args!( "Argument `{argument_name}` does not match any known parameter{}", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" of {kind} `{name}`") } else { String::new() @@ -838,7 +838,7 @@ impl<'db> BindingError<'db> { Self::get_node(node, *argument_index), format_args!( "Multiple values provided for parameter {parameter}{}", - if let Some(CallableDescriptor { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_descriptor { format!(" of {kind} `{name}`") } else { String::new() From 9cf9e28e5d3edbf61db37d645547377a70cdc625 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:47:43 -0400 Subject: [PATCH 30/57] Doesn't panic anymore --- crates/red_knot_python_semantic/src/types/signatures.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 366ab74317866..0243b4922a7c7 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -126,8 +126,7 @@ impl<'db> CallableSignature<'db> { } } - /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. Panics if - /// the iterator is empty. + /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. pub(crate) fn from_overloads(ty: Type<'db>, signature_ty: Type<'db>, overloads: I) -> Self where I: IntoIterator>, From 00891ae37141715def5c1fb5a7f209acad5f37f9 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 14 Mar 2025 16:50:17 -0400 Subject: [PATCH 31/57] =?UTF-8?q?ty=20=E2=86=92=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/types/call/bind.rs | 19 ++--- .../src/types/infer.rs | 2 +- .../src/types/signatures.rs | 69 +++++++++++-------- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index fa6b98b5ba354..e2b6d24ac2bfb 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -74,7 +74,7 @@ impl<'db> Bindings<'db> { } pub(crate) fn callable_type(&self) -> Type<'db> { - self.signatures.ty + self.signatures.callable_type } /// Returns the return type of the call. For successful calls, this is the actual return type. @@ -177,7 +177,7 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signatures.ty.display(context.db()) + self.signatures.callable_type.display(context.db()) ), ); return; @@ -361,7 +361,7 @@ impl<'db> CallableBinding<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signature.ty.display(context.db()), + self.signature.callable_type.display(context.db()), ), ); return; @@ -373,13 +373,14 @@ impl<'db> CallableBinding<'db> { node, format_args!( "Object of type `{}` is not callable (possibly unbound `__call__` method)", - self.signature.ty.display(context.db()), + self.signature.callable_type.display(context.db()), ), ); return; } - let callable_descriptor = CallableDescription::new(context.db(), self.signature.ty); + let callable_descriptor = + CallableDescription::new(context.db(), self.signature.callable_type); if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, @@ -397,12 +398,12 @@ impl<'db> CallableBinding<'db> { } let callable_descriptor = - CallableDescription::new(context.db(), self.signature.signature_ty); + CallableDescription::new(context.db(), self.signature.signature_type); for overload in self.overloads() { overload.report_diagnostics( context, node, - self.signature.signature_ty, + self.signature.signature_type, callable_descriptor.as_ref(), ); } @@ -586,8 +587,8 @@ pub(crate) struct CallableDescription<'a> { } impl<'db> CallableDescription<'db> { - fn new(db: &'db dyn Db, ty: Type<'db>) -> Option> { - match ty { + fn new(db: &'db dyn Db, callable_type: Type<'db>) -> Option> { + match callable_type { Type::FunctionLiteral(function) => Some(CallableDescription { kind: "function", name: function.name(db), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 0e1a0bb8a0045..647500c91a70f 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3548,7 +3548,7 @@ impl<'db> TypeInferenceBuilder<'db> { for binding in bindings.iter() { let Some(known_function) = binding .signature - .ty + .callable_type .into_function_literal() .and_then(|function_type| function_type.known(self.db())) else { diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 0243b4922a7c7..fd94f3fb3c9ba 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -21,33 +21,40 @@ use ruff_python_ast::{self as ast, name::Name}; #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct Signatures<'db> { /// The type that is (hopefully) callable. - pub(crate) ty: Type<'db>, + pub(crate) callable_type: Type<'db>, /// For an object that's callable via a `__call__` method, the type of that method. For an /// object that is directly callable, the type of the object. - pub(crate) signature_ty: Type<'db>, + pub(crate) signature_type: Type<'db>, elements: Vec>, } impl<'db> Signatures<'db> { - pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { + pub(crate) fn not_callable(callable_type: Type<'db>, signature_type: Type<'db>) -> Self { Self { - ty, - signature_ty, - elements: vec![CallableSignature::not_callable(ty, signature_ty)], + callable_type, + signature_type, + elements: vec![CallableSignature::not_callable( + callable_type, + signature_type, + )], } } pub(crate) fn single(signature: CallableSignature<'db>) -> Self { Self { - ty: signature.ty, - signature_ty: signature.signature_ty, + callable_type: signature.callable_type, + signature_type: signature.signature_type, elements: vec![signature], } } /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is /// empty. - pub(crate) fn from_union(ty: Type<'db>, signature_ty: Type<'db>, elements: I) -> Self + pub(crate) fn from_union( + callable_type: Type<'db>, + signature_type: Type<'db>, + elements: I, + ) -> Self where I: IntoIterator>, { @@ -56,8 +63,8 @@ impl<'db> Signatures<'db> { .flat_map(|s| s.elements.iter().cloned()) .collect(); Self { - ty, - signature_ty, + callable_type, + signature_type, elements, } } @@ -85,11 +92,11 @@ impl<'db> Signatures<'db> { #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct CallableSignature<'db> { /// The type that is (hopefully) callable. - pub(crate) ty: Type<'db>, + pub(crate) callable_type: Type<'db>, /// For an object that's callable via a `__call__` method, the type of that method. For an /// object that is directly callable, the type of the object. - pub(crate) signature_ty: Type<'db>, + 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. @@ -102,10 +109,10 @@ pub(crate) struct CallableSignature<'db> { } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(ty: Type<'db>, signature_ty: Type<'db>) -> Self { + pub(crate) fn not_callable(callable_type: Type<'db>, signature_type: Type<'db>) -> Self { Self { - ty, - signature_ty, + callable_type, + signature_type, dunder_call_boundness: None, bound_type: None, overloads: vec![], @@ -113,13 +120,13 @@ impl<'db> CallableSignature<'db> { } pub(crate) fn single( - ty: Type<'db>, - signature_ty: Type<'db>, + callable_type: Type<'db>, + signature_type: Type<'db>, signature: Signature<'db>, ) -> Self { Self { - ty, - signature_ty, + callable_type, + signature_type, dunder_call_boundness: None, bound_type: None, overloads: vec![signature], @@ -127,13 +134,17 @@ impl<'db> CallableSignature<'db> { } /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. - pub(crate) fn from_overloads(ty: Type<'db>, signature_ty: Type<'db>, overloads: I) -> Self + pub(crate) fn from_overloads( + callable_type: Type<'db>, + signature_type: Type<'db>, + overloads: I, + ) -> Self where I: IntoIterator>, { Self { - ty, - signature_ty, + callable_type, + signature_type, dunder_call_boundness: None, bound_type: None, overloads: overloads.into_iter().collect(), @@ -141,23 +152,23 @@ impl<'db> CallableSignature<'db> { } /// Return a signature for a dynamic callable - pub(crate) fn dynamic(ty: Type<'db>) -> Self { + pub(crate) fn dynamic(callable_type: Type<'db>) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), - return_ty: Some(ty), + return_ty: Some(callable_type), }; - Self::single(ty, ty, signature) + Self::single(callable_type, callable_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 ty = todo_type!(reason); + let callable_type = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), - return_ty: Some(ty), + return_ty: Some(callable_type), }; - Self::single(ty, ty, signature) + Self::single(callable_type, callable_type, signature) } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. From 3357df654e6afd34663474ef384bb2de61715ea4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:24:39 -0400 Subject: [PATCH 32/57] Apply suggestions from code review Co-authored-by: Alex Waygood Co-authored-by: Carl Meyer --- crates/red_knot_python_semantic/src/types.rs | 20 +++++++------- .../src/types/call/bind.rs | 26 +++---------------- .../src/types/signatures.rs | 4 +-- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index adad3f1897a06..e1d4af4c3b5a4 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2599,10 +2599,11 @@ impl<'db> Type<'db> { }, Type::Instance(_) => { - // Note that for objects that are callable via a `__call__` method, we will get the - // signature of the dunder method, but will pass in the type of the object as the - // "callable type". That ensures that we get errors like "`X` is not callable" - // instead of "`` is not callable". + // Note that for objects that have a (possibly not callable!) `__call__` attribute, + // we will get the signature of the `__call__` attribute, but will pass in the type + // of the original object as the "callable type". That ensures that we get errors + // like "`X` is not callable" instead of "`` is not + // callable". let Some((signatures, boundness)) = self.dunder_signature(db, Some(callable_ty), "__call__") else { @@ -2636,10 +2637,11 @@ impl<'db> Type<'db> { } /// Calls `self`. Returns a [`CallError`] if `self` is (always or possibly) not callable, or if - /// the arguments are not compatible with the formal parameters. You get back a [`Bindings`] - /// for both successful and unsuccessful calls. It contains information about which formal - /// parameters each argument was matched to, and about any errors matching arguments and - /// parameters. + /// the arguments are not compatible with the formal parameters. + /// + /// You get back a [`Bindings`] for both successful and unsuccessful calls. + /// It contains information about which formal parameters each argument was matched to, + /// and about any errors matching arguments and parameters. fn try_call( self, db: &'db dyn Db, @@ -2648,7 +2650,7 @@ impl<'db> Type<'db> { let signatures = self.signatures(db, self); let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.iter_mut() { - // For certain known callables, we have special case logic to determine the return type + // For certain known callables, we have special-case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case // listed here should have a corresponding clause above in `signatures`. let Some((overload_index, overload)) = binding.matching_overload_mut() else { diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index e2b6d24ac2bfb..f255726b481e5 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -61,8 +61,7 @@ impl<'db> Bindings<'db> { let bindings = signatures .iter() .map(|signature| CallableBinding::bind(db, signature, arguments)) - .collect::>() - .into_boxed_slice(); + .collect(); Bindings { signatures, inner: BindingsInner::Union(bindings), @@ -84,25 +83,7 @@ impl<'db> Bindings<'db> { match &self.inner { BindingsInner::Single(binding) => binding.return_type(), BindingsInner::Union(bindings) => { - // The return types from successfully bound elements should come first, then the - // fallback return types for any bindings with errors. - let mut builder = UnionBuilder::new(db); - let mut any_errors = false; - for binding in bindings { - if binding.has_binding_errors() { - any_errors = true; - } else { - builder = builder.add(binding.return_type()); - } - } - if any_errors { - for binding in bindings { - if binding.has_binding_errors() { - builder = builder.add(binding.return_type()); - } - } - } - builder.build() + UnionType::from_elements(db, bindings.iter().map(|b| b.return_type())) } } } @@ -262,8 +243,7 @@ impl<'db> CallableBinding<'db> { let overloads = signature .iter() .map(|signature| Binding::bind(db, signature, arguments.as_ref())) - .collect::>() - .into_boxed_slice(); + .collect(); CallableBinding { signature, inner: CallableBindingInner::Overloaded(overloads), diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index fd94f3fb3c9ba..acd55279d9d2a 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -22,8 +22,8 @@ use ruff_python_ast::{self as ast, name::Name}; pub(crate) struct Signatures<'db> { /// The type that is (hopefully) callable. pub(crate) callable_type: Type<'db>, - /// For an object that's callable via a `__call__` method, the type of that method. For an - /// object that is directly callable, the type of the object. + /// 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>, elements: Vec>, } From 99578795e3ffbaf6fb0363d43e86d8d0e52d3ba0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 10:26:57 -0400 Subject: [PATCH 33/57] Panic again --- crates/red_knot_python_semantic/src/types/signatures.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index acd55279d9d2a..839d923551bb6 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -58,10 +58,11 @@ impl<'db> Signatures<'db> { where I: IntoIterator>, { - let elements = elements + let elements: Vec<_> = elements .into_iter() .flat_map(|s| s.elements.iter().cloned()) .collect(); + assert!(!elements.is_empty()); Self { callable_type, signature_type, From aed2a87b7254232454ecaba22cec0c0e012160d1 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 10:53:09 -0400 Subject: [PATCH 34/57] Remove unneeded lifetime params --- crates/red_knot_python_semantic/src/types/signatures.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 839d923551bb6..230197f7c4d07 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -228,7 +228,7 @@ impl<'db> Signature<'db> { pub(super) fn from_function( db: &'db dyn Db, definition: Definition<'db>, - function_node: &'db ast::StmtFunctionDef, + function_node: &ast::StmtFunctionDef, ) -> Self { let return_ty = function_node.returns.as_ref().map(|returns| { if function_node.is_async { @@ -366,7 +366,7 @@ impl<'db> Parameters<'db> { fn from_parameters( db: &'db dyn Db, definition: Definition<'db>, - parameters: &'db ast::Parameters, + parameters: &ast::Parameters, ) -> Self { let ast::Parameters { posonlyargs, @@ -530,7 +530,7 @@ impl<'db> Parameter<'db> { fn from_node_and_kind( db: &'db dyn Db, definition: Definition<'db>, - parameter: &'db ast::Parameter, + parameter: &ast::Parameter, kind: ParameterKind<'db>, ) -> Self { Self { From e7706808d32d996ee948041ebd5ce6e03fb9fd56 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 10:53:52 -0400 Subject: [PATCH 35/57] Comment empty overloads vec --- crates/red_knot_python_semantic/src/types/signatures.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 230197f7c4d07..13c13165ca637 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -106,6 +106,8 @@ pub(crate) struct CallableSignature<'db> { /// The type of the bound `self` or `cls` parameter if this signature is for a bound method. pub(crate) bound_type: Option>, + /// The signatures of each overload of this callable. Will be empty if the type is not + /// callable. overloads: Vec>, } From 0cf9acdb0d08732058c8357607a5e5fc97a9d6d6 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 10:55:56 -0400 Subject: [PATCH 36/57] Better comments --- crates/red_knot_python_semantic/src/types/signatures.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 13c13165ca637..da16c43878418 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -95,8 +95,9 @@ pub(crate) struct CallableSignature<'db> { /// The type that is (hopefully) callable. pub(crate) callable_type: Type<'db>, - /// For an object that's callable via a `__call__` method, the type of that method. For an - /// object that is directly callable, the type of the object. + /// 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 @@ -136,7 +137,8 @@ impl<'db> CallableSignature<'db> { } } - /// Creates a new `CallableSignature` from a non-empty iterator of [`Signature`]s. + /// 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( callable_type: Type<'db>, signature_type: Type<'db>, From 1cd9df87528b2b6b88cc0eeeee8fd2cc9e3c4c5d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 13:42:02 -0400 Subject: [PATCH 37/57] Use bool for dunder_call_is_possibly_unbound --- crates/red_knot_python_semantic/src/types.rs | 4 +++- .../src/types/call/bind.rs | 14 ++------------ .../src/types/signatures.rs | 13 ++++++------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index e1d4af4c3b5a4..11d4da8312c3d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2610,7 +2610,9 @@ impl<'db> Type<'db> { return Signatures::not_callable(callable_ty, self); }; let mut signatures = signatures.clone(); - signatures.set_dunder_call_boundness(boundness); + if boundness == Boundness::PossiblyUnbound { + signatures.set_dunder_call_is_possibly_unbound(); + } signatures } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index f255726b481e5..732cdbe416e61 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -10,7 +10,6 @@ use super::{ Signatures, Type, }; use crate::db::Db; -use crate::symbol::Boundness; use crate::types::diagnostic::{ CALL_NON_CALLABLE, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, @@ -275,7 +274,7 @@ impl<'db> CallableBinding<'db> { return Err(CallErrorKind::BindingError); } - if self.dunder_is_possibly_unbound() { + if self.signature.dunder_call_is_possibly_unbound { return Err(CallErrorKind::PossiblyNotCallable); } @@ -292,15 +291,6 @@ impl<'db> CallableBinding<'db> { self.matching_overload().is_none() } - /// Returns whether this binding is for an object that is callable via a `__call__` method that - /// is possibly unbound. - pub(crate) const fn dunder_is_possibly_unbound(&self) -> bool { - matches!( - self.signature.dunder_call_boundness, - Some(Boundness::PossiblyUnbound) - ) - } - /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. pub(crate) fn matching_overload(&self) -> Option<(usize, &Binding<'db>)> { @@ -347,7 +337,7 @@ impl<'db> CallableBinding<'db> { return; } - if self.dunder_is_possibly_unbound() { + if self.signature.dunder_call_is_possibly_unbound { context.report_lint( &CALL_NON_CALLABLE, node, diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index da16c43878418..ce4da1c142985 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -12,7 +12,6 @@ use super::{definition_expression_type, DynamicType, Type}; use crate::semantic_index::definition::Definition; -use crate::symbol::Boundness; use crate::types::todo_type; use crate::Db; use ruff_python_ast::{self as ast, name::Name}; @@ -81,9 +80,9 @@ impl<'db> Signatures<'db> { self.elements.iter() } - pub(crate) fn set_dunder_call_boundness(&mut self, boundness: Boundness) { + pub(crate) fn set_dunder_call_is_possibly_unbound(&mut self) { for signature in &mut self.elements { - signature.dunder_call_boundness = Some(boundness); + signature.dunder_call_is_possibly_unbound = true; } } } @@ -102,7 +101,7 @@ pub(crate) struct CallableSignature<'db> { /// If this is a callable object (i.e. called via a `__call__` method), the boundness of /// that call method. - pub(crate) dunder_call_boundness: Option, + 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>, @@ -117,7 +116,7 @@ impl<'db> CallableSignature<'db> { Self { callable_type, signature_type, - dunder_call_boundness: None, + dunder_call_is_possibly_unbound: false, bound_type: None, overloads: vec![], } @@ -131,7 +130,7 @@ impl<'db> CallableSignature<'db> { Self { callable_type, signature_type, - dunder_call_boundness: None, + dunder_call_is_possibly_unbound: false, bound_type: None, overloads: vec![signature], } @@ -150,7 +149,7 @@ impl<'db> CallableSignature<'db> { Self { callable_type, signature_type, - dunder_call_boundness: None, + dunder_call_is_possibly_unbound: false, bound_type: None, overloads: overloads.into_iter().collect(), } From 11951dd5ff08d1bfc9f0a7ccf338c6f7858b0229 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 13:43:01 -0400 Subject: [PATCH 38/57] =?UTF-8?q?callable=5Fdescriptor=20=E2=86=92=20descr?= =?UTF-8?q?iption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/types/call/bind.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 732cdbe416e61..4bed88f92cfe9 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -349,7 +349,7 @@ impl<'db> CallableBinding<'db> { return; } - let callable_descriptor = + let callable_description = CallableDescription::new(context.db(), self.signature.callable_type); if self.overloads().len() > 1 { context.report_lint( @@ -357,7 +357,7 @@ impl<'db> CallableBinding<'db> { node, format_args!( "No overload{} matches arguments", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") } else { String::new() @@ -367,14 +367,14 @@ impl<'db> CallableBinding<'db> { return; } - let callable_descriptor = + let callable_description = CallableDescription::new(context.db(), self.signature.signature_type); for overload in self.overloads() { overload.report_diagnostics( context, node, self.signature.signature_type, - callable_descriptor.as_ref(), + callable_description.as_ref(), ); } } @@ -534,10 +534,10 @@ impl<'db> Binding<'db> { context: &InferContext<'db>, node: ast::AnyNodeRef, callable_ty: Type<'db>, - callable_descriptor: Option<&CallableDescription>, + callable_description: Option<&CallableDescription>, ) { for error in &self.errors { - error.report_diagnostic(context, node, callable_ty, callable_descriptor); + error.report_diagnostic(context, node, callable_ty, callable_description); } } @@ -709,7 +709,7 @@ impl<'db> BindingError<'db> { context: &InferContext<'db>, node: ast::AnyNodeRef, callable_ty: Type<'db>, - callable_descriptor: Option<&CallableDescription>, + callable_description: Option<&CallableDescription>, ) { match self { Self::InvalidArgumentType { @@ -736,7 +736,7 @@ impl<'db> BindingError<'db> { format_args!( "Object of type `{provided_ty_display}` cannot be assigned to \ parameter {parameter}{}; expected type `{expected_ty_display}`", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") } else { String::new() @@ -757,7 +757,7 @@ impl<'db> BindingError<'db> { format_args!( "Too many positional arguments{}: expected \ {expected_positional_count}, got {provided_positional_count}", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" to {kind} `{name}`") } else { String::new() @@ -773,7 +773,7 @@ impl<'db> BindingError<'db> { node, format_args!( "No argument{s} provided for required parameter{s} {parameters}{}", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") } else { String::new() @@ -791,7 +791,7 @@ impl<'db> BindingError<'db> { Self::get_node(node, *argument_index), format_args!( "Argument `{argument_name}` does not match any known parameter{}", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") } else { String::new() @@ -809,7 +809,7 @@ impl<'db> BindingError<'db> { Self::get_node(node, *argument_index), format_args!( "Multiple values provided for parameter {parameter}{}", - if let Some(CallableDescription { kind, name }) = callable_descriptor { + if let Some(CallableDescription { kind, name }) = callable_description { format!(" of {kind} `{name}`") } else { String::new() From 86be76a35b5ae2796ad52ba3178113a0c361edbb Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 13:49:22 -0400 Subject: [PATCH 39/57] Clarify CallError comments --- crates/red_knot_python_semantic/src/types/call.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 7d82d449efd90..79a896f7d1fd5 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -15,14 +15,18 @@ pub(super) struct CallError<'db>(pub(super) CallErrorKind, pub(super) Box Date: Sat, 15 Mar 2025 14:09:49 -0400 Subject: [PATCH 40/57] Bring back replace_type --- crates/red_knot_python_semantic/src/types.rs | 94 ++++++++----------- .../src/types/signatures.rs | 64 ++++++------- 2 files changed, 71 insertions(+), 87 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 11d4da8312c3d..562192e901b7a 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2322,11 +2322,11 @@ impl<'db> Type<'db> { /// particular argument list, via [`try_call`][Self::try_call] and /// [`CallErrorKind::NotCallable`]. #[salsa::tracked(return_ref)] - fn signatures(self, db: &'db dyn Db, callable_ty: Type<'db>) -> Signatures<'db> { + fn signatures(self, db: &'db dyn Db) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let mut signature = CallableSignature::single(callable_ty, self, signature.clone()); + let mut signature = CallableSignature::single(self, signature.clone()); signature.bound_type = Some(bound_method.self_instance(db)); Signatures::single(signature) } @@ -2347,7 +2347,6 @@ impl<'db> Type<'db> { let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( - callable_ty, self, [ Signature::new( @@ -2399,7 +2398,6 @@ impl<'db> Type<'db> { // removed. let not_none = Type::none(db).negate(db); let signature = CallableSignature::from_overloads( - callable_ty, self, [ Signature::new( @@ -2453,7 +2451,6 @@ impl<'db> Type<'db> { } Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::single( - callable_ty, self, function_type.signature(db).clone(), )), @@ -2466,7 +2463,6 @@ impl<'db> Type<'db> { // def __new__(cls, o: object = ..., /) -> Self: ... // ``` let signature = CallableSignature::single( - callable_ty, self, Signature::new( Parameters::new([Parameter::new( @@ -2493,7 +2489,6 @@ impl<'db> Type<'db> { // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... // ``` let signature = CallableSignature::from_overloads( - callable_ty, self, [ Signature::new( @@ -2542,7 +2537,6 @@ impl<'db> Type<'db> { // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... // ``` let signature = CallableSignature::from_overloads( - callable_ty, self, [ Signature::new( @@ -2582,7 +2576,6 @@ impl<'db> Type<'db> { // TODO check call vs signatures of `__new__` and/or `__init__` Type::ClassLiteral(ClassLiteralType { .. }) => { let signature = CallableSignature::single( - callable_ty, self, Signature::new(Parameters::gradual_form(), self.to_instance(db)), ); @@ -2590,12 +2583,10 @@ impl<'db> Type<'db> { } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type) - .signatures(db, callable_ty) - .clone(), - ClassBase::Class(class) => Type::class_literal(class) - .signatures(db, callable_ty) - .clone(), + ClassBase::Dynamic(dynamic_type) => { + Type::Dynamic(dynamic_type).signatures(db).clone() + } + ClassBase::Class(class) => Type::class_literal(class).signatures(db).clone(), }, Type::Instance(_) => { @@ -2604,16 +2595,24 @@ impl<'db> Type<'db> { // of the original object as the "callable type". That ensures that we get errors // like "`X` is not callable" instead of "`` is not // callable". - let Some((signatures, boundness)) = - self.dunder_signature(db, Some(callable_ty), "__call__") - else { - return Signatures::not_callable(callable_ty, self); - }; - let mut signatures = signatures.clone(); - if boundness == Boundness::PossiblyUnbound { - signatures.set_dunder_call_is_possibly_unbound(); + match self + .member_lookup_with_policy( + db, + Name::new_static("__call__"), + MemberLookupPolicy::NoInstanceFallback, + ) + .symbol + { + Symbol::Type(dunder_callable, boundness) => { + let mut signatures = dunder_callable.signatures(db).clone(); + signatures.replace_callable_type(dunder_callable, self); + if boundness == Boundness::PossiblyUnbound { + signatures.set_dunder_call_is_possibly_unbound(); + } + signatures + } + Symbol::Unbound => Signatures::not_callable(self), } - signatures } // Dynamic types are callable, and the return type is the same dynamic type. Similarly, @@ -2622,19 +2621,18 @@ impl<'db> Type<'db> { // Note that this correctly returns `None` if none of the union elements are callable. Type::Union(union) => Signatures::from_union( - callable_ty, self, union .elements(db) .iter() - .map(|element| element.signatures(db, *element)), + .map(|element| element.signatures(db)), ), Type::Intersection(_) => { Signatures::single(CallableSignature::todo("Type::Intersection.call()")) } - _ => Signatures::not_callable(callable_ty, self), + _ => Signatures::not_callable(self), } } @@ -2649,7 +2647,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { - let signatures = self.signatures(db, self); + let signatures = self.signatures(db); let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; for binding in bindings.iter_mut() { // For certain known callables, we have special-case logic to determine the return type @@ -2928,26 +2926,6 @@ impl<'db> Type<'db> { Ok(bindings) } - /// Looks up a dunder method on the meta-type of `self` and returns its signature and - /// boundness. Returns `None` if the meta-type does not contain the dunder method. - fn dunder_signature( - self, - db: &'db dyn Db, - callable_ty: Option>, - name: &str, - ) -> Option<(&'db Signatures<'db>, Boundness)> { - match self - .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) - .symbol - { - Symbol::Type(dunder_callable, boundness) => { - let callable_ty = callable_ty.unwrap_or(dunder_callable); - Some((dunder_callable.signatures(db, callable_ty), boundness)) - } - Symbol::Unbound => None, - } - } - /// Look up a dunder method on the meta-type of `self` and call it. /// /// Returns an `Err` if the dunder method can't be called, @@ -2958,14 +2936,20 @@ impl<'db> Type<'db> { name: &str, arguments: &CallArguments<'_, 'db>, ) -> Result, CallDunderError<'db>> { - let Some((signature, boundness)) = self.dunder_signature(db, None, name) else { - return Err(CallDunderError::MethodNotAvailable); - }; - let bindings = Bindings::bind(db, signature, arguments).into_result()?; - if boundness == Boundness::PossiblyUnbound { - return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); + match self + .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) + .symbol + { + Symbol::Type(dunder_callable, boundness) => { + let signatures = dunder_callable.signatures(db); + let bindings = Bindings::bind(db, signatures, arguments).into_result()?; + if boundness == Boundness::PossiblyUnbound { + return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); + } + Ok(bindings) + } + Symbol::Unbound => return Err(CallDunderError::MethodNotAvailable), } - Ok(bindings) } /// Returns the element type when iterating over `self`. diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index ce4da1c142985..075a4e79d96e3 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -28,14 +28,11 @@ pub(crate) struct Signatures<'db> { } impl<'db> Signatures<'db> { - pub(crate) fn not_callable(callable_type: Type<'db>, signature_type: Type<'db>) -> Self { + pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { Self { - callable_type, + callable_type: signature_type, signature_type, - elements: vec![CallableSignature::not_callable( - callable_type, - signature_type, - )], + elements: vec![CallableSignature::not_callable(signature_type)], } } @@ -49,11 +46,7 @@ impl<'db> Signatures<'db> { /// Creates a new `Signatures` from an iterator of [`Signature`]s. Panics if the iterator is /// empty. - pub(crate) fn from_union( - callable_type: Type<'db>, - signature_type: Type<'db>, - elements: I, - ) -> Self + pub(crate) fn from_union(signature_type: Type<'db>, elements: I) -> Self where I: IntoIterator>, { @@ -63,7 +56,7 @@ impl<'db> Signatures<'db> { .collect(); assert!(!elements.is_empty()); Self { - callable_type, + callable_type: signature_type, signature_type, elements, } @@ -80,6 +73,15 @@ impl<'db> Signatures<'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; @@ -112,9 +114,9 @@ pub(crate) struct CallableSignature<'db> { } impl<'db> CallableSignature<'db> { - pub(crate) fn not_callable(callable_type: Type<'db>, signature_type: Type<'db>) -> Self { + pub(crate) fn not_callable(signature_type: Type<'db>) -> Self { Self { - callable_type, + callable_type: signature_type, signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, @@ -122,13 +124,9 @@ impl<'db> CallableSignature<'db> { } } - pub(crate) fn single( - callable_type: Type<'db>, - signature_type: Type<'db>, - signature: Signature<'db>, - ) -> Self { + pub(crate) fn single(signature_type: Type<'db>, signature: Signature<'db>) -> Self { Self { - callable_type, + callable_type: signature_type, signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, @@ -138,16 +136,12 @@ impl<'db> CallableSignature<'db> { /// 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( - callable_type: Type<'db>, - signature_type: Type<'db>, - overloads: I, - ) -> Self + pub(crate) fn from_overloads(signature_type: Type<'db>, overloads: I) -> Self where I: IntoIterator>, { Self { - callable_type, + callable_type: signature_type, signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, @@ -156,23 +150,29 @@ impl<'db> CallableSignature<'db> { } /// Return a signature for a dynamic callable - pub(crate) fn dynamic(callable_type: Type<'db>) -> Self { + pub(crate) fn dynamic(signature_type: Type<'db>) -> Self { let signature = Signature { parameters: Parameters::gradual_form(), - return_ty: Some(callable_type), + return_ty: Some(signature_type), }; - Self::single(callable_type, callable_type, signature) + 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 callable_type = todo_type!(reason); + let signature_type = todo_type!(reason); let signature = Signature { parameters: Parameters::todo(), - return_ty: Some(callable_type), + return_ty: Some(signature_type), }; - Self::single(callable_type, callable_type, signature) + Self::single(signature_type, signature) + } + + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { + if self.callable_type == before { + self.callable_type = after; + } } /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. From 9082b4bbd0860fc0171e10b6e5e7b7b27d448adf Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 14:25:15 -0400 Subject: [PATCH 41/57] Don't intern signatures --- crates/red_knot_python_semantic/src/types.rs | 25 +++++----- .../src/types/call/bind.rs | 48 ++++++++++--------- .../src/types/class.rs | 2 +- .../src/types/infer.rs | 5 +- .../src/types/signatures.rs | 4 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 562192e901b7a..6d2b8dc7140a2 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2321,7 +2321,6 @@ impl<'db> Type<'db> { /// 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`]. - #[salsa::tracked(return_ref)] fn signatures(self, db: &'db dyn Db) -> Signatures<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { @@ -2648,7 +2647,7 @@ impl<'db> Type<'db> { arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { let signatures = self.signatures(db); - let mut bindings = Bindings::bind(db, signatures, arguments).into_result()?; + let mut bindings = Bindings::bind(db, &signatures, arguments).into_result()?; for binding in bindings.iter_mut() { // For certain known callables, we have special-case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -2942,7 +2941,7 @@ impl<'db> Type<'db> { { Symbol::Type(dunder_callable, boundness) => { let signatures = dunder_callable.signatures(db); - let bindings = Bindings::bind(db, signatures, arguments).into_result()?; + let bindings = Bindings::bind(db, &signatures, arguments).into_result()?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } @@ -3908,7 +3907,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type().display(db), + dunder_iter_type = bindings.callable_type.display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => { report_not_iterable(format_args!( @@ -3916,7 +3915,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type().display(db), + dunder_iter_type = bindings.callable_type.display(db), )); } CallErrorKind::PossiblyNotCallable => { @@ -3925,7 +3924,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type().display(db), + dunder_iter_type = bindings.callable_type.display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -3939,7 +3938,7 @@ impl<'db> IterationErrorKind<'db> { because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type().display(db), + dunder_iter_type = bindings.callable_type.display(db), )), } @@ -4014,7 +4013,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4029,7 +4028,7 @@ impl<'db> IterationErrorKind<'db> { and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4049,7 +4048,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )), } } @@ -4072,7 +4071,7 @@ impl<'db> IterationErrorKind<'db> { its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )), CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4086,7 +4085,7 @@ impl<'db> IterationErrorKind<'db> { because it has no `__iter__` method and its `__getitem__` attribute \ (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )); } CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( @@ -4106,7 +4105,7 @@ impl<'db> IterationErrorKind<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type().display(db), + dunder_getitem_type = bindings.callable_type.display(db), )), } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 4bed88f92cfe9..d0c00c1b0ef62 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -26,7 +26,7 @@ use ruff_text_size::Ranged; /// It's guaranteed that the wrapped bindings have no errors. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Bindings<'db> { - pub(crate) signatures: &'db Signatures<'db>, + pub(crate) callable_type: Type<'db>, inner: BindingsInner<'db>, } @@ -47,12 +47,12 @@ impl<'db> Bindings<'db> { /// overload (if any). pub(crate) fn bind( db: &'db dyn Db, - signatures: &'db Signatures<'db>, + signatures: &Signatures<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { if let Some(signature) = signatures.as_single() { return Bindings { - signatures, + callable_type: signatures.callable_type, inner: BindingsInner::Single(CallableBinding::bind(db, signature, arguments)), }; } @@ -62,7 +62,7 @@ impl<'db> Bindings<'db> { .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect(); Bindings { - signatures, + callable_type: signatures.callable_type, inner: BindingsInner::Union(bindings), } } @@ -71,10 +71,6 @@ impl<'db> Bindings<'db> { matches!(&self.inner, BindingsInner::Single(_)) } - pub(crate) fn callable_type(&self) -> Type<'db> { - self.signatures.callable_type - } - /// Returns the return type of the call. For successful calls, this is the actual return type. /// For calls with binding errors, this is a type that best approximates the return type. For /// types that are not callable, returns `Type::Unknown`. @@ -157,7 +153,7 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signatures.callable_type.display(context.db()) + self.callable_type.display(context.db()) ), ); return; @@ -189,7 +185,9 @@ impl<'db> Bindings<'db> { /// [overloads]: https://github.com/python/typing/pull/1839 #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallableBinding<'db> { - pub(crate) signature: &'db CallableSignature<'db>, + pub(crate) callable_type: Type<'db>, + pub(crate) signature_type: Type<'db>, + pub(crate) dunder_call_is_possibly_unbound: bool, inner: CallableBindingInner<'db>, } @@ -207,12 +205,14 @@ impl<'db> CallableBinding<'db> { /// all parameters, and any errors resulting from binding the call. fn bind( db: &'db dyn Db, - signature: &'db CallableSignature<'db>, + signature: &CallableSignature<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { if !signature.is_callable() { return CallableBinding { - signature, + callable_type: signature.callable_type, + signature_type: signature.signature_type, + dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, inner: CallableBindingInner::NotCallable, }; } @@ -228,7 +228,9 @@ impl<'db> CallableBinding<'db> { if let Some(single) = signature.as_single() { let binding = Binding::bind(db, single, arguments.as_ref()); return CallableBinding { - signature, + callable_type: signature.callable_type, + signature_type: signature.signature_type, + dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, inner: CallableBindingInner::Single(binding), }; } @@ -244,7 +246,9 @@ impl<'db> CallableBinding<'db> { .map(|signature| Binding::bind(db, signature, arguments.as_ref())) .collect(); CallableBinding { - signature, + callable_type: signature.callable_type, + signature_type: signature.signature_type, + dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, inner: CallableBindingInner::Overloaded(overloads), } } @@ -274,7 +278,7 @@ impl<'db> CallableBinding<'db> { return Err(CallErrorKind::BindingError); } - if self.signature.dunder_call_is_possibly_unbound { + if self.dunder_call_is_possibly_unbound { return Err(CallErrorKind::PossiblyNotCallable); } @@ -331,26 +335,25 @@ impl<'db> CallableBinding<'db> { node, format_args!( "Object of type `{}` is not callable", - self.signature.callable_type.display(context.db()), + self.callable_type.display(context.db()), ), ); return; } - if self.signature.dunder_call_is_possibly_unbound { + if self.dunder_call_is_possibly_unbound { context.report_lint( &CALL_NON_CALLABLE, node, format_args!( "Object of type `{}` is not callable (possibly unbound `__call__` method)", - self.signature.callable_type.display(context.db()), + self.callable_type.display(context.db()), ), ); return; } - let callable_description = - CallableDescription::new(context.db(), self.signature.callable_type); + let callable_description = CallableDescription::new(context.db(), self.callable_type); if self.overloads().len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, @@ -367,13 +370,12 @@ impl<'db> CallableBinding<'db> { return; } - let callable_description = - CallableDescription::new(context.db(), self.signature.signature_type); + let callable_description = CallableDescription::new(context.db(), self.signature_type); for overload in self.overloads() { overload.report_diagnostics( context, node, - self.signature.signature_type, + self.signature_type, callable_description.as_ref(), ); } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 55c34948d510d..33182733c671c 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -285,7 +285,7 @@ impl<'db> Class<'db> { Ok(bindings) => Ok(bindings.return_type(db)), Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError { - kind: MetaclassErrorKind::NotCallable(bindings.callable_type()), + kind: MetaclassErrorKind::NotCallable(bindings.callable_type), }), // TODO we should also check for binding errors that would indicate the metaclass diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index fde224c34e909..541c0378e18a9 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3863,7 +3863,6 @@ impl<'db> TypeInferenceBuilder<'db> { Ok(bindings) => { for binding in bindings.iter() { let Some(known_function) = binding - .signature .callable_type .into_function_literal() .and_then(|function_type| function_type.known(self.db())) @@ -5654,7 +5653,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type().display(self.db()), + bindings.callable_type.display(self.db()), value_ty.display(self.db()), ), ); @@ -5704,7 +5703,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type().display(self.db()), + bindings.callable_type.display(self.db()), value_ty.display(self.db()), ), ); diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 075a4e79d96e3..5bc43e37729be 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -48,11 +48,11 @@ impl<'db> Signatures<'db> { /// empty. pub(crate) fn from_union(signature_type: Type<'db>, elements: I) -> Self where - I: IntoIterator>, + I: IntoIterator>, { let elements: Vec<_> = elements .into_iter() - .flat_map(|s| s.elements.iter().cloned()) + .flat_map(|s| s.elements.into_iter()) .collect(); assert!(!elements.is_empty()); Self { From edc8c1df04a0361379fa0ab44ef7aef594e2df84 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 14:27:46 -0400 Subject: [PATCH 42/57] Add with_bound_type --- crates/red_knot_python_semantic/src/types.rs | 4 ++-- crates/red_knot_python_semantic/src/types/signatures.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6d2b8dc7140a2..82e0896da8082 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2325,8 +2325,8 @@ impl<'db> Type<'db> { match self { Type::Callable(CallableType::BoundMethod(bound_method)) => { let signature = bound_method.function(db).signature(db); - let mut signature = CallableSignature::single(self, signature.clone()); - signature.bound_type = Some(bound_method.self_instance(db)); + let signature = CallableSignature::single(self, signature.clone()) + .with_bound_type(bound_method.self_instance(db)); Signatures::single(signature) } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 5bc43e37729be..e412cef40d9bb 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -169,6 +169,11 @@ impl<'db> CallableSignature<'db> { 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 + } + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { if self.callable_type == before { self.callable_type = after; From bb177159a9193d2ec52c6f783a6ca8239a50f9d2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 14:28:26 -0400 Subject: [PATCH 43/57] clippy --- crates/red_knot_python_semantic/src/types.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 82e0896da8082..19aacb9264cf8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2582,10 +2582,8 @@ impl<'db> Type<'db> { } Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { - ClassBase::Dynamic(dynamic_type) => { - Type::Dynamic(dynamic_type).signatures(db).clone() - } - ClassBase::Class(class) => Type::class_literal(class).signatures(db).clone(), + ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).signatures(db), + ClassBase::Class(class) => Type::class_literal(class).signatures(db), }, Type::Instance(_) => { @@ -2947,7 +2945,7 @@ impl<'db> Type<'db> { } Ok(bindings) } - Symbol::Unbound => return Err(CallDunderError::MethodNotAvailable), + Symbol::Unbound => Err(CallDunderError::MethodNotAvailable), } } From 50f4bd300ba7b8a429782ba0ae84d9adbeaf0c16 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:05:17 -0400 Subject: [PATCH 44/57] Match on the binding type --- .../resources/mdtest/call/union.md | 13 +++++++++++++ crates/red_knot_python_semantic/src/types.rs | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/union.md b/crates/red_knot_python_semantic/resources/mdtest/call/union.md index 31240b0c1d9ba..b5c7964cb65d0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/union.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/union.md @@ -148,3 +148,16 @@ def _(flag: bool): x = f(3) reveal_type(x) # revealed: Unknown ``` + +## Union including a special-cased function + +```py +def _(flag: bool): + if flag: + f = str + else: + f = repr + reveal_type(str("string")) # revealed: Literal["string"] + reveal_type(repr("string")) # revealed: Literal["'string'"] + reveal_type(f("string")) # revealed: Literal["string", "'string'"] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 19aacb9264cf8..e58fce4db1086 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2650,11 +2650,12 @@ impl<'db> Type<'db> { // For certain known callables, we have special-case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case // listed here should have a corresponding clause above in `signatures`. + let binding_type = binding.callable_type; let Some((overload_index, overload)) = binding.matching_overload_mut() else { continue; }; - match self { + match binding_type { Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { if function.has_known_class_decorator(db, KnownClass::Classmethod) && function.decorators(db).len() == 1 From 3946f37db08adbcad5fa6442a1b75b888c41bacf Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:17:11 -0400 Subject: [PATCH 45/57] replace iter methods with IntoIterator impls --- crates/red_knot_python_semantic/src/types.rs | 2 +- .../src/types/call.rs | 4 +- .../src/types/call/bind.rs | 48 +++++++++++-------- .../src/types/infer.rs | 2 +- .../src/types/signatures.rs | 26 ++++++---- 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index e58fce4db1086..84e3e85fb7615 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2646,7 +2646,7 @@ impl<'db> Type<'db> { ) -> Result, CallError<'db>> { let signatures = self.signatures(db); let mut bindings = Bindings::bind(db, &signatures, arguments).into_result()?; - for binding in bindings.iter_mut() { + for binding in &mut bindings { // For certain known callables, we have special-case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case // listed here should have a corresponding clause above in `signatures`. diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 79a896f7d1fd5..6d6b80699947e 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -10,11 +10,11 @@ pub(super) use bind::Bindings; /// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. #[derive(Debug, Clone, PartialEq, Eq)] -pub(super) struct CallError<'db>(pub(super) CallErrorKind, pub(super) Box>); +pub(crate) struct CallError<'db>(pub(crate) CallErrorKind, pub(crate) Box>); /// The reason why calling a type failed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum CallErrorKind { +pub(crate) enum CallErrorKind { /// The type is not callable. For a union type, _none_ of the union elements are callable. NotCallable, diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index d0c00c1b0ef62..76efc9c0c5ac5 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -58,7 +58,7 @@ impl<'db> Bindings<'db> { } let bindings = signatures - .iter() + .into_iter() .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect(); Bindings { @@ -104,7 +104,7 @@ impl<'db> Bindings<'db> { let mut all_ok = true; let mut any_binding_error = false; let mut all_not_callable = true; - for binding in self.iter() { + for binding in &self { let result = binding.as_result(); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); @@ -125,20 +125,6 @@ impl<'db> Bindings<'db> { } } - pub(crate) fn iter(&self) -> impl Iterator> + '_ { - match &self.inner { - BindingsInner::Single(binding) => std::slice::from_ref(binding).iter(), - BindingsInner::Union(bindings) => bindings.iter(), - } - } - - pub(crate) fn iter_mut(&mut self) -> impl Iterator> + '_ { - match &mut self.inner { - BindingsInner::Single(binding) => std::slice::from_mut(binding).iter_mut(), - BindingsInner::Union(bindings) => bindings.iter_mut(), - } - } - /// Report diagnostics for all of the errors that occurred when trying to match actual /// arguments to formal parameters. If the callable is a union, or has multiple overloads, we /// report a single diagnostic if we couldn't match any union element or overload. @@ -147,7 +133,7 @@ impl<'db> Bindings<'db> { pub(crate) fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) { // If all union elements are not callable, report that the union as a whole is not // callable. - if self.iter().all(|b| !b.is_callable()) { + if self.into_iter().all(|b| !b.is_callable()) { context.report_lint( &CALL_NON_CALLABLE, node, @@ -162,12 +148,36 @@ impl<'db> Bindings<'db> { // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. - if let Some(first) = self.iter().find(|b| b.as_result().is_err()) { + if let Some(first) = self.into_iter().find(|b| b.as_result().is_err()) { first.report_diagnostics(context, node); } } } +impl<'a, 'db> IntoIterator for &'a Bindings<'db> { + type Item = &'a CallableBinding<'db>; + type IntoIter = std::slice::Iter<'a, CallableBinding<'db>>; + + fn into_iter(self) -> Self::IntoIter { + match &self.inner { + BindingsInner::Single(binding) => std::slice::from_ref(binding).iter(), + BindingsInner::Union(bindings) => bindings.iter(), + } + } +} + +impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { + type Item = &'a mut CallableBinding<'db>; + type IntoIter = std::slice::IterMut<'a, CallableBinding<'db>>; + + fn into_iter(self) -> Self::IntoIter { + match &mut self.inner { + BindingsInner::Single(binding) => std::slice::from_mut(binding).iter_mut(), + BindingsInner::Union(bindings) => bindings.iter_mut(), + } + } +} + /// Binding information for a single callable. If the callable is overloaded, there is a separate /// [`Binding`] for each overload. /// @@ -242,7 +252,7 @@ impl<'db> CallableBinding<'db> { // // [1] https://github.com/python/typing/pull/1839 let overloads = signature - .iter() + .into_iter() .map(|signature| Binding::bind(db, signature, arguments.as_ref())) .collect(); CallableBinding { diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 541c0378e18a9..f86a74eba8145 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3861,7 +3861,7 @@ impl<'db> TypeInferenceBuilder<'db> { let call_arguments = self.infer_arguments(arguments, parameter_expectations); match function_type.try_call(self.db(), &call_arguments) { Ok(bindings) => { - for binding in bindings.iter() { + for binding in &bindings { let Some(known_function) = binding .callable_type .into_function_literal() diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index e412cef40d9bb..2d5a8decf2d42 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -69,10 +69,6 @@ impl<'db> Signatures<'db> { } } - pub(crate) fn iter(&self) -> impl Iterator> { - 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; @@ -89,6 +85,15 @@ impl<'db> Signatures<'db> { } } +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.elements.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)] @@ -188,16 +193,21 @@ impl<'db> CallableSignature<'db> { } } - pub(crate) fn iter(&self) -> impl Iterator> { - self.overloads.iter() - } - /// Returns whether this signature is callable. pub(crate) fn is_callable(&self) -> bool { !self.overloads.is_empty() } } +impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { + type Item = &'a Signature<'db>; + type IntoIter = std::slice::Iter<'a, Signature<'db>>; + + fn into_iter(self) -> Self::IntoIter { + self.overloads.iter() + } +} + /// The signature of one of the overloads of a callable. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub struct Signature<'db> { From 9677ec5fcb82ba75c6caa49d9a77b87fa4f9b232 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:17:36 -0400 Subject: [PATCH 46/57] reduce indentation --- crates/red_knot_python_semantic/src/types.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 84e3e85fb7615..c5198e2a51128 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2669,17 +2669,13 @@ impl<'db> Type<'db> { BoundMethodType::new(db, function, instance.to_meta_type(db)), ))); } - } else { - if let Some(first) = arguments.first_argument() { - if first.is_none(db) { - overload.set_return_type(Type::FunctionLiteral(function)); - } else { - overload.set_return_type(Type::Callable( - CallableType::BoundMethod(BoundMethodType::new( - db, function, first, - )), - )); - } + } else if let Some(first) = arguments.first_argument() { + if first.is_none(db) { + overload.set_return_type(Type::FunctionLiteral(function)); + } else { + overload.set_return_type(Type::Callable(CallableType::BoundMethod( + BoundMethodType::new(db, function, first), + ))); } } } From f7b824785659d13e376a4d09faf84a18ee9fe179 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:20:33 -0400 Subject: [PATCH 47/57] Clarify boxed bindings in `CallError` --- crates/red_knot_python_semantic/src/types/call.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 6d6b80699947e..84f64bcc4958c 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -9,6 +9,8 @@ pub(super) use bind::Bindings; /// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. +/// +/// The bindings are boxed so that we do not pass around large `Err` variants on the stack. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CallError<'db>(pub(crate) CallErrorKind, pub(crate) Box>); From 608ad34751d997a18fec19d88a06b95a8092a6ed Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:27:47 -0400 Subject: [PATCH 48/57] clippy --- crates/red_knot_python_semantic/src/types/call/bind.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 76efc9c0c5ac5..cdcd90976bff5 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -15,7 +15,7 @@ use crate::types::diagnostic::{ PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, }; use crate::types::signatures::Parameter; -use crate::types::{CallableType, UnionBuilder, UnionType}; +use crate::types::{CallableType, UnionType}; use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -78,7 +78,7 @@ impl<'db> Bindings<'db> { match &self.inner { BindingsInner::Single(binding) => binding.return_type(), BindingsInner::Union(bindings) => { - UnionType::from_elements(db, bindings.iter().map(|b| b.return_type())) + UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) } } } From 2e317e19e217a803e376828fb65433db092b4649 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:37:25 -0400 Subject: [PATCH 49/57] Use SmallVec to optimize non-union and non-overloads --- .../src/types/call/bind.rs | 113 +++++------------- .../src/types/signatures.rs | 41 +++---- 2 files changed, 42 insertions(+), 112 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index cdcd90976bff5..d81c769a85f72 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -5,6 +5,8 @@ use std::borrow::Cow; +use smallvec::SmallVec; + use super::{ Argument, CallArguments, CallError, CallErrorKind, CallableSignature, InferContext, Signature, Signatures, Type, @@ -27,16 +29,9 @@ use ruff_text_size::Ranged; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Bindings<'db> { pub(crate) callable_type: Type<'db>, - inner: BindingsInner<'db>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum BindingsInner<'db> { - /// The call resolves to exactly one binding. - Single(CallableBinding<'db>), - - /// The call resolves to multiple bindings. - Union(Box<[CallableBinding<'db>]>), + /// By using SmallVec, we avoid an extra heap allocation for the common case of a non-union + /// type. + elements: SmallVec<[CallableBinding<'db>; 1]>, } impl<'db> Bindings<'db> { @@ -50,37 +45,28 @@ impl<'db> Bindings<'db> { signatures: &Signatures<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if let Some(signature) = signatures.as_single() { - return Bindings { - callable_type: signatures.callable_type, - inner: BindingsInner::Single(CallableBinding::bind(db, signature, arguments)), - }; - } - - let bindings = signatures + let elements = signatures .into_iter() .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect(); Bindings { callable_type: signatures.callable_type, - inner: BindingsInner::Union(bindings), + elements, } } - pub(crate) const fn is_single(&self) -> bool { - matches!(&self.inner, BindingsInner::Single(_)) + pub(crate) fn is_single(&self) -> bool { + self.elements.len() == 1 } /// Returns the return type of the call. For successful calls, this is the actual return type. /// For calls with binding errors, this is a type that best approximates the return type. For /// types that are not callable, returns `Type::Unknown`. pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { - match &self.inner { - BindingsInner::Single(binding) => binding.return_type(), - BindingsInner::Union(bindings) => { - UnionType::from_elements(db, bindings.iter().map(CallableBinding::return_type)) - } + if let [binding] = self.elements.as_slice() { + return binding.return_type(); } + UnionType::from_elements(db, self.into_iter().map(CallableBinding::return_type)) } /// Returns whether all bindings were successful, or an error describing why some bindings were @@ -159,10 +145,7 @@ impl<'a, 'db> IntoIterator for &'a Bindings<'db> { type IntoIter = std::slice::Iter<'a, CallableBinding<'db>>; fn into_iter(self) -> Self::IntoIter { - match &self.inner { - BindingsInner::Single(binding) => std::slice::from_ref(binding).iter(), - BindingsInner::Union(bindings) => bindings.iter(), - } + self.elements.iter() } } @@ -171,10 +154,7 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { type IntoIter = std::slice::IterMut<'a, CallableBinding<'db>>; fn into_iter(self) -> Self::IntoIter { - match &mut self.inner { - BindingsInner::Single(binding) => std::slice::from_mut(binding).iter_mut(), - BindingsInner::Union(bindings) => bindings.iter_mut(), - } + self.elements.iter_mut() } } @@ -198,14 +178,12 @@ pub(crate) struct CallableBinding<'db> { pub(crate) callable_type: Type<'db>, pub(crate) signature_type: Type<'db>, pub(crate) dunder_call_is_possibly_unbound: bool, - inner: CallableBindingInner<'db>, -} -#[derive(Debug, Clone, PartialEq, Eq)] -enum CallableBindingInner<'db> { - NotCallable, - Single(Binding<'db>), - Overloaded(Box<[Binding<'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 + /// non-overloaded callable. + overloads: SmallVec<[Binding<'db>; 1]>, } impl<'db> CallableBinding<'db> { @@ -218,15 +196,6 @@ impl<'db> CallableBinding<'db> { signature: &CallableSignature<'db>, arguments: &CallArguments<'_, 'db>, ) -> Self { - if !signature.is_callable() { - return CallableBinding { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - inner: CallableBindingInner::NotCallable, - }; - } - // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. let arguments = if let Some(bound_type) = signature.bound_type { @@ -235,16 +204,6 @@ impl<'db> CallableBinding<'db> { Cow::Borrowed(arguments) }; - if let Some(single) = signature.as_single() { - let binding = Binding::bind(db, single, arguments.as_ref()); - return CallableBinding { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - inner: CallableBindingInner::Single(binding), - }; - } - // 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 // the matching overloads. Make sure to implement that as part of separating call binding into @@ -259,28 +218,12 @@ impl<'db> CallableBinding<'db> { callable_type: signature.callable_type, signature_type: signature.signature_type, dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - inner: CallableBindingInner::Overloaded(overloads), - } - } - - fn overloads(&self) -> &[Binding<'db>] { - match &self.inner { - CallableBindingInner::NotCallable => &[], - CallableBindingInner::Single(binding) => std::slice::from_ref(binding), - CallableBindingInner::Overloaded(bindings) => bindings, - } - } - - fn overloads_mut(&mut self) -> &mut [Binding<'db>] { - match &mut self.inner { - CallableBindingInner::NotCallable => &mut [], - CallableBindingInner::Single(binding) => std::slice::from_mut(binding), - CallableBindingInner::Overloaded(bindings) => bindings, + overloads, } } fn as_result(&self) -> Result<(), CallErrorKind> { - if matches!(self.inner, CallableBindingInner::NotCallable) { + if !self.is_callable() { return Err(CallErrorKind::NotCallable); } @@ -295,8 +238,8 @@ impl<'db> CallableBinding<'db> { Ok(()) } - const fn is_callable(&self) -> bool { - !matches!(&self.inner, CallableBindingInner::NotCallable) + fn is_callable(&self) -> bool { + !self.overloads.is_empty() } /// Returns whether there were any errors binding this call site. If the callable has multiple @@ -308,7 +251,7 @@ impl<'db> CallableBinding<'db> { /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. pub(crate) fn matching_overload(&self) -> Option<(usize, &Binding<'db>)> { - self.overloads() + self.overloads .iter() .enumerate() .find(|(_, overload)| overload.as_result().is_ok()) @@ -317,7 +260,7 @@ impl<'db> CallableBinding<'db> { /// Returns the overload that matched for this call binding. Returns `None` if none of the /// overloads matched. pub(crate) fn matching_overload_mut(&mut self) -> Option<(usize, &mut Binding<'db>)> { - self.overloads_mut() + self.overloads .iter_mut() .enumerate() .find(|(_, overload)| overload.as_result().is_ok()) @@ -332,7 +275,7 @@ impl<'db> CallableBinding<'db> { if let Some((_, overload)) = self.matching_overload() { return overload.return_type(); } - if let [overload] = self.overloads() { + if let [overload] = self.overloads.as_slice() { return overload.return_type(); } Type::unknown() @@ -364,7 +307,7 @@ impl<'db> CallableBinding<'db> { } let callable_description = CallableDescription::new(context.db(), self.callable_type); - if self.overloads().len() > 1 { + if self.overloads.len() > 1 { context.report_lint( &NO_MATCHING_OVERLOAD, node, @@ -381,7 +324,7 @@ impl<'db> CallableBinding<'db> { } let callable_description = CallableDescription::new(context.db(), self.signature_type); - for overload in self.overloads() { + for overload in &self.overloads { overload.report_diagnostics( context, node, diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 2d5a8decf2d42..ecec9d10d7851 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -10,6 +10,8 @@ //! argument types and return types. For each callable type in the union, the call expression's //! arguments must match _at least one_ overload. +use smallvec::{smallvec, SmallVec}; + use super::{definition_expression_type, DynamicType, Type}; use crate::semantic_index::definition::Definition; use crate::types::todo_type; @@ -24,7 +26,9 @@ pub(crate) struct Signatures<'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>, - elements: Vec>, + /// 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> { @@ -32,7 +36,7 @@ impl<'db> Signatures<'db> { Self { callable_type: signature_type, signature_type, - elements: vec![CallableSignature::not_callable(signature_type)], + elements: smallvec![CallableSignature::not_callable(signature_type)], } } @@ -40,7 +44,7 @@ impl<'db> Signatures<'db> { Self { callable_type: signature.callable_type, signature_type: signature.signature_type, - elements: vec![signature], + elements: smallvec![signature], } } @@ -50,7 +54,7 @@ impl<'db> Signatures<'db> { where I: IntoIterator>, { - let elements: Vec<_> = elements + let elements: SmallVec<_> = elements .into_iter() .flat_map(|s| s.elements.into_iter()) .collect(); @@ -62,13 +66,6 @@ impl<'db> Signatures<'db> { } } - pub(crate) fn as_single(&self) -> Option<&CallableSignature<'db>> { - match self.elements.as_slice() { - [signature] => Some(signature), - _ => None, - } - } - pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { if self.callable_type == before { self.callable_type = after; @@ -115,7 +112,10 @@ pub(crate) struct CallableSignature<'db> { /// The signatures of each overload of this callable. Will be empty if the type is not /// callable. - overloads: Vec>, + /// + /// By using SmallVec, we avoid an extra heap allocation for the common case of a + /// non-overloaded callable. + overloads: SmallVec<[Signature<'db>; 1]>, } impl<'db> CallableSignature<'db> { @@ -125,7 +125,7 @@ impl<'db> CallableSignature<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, - overloads: vec![], + overloads: smallvec![], } } @@ -135,7 +135,7 @@ impl<'db> CallableSignature<'db> { signature_type, dunder_call_is_possibly_unbound: false, bound_type: None, - overloads: vec![signature], + overloads: smallvec![signature], } } @@ -184,19 +184,6 @@ impl<'db> CallableSignature<'db> { self.callable_type = after; } } - - /// Returns the [`Signature`] if this is a non-overloaded callable, [None] otherwise. - pub(crate) fn as_single(&self) -> Option<&Signature<'db>> { - match self.overloads.as_slice() { - [signature] => Some(signature), - _ => None, - } - } - - /// Returns whether this signature is callable. - pub(crate) fn is_callable(&self) -> bool { - !self.overloads.is_empty() - } } impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { From 1d5c021d9e9880944a5efdf4b9df1780b4f1e6c1 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:47:01 -0400 Subject: [PATCH 50/57] Remove into_result for real I guess --- crates/red_knot_python_semantic/src/types.rs | 4 +- .../src/types/call/bind.rs | 55 +++++++++---------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index c5198e2a51128..9a5b68ff1efb8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2645,7 +2645,7 @@ impl<'db> Type<'db> { arguments: &CallArguments<'_, 'db>, ) -> Result, CallError<'db>> { let signatures = self.signatures(db); - let mut bindings = Bindings::bind(db, &signatures, arguments).into_result()?; + let mut bindings = Bindings::bind(db, &signatures, arguments)?; for binding in &mut bindings { // For certain known callables, we have special-case logic to determine the return type // in a way that isn't directly expressible in the type system. Each special case @@ -2936,7 +2936,7 @@ impl<'db> Type<'db> { { Symbol::Type(dunder_callable, boundness) => { let signatures = dunder_callable.signatures(db); - let bindings = Bindings::bind(db, &signatures, arguments).into_result()?; + let bindings = Bindings::bind(db, &signatures, arguments)?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index d81c769a85f72..2ec2d1a6e8128 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -44,34 +44,12 @@ impl<'db> Bindings<'db> { db: &'db dyn Db, signatures: &Signatures<'db>, arguments: &CallArguments<'_, 'db>, - ) -> Self { - let elements = signatures + ) -> Result> { + let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures .into_iter() .map(|signature| CallableBinding::bind(db, signature, arguments)) .collect(); - Bindings { - callable_type: signatures.callable_type, - elements, - } - } - - pub(crate) fn is_single(&self) -> bool { - self.elements.len() == 1 - } - - /// Returns the return type of the call. For successful calls, this is the actual return type. - /// For calls with binding errors, this is a type that best approximates the return type. For - /// types that are not callable, returns `Type::Unknown`. - pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { - if let [binding] = self.elements.as_slice() { - return binding.return_type(); - } - UnionType::from_elements(db, self.into_iter().map(CallableBinding::return_type)) - } - /// Returns whether all bindings were successful, or an error describing why some bindings were - /// unsuccessful. - pub(crate) fn into_result(self) -> Result> { // In order of precedence: // // - If every union element is Ok, then the union is too. @@ -90,27 +68,46 @@ impl<'db> Bindings<'db> { let mut all_ok = true; let mut any_binding_error = false; let mut all_not_callable = true; - for binding in &self { + for binding in &elements { let result = binding.as_result(); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable)); } + let bindings = Bindings { + callable_type: signatures.callable_type, + elements, + }; + if all_ok { - Ok(self) + Ok(bindings) } else if any_binding_error { - Err(CallError(CallErrorKind::BindingError, Box::new(self))) + Err(CallError(CallErrorKind::BindingError, Box::new(bindings))) } else if all_not_callable { - Err(CallError(CallErrorKind::NotCallable, Box::new(self))) + Err(CallError(CallErrorKind::NotCallable, Box::new(bindings))) } else { Err(CallError( CallErrorKind::PossiblyNotCallable, - Box::new(self), + Box::new(bindings), )) } } + pub(crate) fn is_single(&self) -> bool { + self.elements.len() == 1 + } + + /// Returns the return type of the call. For successful calls, this is the actual return type. + /// For calls with binding errors, this is a type that best approximates the return type. For + /// types that are not callable, returns `Type::Unknown`. + pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { + if let [binding] = self.elements.as_slice() { + return binding.return_type(); + } + UnionType::from_elements(db, self.into_iter().map(CallableBinding::return_type)) + } + /// Report diagnostics for all of the errors that occurred when trying to match actual /// arguments to formal parameters. If the callable is a union, or has multiple overloads, we /// report a single diagnostic if we couldn't match any union element or overload. From 23c311d4669a388ab79dced67cb991dc57d9c34e Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:47:38 -0400 Subject: [PATCH 51/57] clippy --- crates/red_knot_python_semantic/src/types/call/bind.rs | 4 ++-- crates/red_knot_python_semantic/src/types/signatures.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 2ec2d1a6e8128..3b25fe89451e9 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -29,7 +29,7 @@ use ruff_text_size::Ranged; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Bindings<'db> { pub(crate) callable_type: Type<'db>, - /// By using SmallVec, we avoid an extra heap allocation for the common case of a non-union + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union /// type. elements: SmallVec<[CallableBinding<'db>; 1]>, } @@ -178,7 +178,7 @@ pub(crate) struct CallableBinding<'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 + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a /// non-overloaded callable. overloads: SmallVec<[Binding<'db>; 1]>, } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index ecec9d10d7851..eeac874ab70d9 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -26,7 +26,7 @@ pub(crate) struct Signatures<'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 + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union /// type. elements: SmallVec<[CallableSignature<'db>; 1]>, } @@ -113,7 +113,7 @@ pub(crate) 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 + /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a /// non-overloaded callable. overloads: SmallVec<[Signature<'db>; 1]>, } From c7946127fd1ce0b73512a0f32b0e96d87c0694a6 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 15:57:29 -0400 Subject: [PATCH 52/57] Remove some identation --- crates/red_knot_python_semantic/src/types.rs | 278 +++++++++---------- 1 file changed, 135 insertions(+), 143 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 9a5b68ff1efb8..045c6b4424267 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -3896,46 +3896,44 @@ impl<'db> IterationErrorKind<'db> { // or similar, rather than as part of the same sentence as the error message. match self { - Self::IterCallError(dunder_iter_call_error, bindings) => match dunder_iter_call_error { - CallErrorKind::NotCallable => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because its `__iter__` attribute has type `{dunder_iter_type}`, \ - which is not callable", + Self::IterCallError(CallErrorKind::NotCallable, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because its `__iter__` attribute has type `{dunder_iter_type}`, \ + which is not callable", + iterable_type = iterable_type.display(db), + dunder_iter_type = bindings.callable_type.display(db), + )), + Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => { + report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because its `__iter__` attribute (with type `{dunder_iter_type}`) \ + may not be callable", iterable_type = iterable_type.display(db), dunder_iter_type = bindings.callable_type.display(db), - )), - CallErrorKind::PossiblyNotCallable if bindings.is_single() => { - report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` attribute (with type `{dunder_iter_type}`) \ - may not be callable", - iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), - )); - } - CallErrorKind::PossiblyNotCallable => { - report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` attribute (with type `{dunder_iter_type}`) \ - may not be callable", - iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), - )); - } - CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because its `__iter__` method has an invalid signature \ - (expected `def __iter__(self): ...`)", - iterable_type = iterable_type.display(db), - )), - CallErrorKind::BindingError => report_not_iterable(format_args!( + )); + } + Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) => { + report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` method (with type `{dunder_iter_type}`) \ - may have an invalid signature (expected `def __iter__(self): ...`)", + because its `__iter__` attribute (with type `{dunder_iter_type}`) \ + may not be callable", iterable_type = iterable_type.display(db), dunder_iter_type = bindings.callable_type.display(db), - )), + )); } + Self::IterCallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because its `__iter__` method has an invalid signature \ + (expected `def __iter__(self): ...`)", + iterable_type = iterable_type.display(db), + )), + Self::IterCallError(CallErrorKind::BindingError, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because its `__iter__` method (with type `{dunder_iter_type}`) \ + may have an invalid signature (expected `def __iter__(self): ...`)", + iterable_type = iterable_type.display(db), + dunder_iter_type = bindings.callable_type.display(db), + )), Self::IterReturnsInvalidIterator { iterator, @@ -3955,36 +3953,34 @@ impl<'db> IterationErrorKind<'db> { iterable_type = iterable_type.display(db), iterator_type = iterator.display(db), )), - CallDunderError::CallError(dunder_next_call_error, bindings) => match dunder_next_call_error { - CallErrorKind::NotCallable => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because its `__iter__` method returns an object of type `{iterator_type}`, \ - which has a `__next__` attribute that is not callable", - iterable_type = iterable_type.display(db), - iterator_type = iterator.display(db), - )), - CallErrorKind::PossiblyNotCallable => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` method returns an object of type `{iterator_type}`, \ - which has a `__next__` attribute that may not be callable", - iterable_type = iterable_type.display(db), - iterator_type = iterator.display(db), - )), - CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because its `__iter__` method returns an object of type `{iterator_type}`, \ - which has an invalid `__next__` method (expected `def __next__(self): ...`)", - iterable_type = iterable_type.display(db), - iterator_type = iterator.display(db), - )), - CallErrorKind::BindingError => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because its `__iter__` method returns an object of type `{iterator_type}`, \ - which may have an invalid `__next__` method (expected `def __next__(self): ...`)", - iterable_type = iterable_type.display(db), - iterator_type = iterator.display(db), - )), - } + CallDunderError::CallError(CallErrorKind::NotCallable, _) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because its `__iter__` method returns an object of type `{iterator_type}`, \ + which has a `__next__` attribute that is not callable", + iterable_type = iterable_type.display(db), + iterator_type = iterator.display(db), + )), + CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, _) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because its `__iter__` method returns an object of type `{iterator_type}`, \ + which has a `__next__` attribute that may not be callable", + iterable_type = iterable_type.display(db), + iterator_type = iterator.display(db), + )), + CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because its `__iter__` method returns an object of type `{iterator_type}`, \ + which has an invalid `__next__` method (expected `def __next__(self): ...`)", + iterable_type = iterable_type.display(db), + iterator_type = iterator.display(db), + )), + CallDunderError::CallError(CallErrorKind::BindingError, _) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because its `__iter__` method returns an object of type `{iterator_type}`, \ + which may have an invalid `__next__` method (expected `def __next__(self): ...`)", + iterable_type = iterable_type.display(db), + iterator_type = iterator.display(db), + )), } Self::PossiblyUnboundIterAndGetitemError { @@ -4001,51 +3997,49 @@ impl<'db> IterationErrorKind<'db> { because it may not have an `__iter__` method or a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::CallError(dunder_getitem_call_error, bindings) => match dunder_getitem_call_error { - CallErrorKind::NotCallable => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it may not have an `__iter__` method \ - and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ - which is not callable", - iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), - )), - CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it may not have an `__iter__` method \ - and its `__getitem__` attribute may not be callable", - iterable_type = iterable_type.display(db), - )), - CallErrorKind::PossiblyNotCallable => { - report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it may not have an `__iter__` method \ - and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ - may not be callable", - iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), - )); - } - CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it may not have an `__iter__` method \ - and its `__getitem__` method has an incorrect signature \ - for the old-style iteration protocol \ - (expected a signature at least as permissive as \ - `def __getitem__(self, key: int): ...`)", - iterable_type = iterable_type.display(db), - )), - CallErrorKind::BindingError => report_not_iterable(format_args!( + CallDunderError::CallError(CallErrorKind::NotCallable, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it may not have an `__iter__` method \ + and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ + which is not callable", + iterable_type = iterable_type.display(db), + dunder_getitem_type = bindings.callable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it may not have an `__iter__` method \ + and its `__getitem__` attribute may not be callable", + iterable_type = iterable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => { + report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ - and its `__getitem__` method (with type `{dunder_getitem_type}`) - may have an incorrect signature for the old-style iteration protocol \ - (expected a signature at least as permissive as \ - `def __getitem__(self, key: int): ...`)", + and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ + may not be callable", iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.callable_type.display(db), - )), + )); } + CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it may not have an `__iter__` method \ + and its `__getitem__` method has an incorrect signature \ + for the old-style iteration protocol \ + (expected a signature at least as permissive as \ + `def __getitem__(self, key: int): ...`)", + iterable_type = iterable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::BindingError, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it may not have an `__iter__` method \ + and its `__getitem__` method (with type `{dunder_getitem_type}`) + may have an incorrect signature for the old-style iteration protocol \ + (expected a signature at least as permissive as \ + `def __getitem__(self, key: int): ...`)", + iterable_type = iterable_type.display(db), + dunder_getitem_type = bindings.callable_type.display(db), + )), } Self::UnboundIterAndGetitemError { dunder_getitem_error } => match dunder_getitem_error { @@ -4059,50 +4053,48 @@ impl<'db> IterationErrorKind<'db> { and it may not have a `__getitem__` method", iterable_type.display(db) )), - CallDunderError::CallError(dunder_getitem_call_error, bindings) => match dunder_getitem_call_error { - CallErrorKind::NotCallable => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because it has no `__iter__` method and \ - its `__getitem__` attribute has type `{dunder_getitem_type}`, \ - which is not callable", - iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), - )), - CallErrorKind::PossiblyNotCallable if bindings.is_single() => report_not_iterable(format_args!( + CallDunderError::CallError(CallErrorKind::NotCallable, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because it has no `__iter__` method and \ + its `__getitem__` attribute has type `{dunder_getitem_type}`, \ + which is not callable", + iterable_type = iterable_type.display(db), + dunder_getitem_type = bindings.callable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it has no `__iter__` method and its `__getitem__` attribute \ + may not be callable", + iterable_type = iterable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) => { + report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it has no `__iter__` method and its `__getitem__` attribute \ - may not be callable", - iterable_type = iterable_type.display(db), - )), - CallErrorKind::PossiblyNotCallable => { - report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it has no `__iter__` method and its `__getitem__` attribute \ - (with type `{dunder_getitem_type}`) may not be callable", - iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), - )); - } - CallErrorKind::BindingError if bindings.is_single() => report_not_iterable(format_args!( - "Object of type `{iterable_type}` is not iterable \ - because it has no `__iter__` method and \ - its `__getitem__` method has an incorrect signature \ - for the old-style iteration protocol \ - (expected a signature at least as permissive as \ - `def __getitem__(self, key: int): ...`)", - iterable_type = iterable_type.display(db), - )), - CallErrorKind::BindingError => report_not_iterable(format_args!( - "Object of type `{iterable_type}` may not be iterable \ - because it has no `__iter__` method and \ - its `__getitem__` method (with type `{dunder_getitem_type}`) \ - may have an incorrect signature for the old-style iteration protocol \ - (expected a signature at least as permissive as \ - `def __getitem__(self, key: int): ...`)", + (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), dunder_getitem_type = bindings.callable_type.display(db), - )), + )); } + CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( + "Object of type `{iterable_type}` is not iterable \ + because it has no `__iter__` method and \ + its `__getitem__` method has an incorrect signature \ + for the old-style iteration protocol \ + (expected a signature at least as permissive as \ + `def __getitem__(self, key: int): ...`)", + iterable_type = iterable_type.display(db), + )), + CallDunderError::CallError(CallErrorKind::BindingError, bindings) => report_not_iterable(format_args!( + "Object of type `{iterable_type}` may not be iterable \ + because it has no `__iter__` method and \ + its `__getitem__` method (with type `{dunder_getitem_type}`) \ + may have an incorrect signature for the old-style iteration protocol \ + (expected a signature at least as permissive as \ + `def __getitem__(self, key: int): ...`)", + iterable_type = iterable_type.display(db), + dunder_getitem_type = bindings.callable_type.display(db), + )), } } } From fd1ccd8be32e1c89cb279748e308837a96044f7f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 16:00:03 -0400 Subject: [PATCH 53/57] todo half done --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 045c6b4424267..d629cc36a21b7 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -4326,7 +4326,7 @@ impl<'db> FunctionType<'db> { /// Returns `None` if the function is overloaded. This powers the `CallableTypeFromFunction` /// special form from the `knot_extensions` module. pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - // TODO: Add support for overloaded callables; return `Type`, not `Option`. + // TODO: Add support for overloaded callables Type::Callable(CallableType::General(GeneralCallableType::new( db, self.signature(db).clone(), From 28c2c62d85aad321c6b3683d98922471480fab8f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 16:02:36 -0400 Subject: [PATCH 54/57] Remove multiple class is_known calls --- crates/red_knot_python_semantic/src/types.rs | 220 +++++++++---------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index d629cc36a21b7..15a6869b5fc6b 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2454,132 +2454,128 @@ impl<'db> Type<'db> { function_type.signature(db).clone(), )), - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Bool) => - { - // ```py - // class bool(int): - // def __new__(cls, o: object = ..., /) -> Self: ... - // ``` - let signature = CallableSignature::single( - self, - Signature::new( - Parameters::new([Parameter::new( - Some(Name::new_static("o")), - Some(Type::any()), - ParameterKind::PositionalOnly { - default_ty: Some(Type::BooleanLiteral(false)), - }, - )]), - Some(KnownClass::Bool.to_instance(db)), - ), - ); - Signatures::single(signature) - } - - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Str) => - { - // ```py - // class str(Sequence[str]): - // @overload - // def __new__(cls, object: object = ...) -> Self: ... - // @overload - // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... - // ``` - let signature = CallableSignature::from_overloads( - self, - [ + Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) { + Some(KnownClass::Bool) => { + // ```py + // class bool(int): + // def __new__(cls, o: object = ..., /) -> Self: ... + // ``` + let signature = CallableSignature::single( + self, Signature::new( Parameters::new([Parameter::new( Some(Name::new_static("o")), Some(Type::any()), ParameterKind::PositionalOnly { - default_ty: Some(Type::string_literal(db, "")), + default_ty: Some(Type::BooleanLiteral(false)), }, )]), - Some(KnownClass::Str.to_instance(db)), - ), - Signature::new( - Parameters::new([ - Parameter::new( - Some(Name::new_static("o")), - Some(Type::any()), // TODO: ReadableBuffer - ParameterKind::PositionalOnly { default_ty: None }, - ), - Parameter::new( - Some(Name::new_static("encoding")), - Some(KnownClass::Str.to_instance(db)), - ParameterKind::PositionalOnly { default_ty: None }, - ), - Parameter::new( - Some(Name::new_static("errors")), - Some(KnownClass::Str.to_instance(db)), - ParameterKind::PositionalOnly { default_ty: None }, - ), - ]), - Some(KnownClass::Str.to_instance(db)), + Some(KnownClass::Bool.to_instance(db)), ), - ], - ); - Signatures::single(signature) - } + ); + Signatures::single(signature) + } - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Type) => - { - // ```py - // class type: - // @overload - // def __init__(self, o: object, /) -> None: ... - // @overload - // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... - // ``` - let signature = CallableSignature::from_overloads( - self, - [ - Signature::new( - Parameters::new([Parameter::new( - Some(Name::new_static("o")), - Some(Type::any()), - ParameterKind::PositionalOnly { default_ty: None }, - )]), - Some(KnownClass::Type.to_instance(db)), - ), - Signature::new( - Parameters::new([ - Parameter::new( + Some(KnownClass::Str) => { + // ```py + // class str(Sequence[str]): + // @overload + // def __new__(cls, object: object = ...) -> Self: ... + // @overload + // def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... + // ``` + let signature = CallableSignature::from_overloads( + self, + [ + Signature::new( + Parameters::new([Parameter::new( Some(Name::new_static("o")), Some(Type::any()), - ParameterKind::PositionalOnly { default_ty: None }, - ), - Parameter::new( - Some(Name::new_static("bases")), - Some(Type::any()), - ParameterKind::PositionalOnly { default_ty: None }, - ), - Parameter::new( - Some(Name::new_static("dict")), + ParameterKind::PositionalOnly { + default_ty: Some(Type::string_literal(db, "")), + }, + )]), + Some(KnownClass::Str.to_instance(db)), + ), + Signature::new( + Parameters::new([ + Parameter::new( + Some(Name::new_static("o")), + Some(Type::any()), // TODO: ReadableBuffer + ParameterKind::PositionalOnly { default_ty: None }, + ), + Parameter::new( + Some(Name::new_static("encoding")), + Some(KnownClass::Str.to_instance(db)), + ParameterKind::PositionalOnly { default_ty: None }, + ), + Parameter::new( + Some(Name::new_static("errors")), + Some(KnownClass::Str.to_instance(db)), + ParameterKind::PositionalOnly { default_ty: None }, + ), + ]), + Some(KnownClass::Str.to_instance(db)), + ), + ], + ); + Signatures::single(signature) + } + + Some(KnownClass::Type) => { + // ```py + // class type: + // @overload + // def __init__(self, o: object, /) -> None: ... + // @overload + // def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... + // ``` + let signature = CallableSignature::from_overloads( + self, + [ + Signature::new( + Parameters::new([Parameter::new( + Some(Name::new_static("o")), Some(Type::any()), ParameterKind::PositionalOnly { default_ty: None }, - ), - ]), - Some(KnownClass::Type.to_instance(db)), - ), - ], - ); - Signatures::single(signature) - } + )]), + Some(KnownClass::Type.to_instance(db)), + ), + Signature::new( + Parameters::new([ + Parameter::new( + Some(Name::new_static("o")), + Some(Type::any()), + ParameterKind::PositionalOnly { default_ty: None }, + ), + Parameter::new( + Some(Name::new_static("bases")), + Some(Type::any()), + ParameterKind::PositionalOnly { default_ty: None }, + ), + Parameter::new( + Some(Name::new_static("dict")), + Some(Type::any()), + ParameterKind::PositionalOnly { default_ty: None }, + ), + ]), + Some(KnownClass::Type.to_instance(db)), + ), + ], + ); + Signatures::single(signature) + } - // TODO annotated return type on `__new__` or metaclass `__call__` - // TODO check call vs signatures of `__new__` and/or `__init__` - Type::ClassLiteral(ClassLiteralType { .. }) => { - let signature = CallableSignature::single( - self, - Signature::new(Parameters::gradual_form(), self.to_instance(db)), - ); - Signatures::single(signature) - } + // TODO annotated return type on `__new__` or metaclass `__call__` + // TODO check call vs signatures of `__new__` and/or `__init__` + _ => { + let signature = CallableSignature::single( + self, + Signature::new(Parameters::gradual_form(), self.to_instance(db)), + ); + Signatures::single(signature) + } + }, Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).signatures(db), From e57f9868677ceb9a9f79038d4ec5c7fc52e894a8 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 15 Mar 2025 16:04:21 -0400 Subject: [PATCH 55/57] Fix test assertions --- .../resources/mdtest/call/callable_instance.md | 2 +- .../red_knot_python_semantic/resources/mdtest/call/union.md | 4 ++-- ...unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 5b6bb368797bb..4a6b2b1ad1c6d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -71,7 +71,7 @@ def _(flag: bool): a = NonCallable() # error: [call-non-callable] "Object of type `Literal[1]` is not callable" - reveal_type(a()) # revealed: int | Unknown + reveal_type(a()) # revealed: Unknown | int ``` ## Call binding errors diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/union.md b/crates/red_knot_python_semantic/resources/mdtest/call/union.md index b5c7964cb65d0..c72196c6954af 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/union.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/union.md @@ -40,7 +40,7 @@ def _(flag: bool): def f() -> int: return 1 x = f() # error: [call-non-callable] "Object of type `Literal[1]` is not callable" - reveal_type(x) # revealed: int | Unknown + reveal_type(x) # revealed: Unknown | int ``` ## Multiple non-callable elements in a union @@ -58,7 +58,7 @@ def _(flag: bool, flag2: bool): return 1 # TODO we should mention all non-callable elements of the union # error: [call-non-callable] "Object of type `Literal[1]` is not callable" - # revealed: int | Unknown + # revealed: Unknown | int reveal_type(f()) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 95953b915a172..78b92a5c7e22a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -87,7 +87,7 @@ error: lint:not-iterable 35 | # error: [not-iterable] 36 | for y in Iterable2(): | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type ` | `) - may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) + may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) 37 | reveal_type(y) # revealed: bytes | str | int | From 7c88a34dd66e988ea331eae78a2ca3ca7584eb24 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 17 Mar 2025 10:09:53 -0400 Subject: [PATCH 56/57] Update crates/red_knot_python_semantic/src/types.rs Co-authored-by: Alex Waygood --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 15a6869b5fc6b..82b65bce6b640 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -4029,7 +4029,7 @@ impl<'db> IterationErrorKind<'db> { CallDunderError::CallError(CallErrorKind::BindingError, bindings) => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ because it may not have an `__iter__` method \ - and its `__getitem__` method (with type `{dunder_getitem_type}`) + and its `__getitem__` method (with type `{dunder_getitem_type}`) \ may have an incorrect signature for the old-style iteration protocol \ (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", From 6dc3dcf32c89792a1b347f8e5b1e4e5a1545272a Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 17 Mar 2025 10:15:48 -0400 Subject: [PATCH 57/57] Update tests --- ..._unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap index 78b92a5c7e22a..357f420c5627e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__iter__`_and_possibly_invalid_`__getitem__`.snap @@ -86,8 +86,7 @@ error: lint:not-iterable | 35 | # error: [not-iterable] 36 | for y in Iterable2(): - | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type ` | `) - may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) + | ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type ` | `) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`) 37 | reveal_type(y) # revealed: bytes | str | int |