Skip to content

Commit 974e69b

Browse files
committed
Recover from missing slots in delimited parsing
1 parent e865d45 commit 974e69b

16 files changed

+261
-55
lines changed

crates/hir-expand/src/fixup.rs

+14
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,20 @@ fn foo () {a . b ; bar () ;}
631631
)
632632
}
633633

634+
#[test]
635+
fn extraneous_comma() {
636+
check(
637+
r#"
638+
fn foo() {
639+
bar(,);
640+
}
641+
"#,
642+
expect![[r#"
643+
fn foo () {__ra_fixup ;}
644+
"#]],
645+
)
646+
}
647+
634648
#[test]
635649
fn fixup_if_1() {
636650
check(

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

+9-24
Original file line numberDiff line numberDiff line change
@@ -620,30 +620,15 @@ fn arg_list(p: &mut Parser<'_>) {
620620
// fn main() {
621621
// foo(#[attr] 92)
622622
// }
623-
p.bump(T!['(']);
624-
while !p.at(T![')']) && !p.at(EOF) {
625-
if p.at(T![,]) {
626-
// Recover if an argument is missing and only got a delimiter,
627-
// e.g. `(a, , b)`.
628-
p.error("expected expression");
629-
p.bump(T![,]);
630-
continue;
631-
}
632-
633-
if expr(p).is_none() {
634-
break;
635-
}
636-
if !p.at(T![,]) {
637-
if p.at_ts(EXPR_FIRST.union(ATTRIBUTE_FIRST)) {
638-
p.error(format!("expected {:?}", T![,]));
639-
} else {
640-
break;
641-
}
642-
} else {
643-
p.bump(T![,]);
644-
}
645-
}
646-
p.expect(T![')']);
623+
delimited(
624+
p,
625+
T!['('],
626+
T![')'],
627+
T![,],
628+
|| "expected expression".into(),
629+
EXPR_FIRST.union(ATTRIBUTE_FIRST),
630+
|p| expr(p).is_some(),
631+
);
647632
m.complete(p, ARG_LIST);
648633
}
649634

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ SOURCE_FILE
8585
IDENT "a"
8686
COMMA ","
8787
WHITESPACE " "
88-
COMMA ","
88+
ERROR
89+
COMMA ","
8990
WHITESPACE " "
9091
PATH_EXPR
9192
PATH
@@ -101,4 +102,4 @@ error 25: expected identifier
101102
error 39: expected COMMA
102103
error 39: expected expression
103104
error 55: expected expression
104-
error 68: expected expression
105+
error 69: expected expression

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>;

0 commit comments

Comments
 (0)