Skip to content

Commit e5281c3

Browse files
committed
Provide a better error for Fn traits with lifetime params
Currently, given `Fn`-family traits with lifetime params like `Fn<'a>(&'a str) -> bool`, many unhelpful errors show up. These are a bit confusing. This commit allows these situations to suggest simply using higher-ranked trait bounds like `for<'a> Fn(&'a str) -> bool`.
1 parent b15ca66 commit e5281c3

File tree

4 files changed

+176
-3
lines changed

4 files changed

+176
-3
lines changed

compiler/rustc_parse/src/parser/item.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,7 @@ impl<'a> Parser<'a> {
24232423
}
24242424

24252425
/// Parses the parameter list of a function, including the `(` and `)` delimiters.
2426-
fn parse_fn_params(&mut self, req_name: ReqName) -> PResult<'a, Vec<Param>> {
2426+
pub(super) fn parse_fn_params(&mut self, req_name: ReqName) -> PResult<'a, Vec<Param>> {
24272427
let mut first_param = true;
24282428
// Parse the arguments, starting out with `self` being allowed...
24292429
let (mut params, _) = self.parse_paren_comma_seq(|p| {

compiler/rustc_parse/src/parser/ty.rs

+93-2
Original file line numberDiff line numberDiff line change
@@ -933,15 +933,20 @@ impl<'a> Parser<'a> {
933933
has_parens: bool,
934934
modifiers: BoundModifiers,
935935
) -> PResult<'a, GenericBound> {
936-
let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
937-
let path = if self.token.is_keyword(kw::Fn)
936+
let mut lifetime_defs = self.parse_late_bound_lifetime_defs()?;
937+
let mut path = if self.token.is_keyword(kw::Fn)
938938
&& self.look_ahead(1, |tok| tok.kind == TokenKind::OpenDelim(Delimiter::Parenthesis))
939939
&& let Some(path) = self.recover_path_from_fn()
940940
{
941941
path
942942
} else {
943943
self.parse_path(PathStyle::Type)?
944944
};
945+
946+
if self.may_recover() && self.token == TokenKind::OpenDelim(Delimiter::Parenthesis) {
947+
self.recover_fn_trait_with_lifetime_params(&mut path, &mut lifetime_defs)?;
948+
}
949+
945950
if has_parens {
946951
if self.token.is_like_plus() {
947952
// Someone has written something like `&dyn (Trait + Other)`. The correct code
@@ -1016,6 +1021,92 @@ impl<'a> Parser<'a> {
10161021
}
10171022
}
10181023

1024+
/// Recover from `Fn`-family traits (Fn, FnMut, FnOnce) with lifetime arguments
1025+
/// (e.g. `FnOnce<'a>(&'a str) -> bool`). Up to generic arguments have already
1026+
/// been eaten.
1027+
fn recover_fn_trait_with_lifetime_params(
1028+
&mut self,
1029+
fn_path: &mut ast::Path,
1030+
lifetime_defs: &mut Vec<GenericParam>,
1031+
) -> PResult<'a, ()> {
1032+
let fn_path_segment = fn_path.segments.last_mut().unwrap();
1033+
let generic_args = if let Some(p_args) = &fn_path_segment.args {
1034+
p_args.clone().into_inner()
1035+
} else {
1036+
// Normally it wouldn't come here because the upstream should have parsed
1037+
// generic parameters (otherwise it's impossible to call this function).
1038+
return Ok(());
1039+
};
1040+
let lifetimes =
1041+
if let ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { span: _, args }) =
1042+
&generic_args
1043+
{
1044+
args.into_iter()
1045+
.filter_map(|arg| {
1046+
if let ast::AngleBracketedArg::Arg(generic_arg) = arg
1047+
&& let ast::GenericArg::Lifetime(lifetime) = generic_arg {
1048+
Some(lifetime)
1049+
} else {
1050+
None
1051+
}
1052+
})
1053+
.collect()
1054+
} else {
1055+
Vec::new()
1056+
};
1057+
// Only try to recover if the trait has lifetime params.
1058+
if lifetimes.is_empty() {
1059+
return Ok(());
1060+
}
1061+
1062+
// Parse `(T, U) -> R`.
1063+
let inputs_lo = self.token.span;
1064+
let inputs: Vec<_> =
1065+
self.parse_fn_params(|_| false)?.into_iter().map(|input| input.ty).collect();
1066+
let inputs_span = inputs_lo.to(self.prev_token.span);
1067+
let output = self.parse_ret_ty(AllowPlus::No, RecoverQPath::No, RecoverReturnSign::No)?;
1068+
let args = ast::ParenthesizedArgs {
1069+
span: fn_path_segment.span().to(self.prev_token.span),
1070+
inputs,
1071+
inputs_span,
1072+
output,
1073+
}
1074+
.into();
1075+
*fn_path_segment =
1076+
ast::PathSegment { ident: fn_path_segment.ident, args, id: ast::DUMMY_NODE_ID };
1077+
1078+
// Convert parsed `<'a>` in `Fn<'a>` into `for<'a>`.
1079+
let mut generic_params = lifetimes
1080+
.iter()
1081+
.map(|lt| GenericParam {
1082+
id: lt.id,
1083+
ident: lt.ident,
1084+
attrs: ast::AttrVec::new(),
1085+
bounds: Vec::new(),
1086+
is_placeholder: false,
1087+
kind: ast::GenericParamKind::Lifetime,
1088+
colon_span: None,
1089+
})
1090+
.collect::<Vec<GenericParam>>();
1091+
lifetime_defs.append(&mut generic_params);
1092+
1093+
let generic_args_span = generic_args.span();
1094+
let mut err =
1095+
self.struct_span_err(generic_args_span, "`Fn` traits cannot take lifetime parameters");
1096+
let snippet = format!(
1097+
"for<{}> ",
1098+
lifetimes.iter().map(|lt| lt.ident.as_str()).intersperse(", ").collect::<String>(),
1099+
);
1100+
let before_fn_path = fn_path.span.shrink_to_lo();
1101+
err.multipart_suggestion(
1102+
"consider using a higher-ranked trait bound instead",
1103+
vec![(generic_args_span, "".to_owned()), (before_fn_path, snippet)],
1104+
Applicability::MaybeIncorrect,
1105+
)
1106+
.emit();
1107+
Ok(())
1108+
}
1109+
10191110
pub(super) fn check_lifetime(&mut self) -> bool {
10201111
self.expected_tokens.push(TokenType::Lifetime);
10211112
self.token.is_lifetime()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Test that Fn-family traits with lifetime parameters shouldn't compile and
2+
// we suggest the usage of higher-rank trait bounds instead.
3+
4+
fn fa(_: impl Fn<'a>(&'a str) -> bool) {}
5+
//~^ ERROR `Fn` traits cannot take lifetime parameters
6+
7+
fn fb(_: impl FnMut<'a, 'b>(&'a str, &'b str) -> bool) {}
8+
//~^ ERROR `Fn` traits cannot take lifetime parameters
9+
10+
fn fc(_: impl std::fmt::Display + FnOnce<'a>(&'a str) -> bool + std::fmt::Debug) {}
11+
//~^ ERROR `Fn` traits cannot take lifetime parameters
12+
13+
use std::ops::Fn as AliasedFn;
14+
fn fd(_: impl AliasedFn<'a>(&'a str) -> bool) {}
15+
//~^ ERROR `Fn` traits cannot take lifetime parameters
16+
17+
fn fe<F>(_: F) where F: Fn<'a>(&'a str) -> bool {}
18+
//~^ ERROR `Fn` traits cannot take lifetime parameters
19+
20+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
error: `Fn` traits cannot take lifetime parameters
2+
--> $DIR/hrtb-malformed-lifetime-generics.rs:4:17
3+
|
4+
LL | fn fa(_: impl Fn<'a>(&'a str) -> bool) {}
5+
| ^^^^
6+
|
7+
help: consider using a higher-ranked trait bound instead
8+
|
9+
LL - fn fa(_: impl Fn<'a>(&'a str) -> bool) {}
10+
LL + fn fa(_: impl for<'a> Fn(&'a str) -> bool) {}
11+
|
12+
13+
error: `Fn` traits cannot take lifetime parameters
14+
--> $DIR/hrtb-malformed-lifetime-generics.rs:7:20
15+
|
16+
LL | fn fb(_: impl FnMut<'a, 'b>(&'a str, &'b str) -> bool) {}
17+
| ^^^^^^^^
18+
|
19+
help: consider using a higher-ranked trait bound instead
20+
|
21+
LL - fn fb(_: impl FnMut<'a, 'b>(&'a str, &'b str) -> bool) {}
22+
LL + fn fb(_: impl for<'a, 'b> FnMut(&'a str, &'b str) -> bool) {}
23+
|
24+
25+
error: `Fn` traits cannot take lifetime parameters
26+
--> $DIR/hrtb-malformed-lifetime-generics.rs:10:41
27+
|
28+
LL | fn fc(_: impl std::fmt::Display + FnOnce<'a>(&'a str) -> bool + std::fmt::Debug) {}
29+
| ^^^^
30+
|
31+
help: consider using a higher-ranked trait bound instead
32+
|
33+
LL - fn fc(_: impl std::fmt::Display + FnOnce<'a>(&'a str) -> bool + std::fmt::Debug) {}
34+
LL + fn fc(_: impl std::fmt::Display + for<'a> FnOnce(&'a str) -> bool + std::fmt::Debug) {}
35+
|
36+
37+
error: `Fn` traits cannot take lifetime parameters
38+
--> $DIR/hrtb-malformed-lifetime-generics.rs:14:24
39+
|
40+
LL | fn fd(_: impl AliasedFn<'a>(&'a str) -> bool) {}
41+
| ^^^^
42+
|
43+
help: consider using a higher-ranked trait bound instead
44+
|
45+
LL - fn fd(_: impl AliasedFn<'a>(&'a str) -> bool) {}
46+
LL + fn fd(_: impl for<'a> AliasedFn(&'a str) -> bool) {}
47+
|
48+
49+
error: `Fn` traits cannot take lifetime parameters
50+
--> $DIR/hrtb-malformed-lifetime-generics.rs:17:27
51+
|
52+
LL | fn fe<F>(_: F) where F: Fn<'a>(&'a str) -> bool {}
53+
| ^^^^
54+
|
55+
help: consider using a higher-ranked trait bound instead
56+
|
57+
LL - fn fe<F>(_: F) where F: Fn<'a>(&'a str) -> bool {}
58+
LL + fn fe<F>(_: F) where F: for<'a> Fn(&'a str) -> bool {}
59+
|
60+
61+
error: aborting due to 5 previous errors
62+

0 commit comments

Comments
 (0)