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

@ -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);
}
}