Add convenience helper methods for AST nodes representing function parameters (#15871)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

This commit is contained in:
Alex Waygood 2025-02-01 17:16:32 +00:00 committed by GitHub
parent bcdb3f9840
commit d9a1034db0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 150 additions and 238 deletions

View file

@ -605,7 +605,7 @@ impl<'db> SemanticIndexBuilder<'db> {
}
fn declare_parameter(&mut self, parameter: &'db ast::ParameterWithDefault) {
let symbol = self.add_symbol(parameter.parameter.name.id().clone());
let symbol = self.add_symbol(parameter.name().id().clone());
let definition = self.add_definition(symbol, parameter);

View file

@ -1294,7 +1294,7 @@ impl<'db> TypeInferenceBuilder<'db> {
parameter: &ast::Parameter,
definition: Definition<'db>,
) {
if let Some(annotation) = parameter.annotation.as_ref() {
if let Some(annotation) = parameter.annotation() {
let _annotated_ty = self.file_expression_type(annotation);
// TODO `tuple[annotated_ty, ...]`
let ty = KnownClass::Tuple.to_instance(self.db());
@ -1323,7 +1323,7 @@ impl<'db> TypeInferenceBuilder<'db> {
parameter: &ast::Parameter,
definition: Definition<'db>,
) {
if let Some(annotation) = parameter.annotation.as_ref() {
if let Some(annotation) = parameter.annotation() {
let _annotated_ty = self.file_expression_type(annotation);
// TODO `dict[str, annotated_ty]`
let ty = KnownClass::Dict.to_instance(self.db());

View file

@ -93,10 +93,9 @@ impl<'db> Parameters<'db> {
kwarg,
range: _,
} = parameters;
let default_ty = |parameter_with_default: &ast::ParameterWithDefault| {
parameter_with_default
.default
.as_deref()
let default_ty = |param: &ast::ParameterWithDefault| {
param
.default()
.map(|default| definition_expression_type(db, definition, default))
};
let positional_only = posonlyargs.iter().map(|arg| {
@ -243,8 +242,7 @@ impl<'db> Parameter<'db> {
Self {
name: Some(parameter.name.id.clone()),
annotated_ty: parameter
.annotation
.as_deref()
.annotation()
.map(|annotation| definition_expression_type(db, definition, annotation)),
kind,
}

View file

@ -164,14 +164,14 @@ fn check_function_parameters(checker: &mut Checker, function_def: &StmtFunctionD
}
for param in function_def.parameters.iter_non_variadic_params() {
let param_name = param.parameter.name.as_str();
if REMOVED_CONTEXT_KEYS.contains(&param_name) {
let param_name = param.name();
if REMOVED_CONTEXT_KEYS.contains(&param_name.as_str()) {
checker.diagnostics.push(Diagnostic::new(
Airflow3Removal {
deprecated: param_name.to_string(),
replacement: Replacement::None,
},
param.parameter.name.range(),
param_name.range(),
));
}
}

View file

@ -107,8 +107,7 @@ pub(crate) fn fastapi_non_annotated_dependency(
.iter()
.chain(&function_def.parameters.kwonlyargs)
{
let (Some(annotation), Some(default)) =
(&parameter.parameter.annotation, &parameter.default)
let (Some(annotation), Some(default)) = (parameter.annotation(), parameter.default())
else {
seen_default |= parameter.default.is_some();
continue;
@ -120,7 +119,7 @@ pub(crate) fn fastapi_non_annotated_dependency(
annotation,
default,
kind: dependency,
name: &parameter.parameter.name,
name: parameter.name(),
range: parameter.range,
};
seen_default = create_diagnostic(

View file

@ -183,7 +183,7 @@ pub(crate) fn fastapi_unused_path_parameter(
.parameters
.posonlyargs
.iter()
.any(|arg| arg.parameter.name.as_str() == path_param);
.any(|param| param.name() == path_param);
let mut diagnostic = Diagnostic::new(
FastApiUnusedPathParameter {
@ -258,25 +258,17 @@ impl<'a> Dependency<'a> {
/// ): ...
/// ```
fn from_parameter(
parameter_with_default: &'a ParameterWithDefault,
parameter: &'a ParameterWithDefault,
semantic: &SemanticModel<'a>,
) -> Option<Self> {
let ParameterWithDefault {
parameter, default, ..
} = parameter_with_default;
if let Some(dependency) = default
.as_deref()
if let Some(dependency) = parameter
.default()
.and_then(|default| Self::from_default(default, semantic))
{
return Some(dependency);
}
let Expr::Subscript(ExprSubscript { value, slice, .. }) =
&parameter.annotation.as_deref()?
else {
return None;
};
let ExprSubscript { value, slice, .. } = parameter.annotation()?.as_subscript_expr()?;
if !semantic.match_typing_expr(value, "Annotated") {
return None;
@ -327,7 +319,7 @@ impl<'a> Dependency<'a> {
};
let parameter_names = non_posonly_non_variadic_parameters(function_def)
.map(|ParameterWithDefault { parameter, .. }| &*parameter.name)
.map(|param| param.name().as_str())
.collect();
Some(Self::Function(parameter_names))

View file

@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::ReturnStatementVisitor;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::Definition;
use ruff_python_stdlib::typing::simple_magic_return_type;
@ -613,21 +613,17 @@ pub(crate) fn definition(
let is_overridden = visibility::is_override(decorator_list, checker.semantic());
// If this is a non-static method, skip `cls` or `self`.
for ParameterWithDefault {
parameter,
default: _,
range: _,
} in parameters.iter_non_variadic_params().skip(usize::from(
for parameter 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 {
if let Some(annotation) = parameter.annotation() {
has_any_typed_arg = true;
if checker.enabled(Rule::AnyType) && !is_overridden {
check_dynamically_typed(
checker,
annotation,
|| parameter.name.to_string(),
|| parameter.name().to_string(),
&mut diagnostics,
);
}
@ -636,14 +632,14 @@ pub(crate) fn definition(
&& checker
.settings
.dummy_variable_rgx
.is_match(&parameter.name))
.is_match(parameter.name()))
{
if checker.enabled(Rule::MissingTypeFunctionArgument) {
diagnostics.push(Diagnostic::new(
MissingTypeFunctionArgument {
name: parameter.name.to_string(),
name: parameter.name().to_string(),
},
parameter.range(),
parameter.parameter.range(),
));
}
}
@ -915,7 +911,7 @@ pub(crate) fn definition(
.posonlyargs
.first()
.or_else(|| parameters.args.first())
.is_some_and(|first_param| first_param.parameter.annotation.is_some()))
.is_some_and(|first_param| first_param.annotation().is_some()))
{
diagnostics
} else {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Expr, Parameter, ParameterWithDefault, Parameters};
use ruff_python_ast::{Expr, Parameter, Parameters};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
@ -70,16 +70,11 @@ fn check_password_kwarg(parameter: &Parameter, default: &Expr) -> Option<Diagnos
/// S107
pub(crate) fn hardcoded_password_default(checker: &mut Checker, parameters: &Parameters) {
for ParameterWithDefault {
parameter,
default,
range: _,
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
if let Some(diagnostic) = check_password_kwarg(parameter, default) {
if let Some(diagnostic) = check_password_kwarg(&parameter.parameter, default) {
checker.diagnostics.push(diagnostic);
}
}

View file

@ -1,9 +1,9 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::UnqualifiedName;
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
use ruff_python_ast::{Decorator, Expr, Parameters};
use ruff_python_semantic::analyze::visibility;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
@ -115,16 +115,8 @@ pub(crate) fn boolean_default_value_positional_argument(
return;
}
for ParameterWithDefault {
parameter,
default,
range: _,
} in parameters.posonlyargs.iter().chain(&parameters.args)
{
if default
.as_ref()
.is_some_and(|default| default.is_boolean_literal_expr())
{
for param in parameters.posonlyargs.iter().chain(&parameters.args) {
if param.default().is_some_and(Expr::is_boolean_literal_expr) {
// Allow Boolean defaults in setters.
if decorator_list.iter().any(|decorator| {
UnqualifiedName::from_expr(&decorator.expression)
@ -141,7 +133,7 @@ pub(crate) fn boolean_default_value_positional_argument(
checker.diagnostics.push(Diagnostic::new(
BooleanDefaultValuePositionalArgument,
parameter.name.range(),
param.identifier(),
));
}
}

View file

@ -1,11 +1,11 @@
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::UnqualifiedName;
use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parameters};
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
@ -124,13 +124,8 @@ pub(crate) fn boolean_type_hint_positional_argument(
return;
}
for ParameterWithDefault {
parameter,
default: _,
range: _,
} in parameters.posonlyargs.iter().chain(&parameters.args)
{
let Some(annotation) = parameter.annotation.as_ref() else {
for parameter in parameters.posonlyargs.iter().chain(&parameters.args) {
let Some(annotation) = parameter.annotation() else {
continue;
};
if checker.settings.preview.is_enabled() {
@ -164,7 +159,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
checker.diagnostics.push(Diagnostic::new(
BooleanTypeHintPositionalArgument,
parameter.name.range(),
parameter.identifier(),
));
}
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters};
use ruff_python_ast::{self as ast, Expr, Parameters};
use ruff_text_size::{Ranged, TextRange};
use ruff_diagnostics::Violation;
@ -139,17 +139,12 @@ pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameter
.collect();
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls);
for ParameterWithDefault {
default,
parameter,
range: _,
} in parameters.iter_non_variadic_params()
{
if let Some(expr) = &default {
if !parameter.annotation.as_ref().is_some_and(|expr| {
for parameter in parameters.iter_non_variadic_params() {
if let Some(default) = parameter.default() {
if !parameter.annotation().is_some_and(|expr| {
is_immutable_annotation(expr, checker.semantic(), &extend_immutable_calls)
}) {
visitor.visit_expr(expr);
visitor.visit_expr(default);
}
}
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
use ruff_python_ast::{self as ast, Expr};
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, Violation};
@ -101,13 +101,8 @@ impl<'a> Visitor<'a> for NameFinder<'a> {
visitor::walk_expr(self, body);
if let Some(parameters) = parameters {
for ParameterWithDefault {
parameter,
default: _,
range: _,
} in parameters.iter_non_variadic_params()
{
self.names.remove(parameter.name.as_str());
for parameter in parameters.iter_non_variadic_params() {
self.names.remove(parameter.name().as_str());
}
}
}

View file

@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::is_docstring_stmt;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault};
use ruff_python_ast::{self as ast, Expr, Parameter};
use ruff_python_codegen::{Generator, Stylist};
use ruff_python_index::Indexer;
use ruff_python_semantic::analyze::function_type::is_stub;
@ -91,13 +91,8 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
return;
}
for ParameterWithDefault {
parameter,
default,
range: _,
} in function_def.parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in function_def.parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
@ -110,7 +105,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
.collect();
if is_mutable_expr(default, checker.semantic())
&& !parameter.annotation.as_ref().is_some_and(|expr| {
&& !parameter.annotation().is_some_and(|expr| {
is_immutable_annotation(expr, checker.semantic(), extend_immutable_calls.as_slice())
})
{
@ -119,7 +114,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
// If the function body is on the same line as the function def, do not fix
if let Some(fix) = move_initialization(
function_def,
parameter,
&parameter.parameter,
default,
checker.semantic(),
checker.locator(),
@ -165,12 +160,12 @@ fn move_initialization(
// Add an `if`, to set the argument to its original value if still `None`.
let mut content = String::new();
content.push_str(&format!("if {} is None:", parameter.name.as_str()));
content.push_str(&format!("if {} is None:", parameter.name()));
content.push_str(stylist.line_ending().as_str());
content.push_str(stylist.indentation());
content.push_str(&format!(
"{} = {}",
parameter.name.as_str(),
parameter.name(),
generator.expr(default)
));
content.push_str(stylist.line_ending().as_str());

View file

@ -65,7 +65,7 @@ impl Violation for BuiltinArgumentShadowing {
/// A002
pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Parameter) {
if shadows_builtin(
parameter.name.as_str(),
parameter.name(),
checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,

View file

@ -39,9 +39,9 @@ pub(crate) fn builtin_lambda_argument_shadowing(checker: &mut Checker, lambda: &
return;
};
for param in parameters.iter_non_variadic_params() {
let name = &param.parameter.name;
let name = param.name();
if shadows_builtin(
name.as_ref(),
name,
checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,

View file

@ -68,7 +68,7 @@ pub(crate) fn any_eq_ne_annotation(checker: &mut Checker, name: &str, parameters
return;
}
let Some(annotation) = &parameters.args[1].parameter.annotation else {
let Some(annotation) = &parameters.args[1].annotation() else {
return;
};

View file

@ -95,7 +95,7 @@ pub(crate) fn custom_type_var_return_type(
.iter()
.chain(&parameters.args)
.next()
.and_then(|parameter_with_default| parameter_with_default.parameter.annotation.as_ref())
.and_then(|parameter_with_default| parameter_with_default.annotation())
else {
return;
};
@ -341,12 +341,8 @@ fn remove_first_parameter_annotation(parameters: &Parameters) -> Edit {
// The first parameter is guaranteed to be `self`/`cls`,
// as verified by `uses_custom_var()`.
let mut non_variadic_positional = parameters.posonlyargs.iter().chain(&parameters.args);
let first = &non_variadic_positional.next().unwrap().parameter;
let name_end = first.name.range.end();
let annotation_end = first.range.end();
Edit::deletion(name_end, annotation_end)
let first = &non_variadic_positional.next().unwrap();
Edit::deletion(first.name().end(), first.end())
}
fn replace_return_annotation_with_self(self_symbol_binding: String, returns: &Expr) -> Edit {

View file

@ -199,8 +199,7 @@ pub(crate) fn bad_exit_annotation(checker: &mut Checker, function: &StmtFunction
fn check_short_args_list(checker: &mut Checker, parameters: &Parameters, func_kind: FuncKind) {
if let Some(varargs) = &parameters.vararg {
if let Some(annotation) = varargs
.annotation
.as_ref()
.annotation()
.filter(|ann| !is_object_or_unused(ann, checker.semantic()))
{
let mut diagnostic = Diagnostic::new(
@ -238,7 +237,7 @@ fn check_short_args_list(checker: &mut Checker, parameters: &Parameters, func_ki
/// (that is not decorated with `@typing.overload`) are annotated correctly.
fn check_positional_args_for_non_overloaded_method(
checker: &mut Checker,
non_self_positional_args: &[&ParameterWithDefault],
non_self_positional_params: &[&ParameterWithDefault],
kind: FuncKind,
) {
// For each argument, define the predicate against which to check the annotation.
@ -252,8 +251,10 @@ fn check_positional_args_for_non_overloaded_method(
(ErrorKind::ThirdArgBadAnnotation, is_traceback_type),
];
for (arg, (error_info, predicate)) in non_self_positional_args.iter().take(3).zip(validations) {
let Some(annotation) = arg.parameter.annotation.as_ref() else {
for (param, (error_info, predicate)) in
non_self_positional_params.iter().take(3).zip(validations)
{
let Some(annotation) = param.annotation() else {
continue;
};
@ -293,11 +294,7 @@ fn check_positional_args_for_overloaded_method(
predicate: impl FnOnce(&Expr) -> bool,
semantic: &SemanticModel,
) -> bool {
parameter
.parameter
.annotation
.as_ref()
.map_or(true, |annotation| {
parameter.annotation().map_or(true, |annotation| {
predicate(annotation) || is_object_or_unused(annotation, semantic)
})
}

View file

@ -100,7 +100,7 @@ pub(crate) fn pep_484_positional_parameter(
/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e.,
/// its name starts with `__` and does not end with `__`).
fn is_old_style_positional_only(arg: &ParameterWithDefault) -> bool {
let arg_name = &arg.parameter.name;
fn is_old_style_positional_only(param: &ParameterWithDefault) -> bool {
let arg_name = param.name();
arg_name.starts_with("__") && !arg_name.ends_with("__")
}

View file

@ -1,9 +1,7 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{
self as ast, Expr, Operator, ParameterWithDefault, Parameters, Stmt, UnaryOp,
};
use ruff_python_ast::{self as ast, Expr, Operator, Parameters, Stmt, UnaryOp};
use ruff_python_semantic::{analyze::class::is_enumeration, ScopeKind, SemanticModel};
use ruff_text_size::Ranged;
@ -489,16 +487,11 @@ fn is_annotatable_type_alias(value: &Expr, semantic: &SemanticModel) -> bool {
/// PYI011
pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, parameters: &Parameters) {
for ParameterWithDefault {
parameter,
default,
range: _,
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
if parameter.annotation.is_some() {
if parameter.annotation().is_some() {
if !is_valid_default_value_with_annotation(
default,
true,
@ -520,16 +513,11 @@ pub(crate) fn typed_argument_simple_defaults(checker: &mut Checker, parameters:
/// PYI014
pub(crate) fn argument_simple_defaults(checker: &mut Checker, parameters: &Parameters) {
for ParameterWithDefault {
parameter,
default,
range: _,
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
if parameter.annotation.is_none() {
if parameter.annotation().is_none() {
if !is_valid_default_value_with_annotation(
default,
true,

View file

@ -809,7 +809,7 @@ fn check_fixture_returns(checker: &mut Checker, name: &str, body: &[Stmt], retur
/// PT019
fn check_test_function_args(checker: &mut Checker, parameters: &Parameters) {
for parameter in parameters.iter_non_variadic_params() {
let name = &parameter.parameter.name;
let name = parameter.name();
if name.starts_with('_') {
checker.diagnostics.push(Diagnostic::new(
PytestFixtureParamWithoutValue {

View file

@ -2,7 +2,7 @@ use crate::checkers::ast::Checker;
use crate::rules::flake8_pytest_style::rules::helpers::is_likely_pytest_test;
use ruff_diagnostics::{Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{ParameterWithDefault, StmtFunctionDef};
use ruff_python_ast::StmtFunctionDef;
use ruff_text_size::Ranged;
/// ## What it does
@ -58,25 +58,15 @@ pub(crate) fn parameter_with_default_argument(
return;
}
let parameters = function_def.parameters.as_ref();
for ParameterWithDefault {
parameter,
default,
range: pwd_range,
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in function_def.parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
let parameter_name = parameter.name.to_string();
let parameter_name = parameter.name().to_string();
let kind = PytestParameterWithDefaultArgument { parameter_name };
let diagnostic = Diagnostic::new(kind, default.range());
let edit = Edit::deletion(parameter.end(), pwd_range.end());
let edit = Edit::deletion(parameter.parameter.end(), parameter.end());
let fix = Fix::display_only_edit(edit);
checker.diagnostics.push(diagnostic.with_fix(fix));
}
}

View file

@ -330,11 +330,11 @@ fn call<'a>(
) {
diagnostics.extend(parameters.filter_map(|arg| {
let binding = scope
.get(arg.name.as_str())
.get(arg.name())
.map(|binding_id| semantic.binding(binding_id))?;
if binding.kind.is_argument()
&& binding.is_unused()
&& !dummy_variable_rgx.is_match(arg.name.as_str())
&& !dummy_variable_rgx.is_match(arg.name())
{
Some(Diagnostic::new(
argumentable.check_for(arg.name.to_string()),

View file

@ -8,7 +8,6 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::docstrings::{clean_space, leading_space};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::ParameterWithDefault;
use ruff_python_semantic::analyze::visibility::is_staticmethod;
use ruff_python_trivia::textwrap::dedent;
use ruff_source_file::NewlineWithTrailingNewline;
@ -1791,11 +1790,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
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
for parameter in function
.parameters
.iter_non_variadic_params()
.skip(usize::from(
@ -1803,7 +1798,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
&& !is_staticmethod(&function.decorator_list, checker.semantic()),
))
{
let arg_name = parameter.name.as_str();
let arg_name = parameter.name().as_str();
if !arg_name.starts_with('_') && !docstrings_args.contains(arg_name) {
missing_arg_names.insert(arg_name.to_string());
}

View file

@ -104,17 +104,11 @@ pub(crate) fn no_self_use(
}
// Identify the `self` parameter.
let Some(parameter) = parameters
.posonlyargs
.iter()
.chain(&parameters.args)
.next()
.map(|param| &param.parameter)
else {
let Some(parameter) = parameters.posonlyargs.iter().chain(&parameters.args).next() else {
return;
};
if parameter.name.as_str() != "self" {
if parameter.name() != "self" {
return;
}

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::function_type::{self as function_type, FunctionType};
use ruff_python_semantic::ScopeKind;
use ruff_text_size::Ranged;
@ -82,10 +82,7 @@ pub(crate) fn self_or_cls_assignment(checker: &mut Checker, target: &Expr) {
return;
};
let Some(ParameterWithDefault {
parameter: self_or_cls,
..
}) = parameters
let Some(self_or_cls) = parameters
.posonlyargs
.first()
.or_else(|| parameters.args.first())
@ -102,7 +99,7 @@ pub(crate) fn self_or_cls_assignment(checker: &mut Checker, target: &Expr) {
&checker.settings.pep8_naming.staticmethod_decorators,
);
let method_type = match (function_type, self_or_cls.name.as_str()) {
let method_type = match (function_type, self_or_cls.name().as_str()) {
(FunctionType::Method { .. }, "self") => MethodType::Instance,
(FunctionType::ClassMethod { .. }, "cls") => MethodType::Class,
_ => return,

View file

@ -68,12 +68,7 @@ pub(crate) fn too_many_arguments(checker: &mut Checker, function_def: &ast::Stmt
let num_arguments = function_def
.parameters
.iter_non_variadic_params()
.filter(|arg| {
!checker
.settings
.dummy_variable_rgx
.is_match(&arg.parameter.name)
})
.filter(|param| !checker.settings.dummy_variable_rgx.is_match(param.name()))
.count();
if num_arguments <= checker.settings.pylint.max_args {

View file

@ -72,12 +72,7 @@ pub(crate) fn too_many_positional_arguments(
.posonlyargs
.iter()
.chain(&function_def.parameters.args)
.filter(|param| {
!checker
.settings
.dummy_variable_rgx
.is_match(&param.parameter.name)
})
.filter(|param| !checker.settings.dummy_variable_rgx.is_match(param.name()))
.count();
if num_positional_args <= checker.settings.pylint.max_positional_args {

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{self as ast, Expr, Parameter, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_text_size::{Ranged, TextSize};
use crate::checkers::ast::Checker;
@ -90,13 +90,7 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr
};
// Extract the name of the first argument to the enclosing function.
let Some(ParameterWithDefault {
parameter: Parameter {
name: parent_arg, ..
},
..
}) = parent_parameters.args.first()
else {
let Some(parent_arg) = parent_parameters.args.first() else {
return;
};
@ -122,7 +116,7 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr
return;
};
if !(first_arg_id == parent_name.as_str() && second_arg_id == parent_arg.as_str()) {
if !(first_arg_id == parent_name.as_str() && second_arg_id == parent_arg.name().as_str()) {
return;
}

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::ExprSubscript;
use ruff_python_ast::{Expr, ExprSubscript};
use ruff_python_semantic::SemanticModel;
use crate::{checkers::ast::Checker, settings::types::PythonVersion};
@ -142,7 +142,7 @@ fn in_vararg(expr: &ExprSubscript, semantic: &SemanticModel) -> bool {
.parameters
.vararg
.as_ref()
.and_then(|vararg| vararg.annotation.as_ref())
.and_then(|annotation| annotation.as_subscript_expr())
.and_then(|vararg| vararg.annotation())
.and_then(Expr::as_subscript_expr)
== Some(expr)
}

View file

@ -456,11 +456,11 @@ fn match_arguments(
/// Returns `true` if the given argument is the "same" as the given expression. For example, if
/// the argument has a default, it is not considered the same as any expression; if both match the
/// same name, they are considered the same.
fn is_same_expression(arg: &ast::ParameterWithDefault, expr: &Expr) -> bool {
if arg.default.is_some() {
fn is_same_expression(param: &ast::ParameterWithDefault, expr: &Expr) -> bool {
if param.default.is_some() {
false
} else if let Expr::Name(name) = expr {
name.id == arg.parameter.name.as_str()
name.id == param.name().as_str()
} else {
false
}

View file

@ -6,7 +6,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, Expr, Operator, ParameterWithDefault, Parameters};
use ruff_python_ast::{self as ast, Expr, Operator, Parameters};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
@ -163,21 +163,15 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
/// RUF013
pub(crate) fn implicit_optional(checker: &mut Checker, parameters: &Parameters) {
for ParameterWithDefault {
parameter,
default,
range: _,
} in parameters.iter_non_variadic_params()
{
let Some(default) = default else { continue };
if !default.is_none_literal_expr() {
for parameter in parameters.iter_non_variadic_params() {
let Some(Expr::NoneLiteral(_)) = parameter.default() else {
continue;
}
let Some(annotation) = &parameter.annotation else {
};
let Some(annotation) = parameter.annotation() else {
continue;
};
if let Expr::StringLiteral(string_expr) = annotation.as_ref() {
if let Expr::StringLiteral(string_expr) = annotation {
// Quoted annotation.
if let Ok(parsed_annotation) = checker.parse_type_annotation(string_expr) {
let Some(expr) = type_hint_explicitly_allows_none(

View file

@ -104,20 +104,21 @@ pub(crate) fn post_init_default(checker: &mut Checker, function_def: &ast::StmtF
let mut stopped_fixes = false;
let mut diagnostics = vec![];
for ast::ParameterWithDefault {
parameter,
default,
range: _,
} in function_def.parameters.iter_non_variadic_params()
{
let Some(default) = default else {
for parameter in function_def.parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
let mut diagnostic = Diagnostic::new(PostInitDefault, default.range());
if !stopped_fixes {
diagnostic.try_set_fix(|| {
use_initvar(current_scope, function_def, parameter, default, checker)
use_initvar(
current_scope,
function_def,
&parameter.parameter,
default,
checker,
)
});
// Need to stop fixes as soon as there is a parameter we cannot fix.
// Otherwise, we risk a syntax error (a parameter without a default
@ -169,8 +170,7 @@ fn use_initvar(
let line_ending = checker.stylist().line_ending().as_str();
if let Some(annotation) = &parameter
.annotation
.as_deref()
.annotation()
.map(|annotation| locator.slice(annotation))
{
format!("{parameter_name}: {initvar_binding}[{annotation}] = {default}{line_ending}")

View file

@ -2654,6 +2654,16 @@ pub struct Parameter {
pub annotation: Option<Box<Expr>>,
}
impl Parameter {
pub const fn name(&self) -> &Identifier {
&self.name
}
pub fn annotation(&self) -> Option<&Expr> {
self.annotation.as_deref()
}
}
/// See also [keyword](https://docs.python.org/3/library/ast.html#ast.keyword)
#[derive(Clone, Debug, PartialEq)]
pub struct Keyword {
@ -3147,6 +3157,20 @@ pub struct ParameterWithDefault {
pub default: Option<Box<Expr>>,
}
impl ParameterWithDefault {
pub fn default(&self) -> Option<&Expr> {
self.default.as_deref()
}
pub const fn name(&self) -> &Identifier {
self.parameter.name()
}
pub fn annotation(&self) -> Option<&Expr> {
self.parameter.annotation()
}
}
/// An AST node used to represent the arguments passed to a function call or class definition.
///
/// For example, given:

View file

@ -798,7 +798,7 @@ fn handle_parameter_comment<'a>(
parameter: &'a Parameter,
source: &str,
) -> CommentPlacement<'a> {
if parameter.annotation.as_deref().is_some() {
if parameter.annotation().is_some() {
let colon = first_non_trivia_token(parameter.name.end(), source).expect(
"A annotated parameter should have a colon following its name when it is valid syntax.",
);

View file

@ -1,6 +1,7 @@
//! Analysis rules for the `typing` module.
use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{
self as ast, Expr, ExprCall, Int, Operator, ParameterWithDefault, Parameters, Stmt, StmtAssign,
@ -617,7 +618,7 @@ fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -> bo
let Some(parameter) = find_parameter(parameters, binding) else {
return false;
};
let Some(ref annotation) = parameter.parameter.annotation else {
let Some(annotation) = parameter.annotation() else {
return false;
};
T::match_annotation(annotation, semantic)
@ -1026,7 +1027,7 @@ fn find_parameter<'a>(
) -> Option<&'a ParameterWithDefault> {
parameters
.iter_non_variadic_params()
.find(|arg| arg.parameter.name.range() == binding.range())
.find(|param| param.identifier() == binding.range())
}
/// Return the [`QualifiedName`] of the value to which the given [`Expr`] is assigned, if any.