Skip to content

Commit 7ab385e

Browse files
authored
Rollup merge of #139854 - fmease:modern-diag-for-lt-in-ty, r=davidtwco
Improve parse errors for stray lifetimes in type position While technically & syntactically speaking lifetimes do begin[^1] types in type contexts (this essentially excludes generic argument lists) and require a following `+` to form a complete type (`'a +` denotes a bare trait object type), the likelihood that a user meant to write a lifetime-prefixed bare trait object type in *modern* editions (Rust ≥2021) when placing a lifetime into a type context is incredibly low (they would need to add at least three tokens to turn it into a *semantically* well-formed TOT: `'a` → `dyn 'a + Trait`). Therefore let's *lie* in modern editions (just like in PR #131239, a precedent if you will) by stating "*expected type, found lifetime*" in such cases which is a lot more a approachable, digestible and friendly compared to "*lifetime in trait object type must be followed by `+`*" (as added in PR #69760). I've also added recovery for "ampersand-less" reference types (e.g., `'a ()`, `'a mut Ty`) in modern editions because it was trivial to do and I think it's not unlikely to occur in practice. Fixes #133413. [^1]: For example, in the context of decl macros, this implies that a lone `'a` always matches syntax fragment `ty` ("even if" there's a later macro matcher expecting syntax fragment `lifetime`). Rephrased, lifetimes (in type contexts) *commit* to the type parser.
2 parents c594a88 + 6242335 commit 7ab385e

25 files changed

+283
-92
lines changed

Diff for: compiler/rustc_parse/messages.ftl

+4-2
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ parse_maybe_recover_from_bad_qpath_stage_2 =
543543
.suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths
544544
545545
parse_maybe_recover_from_bad_type_plus =
546-
expected a path on the left-hand side of `+`, not `{$ty}`
546+
expected a path on the left-hand side of `+`
547547
548548
parse_maybe_report_ambiguous_plus =
549549
ambiguous `+` in a type
@@ -642,7 +642,9 @@ parse_mut_on_nested_ident_pattern = `mut` must be attached to each individual bi
642642
.suggestion = add `mut` to each binding
643643
parse_mut_on_non_ident_pattern = `mut` must be followed by a named binding
644644
.suggestion = remove the `mut` prefix
645-
parse_need_plus_after_trait_object_lifetime = lifetime in trait object type must be followed by `+`
645+
646+
parse_need_plus_after_trait_object_lifetime = lifetimes must be followed by `+` to form a trait object type
647+
.suggestion = consider adding a trait bound after the potential lifetime bound
646648
647649
parse_nested_adt = `{$kw_str}` definition cannot be nested inside `{$keyword}`
648650
.suggestion = consider creating a new `{$kw_str}` definition instead of nesting

Diff for: compiler/rustc_parse/src/errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub(crate) struct AmbiguousPlus {
3030
#[derive(Diagnostic)]
3131
#[diag(parse_maybe_recover_from_bad_type_plus, code = E0178)]
3232
pub(crate) struct BadTypePlus {
33-
pub ty: String,
3433
#[primary_span]
3534
pub span: Span,
3635
#[subdiagnostic]
@@ -2806,6 +2805,8 @@ pub(crate) struct ReturnTypesUseThinArrow {
28062805
pub(crate) struct NeedPlusAfterTraitObjectLifetime {
28072806
#[primary_span]
28082807
pub span: Span,
2808+
#[suggestion(code = " + /* Trait */", applicability = "has-placeholders")]
2809+
pub suggestion: Span,
28092810
}
28102811

28112812
#[derive(Diagnostic)]

Diff for: compiler/rustc_parse/src/parser/diagnostics.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1657,19 +1657,19 @@ impl<'a> Parser<'a> {
16571657

16581658
self.bump(); // `+`
16591659
let _bounds = self.parse_generic_bounds()?;
1660-
let sum_span = ty.span.to(self.prev_token.span);
1661-
16621660
let sub = match &ty.kind {
16631661
TyKind::Ref(_lifetime, mut_ty) => {
16641662
let lo = mut_ty.ty.span.shrink_to_lo();
16651663
let hi = self.prev_token.span.shrink_to_hi();
16661664
BadTypePlusSub::AddParen { suggestion: AddParen { lo, hi } }
16671665
}
1668-
TyKind::Ptr(..) | TyKind::BareFn(..) => BadTypePlusSub::ForgotParen { span: sum_span },
1669-
_ => BadTypePlusSub::ExpectPath { span: sum_span },
1666+
TyKind::Ptr(..) | TyKind::BareFn(..) => {
1667+
BadTypePlusSub::ForgotParen { span: ty.span.to(self.prev_token.span) }
1668+
}
1669+
_ => BadTypePlusSub::ExpectPath { span: ty.span },
16701670
};
16711671

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

16741674
Ok(())
16751675
}

Diff for: compiler/rustc_parse/src/parser/ty.rs

+57-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_ast::{
77
Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty,
88
TyKind, UnsafeBinderTy,
99
};
10-
use rustc_errors::{Applicability, PResult};
10+
use rustc_errors::{Applicability, Diag, PResult};
1111
use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
1212
use thin_vec::{ThinVec, thin_vec};
1313

@@ -411,6 +411,9 @@ impl<'a> Parser<'a> {
411411
TyKind::Path(None, path) if maybe_bounds => {
412412
self.parse_remaining_bounds_path(ThinVec::new(), path, lo, true)
413413
}
414+
// For `('a) + …`, we know that `'a` in type position already lead to an error being
415+
// emitted. To reduce output, let's indirectly suppress E0178 (bad `+` in type) and
416+
// other irrelevant consequential errors.
414417
TyKind::TraitObject(bounds, TraitObjectSyntax::None)
415418
if maybe_bounds && bounds.len() == 1 && !trailing_plus =>
416419
{
@@ -425,12 +428,60 @@ impl<'a> Parser<'a> {
425428
}
426429

427430
fn parse_bare_trait_object(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> {
428-
let lt_no_plus = self.check_lifetime() && !self.look_ahead(1, |t| t.is_like_plus());
429-
let bounds = self.parse_generic_bounds_common(allow_plus)?;
430-
if lt_no_plus {
431-
self.dcx().emit_err(NeedPlusAfterTraitObjectLifetime { span: lo });
431+
// A lifetime only begins a bare trait object type if it is followed by `+`!
432+
if self.token.is_lifetime() && !self.look_ahead(1, |t| t.is_like_plus()) {
433+
// In Rust 2021 and beyond, we assume that the user didn't intend to write a bare trait
434+
// object type with a leading lifetime bound since that seems very unlikely given the
435+
// fact that `dyn`-less trait objects are *semantically* invalid.
436+
if self.psess.edition.at_least_rust_2021() {
437+
let lt = self.expect_lifetime();
438+
let mut err = self.dcx().struct_span_err(lo, "expected type, found lifetime");
439+
err.span_label(lo, "expected type");
440+
return Ok(match self.maybe_recover_ref_ty_no_leading_ampersand(lt, lo, err) {
441+
Ok(ref_ty) => ref_ty,
442+
Err(err) => TyKind::Err(err.emit()),
443+
});
444+
}
445+
446+
self.dcx().emit_err(NeedPlusAfterTraitObjectLifetime {
447+
span: lo,
448+
suggestion: lo.shrink_to_hi(),
449+
});
450+
}
451+
Ok(TyKind::TraitObject(
452+
self.parse_generic_bounds_common(allow_plus)?,
453+
TraitObjectSyntax::None,
454+
))
455+
}
456+
457+
fn maybe_recover_ref_ty_no_leading_ampersand<'cx>(
458+
&mut self,
459+
lt: Lifetime,
460+
lo: Span,
461+
mut err: Diag<'cx>,
462+
) -> Result<TyKind, Diag<'cx>> {
463+
if !self.may_recover() {
464+
return Err(err);
465+
}
466+
let snapshot = self.create_snapshot_for_diagnostic();
467+
let mutbl = self.parse_mutability();
468+
match self.parse_ty_no_plus() {
469+
Ok(ty) => {
470+
err.span_suggestion_verbose(
471+
lo.shrink_to_lo(),
472+
"you might have meant to write a reference type here",
473+
"&",
474+
Applicability::MaybeIncorrect,
475+
);
476+
err.emit();
477+
Ok(TyKind::Ref(Some(lt), MutTy { ty, mutbl }))
478+
}
479+
Err(diag) => {
480+
diag.cancel();
481+
self.restore_snapshot(snapshot);
482+
Err(err)
483+
}
432484
}
433-
Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::None))
434485
}
435486

436487
fn parse_remaining_bounds_path(

Diff for: tests/ui/did_you_mean/E0178.stderr

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
1-
error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`
1+
error[E0178]: expected a path on the left-hand side of `+`
22
--> $DIR/E0178.rs:6:8
33
|
44
LL | w: &'a Foo + Copy,
5-
| ^^^^^^^^^^^^^^
5+
| ^^^^^^^
66
|
77
help: try adding parentheses
88
|
99
LL | w: &'a (Foo + Copy),
1010
| + +
1111

12-
error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`
12+
error[E0178]: expected a path on the left-hand side of `+`
1313
--> $DIR/E0178.rs:7:8
1414
|
1515
LL | x: &'a Foo + 'a,
16-
| ^^^^^^^^^^^^
16+
| ^^^^^^^
1717
|
1818
help: try adding parentheses
1919
|
2020
LL | x: &'a (Foo + 'a),
2121
| + +
2222

23-
error[E0178]: expected a path on the left-hand side of `+`, not `&'a mut Foo`
23+
error[E0178]: expected a path on the left-hand side of `+`
2424
--> $DIR/E0178.rs:8:8
2525
|
2626
LL | y: &'a mut Foo + 'a,
27-
| ^^^^^^^^^^^^^^^^
27+
| ^^^^^^^^^^^
2828
|
2929
help: try adding parentheses
3030
|
3131
LL | y: &'a mut (Foo + 'a),
3232
| + +
3333

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

