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.
This commit is contained in:
bors 2024-02-08 10:16:36 +00:00
commit 5c80ad05ae
16 changed files with 267 additions and 30 deletions

View file

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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

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

View file

@ -2,4 +2,5 @@ fn main() {
foo(bar::);
foo(bar:);
foo(bar+);
foo(a, , b);
}

View file

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

View file

@ -2,3 +2,4 @@ use {a;
use b;
struct T;
fn test() {}
use {a ,, b};

View file

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

View file

@ -0,0 +1,2 @@
struct S(struct S;
struct S(A,,B);

View file

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

View file

@ -0,0 +1 @@
type T = T<0, ,T>;

View file

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

View file

@ -0,0 +1 @@
fn f<T: Clone,, U:, V>() {}