Add convenience methods for iterating over all parameter nodes in a function (#11174)

This commit is contained in:
Alex Waygood 2024-04-29 11:36:15 +01:00 committed by GitHub
parent 8a887daeb4
commit 87929ad5f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 399 additions and 448 deletions

View file

@ -31,8 +31,8 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, FStringElement,
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
self as ast, AnyParameterRef, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
FStringElement, Keyword, MatchCase, Parameter, Parameters, Pattern, Stmt, Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
@ -604,15 +604,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
self.visit_type_params(type_params);
}
for parameter_with_default in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
{
if let Some(expr) = &parameter_with_default.parameter.annotation {
if singledispatch {
for parameter in &**parameters {
if let Some(expr) = parameter.annotation() {
if singledispatch && !parameter.is_variadic() {
self.visit_runtime_required_annotation(expr);
singledispatch = false;
} else {
match annotation {
AnnotationContext::RuntimeRequired => {
@ -625,42 +621,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
self.visit_annotation(expr);
}
}
};
}
}
if let Some(expr) = &parameter_with_default.default {
if let Some(expr) = parameter.default() {
self.visit_expr(expr);
}
singledispatch = false;
}
if let Some(arg) = &parameters.vararg {
if let Some(expr) = &arg.annotation {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
}
}
if let Some(arg) = &parameters.kwarg {
if let Some(expr) = &arg.annotation {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
}
}
for expr in returns {
match annotation {
@ -1043,19 +1008,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
) => {
// Visit the default arguments, but avoid the body, which will be deferred.
if let Some(parameters) = parameters {
for ParameterWithDefault {
default,
parameter: _,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
for default in parameters
.iter_non_variadic_params()
.filter_map(|param| param.default.as_deref())
{
if let Some(expr) = &default {
self.visit_expr(expr);
}
self.visit_expr(default);
}
}
@ -1483,20 +1440,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
// Step 1: Binding.
// Bind, but intentionally avoid walking default expressions, as we handle them
// upstream.
for parameter_with_default in &parameters.posonlyargs {
self.visit_parameter(&parameter_with_default.parameter);
}
for parameter_with_default in &parameters.args {
self.visit_parameter(&parameter_with_default.parameter);
}
if let Some(arg) = &parameters.vararg {
self.visit_parameter(arg);
}
for parameter_with_default in &parameters.kwonlyargs {
self.visit_parameter(&parameter_with_default.parameter);
}
if let Some(arg) = &parameters.kwarg {
self.visit_parameter(arg);
for parameter in parameters.iter().map(AnyParameterRef::as_parameter) {
self.visit_parameter(parameter);
}
// Step 4: Analysis

View file

@ -582,6 +582,7 @@ fn is_stub_function(function_def: &ast::StmtFunctionDef, checker: &Checker) -> b
}
/// Generate flake8-annotation checks for a given `Definition`.
/// ANN001, ANN401
pub(crate) fn definition(
checker: &Checker,
definition: &Definition,
@ -615,23 +616,14 @@ pub(crate) fn definition(
let is_overridden = visibility::is_override(decorator_list, checker.semantic());
// ANN001, ANN401
// If this is a non-static method, skip `cls` or `self`.
for ParameterWithDefault {
parameter,
default: _,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
),
)
{
} in parameters.iter_non_variadic_params().skip(usize::from(
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
)) {
// ANN401 for dynamically typed parameters
if let Some(annotation) = &parameter.annotation {
has_any_typed_arg = true;

View file

@ -74,11 +74,7 @@ pub(crate) fn hardcoded_password_default(checker: &mut Checker, parameters: &Par
parameter,
default,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
continue;

View file

@ -49,44 +49,35 @@ impl Violation for SslWithBadDefaults {
/// S503
pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFunctionDef) {
function_def
for default in function_def
.parameters
.posonlyargs
.iter()
.chain(
function_def
.parameters
.args
.iter()
.chain(function_def.parameters.kwonlyargs.iter()),
)
.for_each(|param| {
if let Some(default) = &param.default {
match default.as_ref() {
Expr::Name(ast::ExprName { id, range, .. }) => {
if is_insecure_protocol(id.as_str()) {
checker.diagnostics.push(Diagnostic::new(
SslWithBadDefaults {
protocol: id.to_string(),
},
*range,
));
}
}
Expr::Attribute(ast::ExprAttribute { attr, range, .. }) => {
if is_insecure_protocol(attr.as_str()) {
checker.diagnostics.push(Diagnostic::new(
SslWithBadDefaults {
protocol: attr.to_string(),
},
*range,
));
}
}
_ => {}
.iter_non_variadic_params()
.filter_map(|param| param.default.as_deref())
{
match default {
Expr::Name(ast::ExprName { id, range, .. }) => {
if is_insecure_protocol(id.as_str()) {
checker.diagnostics.push(Diagnostic::new(
SslWithBadDefaults {
protocol: id.to_string(),
},
*range,
));
}
}
});
Expr::Attribute(ast::ExprAttribute { attr, range, .. }) => {
if is_insecure_protocol(attr.as_str()) {
checker.diagnostics.push(Diagnostic::new(
SslWithBadDefaults {
protocol: attr.to_string(),
},
*range,
));
}
}
_ => {}
}
}
}
/// Returns `true` if the given protocol name is insecure.

View file

@ -139,11 +139,7 @@ pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameter
default,
parameter,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
if let Some(expr) = &default {
if !parameter.annotation.as_ref().is_some_and(|expr| {

View file

@ -105,11 +105,7 @@ impl<'a> Visitor<'a> for NameFinder<'a> {
parameter,
default: _,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
self.names.remove(parameter.name.as_str());
}

View file

@ -89,12 +89,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
parameter,
default,
range: _,
} in function_def
.parameters
.posonlyargs
.iter()
.chain(&function_def.parameters.args)
.chain(&function_def.parameters.kwonlyargs)
} in function_def.parameters.iter_non_variadic_params()
{
let Some(default) = default else {
continue;

View file

@ -107,10 +107,7 @@ pub(crate) fn unnecessary_map(
if parameters.as_ref().is_some_and(|parameters| {
late_binding(parameters, body)
|| parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.iter_non_variadic_params()
.any(|param| param.default.is_some())
|| parameters.vararg.is_some()
|| parameters.kwarg.is_some()
@ -152,10 +149,7 @@ pub(crate) fn unnecessary_map(
if parameters.as_ref().is_some_and(|parameters| {
late_binding(parameters, body)
|| parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.iter_non_variadic_params()
.any(|param| param.default.is_some())
|| parameters.vararg.is_some()
|| parameters.kwarg.is_some()
@ -207,10 +201,7 @@ pub(crate) fn unnecessary_map(
if parameters.as_ref().is_some_and(|parameters| {
late_binding(parameters, body)
|| parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.iter_non_variadic_params()
.any(|param| param.default.is_some())
|| parameters.vararg.is_some()
|| parameters.kwarg.is_some()

View file

@ -2,7 +2,7 @@ use std::fmt;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, Parameters};
use ruff_python_ast::{AnyParameterRef, Parameters};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -58,43 +58,21 @@ pub(crate) fn no_return_argument_annotation(checker: &mut Checker, parameters: &
// Ex) def func(arg: NoReturn): ...
// Ex) def func(arg: NoReturn, /): ...
// Ex) def func(*, arg: NoReturn): ...
for annotation in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.filter_map(|arg| arg.parameter.annotation.as_ref())
{
check_no_return_argument_annotation(checker, annotation);
}
// Ex) def func(*args: NoReturn): ...
if let Some(arg) = &parameters.vararg {
if let Some(annotation) = &arg.annotation {
check_no_return_argument_annotation(checker, annotation);
}
}
// Ex) def func(**kwargs: NoReturn): ...
if let Some(arg) = &parameters.kwarg {
if let Some(annotation) = &arg.annotation {
check_no_return_argument_annotation(checker, annotation);
}
}
}
fn check_no_return_argument_annotation(checker: &mut Checker, annotation: &Expr) {
if checker.semantic().match_typing_expr(annotation, "NoReturn") {
checker.diagnostics.push(Diagnostic::new(
NoReturnArgumentAnnotationInStub {
module: if checker.settings.target_version >= Py311 {
TypingModule::Typing
} else {
TypingModule::TypingExtensions
for annotation in parameters.iter().filter_map(AnyParameterRef::annotation) {
if checker.semantic().match_typing_expr(annotation, "NoReturn") {
checker.diagnostics.push(Diagnostic::new(
NoReturnArgumentAnnotationInStub {
module: if checker.settings.target_version >= Py311 {
TypingModule::Typing
} else {
TypingModule::TypingExtensions
},
},
},
annotation.range(),
));
annotation.range(),
));
}
}
}

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, Parameters};
use ruff_python_ast::{AnyParameterRef, Expr, Parameters};
use ruff_python_semantic::analyze::typing::traverse_union;
use ruff_text_size::Ranged;
@ -61,25 +61,7 @@ impl Violation for RedundantNumericUnion {
/// PYI041
pub(crate) fn redundant_numeric_union(checker: &mut Checker, parameters: &Parameters) {
for annotation in parameters
.args
.iter()
.chain(parameters.posonlyargs.iter())
.chain(parameters.kwonlyargs.iter())
.filter_map(|arg| arg.parameter.annotation.as_ref())
{
check_annotation(checker, annotation);
}
// If annotations on `args` or `kwargs` are flagged by this rule, the annotations themselves
// are not accurate, but check them anyway. It's possible that flagging them will help the user
// realize they're incorrect.
for annotation in parameters
.vararg
.iter()
.chain(parameters.kwarg.iter())
.filter_map(|arg| arg.annotation.as_ref())
{
for annotation in parameters.iter().filter_map(AnyParameterRef::annotation) {
check_annotation(checker, annotation);
}
}

View file

@ -495,11 +495,7 @@ pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, parameters:
parameter,
default,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
continue;
@ -530,11 +526,7 @@ pub(crate) fn argument_simple_defaults(checker: &mut Checker, parameters: &Param
parameter,
default,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
continue;

View file

@ -8,7 +8,7 @@ use ruff_python_ast::name::UnqualifiedName;
use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::Decorator;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters, Stmt};
use ruff_python_ast::{self as ast, Expr, Parameters, Stmt};
use ruff_python_semantic::analyze::visibility::is_abstract;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
@ -841,28 +841,17 @@ fn check_fixture_returns(
/// PT019
fn check_test_function_args(checker: &mut Checker, parameters: &Parameters) {
parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.for_each(
|ParameterWithDefault {
parameter,
default: _,
range: _,
}| {
let name = &parameter.name;
if name.starts_with('_') {
checker.diagnostics.push(Diagnostic::new(
PytestFixtureParamWithoutValue {
name: name.to_string(),
},
parameter.range(),
));
}
},
);
for parameter in parameters.iter_non_variadic_params() {
let name = &parameter.parameter.name;
if name.starts_with('_') {
checker.diagnostics.push(Diagnostic::new(
PytestFixtureParamWithoutValue {
name: name.to_string(),
},
parameter.range(),
));
}
}
}
/// PT020

View file

@ -1,5 +1,3 @@
use std::iter;
use regex::Regex;
use ruff_python_ast as ast;
use ruff_python_ast::{Parameter, Parameters};
@ -224,19 +222,20 @@ fn function(
diagnostics: &mut Vec<Diagnostic>,
) {
let args = parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.iter_non_variadic_params()
.map(|parameter_with_default| &parameter_with_default.parameter)
.chain(
iter::once::<Option<&Parameter>>(parameters.vararg.as_deref())
.flatten()
parameters
.vararg
.as_deref()
.into_iter()
.skip(usize::from(ignore_variadic_names)),
)
.chain(
iter::once::<Option<&Parameter>>(parameters.kwarg.as_deref())
.flatten()
parameters
.kwarg
.as_deref()
.into_iter()
.skip(usize::from(ignore_variadic_names)),
);
call(
@ -260,20 +259,21 @@ fn method(
diagnostics: &mut Vec<Diagnostic>,
) {
let args = parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.iter_non_variadic_params()
.skip(1)
.map(|parameter_with_default| &parameter_with_default.parameter)
.chain(
iter::once::<Option<&Parameter>>(parameters.vararg.as_deref())
.flatten()
parameters
.vararg
.as_deref()
.into_iter()
.skip(usize::from(ignore_variadic_names)),
)
.chain(
iter::once::<Option<&Parameter>>(parameters.kwarg.as_deref())
.flatten()
parameters
.kwarg
.as_deref()
.into_iter()
.skip(usize::from(ignore_variadic_names)),
);
call(

View file

@ -257,15 +257,9 @@ fn rename_parameter(
) -> Result<Option<Fix>> {
// Don't fix if another parameter has the valid name.
if parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.skip(1)
.map(|parameter_with_default| &parameter_with_default.parameter)
.chain(parameters.vararg.as_deref())
.chain(parameters.kwarg.as_deref())
.any(|parameter| &parameter.name == function_type.valid_first_argument_name())
.any(|parameter| parameter.name() == function_type.valid_first_argument_name())
{
return Ok(None);
}

View file

@ -199,8 +199,8 @@ fn function(
let parameters = parameters.cloned().unwrap_or_default();
if let Some(annotation) = annotation {
if let Some((arg_types, return_type)) = extract_types(annotation, semantic) {
// A `lambda` expression can only have positional and positional-only
// arguments. The order is always positional-only first, then positional.
// A `lambda` expression can only have positional-only and positional-or-keyword
// arguments. The order is always positional-only first, then positional-or-keyword.
let new_posonlyargs = parameters
.posonlyargs
.iter()

View file

@ -1755,23 +1755,19 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
// Look for arguments that weren't included in the docstring.
let mut missing_arg_names: FxHashSet<String> = FxHashSet::default();
// If this is a non-static method, skip `cls` or `self`.
for ParameterWithDefault {
parameter,
default: _,
range: _,
} in function
.parameters
.posonlyargs
.iter()
.chain(&function.parameters.args)
.chain(&function.parameters.kwonlyargs)
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(
docstring.definition.is_method()
&& !is_staticmethod(&function.decorator_list, checker.semantic()),
),
)
.iter_non_variadic_params()
.skip(usize::from(
docstring.definition.is_method()
&& !is_staticmethod(&function.decorator_list, checker.semantic()),
))
{
let arg_name = parameter.name.as_str();
if !arg_name.starts_with('_') && !docstrings_args.contains(arg_name) {

View file

@ -61,10 +61,7 @@ impl Violation for TooManyArguments {
pub(crate) fn too_many_arguments(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
let num_arguments = function_def
.parameters
.args
.iter()
.chain(&function_def.parameters.kwonlyargs)
.chain(&function_def.parameters.posonlyargs)
.iter_non_variadic_params()
.filter(|arg| {
!checker
.settings

View file

@ -62,14 +62,14 @@ pub(crate) fn too_many_positional(checker: &mut Checker, function_def: &ast::Stm
// Count the number of positional arguments.
let num_positional_args = function_def
.parameters
.args
.posonlyargs
.iter()
.chain(&function_def.parameters.posonlyargs)
.filter(|arg| {
.chain(&function_def.parameters.args)
.filter(|param| {
!checker
.settings
.dummy_variable_rgx
.is_match(&arg.parameter.name)
.is_match(&param.parameter.name)
})
.count();

View file

@ -167,11 +167,7 @@ pub(crate) fn implicit_optional(checker: &mut Checker, parameters: &Parameters)
parameter,
default,
range: _,
} in parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else { continue };
if !default.is_none_literal_expr() {

View file

@ -348,39 +348,18 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool {
returns,
..
}) => {
parameters
.posonlyargs
.iter()
.chain(parameters.args.iter().chain(parameters.kwonlyargs.iter()))
.any(|parameter| {
parameter
.default
.as_ref()
.is_some_and(|expr| any_over_expr(expr, func))
|| parameter
.parameter
.annotation
.as_ref()
.is_some_and(|expr| any_over_expr(expr, func))
})
|| parameters.vararg.as_ref().is_some_and(|parameter| {
parameter
.annotation
.as_ref()
.is_some_and(|expr| any_over_expr(expr, func))
})
|| parameters.kwarg.as_ref().is_some_and(|parameter| {
parameter
.annotation
.as_ref()
.is_some_and(|expr| any_over_expr(expr, func))
})
|| type_params.as_ref().is_some_and(|type_params| {
type_params
.iter()
.any(|type_param| any_over_type_param(type_param, func))
})
|| body.iter().any(|stmt| any_over_stmt(stmt, func))
parameters.iter().any(|param| {
param
.default()
.is_some_and(|default| any_over_expr(default, func))
|| param
.annotation()
.is_some_and(|annotation| any_over_expr(annotation, func))
}) || type_params.as_ref().is_some_and(|type_params| {
type_params
.iter()
.any(|type_param| any_over_type_param(type_param, func))
}) || body.iter().any(|stmt| any_over_stmt(stmt, func))
|| decorator_list
.iter()
.any(|decorator| any_over_expr(&decorator.expression, func))

View file

@ -1,12 +1,13 @@
use crate::visitor::preorder::PreorderVisitor;
use crate::{
self as ast, Alias, ArgOrKeyword, Arguments, Comprehension, Decorator, ExceptHandler, Expr,
FStringElement, Keyword, MatchCase, Mod, Parameter, ParameterWithDefault, Parameters, Pattern,
PatternArguments, PatternKeyword, Stmt, StmtAnnAssign, StmtAssert, StmtAssign, StmtAugAssign,
StmtBreak, StmtClassDef, StmtContinue, StmtDelete, StmtExpr, StmtFor, StmtFunctionDef,
StmtGlobal, StmtIf, StmtImport, StmtImportFrom, StmtIpyEscapeCommand, StmtMatch, StmtNonlocal,
StmtPass, StmtRaise, StmtReturn, StmtTry, StmtTypeAlias, StmtWhile, StmtWith, TypeParam,
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams, WithItem,
self as ast, Alias, AnyParameterRef, ArgOrKeyword, Arguments, Comprehension, Decorator,
ExceptHandler, Expr, FStringElement, Keyword, MatchCase, Mod, Parameter, ParameterWithDefault,
Parameters, Pattern, PatternArguments, PatternKeyword, Stmt, StmtAnnAssign, StmtAssert,
StmtAssign, StmtAugAssign, StmtBreak, StmtClassDef, StmtContinue, StmtDelete, StmtExpr,
StmtFor, StmtFunctionDef, StmtGlobal, StmtIf, StmtImport, StmtImportFrom, StmtIpyEscapeCommand,
StmtMatch, StmtNonlocal, StmtPass, StmtRaise, StmtReturn, StmtTry, StmtTypeAlias, StmtWhile,
StmtWith, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams,
WithItem,
};
use ruff_text_size::{Ranged, TextRange};
use std::ptr::NonNull;
@ -4221,28 +4222,13 @@ impl AstNode for Parameters {
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::Parameters {
range: _,
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = self;
for arg in posonlyargs.iter().chain(args) {
visitor.visit_parameter_with_default(arg);
}
if let Some(arg) = vararg {
visitor.visit_parameter(arg);
}
for arg in kwonlyargs {
visitor.visit_parameter_with_default(arg);
}
if let Some(arg) = kwarg {
visitor.visit_parameter(arg);
for parameter in self {
match parameter {
AnyParameterRef::NonVariadic(parameter_with_default) => {
visitor.visit_parameter_with_default(parameter_with_default);
}
AnyParameterRef::Variadic(parameter) => visitor.visit_parameter(parameter),
}
}
}
}

View file

@ -2,6 +2,7 @@
use std::fmt;
use std::fmt::Debug;
use std::iter::FusedIterator;
use std::ops::Deref;
use std::slice::{Iter, IterMut};
use std::sync::OnceLock;
@ -3175,6 +3176,63 @@ pub struct Decorator {
pub expression: Expr,
}
/// Enumeration of the two kinds of parameter
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum AnyParameterRef<'a> {
/// Variadic parameters cannot have default values,
/// e.g. both `*args` and `**kwargs` in the following function:
///
/// ```python
/// def foo(*args, **kwargs): pass
/// ```
Variadic(&'a Parameter),
/// Non-variadic parameters can have default values,
/// though they won't necessarily always have them:
///
/// ```python
/// def bar(a=1, /, b=2, *, c=3): pass
/// ```
NonVariadic(&'a ParameterWithDefault),
}
impl<'a> AnyParameterRef<'a> {
pub const fn as_parameter(self) -> &'a Parameter {
match self {
Self::NonVariadic(param) => &param.parameter,
Self::Variadic(param) => param,
}
}
pub const fn name(self) -> &'a Identifier {
&self.as_parameter().name
}
pub const fn is_variadic(self) -> bool {
matches!(self, Self::Variadic(_))
}
pub fn annotation(self) -> Option<&'a Expr> {
self.as_parameter().annotation.as_deref()
}
pub fn default(self) -> Option<&'a Expr> {
match self {
Self::NonVariadic(param) => param.default.as_deref(),
Self::Variadic(_) => None,
}
}
}
impl Ranged for AnyParameterRef<'_> {
fn range(&self) -> TextRange {
match self {
Self::NonVariadic(param) => param.range,
Self::Variadic(param) => param.range,
}
}
}
/// An alternative type of AST `arguments`. This is ruff_python_parser-friendly and human-friendly definition of function arguments.
/// This form also has advantage to implement pre-order traverse.
///
@ -3196,37 +3254,56 @@ pub struct Parameters {
}
impl Parameters {
/// Returns the [`ParameterWithDefault`] with the given name, or `None` if no such [`ParameterWithDefault`] exists.
pub fn find(&self, name: &str) -> Option<&ParameterWithDefault> {
/// Returns an iterator over all non-variadic parameters included in this [`Parameters`] node.
///
/// The variadic parameters (`.vararg` and `.kwarg`) can never have default values;
/// non-variadic parameters sometimes will.
pub fn iter_non_variadic_params(&self) -> impl Iterator<Item = &ParameterWithDefault> {
self.posonlyargs
.iter()
.chain(&self.args)
.chain(&self.kwonlyargs)
}
/// Returns the [`ParameterWithDefault`] with the given name, or `None` if no such [`ParameterWithDefault`] exists.
pub fn find(&self, name: &str) -> Option<&ParameterWithDefault> {
self.iter_non_variadic_params()
.find(|arg| arg.parameter.name.as_str() == name)
}
/// Returns `true` if a parameter with the given name included in this [`Parameters`].
/// Returns an iterator over all parameters included in this [`Parameters`] node.
pub fn iter(&self) -> ParametersIterator {
ParametersIterator::new(self)
}
/// Returns the total number of parameters included in this [`Parameters`] node.
pub fn len(&self) -> usize {
let Parameters {
range: _,
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = self;
// Safety: a Python function can have an arbitrary number of parameters,
// so theoretically this could be a number that wouldn't fit into a usize,
// which would lead to a panic. A Python function with that many parameters
// is extremely unlikely outside of generated code, however, and it's even
// more unlikely that we'd find a function with that many parameters in a
// source-code file <=4GB large (Ruff's maximum).
posonlyargs
.len()
.checked_add(args.len())
.and_then(|length| length.checked_add(usize::from(vararg.is_some())))
.and_then(|length| length.checked_add(kwonlyargs.len()))
.and_then(|length| length.checked_add(usize::from(kwarg.is_some())))
.expect("Failed to fit the number of parameters into a usize")
}
/// Returns `true` if a parameter with the given name is included in this [`Parameters`].
pub fn includes(&self, name: &str) -> bool {
if self
.posonlyargs
.iter()
.chain(&self.args)
.chain(&self.kwonlyargs)
.any(|arg| arg.parameter.name.as_str() == name)
{
return true;
}
if let Some(arg) = &self.vararg {
if arg.name.as_str() == name {
return true;
}
}
if let Some(arg) = &self.kwarg {
if arg.name.as_str() == name {
return true;
}
}
false
self.iter().any(|param| param.name() == name)
}
/// Returns `true` if the [`Parameters`] is empty.
@ -3239,6 +3316,136 @@ impl Parameters {
}
}
pub struct ParametersIterator<'a> {
posonlyargs: Iter<'a, ParameterWithDefault>,
args: Iter<'a, ParameterWithDefault>,
vararg: Option<&'a Parameter>,
kwonlyargs: Iter<'a, ParameterWithDefault>,
kwarg: Option<&'a Parameter>,
}
impl<'a> ParametersIterator<'a> {
fn new(parameters: &'a Parameters) -> Self {
let Parameters {
range: _,
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = parameters;
Self {
posonlyargs: posonlyargs.iter(),
args: args.iter(),
vararg: vararg.as_deref(),
kwonlyargs: kwonlyargs.iter(),
kwarg: kwarg.as_deref(),
}
}
}
impl<'a> Iterator for ParametersIterator<'a> {
type Item = AnyParameterRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
let ParametersIterator {
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = self;
if let Some(param) = posonlyargs.next() {
return Some(AnyParameterRef::NonVariadic(param));
}
if let Some(param) = args.next() {
return Some(AnyParameterRef::NonVariadic(param));
}
if let Some(param) = vararg.take() {
return Some(AnyParameterRef::Variadic(param));
}
if let Some(param) = kwonlyargs.next() {
return Some(AnyParameterRef::NonVariadic(param));
}
kwarg.take().map(AnyParameterRef::Variadic)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let ParametersIterator {
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = self;
let posonlyargs_len = posonlyargs.len();
let args_len = args.len();
let vararg_len = usize::from(vararg.is_some());
let kwonlyargs_len = kwonlyargs.len();
let kwarg_len = usize::from(kwarg.is_some());
let lower = posonlyargs_len
.saturating_add(args_len)
.saturating_add(vararg_len)
.saturating_add(kwonlyargs_len)
.saturating_add(kwarg_len);
let upper = posonlyargs_len
.checked_add(args_len)
.and_then(|length| length.checked_add(vararg_len))
.and_then(|length| length.checked_add(kwonlyargs_len))
.and_then(|length| length.checked_add(kwarg_len));
(lower, upper)
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
}
impl<'a> DoubleEndedIterator for ParametersIterator<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let ParametersIterator {
posonlyargs,
args,
vararg,
kwonlyargs,
kwarg,
} = self;
if let Some(param) = kwarg.take() {
return Some(AnyParameterRef::Variadic(param));
}
if let Some(param) = kwonlyargs.next_back() {
return Some(AnyParameterRef::NonVariadic(param));
}
if let Some(param) = vararg.take() {
return Some(AnyParameterRef::Variadic(param));
}
if let Some(param) = args.next_back() {
return Some(AnyParameterRef::NonVariadic(param));
}
posonlyargs.next_back().map(AnyParameterRef::NonVariadic)
}
}
impl<'a> FusedIterator for ParametersIterator<'a> {}
/// We rely on the same invariants outlined in the comment above `Parameters::len()`
/// in order to implement `ExactSizeIterator` here
impl<'a> ExactSizeIterator for ParametersIterator<'a> {}
impl<'a> IntoIterator for &'a Parameters {
type IntoIter = ParametersIterator<'a>;
type Item = AnyParameterRef<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
/// An alternative type of AST `arg`. This is used for each function argument that might have a default value.
/// Used by `Arguments` original type.
///

View file

@ -4,11 +4,11 @@ pub mod preorder;
pub mod transformer;
use crate::{
self as ast, Alias, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension, Decorator,
ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement, FStringPart,
Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments, PatternKeyword,
Stmt, StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
TypeParams, UnaryOp, WithItem,
self as ast, Alias, AnyParameterRef, Arguments, BoolOp, BytesLiteral, CmpOp, Comprehension,
Decorator, ElifElseClause, ExceptHandler, Expr, ExprContext, FString, FStringElement,
FStringPart, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, PatternArguments,
PatternKeyword, Stmt, StringLiteral, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
TypeParamTypeVarTuple, TypeParams, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@ -607,36 +607,15 @@ pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &
pub fn walk_parameters<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, parameters: &'a Parameters) {
// Defaults are evaluated before annotations.
for arg in &parameters.posonlyargs {
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
}
for arg in &parameters.args {
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
}
for arg in &parameters.kwonlyargs {
if let Some(default) = &arg.default {
visitor.visit_expr(default);
}
for default in parameters
.iter_non_variadic_params()
.filter_map(|param| param.default.as_deref())
{
visitor.visit_expr(default);
}
for arg in &parameters.posonlyargs {
visitor.visit_parameter(&arg.parameter);
}
for arg in &parameters.args {
visitor.visit_parameter(&arg.parameter);
}
if let Some(arg) = &parameters.vararg {
visitor.visit_parameter(arg);
}
for arg in &parameters.kwonlyargs {
visitor.visit_parameter(&arg.parameter);
}
if let Some(arg) = &parameters.kwarg {
visitor.visit_parameter(arg);
for parameter in parameters.iter().map(AnyParameterRef::as_parameter) {
visitor.visit_parameter(parameter);
}
}

View file

@ -240,11 +240,7 @@ impl FormatNodeRule<Parameters> for FormatParameters {
Ok(())
});
let num_parameters = posonlyargs.len()
+ args.len()
+ usize::from(vararg.is_some())
+ kwonlyargs.len()
+ usize::from(kwarg.is_some());
let num_parameters = item.len();
if self.parentheses == ParametersParentheses::Never {
write!(f, [group(&format_inner), dangling_comments(dangling)])

View file

@ -3371,34 +3371,15 @@ impl<'src> Parser<'src> {
///
/// Report errors for all the duplicate names found.
fn validate_parameters(&mut self, parameters: &ast::Parameters) {
let mut all_arg_names = FxHashSet::with_capacity_and_hasher(
parameters.posonlyargs.len()
+ parameters.args.len()
+ usize::from(parameters.vararg.is_some())
+ parameters.kwonlyargs.len()
+ usize::from(parameters.kwarg.is_some()),
BuildHasherDefault::default(),
);
let mut all_arg_names =
FxHashSet::with_capacity_and_hasher(parameters.len(), BuildHasherDefault::default());
let posonlyargs = parameters.posonlyargs.iter();
let args = parameters.args.iter();
let kwonlyargs = parameters.kwonlyargs.iter();
let vararg = parameters.vararg.as_deref();
let kwarg = parameters.kwarg.as_deref();
for arg in posonlyargs
.chain(args)
.chain(kwonlyargs)
.map(|arg| &arg.parameter)
.chain(vararg)
.chain(kwarg)
{
let range = arg.name.range;
let arg_name = arg.name.as_str();
if !all_arg_names.insert(arg_name) {
for parameter in parameters {
let range = parameter.name().range();
let param_name = parameter.name().as_str();
if !all_arg_names.insert(param_name) {
self.add_error(
ParseErrorType::DuplicateParameter(arg_name.to_string()),
ParseErrorType::DuplicateParameter(param_name.to_string()),
range,
);
}

View file

@ -139,6 +139,12 @@ Module(
|
|
1 | def foo(a, a=10, *a, a, a: str, **a): ...
| ^ Syntax Error: Duplicate parameter "a"
|
|
1 | def foo(a, a=10, *a, a, a: str, **a): ...
| ^ Syntax Error: Duplicate parameter "a"
@ -151,12 +157,6 @@ Module(
|
|
1 | def foo(a, a=10, *a, a, a: str, **a): ...
| ^ Syntax Error: Duplicate parameter "a"
|
|
1 | def foo(a, a=10, *a, a, a: str, **a): ...
| ^ Syntax Error: Duplicate parameter "a"

View file

@ -736,10 +736,7 @@ fn find_parameter<'a>(
binding: &Binding,
) -> Option<&'a ParameterWithDefault> {
parameters
.args
.iter()
.chain(parameters.posonlyargs.iter())
.chain(parameters.kwonlyargs.iter())
.iter_non_variadic_params()
.find(|arg| arg.parameter.name.range() == binding.range())
}