1
Fork 0

Improve parse errors for lifetimes in type position

This commit is contained in:
León Orell Valerian Liehr 2025-04-14 20:48:51 +02:00
parent 97c966bb40
commit 8887af72a0
No known key found for this signature in database
GPG key ID: D17A07215F68E713
14 changed files with 234 additions and 42 deletions

View file

@ -642,7 +642,9 @@ parse_mut_on_nested_ident_pattern = `mut` must be attached to each individual bi
.suggestion = add `mut` to each binding .suggestion = add `mut` to each binding
parse_mut_on_non_ident_pattern = `mut` must be followed by a named binding parse_mut_on_non_ident_pattern = `mut` must be followed by a named binding
.suggestion = remove the `mut` prefix .suggestion = remove the `mut` prefix
parse_need_plus_after_trait_object_lifetime = lifetime in trait object type must be followed by `+`
parse_need_plus_after_trait_object_lifetime = lifetimes must be followed by `+` to form a trait object type
.suggestion = consider adding a trait bound after the potential lifetime bound
parse_nested_adt = `{$kw_str}` definition cannot be nested inside `{$keyword}` parse_nested_adt = `{$kw_str}` definition cannot be nested inside `{$keyword}`
.suggestion = consider creating a new `{$kw_str}` definition instead of nesting .suggestion = consider creating a new `{$kw_str}` definition instead of nesting

View file

