Skip to content

Commit c57ed9d

Browse files
committed
Auto merge of #61331 - estebank:fn-arg-parse-recovery, r=varkor
Recover gracefully from argument with missing type or param name
2 parents d59dcb2 + e275f2c commit c57ed9d

8 files changed

+182
-40
lines changed

src/libsyntax/parse/diagnostics.rs

+64-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use crate::ast;
21
use crate::ast::{
3-
BlockCheckMode, BinOpKind, Expr, ExprKind, Item, ItemKind, Pat, PatKind, PathSegment, QSelf,
4-
Ty, TyKind, VariantData,
2+
self, Arg, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
3+
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, VariantData,
54
};
65
use crate::parse::{SeqSep, token, PResult, Parser};
76
use crate::parse::parser::{BlockMode, PathStyle, SemiColonMode, TokenType, TokenExpectType};
@@ -12,9 +11,25 @@ use crate::symbol::{kw, sym};
1211
use crate::ThinVec;
1312
use crate::util::parser::AssocOp;
1413
use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
14+
use rustc_data_structures::fx::FxHashSet;
1515
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
1616
use log::{debug, trace};
1717

18+
/// Creates a placeholder argument.
19+
crate fn dummy_arg(ident: Ident) -> Arg {
20+
let pat = P(Pat {
21+
id: ast::DUMMY_NODE_ID,
22+
node: PatKind::Ident(BindingMode::ByValue(Mutability::Immutable), ident, None),
23+
span: ident.span,
24+
});
25+
let ty = Ty {
26+
node: TyKind::Err,
27+
span: ident.span,
28+
id: ast::DUMMY_NODE_ID
29+
};
30+
Arg { ty: P(ty), pat: pat, id: ast::DUMMY_NODE_ID, source: ast::ArgSource::Normal }
31+
}
32+
1833
pub enum Error {
1934
FileNotFoundForModule {
2035
mod_name: String,
@@ -1092,12 +1107,12 @@ impl<'a> Parser<'a> {
10921107
pat: P<ast::Pat>,
10931108
require_name: bool,
10941109
is_trait_item: bool,
1095-
) {
1110+
) -> Option<Ident> {
10961111
// If we find a pattern followed by an identifier, it could be an (incorrect)
10971112
// C-style parameter declaration.
10981113
if self.check_ident() && self.look_ahead(1, |t| {
10991114
*t == token::Comma || *t == token::CloseDelim(token::Paren)
1100-
}) {
1115+
}) { // `fn foo(String s) {}`
11011116
let ident = self.parse_ident().unwrap();
11021117
let span = pat.span.with_hi(ident.span.hi());
11031118

@@ -1107,18 +1122,30 @@ impl<'a> Parser<'a> {
11071122
String::from("<identifier>: <type>"),
11081123
Applicability::HasPlaceholders,
11091124
);
1110-
} else if require_name && is_trait_item {
1111-
if let PatKind::Ident(_, ident, _) = pat.node {
1125+
return Some(ident);
1126+
} else if let PatKind::Ident(_, ident, _) = pat.node {
1127+
if require_name && (
1128+
is_trait_item ||
1129+
self.token == token::Comma ||
1130+
self.token == token::CloseDelim(token::Paren)
1131+
) { // `fn foo(a, b) {}` or `fn foo(usize, usize) {}`
11121132
err.span_suggestion(
11131133
pat.span,
1114-
"explicitly ignore parameter",
1134+
"if this was a parameter name, give it a type",
1135+
format!("{}: TypeName", ident),
1136+
Applicability::HasPlaceholders,
1137+
);
1138+
err.span_suggestion(
1139+
pat.span,
1140+
"if this is a type, explicitly ignore the parameter name",
11151141
format!("_: {}", ident),
11161142
Applicability::MachineApplicable,
11171143
);
1144+
err.note("anonymous parameters are removed in the 2018 edition (see RFC 1685)");
1145+
return Some(ident);
11181146
}
1119-
1120-
err.note("anonymous parameters are removed in the 2018 edition (see RFC 1685)");
11211147
}
1148+
None
11221149
}
11231150

11241151
crate fn recover_arg_parse(&mut self) -> PResult<'a, (P<ast::Pat>, P<ast::Ty>)> {
@@ -1205,4 +1232,31 @@ impl<'a> Parser<'a> {
12051232
err.span_label(span, "expected expression");
12061233
err
12071234
}
1235+
1236+
/// Replace duplicated recovered arguments with `_` pattern to avoid unecessary errors.
1237+
///
1238+
/// This is necessary because at this point we don't know whether we parsed a function with
1239+
/// anonymous arguments or a function with names but no types. In order to minimize
1240+
/// unecessary errors, we assume the arguments are in the shape of `fn foo(a, b, c)` where
1241+
/// the arguments are *names* (so we don't emit errors about not being able to find `b` in
1242+
/// the local scope), but if we find the same name multiple times, like in `fn foo(i8, i8)`,
1243+
/// we deduplicate them to not complain about duplicated argument names.
1244+
crate fn deduplicate_recovered_arg_names(&self, fn_inputs: &mut Vec<Arg>) {
1245+
let mut seen_inputs = FxHashSet::default();
1246+
for input in fn_inputs.iter_mut() {
1247+
let opt_ident = if let (PatKind::Ident(_, ident, _), TyKind::Err) = (
1248+
&input.pat.node, &input.ty.node,
1249+
) {
1250+
Some(*ident)
1251+
} else {
1252+
None
1253+
};
1254+
if let Some(ident) = opt_ident {
1255+
if seen_inputs.contains(&ident) {
1256+
input.pat.node = PatKind::Wild;
1257+
}
1258+
seen_inputs.insert(ident);
1259+
}
1260+
}
1261+
}
12081262
}

src/libsyntax/parse/parser.rs

+17-21
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::parse::PResult;
4747
use crate::ThinVec;
4848
use crate::tokenstream::{self, DelimSpan, TokenTree, TokenStream, TreeAndJoint};
4949
use crate::symbol::{kw, sym, Symbol};
50-
use crate::parse::diagnostics::Error;
50+
use crate::parse::diagnostics::{Error, dummy_arg};
5151

5252
use errors::{Applicability, DiagnosticBuilder, DiagnosticId, FatalError};
5353
use rustc_target::spec::abi::{self, Abi};
@@ -451,22 +451,6 @@ impl From<P<Expr>> for LhsExpr {
451451
}
452452
}
453453

