From 47c5af2f16904ad41f233cab61a7b093e1993441 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 29 Oct 2025 12:28:57 +0100 Subject: [PATCH] fix: Improve error recovery when parsing malformed function return types --- crates/parser/src/grammar/expressions/atom.rs | 6 + crates/parser/src/grammar/items.rs | 8 ++ crates/parser/test_data/generated/runner.rs | 8 ++ .../inline/err/closure_ret_recovery.rast | 52 ++++++++ .../parser/inline/err/closure_ret_recovery.rs | 1 + .../parser/inline/err/fn_ret_recovery.rast | 112 ++++++++++++++++++ .../parser/inline/err/fn_ret_recovery.rs | 2 + 7 files changed, 189 insertions(+) create mode 100644 crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast create mode 100644 crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs create mode 100644 crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast create mode 100644 crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index ed8a91c39c..cde62e0323 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -588,6 +588,12 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker { } params::param_list_closure(p); if opt_ret_type(p) { + // test_err closure_ret_recovery + // fn foo() { || -> A> { let x = 1; } } + while p.at(T![>]) { + // recover from unbalanced return type brackets + p.err_and_bump("expected a curly brace"); + } // test lambda_ret_block // fn main() { || -> i32 { 92 }(); } block_expr(p); diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index 8e551b0b96..c609f9383e 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -424,6 +424,14 @@ fn fn_(p: &mut Parser<'_>, m: Marker) { // fn bar() -> () {} opt_ret_type(p); + // test_err fn_ret_recovery + // fn foo() -> A>]) { let x = 1; } + // fn foo() -> A>]) where T: Copy { let x = 1; } + while p.at(T![')']) | p.at(T![']']) | p.at(T![>]) { + // recover from unbalanced return type brackets + p.err_and_bump("expected a curly brace"); + } + // test function_where_clause // fn foo() where T: Copy {} generic_params::opt_where_clause(p); diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 9bdbe56330..c56eb5090c 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -749,6 +749,10 @@ mod err { #[test] fn bad_asm_expr() { run_and_expect_errors("test_data/parser/inline/err/bad_asm_expr.rs"); } #[test] + fn closure_ret_recovery() { + run_and_expect_errors("test_data/parser/inline/err/closure_ret_recovery.rs"); + } + #[test] fn comma_after_default_values_syntax() { run_and_expect_errors("test_data/parser/inline/err/comma_after_default_values_syntax.rs"); } @@ -773,6 +777,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/fn_pointer_type_missing_fn.rs"); } #[test] + fn fn_ret_recovery() { + run_and_expect_errors("test_data/parser/inline/err/fn_ret_recovery.rs"); + } + #[test] fn gen_fn() { run_and_expect_errors_with_edition( "test_data/parser/inline/err/gen_fn.rs", diff --git a/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast new file mode 100644 index 0000000000..f6266510bb --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast @@ -0,0 +1,52 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + CLOSURE_EXPR + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" +error 18: expected a curly brace diff --git a/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs new file mode 100644 index 0000000000..7a758ec076 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs @@ -0,0 +1 @@ +fn foo() { || -> A> { let x = 1; } } diff --git a/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast new file mode 100644 index 0000000000..0323df8bf0 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast @@ -0,0 +1,112 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + ERROR + R_BRACK "]" + ERROR + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + ERROR + R_BRACK "]" + ERROR + R_PAREN ")" + WHITESPACE " " + WHERE_CLAUSE + WHERE_KW "where" + WHITESPACE " " + WHERE_PRED + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Copy" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" +error 13: expected a curly brace +error 14: expected a curly brace +error 15: expected a curly brace +error 45: expected a curly brace +error 46: expected a curly brace +error 47: expected a curly brace diff --git a/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs new file mode 100644 index 0000000000..73e3d84d74 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs @@ -0,0 +1,2 @@ +fn foo() -> A>]) { let x = 1; } +fn foo() -> A>]) where T: Copy { let x = 1; }