Skip to content

Commit 5c80ad0

Browse files
committed
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 #15683.
2 parents ae89522 + 974e69b commit 5c80ad0

16 files changed

+267
-30
lines changed

crates/parser/src/grammar.rs

+15
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,26 @@ fn delimited(
393393
bra: SyntaxKind,
394394
ket: SyntaxKind,
395395
delim: SyntaxKind,
396+
unexpected_delim_message: impl Fn() -> String,
396397
first_set: TokenSet,
397398
mut parser: impl FnMut(&mut Parser<'_>) -> bool,
398399
) {
399400
p.bump(bra);
400401
while !p.at(ket) && !p.at(EOF) {
402+
if p.at(delim) {
403+
// Recover if an argument is missing and only got a delimiter,
404+
// e.g. `(a, , b)`.
405+
406+
// Wrap the erroneous delimiter in an error node so that fixup logic gets rid of it.
407+
// FIXME: Ideally this should be handled in fixup in a structured way, but our list
408+
// nodes currently have no concept of a missing node between two delimiters.
409+
// So doing it this way is easier.
410+
let m = p.start();
411+
p.error(unexpected_delim_message());
412+
p.bump(delim);
413+
m.complete(p, ERROR);
414+
continue;
415+
}
401416
if !parser(p) {
402417
break;
403418
}

crates/parser/src/grammar/expressions.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
611611
// foo(bar::);
612612
// foo(bar:);
613613
// foo(bar+);
614+
// foo(a, , b);
614615
// }
615616
fn arg_list(p: &mut Parser<'_>) {
616617
assert!(p.at(T!['(']));
@@ -624,8 +625,9 @@ fn arg_list(p: &mut Parser<'_>) {
624625
T!['('],
625626
T![')'],
626627
T![,],
628+
|| "expected expression".into(),
627629
EXPR_FIRST.union(ATTRIBUTE_FIRST),
628-
|p: &mut Parser<'_>| expr(p).is_some(),
630+
|p| expr(p).is_some(),
629631
);
630632
m.complete(p, ARG_LIST);
631633
}

crates/parser/src/grammar/generic_args.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use super::*;
22

3+
// test_err generic_arg_list_recover
4+
// type T = T<0, ,T>;
35
pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: bool) {
46
let m;
57
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
1113
return;
1214
}
1315

14-
delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
16+
delimited(
17+
p,
18+
T![<],
19+
T![>],
20+
T![,],
21+
|| "expected generic argument".into(),
22+
GENERIC_ARG_FIRST,
23+
generic_arg,
24+
);
1525
m.complete(p, GENERIC_ARG_LIST);
1626
}
1727

crates/parser/src/grammar/generic_params.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,27 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
1010

1111
// test generic_param_list
1212
// fn f<T: Clone>() {}
13+
14+
// test_err generic_param_list_recover
15+
// fn f<T: Clone,, U:, V>() {}
1316
fn generic_param_list(p: &mut Parser<'_>) {
1417
assert!(p.at(T![<]));
1518
let m = p.start();
16-
delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| {
17-
// test generic_param_attribute
18-
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
19-
let m = p.start();
20-
attributes::outer_attrs(p);
21-
generic_param(p, m)
22-
});
19+
delimited(
20+
p,
21+
T![<],
22+
T![>],
23+
T![,],
24+
|| "expected generic parameter".into(),
25+
GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST),
26+
|p| {
27+
// test generic_param_attribute
28+
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
29+
let m = p.start();
30+
attributes::outer_attrs(p);
31+
generic_param(p, m)
32+
},
33+
);
2334

2435
m.complete(p, GENERIC_PARAM_LIST);
2536
}

crates/parser/src/grammar/items/adt.rs

+29-18
Original file line numberDiff line numberDiff line change
@@ -146,28 +146,39 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
146146
const TUPLE_FIELD_FIRST: TokenSet =
147147
types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST);
148148

