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 {
|
if let Symbol::Type(descr_get, descr_get_boundness) = descr_get {
|
||||||
let return_ty = descr_get
|
let return_ty = descr_get
|
||||||
.try_call(
|
.try_call(db, &CallArgumentTypes::positional([self, instance, owner]))
|
||||||
db,
|
|
||||||
&mut CallArgumentTypes::positional([self, instance, owner]),
|
|
||||||
)
|
|
||||||
.map(|bindings| {
|
.map(|bindings| {
|
||||||
if descr_get_boundness == Boundness::Bound {
|
if descr_get_boundness == Boundness::Bound {
|
||||||
bindings.return_type(db)
|
bindings.return_type(db)
|
||||||
|
|
@ -4207,7 +4204,7 @@ impl<'db> Type<'db> {
|
||||||
fn try_call(
|
fn try_call(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||||
) -> Result<Bindings<'db>, CallError<'db>> {
|
) -> Result<Bindings<'db>, CallError<'db>> {
|
||||||
let signatures = self.signatures(db);
|
let signatures = self.signatures(db);
|
||||||
Bindings::match_parameters(signatures, argument_types).check_types(db, argument_types)
|
Bindings::match_parameters(signatures, argument_types).check_types(db, argument_types)
|
||||||
|
|
@ -4420,7 +4417,7 @@ impl<'db> Type<'db> {
|
||||||
fn try_call_constructor(
|
fn try_call_constructor(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
mut argument_types: CallArgumentTypes<'_, 'db>,
|
argument_types: CallArgumentTypes<'_, 'db>,
|
||||||
) -> Result<Type<'db>, ConstructorCallError<'db>> {
|
) -> Result<Type<'db>, ConstructorCallError<'db>> {
|
||||||
debug_assert!(matches!(
|
debug_assert!(matches!(
|
||||||
self,
|
self,
|
||||||
|
|
@ -4486,7 +4483,7 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
match new_method {
|
match new_method {
|
||||||
Symbol::Type(new_method, boundness) => {
|
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 {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
return Some(Err(DunderNewCallError::PossiblyUnbound(result.err())));
|
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`
|
/// 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
|
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
|
||||||
/// function is invoked with the unmodified argument list.)
|
/// 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
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&Self) -> R,
|
||||||
{
|
{
|
||||||
|
let mut call_arguments = self.clone();
|
||||||
|
|
||||||
if bound_self.is_some() {
|
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() {
|
if bound_self.is_some() {
|
||||||
self.0.pop_front();
|
call_arguments.0.pop_front();
|
||||||
}
|
}
|
||||||
result
|
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.
|
/// Arguments for a single call, in source order, along with inferred types for each argument.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct CallArgumentTypes<'a, 'db> {
|
pub(crate) struct CallArgumentTypes<'a, 'db> {
|
||||||
arguments: CallArguments<'a>,
|
arguments: CallArguments<'a>,
|
||||||
types: VecDeque<Type<'db>>,
|
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`
|
/// 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
|
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
|
||||||
/// function is invoked with the unmodified argument list.)
|
/// 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
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(Self) -> R,
|
||||||
{
|
{
|
||||||
|
let mut call_argument_types = self.clone();
|
||||||
|
|
||||||
if let Some(bound_self) = bound_self {
|
if let Some(bound_self) = bound_self {
|
||||||
self.arguments.0.push_front(Argument::Synthetic);
|
call_argument_types
|
||||||
self.types.push_front(bound_self);
|
.arguments
|
||||||
|
.0
|
||||||
|
.push_front(Argument::Synthetic);
|
||||||
|
call_argument_types.types.push_front(bound_self);
|
||||||
}
|
}
|
||||||
let result = f(self);
|
f(call_argument_types)
|
||||||
if bound_self.is_some() {
|
|
||||||
self.arguments.0.pop_front();
|
|
||||||
self.types.pop_front();
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + '_ {
|
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.
|
/// verify that each argument type is assignable to the corresponding parameter type.
|
||||||
pub(crate) fn match_parameters(
|
pub(crate) fn match_parameters(
|
||||||
signatures: Signatures<'db>,
|
signatures: Signatures<'db>,
|
||||||
arguments: &mut CallArguments<'_>,
|
arguments: &CallArguments<'_>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut argument_forms = vec![None; arguments.len()];
|
let mut argument_forms = vec![None; arguments.len()];
|
||||||
let mut conflicting_forms = vec![false; arguments.len()];
|
let mut conflicting_forms = vec![false; arguments.len()];
|
||||||
|
|
@ -92,7 +92,7 @@ impl<'db> Bindings<'db> {
|
||||||
pub(crate) fn check_types(
|
pub(crate) fn check_types(
|
||||||
mut self,
|
mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||||
) -> Result<Self, CallError<'db>> {
|
) -> Result<Self, CallError<'db>> {
|
||||||
for (signature, element) in self.signatures.iter().zip(&mut self.elements) {
|
for (signature, element) in self.signatures.iter().zip(&mut self.elements) {
|
||||||
element.check_types(db, signature, argument_types);
|
element.check_types(db, signature, argument_types);
|
||||||
|
|
@ -351,10 +351,7 @@ impl<'db> Bindings<'db> {
|
||||||
[Some(Type::PropertyInstance(property)), Some(instance), ..] => {
|
[Some(Type::PropertyInstance(property)), Some(instance), ..] => {
|
||||||
if let Some(getter) = property.getter(db) {
|
if let Some(getter) = property.getter(db) {
|
||||||
if let Ok(return_ty) = getter
|
if let Ok(return_ty) = getter
|
||||||
.try_call(
|
.try_call(db, &CallArgumentTypes::positional([*instance]))
|
||||||
db,
|
|
||||||
&mut CallArgumentTypes::positional([*instance]),
|
|
||||||
)
|
|
||||||
.map(|binding| binding.return_type(db))
|
.map(|binding| binding.return_type(db))
|
||||||
{
|
{
|
||||||
overload.set_return_type(return_ty);
|
overload.set_return_type(return_ty);
|
||||||
|
|
@ -383,10 +380,7 @@ impl<'db> Bindings<'db> {
|
||||||
[Some(instance), ..] => {
|
[Some(instance), ..] => {
|
||||||
if let Some(getter) = property.getter(db) {
|
if let Some(getter) = property.getter(db) {
|
||||||
if let Ok(return_ty) = getter
|
if let Ok(return_ty) = getter
|
||||||
.try_call(
|
.try_call(db, &CallArgumentTypes::positional([*instance]))
|
||||||
db,
|
|
||||||
&mut CallArgumentTypes::positional([*instance]),
|
|
||||||
)
|
|
||||||
.map(|binding| binding.return_type(db))
|
.map(|binding| binding.return_type(db))
|
||||||
{
|
{
|
||||||
overload.set_return_type(return_ty);
|
overload.set_return_type(return_ty);
|
||||||
|
|
@ -414,7 +408,7 @@ impl<'db> Bindings<'db> {
|
||||||
if let Some(setter) = property.setter(db) {
|
if let Some(setter) = property.setter(db) {
|
||||||
if let Err(_call_error) = setter.try_call(
|
if let Err(_call_error) = setter.try_call(
|
||||||
db,
|
db,
|
||||||
&mut CallArgumentTypes::positional([*instance, *value]),
|
&CallArgumentTypes::positional([*instance, *value]),
|
||||||
) {
|
) {
|
||||||
overload.errors.push(BindingError::InternalCallError(
|
overload.errors.push(BindingError::InternalCallError(
|
||||||
"calling the setter failed",
|
"calling the setter failed",
|
||||||
|
|
@ -433,7 +427,7 @@ impl<'db> Bindings<'db> {
|
||||||
if let Some(setter) = property.setter(db) {
|
if let Some(setter) = property.setter(db) {
|
||||||
if let Err(_call_error) = setter.try_call(
|
if let Err(_call_error) = setter.try_call(
|
||||||
db,
|
db,
|
||||||
&mut CallArgumentTypes::positional([*instance, *value]),
|
&CallArgumentTypes::positional([*instance, *value]),
|
||||||
) {
|
) {
|
||||||
overload.errors.push(BindingError::InternalCallError(
|
overload.errors.push(BindingError::InternalCallError(
|
||||||
"calling the setter failed",
|
"calling the setter failed",
|
||||||
|
|
@ -874,7 +868,7 @@ pub(crate) struct CallableBinding<'db> {
|
||||||
impl<'db> CallableBinding<'db> {
|
impl<'db> CallableBinding<'db> {
|
||||||
fn match_parameters(
|
fn match_parameters(
|
||||||
signature: &CallableSignature<'db>,
|
signature: &CallableSignature<'db>,
|
||||||
arguments: &mut CallArguments<'_>,
|
arguments: &CallArguments<'_>,
|
||||||
argument_forms: &mut [Option<ParameterForm>],
|
argument_forms: &mut [Option<ParameterForm>],
|
||||||
conflicting_forms: &mut [bool],
|
conflicting_forms: &mut [bool],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -912,13 +906,13 @@ impl<'db> CallableBinding<'db> {
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
signature: &CallableSignature<'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
|
// If this callable is a bound method, prepend the self instance onto the arguments list
|
||||||
// before checking.
|
// before checking.
|
||||||
argument_types.with_self(signature.bound_type, |argument_types| {
|
argument_types.with_self(signature.bound_type, |argument_types| {
|
||||||
for (signature, overload) in signature.iter().zip(&mut self.overloads) {
|
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);
|
let namespace = KnownClass::Dict.to_instance(db);
|
||||||
|
|
||||||
// TODO: Other keyword arguments?
|
// 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)),
|
Ok(bindings) => Ok(bindings.return_type(db)),
|
||||||
|
|
||||||
Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError {
|
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() {
|
for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() {
|
||||||
inferred_ty = match decorator_ty
|
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()))
|
.map(|bindings| bindings.return_type(self.db()))
|
||||||
{
|
{
|
||||||
Ok(return_ty) => return_ty,
|
Ok(return_ty) => return_ty,
|
||||||
|
|
@ -2832,7 +2832,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let successful_call = meta_dunder_set
|
let successful_call = meta_dunder_set
|
||||||
.try_call(
|
.try_call(
|
||||||
db,
|
db,
|
||||||
&mut CallArgumentTypes::positional([
|
&CallArgumentTypes::positional([
|
||||||
meta_attr_ty,
|
meta_attr_ty,
|
||||||
object_ty,
|
object_ty,
|
||||||
value_ty,
|
value_ty,
|
||||||
|
|
@ -2973,7 +2973,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let successful_call = meta_dunder_set
|
let successful_call = meta_dunder_set
|
||||||
.try_call(
|
.try_call(
|
||||||
db,
|
db,
|
||||||
&mut CallArgumentTypes::positional([
|
&CallArgumentTypes::positional([
|
||||||
meta_attr_ty,
|
meta_attr_ty,
|
||||||
object_ty,
|
object_ty,
|
||||||
value_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
|
// 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
|
// arguments after matching them to parameters, but before checking that the argument types
|
||||||
// are assignable to any parameter annotations.
|
// 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);
|
let callable_type = self.infer_expression(func);
|
||||||
|
|
||||||
if let Type::FunctionLiteral(function) = callable_type {
|
if let Type::FunctionLiteral(function) = callable_type {
|
||||||
|
|
@ -4640,11 +4640,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signatures = callable_type.signatures(self.db());
|
let signatures = callable_type.signatures(self.db());
|
||||||
let bindings = Bindings::match_parameters(signatures, &mut call_arguments);
|
let bindings = Bindings::match_parameters(signatures, &call_arguments);
|
||||||
let mut call_argument_types =
|
let call_argument_types =
|
||||||
self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms);
|
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) => {
|
Ok(mut bindings) => {
|
||||||
for binding in &mut bindings {
|
for binding in &mut bindings {
|
||||||
let binding_type = binding.callable_type;
|
let binding_type = binding.callable_type;
|
||||||
|
|
@ -6486,7 +6486,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Symbol::Type(contains_dunder, Boundness::Bound) => {
|
Symbol::Type(contains_dunder, Boundness::Bound) => {
|
||||||
// If `__contains__` is available, it is used directly for the membership test.
|
// If `__contains__` is available, it is used directly for the membership test.
|
||||||
contains_dunder
|
contains_dunder
|
||||||
.try_call(db, &mut CallArgumentTypes::positional([right, left]))
|
.try_call(db, &CallArgumentTypes::positional([right, left]))
|
||||||
.map(|bindings| bindings.return_type(db))
|
.map(|bindings| bindings.return_type(db))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
@ -6640,7 +6640,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
generic_context: GenericContext<'db>,
|
generic_context: GenericContext<'db>,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
let slice_node = subscript.slice.as_ref();
|
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(
|
ast::Expr::Tuple(tuple) => CallArgumentTypes::positional(
|
||||||
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)),
|
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)),
|
||||||
),
|
),
|
||||||
|
|
@ -6650,8 +6650,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
value_ty,
|
value_ty,
|
||||||
generic_context.signature(self.db()),
|
generic_context.signature(self.db()),
|
||||||
));
|
));
|
||||||
let bindings = match Bindings::match_parameters(signatures, &mut call_argument_types)
|
let bindings = match Bindings::match_parameters(signatures, &call_argument_types)
|
||||||
.check_types(self.db(), &mut call_argument_types)
|
.check_types(self.db(), &call_argument_types)
|
||||||
{
|
{
|
||||||
Ok(bindings) => bindings,
|
Ok(bindings) => bindings,
|
||||||
Err(CallError(_, bindings)) => {
|
Err(CallError(_, bindings)) => {
|
||||||
|
|
@ -6893,7 +6893,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
match ty.try_call(
|
match ty.try_call(
|
||||||
self.db(),
|
self.db(),
|
||||||
&mut CallArgumentTypes::positional([value_ty, slice_ty]),
|
&CallArgumentTypes::positional([value_ty, slice_ty]),
|
||||||
) {
|
) {
|
||||||
Ok(bindings) => return bindings.return_type(self.db()),
|
Ok(bindings) => return bindings.return_type(self.db()),
|
||||||
Err(CallError(_, bindings)) => {
|
Err(CallError(_, bindings)) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue