mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 20:10:09 +00:00
[red-knot] handle synthetic 'self' argument in call-binding diagnostics (#15362)
This commit is contained in:
parent
21aa12a073
commit
a95deec00f
3 changed files with 65 additions and 15 deletions
|
@ -70,3 +70,32 @@ def _(flag: bool):
|
||||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||||
reveal_type(a()) # revealed: Unknown | int
|
reveal_type(a()) # revealed: Unknown | int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Call binding errors
|
||||||
|
|
||||||
|
### Wrong argument type
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C:
|
||||||
|
def __call__(self, x: int) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
c = C()
|
||||||
|
|
||||||
|
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 2 (`x`) of function `__call__`; expected type `int`"
|
||||||
|
reveal_type(c("foo")) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrong argument type on `self`
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C:
|
||||||
|
# TODO this definition should also be an error; `C` must be assignable to type of `self`
|
||||||
|
def __call__(self: int) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
c = C()
|
||||||
|
|
||||||
|
# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`) of function `__call__`; expected type `int`"
|
||||||
|
reveal_type(c()) # revealed: int
|
||||||
|
```
|
||||||
|
|
|
@ -16,7 +16,7 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
/// Prepend an extra positional argument.
|
/// Prepend an extra positional argument.
|
||||||
pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self {
|
pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self {
|
||||||
let mut arguments = Vec::with_capacity(self.0.len() + 1);
|
let mut arguments = Vec::with_capacity(self.0.len() + 1);
|
||||||
arguments.push(Argument::Positional(self_ty));
|
arguments.push(Argument::Synthetic(self_ty));
|
||||||
arguments.extend_from_slice(&self.0);
|
arguments.extend_from_slice(&self.0);
|
||||||
Self(arguments)
|
Self(arguments)
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,8 @@ impl<'a, 'db> FromIterator<Argument<'a, 'db>> for CallArguments<'a, 'db> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Argument<'a, 'db> {
|
pub(crate) enum Argument<'a, 'db> {
|
||||||
|
/// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site.
|
||||||
|
Synthetic(Type<'db>),
|
||||||
/// A positional argument.
|
/// A positional argument.
|
||||||
Positional(Type<'db>),
|
Positional(Type<'db>),
|
||||||
/// A starred positional argument (e.g. `*args`).
|
/// A starred positional argument (e.g. `*args`).
|
||||||
|
@ -61,6 +63,7 @@ pub(crate) enum Argument<'a, 'db> {
|
||||||
impl<'db> Argument<'_, 'db> {
|
impl<'db> Argument<'_, 'db> {
|
||||||
fn ty(&self) -> Type<'db> {
|
fn ty(&self) -> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Synthetic(ty) => *ty,
|
||||||
Self::Positional(ty) => *ty,
|
Self::Positional(ty) => *ty,
|
||||||
Self::Variadic(ty) => *ty,
|
Self::Variadic(ty) => *ty,
|
||||||
Self::Keyword { name: _, ty } => *ty,
|
Self::Keyword { name: _, ty } => *ty,
|
||||||
|
|
|
@ -24,9 +24,24 @@ pub(crate) fn bind_call<'db>(
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut next_positional = 0;
|
let mut next_positional = 0;
|
||||||
let mut first_excess_positional = None;
|
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() {
|
for (argument_index, argument) in arguments.iter().enumerate() {
|
||||||
let (index, parameter, argument_ty, positional) = match argument {
|
let (index, parameter, argument_ty, positional) = match argument {
|
||||||
Argument::Positional(ty) => {
|
Argument::Positional(ty) | Argument::Synthetic(ty) => {
|
||||||
|
if matches!(argument, Argument::Synthetic(_)) {
|
||||||
|
num_synthetic_args += 1;
|
||||||
|
}
|
||||||
let Some((index, parameter)) = parameters
|
let Some((index, parameter)) = parameters
|
||||||
.get_positional(next_positional)
|
.get_positional(next_positional)
|
||||||
.map(|param| (next_positional, param))
|
.map(|param| (next_positional, param))
|
||||||
|
@ -46,7 +61,7 @@ pub(crate) fn bind_call<'db>(
|
||||||
else {
|
else {
|
||||||
errors.push(CallBindingError::UnknownArgument {
|
errors.push(CallBindingError::UnknownArgument {
|
||||||
argument_name: ast::name::Name::new(name),
|
argument_name: ast::name::Name::new(name),
|
||||||
argument_index,
|
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -62,7 +77,7 @@ pub(crate) fn bind_call<'db>(
|
||||||
if !argument_ty.is_assignable_to(db, expected_ty) {
|
if !argument_ty.is_assignable_to(db, expected_ty) {
|
||||||
errors.push(CallBindingError::InvalidArgumentType {
|
errors.push(CallBindingError::InvalidArgumentType {
|
||||||
parameter: ParameterContext::new(parameter, index, positional),
|
parameter: ParameterContext::new(parameter, index, positional),
|
||||||
argument_index,
|
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||||
expected_ty,
|
expected_ty,
|
||||||
provided_ty: *argument_ty,
|
provided_ty: *argument_ty,
|
||||||
});
|
});
|
||||||
|
@ -74,7 +89,7 @@ pub(crate) fn bind_call<'db>(
|
||||||
parameter_tys[index].replace(union);
|
parameter_tys[index].replace(union);
|
||||||
} else {
|
} else {
|
||||||
errors.push(CallBindingError::ParameterAlreadyAssigned {
|
errors.push(CallBindingError::ParameterAlreadyAssigned {
|
||||||
argument_index,
|
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||||
parameter: ParameterContext::new(parameter, index, positional),
|
parameter: ParameterContext::new(parameter, index, positional),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -82,7 +97,10 @@ pub(crate) fn bind_call<'db>(
|
||||||
}
|
}
|
||||||
if let Some(first_excess_argument_index) = first_excess_positional {
|
if let Some(first_excess_argument_index) = first_excess_positional {
|
||||||
errors.push(CallBindingError::TooManyPositionalArguments {
|
errors.push(CallBindingError::TooManyPositionalArguments {
|
||||||
first_excess_argument_index,
|
first_excess_argument_index: get_argument_index(
|
||||||
|
first_excess_argument_index,
|
||||||
|
num_synthetic_args,
|
||||||
|
),
|
||||||
expected_positional_count: parameters.positional().count(),
|
expected_positional_count: parameters.positional().count(),
|
||||||
provided_positional_count: next_positional,
|
provided_positional_count: next_positional,
|
||||||
});
|
});
|
||||||
|
@ -243,7 +261,7 @@ pub(crate) enum CallBindingError<'db> {
|
||||||
/// parameter.
|
/// parameter.
|
||||||
InvalidArgumentType {
|
InvalidArgumentType {
|
||||||
parameter: ParameterContext,
|
parameter: ParameterContext,
|
||||||
argument_index: usize,
|
argument_index: Option<usize>,
|
||||||
expected_ty: Type<'db>,
|
expected_ty: Type<'db>,
|
||||||
provided_ty: Type<'db>,
|
provided_ty: Type<'db>,
|
||||||
},
|
},
|
||||||
|
@ -252,17 +270,17 @@ pub(crate) enum CallBindingError<'db> {
|
||||||
/// A call argument can't be matched to any parameter.
|
/// A call argument can't be matched to any parameter.
|
||||||
UnknownArgument {
|
UnknownArgument {
|
||||||
argument_name: ast::name::Name,
|
argument_name: ast::name::Name,
|
||||||
argument_index: usize,
|
argument_index: Option<usize>,
|
||||||
},
|
},
|
||||||
/// More positional arguments are provided in the call than can be handled by the signature.
|
/// More positional arguments are provided in the call than can be handled by the signature.
|
||||||
TooManyPositionalArguments {
|
TooManyPositionalArguments {
|
||||||
first_excess_argument_index: usize,
|
first_excess_argument_index: Option<usize>,
|
||||||
expected_positional_count: usize,
|
expected_positional_count: usize,
|
||||||
provided_positional_count: usize,
|
provided_positional_count: usize,
|
||||||
},
|
},
|
||||||
/// Multiple arguments were provided for a single parameter.
|
/// Multiple arguments were provided for a single parameter.
|
||||||
ParameterAlreadyAssigned {
|
ParameterAlreadyAssigned {
|
||||||
argument_index: usize,
|
argument_index: Option<usize>,
|
||||||
parameter: ParameterContext,
|
parameter: ParameterContext,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -372,11 +390,11 @@ impl<'db> CallBindingError<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_node(node: ast::AnyNodeRef, argument_index: usize) -> ast::AnyNodeRef {
|
fn get_node(node: ast::AnyNodeRef, argument_index: Option<usize>) -> ast::AnyNodeRef {
|
||||||
// If we have a Call node, report the diagnostic on the correct argument node;
|
// If we have a Call node and an argument index, report the diagnostic on the correct
|
||||||
// otherwise, report it on the entire provided node.
|
// argument node; otherwise, report it on the entire provided node.
|
||||||
match node {
|
match (node, argument_index) {
|
||||||
ast::AnyNodeRef::ExprCall(call_node) => {
|
(ast::AnyNodeRef::ExprCall(call_node), Some(argument_index)) => {
|
||||||
match call_node
|
match call_node
|
||||||
.arguments
|
.arguments
|
||||||
.arguments_source_order()
|
.arguments_source_order()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue