Skip to content

Commit

Permalink
Rollup merge of #104223 - fmease:recover-fn-ptr-with-generics, r=este…
Browse files Browse the repository at this point in the history
…bank

Recover from function pointer types with generic parameter list

Give a more helpful error when encountering function pointer types with a generic parameter list like `fn<'a>(&'a str) -> bool` or `fn<T>(T) -> T` and suggest moving lifetime parameters to a `for<>` parameter list.

I've added a bunch of extra code to properly handle (unlikely?) corner cases like `for<'a> fn<'b>()` (where there already exists a `for<>` parameter list) correctly suggesting `for<'a, 'b> fn()` (merging the lists). If you deem this useless, I can simplify the code by suggesting nothing at all in this case.

I am quite open to suggestions regarding the wording of the diagnostic messages.

Fixes #103487.
``@rustbot`` label A-diagnostics
r? diagnostics
  • Loading branch information
matthiaskrgr authored Nov 14, 2022
2 parents 8c77da8 + c2b906b commit a86bdb4
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 3 deletions.
9 changes: 9 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/parser.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,12 @@ parser_async_move_order_incorrect = the order of `move` and `async` is incorrect
parser_double_colon_in_bound = expected `:` followed by trait or lifetime
.suggestion = use single colon
parser_fn_ptr_with_generics = function pointer types may not have generic parameters
.suggestion = consider moving the lifetime {$arity ->
[one] parameter
*[other] parameters
} to {$for_param_list_exists ->
[true] the
*[false] a
} `for` parameter list
21 changes: 21 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,3 +1280,24 @@ pub(crate) struct DoubleColonInBound {
#[suggestion(code = ": ", applicability = "machine-applicable")]
pub between: Span,
}

#[derive(Diagnostic)]
#[diag(parser_fn_ptr_with_generics)]
pub(crate) struct FnPtrWithGenerics {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sugg: Option<FnPtrWithGenericsSugg>,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(suggestion, applicability = "maybe-incorrect")]
pub(crate) struct FnPtrWithGenericsSugg {
#[suggestion_part(code = "{snippet}")]
pub left: Span,
pub snippet: String,
#[suggestion_part(code = "")]
pub right: Span,
pub arity: usize,
pub for_param_list_exists: bool,
}
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![feature(array_windows)]
#![feature(box_patterns)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
#![feature(never_type)]
#![feature(rustc_attrs)]
Expand Down
58 changes: 55 additions & 3 deletions compiler/rustc_parse/src/parser/ty.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{Parser, PathStyle, TokenType};

use crate::errors::{FnPtrWithGenerics, FnPtrWithGenericsSugg};
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};

