Skip to content

Fix grammar rules containing or pertaining to bounds - #2257

Merged
ehuss merged 5 commits into
rust-lang:masterfrom
fmease:fix-bounds-grammars
Jun 20, 2026
Merged

Fix grammar rules containing or pertaining to bounds#2257
ehuss merged 5 commits into
rust-lang:masterfrom
fmease:fix-bounds-grammars

Conversation

@fmease

@fmeasefmease commented May 5, 2026

Copy link
Copy Markdown
Member

At least since PR rust-lang/rust#39158 (2017) bounds are intentionally always optional after "bound heralds" (:, impl, dyn). However, the Reference didn't reflect this fact everywhere. This PR rectifies this.

For example reference@master considers the following snippets to be syntactically ill-formed which directly contradicts rustc:

  • items type T: ;, type T: where;, type T: = U;
  • type paths T<U: >, T<U<V>: > (as briefly mentioned in PR Fix the grammar of generic arguments #2247)
  • types impl, fn() -> impl
  • types dyn, fn() -> dyn
  • struct T<'a:> where 'a:;

Furthermore, the edition disclaimer for trait object types is incomplete / imprecise / inaccurate:

It states that token sequence dyn:: will be interpreted as the start of a path in Rust 2015 which is correct in isolation but far from complete in context. Per argumentum e contrario, it would wrongly imply that in Rust 2015 the following snippets all contain (bare) trait object types:

  • type T = dyn;, type T = (dyn);, type T = [dyn];
  • type T = dyn<>;, type T = dyn<()>;, type T = dyn<<T>::S>;

To address this, I've changed the note to use an exhaustive and positive listing of tokens. The follow set is {PathIdentSegment, LIFETIME_OR_LABEL, for, (, ?} as per rustc's can_begin_dyn_bound_in_edition_2015.

@rustbotrustbot added the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label May 5, 2026
Comment threadsrc/types/trait-object.md Outdated
r[type.trait-object.syntax]
```grammar,types
TraitObjectType -> `dyn`? TypeParamBounds
TraitObjectType -> TypeParamBounds | `dyn` TypeParamBounds?

@fmeasefmeaseMay 5, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Due to this grammar rule, TypeParamBounds (now: Bounds) must remain non-empty.

View changes since the review

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from 4200b2e to 7b807acCompareMay 5, 2026 10:25
Comment threadsrc/types/trait-object.md Outdated
@rustbot

This comment has been minimized.

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from 7b807ac to e8c2309CompareMay 6, 2026 13:38
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from e8c2309 to 9f6b416CompareMay 11, 2026 21:21
@rustbot

This comment has been minimized.

@ehussehuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks great, thanks!

View changes since this review

Comment threadsrc/types/impl-trait.md
Comment threadsrc/types/trait-object.md Outdated
Comment threadsrc/types/trait-object.md Outdated
@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from d43e9b5 to 5fd453fCompareMay 27, 2026 12:22
Comment threadsrc/types/trait-object.md Outdated
fn bar() -> impl Trait {
}
```

@fmeasefmeaseMay 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'm mirroring the structure of the section about trait object types here with the name vs. constraint split. I'm definitely not married to it. I actually find the rule name name a bit confusing and I feel like the rule sort of mixes syntax and semantics (just like its preexisting sibling for trait object types).

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking more closely at this, I agree the ".name" rules don't really make sense.

Generally we don't want to have an English description of the syntax (which is often very imprecise). I'm thinking we can just drop these two .name rules entirely.

Comment threadsrc/types/impl-trait.md Outdated
`impl Trait` types are written as the keyword `impl` followed by a set of bounds, but with the following restrictions on the bounds:

r[type.impl-trait.constraint]
There must be at least one trait bound, no more than one `use<..>` bound, and no more than one opt-out bound (e.g. `?Sized`).

@fmeasefmeaseMay 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

no more than one use<...> bound

This is already mentioned in rule type.impl-trait.generic-capture.precise.constraint-single but it feels logical to mentioned it here to be exhaustive.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yea, normally I don't like duplicating things since it makes it more difficult to maintain. But in this case it seems reasonable.

Comment threadsrc/types/impl-trait.md Outdated
`impl Trait` types are written as the keyword `impl` followed by a set of bounds, but with the following restrictions on the bounds:

r[type.impl-trait.constraint]
There must be at least one trait bound, no more than one `use<..>` bound, and no more than one opt-out bound (e.g. `?Sized`).

@fmeasefmeaseMay 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

no more than one opt-out bound (e.g. ?Sized)

Copying the terminology from the trait object section. Personally speaking I call them relaxed bounds and that's what the compiler calls them now everywhere, too (it used to call them relaxed bounds, maybe bounds and unbounds).

Strictly speaking, duplicate relaxed bounds are forbidden but on stable that doesn't make a difference since there's only one trait that can be relaxed (Sized).

It felt natural to mention this here but of course, relaxed bounds (and use<..> bounds) come with a lot more semantic restrictions (specifically when it comes to legal locations) but the Reference doesn't have a central place to talk about them1.

View changes since the review

Footnotes

  1. E.g., for the longest time ?Trait was allowed even for Trait != Sized but two years ago or so that was finally made an error but the Reference was never updated.

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from 5fd453f to 1a54708CompareMay 27, 2026 13:17

r[type.trait-object.constraint]
There may not be more than one non-auto trait, no more than one lifetime, and opt-out bounds (e.g. `?Sized`) are not allowed. Furthermore, paths to traits may be parenthesized.
r[type.trait-object.bounds]

@fmeasefmeaseMay 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Changed it to bounds from constraint since the corresponding rule for impl-Trait had to be renamed to bounds not to conflict with the preexisting types.impl-trait.constraint.

View changes since the review

Comment threadsrc/types/trait-object.md Outdated
r[type.trait-object.syntax-edition2018]
> [!EDITION-2018]
> In the 2015 edition, if the first bound of the trait object is a path that starts with `::`, then the `dyn` will be treated as a part of the path. The first path can be put in parenthesis to get around this. As such, if you want a trait object with the trait `::your_module::Trait`, you should write it as `dyn (::your_module::Trait)`.
> In the 2015 edition, `dyn` must be followed by [PathIdentSegment], [LIFETIME_OR_LABEL], `for`, `(` or `?` to be interpreted as the start of a trait object type. Otherwise, it will be interpreted as a regular identifier.

@fmeasefmeaseJun 7, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

FIXME: Imprecise language: In Rust 2015 dyn+ is a legal trait object type where dyn indeed isn't a keyword but the name of the trait, so strictly speaking dyn is actually "interpreted as the start of a trait object type". This sentence needs to be reworded to say something about dyn being an "active keyword" if followed by XY // something of that nature.

View changes since the review

Comment threadsrc/types/trait-object.md Outdated
TraitObjectType -> Bounds | `dyn` Bounds?

TraitObjectTypeOneBound -> `dyn`? TraitBound
TraitObjectTypeOneBound -> TraitBound | `dyn` TraitBound?

@ehussehussJun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just wanted to double-check. The reason for adding the variants without dyn is because the requirement for dyn is done during validation and not during parsing, correct?

If so, I'm thinking of adding a footnote on these to point to type.trait-object.syntax-edition2021 like we do in other places like ExternBlock where old edition syntax could be potentially confusing to readers.

View changes since the review

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from 1a54708 to a88a302CompareJune 16, 2026 21:47
@rustbot

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@fmease

fmease commented Jun 16, 2026

Copy link
Copy Markdown
MemberAuthor

What changed since your last review:

  1. Removed rule type.trait-object.nameas per your review comment & parts of type.trait-object.boundsas per my own review comment.
  2. Clarified the section about dyn in Rust 2015 to address my own concern
  3. Added footnotes to the grammar of trait object types to highlight edition-sensitive parts to address this review comment
  4. Replaced LIFETIME_OR_LABEL with LIFETIME_TOKEN in the Rust 2015 dyn paragraph. I accidentally mixed them up and wrongly thought the "or label" one was the more general of the two. Obviously, dyn 'static (where we have a ticked keyword) is a syntactically valid trait object type in Rust 2015.

Ready for re-review.

@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from a88a302 to eb08631CompareJune 16, 2026 22:04
Comment threadsrc/types/trait-object.md Outdated
Furthermore, drop rule `type.trait-object.name` entirely
and parts of `type.trait-object.bounds` since they mixed
syntax & semantics and tried to redescribe the grammar in
prose which didn't add value, were too ambiguous & incomplete.
@fmease
fmeaseforce-pushed the fix-bounds-grammars branch from eb08631 to 362c0eeCompareJune 16, 2026 22:20

@ehussehuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks great, thanks!

(Noting you still had a FIXME for Rust 2015, but I'm fine moving forward.)

View changes since this review

@ehuss
ehuss added this pull request to the merge queueJun 20, 2026
Merged via the queue into rust-lang:master with commit f3627a2Jun 20, 2026
6 checks passed
@rustbotrustbot removed the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label Jun 20, 2026
@fmease
fmease deleted the fix-bounds-grammars branch June 20, 2026 22:38
@rustbotrustbot mentioned this pull request Jun 29, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 30, 2026
Update books
## rust-lang/nomicon
1 commits in cc6a6bae8c3bfa389974e533c54694662c1a9de6..5012a37c682b26c4e19433888ed2ca9b129696ca
2026-06-25 10:05:58 UTC to 2026-06-25 10:05:58 UTC
- Clarify when safety may rely on correctness (rust-lang/nomicon#523)
## rust-lang/reference
10 commits in 2c27905c15a51983b54d84f050d3bda096194d27..86635e30bf861a038dc197d7e16fd09e7e514e7a
2026-06-25 17:00:32 UTC to 2026-06-16 18:56:35 UTC
- use-declarations.md: move example to where it's described (rust-lang/reference#2295)
- fix link target (rust-lang/reference#2299)
- remove broken text (rust-lang/reference#2297)
- type-layout: rewrite `#[repr(C)]` struct layout algorithm (rust-lang/reference#2243)
- Fix grammar rules containing or pertaining to bounds (rust-lang/reference#2257)
- Document metadata of ptrs to indirectly unsized types (rust-lang/reference#2289)
- Generalize the wide-pointer total-size bound (rust-lang/reference#2290)
- Cover `str` in the wide-pointer metadata rule (rust-lang/reference#2288)
- Define an unsized tail (rust-lang/reference#2287)
- Define pointer metadata (rust-lang/reference#2286)
@rustbotrustbot added this to the 1.98.0 milestone Jun 30, 2026
rust-timer added a commit to rust-lang/rust that referenced this pull request Jun 30, 2026
Rollup merge of #158571 - rustbot:docs-update, r=ehuss
Update books
## rust-lang/nomicon
1 commits in cc6a6bae8c3bfa389974e533c54694662c1a9de6..5012a37c682b26c4e19433888ed2ca9b129696ca
2026-06-25 10:05:58 UTC to 2026-06-25 10:05:58 UTC
- Clarify when safety may rely on correctness (rust-lang/nomicon#523)
## rust-lang/reference
10 commits in 2c27905c15a51983b54d84f050d3bda096194d27..86635e30bf861a038dc197d7e16fd09e7e514e7a
2026-06-25 17:00:32 UTC to 2026-06-16 18:56:35 UTC
- use-declarations.md: move example to where it's described (rust-lang/reference#2295)
- fix link target (rust-lang/reference#2299)
- remove broken text (rust-lang/reference#2297)
- type-layout: rewrite `#[repr(C)]` struct layout algorithm (rust-lang/reference#2243)
- Fix grammar rules containing or pertaining to bounds (rust-lang/reference#2257)
- Document metadata of ptrs to indirectly unsized types (rust-lang/reference#2289)
- Generalize the wide-pointer total-size bound (rust-lang/reference#2290)
- Cover `str` in the wide-pointer metadata rule (rust-lang/reference#2288)
- Define an unsized tail (rust-lang/reference#2287)
- Define pointer metadata (rust-lang/reference#2286)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@fmease@rustbot@ehuss