Introduce an Arguments AST node for function calls and class definitions (#6259)

## Summary

This PR adds a new `Arguments` AST node, which we can use for function
calls and class definitions.

The `Arguments` node spans from the left (open) to right (close)
parentheses inclusive.

In the case of classes, the `Arguments` is an option, to differentiate
between:

```python
# None
class C: ...

# Some, with empty vectors
class C(): ...
```

In this PR, we don't really leverage this change (except that a few
rules get much simpler, since we don't need to lex to find the start and
end ranges of the parentheses, e.g.,
`crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs`,
`crates/ruff/src/rules/pyupgrade/rules/unnecessary_class_parentheses.rs`).

In future PRs, this will be especially helpful for the formatter, since
we can track comments enclosed on the node itself.

## Test Plan

`cargo test`
This commit is contained in:
Charlie Marsh 2023-08-02 10:01:13 -04:00 committed by GitHub
parent 0d62ad2480
commit 981e64f82b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 24258 additions and 23835 deletions

View file

@ -74,6 +74,9 @@ pub(crate) fn remove_unused_imports<'a>(
///
/// Supports the removal of parentheses when this is the only (kw)arg left.
/// For this behavior, set `remove_parentheses` to `true`.
///
/// TODO(charlie): Migrate this signature to take [`Arguments`] rather than
/// separate args and keywords.
pub(crate) fn remove_argument(
locator: &Locator,
call_at: TextSize,

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, ExprContext, Operator, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator, Ranged};
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
use ruff_diagnostics::Diagnostic;
@ -211,11 +211,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
if let ScopeKind::Class(ast::StmtClassDef { bases, .. }) =
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
&checker.semantic.scope().kind
{
pep8_naming::rules::mixed_case_variable_in_class_scope(
checker, expr, id, bases,
checker,
expr,
id,
arguments.as_ref(),
);
}
}
@ -323,8 +326,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Expr::Call(
call @ ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
},
) => {

View file

@ -363,8 +363,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
Stmt::ClassDef(
class_def @ ast::StmtClassDef {
name,
bases,
keywords,
arguments,
type_params: _,
decorator_list,
body,
@ -376,20 +375,24 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::DjangoExcludeWithModelForm) {
if let Some(diagnostic) =
flake8_django::rules::exclude_with_model_form(checker, bases, body)
flake8_django::rules::exclude_with_model_form(checker, arguments.as_ref(), body)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::DjangoAllWithModelForm) {
if let Some(diagnostic) =
flake8_django::rules::all_with_model_form(checker, bases, body)
flake8_django::rules::all_with_model_form(checker, arguments.as_ref(), body)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::DjangoUnorderedBodyContentInModel) {
flake8_django::rules::unordered_body_content_in_model(checker, bases, body);
flake8_django::rules::unordered_body_content_in_model(
checker,
arguments.as_ref(),
body,
);
}
if !checker.is_stub {
if checker.enabled(Rule::DjangoModelWithoutDunderStr) {
@ -425,7 +428,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ErrorSuffixOnExceptionName) {
if let Some(diagnostic) = pep8_naming::rules::error_suffix_on_exception_name(
stmt,
bases,
arguments.as_ref(),
name,
&checker.settings.pep8_naming.ignore_names,
) {
@ -438,7 +441,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
Rule::EmptyMethodWithoutAbstractDecorator,
]) {
flake8_bugbear::rules::abstract_base_class(
checker, stmt, name, bases, keywords, body,
checker,
stmt,
name,
arguments.as_ref(),
body,
);
}
}
@ -478,7 +485,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
}
if checker.enabled(Rule::DuplicateBases) {
pylint::rules::duplicate_bases(checker, name, bases);
pylint::rules::duplicate_bases(checker, name, arguments.as_ref());
}
if checker.enabled(Rule::NoSlotsInStrSubclass) {
flake8_slots::rules::no_slots_in_str_subclass(checker, stmt, class_def);

View file

@ -31,8 +31,9 @@ use std::path::Path;
use itertools::Itertools;
use log::error;
use ruff_python_ast::{
self as ast, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr, ExprContext,
Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt, Suite, UnaryOp,
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt,
Suite, UnaryOp,
};
use ruff_text_size::{TextRange, TextSize};
@ -552,8 +553,7 @@ where
Stmt::ClassDef(
class_def @ ast::StmtClassDef {
body,
bases,
keywords,
arguments,
decorator_list,
type_params,
..
@ -568,11 +568,9 @@ where
for type_param in type_params {
self.visit_type_param(type_param);
}
for expr in bases {
self.visit_expr(expr);
}
for keyword in keywords {
self.visit_keyword(keyword);
if let Some(arguments) = arguments {
self.visit_arguments(arguments);
}
let definition = docstrings::extraction::extract_definition(
@ -837,8 +835,7 @@ where
match expr {
Expr::Call(ast::ExprCall {
func,
args: _,
keywords: _,
arguments: _,
range: _,
}) => {
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
@ -924,8 +921,12 @@ where
}
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) => {
self.visit_expr(func);

View file

@ -1,5 +1,5 @@
use ruff_python_ast as ast;
use ruff_python_ast::{Expr, Ranged};
use ruff_python_ast::{Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -60,7 +60,12 @@ pub(crate) fn variable_name_task_id(
};
// If the value is not a call, we can't do anything.
let Expr::Call(ast::ExprCall { func, keywords, .. }) = value else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) = value
else {
return None;
};

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -94,7 +94,12 @@ pub(crate) fn blind_except(
// If the exception is logged, don't flag an error.
if body.iter().any(|stmt| {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Call(ast::ExprCall { func, keywords, .. }) = value.as_ref() {
if let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) = value.as_ref()
{
if logging::is_logger_candidate(
func,
checker.semantic(),

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -139,14 +139,17 @@ pub(crate) fn abstract_base_class(
checker: &mut Checker,
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
arguments: Option<&Arguments>,
body: &[Stmt],
) {
if bases.len() + keywords.len() != 1 {
let Some(Arguments { args, keywords, .. }) = arguments else {
return;
};
if args.len() + keywords.len() != 1 {
return;
}
if !is_abc_class(bases, keywords, checker.semantic()) {
if !is_abc_class(args, keywords, checker.semantic()) {
return;
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, ExprContext, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Ranged, Stmt};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -53,12 +53,15 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
ctx: ExprContext::Load,
range: TextRange::default(),
})),
args: if let Some(msg) = msg {
vec![msg.clone()]
} else {
vec![]
arguments: Arguments {
args: if let Some(msg) = msg {
vec![msg.clone()]
} else {
vec![]
},
keywords: vec![],
range: TextRange::default(),
},
keywords: vec![],
range: TextRange::default(),
}))),
cause: None,

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, Ranged, WithItem};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, WithItem};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -81,8 +81,12 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
for item in items {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = &item.context_expr
else {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Comprehension, Expr, ExprContext, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprContext, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -130,8 +130,12 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
match expr {
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) => {
match func.as_ref() {

View file

@ -68,7 +68,7 @@ pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall
return;
};
if call.args.len() > method.num_args() {
if call.arguments.args.len() > method.num_args() {
checker.diagnostics.push(Diagnostic::new(
ReSubPositionalArgs { method },
call.range(),

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -68,8 +68,7 @@ pub(crate) fn zip_without_explicit_strict(
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
..
}) = &arg
else {

View file

@ -1,5 +1,5 @@
use ruff_python_ast as ast;
use ruff_python_ast::Decorator;
use ruff_python_ast::{Arguments, Decorator};
use ruff_text_size::TextRange;
use ruff_diagnostics::Diagnostic;
@ -78,8 +78,7 @@ pub(crate) fn builtin_attribute_shadowing(
// Ignore shadowing within `TypedDict` definitions, since these are only accessible through
// subscripting and not through attribute access.
if class_def
.bases
.iter()
.bases()
.any(|base| checker.semantic().match_typing_expr(base, "TypedDict"))
{
return;
@ -133,15 +132,18 @@ fn is_standard_library_override(
class_def: &ast::StmtClassDef,
model: &SemanticModel,
) -> bool {
let Some(Arguments { args: bases, .. }) = class_def.arguments.as_ref() else {
return false;
};
match name {
// Ex) `Event#set`
"set" => class_def.bases.iter().any(|base| {
"set" => bases.iter().any(|base| {
model
.resolve_call_path(base)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["threading", "Event"]))
}),
// Ex) `Filter#filter`
"filter" => class_def.bases.iter().any(|base| {
"filter" => bases.iter().any(|base| {
model
.resolve_call_path(base)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Filter"]))

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
use ruff_macros::{derive_message_formats, violation};
@ -88,7 +88,9 @@ pub(crate) fn unnecessary_double_cast_or_process(
};
let Expr::Call(ast::ExprCall {
func,
keywords: inner_kw,
arguments: Arguments {
keywords: inner_kw, ..
},
..
}) = arg
else {

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, ExprContext, Parameters, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Parameters, Ranged, Stmt};
use ruff_diagnostics::{AutofixKind, Violation};
use ruff_diagnostics::{Diagnostic, Fix};
@ -111,7 +111,12 @@ pub(crate) fn unnecessary_map(
}
ObjectType::List | ObjectType::Set => {
// Only flag, e.g., `list(map(lambda x: x + 1, iterable))`.
let [Expr::Call(ast::ExprCall { func, args, .. })] = args else {
let [Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
})] = args
else {
return;
};
@ -137,7 +142,12 @@ pub(crate) fn unnecessary_map(
}
ObjectType::Dict => {
// Only flag, e.g., `dict(map(lambda v: (v, v ** 2), values))`.
let [Expr::Call(ast::ExprCall { func, args, .. })] = args else {
let [Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
})] = args
else {
return;
};

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr};
use ruff_text_size::TextRange;
use ruff_diagnostics::{Diagnostic, Violation};
@ -59,7 +59,11 @@ pub(crate) fn call_datetime_strptime_without_zone(
return;
};
if let Expr::Call(ast::ExprCall { keywords, .. }) = grandparent {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { keywords, .. },
..
}) = grandparent
{
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = parent {
let attr = attr.as_str();
// Ex) `datetime.strptime(...).astimezone()`

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -49,15 +49,18 @@ impl Violation for DjangoAllWithModelForm {
/// DJ007
pub(crate) fn all_with_model_form(
checker: &Checker,
bases: &[Expr],
arguments: Option<&Arguments>,
body: &[Stmt],
) -> Option<Diagnostic> {
if !bases
.iter()
.any(|base| is_model_form(base, checker.semantic()))
{
if !arguments.is_some_and(|arguments| {
arguments
.args
.iter()
.any(|base| is_model_form(base, checker.semantic()))
}) {
return None;
}
for element in body {
let Stmt::ClassDef(ast::StmtClassDef { name, body, .. }) = element else {
continue;

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -47,15 +47,18 @@ impl Violation for DjangoExcludeWithModelForm {
/// DJ006
pub(crate) fn exclude_with_model_form(
checker: &Checker,
bases: &[Expr],
arguments: Option<&Arguments>,
body: &[Stmt],
) -> Option<Diagnostic> {
if !bases
.iter()
.any(|base| is_model_form(base, checker.semantic()))
{
if !arguments.is_some_and(|arguments| {
arguments
.args
.iter()
.any(|base| is_model_form(base, checker.semantic()))
}) {
return None;
}
for element in body {
let Stmt::ClassDef(ast::StmtClassDef { name, body, .. }) = element else {
continue;

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -54,10 +54,13 @@ impl Violation for DjangoModelWithoutDunderStr {
pub(crate) fn model_without_dunder_str(
checker: &mut Checker,
ast::StmtClassDef {
name, bases, body, ..
name,
arguments,
body,
..
}: &ast::StmtClassDef,
) {
if !is_non_abstract_model(bases, body, checker.semantic()) {
if !is_non_abstract_model(arguments.as_ref(), body, checker.semantic()) {
return;
}
if has_dunder_method(body) {
@ -80,16 +83,20 @@ fn has_dunder_method(body: &[Stmt]) -> bool {
})
}
fn is_non_abstract_model(bases: &[Expr], body: &[Stmt], semantic: &SemanticModel) -> bool {
for base in bases {
if is_model_abstract(body) {
continue;
}
if helpers::is_model(base, semantic) {
return true;
}
fn is_non_abstract_model(
arguments: Option<&Arguments>,
body: &[Stmt],
semantic: &SemanticModel,
) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
if is_model_abstract(body) {
return false;
}
false
bases.iter().any(|base| helpers::is_model(base, semantic))
}
/// Check if class is abstract, in terms of Django model inheritance.

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -69,7 +69,12 @@ pub(crate) fn nullable_model_string_field(checker: &mut Checker, body: &[Stmt])
}
fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
let Expr::Call(ast::ExprCall { func, keywords, .. }) = value else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) = value
else {
return None;
};

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -143,13 +143,15 @@ fn get_element_type(element: &Stmt, semantic: &SemanticModel) -> Option<ContentT
/// DJ012
pub(crate) fn unordered_body_content_in_model(
checker: &mut Checker,
bases: &[Expr],
arguments: Option<&Arguments>,
body: &[Stmt],
) {
if !bases
.iter()
.any(|base| helpers::is_model(base, checker.semantic()))
{
if !arguments.is_some_and(|arguments| {
arguments
.args
.iter()
.any(|base| helpers::is_model(base, checker.semantic()))
}) {
return;
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, ExprContext, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Ranged, Stmt};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
@ -174,7 +174,11 @@ impl Violation for DotFormatInException {
/// EM101, EM102, EM103
pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr) {
if let Expr::Call(ast::ExprCall { args, .. }) = exc {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = exc
{
if let Some(first) = args.first() {
match first {
// Check for string literals.

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Operator, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Operator, Ranged};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_python_ast::helpers::{find_keyword, CallArguments};
@ -108,7 +108,11 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
}
}
}
Expr::Call(ast::ExprCall { func, keywords, .. }) => {
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) => {
if checker
.semantic()
.resolve_call_path(func)

View file

@ -5,7 +5,7 @@ use itertools::Either::{Left, Right};
use ruff_text_size::TextRange;
use ruff_python_ast::{self as ast, BoolOp, Expr, ExprContext, Identifier, Ranged};
use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr, ExprContext, Identifier, Ranged};
use ruff_diagnostics::AlwaysAutofixableViolation;
use ruff_diagnostics::{Diagnostic, Edit, Fix};
@ -73,8 +73,12 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
for (index, call) in values.iter().enumerate() {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = &call
else {
@ -118,8 +122,12 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
.map(|expr| {
let Expr::Call(ast::ExprCall {
func: _,
args,
keywords: _,
arguments:
Arguments {
args,
keywords: _,
range: _,
},
range: _,
}) = expr
else {
@ -161,8 +169,11 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
args: vec![node],
keywords: vec![],
arguments: Arguments {
args: vec![node],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
});
let call = node3;

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt};
use rustc_hash::FxHashSet;
use ruff_diagnostics::Diagnostic;
@ -54,7 +54,11 @@ impl Violation for NonUniqueEnums {
/// PIE796
pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stmt]) {
let Stmt::ClassDef(ast::StmtClassDef { bases, .. }) = parent else {
let Stmt::ClassDef(ast::StmtClassDef {
arguments: Some(Arguments { args: bases, .. }),
..
}) = parent
else {
return;
};

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt};
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Parameters, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -186,7 +186,7 @@ pub(crate) fn non_self_return_type(
match name {
"__iter__" => {
if is_iterable(returns, checker.semantic())
&& is_iterator(&class_def.bases, checker.semantic())
&& is_iterator(class_def.arguments.as_ref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@ -199,7 +199,7 @@ pub(crate) fn non_self_return_type(
}
"__aiter__" => {
if is_async_iterable(returns, checker.semantic())
&& is_async_iterator(&class_def.bases, checker.semantic())
&& is_async_iterator(class_def.arguments.as_ref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@ -248,7 +248,10 @@ fn is_self(expr: &Expr, semantic: &SemanticModel) -> bool {
}
/// Return `true` if the given class extends `collections.abc.Iterator`.
fn is_iterator(bases: &[Expr], semantic: &SemanticModel) -> bool {
fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
bases.iter().any(|expr| {
semantic
.resolve_call_path(map_subscript(expr))
@ -275,7 +278,10 @@ fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
}
/// Return `true` if the given class extends `collections.abc.AsyncIterator`.
fn is_async_iterator(bases: &[Expr], semantic: &SemanticModel) -> bool {
fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
bases.iter().any(|expr| {
semantic
.resolve_call_path(map_subscript(expr))

View file

@ -1,5 +1,6 @@
use ruff_python_ast::{
self as ast, Constant, Expr, Operator, ParameterWithDefault, Parameters, Ranged, Stmt, UnaryOp,
self as ast, Arguments, Constant, Expr, Operator, ParameterWithDefault, Parameters, Ranged,
Stmt, UnaryOp,
};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix, Violation};
@ -368,7 +369,10 @@ fn is_final_assignment(annotation: &Expr, value: &Expr, semantic: &SemanticModel
}
/// Returns `true` if the a class is an enum, based on its base classes.
fn is_enum(bases: &[Expr], semantic: &SemanticModel) -> bool {
fn is_enum(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
return bases.iter().any(|expr| {
semantic.resolve_call_path(expr).is_some_and(|call_path| {
matches!(
@ -565,8 +569,8 @@ pub(crate) fn unannotated_assignment_in_stub(
return;
}
if let ScopeKind::Class(ast::StmtClassDef { bases, .. }) = checker.semantic().scope().kind {
if is_enum(bases, checker.semantic()) {
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = checker.semantic().scope().kind {
if is_enum(arguments.as_ref(), checker.semantic()) {
return;
}
}

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_semantic::Scope;
use crate::checkers::ast::Checker;
@ -216,8 +216,12 @@ pub(crate) fn unused_private_protocol(
let Some(source) = binding.source else {
continue;
};
let Stmt::ClassDef(ast::StmtClassDef { name, bases, .. }) =
checker.semantic().stmts[source]
let Stmt::ClassDef(ast::StmtClassDef {
name,
arguments: Some(Arguments { args: bases, .. }),
..
}) = checker.semantic().stmts[source]
else {
continue;
};
@ -304,8 +308,11 @@ pub(crate) fn unused_private_typed_dict(
let Some(source) = binding.source else {
continue;
};
let Stmt::ClassDef(ast::StmtClassDef { name, bases, .. }) =
checker.semantic().stmts[source]
let Stmt::ClassDef(ast::StmtClassDef {
name,
arguments: Some(Arguments { args: bases, .. }),
..
}) = checker.semantic().stmts[source]
else {
continue;
};

View file

@ -1,7 +1,7 @@
use std::fmt;
use ruff_python_ast::Decorator;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters, Ranged, Stmt};
use ruff_python_ast::{Arguments, Decorator};
use ruff_text_size::{TextLen, TextRange};
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
@ -476,8 +476,12 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
match &decorator.expression {
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) => {
if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -106,8 +106,12 @@ fn check_mark_parentheses(checker: &mut Checker, decorator: &Decorator, call_pat
match &decorator.expression {
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) => {
if !checker.settings.flake8_pytest_style.mark_parentheses
@ -134,7 +138,11 @@ fn check_useless_usefixtures(checker: &mut Checker, decorator: &Decorator, call_
let mut has_parameters = false;
if let Expr::Call(ast::ExprCall { args, keywords, .. }) = &decorator.expression {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, keywords, .. },
..
}) = &decorator.expression
{
if !args.is_empty() || !keywords.is_empty() {
has_parameters = true;
}

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Decorator, Expr, ExprContext, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Decorator, Expr, ExprContext, Ranged};
use ruff_python_parser::{lexer, Mode, Tok};
use ruff_text_size::TextRange;
@ -414,7 +414,11 @@ fn handle_value_rows(
pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Decorator]) {
for decorator in decorators {
if is_pytest_parametrize(decorator, checker.semantic()) {
if let Expr::Call(ast::ExprCall { args, .. }) = &decorator.expression {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = &decorator.expression
{
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
if let Some(names) = args.get(0) {
check_names(checker, decorator, names);

View file

@ -2,7 +2,7 @@ use std::hash::BuildHasherDefault;
use anyhow::{anyhow, bail, Result};
use ruff_python_ast::{
self as ast, CmpOp, Constant, Expr, ExprContext, Identifier, Keyword, Stmt, UnaryOp,
self as ast, Arguments, CmpOp, Constant, Expr, ExprContext, Identifier, Keyword, Stmt, UnaryOp,
};
use ruff_text_size::TextRange;
use rustc_hash::FxHashMap;
@ -355,8 +355,11 @@ impl UnittestAssert {
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
args: vec![(**obj).clone(), (**cls).clone()],
keywords: vec![],
arguments: Arguments {
args: vec![(**obj).clone(), (**cls).clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let isinstance = node1.into();
@ -396,8 +399,11 @@ impl UnittestAssert {
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
args: vec![(**regex).clone(), (**text).clone()],
keywords: vec![],
arguments: Arguments {
args: vec![(**regex).clone(), (**text).clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let re_search = node2.into();

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use ruff_python_parser::{lexer, Mode, Tok};
use ruff_text_size::{TextRange, TextSize};
@ -47,31 +47,37 @@ impl AlwaysAutofixableViolation for UnnecessaryParenOnRaiseException {
/// RSE102
pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr: &Expr) {
if let Expr::Call(ast::ExprCall {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = expr
{
if args.is_empty() && keywords.is_empty() {
// `raise func()` still requires parentheses; only `raise Class()` does not.
if checker
.semantic()
.lookup_attribute(func)
.is_some_and(|id| checker.semantic().binding(id).kind.is_function_definition())
{
return;
}
else {
return;
};
let range = match_parens(func.end(), checker.locator())
.expect("Expected call to include parentheses");
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, range);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::deletion(func.end(), range.end())));
}
checker.diagnostics.push(diagnostic);
if args.is_empty() && keywords.is_empty() {
// `raise func()` still requires parentheses; only `raise Class()` does not.
if checker
.semantic()
.lookup_attribute(func)
.is_some_and(|id| checker.semantic().binding(id).kind.is_function_definition())
{
return;
}
let range = match_parens(func.end(), checker.locator())
.expect("Expected call to include parentheses");
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, range);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::deletion(func.end(), range.end())));
}
checker.diagnostics.push(diagnostic);
}
}

View file

@ -3,7 +3,7 @@ use std::iter;
use itertools::Either::{Left, Right};
use itertools::Itertools;
use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr, ExprContext, Ranged, UnaryOp};
use ruff_python_ast::{self as ast, Arguments, BoolOp, CmpOp, Expr, ExprContext, Ranged, UnaryOp};
use ruff_text_size::TextRange;
use rustc_hash::FxHashMap;
@ -315,8 +315,12 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
// Verify that this is an `isinstance` call.
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = &call
else {
@ -351,7 +355,11 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
if indices.len() > 1 {
// Grab the target used in each duplicate `isinstance` call (e.g., `obj` in
// `isinstance(obj, int)`).
let target = if let Expr::Call(ast::ExprCall { args, .. }) = &values[indices[0]] {
let target = if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = &values[indices[0]]
{
args.get(0).expect("`isinstance` should have two arguments")
} else {
unreachable!("Indices should only contain `isinstance` calls")
@ -374,7 +382,11 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
.iter()
.map(|index| &values[*index])
.map(|expr| {
let Expr::Call(ast::ExprCall { args, .. }) = expr else {
let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = expr
else {
unreachable!("Indices should only contain `isinstance` calls")
};
args.get(1).expect("`isinstance` should have two arguments")
@ -405,8 +417,11 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
args: vec![target.clone(), node.into()],
keywords: vec![],
arguments: Arguments {
args: vec![target.clone(), node.into()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let call = node2.into();

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, AutofixKind, Diagnostic, Edit, Fix, Violation};
@ -103,7 +103,12 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
}
// Ex) `os.environ.get('foo')`, `os.getenv('foo')`
let Expr::Call(ast::ExprCall { func, args, .. }) = expr else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) = expr
else {
return;
};
let Some(arg) = args.get(0) else {
@ -200,8 +205,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
pub(crate) fn dict_get_with_none_default(checker: &mut Checker, expr: &Expr) {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _,
}) = expr
else {

View file

@ -1,7 +1,7 @@
use log::error;
use ruff_python_ast::{
self as ast, CmpOp, Constant, ElifElseClause, Expr, ExprContext, Identifier, Ranged, Stmt,
StmtIf,
self as ast, Arguments, CmpOp, Constant, ElifElseClause, Expr, ExprContext, Identifier, Ranged,
Stmt,
};
use ruff_text_size::TextRange;
use rustc_hash::FxHashSet;
@ -314,8 +314,8 @@ fn find_last_nested_if(body: &[Stmt]) -> Option<(&Expr, &Stmt)> {
}
/// Returns the body, the range of the `if` or `elif` and whether the range is for an `if` or `elif`
fn nested_if_body(stmt_if: &StmtIf) -> Option<(&[Stmt], TextRange, bool)> {
let StmtIf {
fn nested_if_body(stmt_if: &ast::StmtIf) -> Option<(&[Stmt], TextRange, bool)> {
let ast::StmtIf {
test,
body,
elif_else_clauses,
@ -361,7 +361,11 @@ fn nested_if_body(stmt_if: &StmtIf) -> Option<(&[Stmt], TextRange, bool)> {
}
/// SIM102
pub(crate) fn nested_if_statements(checker: &mut Checker, stmt_if: &StmtIf, parent: Option<&Stmt>) {
pub(crate) fn nested_if_statements(
checker: &mut Checker,
stmt_if: &ast::StmtIf,
parent: Option<&Stmt>,
) {
let Some((body, range, is_elif)) = nested_if_body(stmt_if) else {
return;
};
@ -538,8 +542,11 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
args: vec![if_test.clone()],
keywords: vec![],
arguments: Arguments {
args: vec![if_test.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let node2 = ast::StmtReturn {
@ -692,7 +699,7 @@ fn body_range(branch: &IfElifBranch, locator: &Locator) -> TextRange {
}
/// SIM114
pub(crate) fn if_with_same_arms(checker: &mut Checker, locator: &Locator, stmt_if: &StmtIf) {
pub(crate) fn if_with_same_arms(checker: &mut Checker, locator: &Locator, stmt_if: &ast::StmtIf) {
let mut branches_iter = if_elif_branches(stmt_if).peekable();
while let Some(current_branch) = branches_iter.next() {
let Some(following_branch) = branches_iter.peek() else {
@ -731,12 +738,12 @@ pub(crate) fn if_with_same_arms(checker: &mut Checker, locator: &Locator, stmt_i
}
/// SIM116
pub(crate) fn manual_dict_lookup(checker: &mut Checker, stmt_if: &StmtIf) {
pub(crate) fn manual_dict_lookup(checker: &mut Checker, stmt_if: &ast::StmtIf) {
// Throughout this rule:
// * Each if or elif statement's test must consist of a constant equality check with the same variable.
// * Each if or elif statement's body must consist of a single `return`.
// * The else clause must be empty, or a single `return`.
let StmtIf {
let ast::StmtIf {
body,
test,
elif_else_clauses,
@ -842,8 +849,8 @@ pub(crate) fn manual_dict_lookup(checker: &mut Checker, stmt_if: &StmtIf) {
}
/// SIM401
pub(crate) fn use_dict_get_with_default(checker: &mut Checker, stmt_if: &StmtIf) {
let StmtIf {
pub(crate) fn use_dict_get_with_default(checker: &mut Checker, stmt_if: &ast::StmtIf) {
let ast::StmtIf {
test,
body,
elif_else_clauses,
@ -949,8 +956,11 @@ pub(crate) fn use_dict_get_with_default(checker: &mut Checker, stmt_if: &StmtIf)
};
let node3 = ast::ExprCall {
func: Box::new(node2.into()),
args: vec![node1, node],
keywords: vec![],
arguments: Arguments {
args: vec![node1, node],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let node4 = expected_var.clone();

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, ExprContext, Ranged, UnaryOp};
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Ranged, UnaryOp};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, AutofixKind, Diagnostic, Edit, Fix, Violation};
@ -166,8 +166,11 @@ pub(crate) fn explicit_true_false_in_ifexpr(
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
args: vec![test.clone()],
keywords: vec![],
arguments: Arguments {
args: vec![test.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, CmpOp, Expr, ExprContext, Ranged, Stmt, UnaryOp};
use ruff_python_ast::{self as ast, Arguments, CmpOp, Expr, ExprContext, Ranged, Stmt, UnaryOp};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -286,8 +286,11 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: UnaryOp, o
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
args: vec![*operand.clone()],
keywords: vec![],
arguments: Arguments {
args: vec![*operand.clone()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use ruff_python_ast::{self as ast, CmpOp, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, CmpOp, Expr, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::Edit;
@ -71,8 +71,7 @@ fn key_in_dict(
) {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _,
}) = &right
else {

View file

@ -1,5 +1,6 @@
use ruff_python_ast::{
self as ast, CmpOp, Comprehension, Constant, Expr, ExprContext, Ranged, Stmt, UnaryOp,
self as ast, Arguments, CmpOp, Comprehension, Constant, Expr, ExprContext, Ranged, Stmt,
UnaryOp,
};
use ruff_text_size::TextRange;
@ -394,8 +395,11 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
args: vec![node.into()],
keywords: vec![],
arguments: Arguments {
args: vec![node.into()],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
let node3 = ast::StmtReturn {

View file

@ -1,5 +1,5 @@
use ruff_python_ast as ast;
use ruff_python_ast::{Expr, StmtClassDef};
use ruff_python_ast::{Arguments, Expr, StmtClassDef};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -63,7 +63,11 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
stmt: &Stmt,
class: &StmtClassDef,
) {
if class.bases.iter().any(|base| {
let Some(Arguments { args: bases, .. }) = class.arguments.as_ref() else {
return;
};
if bases.iter().any(|base| {
let Expr::Call(ast::ExprCall { func, .. }) = base else {
return false;
};

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Expr, Stmt, StmtClassDef};
use ruff_python_ast::{Arguments, Expr, Stmt, StmtClassDef};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -51,7 +51,11 @@ impl Violation for NoSlotsInStrSubclass {
/// SLOT000
pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class: &StmtClassDef) {
if is_str_subclass(&class.bases, checker.semantic()) {
let Some(Arguments { args: bases, .. }) = class.arguments.as_ref() else {
return;
};
if is_str_subclass(bases, checker.semantic()) {
if !has_slots(&class.body) {
checker
.diagnostics

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Stmt, StmtClassDef};
use ruff_python_ast::{Arguments, Stmt, StmtClassDef};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -51,7 +51,11 @@ impl Violation for NoSlotsInTupleSubclass {
/// SLOT001
pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, class: &StmtClassDef) {
if class.bases.iter().any(|base| {
let Some(Arguments { args: bases, .. }) = class.arguments.as_ref() else {
return;
};
if bases.iter().any(|base| {
checker
.semantic()
.resolve_call_path(map_subscript(base))

View file

@ -1,4 +1,5 @@
use ruff_python_ast as ast;
use ruff_python_ast::Arguments;
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::helpers::map_callable;
@ -37,7 +38,11 @@ pub(crate) fn runtime_evaluated(
}
fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticModel) -> bool {
if let ScopeKind::Class(ast::StmtClassDef { bases, .. }) = &semantic.scope().kind {
if let ScopeKind::Class(ast::StmtClassDef {
arguments: Some(Arguments { args: bases, .. }),
..
}) = &semantic.scope().kind
{
for base in bases {
if let Some(call_path) = semantic.resolve_call_path(base) {
if base_classes

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Constant, Expr, ExprCall, ExprConstant};
use ruff_python_ast::{Arguments, Constant, Expr, ExprCall, ExprConstant};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -54,7 +54,11 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
return;
}
let Expr::Call(ExprCall { args, keywords, .. }) = expr else {
let Expr::Call(ExprCall {
arguments: Arguments { args, keywords, .. },
..
}) = expr
else {
return;
};

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, ConversionFlag, Expr};
use ruff_python_ast::{self as ast, Arguments, Constant, ConversionFlag, Expr};
use ruff_text_size::TextRange;
/// Wrap an expression in a `FormattedValue` with no special formatting.
@ -29,8 +29,12 @@ fn is_simple_call(expr: &Expr) -> bool {
match expr {
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) => args.is_empty() && keywords.is_empty() && is_simple_callee(func),
_ => false,

View file

@ -1,5 +1,5 @@
use itertools::Itertools;
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -106,7 +106,11 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
}
pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner: &str) {
let Expr::Call(ast::ExprCall { args, keywords, .. }) = expr else {
let Expr::Call(ast::ExprCall {
arguments: Arguments { args, keywords, .. },
..
}) = expr
else {
return;
};

View file

@ -1,5 +1,5 @@
use itertools::Itertools;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_semantic::SemanticModel;
use ruff_python_stdlib::str::{is_cased_lowercase, is_cased_uppercase};
@ -61,10 +61,13 @@ pub(super) fn is_type_var_assignment(stmt: &Stmt, semantic: &SemanticModel) -> b
.is_some_and(|call_path| matches!(call_path.as_slice(), ["typing", "TypeVar" | "NewType"]))
}
pub(super) fn is_typed_dict_class(bases: &[Expr], semantic: &SemanticModel) -> bool {
bases
.iter()
.any(|base| semantic.match_typing_expr(base, "TypedDict"))
pub(super) fn is_typed_dict_class(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
arguments.is_some_and(|arguments| {
arguments
.args
.iter()
.any(|base| semantic.match_typing_expr(base, "TypedDict"))
})
}
#[cfg(test)]

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -45,10 +45,26 @@ impl Violation for ErrorSuffixOnExceptionName {
/// N818
pub(crate) fn error_suffix_on_exception_name(
class_def: &Stmt,
bases: &[Expr],
arguments: Option<&Arguments>,
name: &str,
ignore_names: &[IdentifierPattern],
) -> Option<Diagnostic> {
if name.ends_with("Error") {
return None;
}
if !arguments.is_some_and(|arguments| {
arguments.args.iter().any(|base| {
if let Expr::Name(ast::ExprName { id, .. }) = &base {
id == "Exception" || id.ends_with("Error")
} else {
false
}
})
}) {
return None;
}
if ignore_names
.iter()
.any(|ignore_name| ignore_name.matches(name))
@ -56,19 +72,6 @@ pub(crate) fn error_suffix_on_exception_name(
return None;
}
if !bases.iter().any(|base| {
if let Expr::Name(ast::ExprName { id, .. }) = &base {
id == "Exception" || id.ends_with("Error")
} else {
false
}
}) {
return None;
}
if name.ends_with("Error") {
return None;
}
Some(Diagnostic::new(
ErrorSuffixOnExceptionName {
name: name.to_string(),

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Expr, Ranged};
use ruff_python_ast::{Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -54,7 +54,7 @@ pub(crate) fn mixed_case_variable_in_class_scope(
checker: &mut Checker,
expr: &Expr,
name: &str,
bases: &[Expr],
arguments: Option<&Arguments>,
) {
if checker
.settings
@ -72,7 +72,7 @@ pub(crate) fn mixed_case_variable_in_class_scope(
let parent = checker.semantic().stmt();
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|| helpers::is_typed_dict_class(bases, checker.semantic())
|| helpers::is_typed_dict_class(arguments, checker.semantic())
{
return;
}

View file

@ -1,8 +1,8 @@
use std::fmt;
use ruff_python_ast as ast;
use ruff_python_ast::Expr;
use ruff_python_ast::Ranged;
use ruff_python_ast::{Arguments, Expr};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -65,7 +65,12 @@ pub(crate) fn incorrect_dict_iterator(checker: &mut Checker, target: &Expr, iter
let [key, value] = elts.as_slice() else {
return;
};
let Expr::Call(ast::ExprCall { func, args, .. }) = iter else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) = iter
else {
return;
};
if !args.is_empty() {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -92,9 +92,13 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
let Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
range,
args,
keywords,
}) = value.as_ref()
else {
return;

View file

@ -1,7 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use crate::checkers::ast::Checker;
@ -59,9 +59,13 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
let Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
range,
args,
keywords,
}) = value.as_ref()
else {
return;

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Arguments, Expr};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -54,17 +54,21 @@ impl AlwaysAutofixableViolation for UnnecessaryListCast {
pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
let Expr::Call(ast::ExprCall {
func,
args,
arguments:
Arguments {
args,
keywords: _,
range: _,
},
range: list_range,
..
}) = iter
else {
return;
};
if args.len() != 1 {
let [arg] = args.as_slice() else {
return;
}
};
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
return;
@ -74,7 +78,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
return;
}
match &args[0] {
match arg {
Expr::Tuple(ast::ExprTuple {
range: iterable_range,
..

View file

@ -1,5 +1,5 @@
use itertools::izip;
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, CmpOp, Constant, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -49,7 +49,11 @@ pub(crate) fn type_comparison(
continue;
}
match right {
Expr::Call(ast::ExprCall { func, args, .. }) => {
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
// Ex) `type(False)`
if id == "type" && checker.semantic().is_builtin("type") {

View file

@ -1,6 +1,6 @@
use std::hash::BuildHasherDefault;
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use rustc_hash::FxHashSet;
use ruff_diagnostics::{Diagnostic, Violation};
@ -51,7 +51,11 @@ impl Violation for DuplicateBases {
}
/// PLE0241
pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, bases: &[Expr]) {
pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, arguments: Option<&Arguments>) {
let Some(Arguments { args: bases, .. }) = arguments else {
return;
};
let mut seen: FxHashSet<&str> =
FxHashSet::with_capacity_and_hasher(bases.len(), BuildHasherDefault::default());
for base in bases {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
@ -95,8 +95,12 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel)
for arg in args {
if let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = arg
{
@ -135,14 +139,18 @@ pub(crate) fn nested_min_max(
return;
};
if args.len() == 1
&& matches!(&args[0], Expr::Call(ast::ExprCall { args, .. }) if args.len() == 1)
if matches!(&args, [Expr::Call(ast::ExprCall { arguments: Arguments {args, .. }, .. })] if args.len() == 1)
{
return;
}
if args.iter().any(|arg| {
let Expr::Call(ast::ExprCall { func, keywords, .. }) = arg else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) = arg
else {
return false;
};
MinMax::try_from_call(func.as_ref(), keywords.as_ref(), checker.semantic()) == Some(min_max)
@ -152,8 +160,11 @@ pub(crate) fn nested_min_max(
if !checker.indexer().has_comments(expr, checker.locator()) {
let flattened_expr = Expr::Call(ast::ExprCall {
func: Box::new(func.clone()),
args: collect_nested_args(min_max, args, checker.semantic()),
keywords: keywords.to_owned(),
arguments: Arguments {
args: collect_nested_args(min_max, args, checker.semantic()),
keywords: keywords.to_owned(),
range: TextRange::default(),
},
range: TextRange::default(),
});
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(

View file

@ -1,7 +1,7 @@
use std::{fmt, iter};
use regex::Regex;
use ruff_python_ast::{self as ast, Expr, ExprContext, Ranged, Stmt, WithItem};
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Ranged, Stmt, WithItem};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -237,7 +237,12 @@ impl<'a, 'b> StatementVisitor<'b> for InnerForWithAssignTargetsVisitor<'a, 'b> {
/// x = cast(int, x)
/// ```
fn assignment_is_cast_expr(value: &Expr, target: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Call(ast::ExprCall { func, args, .. }) = value else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) = value
else {
return false;
};
let Expr::Name(ast::ExprName { id: target_id, .. }) = target else {

View file

@ -1,5 +1,5 @@
use itertools::Itertools;
use ruff_python_ast::{self as ast, BoolOp, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr, Ranged};
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -73,7 +73,12 @@ pub(crate) fn repeated_isinstance_calls(
let mut obj_to_types: FxHashMap<HashableExpr, (usize, FxHashSet<HashableExpr>)> =
FxHashMap::default();
for value in values {
let Expr::Call(ast::ExprCall { func, args, .. }) = value else {
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) = value
else {
continue;
};
if !matches!(func.as_ref(), Expr::Name(ast::ExprName { id, .. }) if id == "isinstance") {

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -76,8 +76,7 @@ impl Violation for TypeBivariance {
pub(crate) fn type_bivariance(checker: &mut Checker, value: &Expr) {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
..
}) = value
else {

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -67,8 +67,7 @@ impl Violation for TypeNameIncorrectVariance {
pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
..
}) = value
else {

View file

@ -1,6 +1,6 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -69,8 +69,7 @@ pub(crate) fn type_param_name_mismatch(checker: &mut Checker, value: &Expr, targ
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
..
}) = value
else {

View file

@ -1,7 +1,7 @@
use anyhow::{bail, Result};
use log::debug;
use ruff_python_ast::{
self as ast, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt,
self as ast, Arguments, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt,
};
use ruff_text_size::TextRange;
@ -78,8 +78,7 @@ fn match_named_tuple_assign<'a>(
};
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _,
}) = value
else {
@ -91,7 +90,7 @@ fn match_named_tuple_assign<'a>(
Some((typename, args, keywords, func))
}
/// Generate a `Stmt::AnnAssign` representing the provided property
/// Generate a [`Stmt::AnnAssign`] representing the provided property
/// definition.
fn create_property_assignment_stmt(property: &str, annotation: &Expr) -> Stmt {
ast::StmtAnnAssign {
@ -167,8 +166,11 @@ fn create_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) -> Stmt {
ast::StmtClassDef {
name: Identifier::new(typename.to_string(), TextRange::default()),
bases: vec![base_class.clone()],
keywords: vec![],
arguments: Some(Arguments {
args: vec![base_class.clone()],
keywords: vec![],
range: TextRange::default(),
}),
body,
type_params: vec![],
decorator_list: vec![],

View file

@ -1,7 +1,7 @@
use anyhow::{bail, Result};
use log::debug;
use ruff_python_ast::{
self as ast, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt,
self as ast, Arguments, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt,
};
use ruff_text_size::TextRange;
@ -77,8 +77,7 @@ fn match_typed_dict_assign<'a>(
};
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _,
}) = value
else {
@ -90,7 +89,7 @@ fn match_typed_dict_assign<'a>(
Some((class_name, args, keywords, func))
}
/// Generate a `Stmt::AnnAssign` representing the provided property
/// Generate a [`Stmt::AnnAssign`] representing the provided property
/// definition.
fn create_property_assignment_stmt(property: &str, annotation: &Expr) -> Stmt {
ast::StmtAnnAssign {
@ -118,14 +117,16 @@ fn create_class_def_stmt(
total_keyword: Option<&Keyword>,
base_class: &Expr,
) -> Stmt {
let keywords = match total_keyword {
Some(keyword) => vec![keyword.clone()],
None => vec![],
};
ast::StmtClassDef {
name: Identifier::new(class_name.to_string(), TextRange::default()),
bases: vec![base_class.clone()],
keywords,
arguments: Some(Arguments {
args: vec![base_class.clone()],
keywords: match total_keyword {
Some(keyword) => vec![keyword.clone()],
None => vec![],
},
range: TextRange::default(),
}),
body,
type_params: vec![],
decorator_list: vec![],
@ -217,9 +218,11 @@ fn match_properties_and_total<'a>(
values,
range: _,
}) => Ok((properties_from_dict_literal(keys, values)?, total)),
Expr::Call(ast::ExprCall { func, keywords, .. }) => {
Ok((properties_from_dict_call(func, keywords)?, total))
}
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) => Ok((properties_from_dict_call(func, keywords)?, total)),
_ => bail!("Expected `arg` to be `Expr::Dict` or `Expr::Call`"),
}
} else if !keywords.is_empty() {

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged};
use ruff_python_literal::format::{
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
};
@ -65,7 +65,11 @@ impl<'a> FormatSummaryValues<'a> {
fn try_from_expr(expr: &'a Expr, locator: &'a Locator) -> Option<Self> {
let mut extracted_args: Vec<&Expr> = Vec::new();
let mut extracted_kwargs: FxHashMap<&str, &Expr> = FxHashMap::default();
if let Expr::Call(ast::ExprCall { args, keywords, .. }) = expr {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, keywords, .. },
..
}) = expr
{
for arg in args {
if contains_invalids(locator.slice(arg.range()))
|| locator.contains_line_break(arg.range())

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Keyword, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -61,8 +61,12 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &mut Checker, decorator_list:
for decorator in decorator_list {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
}) = &decorator.expression
else {

View file

@ -1,8 +1,7 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Decorator, Expr, Ranged};
use ruff_text_size::TextRange;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -59,8 +58,7 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list
for decorator in decorator_list {
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments,
range: _,
}) = &decorator.expression
else {
@ -68,8 +66,8 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list
};
// Look for, e.g., `import functools; @functools.lru_cache()`.
if args.is_empty()
&& keywords.is_empty()
if arguments.args.is_empty()
&& arguments.keywords.is_empty()
&& checker
.semantic()
.resolve_call_path(func)
@ -80,9 +78,9 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list
TextRange::new(func.end(), decorator.end()),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
checker.generator().expr(func),
decorator.expression.range(),
diagnostic.set_fix(Fix::automatic(Edit::deletion(
arguments.start(),
arguments.end(),
)));
}
checker.diagnostics.push(diagnostic);

View file

@ -1,7 +1,7 @@
use std::str::FromStr;
use anyhow::{anyhow, Result};
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged};
use ruff_python_parser::{lexer, Mode};
use ruff_text_size::TextSize;
@ -119,8 +119,12 @@ fn match_open<'a>(
) -> Option<(Option<&'a Expr>, &'a [Keyword])> {
let ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
range: _,
} = expr.as_call_expr()?;

View file

@ -1,10 +1,6 @@
use std::ops::Add;
use ruff_python_ast::{self as ast, Ranged};
use ruff_text_size::{TextRange, TextSize};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Ranged};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -44,54 +40,20 @@ impl AlwaysAutofixableViolation for UnnecessaryClassParentheses {
/// UP039
pub(crate) fn unnecessary_class_parentheses(checker: &mut Checker, class_def: &ast::StmtClassDef) {
if !class_def.bases.is_empty() || !class_def.keywords.is_empty() {
return;
}
let offset = class_def.name.end();
let contents = checker.locator().after(offset);
// Find the open and closing parentheses between the class name and the colon, if they exist.
let mut depth = 0u32;
let mut start = None;
let mut end = None;
for (i, c) in contents.char_indices() {
match c {
'(' => {
if depth == 0 {
start = Some(i);
}
depth = depth.saturating_add(1);
}
')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
end = Some(i + c.len_utf8());
}
}
':' => {
if depth == 0 {
break;
}
}
_ => {}
}
}
let (Some(start), Some(end)) = (start, end) else {
let Some(arguments) = class_def.arguments.as_ref() else {
return;
};
// Convert to `TextSize`.
let start = TextSize::try_from(start).unwrap();
let end = TextSize::try_from(end).unwrap();
if !arguments.args.is_empty() || !arguments.keywords.is_empty() {
return;
}
// Add initial offset.
let start = offset.add(start);
let end = offset.add(end);
let mut diagnostic = Diagnostic::new(UnnecessaryClassParentheses, TextRange::new(start, end));
let mut diagnostic = Diagnostic::new(UnnecessaryClassParentheses, arguments.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::deletion(start, end)));
diagnostic.set_fix(Fix::automatic(Edit::deletion(
arguments.start(),
arguments.end(),
)));
}
checker.diagnostics.push(diagnostic);
}

View file

@ -47,7 +47,11 @@ impl AlwaysAutofixableViolation for UselessObjectInheritance {
/// UP004
pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast::StmtClassDef) {
for expr in &class_def.bases {
let Some(arguments) = class_def.arguments.as_ref() else {
return;
};
for expr in &arguments.args {
let Expr::Name(ast::ExprName { id, .. }) = expr else {
continue;
};
@ -70,8 +74,8 @@ pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast:
checker.locator(),
class_def.name.end(),
expr.range(),
&class_def.bases,
&class_def.keywords,
&arguments.args,
&arguments.keywords,
true,
)?;
Ok(Fix::automatic(edit))

View file

@ -2,7 +2,7 @@ use anyhow::{bail, Result};
use libcst_native::{
ConcatenatedString, Expression, FormattedStringContent, FormattedStringExpression,
};
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -80,8 +80,12 @@ pub(crate) fn explicit_f_string_type_conversion(
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
..
}) = value.as_ref()
else {

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Arguments, Expr};
use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_semantic::{BindingKind, SemanticModel};
@ -51,7 +51,11 @@ pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticMod
/// Returns `true` if the given class is a Pydantic `BaseModel` or `BaseSettings` subclass.
pub(super) fn is_pydantic_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
class_def.bases.iter().any(|expr| {
let Some(Arguments { args: bases, .. }) = class_def.arguments.as_ref() else {
return false;
};
bases.iter().any(|expr| {
semantic.resolve_call_path(expr).is_some_and(|call_path| {
matches!(
call_path.as_slice(),

View file

@ -5,7 +5,7 @@ use unicode_width::UnicodeWidthStr;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Comprehension, Constant, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Comprehension, Constant, Expr, Ranged};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::{TextRange, TextSize};
@ -151,7 +151,11 @@ struct IterationTarget {
/// redundant comprehension).
fn match_iteration_target(expr: &Expr, model: &SemanticModel) -> Option<IterationTarget> {
let result = match expr {
Expr::Call(ast::ExprCall { func, args, .. }) => {
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, .. },
..
}) => {
let ast::ExprName { id, .. } = func.as_name_expr()?;
if !matches!(id.as_str(), "tuple" | "list") {

View file

@ -66,7 +66,7 @@ pub(crate) fn error_instead_of_exception(checker: &mut Checker, handlers: &[Exce
for expr in calls {
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = expr.func.as_ref() {
if attr == "error" {
if exc_info(&expr.keywords, checker.semantic()).is_none() {
if exc_info(&expr.arguments.keywords, checker.semantic()).is_none() {
checker
.diagnostics
.push(Diagnostic::new(ErrorInsteadOfException, expr.range()));

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@ -77,7 +77,11 @@ where
/// TRY003
pub(crate) fn raise_vanilla_args(checker: &mut Checker, expr: &Expr) {
if let Expr::Call(ast::ExprCall { args, .. }) = expr {
if let Expr::Call(ast::ExprCall {
arguments: Arguments { args, .. },
..
}) = expr
{
if let Some(arg) = args.first() {
if any_string(arg, |part| part.chars().any(char::is_whitespace)) {
checker

View file

@ -79,7 +79,7 @@ pub(crate) fn verbose_log_message(checker: &mut Checker, handlers: &[ExceptHandl
// Collect all referenced names in the `logging.exception` call.
let names: Vec<&ast::ExprName> = {
let mut names = Vec::new();
for arg in &expr.args {
for arg in &expr.arguments.args {
let mut visitor = NameVisitor::default();
visitor.visit_expr(arg);
names.extend(visitor.names);

View file

@ -64,8 +64,7 @@ where
}
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: ast::Arguments { args, keywords, .. },
..
}) => {
// Allow `tuple()`, `list()`, and their generic forms, like `list[int]()`.

View file

@ -339,6 +339,27 @@ impl<'a> From<&'a ast::Constant> for ComparableConstant<'a> {
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableArguments<'a> {
args: Vec<ComparableExpr<'a>>,
keywords: Vec<ComparableKeyword<'a>>,
}
impl<'a> From<&'a ast::Arguments> for ComparableArguments<'a> {
fn from(arguments: &'a ast::Arguments) -> Self {
Self {
args: arguments.args.iter().map(Into::into).collect(),
keywords: arguments.keywords.iter().map(Into::into).collect(),
}
}
}
impl<'a> From<&'a Box<ast::Arguments>> for ComparableArguments<'a> {
fn from(arguments: &'a Box<ast::Arguments>) -> Self {
(arguments.as_ref()).into()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableParameters<'a> {
posonlyargs: Vec<ComparableParameterWithDefault<'a>>,
@ -583,8 +604,7 @@ pub struct ExprCompare<'a> {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprCall<'a> {
func: Box<ComparableExpr<'a>>,
args: Vec<ComparableExpr<'a>>,
keywords: Vec<ComparableKeyword<'a>>,
arguments: ComparableArguments<'a>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
@ -837,13 +857,11 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
}),
ast::Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments,
range: _range,
}) => Self::Call(ExprCall {
func: func.into(),
args: args.iter().map(Into::into).collect(),
keywords: keywords.iter().map(Into::into).collect(),
arguments: arguments.into(),
}),
ast::Expr::FormattedValue(ast::ExprFormattedValue {
value,
@ -968,8 +986,7 @@ pub struct StmtAsyncFunctionDef<'a> {
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct StmtClassDef<'a> {
name: &'a str,
bases: Vec<ComparableExpr<'a>>,
keywords: Vec<ComparableKeyword<'a>>,
arguments: Option<ComparableArguments<'a>>,
body: Vec<ComparableStmt<'a>>,
decorator_list: Vec<ComparableDecorator<'a>>,
type_params: Vec<ComparableTypeParam<'a>>,
@ -1240,16 +1257,14 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
}),
ast::Stmt::ClassDef(ast::StmtClassDef {
name,
bases,
keywords,
arguments,
body,
decorator_list,
type_params,
range: _range,
}) => Self::ClassDef(StmtClassDef {
name: name.as_str(),
bases: bases.iter().map(Into::into).collect(),
keywords: keywords.iter().map(Into::into).collect(),
arguments: arguments.as_ref().map(Into::into),
body: body.iter().map(Into::into).collect(),
decorator_list: decorator_list.iter().map(Into::into).collect(),
type_params: type_params.iter().map(Into::into).collect(),

View file

@ -2,8 +2,8 @@ use std::borrow::Cow;
use std::path::Path;
use crate::{
self as ast, Constant, ExceptHandler, Expr, Keyword, MatchCase, Parameters, Pattern, Ranged,
Stmt, TypeParam,
self as ast, Arguments, Constant, ExceptHandler, Expr, Keyword, MatchCase, Parameters, Pattern,
Ranged, Stmt, TypeParam,
};
use num_traits::Zero;
use ruff_text_size::TextRange;
@ -50,8 +50,7 @@ where
// Accept empty initializers.
if let Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _range,
}) = expr
{
@ -237,8 +236,7 @@ where
}) => any_over_expr(left, func) || comparators.iter().any(|expr| any_over_expr(expr, func)),
Expr::Call(ast::ExprCall {
func: call_func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range: _range,
}) => {
any_over_expr(call_func, func)
@ -396,16 +394,19 @@ where
.is_some_and(|value| any_over_expr(value, func))
}
Stmt::ClassDef(ast::StmtClassDef {
bases,
keywords,
arguments,
body,
decorator_list,
..
}) => {
bases.iter().any(|expr| any_over_expr(expr, func))
|| keywords
.iter()
.any(|keyword| any_over_expr(&keyword.value, func))
arguments
.as_ref()
.is_some_and(|Arguments { args, keywords, .. }| {
args.iter().any(|expr| any_over_expr(expr, func))
|| keywords
.iter()
.any(|keyword| any_over_expr(&keyword.value, func))
})
|| body.iter().any(|stmt| any_over_stmt(stmt, func))
|| decorator_list
.iter()
@ -640,6 +641,8 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool {
/// Return the [`Keyword`] with the given name, if it's present in the list of
/// [`Keyword`] arguments.
///
/// TODO(charlie): Make this an associated function on [`Arguments`].
pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> {
keywords.iter().find(|keyword| {
let Keyword { arg, .. } = keyword;
@ -1229,30 +1232,14 @@ impl Truthiness {
None
}
}
Expr::List(ast::ExprList {
elts,
range: _range,
..
})
| Expr::Set(ast::ExprSet {
elts,
range: _range,
})
| Expr::Tuple(ast::ExprTuple {
elts,
range: _range,
..
}) => Some(!elts.is_empty()),
Expr::Dict(ast::ExprDict {
keys,
range: _range,
..
}) => Some(!keys.is_empty()),
Expr::List(ast::ExprList { elts, .. })
| Expr::Set(ast::ExprSet { elts, .. })
| Expr::Tuple(ast::ExprTuple { elts, .. }) => Some(!elts.is_empty()),
Expr::Dict(ast::ExprDict { keys, .. }) => Some(!keys.is_empty()),
Expr::Call(ast::ExprCall {
func,
args,
keywords,
range: _range,
arguments: Arguments { args, keywords, .. },
..
}) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {

View file

@ -1,6 +1,6 @@
use crate::{
self as ast, Alias, Comprehension, Decorator, ExceptHandler, Expr, Keyword, MatchCase, Mod,
Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt, TypeParam,
self as ast, Alias, Arguments, Comprehension, Decorator, ExceptHandler, Expr, Keyword,
MatchCase, Mod, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt, TypeParam,
TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, WithItem,
};
use ruff_text_size::TextRange;
@ -90,6 +90,7 @@ pub enum AnyNode {
PatternMatchAs(ast::PatternMatchAs),
PatternMatchOr(ast::PatternMatchOr),
Comprehension(Comprehension),
Arguments(Arguments),
Parameters(Parameters),
Parameter(Parameter),
ParameterWithDefault(ParameterWithDefault),
@ -177,6 +178,7 @@ impl AnyNode {
| AnyNode::PatternMatchAs(_)
| AnyNode::PatternMatchOr(_)
| AnyNode::Comprehension(_)
| AnyNode::Arguments(_)
| AnyNode::Parameters(_)
| AnyNode::Parameter(_)
| AnyNode::ParameterWithDefault(_)
@ -264,6 +266,7 @@ impl AnyNode {
| AnyNode::PatternMatchAs(_)
| AnyNode::PatternMatchOr(_)
| AnyNode::Comprehension(_)
| AnyNode::Arguments(_)
| AnyNode::Parameters(_)
| AnyNode::Parameter(_)
| AnyNode::ParameterWithDefault(_)
@ -351,6 +354,7 @@ impl AnyNode {
| AnyNode::PatternMatchAs(_)
| AnyNode::PatternMatchOr(_)
| AnyNode::Comprehension(_)
| AnyNode::Arguments(_)
| AnyNode::Parameters(_)
| AnyNode::Parameter(_)
| AnyNode::ParameterWithDefault(_)
@ -438,6 +442,7 @@ impl AnyNode {
| AnyNode::ExprLineMagic(_)
| AnyNode::ExceptHandlerExceptHandler(_)
| AnyNode::Comprehension(_)
| AnyNode::Arguments(_)
| AnyNode::Parameters(_)
| AnyNode::Parameter(_)
| AnyNode::ParameterWithDefault(_)
@ -525,6 +530,7 @@ impl AnyNode {
| AnyNode::PatternMatchAs(_)
| AnyNode::PatternMatchOr(_)
| AnyNode::Comprehension(_)
| AnyNode::Arguments(_)
| AnyNode::Parameters(_)
| AnyNode::Parameter(_)
| AnyNode::ParameterWithDefault(_)
@ -631,6 +637,7 @@ impl AnyNode {
Self::PatternMatchAs(node) => AnyNodeRef::PatternMatchAs(node),
Self::PatternMatchOr(node) => AnyNodeRef::PatternMatchOr(node),
Self::Comprehension(node) => AnyNodeRef::Comprehension(node),
Self::Arguments(node) => AnyNodeRef::Arguments(node),
Self::Parameters(node) => AnyNodeRef::Parameters(node),
Self::Parameter(node) => AnyNodeRef::Parameter(node),
Self::ParameterWithDefault(node) => AnyNodeRef::ParameterWithDefault(node),
@ -3574,6 +3581,7 @@ impl Ranged for AnyNode {
AnyNode::PatternMatchAs(node) => node.range(),
AnyNode::PatternMatchOr(node) => node.range(),
AnyNode::Comprehension(node) => node.range(),
AnyNode::Arguments(node) => node.range(),
AnyNode::Parameters(node) => node.range(),
AnyNode::Parameter(node) => node.range(),
AnyNode::ParameterWithDefault(node) => node.range(),
@ -3661,6 +3669,7 @@ pub enum AnyNodeRef<'a> {
PatternMatchAs(&'a ast::PatternMatchAs),
PatternMatchOr(&'a ast::PatternMatchOr),
Comprehension(&'a Comprehension),
Arguments(&'a Arguments),
Parameters(&'a Parameters),
Parameter(&'a Parameter),
ParameterWithDefault(&'a ParameterWithDefault),
@ -3747,6 +3756,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::PatternMatchAs(node) => NonNull::from(*node).cast(),
AnyNodeRef::PatternMatchOr(node) => NonNull::from(*node).cast(),
AnyNodeRef::Comprehension(node) => NonNull::from(*node).cast(),
AnyNodeRef::Arguments(node) => NonNull::from(*node).cast(),
AnyNodeRef::Parameters(node) => NonNull::from(*node).cast(),
AnyNodeRef::Parameter(node) => NonNull::from(*node).cast(),
AnyNodeRef::ParameterWithDefault(node) => NonNull::from(*node).cast(),
@ -3839,6 +3849,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::PatternMatchAs(_) => NodeKind::PatternMatchAs,
AnyNodeRef::PatternMatchOr(_) => NodeKind::PatternMatchOr,
AnyNodeRef::Comprehension(_) => NodeKind::Comprehension,
AnyNodeRef::Arguments(_) => NodeKind::Arguments,
AnyNodeRef::Parameters(_) => NodeKind::Parameters,
AnyNodeRef::Parameter(_) => NodeKind::Parameter,
AnyNodeRef::ParameterWithDefault(_) => NodeKind::ParameterWithDefault,
@ -3926,6 +3937,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::PatternMatchAs(_)
| AnyNodeRef::PatternMatchOr(_)
| AnyNodeRef::Comprehension(_)
| AnyNodeRef::Arguments(_)
| AnyNodeRef::Parameters(_)
| AnyNodeRef::Parameter(_)
| AnyNodeRef::ParameterWithDefault(_)
@ -4013,6 +4025,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::PatternMatchAs(_)
| AnyNodeRef::PatternMatchOr(_)
| AnyNodeRef::Comprehension(_)
| AnyNodeRef::Arguments(_)
| AnyNodeRef::Parameters(_)
| AnyNodeRef::Parameter(_)
| AnyNodeRef::ParameterWithDefault(_)
@ -4099,6 +4112,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::PatternMatchAs(_)
| AnyNodeRef::PatternMatchOr(_)
| AnyNodeRef::Comprehension(_)
| AnyNodeRef::Arguments(_)
| AnyNodeRef::Parameters(_)
| AnyNodeRef::Parameter(_)
| AnyNodeRef::ParameterWithDefault(_)
@ -4186,6 +4200,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::ExprLineMagic(_)
| AnyNodeRef::ExceptHandlerExceptHandler(_)
| AnyNodeRef::Comprehension(_)
| AnyNodeRef::Arguments(_)
| AnyNodeRef::Parameters(_)
| AnyNodeRef::Parameter(_)
| AnyNodeRef::ParameterWithDefault(_)
@ -4273,6 +4288,7 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::PatternMatchAs(_)
| AnyNodeRef::PatternMatchOr(_)
| AnyNodeRef::Comprehension(_)
| AnyNodeRef::Arguments(_)
| AnyNodeRef::Parameters(_)
| AnyNodeRef::Parameter(_)
| AnyNodeRef::ParameterWithDefault(_)
@ -4985,6 +5001,7 @@ impl Ranged for AnyNodeRef<'_> {
AnyNodeRef::PatternMatchAs(node) => node.range(),
AnyNodeRef::PatternMatchOr(node) => node.range(),
AnyNodeRef::Comprehension(node) => node.range(),
AnyNodeRef::Arguments(node) => node.range(),
AnyNodeRef::Parameters(node) => node.range(),
AnyNodeRef::Parameter(node) => node.range(),
AnyNodeRef::ParameterWithDefault(node) => node.range(),
@ -5075,6 +5092,7 @@ pub enum NodeKind {
PatternMatchOr,
TypeIgnoreTypeIgnore,
Comprehension,
Arguments,
Parameters,
Parameter,
ParameterWithDefault,

View file

@ -158,13 +158,32 @@ impl From<StmtAsyncFunctionDef> for Stmt {
pub struct StmtClassDef {
pub range: TextRange,
pub name: Identifier,
pub bases: Vec<Expr>,
pub keywords: Vec<Keyword>,
pub arguments: Option<Arguments>,
pub body: Vec<Stmt>,
pub type_params: Vec<TypeParam>,
pub decorator_list: Vec<Decorator>,
}
impl StmtClassDef {
/// Return an iterator over the bases of the class.
pub fn bases(&self) -> impl Iterator<Item = &Expr> {
self.arguments
.as_ref()
.map(|arguments| &arguments.args)
.into_iter()
.flatten()
}
/// Return an iterator over the metaclass keywords of the class.
pub fn keywords(&self) -> impl Iterator<Item = &Keyword> {
self.arguments
.as_ref()
.map(|arguments| &arguments.keywords)
.into_iter()
.flatten()
}
}
impl From<StmtClassDef> for Stmt {
fn from(payload: StmtClassDef) -> Self {
Stmt::ClassDef(payload)
@ -836,8 +855,7 @@ impl From<ExprCompare> for Expr {
pub struct ExprCall {
pub range: TextRange,
pub func: Box<Expr>,
pub args: Vec<Expr>,
pub keywords: Vec<Keyword>,
pub arguments: Arguments,
}
impl From<ExprCall> for Expr {
@ -2073,6 +2091,35 @@ pub struct ParameterWithDefault {
pub default: Option<Box<Expr>>,
}
/// An AST node used to represent the arguments passed to a function call or class definition.
///
/// For example, given:
/// ```python
/// foo(1, 2, 3, bar=4, baz=5)
/// ```
/// The `Arguments` node would span from the left to right parentheses (inclusive), and contain
/// the arguments and keyword arguments in the order they appear in the source code.
///
/// Similarly, given:
/// ```python
/// class Foo(Bar, baz=1, qux=2):
/// pass
/// ```
/// The `Arguments` node would again span from the left to right parentheses (inclusive), and
/// contain the `Bar` argument and the `baz` and `qux` keyword arguments in the order they
/// appear in the source code.
///
/// In the context of a class definition, the Python-style AST refers to the arguments as `bases`,
/// as they represent the "explicitly specified base classes", while the keyword arguments are
/// typically used for `metaclass`, with any additional arguments being passed to the `metaclass`.
#[derive(Clone, Debug, PartialEq)]
pub struct Arguments {
pub range: TextRange,
pub args: Vec<Expr>,
pub keywords: Vec<Keyword>,
}
pub type Suite = Vec<Stmt>;
impl CmpOp {
@ -2934,6 +2981,11 @@ impl Ranged for crate::nodes::Decorator {
self.range
}
}
impl Ranged for crate::nodes::Arguments {
fn range(&self) -> TextRange {
self.range
}
}
impl Ranged for crate::nodes::Parameters {
fn range(&self) -> TextRange {
self.range
@ -2951,9 +3003,9 @@ mod size_assertions {
use super::*;
use static_assertions::assert_eq_size;
assert_eq_size!(Stmt, [u8; 168]);
assert_eq_size!(Stmt, [u8; 176]);
assert_eq_size!(StmtFunctionDef, [u8; 128]);
assert_eq_size!(StmtClassDef, [u8; 160]);
assert_eq_size!(StmtClassDef, [u8; 168]);
assert_eq_size!(StmtTry, [u8; 104]);
assert_eq_size!(Expr, [u8; 80]);
assert_eq_size!(Constant, [u8; 32]);

View file

@ -1,4 +1,4 @@
use crate::{nodes, Expr, Keyword};
use crate::{nodes, Arguments, Expr, Keyword};
use ruff_text_size::TextRange;
fn relocate_keyword(keyword: &mut Keyword, location: TextRange) {
@ -116,8 +116,7 @@ pub fn relocate_expr(expr: &mut Expr, location: TextRange) {
}
Expr::Call(nodes::ExprCall {
func,
args,
keywords,
arguments: Arguments { args, keywords, .. },
range,
}) => {
*range = location;

View file

@ -3,9 +3,9 @@
pub mod preorder;
use crate::{
self as ast, Alias, BoolOp, CmpOp, Comprehension, Decorator, ElifElseClause, ExceptHandler,
Expr, ExprContext, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern, Stmt,
TypeParam, TypeParamTypeVar, UnaryOp, WithItem,
self as ast, Alias, Arguments, BoolOp, CmpOp, Comprehension, Decorator, ElifElseClause,
ExceptHandler, Expr, ExprContext, Keyword, MatchCase, Operator, Parameter, Parameters, Pattern,
Stmt, TypeParam, TypeParamTypeVar, UnaryOp, WithItem,
};
/// A trait for AST visitors. Visits all nodes in the AST recursively in evaluation-order.
@ -52,6 +52,9 @@ pub trait Visitor<'a> {
fn visit_format_spec(&mut self, format_spec: &'a Expr) {
walk_format_spec(self, format_spec);
}
fn visit_arguments(&mut self, arguments: &'a Arguments) {
walk_arguments(self, arguments);
}
fn visit_parameters(&mut self, parameters: &'a Parameters) {
walk_parameters(self, parameters);
}
@ -143,8 +146,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_body(body);
}
Stmt::ClassDef(ast::StmtClassDef {
bases,
keywords,
arguments,
body,
decorator_list,
type_params,
@ -156,11 +158,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
for type_param in type_params {
visitor.visit_type_param(type_param);
}
for expr in bases {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
if let Some(arguments) = arguments {
visitor.visit_arguments(arguments);
}
visitor.visit_body(body);
}
@ -522,17 +521,11 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
}
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments,
range: _range,
}) => {
visitor.visit_expr(func);
for expr in args {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
}
visitor.visit_arguments(arguments);
}
Expr::FormattedValue(ast::ExprFormattedValue {
value, format_spec, ..
@ -645,6 +638,15 @@ pub fn walk_format_spec<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, format_spe
visitor.visit_expr(format_spec);
}
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
for arg in &arguments.args {
visitor.visit_expr(arg);
}
for keyword in &arguments.keywords {
visitor.visit_keyword(keyword);
}
}
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 {

View file

@ -1,7 +1,8 @@
use crate::{
self as ast, Alias, BoolOp, CmpOp, Comprehension, Constant, Decorator, ElifElseClause,
ExceptHandler, Expr, Keyword, MatchCase, Mod, Operator, Parameter, ParameterWithDefault,
Parameters, Pattern, Stmt, TypeParam, TypeParamTypeVar, UnaryOp, WithItem,
self as ast, Alias, Arguments, BoolOp, CmpOp, Comprehension, Constant, Decorator,
ElifElseClause, ExceptHandler, Expr, Keyword, MatchCase, Mod, Operator, Parameter,
ParameterWithDefault, Parameters, Pattern, Stmt, TypeParam, TypeParamTypeVar, UnaryOp,
WithItem,
};
/// Visitor that traverses all nodes recursively in pre-order.
@ -56,6 +57,10 @@ pub trait PreorderVisitor<'a> {
walk_format_spec(self, format_spec);
}
fn visit_arguments(&mut self, arguments: &'a Arguments) {
walk_arguments(self, arguments);
}
fn visit_parameters(&mut self, parameters: &'a Parameters) {
walk_parameters(self, parameters);
}
@ -166,8 +171,7 @@ where
}
Stmt::ClassDef(ast::StmtClassDef {
bases,
keywords,
arguments,
body,
decorator_list,
type_params,
@ -181,12 +185,8 @@ where
visitor.visit_type_param(type_param);
}
for expr in bases {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
if let Some(arguments) = arguments {
visitor.visit_arguments(arguments);
}
visitor.visit_body(body);
@ -592,17 +592,11 @@ where
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments,
range: _range,
}) => {
visitor.visit_expr(func);
for expr in args {
visitor.visit_expr(expr);
}
for keyword in keywords {
visitor.visit_keyword(keyword);
}
visitor.visit_arguments(arguments);
}
Expr::FormattedValue(ast::ExprFormattedValue {
@ -749,6 +743,19 @@ pub fn walk_format_spec<'a, V: PreorderVisitor<'a> + ?Sized>(
visitor.visit_expr(format_spec);
}
pub fn walk_arguments<'a, V>(visitor: &mut V, arguments: &'a Arguments)
where
V: PreorderVisitor<'a> + ?Sized,
{
for arg in &arguments.args {
visitor.visit_expr(arg);
}
for keyword in &arguments.keywords {
visitor.visit_keyword(keyword);
}
}
pub fn walk_parameters<'a, V>(visitor: &mut V, parameters: &'a Parameters)
where
V: PreorderVisitor<'a> + ?Sized,

View file

@ -273,8 +273,7 @@ impl<'a> Generator<'a> {
}
Stmt::ClassDef(ast::StmtClassDef {
name,
bases,
keywords,
arguments,
body,
decorator_list,
type_params,
@ -291,24 +290,25 @@ impl<'a> Generator<'a> {
self.p("class ");
self.p_id(name);
self.unparse_type_params(type_params);
let mut first = true;
for base in bases {
self.p_if(first, "(");
self.p_delim(&mut first, ", ");
self.unparse_expr(base, precedence::MAX);
}
for keyword in keywords {
self.p_if(first, "(");
self.p_delim(&mut first, ", ");
if let Some(arg) = &keyword.arg {
self.p_id(arg);
self.p("=");
} else {
self.p("**");
if let Some(arguments) = arguments {
self.p("(");
let mut first = true;
for base in &arguments.args {
self.p_delim(&mut first, ", ");
self.unparse_expr(base, precedence::MAX);
}
self.unparse_expr(&keyword.value, precedence::MAX);
for keyword in &arguments.keywords {
self.p_delim(&mut first, ", ");
if let Some(arg) = &keyword.arg {
self.p_id(arg);
self.p("=");
} else {
self.p("**");
}
self.unparse_expr(&keyword.value, precedence::MAX);
}
self.p(")");
}
self.p_if(!first, ")");
self.p(":");
});
self.body(body);
@ -1149,8 +1149,7 @@ impl<'a> Generator<'a> {
}
Expr::Call(ast::ExprCall {
func,
args,
keywords,
arguments,
range: _range,
}) => {
self.unparse_expr(func, precedence::MAX);
@ -1162,18 +1161,18 @@ impl<'a> Generator<'a> {
range: _range,
})],
[],
) = (args.as_slice(), keywords.as_slice())
) = (arguments.args.as_slice(), arguments.keywords.as_slice())
{
// Ensure that a single generator doesn't get double-parenthesized.
self.unparse_expr(elt, precedence::COMMA);
self.unparse_comp(generators);
} else {
let mut first = true;
for arg in args {
for arg in &arguments.args {
self.p_delim(&mut first, ", ");
self.unparse_expr(arg, precedence::COMMA);
}
for kw in keywords {
for kw in &arguments.keywords {
self.p_delim(&mut first, ", ");
if let Some(arg) = &kw.arg {
self.p_id(arg);

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Expr, ExprCall, Ranged};
use ruff_python_ast::{Arguments, Expr, ExprCall, Ranged};
use ruff_text_size::{TextRange, TextSize};
use crate::builders::empty_parenthesized_with_dangling_comments;
@ -21,8 +21,12 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
let ExprCall {
range: _,
func,
args,
keywords,
arguments:
Arguments {
args,
keywords,
range: _,
},
} = item;
// We have a case with `f()` without any argument, which is a special case because we can

View file

@ -356,8 +356,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
Expr::Call(ast::ExprCall {
range: _,
func,
args: _,
keywords: _,
arguments: _,
}) => {
self.any_parenthesized_expressions = true;
// Only walk the function, the arguments are always parenthesized

View file

@ -1,4 +1,4 @@
use ruff_python_ast::{Ranged, StmtClassDef};
use ruff_python_ast::{Arguments, Ranged, StmtClassDef};
use ruff_text_size::TextRange;
use ruff_formatter::write;
@ -17,8 +17,7 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
let StmtClassDef {
range: _,
name,
bases,
keywords,
arguments,
body,
type_params: _,
decorator_list,
@ -34,7 +33,12 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
write!(f, [text("class"), space(), name.format()])?;
if !(bases.is_empty() && keywords.is_empty()) {
if arguments
.as_ref()
.is_some_and(|Arguments { args, keywords, .. }| {
!(args.is_empty() && keywords.is_empty())
})
{
parenthesized(
"(",
&FormatInheritanceClause {
@ -75,12 +79,19 @@ struct FormatInheritanceClause<'a> {
impl Format<PyFormatContext<'_>> for FormatInheritanceClause<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let StmtClassDef {
bases,
keywords,
arguments:
Some(Arguments {
args: bases,
keywords,
..
}),
name,
body,
..
} = self.class_definition;
} = self.class_definition
else {
return Ok(());
};
let source = f.context().source();

View file

@ -78,14 +78,18 @@ type FunctionArgument = (
);
// Parse arguments as supplied during a function/lambda *call*.
pub(crate) fn parse_args(func_args: Vec<FunctionArgument>) -> Result<ArgumentList, LexicalError> {
pub(crate) fn parse_arguments(
function_arguments: Vec<FunctionArgument>,
) -> Result<ArgumentList, LexicalError> {
let mut args = vec![];
let mut keywords = vec![];
let mut keyword_names =
FxHashSet::with_capacity_and_hasher(func_args.len(), BuildHasherDefault::default());
let mut keyword_names = FxHashSet::with_capacity_and_hasher(
function_arguments.len(),
BuildHasherDefault::default(),
);
let mut double_starred = false;
for (name, value) in func_args {
for (name, value) in function_arguments {
if let Some((start, end, name)) = name {
// Check for duplicate keyword arguments in the call.
if let Some(keyword_name) = &name {

View file

@ -9,7 +9,7 @@ use ruff_python_ast::{self as ast, Ranged, MagicKind};
use crate::{
Mode,
lexer::{LexicalError, LexicalErrorType},
function::{ArgumentList, parse_args, validate_pos_params, validate_arguments},
function::{ArgumentList, parse_arguments, validate_pos_params, validate_arguments},
context::set_context,
string::parse_strings,
token::{self, StringKind},
@ -1194,17 +1194,12 @@ KwargParameter<ParameterType>: Option<Box<ast::Parameter>> = {
};
ClassDef: ast::Stmt = {
<location:@L> <decorator_list:Decorator*> "class" <name:Identifier> <type_params:TypeParamList?> <a:("(" ArgumentList ")")?> ":" <body:Suite> => {
let (bases, keywords) = match a {
Some((_, arg, _)) => (arg.args, arg.keywords),
None => (vec![], vec![]),
};
<location:@L> <decorator_list:Decorator*> "class" <name:Identifier> <type_params:TypeParamList?> <arguments:Arguments?> ":" <body:Suite> => {
let end_location = body.last().unwrap().end();
ast::Stmt::ClassDef(
ast::StmtClassDef {
name,
bases,
keywords,
arguments,
body,
decorator_list,
type_params: type_params.unwrap_or_default(),
@ -1444,9 +1439,9 @@ AtomExpr<Goal>: ast::Expr = {
AtomExpr2<Goal>: ast::Expr = {
Atom<Goal>,
<location:@L> <f:AtomExpr2<"all">> "(" <a:ArgumentList> ")" <end_location:@R> => {
<location:@L> <f:AtomExpr2<"all">> <arguments:Arguments> <end_location:@R> => {
ast::Expr::Call(
ast::ExprCall { func: Box::new(f), args: a.args, keywords: a.keywords, range: (location..end_location).into() }
ast::ExprCall { func: Box::new(f), arguments, range: (location..end_location).into() }
)
},
<location:@L> <e:AtomExpr2<"all">> "[" <s:SubscriptList> "]" <end_location:@R> => ast::Expr::Subscript(
@ -1663,10 +1658,14 @@ SingleForComprehension: ast::Comprehension = {
ExpressionNoCond: ast::Expr = OrTest<"all">;
ComprehensionIf: ast::Expr = "if" <c:ExpressionNoCond> => c;
ArgumentList: ArgumentList = {
<e: Comma<FunctionArgument>> =>? {
let arg_list = parse_args(e)?;
Ok(arg_list)
Arguments: ast::Arguments = {
<location:@L> "(" <e: Comma<FunctionArgument>> ")" <end_location:@R> =>? {
let ArgumentList { args, keywords } = parse_arguments(e)?;
Ok(ast::Arguments {
args,
keywords,
range: (location..end_location).into()
})
}
};

File diff suppressed because it is too large Load diff

View file

@ -48,8 +48,7 @@ expression: parse_ast
id: "Abcd",
range: 59..63,
},
bases: [],
keywords: [],
arguments: None,
body: [
Pass(
StmtPass {

View file

@ -24,124 +24,127 @@ Call(
ctx: Load,
},
),
args: [
GeneratorExp(
ExprGeneratorExp {
range: 14..139,
elt: Name(
ExprName {
range: 14..17,
id: "sql",
ctx: Load,
},
),
generators: [
Comprehension {
range: 22..139,
target: Name(
ExprName {
range: 26..29,
id: "sql",
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 33..139,
elts: [
IfExp(
ExprIfExp {
range: 43..80,
test: Name(
ExprName {
range: 65..70,
id: "limit",
ctx: Load,
},
),
body: BinOp(
ExprBinOp {
range: 43..61,
left: Constant(
ExprConstant {
range: 43..53,
value: Str(
"LIMIT %d",
),
kind: None,
},
),
op: Mod,
right: Name(
ExprName {
range: 56..61,
id: "limit",
ctx: Load,
},
),
},
),
orelse: Constant(
ExprConstant {
range: 76..80,
value: None,
kind: None,
},
),
},
),
IfExp(
ExprIfExp {
range: 90..132,
test: Name(
ExprName {
range: 116..122,
id: "offset",
ctx: Load,
},
),
body: BinOp(
ExprBinOp {
range: 91..111,
left: Constant(
ExprConstant {
range: 91..102,
value: Str(
"OFFSET %d",
),
kind: None,
},
),
op: Mod,
right: Name(
ExprName {
range: 105..111,
id: "offset",
ctx: Load,
},
),
},
),
orelse: Constant(
ExprConstant {
range: 128..132,
value: None,
kind: None,
},
),
},
),
],
ctx: Load,
},
),
ifs: [],
is_async: false,
},
],
},
),
],
keywords: [],
arguments: Arguments {
range: 8..141,
args: [
GeneratorExp(
ExprGeneratorExp {
range: 14..139,
elt: Name(
ExprName {
range: 14..17,
id: "sql",
ctx: Load,
},
),
generators: [
Comprehension {
range: 22..139,
target: Name(
ExprName {
range: 26..29,
id: "sql",
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 33..139,
elts: [
IfExp(
ExprIfExp {
range: 43..80,
test: Name(
ExprName {
range: 65..70,
id: "limit",
ctx: Load,
},
),
body: BinOp(
ExprBinOp {
range: 43..61,
left: Constant(
ExprConstant {
range: 43..53,
value: Str(
"LIMIT %d",
),
kind: None,
},
),
op: Mod,
right: Name(
ExprName {
range: 56..61,
id: "limit",
ctx: Load,
},
),
},
),
orelse: Constant(
ExprConstant {
range: 76..80,
value: None,
kind: None,
},
),
},
),
IfExp(
ExprIfExp {
range: 90..132,
test: Name(
ExprName {
range: 116..122,
id: "offset",
ctx: Load,
},
),
body: BinOp(
ExprBinOp {
range: 91..111,
left: Constant(
ExprConstant {
range: 91..102,
value: Str(
"OFFSET %d",
),
kind: None,
},
),
op: Mod,
right: Name(
ExprName {
range: 105..111,
id: "offset",
ctx: Load,
},
),
},
),
orelse: Constant(
ExprConstant {
range: 128..132,
value: None,
kind: None,
},
),
},
),
],
ctx: Load,
},
),
ifs: [],
is_async: false,
},
],
},
),
],
keywords: [],
},
},
)

View file

@ -216,18 +216,21 @@ Module(
ctx: Load,
},
),
args: [
Constant(
ExprConstant {
range: 716..717,
value: Int(
5,
),
kind: None,
},
),
],
keywords: [],
arguments: Arguments {
range: 715..718,
args: [
Constant(
ExprConstant {
range: 716..717,
value: Int(
5,
),
kind: None,
},
),
],
keywords: [],
},
},
),
body: [

View file

@ -66,16 +66,19 @@ expression: parse_ast
ctx: Load,
},
),
args: [
Name(
ExprName {
range: 68..72,
id: "rest",
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 67..73,
args: [
Name(
ExprName {
range: 68..72,
id: "rest",
ctx: Load,
},
),
],
keywords: [],
},
},
),
},
@ -195,16 +198,19 @@ expression: parse_ast
ctx: Load,
},
),
args: [
Name(
ExprName {
range: 171..176,
id: "label",
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 170..177,
args: [
Name(
ExprName {
range: 171..176,
id: "label",
ctx: Load,
},
),
],
keywords: [],
},
},
),
},

View file

@ -122,42 +122,45 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
Starred(
ExprStarred {
range: 93..99,
value: BinOp(
ExprBinOp {
range: 94..99,
left: Name(
ExprName {
range: 94..95,
id: "a",
ctx: Load,
},
),
op: Add,
right: Name(
ExprName {
range: 98..99,
id: "b",
ctx: Load,
},
),
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 101..102,
id: "c",
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 92..103,
args: [
Starred(
ExprStarred {
range: 93..99,
value: BinOp(
ExprBinOp {
range: 94..99,
left: Name(
ExprName {
range: 94..95,
id: "a",
ctx: Load,
},
),
op: Add,
right: Name(
ExprName {
range: 98..99,
id: "b",
ctx: Load,
},
),
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 101..102,
id: "c",
ctx: Load,
},
),
],
keywords: [],
},
},
),
},
@ -283,22 +286,25 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
UnaryOp(
ExprUnaryOp {
range: 225..227,
op: USub,
operand: Name(
ExprName {
range: 226..227,
id: "a",
ctx: Load,
},
),
},
),
],
keywords: [],
arguments: Arguments {
range: 224..228,
args: [
UnaryOp(
ExprUnaryOp {
range: 225..227,
op: USub,
operand: Name(
ExprName {
range: 226..227,
id: "a",
ctx: Load,
},
),
},
),
],
keywords: [],
},
},
),
op: Mult,
@ -339,8 +345,11 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 270..272,
args: [],
keywords: [],
},
},
),
attr: Identifier {
@ -368,16 +377,19 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
Tuple(
ExprTuple {
range: 298..300,
elts: [],
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 297..301,
args: [
Tuple(
ExprTuple {
range: 298..300,
elts: [],
ctx: Load,
},
),
],
keywords: [],
},
},
),
attr: Identifier {
@ -405,16 +417,19 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
Tuple(
ExprTuple {
range: 329..331,
elts: [],
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 328..333,
args: [
Tuple(
ExprTuple {
range: 329..331,
elts: [],
ctx: Load,
},
),
],
keywords: [],
},
},
),
attr: Identifier {
@ -563,8 +578,11 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 476..478,
args: [],
keywords: [],
},
},
),
slice: Slice(
@ -771,33 +789,39 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
Call(
ExprCall {
range: 626..635,
func: Name(
ExprName {
range: 626..631,
id: "match",
ctx: Load,
},
),
args: [
Constant(
ExprConstant {
range: 632..634,
value: Int(
12,
),
kind: None,
arguments: Arguments {
range: 625..636,
args: [
Call(
ExprCall {
range: 626..635,
func: Name(
ExprName {
range: 626..631,
id: "match",
ctx: Load,
},
),
],
keywords: [],
},
),
],
keywords: [],
arguments: Arguments {
range: 631..635,
args: [
Constant(
ExprConstant {
range: 632..634,
value: Int(
12,
),
kind: None,
},
),
],
keywords: [],
},
},
),
],
keywords: [],
},
},
),
},

View file

@ -1,6 +1,6 @@
---
source: crates/ruff_python_parser/src/parser.rs
expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
expression: "parse_suite(source, \"<test>\").unwrap()"
---
[
Assign(
@ -37,8 +37,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 17..19,
args: [],
keywords: [],
},
},
),
},
@ -176,8 +179,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 88..90,
args: [],
keywords: [],
},
},
),
},
@ -249,8 +255,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 165..167,
args: [],
keywords: [],
},
},
),
},
@ -324,35 +333,41 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [
Call(
ExprCall {
range: 221..240,
func: Attribute(
ExprAttribute {
range: 221..238,
value: Constant(
ExprConstant {
range: 221..227,
value: Int(
11,
),
kind: None,
arguments: Arguments {
range: 220..241,
args: [
Call(
ExprCall {
range: 221..240,
func: Attribute(
ExprAttribute {
range: 221..238,
value: Constant(
ExprConstant {
range: 221..227,
value: Int(
11,
),
kind: None,
},
),
attr: Identifier {
id: "bit_length",
range: 228..238,
},
),
attr: Identifier {
id: "bit_length",
range: 228..238,
ctx: Load,
},
ctx: Load,
),
arguments: Arguments {
range: 238..240,
args: [],
keywords: [],
},
),
args: [],
keywords: [],
},
),
],
keywords: [],
},
),
],
keywords: [],
},
},
),
},
@ -391,8 +406,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 263..265,
args: [],
keywords: [],
},
},
),
},
@ -431,8 +449,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 287..289,
args: [],
keywords: [],
},
},
),
},
@ -504,8 +525,11 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
ctx: Load,
},
),
args: [],
keywords: [],
arguments: Arguments {
range: 327..329,
args: [],
keywords: [],
},
},
),
},
@ -639,16 +663,19 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
kind: None,
},
),
args: [
Name(
ExprName {
range: 388..390,
id: "no",
ctx: Load,
},
),
],
keywords: [],
arguments: Arguments {
range: 387..391,
args: [
Name(
ExprName {
range: 388..390,
id: "no",
ctx: Load,
},
),
],
keywords: [],
},
},
),
},

View file

@ -10,23 +10,28 @@ expression: "parse_suite(source, \"<test>\").unwrap()"
id: "Foo",
range: 6..9,
},
bases: [
Name(
ExprName {
range: 10..11,
id: "A",
ctx: Load,
},
),
Name(
ExprName {
range: 13..14,
id: "B",
ctx: Load,
},
),
],
keywords: [],
arguments: Some(
Arguments {
range: 9..15,
args: [
Name(
ExprName {
range: 10..11,
id: "A",
ctx: Load,
},
),
Name(
ExprName {
range: 13..14,
id: "B",
ctx: Load,
},
),
],
keywords: [],
},
),
body: [
FunctionDef(
StmtFunctionDef {

View file

@ -1,6 +1,6 @@
---
source: crates/ruff_python_parser/src/parser.rs
expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
expression: "parse_suite(source, \"<test>\").unwrap()"
---
[
ClassDef(
@ -10,8 +10,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 16..19,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 22..24,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -48,8 +53,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 58..61,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 69..71,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -94,8 +104,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 111..114,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 131..133,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -155,8 +170,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 165..168,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 174..176,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -203,8 +223,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 206..209,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 216..218,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -251,8 +276,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 246..249,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 254..256,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -288,8 +318,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 281..284,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 289..291,
args: [],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
@ -325,8 +360,13 @@ expression: "ast::Suite::parse(source, \"<test>\").unwrap()"
id: "Foo",
range: 318..321,
},
bases: [],
keywords: [],
arguments: Some(
Arguments {
range: 341..343,
args: [],
keywords: [],
},
),
body: [
Pass(
StmtPass {

View file

@ -16,37 +16,40 @@ expression: parse_ast
ctx: Load,
},
),
args: [
Constant(
ExprConstant {
range: 8..20,
value: Str(
"positional",
),
kind: None,
},
),
],
keywords: [
Keyword {
range: 22..31,
arg: Some(
Identifier {
id: "keyword",
range: 22..29,
},
),
value: Constant(
arguments: Arguments {
range: 7..32,
args: [
Constant(
ExprConstant {
range: 30..31,
value: Int(
2,
range: 8..20,
value: Str(
"positional",
),
kind: None,
},
),
},
],
],
keywords: [
Keyword {
range: 22..31,
arg: Some(
Identifier {
id: "keyword",
range: 22..29,
},
),
value: Constant(
ExprConstant {
range: 30..31,
value: Int(
2,
),
kind: None,
},
),
},
],
},
},
),
},

Some files were not shown because too many files have changed in this diff Show more