149+
// test_err tuple_field_list_recovery
150+
// struct S(struct S;
151+
// struct S(A,,B);
149152
fn tuple_field_list(p: &mut Parser<'_>) {
150153
assert!(p.at(T!['(']));
151154
let m = p.start();
152-
delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| {
153-
let m = p.start();
154-
// test tuple_field_attrs
155-
// struct S (#[attr] f32);
156-
attributes::outer_attrs(p);
157-
let has_vis = opt_visibility(p, true);
158-
if !p.at_ts(types::TYPE_FIRST) {
159-
p.error("expected a type");
160-
if has_vis {
161-
m.complete(p, ERROR);
162-
} else {
163-
m.abandon(p);
155+
delimited(
156+
p,
157+
T!['('],
158+
T![')'],
159+
T![,],
160+
|| "expected tuple field".into(),
161+
TUPLE_FIELD_FIRST,
162+
|p| {
163+
let m = p.start();
164+
// test tuple_field_attrs
165+
// struct S (#[attr] f32);
166+
attributes::outer_attrs(p);
167+
let has_vis = opt_visibility(p, true);
168+
if !p.at_ts(types::TYPE_FIRST) {
169+
p.error("expected a type");
170+
if has_vis {
171+
m.complete(p, ERROR);
172+
} else {
173+
m.abandon(p);
174+
}
175+
return false;
164176
}
165-
return false;
166-
}
167-
types::type_(p);
168-
m.complete(p, TUPLE_FIELD);
169-
true
170-
});
177+
types::type_(p);
178+
m.complete(p, TUPLE_FIELD);
179+
true
180+
},
181+
);
171182

172183
m.complete(p, TUPLE_FIELD_LIST);
173184
}

crates/parser/src/grammar/items/use_item.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,16 @@ pub(crate) fn use_tree_list(p: &mut Parser<'_>) {
9393
// use b;
9494
// struct T;
9595
// fn test() {}
96-
delimited(p, T!['{'], T!['}'], T![,], USE_TREE_LIST_FIRST_SET, |p: &mut Parser<'_>| {
97-
use_tree(p, false) || p.at_ts(USE_TREE_LIST_RECOVERY_SET)
98-
});
96+
// use {a ,, b};
97+
delimited(
98+
p,
99+
T!['{'],
100+
T!['}'],
101+
T![,],
102+
|| "expected use tree".into(),
103+
USE_TREE_LIST_FIRST_SET,
104+
|p: &mut Parser<'_>| use_tree(p, false) || p.at_ts(USE_TREE_LIST_RECOVERY_SET),
105+
);
99106

100107
m.complete(p, USE_TREE_LIST);
101108
}

crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast

+28
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,38 @@ SOURCE_FILE
6868
PLUS "+"
6969
R_PAREN ")"
7070
SEMICOLON ";"
71+
WHITESPACE "\n "
72+
EXPR_STMT
73+
CALL_EXPR
74+
PATH_EXPR
75+
PATH
76+
PATH_SEGMENT
77+
NAME_REF
78+
IDENT "foo"
79+
ARG_LIST
80+
L_PAREN "("
81+
PATH_EXPR
82+
PATH
83+
PATH_SEGMENT
84+
NAME_REF
85+
IDENT "a"
86+
COMMA ","
87+
WHITESPACE " "
88+
ERROR
89+
COMMA ","
90+
WHITESPACE " "
91+
PATH_EXPR
92+
PATH
93+
PATH_SEGMENT
94+
NAME_REF
95+
IDENT "b"
96+
R_PAREN ")"
97+
SEMICOLON ";"
7198
WHITESPACE "\n"
7299
R_CURLY "}"
73100
WHITESPACE "\n"
74101
error 25: expected identifier
75102
error 39: expected COMMA
76103
error 39: expected expression
77104
error 55: expected expression
105+
error 69: expected expression

crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ fn main() {
22
foo(bar::);
33
foo(bar:);
44
foo(bar+);
5+
foo(a, , b);
56
}

crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rast

+25
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,29 @@ SOURCE_FILE
4343
L_CURLY "{"
4444
R_CURLY "}"
4545
WHITESPACE "\n"
46+
USE
47+
USE_KW "use"
48+
WHITESPACE " "
49+
USE_TREE
50+
USE_TREE_LIST
51+
L_CURLY "{"
52+
USE_TREE
53+
PATH
54+
PATH_SEGMENT
55+
NAME_REF
56+
IDENT "a"
57+
WHITESPACE " "
58+
COMMA ","
59+
ERROR
60+
COMMA ","
61+
WHITESPACE " "
62+
USE_TREE
63+
PATH
64+
PATH_SEGMENT
65+
NAME_REF
66+
IDENT "b"
67+
R_CURLY "}"
68+
SEMICOLON ";"
69+
WHITESPACE "\n"
4670
error 6: expected R_CURLY
71+
error 46: expected use tree