4042
error: aborting due to 4 previous errors
4143

Diff for: tests/ui/did_you_mean/trait-object-reference-without-parens-suggestion.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
error[E0178]: expected a path on the left-hand side of `+`, not `&Copy`
1+
error[E0178]: expected a path on the left-hand side of `+`
22
--> $DIR/trait-object-reference-without-parens-suggestion.rs:4:12
33
|
44
LL | let _: &Copy + 'static;
5-
| ^^^^^^^^^^^^^^^
5+
| ^^^^^
66
|
77
help: try adding parentheses
88
|
99
LL | let _: &(Copy + 'static);
1010
| + +
1111

12-
error[E0178]: expected a path on the left-hand side of `+`, not `&'static Copy`
12+
error[E0178]: expected a path on the left-hand side of `+`
1313
--> $DIR/trait-object-reference-without-parens-suggestion.rs:6:12
1414
|
1515
LL | let _: &'static Copy + 'static;
16-
| ^^^^^^^^^^^^^^^^^^^^^^^
16+
| ^^^^^^^^^^^^^
1717
|
1818
help: try adding parentheses
1919
|

Diff for: tests/ui/generic-associated-types/gat-trait-path-parenthesised-args.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ trait X {
33
}
44

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

Diff for: tests/ui/generic-associated-types/gat-trait-path-parenthesised-args.stderr

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
error: lifetime in trait object type must be followed by `+`
1+
error: lifetimes must be followed by `+` to form a trait object type
22
--> $DIR/gat-trait-path-parenthesised-args.rs:5:29
33
|
44
LL | fn foo<'a>(arg: Box<dyn X<Y('a) = &'a ()>>) {}
55
| ^^
6+
|
7+
help: consider adding a trait bound after the potential lifetime bound
8+
|
9+
LL | fn foo<'a>(arg: Box<dyn X<Y('a + /* Trait */) = &'a ()>>) {}
10+
| +++++++++++++
611

