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 all commits
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
6 changes: 4 additions & 2 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ parse_maybe_recover_from_bad_qpath_stage_2 =
.suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths

parse_maybe_recover_from_bad_type_plus =
expected a path on the left-hand side of `+`, not `{$ty}`
expected a path on the left-hand side of `+`

parse_maybe_report_ambiguous_plus =
ambiguous `+` in a type
Expand Down 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
3 changes: 2 additions & 1 deletion compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ pub(crate) struct AmbiguousPlus {
#[derive(Diagnostic)]
#[diag(parse_maybe_recover_from_bad_type_plus, code = E0178)]
pub(crate) struct BadTypePlus {
pub ty: String,
#[primary_span]
pub span: Span,
#[subdiagnostic]
Expand Down Expand Up @@ -2806,6 +2805,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
10 changes: 5 additions & 5 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1636,19 +1636,19 @@ impl<'a> Parser<'a> {

self.bump(); // `+`
let _bounds = self.parse_generic_bounds()?;
let sum_span = ty.span.to(self.prev_token.span);

let sub = match &ty.kind {
TyKind::Ref(_lifetime, mut_ty) => {
let lo = mut_ty.ty.span.shrink_to_lo();
let hi = self.prev_token.span.shrink_to_hi();
BadTypePlusSub::AddParen { suggestion: AddParen { lo, hi } }
}
TyKind::Ptr(..) | TyKind::BareFn(..) => BadTypePlusSub::ForgotParen { span: sum_span },
_ => BadTypePlusSub::ExpectPath { span: sum_span },
TyKind::Ptr(..) | TyKind::BareFn(..) => {
BadTypePlusSub::ForgotParen { span: ty.span.to(self.prev_token.span) }
}
_ => BadTypePlusSub::ExpectPath { span: ty.span },
};

self.dcx().emit_err(BadTypePlus { ty: pprust::ty_to_string(ty), span: sum_span, sub });
self.dcx().emit_err(BadTypePlus { span: ty.span, sub });

Ok(())
}
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
18 changes: 10 additions & 8 deletions tests/ui/did_you_mean/E0178.stderr
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/E0178.rs:6:8
|
LL | w: &'a Foo + Copy,
| ^^^^^^^^^^^^^^
| ^^^^^^^
|
help: try adding parentheses
|
LL | w: &'a (Foo + Copy),
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/E0178.rs:7:8
|
LL | x: &'a Foo + 'a,
| ^^^^^^^^^^^^
| ^^^^^^^
|
help: try adding parentheses
|
LL | x: &'a (Foo + 'a),
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `&'a mut Foo`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/E0178.rs:8:8
|
LL | y: &'a mut Foo + 'a,
| ^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^
|
help: try adding parentheses
|
LL | y: &'a mut (Foo + 'a),
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `fn() -> Foo`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/E0178.rs:9:8
|
LL | z: fn() -> Foo + 'a,
| ^^^^^^^^^^^^^^^^ perhaps you forgot parentheses?
| ^^^^^^^^^^^-----
| |
| perhaps you forgot parentheses?

error: aborting due to 4 previous errors

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
error[E0178]: expected a path on the left-hand side of `+`, not `&Copy`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/trait-object-reference-without-parens-suggestion.rs:4:12
|
LL | let _: &Copy + 'static;
| ^^^^^^^^^^^^^^^
| ^^^^^
|
help: try adding parentheses
|
LL | let _: &(Copy + 'static);
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `&'static Copy`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/trait-object-reference-without-parens-suggestion.rs:6:12
|
LL | let _: &'static Copy + 'static;
| ^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^
|
help: try adding parentheses
|
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
4 changes: 2 additions & 2 deletions tests/ui/impl-trait/impl-trait-plus-priority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type A = fn() -> impl A + B;
type A = fn() -> dyn A + B;
//~^ ERROR ambiguous `+` in a type
type A = fn() -> A + B;
//~^ ERROR expected a path on the left-hand side of `+`, not `fn() -> A`
//~^ ERROR expected a path on the left-hand side of `+`

type A = Fn() -> impl A +;
//~^ ERROR ambiguous `+` in a type
Expand All @@ -44,6 +44,6 @@ type A = &impl A + B;
type A = &dyn A + B;
//~^ ERROR ambiguous `+` in a type
type A = &A + B;
//~^ ERROR expected a path on the left-hand side of `+`, not `&A`
//~^ ERROR expected a path on the left-hand side of `+`

fn main() {}
10 changes: 6 additions & 4 deletions tests/ui/impl-trait/impl-trait-plus-priority.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ help: try adding parentheses
LL | type A = fn() -> (dyn A + B);
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `fn() -> A`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/impl-trait-plus-priority.rs:29:10
|
LL | type A = fn() -> A + B;
| ^^^^^^^^^^^^^ perhaps you forgot parentheses?
| ^^^^^^^^^----
| |
| perhaps you forgot parentheses?

error: ambiguous `+` in a type
--> $DIR/impl-trait-plus-priority.rs:32:18
Expand Down Expand Up @@ -103,11 +105,11 @@ help: try adding parentheses
LL | type A = &(dyn A + B);
| + +

error[E0178]: expected a path on the left-hand side of `+`, not `&A`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/impl-trait-plus-priority.rs:46:10
|
LL | type A = &A + B;
| ^^^^^^
| ^^
|
help: try adding parentheses
|
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/parser/issues/issue-73568-lifetime-after-mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mac!('a);

// avoid false positives
fn y<'a>(y: &mut 'a + Send) {
//~^ ERROR expected a path on the left-hand side of `+`, not `&mut 'a`
//~^ ERROR expected a path on the left-hand side of `+`
//~| ERROR at least one trait is required for an object type
let z = y as &mut 'a + Send;
//~^ ERROR expected value, found trait `Send`
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-73568-lifetime-after-mut.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ LL - fn x<'a>(x: &mut 'a i32){}
LL + fn x<'a>(x: &'a mut i32){}
|

error[E0178]: expected a path on the left-hand side of `+`, not `&mut 'a`
error[E0178]: expected a path on the left-hand side of `+`
--> $DIR/issue-73568-lifetime-after-mut.rs:14:13
|
LL | fn y<'a>(y: &mut 'a + Send) {
| ^^^^^^^^^^^^^^
| ^^^^^^^
|
help: try adding parentheses
|
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
}
Loading
Loading