crates/parser/test_data/parser/inline/err/0026_use_tree_list_err_recovery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ use {a;
22
use b;
33
struct T;
44
fn test() {}
5+
use {a ,, b};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
SOURCE_FILE
2+
STRUCT
3+
STRUCT_KW "struct"
4+
WHITESPACE " "
5+
NAME
6+
IDENT "S"
7+
TUPLE_FIELD_LIST
8+
L_PAREN "("
9+
STRUCT
10+
STRUCT_KW "struct"
11+
WHITESPACE " "
12+
NAME
13+
IDENT "S"
14+
SEMICOLON ";"
15+
WHITESPACE "\n"
16+
STRUCT
17+
STRUCT_KW "struct"
18+
WHITESPACE " "
19+
NAME
20+
IDENT "S"
21+
TUPLE_FIELD_LIST
22+
L_PAREN "("
23+
TUPLE_FIELD
24+
PATH_TYPE
25+
PATH
26+
PATH_SEGMENT
27+
NAME_REF
28+
IDENT "A"
29+
COMMA ","
30+
ERROR
31+
COMMA ","
32+
TUPLE_FIELD
33+
PATH_TYPE
34+
PATH
35+
PATH_SEGMENT
36+
NAME_REF
37+
IDENT "B"
38+
R_PAREN ")"
39+
SEMICOLON ";"
40+
WHITESPACE "\n"
41+
error 9: expected a type
42+
error 9: expected R_PAREN
43+
error 9: expected SEMICOLON
44+
error 30: expected tuple field
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
struct S(struct S;
2+
struct S(A,,B);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
SOURCE_FILE
2+
TYPE_ALIAS
3+
TYPE_KW "type"
4+
WHITESPACE " "
5+
NAME
6+
IDENT "T"
7+
WHITESPACE " "
8+
EQ "="
9+
WHITESPACE " "
10+
PATH_TYPE
11+
PATH
12+
PATH_SEGMENT
13+
NAME_REF
14+
IDENT "T"
15+
GENERIC_ARG_LIST
16+
L_ANGLE "<"
17+
CONST_ARG
18+
LITERAL
19+
INT_NUMBER "0"
20+
COMMA ","
21+
WHITESPACE " "
22+
ERROR
23+
COMMA ","
24+
TYPE_ARG
25+
PATH_TYPE
26+
PATH
27+
PATH_SEGMENT
28+
NAME_REF
29+
IDENT "T"
30+
R_ANGLE ">"
31+
SEMICOLON ";"
32+
WHITESPACE "\n"
33+
error 14: expected generic argument
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type T = T<0, ,T>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
SOURCE_FILE
2+
FN
3+
FN_KW "fn"
4+
WHITESPACE " "
5+
NAME
6+
IDENT "f"
7+
GENERIC_PARAM_LIST
8+
L_ANGLE "<"
9+
TYPE_PARAM
10+
NAME
11+
IDENT "T"
12+
COLON ":"
13+
WHITESPACE " "
14+
TYPE_BOUND_LIST
15+
TYPE_BOUND
16+
PATH_TYPE
17+
PATH
18+
PATH_SEGMENT
19+
NAME_REF
20+
IDENT "Clone"
21+
COMMA ","
22+
ERROR
23+
COMMA ","
24+
WHITESPACE " "
25+
TYPE_PARAM
26+
NAME
27+
IDENT "U"
28+
COLON ":"
29+
TYPE_BOUND_LIST
30+
COMMA ","
31+
WHITESPACE " "
32+
TYPE_PARAM
33+
NAME
34+
IDENT "V"
35+
R_ANGLE ">"
36+
PARAM_LIST
37+
L_PAREN "("
38+
R_PAREN ")"
39+
WHITESPACE " "
40+
BLOCK_EXPR
41+
STMT_LIST
42+
L_CURLY "{"
43+
R_CURLY "}"
44+
WHITESPACE "\n"
45+
error 14: expected generic parameter
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn f<T: Clone,, U:, V>() {}

0 commit comments

Comments
 (0)