Skip to content

Improve parse errors for stray lifetimes in type position #139854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,9 @@ parse_mut_on_nested_ident_pattern = `mut` must be attached to each individual bi
.suggestion = add `mut` to each binding
parse_mut_on_non_ident_pattern = `mut` must be followed by a named binding
.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}`
.suggestion = consider creating a new `{$kw_str}` definition instead of nesting
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2806,6 +2806,8 @@ pub(crate) struct ReturnTypesUseThinArrow {
pub(crate) struct NeedPlusAfterTraitObjectLifetime {
#[primary_span]
pub span: Span,
#[suggestion(code = " + /* Trait */", applicability = "has-placeholders")]
pub suggestion: Span,
}

#[derive(Diagnostic)]
Expand Down
63 changes: 57 additions & 6 deletions compiler/rustc_parse/src/parser/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rustc_ast::{
Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty,
TyKind, UnsafeBinderTy,
};
use rustc_errors::{Applicability, PResult};
use rustc_errors::{Applicability, Diag, PResult};
use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
use thin_vec::{ThinVec, thin_vec};

Expand Down Expand Up @@ -411,6 +411,9 @@ impl<'a> Parser<'a> {
TyKind::Path(None, path) if maybe_bounds => {
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.
Comment on lines +414 to +416
Copy link
Member Author

@fmease fmease Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just an explainer for what's already going on here.

In commit fmease@f8c5436 which I've since removed from this PR, I've experimented with removing this which would've allowed us to suggest removing parentheses in maybe_recover_from_bad_type_plus for type ('a) + Bounds… (that's a FIXME in tests/ui/parser/trait-object-lifetime-parens.rs) but that made things worse (more verbose output, nasty boolean flag, still false-positives (e.g., (?'a) + Bound where ? is invalid but we recover which we can't detect later)).

TyKind::TraitObject(bounds, TraitObjectSyntax::None)
if maybe_bounds && bounds.len() == 1 && !trailing_plus =>
{
Expand All @@ -425,12 +428,60 @@ impl<'a> Parser<'a> {
}

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());
let bounds = self.parse_generic_bounds_common(allow_plus)?;
if lt_no_plus {
self.dcx().emit_err(NeedPlusAfterTraitObjectLifetime { span: lo });
// A lifetime only begins a bare trait object type if it is followed by `+`!
if self.token.is_lifetime() && !self.look_ahead(1, |t| t.is_like_plus()) {
// In Rust 2021 and beyond, we assume that the user didn't intend to write a bare trait
// 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() {
Copy link
Member Author

@fmease fmease Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to dropping the edition check making us emit the new message and output TyKind::Error in all editions (undoing parts of PR #69760), thereby removing NeedPlusAfterTraitObjectLifetime entirely.

Copy link
Member Author

@fmease fmease Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to also suggesting dyn 'a + /* Trait */ when facing 'a in modern editions, too.

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ trait X {
}

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 associated type takes 0 generic arguments but 1 generic argument
//~| ERROR associated type takes 1 lifetime argument but 0 lifetime arguments
Expand Down
Original file line number Diff line number Diff line change
@@ -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
|
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
--> $DIR/gat-trait-path-parenthesised-args.rs:5:27
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/parser/macro/trait-object-macro-matcher.e2015.stderr
Original file line number Diff line number Diff line change
@@ -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`.
16 changes: 16 additions & 0 deletions tests/ui/parser/macro/trait-object-macro-matcher.e2021.stderr
Original file line number Diff line number Diff line change
@@ -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

12 changes: 9 additions & 3 deletions tests/ui/parser/macro/trait-object-macro-matcher.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// A single lifetime is not parsed as a type.
// `ty` matcher in particular doesn't accept a single lifetime

//@ revisions: e2015 e2021
//@[e2015] edition: 2015
//@[e2021] edition: 2021

macro_rules! m {
($t: ty) => {
let _: $t;
};
}

fn main() {
//[e2021]~vv ERROR expected type, found lifetime
//[e2021]~v ERROR expected type, found lifetime
m!('static);
//~^ ERROR lifetime in trait object type must be followed by `+`
//~| ERROR lifetime in trait object type must be followed by `+`
//~| ERROR at least one trait is required for an object type
//[e2015]~^ ERROR lifetimes must be followed by `+` to form a trait object type
//[e2015]~| ERROR lifetimes must be followed by `+` to form a trait object type
//[e2015]~| ERROR at least one trait is required for an object type
}
23 changes: 0 additions & 23 deletions tests/ui/parser/macro/trait-object-macro-matcher.stderr

This file was deleted.

13 changes: 13 additions & 0 deletions tests/ui/parser/recover/recover-ampersand-less-ref-ty.rs
Original file line number Diff line number Diff line change
@@ -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() {}
24 changes: 24 additions & 0 deletions tests/ui/parser/recover/recover-ampersand-less-ref-ty.stderr
Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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)>() {}
| ^^^^
Expand All @@ -11,7 +11,7 @@ LL + fn f<'a, T: Trait + 'a>() {}
|

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)>;
| ^^^^
Expand All @@ -22,11 +22,16 @@ LL - let _: Box<Trait + ('a)>;
LL + let _: Box<Trait + 'a>;
|

error: lifetime in trait object type must be followed by `+`
--> $DIR/trait-object-lifetime-parens.rs:10:17
error: lifetimes must be followed by `+` to form a trait object type
--> $DIR/trait-object-lifetime-parens.rs:16:17
|
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

51 changes: 51 additions & 0 deletions tests/ui/parser/trait-object-lifetime-parens.e2021.stderr
Original file line number Diff line number Diff line change
@@ -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`.
14 changes: 11 additions & 3 deletions tests/ui/parser/trait-object-lifetime-parens.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
#![allow(bare_trait_objects)]
//@ revisions: e2015 e2021
//@[e2015] edition: 2015
//@[e2021] edition: 2021

#![cfg_attr(e2015, allow(bare_trait_objects))]

trait Trait {}

fn f<'a, T: Trait + ('a)>() {} //~ ERROR parenthesized lifetime bounds are not supported

fn check<'a>() {
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.
let _: Box<('a) + Trait>; //~ ERROR lifetime in trait object type must be followed by `+`
//[e2021]~^ ERROR expected a type, found a trait
// 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() {}