712
error: parenthesized generic arguments cannot be used in associated type constraints
813
--> $DIR/gat-trait-path-parenthesised-args.rs:5:27

Diff for: tests/ui/impl-trait/impl-trait-plus-priority.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type A = fn() -> impl A + B;
2727
type A = fn() -> dyn A + B;
2828
//~^ ERROR ambiguous `+` in a type
2929
type A = fn() -> A + B;
30-
//~^ ERROR expected a path on the left-hand side of `+`, not `fn() -> A`
30+
//~^ ERROR expected a path on the left-hand side of `+`
3131

3232
type A = Fn() -> impl A +;
3333
//~^ ERROR ambiguous `+` in a type
@@ -44,6 +44,6 @@ type A = &impl A + B;
4444
type A = &dyn A + B;
4545
//~^ ERROR ambiguous `+` in a type
4646
type A = &A + B;
47-
//~^ ERROR expected a path on the left-hand side of `+`, not `&A`
47+
//~^ ERROR expected a path on the left-hand side of `+`
4848

4949
fn main() {}

Diff for: tests/ui/impl-trait/impl-trait-plus-priority.stderr

+6-4
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ help: try adding parentheses
3131
LL | type A = fn() -> (dyn A + B);
3232
| + +
3333

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

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

106-
error[E0178]: expected a path on the left-hand side of `+`, not `&A`
108+
error[E0178]: expected a path on the left-hand side of `+`
107109
--> $DIR/impl-trait-plus-priority.rs:46:10
108110
|
109111
LL | type A = &A + B;
110-
| ^^^^^^
112+
| ^^
111113
|
112114
help: try adding parentheses
113115
|

