Reduce size of Expr from 80 to 64 bytes (#9900)

## Summary

This PR reduces the size of `Expr` from 80 to 64 bytes, by reducing the
sizes of...

- `ExprCall` from 72 to 56 bytes, by using boxed slices for `Arguments`.
- `ExprCompare` from 64 to 48 bytes, by using boxed slices for its
various vectors.

In testing, the parser gets a bit faster, and the linter benchmarks
improve quite a bit.
This commit is contained in:
Charlie Marsh 2024-02-08 18:53:13 -08:00 committed by GitHub
parent bd8123c0d8
commit 49fe1b85f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 326 additions and 258 deletions

View file

@ -31,8 +31,8 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, Arguments, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, Keyword,
MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
@ -989,12 +989,7 @@ where
}
Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
arguments,
range: _,
}) => {
self.visit_expr(func);
@ -1037,7 +1032,7 @@ where
});
match callable {
Some(typing::Callable::Bool) => {
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_boolean_test(arg);
}
@ -1046,7 +1041,7 @@ where
}
}
Some(typing::Callable::Cast) => {
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_type_definition(arg);
}
@ -1055,7 +1050,7 @@ where
}
}
Some(typing::Callable::NewType) => {
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_non_type_definition(arg);
}
@ -1064,21 +1059,21 @@ where
}
}
Some(typing::Callable::TypeVar) => {
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_non_type_definition(arg);
}
for arg in args {
self.visit_type_definition(arg);
}
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword {
arg,
value,
range: _,
} = keyword;
if let Some(id) = arg {
if id == "bound" {
if id.as_str() == "bound" {
self.visit_type_definition(value);
} else {
self.visit_non_type_definition(value);
@ -1088,7 +1083,7 @@ where
}
Some(typing::Callable::NamedTuple) => {
// Ex) NamedTuple("a", [("a", int)])
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_non_type_definition(arg);
}
@ -1117,7 +1112,7 @@ where
}
}
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword { arg, value, .. } = keyword;
match (arg.as_ref(), value) {
// Ex) NamedTuple("a", **{"a": int})
@ -1144,7 +1139,7 @@ where
}
Some(typing::Callable::TypedDict) => {
// Ex) TypedDict("a", {"a": int})
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_non_type_definition(arg);
}
@ -1167,13 +1162,13 @@ where
}
// Ex) TypedDict("a", a=int)
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword { value, .. } = keyword;
self.visit_type_definition(value);
}
}
Some(typing::Callable::MypyExtension) => {
let mut args = args.iter();
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
// Ex) DefaultNamedArg(bool | None, name="some_prop_name")
self.visit_type_definition(arg);
@ -1181,13 +1176,13 @@ where
for arg in args {
self.visit_non_type_definition(arg);
}
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword { value, .. } = keyword;
self.visit_non_type_definition(value);
}
} else {
// Ex) DefaultNamedArg(type="bool", name="some_prop_name")
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword {
value,
arg,
@ -1205,10 +1200,10 @@ where
// If we're in a type definition, we need to treat the arguments to any
// other callables as non-type definitions (i.e., we don't want to treat
// any strings as deferred type definitions).
for arg in args {
for arg in arguments.args.iter() {
self.visit_non_type_definition(arg);
}
for keyword in keywords {
for keyword in arguments.keywords.iter() {
let Keyword { value, .. } = keyword;
self.visit_non_type_definition(value);
}

View file

@ -59,11 +59,11 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
})),
arguments: Arguments {
args: if let Some(msg) = msg {
vec![msg.clone()]
Box::from([msg.clone()])
} else {
vec![]
Box::from([])
},
keywords: vec![],
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -91,7 +91,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
return;
}
let [arg] = arguments.args.as_slice() else {
let [arg] = &*arguments.args else {
return;
};

View file

@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Node;
use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprContext, Stmt};
use ruff_python_ast::{self as ast, Comprehension, Expr, ExprContext, Stmt};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -126,18 +126,13 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
match expr {
Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
arguments,
range: _,
}) => {
match func.as_ref() {
Expr::Name(ast::ExprName { id, .. }) => {
if matches!(id.as_str(), "filter" | "reduce" | "map") {
for arg in args {
for arg in arguments.args.iter() {
if arg.is_lambda_expr() {
self.safe_functions.push(arg);
}
@ -148,7 +143,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
if attr == "reduce" {
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
if id == "functools" {
for arg in args {
for arg in arguments.args.iter() {
if arg.is_lambda_expr() {
self.safe_functions.push(arg);
}
@ -160,7 +155,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
_ => {}
}
for keyword in keywords {
for keyword in arguments.keywords.iter() {
if keyword.arg.as_ref().is_some_and(|arg| arg == "key")
&& keyword.value.is_lambda_expr()
{

View file

@ -114,7 +114,7 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
}
// Ex) `iterools.repeat(1, times=None)`
for keyword in keywords {
for keyword in keywords.iter() {
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
if keyword.value.is_none_literal_expr() {
return true;

View file

@ -88,7 +88,7 @@ fn is_nullable_field<'a>(value: &'a Expr, semantic: &'a SemanticModel) -> Option
let mut null_key = false;
let mut blank_key = false;
let mut unique_key = false;
for keyword in &call.arguments.keywords {
for keyword in call.arguments.keywords.iter() {
let Some(argument) = &keyword.arg else {
continue;
};

View file

@ -113,7 +113,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
.resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "dict"]))
{
for keyword in keywords {
for keyword in keywords.iter() {
if let Some(attr) = &keyword.arg {
if is_reserved_attr(attr) {
checker.diagnostics.push(Diagnostic::new(

View file

@ -97,7 +97,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
continue;
}
let [arg] = args.as_slice() else {
let [arg] = &**args else {
continue;
};
@ -188,8 +188,8 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
arguments: Arguments {
args: vec![node],
keywords: vec![],
args: Box::from([node]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -59,7 +59,7 @@ impl Violation for UnnecessaryDictKwargs {
/// PIE804
pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCall) {
let mut duplicate_keywords = None;
for keyword in &call.arguments.keywords {
for keyword in call.arguments.keywords.iter() {
// keyword is a spread operator (indicated by None).
if keyword.arg.is_some() {
continue;
@ -145,7 +145,7 @@ fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> {
call.arguments.keywords.len(),
BuildHasherDefault::default(),
);
for keyword in &call.arguments.keywords {
for keyword in call.arguments.keywords.iter() {
if let Some(name) = &keyword.arg {
if !seen.insert(name.as_str()) {
duplicates.insert(name.as_str());

View file

@ -60,7 +60,7 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
}
// Verify that the call has exactly two arguments (no `step`).
let [start, _] = call.arguments.args.as_slice() else {
let [start, _] = &*call.arguments.args else {
return;
};

View file

@ -69,7 +69,7 @@ pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) {
return;
};
let ([op], [_right]) = (ops.as_slice(), comparators.as_slice()) else {
let ([op], [_right]) = (&**ops, &**comparators) else {
return;
};

View file

@ -101,7 +101,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
return;
};
let ([op], [right]) = (ops.as_slice(), comparators.as_slice()) else {
let ([op], [right]) = (&**ops, &**comparators) else {
return;
};

View file

@ -129,7 +129,7 @@ pub(crate) fn unrecognized_version_info(checker: &mut Checker, test: &Expr) {
return;
};
let ([op], [comparator]) = (ops.as_slice(), comparators.as_slice()) else {
let ([op], [comparator]) = (&**ops, &**comparators) else {
return;
};

View file

@ -411,7 +411,7 @@ fn to_pytest_raises_args<'a>(
) -> Option<Cow<'a, str>> {
let args = match attr {
"assertRaises" | "failUnlessRaises" => {
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
match (&*arguments.args, &*arguments.keywords) {
// Ex) `assertRaises(Exception)`
([arg], []) => Cow::Borrowed(checker.locator().slice(arg)),
// Ex) `assertRaises(expected_exception=Exception)`
@ -427,7 +427,7 @@ fn to_pytest_raises_args<'a>(
}
}
"assertRaisesRegex" | "assertRaisesRegexp" => {
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
match (&*arguments.args, &*arguments.keywords) {
// Ex) `assertRaisesRegex(Exception, regex)`
([arg1, arg2], []) => Cow::Owned(format!(
"{}, match={}",

View file

@ -638,17 +638,17 @@ pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Decorator]) {
}) = &decorator.expression
{
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
if let [names, ..] = args.as_slice() {
if let [names, ..] = &**args {
check_names(checker, decorator, names);
}
}
if checker.enabled(Rule::PytestParametrizeValuesWrongType) {
if let [names, values, ..] = args.as_slice() {
if let [names, values, ..] = &**args {
check_values(checker, names, values);
}
}
if checker.enabled(Rule::PytestDuplicateParametrizeTestCases) {
if let [_, values, ..] = args.as_slice() {
if let [_, values, ..] = &**args {
check_duplicates(checker, values);
}
}

View file

@ -173,8 +173,8 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt {
fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr {
Expr::Compare(ast::ExprCompare {
left: Box::new(left.clone()),
ops: vec![cmp_op],
comparators: vec![right.clone()],
ops: Box::from([cmp_op]),
comparators: Box::from([right.clone()]),
range: TextRange::default(),
})
}
@ -390,8 +390,8 @@ impl UnittestAssert {
let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![(**obj).clone(), (**cls).clone()],
keywords: vec![],
args: Box::from([(**obj).clone(), (**cls).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -434,8 +434,8 @@ impl UnittestAssert {
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
arguments: Arguments {
args: vec![(**regex).clone(), (**text).clone()],
keywords: vec![],
args: Box::from([(**regex).clone(), (**text).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -437,8 +437,8 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
arguments: Arguments {
args: vec![target.clone(), node.into()],
keywords: vec![],
args: Box::from([target.clone(), node.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -480,13 +480,13 @@ fn match_eq_target(expr: &Expr) -> Option<(&str, &Expr)> {
else {
return None;
};
if ops != &[CmpOp::Eq] {
if **ops != [CmpOp::Eq] {
return None;
}
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
return None;
};
let [comparator] = comparators.as_slice() else {
let [comparator] = &**comparators else {
return None;
};
if !comparator.is_name_expr() {
@ -551,8 +551,8 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
};
let node2 = ast::ExprCompare {
left: Box::new(node1.into()),
ops: vec![CmpOp::In],
comparators: vec![node.into()],
ops: Box::from([CmpOp::In]),
comparators: Box::from([node.into()]),
range: TextRange::default(),
};
let in_expr = node2.into();

View file

@ -185,8 +185,8 @@ pub(crate) fn if_expr_with_true_false(
.into(),
),
arguments: Arguments {
args: vec![test.clone()],
keywords: vec![],
args: Box::from([test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -176,7 +176,7 @@ pub(crate) fn negation_with_equal_op(
);
let node = ast::ExprCompare {
left: left.clone(),
ops: vec![CmpOp::NotEq],
ops: Box::from([CmpOp::NotEq]),
comparators: comparators.clone(),
range: TextRange::default(),
};
@ -206,7 +206,7 @@ pub(crate) fn negation_with_not_equal_op(
else {
return;
};
if !matches!(&ops[..], [CmpOp::NotEq]) {
if !matches!(&**ops, [CmpOp::NotEq]) {
return;
}
if is_exception_check(checker.semantic().current_statement()) {
@ -231,7 +231,7 @@ pub(crate) fn negation_with_not_equal_op(
);
let node = ast::ExprCompare {
left: left.clone(),
ops: vec![CmpOp::Eq],
ops: Box::from([CmpOp::Eq]),
comparators: comparators.clone(),
range: TextRange::default(),
};
@ -279,8 +279,8 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: UnaryOp, o
let node1 = ast::ExprCall {
func: Box::new(node.into()),
arguments: Arguments {
args: vec![*operand.clone()],
keywords: vec![],
args: Box::from([*operand.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -253,8 +253,7 @@ fn is_main_check(expr: &Expr) -> bool {
{
if let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() {
if id == "__name__" {
if let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] =
comparators.as_slice()
if let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = &**comparators
{
if value == "__main__" {
return true;

View file

@ -122,7 +122,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if:
else {
return;
};
let [test_dict] = test_dict.as_slice() else {
let [test_dict] = &**test_dict else {
return;
};
let (expected_var, expected_value, default_var, default_value) = match ops[..] {
@ -176,8 +176,8 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if:
let node3 = ast::ExprCall {
func: Box::new(node2.into()),
arguments: Arguments {
args: vec![node1, node],
keywords: vec![],
args: Box::from([node1, node]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -233,11 +233,11 @@ pub(crate) fn if_exp_instead_of_dict_get(
else {
return;
};
let [test_dict] = test_dict.as_slice() else {
let [test_dict] = &**test_dict else {
return;
};
let (body, default_value) = match ops.as_slice() {
let (body, default_value) = match &**ops {
[CmpOp::In] => (body, orelse),
[CmpOp::NotIn] => (orelse, body),
_ => {
@ -276,8 +276,8 @@ pub(crate) fn if_exp_instead_of_dict_get(
let fixed_node = ast::ExprCall {
func: Box::new(dict_get_node.into()),
arguments: Arguments {
args: vec![dict_key_node, default_value_node],
keywords: vec![],
args: Box::from([dict_key_node, default_value_node]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -64,10 +64,10 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &mut Checker, stmt_i
let Expr::Name(ast::ExprName { id: target, .. }) = left.as_ref() else {
return;
};
if ops != &[CmpOp::Eq] {
if **ops != [CmpOp::Eq] {
return;
}
let [expr] = comparators.as_slice() else {
let [expr] = &**comparators else {
return;
};
let Some(literal_expr) = expr.as_literal_expr() else {
@ -127,10 +127,10 @@ pub(crate) fn if_else_block_instead_of_dict_lookup(checker: &mut Checker, stmt_i
let Expr::Name(ast::ExprName { id, .. }) = left.as_ref() else {
return;
};
if id != target || ops != &[CmpOp::Eq] {
if id != target || **ops != [CmpOp::Eq] {
return;
}
let [expr] = comparators.as_slice() else {
let [expr] = &**comparators else {
return;
};
let Some(literal_expr) = expr.as_literal_expr() else {

View file

@ -194,7 +194,7 @@ pub(crate) fn key_in_dict_comprehension(checker: &mut Checker, comprehension: &C
/// SIM118 in a comparison.
pub(crate) fn key_in_dict_compare(checker: &mut Checker, compare: &ast::ExprCompare) {
let [op] = compare.ops.as_slice() else {
let [op] = &*compare.ops else {
return;
};
@ -202,7 +202,7 @@ pub(crate) fn key_in_dict_compare(checker: &mut Checker, compare: &ast::ExprComp
return;
}
let [right] = compare.comparators.as_slice() else {
let [right] = &*compare.comparators else {
return;
};

View file

@ -161,8 +161,8 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt_if: &ast::StmtIf) {
let value_node = ast::ExprCall {
func: Box::new(func_node.into()),
arguments: Arguments {
args: vec![if_test.clone()],
keywords: vec![],
args: Box::from([if_test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -140,7 +140,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
range: _,
}) = &loop_.test
{
if let ([op], [comparator]) = (ops.as_slice(), comparators.as_slice()) {
if let ([op], [comparator]) = (&**ops, &**comparators) {
let op = match op {
CmpOp::Eq => CmpOp::NotEq,
CmpOp::NotEq => CmpOp::Eq,
@ -155,8 +155,8 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
};
let node = ast::ExprCompare {
left: left.clone(),
ops: vec![op],
comparators: vec![comparator.clone()],
ops: Box::from([op]),
comparators: Box::from([comparator.clone()]),
range: TextRange::default(),
};
node.into()
@ -391,8 +391,8 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
arguments: Arguments {
args: vec![node.into()],
keywords: vec![],
args: Box::from([node.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -1,7 +1,6 @@
use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use crate::checkers::ast::Checker;
@ -53,19 +52,15 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
return;
}
let Expr::Call(ExprCall {
arguments: Arguments { args, keywords, .. },
..
}) = expr
else {
let Expr::Call(ExprCall { arguments, .. }) = expr else {
return;
};
if !keywords.is_empty() {
if !arguments.keywords.is_empty() {
return;
}
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = args.as_slice() else {
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = &*arguments.args else {
return;
};

View file

@ -116,7 +116,7 @@ pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner:
if !keywords.is_empty() {
return;
}
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return;
};

View file

@ -109,7 +109,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
return;
}
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return;
};

View file

@ -76,7 +76,7 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
return;
}
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return;
};

View file

@ -64,7 +64,7 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
return;
};
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return;
};

View file

@ -139,10 +139,10 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
// Check `left`.
let mut comparator = compare.left.as_ref();
let [op, ..] = compare.ops.as_slice() else {
let [op, ..] = &*compare.ops else {
return;
};
let [next, ..] = compare.comparators.as_slice() else {
let [next, ..] = &*compare.comparators else {
return;
};

View file

@ -90,7 +90,7 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) {
return;
};
match ops.as_slice() {
match &**ops {
[CmpOp::In] => {
if checker.enabled(Rule::NotInTest) {
let mut diagnostic = Diagnostic::new(NotInTest, unary_op.operand.range());

View file

@ -84,10 +84,10 @@ pub(crate) fn comparison_with_itself(
{
continue;
}
let [Expr::Name(left_arg)] = left_call.arguments.args.as_slice() else {
let [Expr::Name(left_arg)] = &*left_call.arguments.args else {
continue;
};
let [Expr::Name(right_right)] = right_call.arguments.args.as_slice() else {
let [Expr::Name(right_right)] = &*right_call.arguments.args else {
continue;
};
if left_arg.id != right_right.id {

View file

@ -59,7 +59,7 @@ pub(crate) fn duplicate_bases(checker: &mut Checker, name: &str, arguments: Opti
let mut seen: FxHashSet<&str> =
FxHashSet::with_capacity_and_hasher(bases.len(), BuildHasherDefault::default());
for base in bases {
for base in bases.iter() {
if let Expr::Name(ast::ExprName { id, .. }) = base {
if !seen.insert(id) {
checker.diagnostics.push(Diagnostic::new(

View file

@ -45,7 +45,7 @@ impl AlwaysFixableViolation for LiteralMembership {
/// PLR6201
pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompare) {
let [op] = compare.ops.as_slice() else {
let [op] = &*compare.ops else {
return;
};
@ -53,7 +53,7 @@ pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompa
return;
}
let [right] = compare.comparators.as_slice() else {
let [right] = &*compare.comparators else {
return;
};

View file

@ -106,7 +106,7 @@ fn collect_nested_args(min_max: MinMax, args: &[Expr], semantic: &SemanticModel)
range: _,
}) = arg
{
if let [arg] = args.as_slice() {
if let [arg] = &**args {
if arg.as_starred_expr().is_none() {
let new_arg = Expr::Starred(ast::ExprStarred {
value: Box::new(arg.clone()),
@ -164,8 +164,8 @@ pub(crate) fn nested_min_max(
let flattened_expr = Expr::Call(ast::ExprCall {
func: Box::new(func.clone()),
arguments: Arguments {
args: collect_nested_args(min_max, args, checker.semantic()),
keywords: keywords.to_owned(),
args: collect_nested_args(min_max, args, checker.semantic()).into_boxed_slice(),
keywords: Box::from(keywords),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -96,7 +96,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
};
// Enforced via `is_allowed_value`.
let [right] = comparators.as_slice() else {
let [right] = &**comparators else {
return;
};
@ -136,14 +136,14 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
checker.generator().expr(&Expr::Compare(ast::ExprCompare {
left: Box::new(value.as_expr().clone()),
ops: match bool_op.op {
BoolOp::Or => vec![CmpOp::In],
BoolOp::And => vec![CmpOp::NotIn],
BoolOp::Or => Box::from([CmpOp::In]),
BoolOp::And => Box::from([CmpOp::NotIn]),
},
comparators: vec![Expr::Tuple(ast::ExprTuple {
comparators: Box::from([Expr::Tuple(ast::ExprTuple {
elts: comparators.iter().copied().cloned().collect(),
range: TextRange::default(),
ctx: ExprContext::Load,
})],
})]),
range: bool_op.range(),
})),
bool_op.range(),
@ -169,7 +169,7 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
};
// Ignore, e.g., `foo == bar == baz`.
let [op] = ops.as_slice() else {
let [op] = &**ops else {
return false;
};
@ -181,7 +181,7 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
}
// Ignore self-comparisons, e.g., `foo == foo`.
let [right] = comparators.as_slice() else {
let [right] = &**comparators else {
return false;
};
if ComparableExpr::from(left) == ComparableExpr::from(right) {

View file

@ -1,10 +1,11 @@
use std::hash::BuildHasherDefault;
use rustc_hash::FxHashSet;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprDict, ExprStringLiteral};
use ruff_python_ast::{Expr, ExprCall, ExprDict, ExprStringLiteral};
use ruff_text_size::Ranged;
use rustc_hash::FxHashSet;
use crate::checkers::ast::Checker;
@ -37,15 +38,14 @@ impl Violation for RepeatedKeywordArgument {
}
pub(crate) fn repeated_keyword_argument(checker: &mut Checker, call: &ExprCall) {
let ExprCall {
arguments: Arguments { keywords, .. },
..
} = call;
let ExprCall { arguments, .. } = call;
let mut seen =
FxHashSet::with_capacity_and_hasher(keywords.len(), BuildHasherDefault::default());
let mut seen = FxHashSet::with_capacity_and_hasher(
arguments.keywords.len(),
BuildHasherDefault::default(),
);
for keyword in keywords {
for keyword in arguments.keywords.iter() {
if let Some(id) = &keyword.arg {
// Ex) `func(a=1, a=2)`
if !seen.insert(id.as_str()) {

View file

@ -111,7 +111,7 @@ pub(crate) fn unnecessary_dunder_call(checker: &mut Checker, call: &ast::ExprCal
let mut title: Option<String> = None;
if let Some(dunder) = DunderReplacement::from_method(attr) {
match (call.arguments.args.as_slice(), dunder) {
match (&*call.arguments.args, dunder) {
([], DunderReplacement::Builtin(replacement, message)) => {
if !checker.semantic().is_builtin(replacement) {
return;

View file

@ -216,8 +216,8 @@ fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) ->
ast::StmtClassDef {
name: Identifier::new(typename.to_string(), TextRange::default()),
arguments: Some(Box::new(Arguments {
args: vec![base_class.clone()],
keywords: vec![],
args: Box::from([base_class.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
})),
body,

View file

@ -148,10 +148,10 @@ fn create_class_def_stmt(
ast::StmtClassDef {
name: Identifier::new(class_name.to_string(), TextRange::default()),
arguments: Some(Box::new(Arguments {
args: vec![base_class.clone()],
args: Box::from([base_class.clone()]),
keywords: match total_keyword {
Some(keyword) => vec![keyword.clone()],
None => vec![],
Some(keyword) => Box::from([keyword.clone()]),
None => Box::from([]),
},
range: TextRange::default(),
})),
@ -226,7 +226,7 @@ fn fields_from_keywords(keywords: &[Keyword]) -> Option<Vec<Stmt>> {
/// Match the fields and `total` keyword from a `TypedDict` call.
fn match_fields_and_total(arguments: &Arguments) -> Option<(Vec<Stmt>, Option<&Keyword>)> {
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
match (&*arguments.args, &*arguments.keywords) {
// Ex) `TypedDict("MyType", {"a": int, "b": str})`
([_typename, fields], [..]) => {
let total = arguments.find_keyword("total");

View file

@ -71,7 +71,7 @@ impl<'a> FormatSummaryValues<'a> {
let mut extracted_args: Vec<&Expr> = Vec::new();
let mut extracted_kwargs: FxHashMap<&str, &Expr> = FxHashMap::default();
for arg in &call.arguments.args {
for arg in call.arguments.args.iter() {
if matches!(arg, Expr::Starred(..))
|| contains_quotes(locator.slice(arg))
|| locator.contains_line_break(arg.range())
@ -80,7 +80,7 @@ impl<'a> FormatSummaryValues<'a> {
}
extracted_args.push(arg);
}
for keyword in &call.arguments.keywords {
for keyword in call.arguments.keywords.iter() {
let Keyword {
arg,
value,

View file

@ -90,7 +90,7 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
continue;
};
let ([op], [comparison]) = (ops.as_slice(), comparators.as_slice()) else {
let ([op], [comparison]) = (&**ops, &**comparators) else {
continue;
};

View file

@ -76,7 +76,7 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr
// For a `super` invocation to be unnecessary, the first argument needs to match
// the enclosing class, and the second argument needs to match the first
// argument to the enclosing function.
let [first_arg, second_arg] = call.arguments.args.as_slice() else {
let [first_arg, second_arg] = &*call.arguments.args else {
return;
};

View file

@ -93,7 +93,7 @@ enum EncodingArg<'a> {
/// Return the encoding argument to an `encode` call, if it can be determined to be a
/// UTF-8-equivalent encoding.
fn match_encoding_arg(arguments: &Arguments) -> Option<EncodingArg> {
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
match (&*arguments.args, &*arguments.keywords) {
// Ex `"".encode()`
([], []) => return Some(EncodingArg::Empty),
// Ex `"".encode(encoding)`

View file

@ -50,7 +50,7 @@ pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast:
return;
};
for base in &arguments.args {
for base in arguments.args.iter() {
let Expr::Name(ast::ExprName { id, .. }) = base else {
continue;
};

View file

@ -21,8 +21,8 @@ pub(super) fn generate_method_call(name: &str, method: &str, generator: Generato
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![],
keywords: vec![],
args: Box::from([]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -55,8 +55,8 @@ pub(super) fn generate_none_identity_comparison(
};
let compare = ast::ExprCompare {
left: Box::new(var.into()),
ops: vec![op],
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
ops: Box::from([op]),
comparators: Box::from([ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())]),
range: TextRange::default(),
};
generator.expr(&compare.into())

View file

@ -74,7 +74,7 @@ pub(crate) fn bit_count(checker: &mut Checker, call: &ExprCall) {
if !call.arguments.keywords.is_empty() {
return;
};
let [arg] = call.arguments.args.as_slice() else {
let [arg] = &*call.arguments.args else {
return;
};
@ -109,7 +109,7 @@ pub(crate) fn bit_count(checker: &mut Checker, call: &ExprCall) {
if !arguments.keywords.is_empty() {
return;
};
let [arg] = arguments.args.as_slice() else {
let [arg] = &*arguments.args else {
return;
};

View file

@ -132,11 +132,11 @@ fn match_check(if_stmt: &ast::StmtIf) -> Option<(&Expr, &ast::ExprName)> {
..
} = if_stmt.test.as_compare_expr()?;
if ops.as_slice() != [CmpOp::In] {
if **ops != [CmpOp::In] {
return None;
}
let [Expr::Name(right @ ast::ExprName { .. })] = comparators.as_slice() else {
let [Expr::Name(right @ ast::ExprName { .. })] = &**comparators else {
return None;
};
@ -165,7 +165,7 @@ fn match_remove(if_stmt: &ast::StmtIf) -> Option<(&Expr, &ast::ExprName)> {
return None;
};
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return None;
};
@ -191,8 +191,8 @@ fn make_suggestion(set: &ast::ExprName, element: &Expr, generator: Generator) ->
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![element.clone()],
keywords: vec![],
args: Box::from([element.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -88,7 +88,7 @@ pub(crate) fn if_expr_min_max(checker: &mut Checker, if_exp: &ast::ExprIfExp) {
};
// Ignore, e.g., `foo < bar < baz`.
let [op] = ops.as_slice() else {
let [op] = &**ops else {
return;
};
@ -102,7 +102,7 @@ pub(crate) fn if_expr_min_max(checker: &mut Checker, if_exp: &ast::ExprIfExp) {
_ => return,
};
let [right] = comparators.as_slice() else {
let [right] = &**comparators else {
return;
};

View file

@ -58,7 +58,7 @@ pub(crate) fn no_implicit_cwd(checker: &mut Checker, call: &ExprCall) {
// Match on arguments, but ignore keyword arguments. `Path()` accepts keyword arguments, but
// ignores them. See: https://github.com/python/cpython/issues/98094.
match arguments.args.as_slice() {
match &*arguments.args {
// Ex) `Path().resolve()`
[] => {}
// Ex) `Path(".").resolve()`

View file

@ -79,7 +79,7 @@ pub(crate) fn print_empty_string(checker: &mut Checker, call: &ast::ExprCall) {
return;
}
match &call.arguments.args.as_slice() {
match &*call.arguments.args {
// Ex) `print("")` or `print("", sep="\t")`
[arg] if is_empty_string(arg) => {
let reason = if call.arguments.find_keyword("sep").is_some() {
@ -211,16 +211,30 @@ fn generate_suggestion(call: &ast::ExprCall, separator: Separator, generator: Ge
let mut call = call.clone();
// Remove all empty string positional arguments.
call.arguments.args.retain(|arg| !is_empty_string(arg));
call.arguments.args = call
.arguments
.args
.iter()
.filter(|arg| !is_empty_string(arg))
.cloned()
.collect::<Vec<_>>()
.into_boxed_slice();
// Remove the `sep` keyword argument if it exists.
if separator == Separator::Remove {
call.arguments.keywords.retain(|keyword| {
call.arguments.keywords = call
.arguments
.keywords
.iter()
.filter(|keyword| {
keyword
.arg
.as_ref()
.map_or(true, |arg| arg.as_str() != "sep")
});
})
.cloned()
.collect::<Vec<_>>()
.into_boxed_slice();
}
generator.expr(&call.into())

View file

@ -322,7 +322,7 @@ fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> SourceCodeSnipp
let call = ast::ExprCall {
func: Box::new(name.into()),
arguments: ast::Arguments {
args: vec![],
args: Box::from([]),
keywords: open.keywords.iter().copied().cloned().collect(),
range: TextRange::default(),
},

View file

@ -70,7 +70,7 @@ pub(crate) fn redundant_log_base(checker: &mut Checker, call: &ast::ExprCall) {
return;
}
let [arg, base] = &call.arguments.args.as_slice() else {
let [arg, base] = &*call.arguments.args else {
return;
};

View file

@ -232,10 +232,10 @@ fn cmp_op(expr: &ast::ExprCompare, params: &ast::Parameters) -> Option<&'static
let [arg1, arg2] = params.args.as_slice() else {
return None;
};
let [op] = expr.ops.as_slice() else {
let [op] = &*expr.ops else {
return None;
};
let [right] = expr.comparators.as_slice() else {
let [right] = &*expr.comparators else {
return None;
};

View file

@ -304,8 +304,8 @@ fn construct_starmap_call(starmap_binding: String, iter: &Expr, func: &Expr) ->
ast::ExprCall {
func: Box::new(starmap.into()),
arguments: ast::Arguments {
args: vec![func.clone(), iter.clone()],
keywords: vec![],
args: Box::from([func.clone(), iter.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -322,8 +322,8 @@ fn wrap_with_call_to(call: ast::ExprCall, func_name: &str) -> ast::ExprCall {
ast::ExprCall {
func: Box::new(name.into()),
arguments: ast::Arguments {
args: vec![call.into()],
keywords: vec![],
args: Box::from([call.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -280,7 +280,7 @@ fn match_append<'a>(semantic: &'a SemanticModel, stmt: &'a Stmt) -> Option<Appen
};
// `append` should have just one argument, an element to be added.
let [argument] = arguments.args.as_slice() else {
let [argument] = &*arguments.args else {
return None;
};
@ -360,8 +360,8 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String {
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![tuple.into()],
keywords: vec![],
args: Box::from([tuple.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -59,7 +59,7 @@ impl Violation for TypeNoneComparison {
/// FURB169
pub(crate) fn type_none_comparison(checker: &mut Checker, compare: &ast::ExprCompare) {
let ([op], [right]) = (compare.ops.as_slice(), compare.comparators.as_slice()) else {
let ([op], [right]) = (&*compare.ops, &*compare.comparators) else {
return;
};

View file

@ -251,8 +251,8 @@ fn generate_range_len_call(name: &str, generator: Generator) -> String {
.into(),
),
arguments: Arguments {
args: vec![var.into()],
keywords: vec![],
args: Box::from([var.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),
@ -268,8 +268,8 @@ fn generate_range_len_call(name: &str, generator: Generator) -> String {
.into(),
),
arguments: Arguments {
args: vec![len.into()],
keywords: vec![],
args: Box::from([len.into()]),
keywords: Box::from([]),
range: TextRange::default(),
},
range: TextRange::default(),

View file

@ -88,7 +88,7 @@ pub(crate) fn explicit_f_string_type_conversion(checker: &mut Checker, f_string:
}
// Can't be a conversion otherwise.
let [arg] = args.as_slice() else {
let [arg] = &**args else {
continue;
};

View file

@ -123,12 +123,12 @@ fn should_be_fstring(
_ => {}
}
}
for keyword in keywords {
for keyword in keywords.iter() {
if let Some(ident) = keyword.arg.as_ref() {
arg_names.insert(ident.as_str());
}
}
for arg in args {
for arg in args.iter() {
if let ast::Expr::Name(ast::ExprName { id, .. }) = arg {
arg_names.insert(id.as_str());
}

View file

@ -84,7 +84,7 @@ pub(crate) fn mutable_fromkeys_value(checker: &mut Checker, call: &ast::ExprCall
}
// Check that the value parameter is a mutable object.
let [keys, value] = call.arguments.args.as_slice() else {
let [keys, value] = &*call.arguments.args else {
return;
};
if !is_mutable_expr(value, checker.semantic()) {

View file

@ -107,7 +107,7 @@ pub(crate) fn sort_dunder_all_extend_call(
..
}: &ast::ExprCall,
) {
let ([value_passed], []) = (args.as_slice(), keywords.as_slice()) else {
let ([value_passed], []) = (&**args, &**keywords) else {
return;
};
let ast::Expr::Attribute(ast::ExprAttribute {

View file

@ -160,11 +160,11 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
let iterable = generator.iter.clone();
let args = Arguments {
args: if value.is_none_literal_expr() {
vec![iterable]
Box::from([iterable])
} else {
vec![iterable, value.clone()]
Box::from([iterable, value.clone()])
},
keywords: vec![],
keywords: Box::from([]),
range: TextRange::default(),
};
Expr::Call(ExprCall {

View file

@ -146,7 +146,7 @@ fn match_iteration_target(expr: &Expr, semantic: &SemanticModel) -> Option<Itera
return None;
}
let [arg] = args.as_slice() else {
let [arg] = &**args else {
return None;
};

View file

@ -72,11 +72,11 @@ pub(crate) fn unnecessary_key_check(checker: &mut Checker, expr: &Expr) {
return;
};
if !matches!(ops.as_slice(), [CmpOp::In]) {
if !matches!(&**ops, [CmpOp::In]) {
return;
}
let [obj_left] = comparators.as_slice() else {
let [obj_left] = &**comparators else {
return;
};

View file

@ -60,16 +60,14 @@ where
}
}
Expr::Call(ast::ExprCall {
func,
arguments: ast::Arguments { args, keywords, .. },
..
func, arguments, ..
}) => {
// Allow `tuple()`, `list()`, and their generic forms, like `list[int]()`.
if keywords.is_empty() && args.len() <= 1 {
if arguments.keywords.is_empty() && arguments.args.len() <= 1 {
if let Expr::Name(ast::ExprName { id, .. }) = map_subscript(func) {
let id = id.as_str();
if matches!(id, "tuple" | "list") && is_builtin(id) {
let [arg] = args.as_slice() else {
let [arg] = arguments.args.as_ref() else {
return (None, DunderAllFlags::empty());
};
match arg {

View file

@ -52,12 +52,12 @@ where
// Accept empty initializers.
if let Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, keywords, .. },
arguments,
range: _,
}) = expr
{
// Ex) `list()`
if args.is_empty() && keywords.is_empty() {
if arguments.is_empty() {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if !is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
return true;
@ -221,14 +221,14 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool {
}) => any_over_expr(left, func) || comparators.iter().any(|expr| any_over_expr(expr, func)),
Expr::Call(ast::ExprCall {
func: call_func,
arguments: Arguments { args, keywords, .. },
arguments,
range: _,
}) => {
any_over_expr(call_func, func)
// Note that this is the evaluation order but not necessarily the declaration order
// (e.g. for `f(*args, a=2, *args2, **kwargs)` it's not)
|| args.iter().any(|expr| any_over_expr(expr, func))
|| keywords
|| arguments.args.iter().any(|expr| any_over_expr(expr, func))
|| arguments.keywords
.iter()
.any(|keyword| any_over_expr(&keyword.value, func))
}
@ -1227,18 +1227,16 @@ impl Truthiness {
}
}
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, keywords, .. },
..
func, arguments, ..
}) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
if args.is_empty() && keywords.is_empty() {
if arguments.is_empty() {
// Ex) `list()`
Self::Falsey
} else if args.len() == 1 && keywords.is_empty() {
} else if arguments.args.len() == 1 && arguments.keywords.is_empty() {
// Ex) `list([1, 2, 3])`
Self::from_expr(&args[0], is_builtin)
Self::from_expr(&arguments.args[0], is_builtin)
} else {
Self::Unknown
}

View file

@ -2588,7 +2588,7 @@ impl AstNode for ast::ExprCompare {
visitor.visit_expr(left);
for (op, comparator) in ops.iter().zip(comparators) {
for (op, comparator) in ops.iter().zip(&**comparators) {
visitor.visit_cmp_op(op);
visitor.visit_expr(comparator);
}

View file

@ -894,8 +894,8 @@ impl From<ExprYieldFrom> for Expr {
pub struct ExprCompare {
pub range: TextRange,
pub left: Box<Expr>,
pub ops: Vec<CmpOp>,
pub comparators: Vec<Expr>,
pub ops: Box<[CmpOp]>,
pub comparators: Box<[Expr]>,
}
impl From<ExprCompare> for Expr {
@ -2987,8 +2987,8 @@ pub struct ParameterWithDefault {
#[derive(Clone, Debug, PartialEq)]
pub struct Arguments {
pub range: TextRange,
pub args: Vec<Expr>,
pub keywords: Vec<Keyword>,
pub args: Box<[Expr]>,
pub keywords: Box<[Keyword]>,
}
/// An entry in the argument list of a function call.
@ -3894,10 +3894,42 @@ mod tests {
assert!(std::mem::size_of::<StmtFunctionDef>() <= 144);
assert!(std::mem::size_of::<StmtClassDef>() <= 104);
assert!(std::mem::size_of::<StmtTry>() <= 112);
// 80 for Rustc < 1.76
assert!(matches!(std::mem::size_of::<Expr>(), 72 | 80));
assert!(std::mem::size_of::<Mod>() <= 32);
// 96 for Rustc < 1.76
assert!(matches!(std::mem::size_of::<Pattern>(), 88 | 96));
assert!(std::mem::size_of::<Mod>() <= 32);
assert_eq!(std::mem::size_of::<Expr>(), 64);
assert_eq!(std::mem::size_of::<ExprAttribute>(), 56);
assert_eq!(std::mem::size_of::<ExprAwait>(), 16);
assert_eq!(std::mem::size_of::<ExprBinOp>(), 32);
assert_eq!(std::mem::size_of::<ExprBoolOp>(), 40);
assert_eq!(std::mem::size_of::<ExprBooleanLiteral>(), 12);
assert_eq!(std::mem::size_of::<ExprBytesLiteral>(), 40);
assert_eq!(std::mem::size_of::<ExprCall>(), 56);
assert_eq!(std::mem::size_of::<ExprCompare>(), 48);
assert_eq!(std::mem::size_of::<ExprDict>(), 56);
assert_eq!(std::mem::size_of::<ExprDictComp>(), 48);
assert_eq!(std::mem::size_of::<ExprEllipsisLiteral>(), 8);
assert_eq!(std::mem::size_of::<ExprFString>(), 48);
assert_eq!(std::mem::size_of::<ExprGeneratorExp>(), 40);
assert_eq!(std::mem::size_of::<ExprIfExp>(), 32);
assert_eq!(std::mem::size_of::<ExprIpyEscapeCommand>(), 32);
assert_eq!(std::mem::size_of::<ExprLambda>(), 24);
assert_eq!(std::mem::size_of::<ExprList>(), 40);
assert_eq!(std::mem::size_of::<ExprListComp>(), 40);
assert_eq!(std::mem::size_of::<ExprName>(), 40);
assert_eq!(std::mem::size_of::<ExprNamedExpr>(), 24);
assert_eq!(std::mem::size_of::<ExprNoneLiteral>(), 8);
assert_eq!(std::mem::size_of::<ExprNumberLiteral>(), 32);
assert_eq!(std::mem::size_of::<ExprSet>(), 32);
assert_eq!(std::mem::size_of::<ExprSetComp>(), 40);
assert_eq!(std::mem::size_of::<ExprSlice>(), 32);
assert_eq!(std::mem::size_of::<ExprStarred>(), 24);
assert_eq!(std::mem::size_of::<ExprStringLiteral>(), 48);
assert_eq!(std::mem::size_of::<ExprSubscript>(), 32);
assert_eq!(std::mem::size_of::<ExprTuple>(), 40);
assert_eq!(std::mem::size_of::<ExprUnaryOp>(), 24);
assert_eq!(std::mem::size_of::<ExprYield>(), 16);
assert_eq!(std::mem::size_of::<ExprYieldFrom>(), 16);
}
}

View file

@ -461,10 +461,10 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
range: _,
}) => {
visitor.visit_expr(left);
for cmp_op in ops {
for cmp_op in &**ops {
visitor.visit_cmp_op(cmp_op);
}
for expr in comparators {
for expr in &**comparators {
visitor.visit_expr(expr);
}
}
@ -594,10 +594,10 @@ pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &
// Note that the there might be keywords before the last arg, e.g. in
// f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then
// `keywords`. See also [Arguments::arguments_source_order`].
for arg in &arguments.args {
for arg in arguments.args.iter() {
visitor.visit_expr(arg);
}
for keyword in &arguments.keywords {
for keyword in arguments.keywords.iter() {
visitor.visit_keyword(keyword);
}
}

View file

@ -448,10 +448,10 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
range: _,
}) => {
visitor.visit_expr(left);
for cmp_op in ops {
for cmp_op in &mut **ops {
visitor.visit_cmp_op(cmp_op);
}
for expr in comparators {
for expr in &mut **comparators {
visitor.visit_expr(expr);
}
}
@ -580,10 +580,10 @@ pub fn walk_arguments<V: Transformer + ?Sized>(visitor: &V, arguments: &mut Argu
// Note that the there might be keywords before the last arg, e.g. in
// f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then
// `keywords`. See also [Arguments::arguments_source_order`].
for arg in &mut arguments.args {
for arg in arguments.args.iter_mut() {
visitor.visit_expr(arg);
}
for keyword in &mut arguments.keywords {
for keyword in arguments.keywords.iter_mut() {
visitor.visit_keyword(keyword);
}
}

View file

@ -1007,7 +1007,7 @@ impl<'a> Generator<'a> {
group_if!(precedence::CMP, {
let new_lvl = precedence::CMP + 1;
self.unparse_expr(left, new_lvl);
for (op, cmp) in ops.iter().zip(comparators) {
for (op, cmp) in ops.iter().zip(&**comparators) {
let op = match op {
CmpOp::Eq => " == ",
CmpOp::NotEq => " != ",
@ -1039,7 +1039,7 @@ impl<'a> Generator<'a> {
range: _,
})],
[],
) = (arguments.args.as_slice(), arguments.keywords.as_slice())
) = (arguments.args.as_ref(), arguments.keywords.as_ref())
{
// Ensure that a single generator doesn't get double-parenthesized.
self.unparse_expr(elt, precedence::COMMA);

View file

@ -38,7 +38,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
let all_arguments = format_with(|f: &mut PyFormatter| {
let source = f.context().source();
let mut joiner = f.join_comma_separated(range.end());
match args.as_slice() {
match args.as_ref() {
[arg] if keywords.is_empty() => {
match arg {
Expr::GeneratorExp(generator_exp) => joiner.entry(
@ -180,7 +180,7 @@ fn is_single_argument_parenthesized(argument: &Expr, call_end: TextSize, source:
/// of those collections.
fn is_arguments_huggable(arguments: &Arguments, context: &PyFormatContext) -> bool {
// Find the lone argument or `**kwargs` keyword.
let arg = match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
let arg = match (arguments.args.as_ref(), arguments.keywords.as_ref()) {
([arg], []) => arg,
([], [keyword]) if keyword.arg.is_none() && !context.comments().has(keyword) => {
&keyword.value

View file

@ -81,16 +81,16 @@ type FunctionArgument = (
pub(crate) fn parse_arguments(
function_arguments: Vec<FunctionArgument>,
) -> Result<ArgumentList, LexicalError> {
let mut args = vec![];
let mut keywords = vec![];
// First, run through the comments to determine the number of positional and keyword arguments.
let mut keyword_names = FxHashSet::with_capacity_and_hasher(
function_arguments.len(),
BuildHasherDefault::default(),
);
let mut double_starred = false;
for (name, value) in function_arguments {
if let Some((start, end, name)) = name {
let mut num_args = 0;
let mut num_keywords = 0;
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 {
if !keyword_names.insert(keyword_name.to_string()) {
@ -98,21 +98,17 @@ pub(crate) fn parse_arguments(
LexicalErrorType::DuplicateKeywordArgumentError(
keyword_name.to_string().into_boxed_str(),
),
start,
*start,
));
}
} else {
double_starred = true;
}
keywords.push(ast::Keyword {
arg: name,
value,
range: TextRange::new(start, end),
});
num_keywords += 1;
} else {
// Positional arguments mustn't follow keyword arguments.
if !keywords.is_empty() && !is_starred(&value) {
if num_keywords > 0 && !is_starred(value) {
return Err(LexicalError::new(
LexicalErrorType::PositionalArgumentError,
value.start(),
@ -126,9 +122,26 @@ pub(crate) fn parse_arguments(
));
}
num_args += 1;
}
}
// Second, push the arguments into vectors of exact capacity. This avoids a vector resize later
// on when these vectors are boxed into slices.
let mut args = Vec::with_capacity(num_args);
let mut keywords = Vec::with_capacity(num_keywords);
for (name, value) in function_arguments {
if let Some((start, end, name)) = name {
keywords.push(ast::Keyword {
arg: name,
value,
range: TextRange::new(start, end),
});
} else {
args.push(value);
}
}
Ok(ArgumentList { args, keywords })
}

View file

@ -569,8 +569,7 @@ mod tests {
#[cfg(target_pointer_width = "64")]
#[test]
fn size_assertions() {
// 80 with Rustc >= 1.76, 88 with Rustc < 1.76
assert!(matches!(std::mem::size_of::<ParenthesizedExpr>(), 80 | 88));
assert_eq!(std::mem::size_of::<ParenthesizedExpr>(), 72);
}
#[test]

View file

@ -1406,8 +1406,18 @@ NotTest<Goal>: crate::parser::ParenthesizedExpr = {
Comparison<Goal>: crate::parser::ParenthesizedExpr = {
<location:@L> <left:Expression<"all">> <comparisons:(CompOp Expression<"all">)+> <end_location:@R> => {
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
},
Expression<Goal>,
};
@ -1880,8 +1890,8 @@ Arguments: ast::Arguments = {
<location:@L> "(" <e: Comma<FunctionArgument>> ")" <end_location:@R> =>? {
let ArgumentList { args, keywords } = parse_arguments(e)?;
Ok(ast::Arguments {
args,
keywords,
args: args.into_boxed_slice(),
keywords: keywords.into_boxed_slice(),
range: (location..end_location).into()
})
}

View file

@ -1,5 +1,5 @@
// auto-generated: "lalrpop 0.20.0"
// sha3: fd05d84d3b654796ff740a7f905ec0ae8915f43f952428717735481947ab55e1
// sha3: 02c60b5c591440061dda68775005d87a203b5448c205120bda1566a62fc2147c
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_python_ast::{self as ast, Int, IpyEscapeKind};
use crate::{
@ -36771,8 +36771,8 @@ fn __action241<
{
let ArgumentList { args, keywords } = parse_arguments(e)?;
Ok(ast::Arguments {
args,
keywords,
args: args.into_boxed_slice(),
keywords: keywords.into_boxed_slice(),
range: (location..end_location).into()
})
}
@ -40651,8 +40651,18 @@ fn __action515<
) -> crate::parser::ParenthesizedExpr
{
{
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
}
}
@ -40816,8 +40826,18 @@ fn __action526<
) -> crate::parser::ParenthesizedExpr
{
{
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
}
}