Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #16124 - ohno418:call-expr-missing-arg, r=Veykril
fix: Recover from missing argument in call expressions Previously, when parsing an argument list with a missing argument (e.g., `(a, , b)` in `foo(a, , b)`), the parser would stop upon an unexpected token (at the second comma in the example), resulting in an incorrect parse tree. This commit improves error handling in such cases, ensuring a more accurate parse tree is built. --- Fixes https://github.com/rust-lang/rust-analyzer/issues/15683.
bors 2024-02-08
parent ae89522 · parent 974e69b · commit 5c80ad0
-rw-r--r--crates/parser/src/grammar.rs15
-rw-r--r--crates/parser/src/grammar/expressions.rs4
-rw-r--r--crates/parser/src/grammar/generic_args.rs12
-rw-r--r--crates/parser/src/grammar/generic_params.rs25
-rw-r--r--crates/parser/src/grammar/items/adt.rs47
-rw-r--r--crates/parser/src/grammar/items/use_item.rs13
-rw-r--r--crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast28
-rw-r--r--crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs1
-rw-r--r--crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast25
-rw-r--r--crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs1
-rw-r--r--crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rast44
-rw-r--r--crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rs2
-rw-r--r--crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rast33
-rw-r--r--crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rs1
-rw-r--r--crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rast45
-rw-r--r--crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rs1
16 files changed, 267 insertions, 30 deletions
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 53fda3ae4f..34715628f1 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -393,11 +393,26 @@ fn delimited(
bra: SyntaxKind,
ket: SyntaxKind,
delim: SyntaxKind,
+ unexpected_delim_message: impl Fn() -> String,
first_set: TokenSet,
mut parser: impl FnMut(&mut Parser<'_>) -> bool,
) {
p.bump(bra);
while !p.at(ket) && !p.at(EOF) {
+ if p.at(delim) {
+ // Recover if an argument is missing and only got a delimiter,
+ // e.g. `(a, , b)`.
+
+ // Wrap the erroneous delimiter in an error node so that fixup logic gets rid of it.
+ // FIXME: Ideally this should be handled in fixup in a structured way, but our list
+ // nodes currently have no concept of a missing node between two delimiters.
+ // So doing it this way is easier.
+ let m = p.start();
+ p.error(unexpected_delim_message());
+ p.bump(delim);
+ m.complete(p, ERROR);
+ continue;
+ }
if !parser(p) {
break;
}
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index e99c111d39..f40c515fa0 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -611,6 +611,7 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
// foo(bar::);
// foo(bar:);
// foo(bar+);
+// foo(a, , b);
// }
fn arg_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
@@ -624,8 +625,9 @@ fn arg_list(p: &mut Parser<'_>) {
T!['('],
T![')'],
T![,],
+ || "expected expression".into(),
EXPR_FIRST.union(ATTRIBUTE_FIRST),
- |p: &mut Parser<'_>| expr(p).is_some(),
+ |p| expr(p).is_some(),
);
m.complete(p, ARG_LIST);
}
diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs
index 211af98e6e..249be2a333 100644
--- a/crates/parser/src/grammar/generic_args.rs
+++ b/crates/parser/src/grammar/generic_args.rs
@@ -1,5 +1,7 @@
use super::*;
+// test_err generic_arg_list_recover
+// type T = T<0, ,T>;
pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: bool) {
let m;
if p.at(T![::]) && p.nth(2) == T![<] {
@@ -11,7 +13,15 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo
return;
}
- delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
+ delimited(
+ p,
+ T![<],
+ T![>],
+ T![,],
+ || "expected generic argument".into(),
+ GENERIC_ARG_FIRST,
+ generic_arg,
+ );
m.complete(p, GENERIC_ARG_LIST);
}
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index 29d9b05d3f..3c577aa3cb 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -10,16 +10,27 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
// test generic_param_list
// fn f<T: Clone>() {}
+
+// test_err generic_param_list_recover
+// fn f<T: Clone,, U:, V>() {}
fn generic_param_list(p: &mut Parser<'_>) {
assert!(p.at(T![<]));
let m = p.start();
- delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| {
- // test generic_param_attribute
- // fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
- let m = p.start();
- attributes::outer_attrs(p);
- generic_param(p, m)
- });
+ delimited(
+ p,
+ T![<],
+ T![>],
+ T![,],
+ || "expected generic parameter".into(),
+ GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST),
+ |p| {
+ // test generic_param_attribute
+ // fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
+ let m = p.start();
+ attributes::outer_attrs(p);
+ generic_param(p, m)
+ },
+ );
m.complete(p, GENERIC_PARAM_LIST);
}
diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs
index 17f41b8e13..21078175c0 100644
--- a/crates/parser/src/grammar/items/adt.rs
+++ b/crates/parser/src/grammar/items/adt.rs
@@ -146,28 +146,39 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
const TUPLE_FIELD_FIRST: TokenSet =
types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST);
+// test_err tuple_field_list_recovery
+// struct S(struct S;
+// struct S(A,,B);
fn tuple_field_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
let m = p.start();
- delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| {
- let m = p.start();
- // test tuple_field_attrs
- // struct S (#[attr] f32);
- attributes::outer_attrs(p);
- let has_vis = opt_visibility(p, true);
- if !p.at_ts(types::TYPE_FIRST) {
- p.error("expected a type");
- if has_vis {
- m.complete(p, ERROR);
- } else {
- m.abandon(p);
+ delimited(
+ p,
+ T!['('],
+ T![')'],
+ T![,],
+ || "expected tuple field".into(),
+ TUPLE_FIELD_FIRST,
+ |p| {
+ let m = p.start();
+ // test tuple_field_attrs
+ // struct S (#[attr] f32);
+ attributes::outer_attrs(p);
+ let has_vis = opt_visibility(p, true);
+ if !p.at_ts(types::TYPE_FIRST) {
+ p.error("expected a type");
+ if has_vis {
+ m.complete(p, ERROR);
+ } else {
+ m.abandon(p);
+ }
+ return false;
}
- return false;
- }
- types::type_(p);
- m.complete(p, TUPLE_FIELD);
- true
- });
+ types::type_(p);
+ m.complete(p, TUPLE_FIELD);
+ true
+ },
+ );
m.complete(p, TUPLE_FIELD_LIST);
}
diff --git a/crates/parser/src/grammar/items/use_item.rs b/crates/parser/src/grammar/items/use_item.rs
index f689c06b31..675a1fd465 100644
--- a/crates/parser/src/grammar/items/use_item.rs
+++ b/crates/parser/src/grammar/items/use_item.rs
@@ -93,9 +93,16 @@ pub(crate) fn use_tree_list(p: &mut Parser<'_>) {
// use b;
// struct T;
// fn test() {}
- delimited(p, T!['{'], T!['}'], T![,], USE_TREE_LIST_FIRST_SET, |p: &mut Parser<'_>| {
- use_tree(p, false) || p.at_ts(USE_TREE_LIST_RECOVERY_SET)
- });
+ // use {a ,, b};
+ delimited(
+ p,
+ T!['{'],
+ T!['}'],
+ T![,],
+ || "expected use tree".into(),
+ USE_TREE_LIST_FIRST_SET,
+ |p: &mut Parser<'_>| use_tree(p, false) || p.at_ts(USE_TREE_LIST_RECOVERY_SET),
+ );
m.complete(p, USE_TREE_LIST);
}
diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
index 5d0fe859c2..cd5aa680c6 100644
--- a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
+++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
@@ -68,6 +68,33 @@ SOURCE_FILE
PLUS "+"
R_PAREN ")"
SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ CALL_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "foo"
+ ARG_LIST
+ L_PAREN "("
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "a"
+ COMMA ","
+ WHITESPACE " "
+ ERROR
+ COMMA ","
+ WHITESPACE " "
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "b"
+ R_PAREN ")"
+ SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
@@ -75,3 +102,4 @@ error 25: expected identifier
error 39: expected COMMA
error 39: expected expression
error 55: expected expression
+error 69: expected expression
diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
index 0e7ac9cc30..175a31f8b5 100644
--- a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
+++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
@@ -2,4 +2,5 @@ fn main() {
foo(bar::);
foo(bar:);
foo(bar+);
+ foo(a, , b);
}
diff --git a/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast b/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast
index cb90b093ba..b576d872e1 100644
--- a/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast
+++ b/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast
@@ -43,4 +43,29 @@ SOURCE_FILE
L_CURLY "{"
R_CURLY "}"
WHITESPACE "\n"
+ USE
+ USE_KW "use"
+ WHITESPACE " "
+ USE_TREE
+ USE_TREE_LIST
+ L_CURLY "{"
+ USE_TREE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "a"
+ WHITESPACE " "
+ COMMA ","
+ ERROR
+ COMMA ","
+ WHITESPACE " "
+ USE_TREE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "b"
+ R_CURLY "}"
+ SEMICOLON ";"
+ WHITESPACE "\n"
error 6: expected R_CURLY
+error 46: expected use tree
diff --git a/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs b/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs
index f16959c25f..9885e6ab27 100644
--- a/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs
+++ b/crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs
@@ -2,3 +2,4 @@ use {a;
use b;
struct T;
fn test() {}
+use {a ,, b};
diff --git a/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rast b/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rast
new file mode 100644
index 0000000000..6b0bfa007e
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rast
@@ -0,0 +1,44 @@
+SOURCE_FILE
+ STRUCT
+ STRUCT_KW "struct"
+ WHITESPACE " "
+ NAME
+ IDENT "S"
+ TUPLE_FIELD_LIST
+ L_PAREN "("
+ STRUCT
+ STRUCT_KW "struct"
+ WHITESPACE " "
+ NAME
+ IDENT "S"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+ STRUCT
+ STRUCT_KW "struct"
+ WHITESPACE " "
+ NAME
+ IDENT "S"
+ TUPLE_FIELD_LIST
+ L_PAREN "("
+ TUPLE_FIELD
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "A"
+ COMMA ","
+ ERROR
+ COMMA ","
+ TUPLE_FIELD
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "B"
+ R_PAREN ")"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+error 9: expected a type
+error 9: expected R_PAREN
+error 9: expected SEMICOLON
+error 30: expected tuple field
diff --git a/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rs b/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rs
new file mode 100644
index 0000000000..ecb4d8bda1
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0029_tuple_field_list_recovery.rs
@@ -0,0 +1,2 @@
+struct S(struct S;
+struct S(A,,B);
diff --git a/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rast b/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rast
new file mode 100644
index 0000000000..4cf5a3386b
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rast
@@ -0,0 +1,33 @@
+SOURCE_FILE
+ TYPE_ALIAS
+ TYPE_KW "type"
+ WHITESPACE " "
+ NAME
+ IDENT "T"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "T"
+ GENERIC_ARG_LIST
+ L_ANGLE "<"
+ CONST_ARG
+ LITERAL
+ INT_NUMBER "0"
+ COMMA ","
+ WHITESPACE " "
+ ERROR
+ COMMA ","
+ TYPE_ARG
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "T"
+ R_ANGLE ">"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+error 14: expected generic argument
diff --git a/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rs b/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rs
new file mode 100644
index 0000000000..7d849aa1be
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0030_generic_arg_list_recover.rs
@@ -0,0 +1 @@
+type T = T<0, ,T>;
diff --git a/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rast b/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rast
new file mode 100644
index 0000000000..0a1ed01fbe
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rast
@@ -0,0 +1,45 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "f"
+ GENERIC_PARAM_LIST
+ L_ANGLE "<"
+ TYPE_PARAM
+ NAME
+ IDENT "T"
+ COLON ":"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Clone"
+ COMMA ","
+ ERROR
+ COMMA ","
+ WHITESPACE " "
+ TYPE_PARAM
+ NAME
+ IDENT "U"
+ COLON ":"
+ TYPE_BOUND_LIST
+ COMMA ","
+ WHITESPACE " "
+ TYPE_PARAM
+ NAME
+ IDENT "V"
+ R_ANGLE ">"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ R_CURLY "}"
+ WHITESPACE "\n"
+error 14: expected generic parameter
diff --git a/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rs b/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rs
new file mode 100644
index 0000000000..2b5149bb0d
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0031_generic_param_list_recover.rs
@@ -0,0 +1 @@
+fn f<T: Clone,, U:, V>() {}