about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-02-08 10:16:36 +0000
committerbors <bors@rust-lang.org>2024-02-08 10:16:36 +0000
commit5c80ad05aed33d754c0ed7ba7bafae088f73cf04 (patch)
tree31ca0bdf20547d8d9d5f7a4daee0d65bfabb9b01
parentae89522928a93d8a7f64ebe4a485ca7d088c2829 (diff)
parent974e69b0c5e6455fb72061316b93026763375d86 (diff)
downloadrust-5c80ad05aed33d754c0ed7ba7bafae088f73cf04.tar.gz
rust-5c80ad05aed33d754c0ed7ba7bafae088f73cf04.zip
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.
-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 53fda3ae4fd..34715628f18 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 e99c111d39e..f40c515fa07 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 211af98e6ef..249be2a3335 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 29d9b05d3f3..3c577aa3cb4 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 17f41b8e13a..21078175c0e 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 f689c06b31c..675a1fd4650 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 5d0fe859c29..cd5aa680c65 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 0e7ac9cc307..175a31f8b58 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 cb90b093ba0..b576d872e13 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 f16959c25f2..9885e6ab273 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 00000000000..6b0bfa007e3
--- /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 00000000000..ecb4d8bda14
--- /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 00000000000..4cf5a3386b9
--- /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 00000000000..7d849aa1bee
--- /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 00000000000..0a1ed01fbe6
--- /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 00000000000..2b5149bb0dc
--- /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>() {}