mirror of https://github.com/astral-sh/ruff
[red-knot] Refactor: no mutability in call APIs (#17788)
## Summary
Remove mutability in parameter types for a few functions such as
`with_self` and `try_call`. I tried the `Rc`-approach with cheap cloning
[suggest
here](https://github.com/astral-sh/ruff/pull/17733#discussion_r2068722860)
first, but it turns out we need a whole stack of prepended arguments
(there can be [both `self` *and*
`cls`](3cf44e401a/crates/red_knot_python_semantic/resources/mdtest/call/constructor.md?plain=1#L113)),
and we would need the same construct not just for `CallArguments` but
also for `CallArgumentTypes`. At that point we're cloning `VecDeque`s
anyway, so the overhead of cloning the whole `VecDeque` with all
arguments didn't seem to justify the additional code complexity.
## Benchmarks
Benchmarks on tomllib, black, jinja, isort seem neutral.
This commit is contained in:
parent
6d2c10cca2
commit
ea3f4ac059
|
|
@ -2649,10 +2649,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
if let Symbol::Type(descr_get, descr_get_boundness) = descr_get {
|
||||
let return_ty = descr_get
|
||||
.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([self, instance, owner]),
|
||||
)
|
||||
.try_call(db, &CallArgumentTypes::positional([self, instance, owner]))
|
||||
.map(|bindings| {
|
||||
if descr_get_boundness == Boundness::Bound {
|
||||
bindings.return_type(db)
|
||||
|
|
@ -4207,7 +4204,7 @@ impl<'db> Type<'db> {
|
|||
fn try_call(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
||||
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||
) -> Result<Bindings<'db>, CallError<'db>> {
|
||||
let signatures = self.signatures(db);
|
||||
Bindings::match_parameters(signatures, argument_types).check_types(db, argument_types)
|
||||
|
|
@ -4420,7 +4417,7 @@ impl<'db> Type<'db> {
|
|||
fn try_call_constructor(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
mut argument_types: CallArgumentTypes<'_, 'db>,
|
||||
argument_types: CallArgumentTypes<'_, 'db>,
|
||||
) -> Result<Type<'db>, ConstructorCallError<'db>> {
|
||||
debug_assert!(matches!(
|
||||
self,
|
||||
|
|
@ -4486,7 +4483,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
match new_method {
|
||||
Symbol::Type(new_method, boundness) => {
|
||||
let result = new_method.try_call(db, argument_types);
|
||||
let result = new_method.try_call(db, &argument_types);
|
||||
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
return Some(Err(DunderNewCallError::PossiblyUnbound(result.err())));
|
||||
|
|
|
|||
|
|
@ -11,16 +11,18 @@ impl<'a> CallArguments<'a> {
|
|||
/// Invoke a function with an optional extra synthetic argument (for a `self` or `cls`
|
||||
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
|
||||
/// function is invoked with the unmodified argument list.)
|
||||
pub(crate) fn with_self<F, R>(&mut self, bound_self: Option<Type<'_>>, f: F) -> R
|
||||
pub(crate) fn with_self<F, R>(&self, bound_self: Option<Type<'_>>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
F: FnOnce(&Self) -> R,
|
||||
{
|
||||
let mut call_arguments = self.clone();
|
||||
|
||||
if bound_self.is_some() {
|
||||
self.0.push_front(Argument::Synthetic);
|
||||
call_arguments.0.push_front(Argument::Synthetic);
|
||||
}
|
||||
let result = f(self);
|
||||
let result = f(&call_arguments);
|
||||
if bound_self.is_some() {
|
||||
self.0.pop_front();
|
||||
call_arguments.0.pop_front();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
@ -55,6 +57,7 @@ pub(crate) enum Argument<'a> {
|
|||
}
|
||||
|
||||
/// Arguments for a single call, in source order, along with inferred types for each argument.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct CallArgumentTypes<'a, 'db> {
|
||||
arguments: CallArguments<'a>,
|
||||
types: VecDeque<Type<'db>>,
|
||||
|
|
@ -93,20 +96,20 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> {
|
|||
/// Invoke a function with an optional extra synthetic argument (for a `self` or `cls`
|
||||
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
|
||||
/// function is invoked with the unmodified argument list.)
|
||||
pub(crate) fn with_self<F, R>(&mut self, bound_self: Option<Type<'db>>, f: F) -> R
|
||||
pub(crate) fn with_self<F, R>(&self, bound_self: Option<Type<'db>>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
F: FnOnce(Self) -> R,
|
||||
{
|
||||
let mut call_argument_types = self.clone();
|
||||
|
||||
if let Some(bound_self) = bound_self {
|
||||
self.arguments.0.push_front(Argument::Synthetic);
|
||||
self.types.push_front(bound_self);
|
||||
call_argument_types
|
||||
.arguments
|
||||
.0
|
||||
.push_front(Argument::Synthetic);
|
||||
call_argument_types.types.push_front(bound_self);
|
||||
}
|
||||
let result = f(self);
|
||||
if bound_self.is_some() {
|
||||
self.arguments.0.pop_front();
|
||||
self.types.pop_front();
|
||||
}
|
||||
result
|
||||
f(call_argument_types)
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + '_ {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ impl<'db> Bindings<'db> {
|
|||
/// verify that each argument type is assignable to the corresponding parameter type.
|
||||
pub(crate) fn match_parameters(
|
||||
signatures: Signatures<'db>,
|
||||
arguments: &mut CallArguments<'_>,
|
||||
arguments: &CallArguments<'_>,
|
||||
) -> Self {
|
||||
let mut argument_forms = vec![None; arguments.len()];
|
||||
let mut conflicting_forms = vec![false; arguments.len()];
|
||||
|
|
@ -92,7 +92,7 @@ impl<'db> Bindings<'db> {
|
|||
pub(crate) fn check_types(
|
||||
mut self,
|
||||
db: &'db dyn Db,
|
||||
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
||||
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||
) -> Result<Self, CallError<'db>> {
|
||||
for (signature, element) in self.signatures.iter().zip(&mut self.elements) {
|
||||
element.check_types(db, signature, argument_types);
|
||||
|
|
@ -351,10 +351,7 @@ impl<'db> Bindings<'db> {
|
|||
[Some(Type::PropertyInstance(property)), Some(instance), ..] => {
|
||||
if let Some(getter) = property.getter(db) {
|
||||
if let Ok(return_ty) = getter
|
||||
.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([*instance]),
|
||||
)
|
||||
.try_call(db, &CallArgumentTypes::positional([*instance]))
|
||||
.map(|binding| binding.return_type(db))
|
||||
{
|
||||
overload.set_return_type(return_ty);
|
||||
|
|
@ -383,10 +380,7 @@ impl<'db> Bindings<'db> {
|
|||
[Some(instance), ..] => {
|
||||
if let Some(getter) = property.getter(db) {
|
||||
if let Ok(return_ty) = getter
|
||||
.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([*instance]),
|
||||
)
|
||||
.try_call(db, &CallArgumentTypes::positional([*instance]))
|
||||
.map(|binding| binding.return_type(db))
|
||||
{
|
||||
overload.set_return_type(return_ty);
|
||||
|
|
@ -414,7 +408,7 @@ impl<'db> Bindings<'db> {
|
|||
if let Some(setter) = property.setter(db) {
|
||||
if let Err(_call_error) = setter.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([*instance, *value]),
|
||||
&CallArgumentTypes::positional([*instance, *value]),
|
||||
) {
|
||||
overload.errors.push(BindingError::InternalCallError(
|
||||
"calling the setter failed",
|
||||
|
|
@ -433,7 +427,7 @@ impl<'db> Bindings<'db> {
|
|||
if let Some(setter) = property.setter(db) {
|
||||
if let Err(_call_error) = setter.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([*instance, *value]),
|
||||
&CallArgumentTypes::positional([*instance, *value]),
|
||||
) {
|
||||
overload.errors.push(BindingError::InternalCallError(
|
||||
"calling the setter failed",
|
||||
|
|
@ -874,7 +868,7 @@ pub(crate) struct CallableBinding<'db> {
|
|||
impl<'db> CallableBinding<'db> {
|
||||
fn match_parameters(
|
||||
signature: &CallableSignature<'db>,
|
||||
arguments: &mut CallArguments<'_>,
|
||||
arguments: &CallArguments<'_>,
|
||||
argument_forms: &mut [Option<ParameterForm>],
|
||||
conflicting_forms: &mut [bool],
|
||||
) -> Self {
|
||||
|
|
@ -912,13 +906,13 @@ impl<'db> CallableBinding<'db> {
|
|||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
signature: &CallableSignature<'db>,
|
||||
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
||||
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||
) {
|
||||
// If this callable is a bound method, prepend the self instance onto the arguments list
|
||||
// before checking.
|
||||
argument_types.with_self(signature.bound_type, |argument_types| {
|
||||
for (signature, overload) in signature.iter().zip(&mut self.overloads) {
|
||||
overload.check_types(db, signature, argument_types);
|
||||
overload.check_types(db, signature, &argument_types);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -772,9 +772,9 @@ impl<'db> ClassLiteral<'db> {
|
|||
let namespace = KnownClass::Dict.to_instance(db);
|
||||
|
||||
// TODO: Other keyword arguments?
|
||||
let mut arguments = CallArgumentTypes::positional([name, bases, namespace]);
|
||||
let arguments = CallArgumentTypes::positional([name, bases, namespace]);
|
||||
|
||||
let return_ty_result = match metaclass.try_call(db, &mut arguments) {
|
||||
let return_ty_result = match metaclass.try_call(db, &arguments) {
|
||||
Ok(bindings) => Ok(bindings.return_type(db)),
|
||||
|
||||
Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError {
|
||||
|
|
|
|||
|
|
@ -1815,7 +1815,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() {
|
||||
inferred_ty = match decorator_ty
|
||||
.try_call(self.db(), &mut CallArgumentTypes::positional([inferred_ty]))
|
||||
.try_call(self.db(), &CallArgumentTypes::positional([inferred_ty]))
|
||||
.map(|bindings| bindings.return_type(self.db()))
|
||||
{
|
||||
Ok(return_ty) => return_ty,
|
||||
|
|
@ -2832,7 +2832,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let successful_call = meta_dunder_set
|
||||
.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([
|
||||
&CallArgumentTypes::positional([
|
||||
meta_attr_ty,
|
||||
object_ty,
|
||||
value_ty,
|
||||
|
|
@ -2973,7 +2973,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let successful_call = meta_dunder_set
|
||||
.try_call(
|
||||
db,
|
||||
&mut CallArgumentTypes::positional([
|
||||
&CallArgumentTypes::positional([
|
||||
meta_attr_ty,
|
||||
object_ty,
|
||||
value_ty,
|
||||
|
|
@ -4561,7 +4561,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// We don't call `Type::try_call`, because we want to perform type inference on the
|
||||
// arguments after matching them to parameters, but before checking that the argument types
|
||||
// are assignable to any parameter annotations.
|
||||
let mut call_arguments = Self::parse_arguments(arguments);
|
||||
let call_arguments = Self::parse_arguments(arguments);
|
||||
let callable_type = self.infer_expression(func);
|
||||
|
||||
if let Type::FunctionLiteral(function) = callable_type {
|
||||
|
|
@ -4640,11 +4640,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
|
||||
let signatures = callable_type.signatures(self.db());
|
||||
let bindings = Bindings::match_parameters(signatures, &mut call_arguments);
|
||||
let mut call_argument_types =
|
||||
let bindings = Bindings::match_parameters(signatures, &call_arguments);
|
||||
let call_argument_types =
|
||||
self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms);
|
||||
|
||||
match bindings.check_types(self.db(), &mut call_argument_types) {
|
||||
match bindings.check_types(self.db(), &call_argument_types) {
|
||||
Ok(mut bindings) => {
|
||||
for binding in &mut bindings {
|
||||
let binding_type = binding.callable_type;
|
||||
|
|
@ -6486,7 +6486,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Symbol::Type(contains_dunder, Boundness::Bound) => {
|
||||
// If `__contains__` is available, it is used directly for the membership test.
|
||||
contains_dunder
|
||||
.try_call(db, &mut CallArgumentTypes::positional([right, left]))
|
||||
.try_call(db, &CallArgumentTypes::positional([right, left]))
|
||||
.map(|bindings| bindings.return_type(db))
|
||||
.ok()
|
||||
}
|
||||
|
|
@ -6640,7 +6640,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
generic_context: GenericContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let slice_node = subscript.slice.as_ref();
|
||||
let mut call_argument_types = match slice_node {
|
||||
let call_argument_types = match slice_node {
|
||||
ast::Expr::Tuple(tuple) => CallArgumentTypes::positional(
|
||||
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)),
|
||||
),
|
||||
|
|
@ -6650,8 +6650,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
value_ty,
|
||||
generic_context.signature(self.db()),
|
||||
));
|
||||
let bindings = match Bindings::match_parameters(signatures, &mut call_argument_types)
|
||||
.check_types(self.db(), &mut call_argument_types)
|
||||
let bindings = match Bindings::match_parameters(signatures, &call_argument_types)
|
||||
.check_types(self.db(), &call_argument_types)
|
||||
{
|
||||
Ok(bindings) => bindings,
|
||||
Err(CallError(_, bindings)) => {
|
||||
|
|
@ -6893,7 +6893,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
match ty.try_call(
|
||||
self.db(),
|
||||
&mut CallArgumentTypes::positional([value_ty, slice_ty]),
|
||||
&CallArgumentTypes::positional([value_ty, slice_ty]),
|
||||
) {
|
||||
Ok(bindings) => return bindings.return_type(self.db()),
|
||||
Err(CallError(_, bindings)) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue