Rollup merge of #146422 - fmease:less-greedy-maybe-const-bounds, r=estebank

Less greedily parse `[const]` bounds

> [!IMPORTANT]
> If you're coming here from any beta backport nomination thread on Zulip, only the last commit is truly relevant (the first commit doesn't need to be backported, it only contains test modifications)!

Don't consider `[` to start a bound, only consider `[const]` in its entirety to do so. This drastically reduces (but doesn't eliminate!) the chance of *real* breakages. Like `const`, `~const` and `async` before, `[const]` unavoidably brings along theoretical breakages, see preexisting tests: `macro-const-trait-bound-theoretical-regression.rs` and `macro-async-trait-bound-theoretical-regression.rs`.

Side note: It's unfortunate that we have to do this but apart from the known fact that MBE hurts forward compatibility, the `[const]` syntax is simply a bit scuffed (also CC'ing https://github.com/rust-lang/rust/issues/146122, section (3)).

Fixes [after beta backport] rust-lang/rust#146417.

* 1st commit: Restore the original test intentions of several preexisting related tests that were unfortunately lost over time
  * I've added a bunch of SCREAMING comments to make it less likely to be lost again
  * CC PR rust-lang/rust#119099 which added most of these tests
  * CC [#144409 (comment)](https://github.com/rust-lang/rust/pull/144409#discussion_r2337587513) for further context (NB: It's not the only PR that negatively affected the test intention)
* 2nd commit: Actually address the regression

r? `@oli-obk` or anyone
This commit is contained in:
Stuart Cook 2025-09-11 14:06:32 +10:00 committed by GitHub
commit d037d1097f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 157 additions and 106 deletions

View file

@ -92,10 +92,10 @@ fn can_continue_type_after_non_fn_ident(t: &Token) -> bool {
}
fn can_begin_dyn_bound_in_edition_2015(t: &Token) -> bool {
// `Not`, `Tilde` & `Const` are deliberately not part of this list to
// `!`, `const`, `[`, `async` are deliberately not part of this list to
// contain the number of potential regressions esp. in MBE code.
// `Const` would regress `rfc-2632-const-trait-impl/mbe-dyn-const-2015.rs`.
// `Not` would regress `dyn!(...)` macro calls in Rust 2015.
// `const` and `[` would regress UI test `macro-dyn-const-2015.rs`.
// `!` would regress `dyn!(...)` macro calls in Rust 2015.
t.is_path_start()
|| t.is_lifetime()
|| t == &TokenKind::Question
@ -1015,12 +1015,18 @@ impl<'a> Parser<'a> {
|| self.check(exp!(Tilde))
|| self.check_keyword(exp!(For))
|| self.check(exp!(OpenParen))
|| self.check(exp!(OpenBracket))
|| self.can_begin_maybe_const_bound()
|| self.check_keyword(exp!(Const))
|| self.check_keyword(exp!(Async))
|| self.check_keyword(exp!(Use))
}
fn can_begin_maybe_const_bound(&mut self) -> bool {
self.check(exp!(OpenBracket))
&& self.look_ahead(1, |t| t.is_keyword(kw::Const))
&& self.look_ahead(2, |t| *t == token::CloseBracket)
}
/// Parse a bound.
///
/// ```ebnf
@ -1199,10 +1205,7 @@ impl<'a> Parser<'a> {
let span = tilde.to(self.prev_token.span);
self.psess.gated_spans.gate(sym::const_trait_impl, span);
BoundConstness::Maybe(span)
} else if self.check(exp!(OpenBracket))
&& self.look_ahead(1, |t| t.is_keyword(kw::Const))
&& self.look_ahead(2, |t| *t == token::CloseBracket)
{
} else if self.can_begin_maybe_const_bound() {
let start = self.token.span;
self.bump();
self.expect_keyword(exp!(Const)).unwrap();

View file

@ -1,21 +1,22 @@
// Demonstrates and records a theoretical regressions / breaking changes caused by the
// introduction of async trait bounds.
// Setting the edition to 2018 since we don't regress `demo! { dyn async }` in Rust <2018.
// Setting the edition to >2015 since we didn't regress `demo! { dyn async }` in Rust 2015.
//@ edition:2018
macro_rules! demo {
($ty:ty) => { compile_error!("ty"); };
($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
//~^ ERROR ty
//~| ERROR ty
(impl $c:ident Trait) => {};
(dyn $c:ident Trait) => {};
// DON'T MODIFY THE MATCHERS BELOW UNLESS THE ASYNC TRAIT MODIFIER SYNTAX CHANGES!
(impl $c:ident Trait) => { /* KEEP THIS EMPTY! */ };
(dyn $c:ident Trait) => { /* KEEP THIS EMPTY! */ };
}
demo! { impl async Trait }
//~^ ERROR `async` trait bounds are unstable
demo! { impl async Trait } //~ ERROR `async` trait bounds are unstable
demo! { dyn async Trait }
//~^ ERROR `async` trait bounds are unstable
demo! { dyn async Trait } //~ ERROR `async` trait bounds are unstable
fn main() {}

View file

@ -1,7 +1,7 @@
error: ty
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:8:19
|
LL | ($ty:ty) => { compile_error!("ty"); };
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { impl async Trait }
@ -12,7 +12,7 @@ LL | demo! { impl async Trait }
error: ty
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:8:19
|
LL | ($ty:ty) => { compile_error!("ty"); };
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { dyn async Trait }
@ -21,7 +21,7 @@ LL | demo! { dyn async Trait }
= note: this error originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0658]: `async` trait bounds are unstable
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:15:14
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:18:14
|
LL | demo! { impl async Trait }
| ^^^^^
@ -32,7 +32,7 @@ LL | demo! { impl async Trait }
= help: use the desugared name of the async trait, such as `AsyncFn`
error[E0658]: `async` trait bounds are unstable
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:18:13
--> $DIR/macro-async-trait-bound-theoretical-regression.rs:20:13
|
LL | demo! { dyn async Trait }
| ^^^^^

View file

@ -1,24 +1,23 @@
// Ensure that we don't consider `const Trait` to
// match the macro fragment specifier `ty` as that would be a breaking
// change theoretically speaking. Syntactically trait object types can
// be "bare", i.e., lack the prefix `dyn`.
// By contrast, `?Trait` *does* match `ty` and therefore an arm like
// `?$Trait:path` would never be reached.
// See `parser/macro/mbe-bare-trait-object-maybe-trait-bound.rs`.
// `[const] Trait` is already an error for a `ty` fragment,
// so we do not need to prevent that.
// Ensure that we don't consider `const Trait` to match the macro fragment specifier `ty`
// as that would be a breaking change theoretically speaking.
//
// Syntactically trait object types can be "bare", i.e., lack the prefix `dyn`.
// By contrast, `?Trait` *does* match `ty` and therefore an arm like `?$Trait:path`
// would never be reached. See `parser/macro/macro-bare-trait-object-maybe-trait-bound.rs`.
//@ check-pass (KEEP THIS AS A PASSING TEST!)
macro_rules! check {
($Type:ty) => {
compile_error!("ty");
};
(const $Trait:path) => {};
([const] $Trait:path) => { [const] Trait };
($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
// DON'T MODIFY THE MATCHERS BELOW UNLESS THE CONST TRAIT MODIFIER SYNTAX CHANGES!
(const $Trait:path) => { /* KEEP THIS EMPTY! */ };
// We don't need to check `[const] Trait` here since that matches the `ty` fragment
// already anyway since `[` may begin a slice or array type. However, it'll then
// subsequently fail due to #146122 (section 3).
}
check! { const Trait }
check! { [const] Trait }
//~^ ERROR: expected identifier, found `]`
//~| ERROR: const trait impls are experimental
fn main() {}

View file

@ -1,22 +0,0 @@
error: expected identifier, found `]`
--> $DIR/macro-bare-trait-objects-const-trait-bounds.rs:20:16
|
LL | ($Type:ty) => {
| -------- while parsing argument for this `ty` macro fragment
...
LL | check! { [const] Trait }
| ^ expected identifier
error[E0658]: const trait impls are experimental
--> $DIR/macro-bare-trait-objects-const-trait-bounds.rs:20:11
|
LL | check! { [const] Trait }
| ^^^^^
|
= note: see issue #143874 <https://github.com/rust-lang/rust/issues/143874> for more information
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View file

@ -1,22 +1,32 @@
// Demonstrates and records a theoretical regressions / breaking changes caused by the
// introduction of const trait bounds.
// introduction of `const` and `[const]` trait bounds.
// Setting the edition to 2018 since we don't regress `demo! { dyn const }` in Rust <2018.
// Setting the edition to >2015 since we didn't regress `demo! { dyn const }` in Rust 2015.
// See also test `traits/const-traits/macro-dyn-const-2015.rs`.
//@ edition:2018
trait Trait {}
macro_rules! demo {
(impl $c:ident Trait) => { impl $c Trait {} };
//~^ ERROR inherent
//~| WARN trait objects without an explicit `dyn` are deprecated
//~| WARN this is accepted in the current edition
(dyn $c:ident Trait) => { dyn $c Trait {} }; //~ ERROR macro expansion
($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
//~^ ERROR ty
//~| ERROR ty
//~| ERROR ty
//~| ERROR ty
// DON'T MODIFY THE MATCHERS BELOW UNLESS THE CONST TRAIT MODIFIER SYNTAX CHANGES!
(impl $c:ident Trait) => { /* KEEP THIS EMPTY! */ };
(dyn $c:ident Trait) => { /* KEEP THIS EMPTY! */ };
(impl [const] Trait) => { /* KEEP THIS EMPTY! */ };
(dyn [const] Trait) => { /* KEEP THIS EMPTY! */ };
}
demo! { impl const Trait }
//~^ ERROR const trait impls are experimental
demo! { impl const Trait } //~ ERROR const trait impls are experimental
demo! { dyn const Trait } //~ ERROR const trait impls are experimental
demo! { dyn const Trait }
demo! { impl [const] Trait } //~ ERROR const trait impls are experimental
demo! { dyn [const] Trait } //~ ERROR const trait impls are experimental
fn main() {}

View file

@ -1,31 +1,49 @@
error: inherent impls cannot be const
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:10:40
error: ty
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:11:19
|
LL | (impl $c:ident Trait) => { impl $c Trait {} };
| ^^^^^ inherent impl for this type
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { impl const Trait }
| --------------------------
| | |
| | const because of this
| in this macro invocation
| -------------------------- in this macro invocation
|
= note: only trait implementations may be annotated with `const`
= note: this error originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: macro expansion ignores keyword `dyn` and any tokens following
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:14:31
error: ty
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:11:19
|
LL | (dyn $c:ident Trait) => { dyn $c Trait {} };
| ^^^
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { dyn const Trait }
| ------------------------- caused by the macro expansion here
| ------------------------- in this macro invocation
|
= note: the usage of `demo!` is likely invalid in item context
= note: this error originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: ty
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:11:19
|
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { impl [const] Trait }
| ---------------------------- in this macro invocation
|
= note: this error originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: ty
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:11:19
|
LL | ($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
| ^^^^^^^^^^^^^^^^^^^^
...
LL | demo! { dyn [const] Trait }
| --------------------------- in this macro invocation
|
= note: this error originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0658]: const trait impls are experimental
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:17:14
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:26:14
|
LL | demo! { impl const Trait }
| ^^^^^
@ -34,24 +52,36 @@ LL | demo! { impl const Trait }
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
warning: trait objects without an explicit `dyn` are deprecated
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:10:40
error[E0658]: const trait impls are experimental
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:27:13
|
LL | (impl $c:ident Trait) => { impl $c Trait {} };
| ^^^^^
...
LL | demo! { impl const Trait }
| -------------------------- in this macro invocation
LL | demo! { dyn const Trait }
| ^^^^^
|
= warning: this is accepted in the current edition (Rust 2018) 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>
= note: `#[warn(bare_trait_objects)]` (part of `#[warn(rust_2021_compatibility)]`) on by default
= note: this warning originates in the macro `demo` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have intended to implement this trait for a given type
|
LL | (impl $c:ident Trait) => { impl $c Trait for /* Type */ {} };
| ++++++++++++++
= note: see issue #143874 <https://github.com/rust-lang/rust/issues/143874> for more information
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 3 previous errors; 1 warning emitted
error[E0658]: const trait impls are experimental
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:29:14
|
LL | demo! { impl [const] Trait }
| ^^^^^^^
|
= note: see issue #143874 <https://github.com/rust-lang/rust/issues/143874> for more information
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: const trait impls are experimental
--> $DIR/macro-const-trait-bound-theoretical-regression.rs:30:13
|
LL | demo! { dyn [const] Trait }
| ^^^^^^^
|
= note: see issue #143874 <https://github.com/rust-lang/rust/issues/143874> for more information
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 8 previous errors
For more information about this error, try `rustc --explain E0658`.

View file

@ -1,14 +1,19 @@
// Ensure that the introduction of const trait bound didn't regress this code in Rust 2015.
// See also `mbe-const-trait-bound-theoretical-regression.rs`.
// Ensure that the introduction of `const` and `[const]` trait bounds didn't regress this
// Rust 2015 code. See also test `macro-const-trait-bound-theoretical-regression.rs`.
//@ edition: 2015
//@ check-pass
//@ check-pass (KEEP THIS AS A PASSING TEST!)
macro_rules! check {
($ty:ty) => { compile_error!("ty"); };
(dyn $c:ident) => {};
($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
// DON'T MODIFY THE MATCHERS BELOW UNLESS THE CONST TRAIT MODIFIER SYNTAX CHANGES!
(dyn $c:ident) => { /* KEEP THIS EMPTY! */ };
(dyn [$c:ident]) => { /* KEEP THIS EMPTY! */ };
}
check! { dyn const }
check! { dyn [const] }
fn main() {}

View file

@ -0,0 +1,25 @@
// Ensure that we don't consider `[` to begin trait bounds to contain breakages.
// Only `[const]` in its entirety begins a trait bound.
// See also test `macro-const-trait-bound-theoretical-regression.rs`.
//@ check-pass (KEEP THIS AS A PASSING TEST!)
// Setting the edition to >2015 since we didn't regress `check! { dyn [const] Trait }` in Rust 2015.
// See also test `traits/const-traits/macro-dyn-const-2015.rs`.
//@ edition:2018
macro_rules! check {
($ty:ty) => { compile_error!("ty"); }; // KEEP THIS RULE FIRST AND AS IS!
// DON'T MODIFY THE MATCHERS BELOW UNLESS THE CONST TRAIT MODIFIER SYNTAX CHANGES!
(dyn [$($any:tt)*] Trait) => { /* KEEP THIS EMPTY! */ };
(impl [$($any:tt)*] Trait) => { /* KEEP THIS EMPTY! */ };
}
check!(dyn [T] Trait);
// issue: <https://github.com/rust-lang/rust/issues/146417>
check!(impl [T] Trait);
check!(impl [T: Bound] Trait);
fn main() {}