use rustc_ast::ptr::P;
Expand Down Expand Up @@ -270,14 +271,19 @@ impl<'a> Parser<'a> {
TyKind::Infer
} else if self.check_fn_front_matter(false, Case::Sensitive) {
// Function pointer type
self.parse_ty_bare_fn(lo, Vec::new(), recover_return_sign)?
self.parse_ty_bare_fn(lo, Vec::new(), None, recover_return_sign)?
} else if self.check_keyword(kw::For) {
// Function pointer type or bound list (trait object type) starting with a poly-trait.
// `for<'lt> [unsafe] [extern "ABI"] fn (&'lt S) -> T`
// `for<'lt> Trait1<'lt> + Trait2 + 'a`
let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
if self.check_fn_front_matter(false, Case::Sensitive) {
self.parse_ty_bare_fn(lo, lifetime_defs, recover_return_sign)?
self.parse_ty_bare_fn(
lo,
lifetime_defs,
Some(self.prev_token.span.shrink_to_lo()),
recover_return_sign,
)?
} else {
let path = self.parse_path(PathStyle::Type)?;
let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus();
Expand Down Expand Up @@ -519,7 +525,8 @@ impl<'a> Parser<'a> {
fn parse_ty_bare_fn(
&mut self,
lo: Span,
params: Vec<GenericParam>,
mut params: Vec<GenericParam>,
param_insertion_point: Option<Span>,
recover_return_sign: RecoverReturnSign,
) -> PResult<'a, TyKind> {
let inherited_vis = rustc_ast::Visibility {
Expand All @@ -530,6 +537,9 @@ impl<'a> Parser<'a> {
let span_start = self.token.span;
let ast::FnHeader { ext, unsafety, constness, asyncness } =
self.parse_fn_front_matter(&inherited_vis)?;
if self.may_recover() && self.token.kind == TokenKind::Lt {
self.recover_fn_ptr_with_generics(lo, &mut params, param_insertion_point)?;
}
let decl = self.parse_fn_decl(|_| false, AllowPlus::No, recover_return_sign)?;
let whole_span = lo.to(self.prev_token.span);
if let ast::Const::Yes(span) = constness {
Expand All @@ -545,6 +555,48 @@ impl<'a> Parser<'a> {
Ok(TyKind::BareFn(P(BareFnTy { ext, unsafety, generic_params: params, decl, decl_span })))
}

/// Recover from function pointer types with a generic parameter list (e.g. `fn<'a>(&'a str)`).
fn recover_fn_ptr_with_generics(
&mut self,
lo: Span,
params: &mut Vec<GenericParam>,
param_insertion_point: Option<Span>,
) -> PResult<'a, ()> {
let generics = self.parse_generics()?;
let arity = generics.params.len();

let mut lifetimes: Vec<_> = generics
.params
.into_iter()
.filter(|param| matches!(param.kind, ast::GenericParamKind::Lifetime))
.collect();

let sugg = if !lifetimes.is_empty() {
let snippet =
lifetimes.iter().map(|param| param.ident.as_str()).intersperse(", ").collect();

let (left, snippet) = if let Some(span) = param_insertion_point {
(span, if params.is_empty() { snippet } else { format!(", {snippet}") })
} else {
(lo.shrink_to_lo(), format!("for<{snippet}> "))
};

Some(FnPtrWithGenericsSugg {
left,
snippet,
right: generics.span,
arity,
for_param_list_exists: param_insertion_point.is_some(),
})
} else {
None
};

self.sess.emit_err(FnPtrWithGenerics { span: generics.span, sugg });
params.append(&mut lifetimes);
Ok(())
}

/// Emit an error for the given bad function pointer qualifier.
fn error_fn_ptr_bad_qualifier(&self, span: Span, qual_span: Span, qual: &str) {
self.struct_span_err(span, &format!("an `fn` pointer type cannot be `{}`", qual))
Expand Down
31 changes: 31 additions & 0 deletions src/test/ui/parser/recover-fn-ptr-with-generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
fn main() {
type Predicate = fn<'a>(&'a str) -> bool;
//~^ ERROR function pointer types may not have generic parameters

type Identity = fn<T>(T) -> T;
//~^ ERROR function pointer types may not have generic parameters
//~| ERROR cannot find type `T` in this scope
//~| ERROR cannot find type `T` in this scope

let _: fn<const N: usize, 'e, Q, 'f>();
//~^ ERROR function pointer types may not have generic parameters

let _: for<'outer> fn<'inner>();
//~^ ERROR function pointer types may not have generic parameters

let _: for<> fn<'r>();
//~^ ERROR function pointer types may not have generic parameters

type Hmm = fn<>();
//~^ ERROR function pointer types may not have generic parameters

let _: extern fn<'a: 'static>();
//~^ ERROR function pointer types may not have generic parameters
//~| ERROR lifetime bounds cannot be used in this context

let _: for<'any> extern "C" fn<'u>();
//~^ ERROR function pointer types may not have generic parameters

type QuiteBroken = fn<const>();
//~^ ERROR expected identifier, found `>`
}
111 changes: 111 additions & 0 deletions src/test/ui/parser/recover-fn-ptr-with-generics.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:2:24
|
LL | type Predicate = fn<'a>(&'a str) -> bool;
| ^^^^
|
help: consider moving the lifetime parameter to a `for` parameter list
|
LL - type Predicate = fn<'a>(&'a str) -> bool;
LL + type Predicate = for<'a> fn(&'a str) -> bool;
|

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:5:23
|
LL | type Identity = fn<T>(T) -> T;
| ^^^

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:10:14
|
LL | let _: fn<const N: usize, 'e, Q, 'f>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider moving the lifetime parameters to a `for` parameter list
|
LL - let _: fn<const N: usize, 'e, Q, 'f>();
LL + let _: for<'e, 'f> fn();
|

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:13:26
|
LL | let _: for<'outer> fn<'inner>();
| ^^^^^^^^
|
help: consider moving the lifetime parameter to the `for` parameter list
|
LL - let _: for<'outer> fn<'inner>();
LL + let _: for<'outer, 'inner> fn();
|

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:16:20
|
LL | let _: for<> fn<'r>();
| ^^^^
|
help: consider moving the lifetime parameter to the `for` parameter list
|
LL - let _: for<> fn<'r>();
LL + let _: for<'r> fn();
|

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:19:18
|
LL | type Hmm = fn<>();
| ^^

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:22:21
|
LL | let _: extern fn<'a: 'static>();
| ^^^^^^^^^^^^^
|
help: consider moving the lifetime parameter to a `for` parameter list
|
LL - let _: extern fn<'a: 'static>();
LL + let _: for<'a> extern fn();
|

error: function pointer types may not have generic parameters
--> $DIR/recover-fn-ptr-with-generics.rs:26:35
|
LL | let _: for<'any> extern "C" fn<'u>();
| ^^^^
|
help: consider moving the lifetime parameter to the `for` parameter list
|
LL - let _: for<'any> extern "C" fn<'u>();
LL + let _: for<'any, 'u> extern "C" fn();
|

error: expected identifier, found `>`
--> $DIR/recover-fn-ptr-with-generics.rs:29:32
|
LL | type QuiteBroken = fn<const>();
| ^ expected identifier

error: lifetime bounds cannot be used in this context
--> $DIR/recover-fn-ptr-with-generics.rs:22:26
|
LL | let _: extern fn<'a: 'static>();
| ^^^^^^^

error[E0412]: cannot find type `T` in this scope
--> $DIR/recover-fn-ptr-with-generics.rs:5:27
|
LL | type Identity = fn<T>(T) -> T;
| ^ not found in this scope

error[E0412]: cannot find type `T` in this scope
--> $DIR/recover-fn-ptr-with-generics.rs:5:33
|
LL | type Identity = fn<T>(T) -> T;
| ^ not found in this scope

error: aborting due to 12 previous errors

For more information about this error, try `rustc --explain E0412`.

0 comments on commit a86bdb4

Please sign in to comment.