Skip to content
Open
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
43 changes: 22 additions & 21 deletions compiler/rustc_parse/src/parser/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,22 @@ enum AllowCVariadic {
/// Determine if the given token can begin a bound assuming it follows Rust 2015 identifier `dyn`.
///
/// In Rust 2015, `dyn` is a contextual keyword, not a full one.
fn can_begin_dyn_bound_in_edition_2015(t: Token) -> bool {
if t.is_path_start() {
// In `dyn::x`, `dyn<X>` and `dyn<<X>::Y>`, `dyn` should (continue to) denote a regular path
// segment for backward compatibility. We make an exception for `dyn(X)` which used to be
// interpreted as a path with parenthesized generic arguments which can be semantically
// well-formed (consider: `use std::ops::Fn as dyn;`). Instead, we treat it as a trait
// object type whose first bound is parenthesized.
return t != token::PathSep && t != token::Lt && t != token::Shl;
}
fn can_begin_dyn_bound_in_rust_2015(t: Token) -> bool {
// In `dyn::x`, `dyn<X>` and `dyn<<X>::Y>`, `dyn` should (continue to) denote a regular path
// segment for backward compatibility. We make an exception for `dyn(X)` which used to be
// interpreted as a path with parenthesized generic arguments which can be semantically
// well-formed (consider: `use std::ops::Fn as dyn;`). Instead, we treat it as a trait
// object type whose first bound is parenthesized.

// Contrary to `Parser::can_begin_bound`, `!`, `const`, `[` and `async` are deliberately not
// part of this list to contain the number of potential regressions esp. in MBE code.
// `const` and `[` would regress UI test `macro-dyn-const-2015.rs` and
// `!` would regress `dyn!(...)` macro calls in Rust 2015 for example.

if t.is_path_start() {
return t != token::PathSep && t != token::Lt && t != token::Shl;
}

t == token::OpenParen || t == token::Question || t.is_lifetime() || t.is_keyword(kw::For)
}

Expand Down Expand Up @@ -347,9 +349,10 @@ impl<'a> Parser<'a> {
} else {
// Try to recover `for<'a> dyn Trait` or `for<'a> impl Trait`.
if self.may_recover()
&& (self.eat_keyword_noexpect(kw::Impl) || self.eat_keyword_noexpect(kw::Dyn))
&& (self.token.is_keyword(kw::Impl) || self.can_begin_dyn_ty())
{
let kw = self.prev_token.ident().unwrap().0;
self.bump();
let (kw, _) = self.prev_token.ident().unwrap();
let removal_span = kw.span.with_hi(self.token.span.lo());
let path = self.parse_path(PathStyle::Type)?;
let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus();
Expand Down Expand Up @@ -397,7 +400,7 @@ impl<'a> Parser<'a> {
}
} else if self.eat_keyword(exp!(Impl)) {
self.parse_impl_ty(&mut impl_dyn_multi)?
} else if self.is_explicit_dyn_type() {
} else if self.can_begin_dyn_ty() {
self.parse_dyn_ty(&mut impl_dyn_multi)?
} else if self.eat_lt() {
// Qualified path
Expand Down Expand Up @@ -1000,16 +1003,14 @@ impl<'a> Parser<'a> {
Ok(GenericBound::Use(args, lo.to(self.prev_token.span)))
}

/// Is a `dyn B0 + ... + Bn` type allowed here?
fn is_explicit_dyn_type(&mut self) -> bool {
self.check_keyword(exp!(Dyn))
/// Can the current token begin a `dyn`-prefixed trait object type?
fn can_begin_dyn_ty(&mut self) -> bool {
self.token.is_keyword(kw::Dyn)
&& (self.token_uninterpolated_span().at_least_rust_2018()
|| self.look_ahead(1, |&t| can_begin_dyn_bound_in_edition_2015(t)))
|| self.look_ahead(1, |&t| can_begin_dyn_bound_in_rust_2015(t)))
}

/// Parses a `dyn B0 + ... + Bn` type.
///
/// Note that this does *not* parse bare trait objects.
/// Parse a `dyn`-prefixed trait object type.
fn parse_dyn_ty(&mut self, impl_dyn_multi: &mut bool) -> PResult<'a, TyKind> {
self.bump(); // `dyn`

Expand Down Expand Up @@ -1067,8 +1068,8 @@ impl<'a> Parser<'a> {
&& (self.token.can_begin_type()
|| (self.token.is_reserved_ident() && !self.token.is_keyword(kw::Where))))
{
if self.token.is_keyword(kw::Dyn) && self.token.span.edition().at_least_rust_2018() {
// Account for `&dyn Trait + dyn Other`.
// Account for `&dyn Trait + dyn Other`.
if self.can_begin_dyn_ty() {
self.bump();
self.dcx().emit_err(InvalidDynKeyword {
span: self.prev_token.span,
Expand Down
21 changes: 20 additions & 1 deletion tests/ui/parser/dyn-2015-identifier.fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ error[E0405]: cannot find trait `dyn` in this scope
LL | type A4 = dyn + dyn;
| ^^^ not found in this scope

error[E0405]: cannot find trait `dyn` in this scope
--> $DIR/dyn-2015-identifier.rs:33:17
|
LL | type A5 = for<> dyn;
| ^^^ not found in this scope

warning: trait objects without an explicit `dyn` are deprecated
--> $DIR/dyn-2015-identifier.rs:24:11
|
Expand All @@ -66,6 +72,19 @@ help: if this is a dyn-compatible trait, use `dyn`
LL | type A4 = dyn dyn + dyn;
| +++

warning: trait objects without an explicit `dyn` are deprecated
--> $DIR/dyn-2015-identifier.rs:33:11
|
LL | type A5 = for<> dyn;
| ^^^^^^^^^
|
= warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2021!
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2021/warnings-promoted-to-error.html>
help: if this is a dyn-compatible trait, use `dyn`
|
LL | type A5 = dyn for<> dyn;
| +++

error[E0433]: cannot find module or crate `dyn` in this scope
--> $DIR/dyn-2015-identifier.rs:10:11
|
Expand All @@ -74,7 +93,7 @@ LL | type A1 = dyn::dyn;
|
= help: you might be missing a crate named `dyn`

error: aborting due to 10 previous errors; 1 warning emitted
error: aborting due to 11 previous errors; 2 warnings emitted

Some errors have detailed explanations: E0405, E0425, E0433.
For more information about an error, try `rustc --explain E0405`.
8 changes: 8 additions & 0 deletions tests/ui/parser/dyn-2015-identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ type A4 = dyn + dyn;
//[fail]~| ERROR cannot find trait `dyn` in this scope
//[fail]~| WARN trait objects without an explicit `dyn` are deprecated
//[fail]~| WARN this is accepted in the current edition

// The `for<…> dyn …` -> `dyn for<…> …` recovery code used to incorrectly treat `dyn` as a keyword
// in Rust 2015 even it's not followed by a token in the "trigger set".
// What's more, this also used to ICE for a period of time (see also #118564).
type A5 = for<> dyn;
//[fail]~^ ERROR cannot find trait `dyn` in this scope
//[fail]~| WARN trait objects without an explicit `dyn` are deprecated
//[fail]~| WARN this is accepted in the current edition
4 changes: 0 additions & 4 deletions tests/ui/parser/recover-hrtb-before-dyn-impl-kw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,4 @@ fn test(_: &for<'a> dyn Trait) {}
fn test2(_: for<'a> impl Trait) {}
//~^ ERROR `for<...>` expected after `impl`, not before

// Issue #118564
type A2 = dyn<for<> dyn>;

@fmease fmease Sep 5, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nightly-2023-12-03 also ICEs for the much smaller type X = for<> dyn; which incidentally is the code I added to dyn-2015-identifier.rs. Hence the other test now kills two birds with one stone allowing me to remove this regression test case.

View changes since the review

//~^ ERROR expected identifier, found `>`

fn main() {}
8 changes: 1 addition & 7 deletions tests/ui/parser/recover-hrtb-before-dyn-impl-kw.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,5 @@ LL - fn test2(_: for<'a> impl Trait) {}
LL + fn test2(_: impl for<'a> Trait) {}
|

error: expected identifier, found `>`
--> $DIR/recover-hrtb-before-dyn-impl-kw.rs:12:24
|
LL | type A2 = dyn<for<> dyn>;
| ^ expected identifier

error: aborting due to 3 previous errors
error: aborting due to 2 previous errors

Loading