Skip to content

Commit d1741e5

Browse files
committed
Auto merge of #77035 - mibac138:fn-fat-arrow-return, r=davidtwco
Gracefully handle mistyping -> as => in function return type Fixes #77019
2 parents 50a9097 + e916641 commit d1741e5

14 files changed

+255
-25
lines changed

compiler/rustc_parse/src/parser/expr.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::pat::{GateOr, PARAM_EXPECTED};
2-
use super::ty::{AllowPlus, RecoverQPath};
2+
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
33
use super::{BlockMode, Parser, PathStyle, Restrictions, TokenType};
44
use super::{SemiColonMode, SeqSep, TokenExpectType};
55
use crate::maybe_recover_from_interpolated_ty_qpath;
@@ -1647,7 +1647,8 @@ impl<'a> Parser<'a> {
16471647
self.expect_or()?;
16481648
args
16491649
};
1650-
let output = self.parse_ret_ty(AllowPlus::Yes, RecoverQPath::Yes)?;
1650+
let output =
1651+
self.parse_ret_ty(AllowPlus::Yes, RecoverQPath::Yes, RecoverReturnSign::Yes)?;
16511652

16521653
Ok(P(FnDecl { inputs, output }))
16531654
}

compiler/rustc_parse/src/parser/generics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ impl<'a> Parser<'a> {
240240

241241
// Parse type with mandatory colon and (possibly empty) bounds,
242242
// or with mandatory equality sign and the second type.
243-
let ty = self.parse_ty()?;
243+
let ty = self.parse_ty_for_where_clause()?;
244244
if self.eat(&token::Colon) {
245245
let bounds = self.parse_generic_bounds(Some(self.prev_token.span))?;
246246
Ok(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {

compiler/rustc_parse/src/parser/item.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::diagnostics::{dummy_arg, ConsumeClosingDelim, Error};
2-
use super::ty::{AllowPlus, RecoverQPath};
2+
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
33
use super::{FollowedByType, Parser, PathStyle};
44

55
use crate::maybe_whole;
@@ -1549,7 +1549,7 @@ impl<'a> Parser<'a> {
15491549
let header = self.parse_fn_front_matter()?; // `const ... fn`
15501550
let ident = self.parse_ident()?; // `foo`
15511551
let mut generics = self.parse_generics()?; // `<'a, T, ...>`
1552-
let decl = self.parse_fn_decl(req_name, AllowPlus::Yes)?; // `(p: u8, ...)`
1552+
let decl = self.parse_fn_decl(req_name, AllowPlus::Yes, RecoverReturnSign::Yes)?; // `(p: u8, ...)`
15531553
generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
15541554

15551555
let mut sig_hi = self.prev_token.span;
@@ -1680,10 +1680,11 @@ impl<'a> Parser<'a> {
16801680
&mut self,
16811681
req_name: ReqName,
16821682
ret_allow_plus: AllowPlus,
1683+
recover_return_sign: RecoverReturnSign,
16831684
) -> PResult<'a, P<FnDecl>> {
16841685
Ok(P(FnDecl {
16851686
inputs: self.parse_fn_params(req_name)?,
1686-
output: self.parse_ret_ty(ret_allow_plus, RecoverQPath::Yes)?,
1687+
output: self.parse_ret_ty(ret_allow_plus, RecoverQPath::Yes, recover_return_sign)?,
16871688
}))
16881689
}
16891690

compiler/rustc_parse/src/parser/path.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::ty::{AllowPlus, RecoverQPath};
1+
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
22
use super::{Parser, TokenType};
33
use crate::maybe_whole;
44
use rustc_ast::ptr::P;
@@ -231,7 +231,8 @@ impl<'a> Parser<'a> {
231231
// `(T, U) -> R`
232232
let (inputs, _) = self.parse_paren_comma_seq(|p| p.parse_ty())?;
233233
let span = ident.span.to(self.prev_token.span);
234-
let output = self.parse_ret_ty(AllowPlus::No, RecoverQPath::No)?;
234+
let output =
235+
self.parse_ret_ty(AllowPlus::No, RecoverQPath::No, RecoverReturnSign::No)?;
235236
ParenthesizedArgs { inputs, output, span }.into()
236237
};
237238

compiler/rustc_parse/src/parser/ty.rs

+96-9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,37 @@ pub(super) enum RecoverQPath {
4343
No,
4444
}
4545

46+
/// Signals whether parsing a type should recover `->`.
47+
///
48+
/// More specifically, when parsing a function like:
49+
/// ```rust
50+
/// fn foo() => u8 { 0 }
51+
/// fn bar(): u8 { 0 }
52+
/// ```
53+
/// The compiler will try to recover interpreting `foo() => u8` as `foo() -> u8` when calling
54+
/// `parse_ty` with anything except `RecoverReturnSign::No`, and it will try to recover `bar(): u8`
55+
/// as `bar() -> u8` when passing `RecoverReturnSign::Yes` to `parse_ty`
56+
#[derive(Copy, Clone, PartialEq)]
57+
pub(super) enum RecoverReturnSign {
58+
Yes,
59+
OnlyFatArrow,
60+
No,
61+
}
62+
63+
impl RecoverReturnSign {
64+
/// [RecoverReturnSign::Yes] allows for recovering `fn foo() => u8` and `fn foo(): u8`,
65+
/// [RecoverReturnSign::OnlyFatArrow] allows for recovering only `fn foo() => u8` (recovering
66+
/// colons can cause problems when parsing where clauses), and
67+
/// [RecoverReturnSign::No] doesn't allow for any recovery of the return type arrow
68+
fn can_recover(self, token: &TokenKind) -> bool {
69+
match self {
70+
Self::Yes => matches!(token, token::FatArrow | token::Colon),
71+
Self::OnlyFatArrow => matches!(token, token::FatArrow),
72+
Self::No => false,
73+
}
74+
}
75+
}
76+
4677
// Is `...` (`CVarArgs`) legal at this level of type parsing?
4778
#[derive(PartialEq)]
4879
enum AllowCVariadic {
@@ -62,14 +93,24 @@ fn can_continue_type_after_non_fn_ident(t: &Token) -> bool {
6293
impl<'a> Parser<'a> {
6394
/// Parses a type.
6495
pub fn parse_ty(&mut self) -> PResult<'a, P<Ty>> {
65-
self.parse_ty_common(AllowPlus::Yes, RecoverQPath::Yes, AllowCVariadic::No)
96+
self.parse_ty_common(
97+
AllowPlus::Yes,
98+
AllowCVariadic::No,
99+
RecoverQPath::Yes,
100+
RecoverReturnSign::Yes,
101+
)
66102
}
67103

68104
/// Parse a type suitable for a function or function pointer parameter.
69105
/// The difference from `parse_ty` is that this version allows `...`
70106
/// (`CVarArgs`) at the top level of the type.
71107
pub(super) fn parse_ty_for_param(&mut self) -> PResult<'a, P<Ty>> {
72-
self.parse_ty_common(AllowPlus::Yes, RecoverQPath::Yes, AllowCVariadic::Yes)
108+
self.parse_ty_common(
109+
AllowPlus::Yes,
110+
AllowCVariadic::Yes,
111+
RecoverQPath::Yes,
112+
RecoverReturnSign::Yes,
113+
)
73114
}
74115

75116
/// Parses a type in restricted contexts where `+` is not permitted.
@@ -79,18 +120,58 @@ impl<'a> Parser<'a> {
79120
/// Example 2: `value1 as TYPE + value2`
80121
/// `+` is prohibited to avoid interactions with expression grammar.
81122
pub(super) fn parse_ty_no_plus(&mut self) -> PResult<'a, P<Ty>> {
82-
self.parse_ty_common(AllowPlus::No, RecoverQPath::Yes, AllowCVariadic::No)
123+
self.parse_ty_common(
124+
AllowPlus::No,
125+
AllowCVariadic::No,
126+
RecoverQPath::Yes,
127+
RecoverReturnSign::Yes,
128+
)
129+
}
130+
131+
/// Parse a type without recovering `:` as `->` to avoid breaking code such as `where fn() : for<'a>`
132+
pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
133+
self.parse_ty_common(
134+
AllowPlus::Yes,
135+
AllowCVariadic::Yes,
136+
RecoverQPath::Yes,
137+
RecoverReturnSign::OnlyFatArrow,
138+
)
83139
}
84140

85141
/// Parses an optional return type `[ -> TY ]` in a function declaration.
86142
pub(super) fn parse_ret_ty(
87143
&mut self,
88144
allow_plus: AllowPlus,
89145
recover_qpath: RecoverQPath,
146+
recover_return_sign: RecoverReturnSign,
90147
) -> PResult<'a, FnRetTy> {
91148
Ok(if self.eat(&token::RArrow) {
92149
// FIXME(Centril): Can we unconditionally `allow_plus`?
93-
let ty = self.parse_ty_common(allow_plus, recover_qpath, AllowCVariadic::No)?;
150+
let ty = self.parse_ty_common(
151+
allow_plus,
152+
AllowCVariadic::No,
153+
recover_qpath,
154+
recover_return_sign,
155+
)?;
156+
FnRetTy::Ty(ty)
157+
} else if recover_return_sign.can_recover(&self.token.kind) {
158+
// Don't `eat` to prevent `=>` from being added as an expected token which isn't
159+
// actually expected and could only confuse users
160+
self.bump();
161+
self.struct_span_err(self.prev_token.span, "return types are denoted using `->`")
162+
.span_suggestion_short(
163+
self.prev_token.span,
164+
"use `->` instead",
165+
"->".to_string(),
166+
Applicability::MachineApplicable,
167+
)
168+
.emit();
169+
let ty = self.parse_ty_common(
170+
allow_plus,
171+
AllowCVariadic::No,
172+
recover_qpath,
173+
recover_return_sign,
174+
)?;
94175
FnRetTy::Ty(ty)
95176
} else {
96177
FnRetTy::Default(self.token.span.shrink_to_lo())
@@ -100,8 +181,9 @@ impl<'a> Parser<'a> {
100181
fn parse_ty_common(
101182
&mut self,
102183
allow_plus: AllowPlus,
103-
recover_qpath: RecoverQPath,
104184
allow_c_variadic: AllowCVariadic,
185+
recover_qpath: RecoverQPath,
186+
recover_return_sign: RecoverReturnSign,
105187
) -> PResult<'a, P<Ty>> {
106188
let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes;
107189
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
@@ -129,14 +211,14 @@ impl<'a> Parser<'a> {
129211
TyKind::Infer
130212
} else if self.check_fn_front_matter() {
131213
// Function pointer type
132-
self.parse_ty_bare_fn(lo, Vec::new())?
214+
self.parse_ty_bare_fn(lo, Vec::new(), recover_return_sign)?
133215
} else if self.check_keyword(kw::For) {
134216
// Function pointer type or bound list (trait object type) starting with a poly-trait.
135217
// `for<'lt> [unsafe] [extern "ABI"] fn (&'lt S) -> T`
136218
// `for<'lt> Trait1<'lt> + Trait2 + 'a`
137219
let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
138220
if self.check_fn_front_matter() {
139-
self.parse_ty_bare_fn(lo, lifetime_defs)?
221+
self.parse_ty_bare_fn(lo, lifetime_defs, recover_return_sign)?
140222
} else {
141223
let path = self.parse_path(PathStyle::Type)?;
142224
let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus();
@@ -338,9 +420,14 @@ impl<'a> Parser<'a> {
338420
/// Function Style ABI Parameter types
339421
/// ```
340422
/// We actually parse `FnHeader FnDecl`, but we error on `const` and `async` qualifiers.
341-
fn parse_ty_bare_fn(&mut self, lo: Span, params: Vec<GenericParam>) -> PResult<'a, TyKind> {
423+
fn parse_ty_bare_fn(
424+
&mut self,
425+
lo: Span,
426+
params: Vec<GenericParam>,
427+
recover_return_sign: RecoverReturnSign,
428+
) -> PResult<'a, TyKind> {
342429
let ast::FnHeader { ext, unsafety, constness, asyncness } = self.parse_fn_front_matter()?;
343-
let decl = self.parse_fn_decl(|_| false, AllowPlus::No)?;
430+
let decl = self.parse_fn_decl(|_| false, AllowPlus::No, recover_return_sign)?;
344431
let whole_span = lo.to(self.prev_token.span);
345432
if let ast::Const::Yes(span) = constness {
346433
self.error_fn_ptr_bad_qualifier(whole_span, span, "const");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
fn a() -> usize { 0 }
4+
//~^ ERROR return types are denoted using `->`
5+
6+
fn b()-> usize { 0 }
7+
//~^ ERROR return types are denoted using `->`
8+
9+
fn bar(_: u32) {}
10+
11+
fn baz() -> *const dyn Fn(u32) { unimplemented!() }
12+
13+
fn foo() {
14+
match () {
15+
_ if baz() == &bar as &dyn Fn(u32) => (),
16+
() => (),
17+
}
18+
}
19+
20+
fn main() {
21+
let foo = |a: bool| -> bool { a };
22+
//~^ ERROR return types are denoted using `->`
23+
dbg!(foo(false));
24+
25+
let bar = |a: bool|-> bool { a };
26+
//~^ ERROR return types are denoted using `->`
27+
dbg!(bar(false));
28+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
fn a() => usize { 0 }
4+
//~^ ERROR return types are denoted using `->`
5+
6+
fn b(): usize { 0 }
7+
//~^ ERROR return types are denoted using `->`
8+
9+
fn bar(_: u32) {}
10+
11+
fn baz() -> *const dyn Fn(u32) { unimplemented!() }
12+
13+
fn foo() {
14+
match () {
15+
_ if baz() == &bar as &dyn Fn(u32) => (),
16+
() => (),
17+
}
18+
}
19+
20+
fn main() {
21+
let foo = |a: bool| => bool { a };
22+
//~^ ERROR return types are denoted using `->`
23+
dbg!(foo(false));
24+
25+
let bar = |a: bool|: bool { a };
26+
//~^ ERROR return types are denoted using `->`
27+
dbg!(bar(false));
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: return types are denoted using `->`
2+
--> $DIR/fn-recover-return-sign.rs:3:8
3+
|
4+
LL | fn a() => usize { 0 }
5+
| ^^ help: use `->` instead
6+
7+
error: return types are denoted using `->`
8+
--> $DIR/fn-recover-return-sign.rs:6:7
9+
|
10+
LL | fn b(): usize { 0 }
11+
| ^ help: use `->` instead
12+
13+
error: return types are denoted using `->`
14+
--> $DIR/fn-recover-return-sign.rs:21:25
15+
|
16+
LL | let foo = |a: bool| => bool { a };
17+
| ^^ help: use `->` instead
18+
19+
error: return types are denoted using `->`
20+
--> $DIR/fn-recover-return-sign.rs:25:24
21+
|
22+
LL | let bar = |a: bool|: bool { a };
23+
| ^ help: use `->` instead
24+
25+
error: aborting due to 4 previous errors
26+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Separate test file because `Fn() => bool` isn't getting fixed and rustfix complained that
2+
// even though a fix was applied the code was still incorrect
3+
4+
fn foo() => impl Fn() => bool {
5+
//~^ ERROR return types are denoted using `->`
6+
//~| ERROR expected one of `+`, `->`, `::`, `;`, `where`, or `{`, found `=>`
7+
unimplemented!()
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: return types are denoted using `->`
2+
--> $DIR/fn-recover-return-sign2.rs:4:10
3+
|
4+
LL | fn foo() => impl Fn() => bool {
5+
| ^^ help: use `->` instead
6+
7+
error: expected one of `+`, `->`, `::`, `;`, `where`, or `{`, found `=>`
8+
--> $DIR/fn-recover-return-sign2.rs:4:23
9+
|
10+
LL | fn foo() => impl Fn() => bool {
11+
| ^^ expected one of `+`, `->`, `::`, `;`, `where`, or `{`
12+
13+
error: aborting due to 2 previous errors
14+

src/test/ui/parser/fn-colon-return-type.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
fn foo(x: i32): i32 { //~ ERROR expected one of `->`, `;`, `where`, or `{`, found `:`
1+
fn foo(x: i32): i32 {
2+
//~^ ERROR return types are denoted using `->`
23
x
34
}
45

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `->`, `;`, `where`, or `{`, found `:`
1+
error: return types are denoted using `->`
22
--> $DIR/fn-colon-return-type.rs:1:15
33
|
44
LL | fn foo(x: i32): i32 {
5-
| ^ expected one of `->`, `;`, `where`, or `{`
5+
| ^ help: use `->` instead
66

77
error: aborting due to previous error
88

src/test/ui/parser/not-a-pred.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
fn f(a: isize, b: isize) : lt(a, b) { }
2-
//~^ ERROR expected one of `->`, `;`, `where`, or `{`, found `:`
2+
//~^ ERROR return types are denoted using `->`
3+
//~| ERROR expected type, found function `lt` [E0573]
4+
//~| ERROR expected type, found local variable `a` [E0573]
5+
//~| ERROR expected type, found local variable `b` [E0573]
36

47
fn lt(a: isize, b: isize) { }
58

6-
fn main() { let a: isize = 10; let b: isize = 23; check (lt(a, b)); f(a, b); }
9+
fn main() {
10+
let a: isize = 10;
11+
let b: isize = 23;
12+
check (lt(a, b));
13+
//~^ ERROR cannot find function `check` in this scope [E0425]
14+
f(a, b);
15+
}

0 commit comments

Comments
 (0)