454-
/// Creates a placeholder argument.
455-
fn dummy_arg(span: Span) -> Arg {
456-
let ident = Ident::new(kw::Invalid, span);
457-
let pat = P(Pat {
458-
id: ast::DUMMY_NODE_ID,
459-
node: PatKind::Ident(BindingMode::ByValue(Mutability::Immutable), ident, None),
460-
span,
461-
});
462-
let ty = Ty {
463-
node: TyKind::Err,
464-
span,
465-
id: ast::DUMMY_NODE_ID
466-
};
467-
Arg { ty: P(ty), pat: pat, id: ast::DUMMY_NODE_ID, source: ast::ArgSource::Normal }
468-
}
469-
470454
#[derive(Copy, Clone, Debug)]
471455
crate enum TokenExpectType {
472456
Expect,
@@ -1528,8 +1512,17 @@ impl<'a> Parser<'a> {
15281512
let pat = self.parse_pat(Some("argument name"))?;
15291513

15301514
if let Err(mut err) = self.expect(&token::Colon) {
1531-
self.argument_without_type(&mut err, pat, require_name, is_trait_item);
1532-
return Err(err);
1515+
if let Some(ident) = self.argument_without_type(
1516+
&mut err,
1517+
pat,
1518+
require_name,
1519+
is_trait_item,
1520+
) {
1521+
err.emit();
1522+
return Ok(dummy_arg(ident));
1523+
} else {
1524+
return Err(err);
1525+
}
15331526
}
15341527

15351528
self.eat_incorrect_doc_comment("a method argument's type");
@@ -5432,7 +5425,7 @@ impl<'a> Parser<'a> {
54325425
p.eat_to_tokens(&[&token::Comma, &token::CloseDelim(token::Paren)]);
54335426
// Create a placeholder argument for proper arg count (issue #34264).
54345427
let span = lo.to(p.prev_span);
5435-
Ok(Some(dummy_arg(span)))
5428+
Ok(Some(dummy_arg(Ident::new(kw::Invalid, span))))
54365429
}
54375430
}
54385431
}
@@ -5585,7 +5578,7 @@ impl<'a> Parser<'a> {
55855578

55865579
// Parse the rest of the function parameter list.
55875580
let sep = SeqSep::trailing_allowed(token::Comma);
5588-
let (fn_inputs, recovered) = if let Some(self_arg) = self_arg {
5581+
let (mut fn_inputs, recovered) = if let Some(self_arg) = self_arg {
55895582
if self.check(&token::CloseDelim(token::Paren)) {
55905583
(vec![self_arg], false)
55915584
} else if self.eat(&token::Comma) {
@@ -5608,6 +5601,9 @@ impl<'a> Parser<'a> {
56085601
// Parse closing paren and return type.
56095602
self.expect(&token::CloseDelim(token::Paren))?;
56105603
}
5604+
// Replace duplicated recovered arguments with `_` pattern to avoid unecessary errors.
5605+
self.deduplicate_recovered_arg_names(&mut fn_inputs);
5606+
56115607
Ok(P(FnDecl {
56125608
inputs: fn_inputs,
56135609
output: self.parse_ret_ty(true)?,

src/test/ui/anon-params-denied-2018.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ trait T {
66
fn foo(i32); //~ expected one of `:` or `@`, found `)`
77

88
fn bar_with_default_impl(String, String) {}
9-
//~^ ERROR expected one of `:` or `@`, found `,`
9+
//~^ ERROR expected one of `:`
10+
//~| ERROR expected one of `:`
11+
12+
// do not complain about missing `b`
13+
fn baz(a:usize, b, c: usize) -> usize { //~ ERROR expected one of `:`
14+
a + b + c
15+
}
1016
}
1117

1218
fn main() {}

src/test/ui/anon-params-denied-2018.stderr

+51-7
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,65 @@ error: expected one of `:` or `@`, found `)`
22
--> $DIR/anon-params-denied-2018.rs:6:15
33
|
44
LL | fn foo(i32);
5-
| ---^ expected one of `:` or `@` here
6-
| |
7-
| help: explicitly ignore parameter: `_: i32`
5+
| ^ expected one of `:` or `@` here
86
|
97
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
8+
help: if this was a parameter name, give it a type
9+
|
10+
LL | fn foo(i32: TypeName);
11+
| ^^^^^^^^^^^^^
12+
help: if this is a type, explicitly ignore the parameter name
13+
|
14+
LL | fn foo(_: i32);
15+
| ^^^^^^
1016

1117
error: expected one of `:` or `@`, found `,`
1218
--> $DIR/anon-params-denied-2018.rs:8:36
1319
|
1420
LL | fn bar_with_default_impl(String, String) {}
15-
| ------^ expected one of `:` or `@` here
16-
| |
17-
| help: explicitly ignore parameter: `_: String`
21+
| ^ expected one of `:` or `@` here
22+
|
23+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
24+
help: if this was a parameter name, give it a type
25+
|
26+
LL | fn bar_with_default_impl(String: TypeName, String) {}
27+
| ^^^^^^^^^^^^^^^^
28+
help: if this is a type, explicitly ignore the parameter name
29+
|
30+
LL | fn bar_with_default_impl(_: String, String) {}
31+
| ^^^^^^^^^
32+
33+
error: expected one of `:` or `@`, found `)`
34+
--> $DIR/anon-params-denied-2018.rs:8:44
35+
|
36+
LL | fn bar_with_default_impl(String, String) {}
37+
| ^ expected one of `:` or `@` here
1838
|
1939
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
40+
help: if this was a parameter name, give it a type
41+
|
42+
LL | fn bar_with_default_impl(String, String: TypeName) {}
43+
| ^^^^^^^^^^^^^^^^
44+
help: if this is a type, explicitly ignore the parameter name
45+
|
46+
LL | fn bar_with_default_impl(String, _: String) {}
47+
| ^^^^^^^^^
48+
49+
error: expected one of `:` or `@`, found `,`
50+
--> $DIR/anon-params-denied-2018.rs:13:22
51+
|
52+
LL | fn baz(a:usize, b, c: usize) -> usize {
53+
| ^ expected one of `:` or `@` here
54+
|
55+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
56+
help: if this was a parameter name, give it a type
57+
|
58+
LL | fn baz(a:usize, b: TypeName, c: usize) -> usize {
59+
| ^^^^^^^^^^^
60+
help: if this is a type, explicitly ignore the parameter name
61+
|
62+
LL | fn baz(a:usize, _: b, c: usize) -> usize {
63+
| ^^^^
2064

21-
error: aborting due to 2 previous errors
65+
error: aborting due to 4 previous errors
2266

src/test/ui/parser/inverted-parameters.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ fn pattern((i32, i32) (a, b)) {}
2020

2121
fn fizz(i32) {}
2222
//~^ ERROR expected one of `:` or `@`
23+
//~| HELP if this was a parameter name, give it a type
24+
//~| HELP if this is a type, explicitly ignore the parameter name
2325

2426
fn missing_colon(quux S) {}
2527
//~^ ERROR expected one of `:` or `@`

src/test/ui/parser/inverted-parameters.stderr

+11-1
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@ error: expected one of `:` or `@`, found `)`
3333
|
3434
LL | fn fizz(i32) {}
3535
| ^ expected one of `:` or `@` here
36+
|
37+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
38+
help: if this was a parameter name, give it a type
39+
|
40+
LL | fn fizz(i32: TypeName) {}
41+
| ^^^^^^^^^^^^^
42+
help: if this is a type, explicitly ignore the parameter name
43+
|
44+
LL | fn fizz(_: i32) {}
45+
| ^^^^^^
3646

3747
error: expected one of `:` or `@`, found `S`
38-
--> $DIR/inverted-parameters.rs:24:23
48+
--> $DIR/inverted-parameters.rs:26:23
3949
|
4050
LL | fn missing_colon(quux S) {}
4151
| -----^

src/test/ui/parser/omitted-arg-in-item-fn.stderr

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ error: expected one of `:` or `@`, found `)`
33
|
44
LL | fn foo(x) {
55
| ^ expected one of `:` or `@` here
6+
|
7+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
8+
help: if this was a parameter name, give it a type
9+
|
10+
LL | fn foo(x: TypeName) {
11+
| ^^^^^^^^^^^
12+
help: if this is a type, explicitly ignore the parameter name
13+
|
14+
LL | fn foo(_: x) {
15+
| ^^^^
616

717
error: aborting due to previous error
818

src/test/ui/span/issue-34264.stderr

+20
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,32 @@ error: expected one of `:` or `@`, found `)`
99
|
1010
LL | fn foo(Option<i32>, String) {}
1111
| ^ expected one of `:` or `@` here
12+
|
13+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
14+
help: if this was a parameter name, give it a type
15+
|
16+
LL | fn foo(Option<i32>, String: TypeName) {}
17+
| ^^^^^^^^^^^^^^^^
18+
help: if this is a type, explicitly ignore the parameter name
19+
|
20+
LL | fn foo(Option<i32>, _: String) {}
21+
| ^^^^^^^^^
1222

1323
error: expected one of `:` or `@`, found `,`
1424
--> $DIR/issue-34264.rs:3:9
1525
|
1626
LL | fn bar(x, y: usize) {}
1727
| ^ expected one of `:` or `@` here
28+
|
29+
= note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
30+
help: if this was a parameter name, give it a type
31+
|
32+
LL | fn bar(x: TypeName, y: usize) {}
33+
| ^^^^^^^^^^^
34+
help: if this is a type, explicitly ignore the parameter name
35+
|
36+
LL | fn bar(_: x, y: usize) {}
37+
| ^^^^
1838

1939
error[E0061]: this function takes 2 parameters but 3 parameters were supplied
2040
--> $DIR/issue-34264.rs:7:5

0 commit comments

Comments
 (0)