diff --git a/rottlib/src/parser/grammar/expression/primary/new.rs b/rottlib/src/parser/grammar/expression/primary/new.rs index 0cc6b2d..c9c3717 100644 --- a/rottlib/src/parser/grammar/expression/primary/new.rs +++ b/rottlib/src/parser/grammar/expression/primary/new.rs @@ -223,7 +223,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { state: &mut NewArgumentListParseState<'src, 'arena>, ) -> Option { let has_parsed_all_allowed_arguments = - state.call_argument_list_parse_state.parsed_slot_count > 2; + state.call_argument_list_parse_state.parsed_slot_count >= 3; let likely_missing_comma = !self.next_token_definitely_cannot_start_expression() && !has_parsed_all_allowed_arguments; if likely_missing_comma { @@ -282,29 +282,42 @@ impl<'src, 'arena> Parser<'src, 'arena> { } } - /// Reports a missing closing `)` in a `new(...)` argument list and - /// determines whether the class specifier should be parsed or skipped. + /// Recovers from a missing closing `)` in a `new(...)` argument list. + /// + /// Returns whether class-specifier parsing should continue after recovery. #[must_use] fn recover_from_missing_new_closing_parenthesis( &mut self, state: &NewArgumentListParseState<'src, 'arena>, ) -> NewClassSpecifierParseAction { - let mut error = self - .make_error_at_last_consumed(ParseErrorKind::NewMissingClosingParenthesis) + self.make_error_at_last_consumed(ParseErrorKind::NewMissingClosingParenthesis) .widen_error_span_from(state.left_parenthesis_position) .blame_token(self.peek_position_or_eof()) .related_token("new_keyword", state.new_keyword_position) - .related_token("left_parenthesis", state.left_parenthesis_position); - let class_specifier_parse_action = if self.next_token_definitely_cannot_start_expression() { - error = error.sync_error_at_matching_delimiter(self, state.left_parenthesis_position); - // Skipping the class specifier avoids cascading errors when - // the next token cannot start an expression anyway. + .related_token("left_parenthesis", state.left_parenthesis_position) + .report(self); + // Missing-delimiter recovery normally syncs to the matching `)`. + // `new(...) ClassName` is an exception: after a missing `)`, the next + // expression may already be the class specifier, not another argument. + let matching_right_parenthesis_ahead = self + .file + .matching_delimiter(state.left_parenthesis_position) + .is_some_and(|right_parenthesis_position| { + self.peek_position_or_eof() <= right_parenthesis_position + }); + if matching_right_parenthesis_ahead { + self.recover_at_matching_delimiter_or_sync(state.left_parenthesis_position); + // After syncing through the matched `)`, the argument-list error is + // contained, so class-specifier parsing can proceed normally. + return NewClassSpecifierParseAction::Parse; + } + if self.next_token_definitely_cannot_start_expression() { + // There is no plausible class specifier to parse, so skip it to + // avoid error cascade. NewClassSpecifierParseAction::Skip } else { NewClassSpecifierParseAction::Parse - }; - error.report_error(self); - class_specifier_parse_action + } } /// Parses the class specifier of a `new` expression after argument-list