mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Add positional-only-parameter-as-kwarg error (#20495)
This commit is contained in:
parent
2c916562ba
commit
4ed8c65d29
7 changed files with 192 additions and 76 deletions
|
@ -19,8 +19,8 @@ use crate::place::{Boundness, Place};
|
|||
use crate::types::call::arguments::{Expansion, is_expandable_type};
|
||||
use crate::types::diagnostic::{
|
||||
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
|
||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
|
||||
UNKNOWN_ARGUMENT,
|
||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, POSITIONAL_ONLY_PARAMETER_AS_KWARG,
|
||||
TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT,
|
||||
};
|
||||
use crate::types::enums::is_enum_class;
|
||||
use crate::types::function::{
|
||||
|
@ -1991,6 +1991,7 @@ struct ArgumentMatcher<'a, 'db> {
|
|||
|
||||
argument_matches: Vec<MatchedArgument<'db>>,
|
||||
parameter_matched: Vec<bool>,
|
||||
suppress_missing_error: Vec<bool>,
|
||||
next_positional: usize,
|
||||
first_excess_positional: Option<usize>,
|
||||
num_synthetic_args: usize,
|
||||
|
@ -2009,6 +2010,7 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
|||
errors,
|
||||
argument_matches: vec![MatchedArgument::default(); arguments.len()],
|
||||
parameter_matched: vec![false; parameters.len()],
|
||||
suppress_missing_error: vec![false; parameters.len()],
|
||||
next_positional: 0,
|
||||
first_excess_positional: None,
|
||||
num_synthetic_args: 0,
|
||||
|
@ -2105,10 +2107,21 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
|||
.keyword_by_name(name)
|
||||
.or_else(|| self.parameters.keyword_variadic())
|
||||
else {
|
||||
self.errors.push(BindingError::UnknownArgument {
|
||||
argument_name: ast::name::Name::new(name),
|
||||
argument_index: self.get_argument_index(argument_index),
|
||||
});
|
||||
if let Some((parameter_index, parameter)) =
|
||||
self.parameters.positional_only_by_name(name)
|
||||
{
|
||||
self.errors
|
||||
.push(BindingError::PositionalOnlyParameterAsKwarg {
|
||||
argument_index: self.get_argument_index(argument_index),
|
||||
parameter: ParameterContext::new(parameter, parameter_index, true),
|
||||
});
|
||||
self.suppress_missing_error[parameter_index] = true;
|
||||
} else {
|
||||
self.errors.push(BindingError::UnknownArgument {
|
||||
argument_name: ast::name::Name::new(name),
|
||||
argument_index: self.get_argument_index(argument_index),
|
||||
});
|
||||
}
|
||||
return Err(());
|
||||
};
|
||||
self.assign_argument(
|
||||
|
@ -2223,6 +2236,9 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
|||
let mut missing = vec![];
|
||||
for (index, matched) in self.parameter_matched.iter().copied().enumerate() {
|
||||
if !matched {
|
||||
if self.suppress_missing_error[index] {
|
||||
continue;
|
||||
}
|
||||
let param = &self.parameters[index];
|
||||
if param.is_variadic()
|
||||
|| param.is_keyword_variadic()
|
||||
|
@ -3094,6 +3110,11 @@ pub(crate) enum BindingError<'db> {
|
|||
argument_name: ast::name::Name,
|
||||
argument_index: Option<usize>,
|
||||
},
|
||||
/// A positional-only parameter is passed as keyword argument.
|
||||
PositionalOnlyParameterAsKwarg {
|
||||
argument_index: Option<usize>,
|
||||
parameter: ParameterContext,
|
||||
},
|
||||
/// More positional arguments are provided in the call than can be handled by the signature.
|
||||
TooManyPositionalArguments {
|
||||
first_excess_argument_index: Option<usize>,
|
||||
|
@ -3349,6 +3370,35 @@ impl<'db> BindingError<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Self::PositionalOnlyParameterAsKwarg {
|
||||
argument_index,
|
||||
parameter,
|
||||
} => {
|
||||
let node = Self::get_node(node, *argument_index);
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSITIONAL_ONLY_PARAMETER_AS_KWARG, node)
|
||||
{
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Positional-only parameter {parameter} passed as keyword argument{}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
));
|
||||
if let Some(union_diag) = union_diag {
|
||||
union_diag.add_union_context(context.db(), &mut diag);
|
||||
} else if let Some(spans) = callable_ty.function_spans(context.db()) {
|
||||
let mut sub = SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!("{callable_kind} signature here"),
|
||||
);
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::ParameterAlreadyAssigned {
|
||||
argument_index,
|
||||
parameter,
|
||||
|
|
|
@ -89,6 +89,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
|||
registry.register_lint(&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS);
|
||||
registry.register_lint(&UNDEFINED_REVEAL);
|
||||
registry.register_lint(&UNKNOWN_ARGUMENT);
|
||||
registry.register_lint(&POSITIONAL_ONLY_PARAMETER_AS_KWARG);
|
||||
registry.register_lint(&UNRESOLVED_ATTRIBUTE);
|
||||
registry.register_lint(&UNRESOLVED_IMPORT);
|
||||
registry.register_lint(&UNRESOLVED_REFERENCE);
|
||||
|
@ -1538,6 +1539,27 @@ declare_lint! {
|
|||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for keyword arguments in calls that match positional-only parameters of the callable.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Providing a positional-only parameter as a keyword argument will raise `TypeError` at runtime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// def f(x: int, /) -> int: ...
|
||||
///
|
||||
/// f(x=1) # Error raised here
|
||||
/// ```
|
||||
pub(crate) static POSITIONAL_ONLY_PARAMETER_AS_KWARG = {
|
||||
summary: "detects positional-only parameters passed as keyword arguments",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for unresolved attributes.
|
||||
|
|
|
@ -1336,6 +1336,17 @@ impl<'db> Parameters<'db> {
|
|||
.and_then(|parameter| parameter.is_positional().then_some(parameter))
|
||||
}
|
||||
|
||||
/// Return a positional-only parameter (with index) with the given name.
|
||||
pub(crate) fn positional_only_by_name(&self, name: &str) -> Option<(usize, &Parameter<'db>)> {
|
||||
self.iter().enumerate().find(|(_, parameter)| {
|
||||
parameter.is_positional_only()
|
||||
&& parameter
|
||||
.name()
|
||||
.map(|p_name| p_name == name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the variadic parameter (`*args`), if any, and its index, or `None`.
|
||||
pub(crate) fn variadic(&self) -> Option<(usize, &Parameter<'db>)> {
|
||||
self.iter()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue