diff --git a/Cargo.lock b/Cargo.lock index 9d53a24ceb388..b7a320c65e36a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3594,6 +3594,7 @@ dependencies = [ "bstr", "compact_str", "datatest-stable", + "drop_bomb", "get-size2", "insta", "itertools 0.15.0", diff --git a/crates/ruff_python_parser/Cargo.toml b/crates/ruff_python_parser/Cargo.toml index 480589a5a60b5..c88c116faf67c 100644 --- a/crates/ruff_python_parser/Cargo.toml +++ b/crates/ruff_python_parser/Cargo.toml @@ -20,6 +20,7 @@ ruff_text_size = { workspace = true, features = ["get-size"] } bitflags = { workspace = true } bstr = { workspace = true } compact_str = { workspace = true } +drop_bomb = { workspace = true } get-size2 = { workspace = true } memchr = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index e38529c1b736f..118a372a1c31b 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -815,8 +815,8 @@ impl<'src> Parser<'src> { }; } - let mut args = vec![]; - let mut keywords = vec![]; + let args_snapshot = self.expr_scratch.snapshot(); + let keywords_snapshot = self.keyword_scratch.snapshot(); let mut seen_keyword_argument = false; // foo = 1 let mut seen_keyword_unpacking = false; // **foo @@ -826,7 +826,7 @@ impl<'src> Parser<'src> { if parser.eat(TokenKind::DoubleStar) { let value = parser.parse_conditional_expression_or_higher(); - keywords.push(ast::Keyword { + parser.keyword_scratch.push(ast::Keyword { arg: None, value: value.expr, range: parser.node_range(argument_start), @@ -916,7 +916,7 @@ impl<'src> Parser<'src> { let value = parser.parse_conditional_expression_or_higher(); - keywords.push(ast::Keyword { + parser.keyword_scratch.push(ast::Keyword { arg: Some(arg), value: value.expr, range: parser.node_range(argument_start), @@ -936,23 +936,19 @@ impl<'src> Parser<'src> { ); } } - // Reserve exactly one slot for the first positional argument, while - // avoiding any allocation for keyword-only calls. - if args.is_empty() { - args.reserve_exact(1); - } - args.push(parsed_expr.expr); + parser.expr_scratch.push(parsed_expr.expr); } } }); self.expect(TokenKind::Rpar); + let keywords = self.keyword_scratch.take_thin_vec(keywords_snapshot); let arguments = ast::Arguments { range: self.node_range(start), node_index: AtomicNodeIndex::NONE, - args: args.into_boxed_slice(), - keywords: keywords.into(), + args: self.expr_scratch.take(args_snapshot), + keywords, }; self.validate_arguments(&arguments, has_trailing_comma, context); @@ -1005,16 +1001,16 @@ impl<'src> Parser<'src> { // If there are more than one element in the slice, we need to create a tuple // expression to represent it. if self.eat(TokenKind::Comma) { - let mut slices = vec![slice]; + let slices_snapshot = self.expr_scratch.snapshot(); + self.expr_scratch.push(slice); self.parse_comma_separated_list(RecoveryContextKind::Slices, |parser| { - slices.push(parser.parse_slice()); + let slice = parser.parse_slice(); + parser.expr_scratch.push(slice); }); - slices.shrink_to_fit(); - slice = Expr::Tuple(ast::ExprTuple { - elts: slices, + elts: self.expr_scratch.take(slices_snapshot), ctx: ExprContext::Load, range: self.node_range(slice_start), parenthesized: false, @@ -1252,8 +1248,8 @@ impl<'src> Parser<'src> { ) -> ast::ExprBoolOp { self.bump(TokenKind::from(op)); - let mut values = Vec::with_capacity(2); - values.push(lhs); + let values_snapshot = self.expr_scratch.snapshot(); + self.expr_scratch.push(lhs); let mut progress = ParserProgress::default(); // Keep adding the expression to `values` until we see a different @@ -1263,17 +1259,15 @@ impl<'src> Parser<'src> { let parsed_expr = self.parse_binary_expression_or_higher(OperatorPrecedence::from(op), context); - values.push(parsed_expr.expr); + self.expr_scratch.push(parsed_expr.expr); if !self.eat(TokenKind::from(op)) { break; } } - values.shrink_to_fit(); - ast::ExprBoolOp { - values, + values: self.expr_scratch.take(values_snapshot), op, range: self.node_range(start), node_index: AtomicNodeIndex::NONE, @@ -1322,7 +1316,7 @@ impl<'src> Parser<'src> { ) -> ast::ExprCompare { self.bump_cmp_op(op); - let mut comparators = vec![]; + let comparators_snapshot = self.expr_scratch.snapshot(); let mut operators = vec![op]; let mut progress = ParserProgress::default(); @@ -1330,13 +1324,13 @@ impl<'src> Parser<'src> { loop { progress.assert_progressing(self); - comparators.push( - self.parse_binary_expression_or_higher( + let comparator = self + .parse_binary_expression_or_higher( OperatorPrecedence::ComparisonsMembershipIdentity, context, ) - .expr, - ); + .expr; + self.expr_scratch.push(comparator); let next_token = self.current_token_kind(); if matches!(next_token, TokenKind::In) && context.is_in_excluded() { @@ -1356,7 +1350,7 @@ impl<'src> Parser<'src> { ast::ExprCompare { left: Box::new(lhs), ops: operators.into_boxed_slice(), - comparators: comparators.into_boxed_slice(), + comparators: self.expr_scratch.take(comparators_snapshot), range: self.node_range(start), node_index: AtomicNodeIndex::NONE, } @@ -2455,20 +2449,20 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Comma); } - let mut elts = vec![first_element]; + let elts_snapshot = self.expr_scratch.snapshot(); + self.expr_scratch.push(first_element); self.parse_comma_separated_list(RecoveryContextKind::TupleElements(parenthesized), |p| { - elts.push(parse_func(p).expr); + let element = parse_func(p).expr; + p.expr_scratch.push(element); }); if parenthesized.is_yes() { self.expect(TokenKind::Rpar); } - elts.shrink_to_fit(); - ast::ExprTuple { - elts, + elts: self.expr_scratch.take(elts_snapshot), ctx: ExprContext::Load, range: self.node_range(start), node_index: AtomicNodeIndex::NONE, @@ -2484,22 +2478,20 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Comma); } - let mut elts = vec![first_element]; + let elts_snapshot = self.expr_scratch.snapshot(); + self.expr_scratch.push(first_element); self.parse_comma_separated_list(RecoveryContextKind::ListElements, |parser| { - elts.push( - parser - .parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or()) - .expr, - ); + let element = parser + .parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or()) + .expr; + parser.expr_scratch.push(element); }); self.expect(TokenKind::Rsqb); - elts.shrink_to_fit(); - ast::ExprList { - elts, + elts: self.expr_scratch.take(elts_snapshot), ctx: ExprContext::Load, range: self.node_range(start), node_index: AtomicNodeIndex::NONE, @@ -2529,7 +2521,8 @@ impl<'src> Parser<'src> { ); } - let mut elts = vec![first_element.expr]; + let elts_snapshot = self.expr_scratch.snapshot(); + self.expr_scratch.push(first_element.expr); self.parse_comma_separated_list(RecoveryContextKind::SetElements, |parser| { let parsed_expr = @@ -2544,7 +2537,7 @@ impl<'src> Parser<'src> { ); } - elts.push(parsed_expr.expr); + parser.expr_scratch.push(parsed_expr.expr); }); self.expect(TokenKind::Rbrace); @@ -2552,7 +2545,7 @@ impl<'src> Parser<'src> { ast::ExprSet { range: self.node_range(start), node_index: AtomicNodeIndex::NONE, - elts, + elts: self.expr_scratch.take(elts_snapshot), } } @@ -2653,7 +2646,7 @@ impl<'src> Parser<'src> { self.expect(TokenKind::In); let iter = self.parse_simple_expression(ExpressionContext::default()); - let mut ifs = Vec::new(); + let ifs_snapshot = self.expr_scratch.snapshot(); let mut progress = ParserProgress::default(); while self.eat(TokenKind::If) { @@ -2661,20 +2654,15 @@ impl<'src> Parser<'src> { let parsed_expr = self.parse_simple_expression(ExpressionContext::default()); - if ifs.is_empty() { - ifs.reserve_exact(1); - } - ifs.push(parsed_expr.expr); + self.expr_scratch.push(parsed_expr.expr); } - ifs.shrink_to_fit(); - ast::Comprehension { range: self.node_range(start), node_index: AtomicNodeIndex::NONE, target: target.expr, iter: iter.expr, - ifs, + ifs: self.expr_scratch.take(ifs_snapshot), is_async, } } diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs index f1a44c9241297..0c7093793a618 100644 --- a/crates/ruff_python_parser/src/parser/mod.rs +++ b/crates/ruff_python_parser/src/parser/mod.rs @@ -6,7 +6,8 @@ use bitflags::bitflags; use ruff_python_ast::name::Name; use ruff_python_ast::token::TokenKind; use ruff_python_ast::{ - AtomicNodeIndex, Int, IpyEscapeKind, Mod, ModExpression, ModModule, StringFlags, + Alias, AtomicNodeIndex, ElifElseClause, Expr, Int, IpyEscapeKind, Keyword, Mod, ModExpression, + ModModule, ParameterWithDefault, Stmt, StringFlags, }; use ruff_python_trivia::is_python_whitespace; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -16,6 +17,7 @@ use unicode_normalization::UnicodeNormalization; use crate::error::UnsupportedSyntaxError; use crate::parser::expression::ExpressionContext; use crate::parser::progress::{ParserProgress, TokenId}; +use crate::parser::scratch_buffer::ScratchBuffer; use crate::string::InterpolatedStringKind; use crate::token_set::TokenSet; use crate::token_source::{TokenSource, TokenSourceCheckpoint}; @@ -30,6 +32,7 @@ mod options; mod pattern; mod progress; mod recovery; +mod scratch_buffer; mod statement; #[cfg(test)] mod tests; @@ -68,6 +71,24 @@ pub(crate) struct Parser<'src> { /// Maximum lexer nesting depth before postfix calls and subscripts should stop recursing. max_nesting_depth: u32, + + /// Reusable, nesting-safe scratch storage for expression lists. + expr_scratch: ScratchBuffer, + + /// Reusable, nesting-safe scratch storage for call keywords. + keyword_scratch: ScratchBuffer, + + /// Reusable, nesting-safe scratch storage for function and lambda parameters. + parameter_scratch: ScratchBuffer, + + /// Reusable, nesting-safe scratch storage for statement lists. + stmt_scratch: ScratchBuffer, + + /// Reusable scratch storage for import aliases. + alias_scratch: ScratchBuffer, + + /// Reusable, nesting-safe scratch storage for `elif` and `else` clauses. + elif_else_scratch: ScratchBuffer, } impl<'src> Parser<'src> { @@ -98,6 +119,12 @@ impl<'src> Parser<'src> { current_token_id: TokenId::default(), depth_remaining, max_nesting_depth, + expr_scratch: ScratchBuffer::with_capacity(16), + keyword_scratch: ScratchBuffer::new(), + parameter_scratch: ScratchBuffer::new(), + stmt_scratch: ScratchBuffer::with_capacity(32), + alias_scratch: ScratchBuffer::new(), + elif_else_scratch: ScratchBuffer::new(), } } @@ -207,7 +234,6 @@ impl<'src> Parser<'src> { TokenKind::EndOfFile, "Parser should be at the end of the file." ); - // TODO consider re-integrating lexical error handling into the parser? let parse_errors = self.errors; let (tokens, lex_errors) = self.tokens.finish(); diff --git a/crates/ruff_python_parser/src/parser/scratch_buffer.rs b/crates/ruff_python_parser/src/parser/scratch_buffer.rs new file mode 100644 index 0000000000000..9f3acb015eaad --- /dev/null +++ b/crates/ruff_python_parser/src/parser/scratch_buffer.rs @@ -0,0 +1,136 @@ +use std::vec::Drain; + +use drop_bomb::DebugDropBomb; +use thin_vec::ThinVec; + +/// Reusable scratch storage that preserves entries belonging to outer parser frames. +#[derive(Debug)] +pub(super) struct ScratchBuffer { + buffer: Vec, +} + +impl ScratchBuffer { + pub(super) fn new() -> Self { + Self { buffer: Vec::new() } + } + + pub(super) fn with_capacity(capacity: usize) -> Self { + Self { + buffer: Vec::with_capacity(capacity), + } + } + + #[inline] + pub(super) fn push(&mut self, value: T) { + self.buffer.push(value); + } + + #[inline] + pub(super) fn is_empty(&self, snapshot: &ScratchSnapshot) -> bool { + debug_assert!( + self.buffer.len() >= snapshot.len, + "Scratch buffer snapshots must be restored in reverse order of creation." + ); + self.buffer.len() == snapshot.len + } + + #[inline] + pub(super) fn snapshot(&self) -> ScratchSnapshot { + ScratchSnapshot { + len: self.buffer.len(), + bomb: DebugDropBomb::new("Scratch buffer snapshots must be restored."), + } + } + + #[inline] + pub(super) fn take>(&mut self, snapshot: ScratchSnapshot) -> C { + self.drain_snapshot(snapshot).collect() + } + + #[inline] + pub(super) fn take_thin_vec(&mut self, mut snapshot: ScratchSnapshot) -> ThinVec { + if self.is_empty(&snapshot) { + snapshot.bomb.defuse(); + return ThinVec::new(); + } + + let drain = self.drain_snapshot(snapshot); + let mut result = ThinVec::with_capacity(drain.len()); + result.extend(drain); + result + } + + #[inline] + fn drain_snapshot(&mut self, mut snapshot: ScratchSnapshot) -> Drain<'_, T> { + debug_assert!( + self.buffer.len() >= snapshot.len, + "Scratch buffer snapshots must be restored in reverse order of creation." + ); + snapshot.bomb.defuse(); + self.buffer.drain(snapshot.len..) + } +} + +impl Drop for ScratchBuffer { + fn drop(&mut self) { + debug_assert!( + self.buffer.is_empty() || std::thread::panicking(), + "Scratch buffers must be empty when dropped." + ); + } +} + +pub(super) struct ScratchSnapshot { + len: usize, + bomb: DebugDropBomb, +} + +#[cfg(test)] +mod tests { + use super::ScratchBuffer; + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Scratch buffers must be empty when dropped.")] + fn buffer_must_be_empty_when_dropped() { + let mut buffer = ScratchBuffer::new(); + buffer.push(1); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Scratch buffer snapshots must be restored.")] + fn snapshot_must_be_restored() { + let buffer = ScratchBuffer::::new(); + let _snapshot = buffer.snapshot(); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic( + expected = "Scratch buffer snapshots must be restored in reverse order of creation." + )] + fn snapshot_must_be_restored_in_reverse_order() { + let mut buffer = ScratchBuffer::new(); + buffer.push(1); + let snapshot = buffer.snapshot(); + buffer.buffer.clear(); + + let _: Vec<_> = buffer.take(snapshot); + } + + #[test] + fn snapshot_is_empty_relative_to_its_buffer() { + let mut buffer = ScratchBuffer::new(); + let outer_snapshot = buffer.snapshot(); + buffer.push(1); + let snapshot = buffer.snapshot(); + assert!(buffer.is_empty(&snapshot)); + + buffer.push(2); + assert!(!buffer.is_empty(&snapshot)); + + let _: Vec<_> = buffer.take(snapshot); + let _: Vec<_> = buffer.take(outer_snapshot); + } +} diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 5f406e304be9a..6785c8dc2f060 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -195,13 +195,14 @@ impl<'src> Parser<'src> { /// /// [Python grammar]: https://docs.python.org/3/reference/grammar.html fn parse_simple_statements(&mut self) -> Suite { - let mut stmts = Suite::with_capacity(1); + let stmts_snapshot = self.stmt_scratch.snapshot(); let mut progress = ParserProgress::default(); loop { progress.assert_progressing(self); - stmts.push(self.parse_simple_statement()); + let stmt = self.parse_simple_statement(); + self.stmt_scratch.push(stmt); if !self.eat(TokenKind::Semi) { if self.at_simple_stmt() { @@ -258,8 +259,7 @@ impl<'src> Parser<'src> { // test_ok simple_stmts_with_semicolons // return; import a; from x import y; z; type T = int - stmts.shrink_to_fit(); - stmts + self.stmt_scratch.take_thin_vec(stmts_snapshot) } /// Parses a simple statement. @@ -620,11 +620,12 @@ impl<'src> Parser<'src> { // import , // import x, y, - let mut names = self.parse_comma_separated_list_into_vec_with_capacity( - RecoveryContextKind::ImportNames, - |p| p.parse_alias(ImportStyle::Import), - 1, - ); + let names_snapshot = self.alias_scratch.snapshot(); + self.parse_comma_separated_list(RecoveryContextKind::ImportNames, |parser| { + let alias = parser.parse_alias(ImportStyle::Import); + parser.alias_scratch.push(alias); + }); + let names: Vec<_> = self.alias_scratch.take(names_snapshot); if names.is_empty() { // test_err import_stmt_empty @@ -632,8 +633,6 @@ impl<'src> Parser<'src> { self.add_error(ParseErrorType::EmptyImportNames, self.current_token_range()); } - names.shrink_to_fit(); - ast::StmtImport { names, is_lazy, @@ -698,7 +697,7 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Import); let names_start = self.node_start(); - let mut names = Vec::new(); + let names_snapshot = self.alias_scratch.snapshot(); let mut seen_star_import = false; let parenthesized = Parenthesized::from(self.eat(TokenKind::Lpar)); @@ -716,9 +715,10 @@ impl<'src> Parser<'src> { // from x import a, b.c, d, e.f, g let alias = parser.parse_alias(ImportStyle::ImportFrom); seen_star_import |= alias.name.id == "*"; - names.push(alias); + parser.alias_scratch.push(alias); }, ); + let names: Vec<_> = self.alias_scratch.take(names_snapshot); if names.is_empty() { // test_err from_import_empty_names @@ -758,8 +758,6 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Rpar); } - names.shrink_to_fit(); - ast::StmtImportFrom { module, names, @@ -1452,23 +1450,21 @@ impl<'src> Parser<'src> { // pass // else: // pass - let mut elif_else_clauses = self.parse_clauses(Clause::ElIf, |p| { - p.parse_elif_or_else_clause(ElifOrElse::Elif) + let elif_else_snapshot = self.elif_else_scratch.snapshot(); + self.parse_clauses(Clause::ElIf, |parser| { + let clause = parser.parse_elif_or_else_clause(ElifOrElse::Elif); + parser.elif_else_scratch.push(clause); }); if self.at(TokenKind::Else) { - if elif_else_clauses.is_empty() { - elif_else_clauses.reserve_exact(1); - } - elif_else_clauses.push(self.parse_elif_or_else_clause(ElifOrElse::Else)); + let clause = self.parse_elif_or_else_clause(ElifOrElse::Else); + self.elif_else_scratch.push(clause); } - elif_else_clauses.shrink_to_fit(); - ast::StmtIf { test: Box::new(test.expr), body, - elif_else_clauses, + elif_else_clauses: self.elif_else_scratch.take(elif_else_snapshot), range: self.node_range(start), node_index: AtomicNodeIndex::NONE, } @@ -1560,7 +1556,8 @@ impl<'src> Parser<'src> { // except* ExceptionGroup: // pass let mut mixed_except_ranges = Vec::new(); - let mut handlers = self.parse_clauses(Clause::Except, |p| { + let mut handlers = Vec::new(); + self.parse_clauses(Clause::Except, |p| { let (handler, kind) = p.parse_except_clause(); if let ExceptClauseKind::Star(range) = kind { p.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::ExceptStar, range); @@ -1570,7 +1567,10 @@ impl<'src> Parser<'src> { } else if is_star != Some(kind.is_star()) { mixed_except_ranges.push(handler.range()); } - handler + if handlers.is_empty() { + handlers.reserve_exact(1); + } + handlers.push(handler); }); handlers.shrink_to_fit(); @@ -3125,10 +3125,13 @@ impl<'src> Parser<'src> { self.bump(TokenKind::Indent); let statements = if let Some(statements) = self.with_recursion(|parser| { - parser.parse_list_into_thin_vec( - RecoveryContextKind::BlockStatements, - Parser::parse_statement, - ) + let snapshot = parser.stmt_scratch.snapshot(); + parser.parse_list(RecoveryContextKind::BlockStatements, |parser| { + let statement = parser.parse_statement(); + parser.stmt_scratch.push(statement); + }); + + parser.stmt_scratch.take_thin_vec(snapshot) }) { statements } else { @@ -3308,6 +3311,9 @@ impl<'src> Parser<'src> { // uses `Parameter` (not `ParameterWithDefault`) which means that the parser cannot // recover well from `*args=(1, 2)`. let mut parameters = ast::Parameters::default(); + let parameters_snapshot = self.parameter_scratch.snapshot(); + let mut args_snapshot = None; + let mut kwonlyargs_snapshot = None; let mut seen_default_param = false; // `a=10` let mut seen_positional_only_separator = false; // `/` @@ -3334,6 +3340,9 @@ impl<'src> Parser<'src> { let star_range = parser.current_token_range(); parser.bump(TokenKind::Star); + kwonlyargs_snapshot + .get_or_insert_with(|| parser.parameter_scratch.snapshot()); + if parser.at_name_or_soft_keyword() { let param = parser.parse_parameter(param_start, function_kind, AllowStarAnnotation::Yes); let param_star_range = parser.node_range(star_range.start()); @@ -3445,7 +3454,10 @@ impl<'src> Parser<'src> { let slash_range = parser.current_token_range(); parser.bump(TokenKind::Slash); - if parameters.is_empty() { + if parser.parameter_scratch.is_empty(¶meters_snapshot) + && parameters.vararg.is_none() + && parameters.kwarg.is_none() + { // test_err params_no_arg_before_slash // def foo(/): ... // def foo(/, a): ... @@ -3485,9 +3497,11 @@ impl<'src> Parser<'src> { } if !seen_positional_only_separator { - // We should only swap if we're seeing the separator for the + // We should only split if we're seeing the separator for the // first time, otherwise it's a user error. - std::mem::swap(&mut parameters.args, &mut parameters.posonlyargs); + if kwonlyargs_snapshot.is_none() { + args_snapshot = Some(parser.parameter_scratch.snapshot()); + } seen_positional_only_separator = true; // test_ok pos_only_py38 @@ -3534,11 +3548,7 @@ impl<'src> Parser<'src> { seen_keyword_only_param_after_separator = true; } - if seen_keyword_only_separator || parameters.vararg.is_some() { - parameters.kwonlyargs.push(param); - } else { - parameters.args.push(param); - } + parser.parameter_scratch.push(param); last_keyword_only_separator_range = None; } _ => { @@ -3562,9 +3572,17 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Rpar); } - parameters.args.shrink_to_fit(); - parameters.kwonlyargs.shrink_to_fit(); - parameters.posonlyargs.shrink_to_fit(); + if let Some(kwonlyargs_snapshot) = kwonlyargs_snapshot { + parameters.kwonlyargs = self.parameter_scratch.take_thin_vec(kwonlyargs_snapshot); + } + if let Some(args_snapshot) = args_snapshot { + parameters.args = self.parameter_scratch.take_thin_vec(args_snapshot); + parameters.posonlyargs = self.parameter_scratch.take_thin_vec(parameters_snapshot); + } else if seen_positional_only_separator { + parameters.posonlyargs = self.parameter_scratch.take_thin_vec(parameters_snapshot); + } else { + parameters.args = self.parameter_scratch.take_thin_vec(parameters_snapshot); + } parameters.range = self.node_range(start); @@ -4000,12 +4018,7 @@ impl<'src> Parser<'src> { /// For now, don't recover when parsing clause headers, but add the terminator tokens (e.g. /// `Else`) to the recovery context so that expression recovery stops when it encounters an /// `else` token. - fn parse_clauses( - &mut self, - clause: Clause, - mut parse_clause: impl FnMut(&mut Parser<'src>) -> T, - ) -> Vec { - let mut clauses = Vec::new(); + fn parse_clauses(&mut self, clause: Clause, mut parse_clause: impl FnMut(&mut Parser<'src>)) { let mut progress = ParserProgress::default(); let recovery_kind = match clause { @@ -4022,15 +4035,10 @@ impl<'src> Parser<'src> { while recovery_kind.is_list_element(self) { progress.assert_progressing(self); - if clauses.is_empty() { - clauses.reserve_exact(1); - } - clauses.push(parse_clause(self)); + parse_clause(self); } self.recovery_context = saved_context; - - clauses } }