@ -2806,6 +2806,8 @@ pub(crate) struct ReturnTypesUseThinArrow {
pub(crate) struct NeedPlusAfterTraitObjectLifetime { pub(crate) struct NeedPlusAfterTraitObjectLifetime {
#[primary_span] #[primary_span]
pub span: Span, pub span: Span,
#[suggestion(code = " + /* Trait */", applicability = "has-placeholders")]
pub suggestion: Span,
} }
#[derive(Diagnostic)] #[derive(Diagnostic)]

View file

@ -7,7 +7,7 @@ use rustc_ast::{
Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty, Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty,
TyKind, UnsafeBinderTy, TyKind, UnsafeBinderTy,
}; };
use rustc_errors::{Applicability, PResult}; use rustc_errors::{Applicability, Diag, PResult};
use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym}; use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
use thin_vec::{ThinVec, thin_vec}; use thin_vec::{ThinVec, thin_vec};
@ -411,6 +411,9 @@ impl<'a> Parser<'a> {
TyKind::Path(None, path) if maybe_bounds => { TyKind::Path(None, path) if maybe_bounds => {
self.parse_remaining_bounds_path(ThinVec::new(), path, lo, true) self.parse_remaining_bounds_path(ThinVec::new(), path, lo, true)
} }
// For `('a) + …`, we know that `'a` in type position already lead to an error being
// emitted. To reduce output, let's indirectly suppress E0178 (bad `+` in type) and
// other irrelevant consequential errors.
TyKind::TraitObject(bounds, TraitObjectSyntax::None) TyKind::TraitObject(bounds, TraitObjectSyntax::None)
if maybe_bounds && bounds.len() == 1 && !trailing_plus => if maybe_bounds && bounds.len() == 1 && !trailing_plus =>
{ {
@ -425,12 +428,60 @@ impl<'a> Parser<'a> {
} }
fn parse_bare_trait_object(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> { fn parse_bare_trait_object(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> {
let lt_no_plus = self.check_lifetime() && !self.look_ahead(1, |t| t.is_like_plus()); // A lifetime only begins a bare trait object type if it is followed by `+`!
let bounds = self.parse_generic_bounds_common(allow_plus)?; if self.token.is_lifetime() && !self.look_ahead(1, |t| t.is_like_plus()) {
if lt_no_plus { // In Rust 2021 and beyond, we assume that the user didn't intend to write a bare trait
self.dcx().emit_err(NeedPlusAfterTraitObjectLifetime { span: lo }); // object type with a leading lifetime bound since that seems very unlikely given the
// fact that `dyn`-less trait objects are *semantically* invalid.
if self.psess.edition.at_least_rust_2021() {
let lt = self.expect_lifetime();
let mut err = self.dcx().struct_span_err(lo, "expected type, found lifetime");
err.span_label(lo, "expected type");
return Ok(match self.maybe_recover_ref_ty_no_leading_ampersand(lt, lo, err) {
Ok(ref_ty) => ref_ty,
Err(err) => TyKind::Err(err.emit()),
});
}
self.dcx().emit_err(NeedPlusAfterTraitObjectLifetime {
span: lo,
suggestion: lo.shrink_to_hi(),
});
}
Ok(TyKind::TraitObject(
self.parse_generic_bounds_common(allow_plus)?,
TraitObjectSyntax::None,
))
}
fn maybe_recover_ref_ty_no_leading_ampersand<'cx>(
&mut self,
lt: Lifetime,
lo: Span,
mut err: Diag<'cx>,
) -> Result<TyKind, Diag<'cx>> {
if !self.may_recover() {
return Err(err);
}
let snapshot = self.create_snapshot_for_diagnostic();
let mutbl = self.parse_mutability();
match self.parse_ty_no_plus() {
Ok(ty) => {
err.span_suggestion_verbose(
lo.shrink_to_lo(),
"you might have meant to write a reference type here",
"&",
Applicability::MaybeIncorrect,
);
err.emit();
Ok(TyKind::Ref(Some(lt), MutTy { ty, mutbl }))
}
Err(diag) => {
diag.cancel();
self.restore_snapshot(snapshot);
Err(err)
}
} }
Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::None))
} }
fn parse_remaining_bounds_path( fn parse_remaining_bounds_path(

View file

@ -3,7 +3,7 @@ trait X {
} }
fn foo<'a>(arg: Box<dyn X<Y('a) = &'a ()>>) {} fn foo<'a>(arg: Box<dyn X<Y('a) = &'a ()>>) {}
//~^ ERROR: lifetime in trait object type must be followed by `+` //~^ ERROR: lifetimes must be followed by `+` to form a trait object type
//~| ERROR: parenthesized generic arguments cannot be used //~| ERROR: parenthesized generic arguments cannot be used
//~| ERROR associated type takes 0 generic arguments but 1 generic argument //~| ERROR associated type takes 0 generic arguments but 1 generic argument
//~| ERROR associated type takes 1 lifetime argument but 0 lifetime arguments //~| ERROR associated type takes 1 lifetime argument but 0 lifetime arguments

View file

@ -1,8 +1,13 @@
error: lifetime in trait object type must be followed by `+` error: lifetimes must be followed by `+` to form a trait object type
--> $DIR/gat-trait-path-parenthesised-args.rs:5:29 --> $DIR/gat-trait-path-parenthesised-args.rs:5:29
| |
LL | fn foo<'a>(arg: Box<dyn X<Y('a) = &'a ()>>) {} LL | fn foo<'a>(arg: Box<dyn X<Y('a) = &'a ()>>) {}
| ^^ | ^^
|
help: consider adding a trait bound after the potential lifetime bound
|
LL | fn foo<'a>(arg: Box<dyn X<Y('a + /* Trait */) = &'a ()>>) {}
| +++++++++++++
error: parenthesized generic arguments cannot be used in associated type constraints error: parenthesized generic arguments cannot be used in associated type constraints
--> $DIR/gat-trait-path-parenthesised-args.rs:5:27 --> $DIR/gat-trait-path-parenthesised-args.rs:5:27

View file