Diff for: tests/ui/parser/issues/issue-73568-lifetime-after-mut.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mac!('a);
1212

1313
// avoid false positives
1414
fn y<'a>(y: &mut 'a + Send) {
15-
//~^ ERROR expected a path on the left-hand side of `+`, not `&mut 'a`
15+
//~^ ERROR expected a path on the left-hand side of `+`
1616
//~| ERROR at least one trait is required for an object type
1717
let z = y as &mut 'a + Send;
1818
//~^ ERROR expected value, found trait `Send`

Diff for: tests/ui/parser/issues/issue-73568-lifetime-after-mut.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ LL - fn x<'a>(x: &mut 'a i32){}
1010
LL + fn x<'a>(x: &'a mut i32){}
1111
|
1212

13-
error[E0178]: expected a path on the left-hand side of `+`, not `&mut 'a`
13+
error[E0178]: expected a path on the left-hand side of `+`
1414
--> $DIR/issue-73568-lifetime-after-mut.rs:14:13
1515
|
1616
LL | fn y<'a>(y: &mut 'a + Send) {
17-
| ^^^^^^^^^^^^^^
17+
| ^^^^^^^
1818
|
1919
help: try adding parentheses
2020
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: lifetimes must be followed by `+` to form a trait object type
2+
--> $DIR/trait-object-macro-matcher.rs:17:8
3+
|
4+
LL | m!('static);
5+
| ^^^^^^^
6+
|
7+
help: consider adding a trait bound after the potential lifetime bound
8+
|
9+
LL | m!('static + /* Trait */);
10+
| +++++++++++++
11+
12+
error: lifetimes must be followed by `+` to form a trait object type
13+
--> $DIR/trait-object-macro-matcher.rs:17:8
14+
|
15+
LL | m!('static);
16+
| ^^^^^^^
17+
|
18+
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
19+
help: consider adding a trait bound after the potential lifetime bound
20+
|
21+
LL | m!('static + /* Trait */);
22+
| +++++++++++++
23+
24+
error[E0224]: at least one trait is required for an object type
25+
--> $DIR/trait-object-macro-matcher.rs:17:8
26+
|
27+
LL | m!('static);
28+
| ^^^^^^^
29+
30+
error: aborting due to 3 previous errors
31+
32+
For more information about this error, try `rustc --explain E0224`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: expected type, found lifetime
2+
--> $DIR/trait-object-macro-matcher.rs:17:8
3+
|
4+
LL | m!('static);
5+
| ^^^^^^^ expected type
6+
7+
error: expected type, found lifetime
8+
--> $DIR/trait-object-macro-matcher.rs:17:8
9+
|
10+
LL | m!('static);
11+
| ^^^^^^^ expected type
12+
|
13+
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
14+
15+
error: aborting due to 2 previous errors
16+

Diff for: tests/ui/parser/macro/trait-object-macro-matcher.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
// A single lifetime is not parsed as a type.
22
// `ty` matcher in particular doesn't accept a single lifetime
33

4+
//@ revisions: e2015 e2021
5+
//@[e2015] edition: 2015
6+
//@[e2021] edition: 2021
7+
48
macro_rules! m {
59
($t: ty) => {
610
let _: $t;
711
};
812
}
913

1014
fn main() {
15+
//[e2021]~vv ERROR expected type, found lifetime
16+
//[e2021]~v ERROR expected type, found lifetime
1117
m!('static);
12-
//~^ ERROR lifetime in trait object type must be followed by `+`
13-
//~| ERROR lifetime in trait object type must be followed by `+`
14-
//~| ERROR at least one trait is required for an object type
18+
//[e2015]~^ ERROR lifetimes must be followed by `+` to form a trait object type
19+
//[e2015]~| ERROR lifetimes must be followed by `+` to form a trait object type
20+
//[e2015]~| ERROR at least one trait is required for an object type
1521
}

0 commit comments

Comments
 (0)