@ -0,0 +1,32 @@
error: lifetimes must be followed by `+` to form a trait object type
--> $DIR/trait-object-macro-matcher.rs:17:8
|
LL | m!('static);
| ^^^^^^^
|
help: consider adding a trait bound after the potential lifetime bound
|
LL | m!('static + /* Trait */);
| +++++++++++++
error: lifetimes must be followed by `+` to form a trait object type
--> $DIR/trait-object-macro-matcher.rs:17:8
|
LL | m!('static);
| ^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: consider adding a trait bound after the potential lifetime bound
|
LL | m!('static + /* Trait */);
| +++++++++++++
error[E0224]: at least one trait is required for an object type
--> $DIR/trait-object-macro-matcher.rs:17:8
|
LL | m!('static);
| ^^^^^^^
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0224`.

View file

@ -0,0 +1,16 @@
error: expected type, found lifetime
--> $DIR/trait-object-macro-matcher.rs:17:8
|
LL | m!('static);
| ^^^^^^^ expected type
error: expected type, found lifetime
--> $DIR/trait-object-macro-matcher.rs:17:8
|
LL | m!('static);
| ^^^^^^^ expected type
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: aborting due to 2 previous errors

View file

@ -1,6 +1,10 @@
// A single lifetime is not parsed as a type. // A single lifetime is not parsed as a type.
// `ty` matcher in particular doesn't accept a single lifetime // `ty` matcher in particular doesn't accept a single lifetime
//@ revisions: e2015 e2021
//@[e2015] edition: 2015
//@[e2021] edition: 2021
macro_rules! m { macro_rules! m {
($t: ty) => { ($t: ty) => {
let _: $t; let _: $t;
@ -8,8 +12,10 @@ macro_rules! m {
} }
fn main() { fn main() {
//[e2021]~vv ERROR expected type, found lifetime
//[e2021]~v ERROR expected type, found lifetime
m!('static); m!('static);
//~^ ERROR lifetime in trait object type must be followed by `+` //[e2015]~^ ERROR lifetimes must be followed by `+` to form a trait object type
//~| ERROR lifetime in trait object type must be followed by `+` //[e2015]~| ERROR lifetimes must be followed by `+` to form a trait object type
//~| ERROR at least one trait is required for an object type //[e2015]~| ERROR at least one trait is required for an object type
} }

View file

@ -1,23 +0,0 @@
error: lifetime in trait object type must be followed by `+`
--> $DIR/trait-object-macro-matcher.rs:11:8
|
LL | m!('static);
| ^^^^^^^
error: lifetime in trait object type must be followed by `+`
--> $DIR/trait-object-macro-matcher.rs:11:8
|
LL | m!('static);
| ^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error[E0224]: at least one trait is required for an object type
--> $DIR/trait-object-macro-matcher.rs:11:8
|
LL | m!('static);
| ^^^^^^^
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0224`.

View file

@ -0,0 +1,13 @@
//@ edition: 2021
struct Entity<'a> {
name: 'a str, //~ ERROR expected type, found lifetime
//~^ HELP you might have meant to write a reference type here
}
struct Buffer<'buf> {
bytes: 'buf mut [u8], //~ ERROR expected type, found lifetime
//~^ HELP you might have meant to write a reference type here
}
fn main() {}

View file

@ -0,0 +1,24 @@
error: expected type, found lifetime
--> $DIR/recover-ampersand-less-ref-ty.rs:4:11
|
LL | name: 'a str,
| ^^ expected type
|
help: you might have meant to write a reference type here
|
LL | name: &'a str,
| +
error: expected type, found lifetime
--> $DIR/recover-ampersand-less-ref-ty.rs:9:12
|
LL | bytes: 'buf mut [u8],
| ^^^^ expected type
|
help: you might have meant to write a reference type here
|
LL | bytes: &'buf mut [u8],
| +
error: aborting due to 2 previous errors

View file

@ -1,5 +1,5 @@
error: parenthesized lifetime bounds are not supported error: parenthesized lifetime bounds are not supported
--> $DIR/trait-object-lifetime-parens.rs:5:21 --> $DIR/trait-object-lifetime-parens.rs:9:21
| |
LL | fn f<'a, T: Trait + ('a)>() {} LL | fn f<'a, T: Trait + ('a)>() {}
| ^^^^ | ^^^^
@ -11,7 +11,7 @@ LL + fn f<'a, T: Trait + 'a>() {}
| |
error: parenthesized lifetime bounds are not supported error: parenthesized lifetime bounds are not supported
--> $DIR/trait-object-lifetime-parens.rs:8:24 --> $DIR/trait-object-lifetime-parens.rs:12:24
| |
LL | let _: Box<Trait + ('a)>; LL | let _: Box<Trait + ('a)>;
| ^^^^ | ^^^^
@ -22,11 +22,16 @@ LL - let _: Box<Trait + ('a)>;
LL + let _: Box<Trait + 'a>; LL + let _: Box<Trait + 'a>;
| |
error: lifetime in trait object type must be followed by `+` error: lifetimes must be followed by `+` to form a trait object type
--> $DIR/trait-object-lifetime-parens.rs:10:17 --> $DIR/trait-object-lifetime-parens.rs:16:17
| |
LL | let _: Box<('a) + Trait>; LL | let _: Box<('a) + Trait>;
| ^^ | ^^
|
help: consider adding a trait bound after the potential lifetime bound
|
LL | let _: Box<('a + /* Trait */) + Trait>;
| +++++++++++++
error: aborting due to 3 previous errors error: aborting due to 3 previous errors

View file

@ -0,0 +1,51 @@
error: parenthesized lifetime bounds are not supported
--> $DIR/trait-object-lifetime-parens.rs:9:21
|
LL | fn f<'a, T: Trait + ('a)>() {}
| ^^^^
|
help: remove the parentheses
|
LL - fn f<'a, T: Trait + ('a)>() {}
LL + fn f<'a, T: Trait + 'a>() {}
|
error: parenthesized lifetime bounds are not supported
--> $DIR/trait-object-lifetime-parens.rs:12:24
|
LL | let _: Box<Trait + ('a)>;
| ^^^^
|
help: remove the parentheses
|
LL - let _: Box<Trait + ('a)>;
LL + let _: Box<Trait + 'a>;
|
error: expected type, found lifetime
--> $DIR/trait-object-lifetime-parens.rs:16:17
|
LL | let _: Box<('a) + Trait>;
| ^^ expected type
error[E0178]: expected a path on the left-hand side of `+`, not `((/*ERROR*/))`
--> $DIR/trait-object-lifetime-parens.rs:16:16
|
LL | let _: Box<('a) + Trait>;
| ^^^^^^^^^^^^ expected a path
error[E0782]: expected a type, found a trait
--> $DIR/trait-object-lifetime-parens.rs:12:16
|
LL | let _: Box<Trait + ('a)>;
| ^^^^^^^^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
LL | let _: Box<dyn Trait + ('a)>;
| +++
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0178, E0782.
For more information about an error, try `rustc --explain E0178`.

View file

@ -1,4 +1,8 @@
#![allow(bare_trait_objects)] //@ revisions: e2015 e2021
//@[e2015] edition: 2015
//@[e2021] edition: 2021
#![cfg_attr(e2015, allow(bare_trait_objects))]
trait Trait {} trait Trait {}
@ -6,8 +10,12 @@ fn f<'a, T: Trait + ('a)>() {} //~ ERROR parenthesized lifetime bounds are not s
fn check<'a>() { fn check<'a>() {
let _: Box<Trait + ('a)>; //~ ERROR parenthesized lifetime bounds are not supported let _: Box<Trait + ('a)>; //~ ERROR parenthesized lifetime bounds are not supported
// FIXME: It'd be great if we could add suggestion to the following case. //[e2021]~^ ERROR expected a type, found a trait
let _: Box<('a) + Trait>; //~ ERROR lifetime in trait object type must be followed by `+` // FIXME: It'd be great if we could suggest removing the parentheses here too.
//[e2015]~v ERROR lifetimes must be followed by `+` to form a trait object type
let _: Box<('a) + Trait>;
//[e2021]~^ ERROR expected type, found lifetime
//[e2021]~| ERROR expected a path on the left-hand side of `+`
} }
fn main() {} fn main() {}