Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3786,10 +3786,10 @@ pub struct TraitAlias {

#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct Trait {
pub impl_restriction: ImplRestriction,
pub constness: Const,
pub safety: Safety,
pub is_auto: IsAuto,
pub impl_restriction: ImplRestriction,
pub ident: Ident,
pub generics: Generics,
#[visitable(extra = BoundKind::SuperTraits)]
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,10 +543,10 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
})
}
ItemKind::Trait(box Trait {
impl_restriction,
constness,
is_auto,
safety,
impl_restriction,
ident,
generics,
bounds,
Expand All @@ -573,10 +573,10 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
},
);
hir::ItemKind::Trait(
impl_restriction,
constness,
*is_auto,
safety,
impl_restriction,
ident,
generics,
bounds,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_ast_pretty/src/pprust/state/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,21 +371,21 @@ impl<'a> State<'a> {
self.bclose(item.span, empty, cb);
}
ast::ItemKind::Trait(box ast::Trait {
impl_restriction,
constness,
safety,
is_auto,
impl_restriction,
ident,
generics,
bounds,
items,
}) => {
let (cb, ib) = self.head("");
self.print_visibility(&item.vis);
self.print_impl_restriction(impl_restriction);
self.print_constness(*constness);
self.print_safety(*safety);
self.print_is_auto(*is_auto);
self.print_impl_restriction(impl_restriction);
self.word_nbsp("trait");
self.print_ident(*ident);
self.print_generic_params(&generics.params);
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4327,17 +4327,17 @@ impl<'hir> Item<'hir> {

expect_trait,
(
&'hir ImplRestriction<'hir>,
Constness,
IsAuto,
Safety,
&'hir ImplRestriction<'hir>,
Ident,
&'hir Generics<'hir>,
GenericBounds<'hir>,
&'hir [TraitItemId]
),
ItemKind::Trait(constness, is_auto, safety, impl_restriction, ident, generics, bounds, items),
(*constness, *is_auto, *safety, impl_restriction, *ident, generics, bounds, items);
ItemKind::Trait(impl_restriction, constness, is_auto, safety, ident, generics, bounds, items),
(impl_restriction, *constness, *is_auto, *safety, *ident, generics, bounds, items);

expect_trait_alias, (Constness, Ident, &'hir Generics<'hir>, GenericBounds<'hir>),
ItemKind::TraitAlias(constness, ident, generics, bounds), (*constness, *ident, generics, bounds);
Expand Down Expand Up @@ -4529,10 +4529,10 @@ pub enum ItemKind<'hir> {
Union(Ident, &'hir Generics<'hir>, VariantData<'hir>),
/// A trait definition.
Trait(
&'hir ImplRestriction<'hir>,
Constness,
IsAuto,
Safety,
&'hir ImplRestriction<'hir>,
Ident,
&'hir Generics<'hir>,
GenericBounds<'hir>,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,10 +619,10 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V::
try_visit!(visitor.visit_variant_data(struct_definition));
}
ItemKind::Trait(
ref impl_restriction,
_constness,
_is_auto,
_safety,
ref impl_restriction,
ident,
ref generics,
bounds,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
let item = tcx.hir_expect_item(def_id);

let (constness, is_alias, is_auto, safety, impl_restriction) = match item.kind {
hir::ItemKind::Trait(constness, is_auto, safety, impl_restriction, ..) => (
hir::ItemKind::Trait(impl_restriction, constness, is_auto, safety, ..) => (
constness,
false,
is_auto == hir::IsAuto::Yes,
Expand Down Expand Up @@ -958,9 +958,9 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {

ty::TraitDef {
def_id: def_id.to_def_id(),
impl_restriction,
safety,
constness,
impl_restriction,
paren_sugar,
has_auto_impl: is_auto,
is_marker,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir_pretty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,20 +758,20 @@ impl<'a> State<'a> {
self.bclose(item.span, cb);
}
hir::ItemKind::Trait(
impl_restriction,
constness,
is_auto,
safety,
impl_restriction,
ident,
generics,
bounds,
trait_items,
) => {
let (cb, ib) = self.head("");
self.print_impl_restriction(impl_restriction);
self.print_constness(constness);
self.print_is_auto(is_auto);
self.print_safety(safety);
self.print_impl_restriction(impl_restriction);
self.word_nbsp("trait");
self.print_ident(ident);
self.print_generic_params(generics.params);
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1886,7 +1886,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
entry.1.insert((self_ty.span, String::new()));
}
Some(Node::Item(hir::Item {
kind: hir::ItemKind::Trait(_, rustc_ast::ast::IsAuto::Yes, ..),
kind: hir::ItemKind::Trait(_, _, rustc_ast::ast::IsAuto::Yes, ..),
span: item_span,
..
})) => {
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_middle/src/ty/trait_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ use crate::ty::{Ident, Ty, TyCtxt};
pub struct TraitDef {
pub def_id: DefId,

/// Restrictions on trait implementations.
pub impl_restriction: ImplRestrictionKind,

pub safety: hir::Safety,

/// Whether this trait is `const`.
pub constness: hir::Constness,

/// Restrictions on trait implementations.
pub impl_restriction: ImplRestrictionKind,

/// If `true`, then this trait had the `#[rustc_paren_sugar]`
/// attribute, indicating that it should be used with `Foo()`
/// sugar. This is a temporary thing -- eventually any trait will
Expand Down
124 changes: 50 additions & 74 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,80 +1052,58 @@ impl<'a> Parser<'a> {
}
}

/// Is there an `[ impl(in? path) ]? trait` item `dist` tokens ahead?
fn is_trait_with_maybe_impl_restriction_in_front(&self, dist: usize) -> bool {
// `trait`
if self.is_keyword_ahead(dist, &[kw::Trait]) {
return true;
}
// `impl(`
if !self.is_keyword_ahead(dist, &[kw::Impl])
|| !self.look_ahead(dist + 1, |t| t == &token::OpenParen)
{
return false;
}
// `crate | super | self) trait`
if self.is_keyword_ahead(dist + 2, &[kw::Crate, kw::Super, kw::SelfLower])
&& self.look_ahead(dist + 3, |t| t == &token::CloseParen)
&& self.is_keyword_ahead(dist + 4, &[kw::Trait])
{
return true;
}
// `impl(in? something) trait`
// We catch cases where the `in` keyword is missing to provide a
// better error message. This is handled later in
// `self.recover_incorrect_impl_restriction`.
self.tree_look_ahead(dist + 2, |t| {
if let TokenTree::Token(token, _) = t { token.is_keyword(kw::Trait) } else { false }
})
.unwrap_or(false)
}

/// Is this an `(const unsafe? auto? [ impl(in? path) ]? | unsafe auto? [ impl(in? path) ]? | auto [ impl(in? path) ]? | [ impl(in? path) ]?) trait` item?
/// Is this an `[impl(in? path)]? const? unsafe? auto? trait` item?
fn check_trait_front_matter(&mut self) -> bool {

@fmease fmease Apr 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If I'm not mistaken the only conflict with impl blocks we have to avoid is impl (crate) { etc. or am I missing classes of cases? Therefore we only need to ensure that impl(CRATE_OR_SUPER_OR_SELF) is not followed by {.

Well, a negative check might not be liked, so I guess it's alright to keep the "expensive" elaborate suffix checks1.

View changes since the review

Footnotes

  1. We have a sort of similar situation with impl !Trait for Type {} versus impl ! {} where rustc does a positive check (whether the next token can begin) but where rust analyzer does a negative check (whether it's not {)).

// `[ impl(in? path) ]? trait`
if self.is_trait_with_maybe_impl_restriction_in_front(0) {
return true;
}
// `auto [ impl(in? path) ]? trait`
if self.check_keyword(exp!(Auto)) && self.is_trait_with_maybe_impl_restriction_in_front(1) {
return true;
}
// `unsafe auto? [ impl(in? path) ]? trait`
if self.check_keyword(exp!(Unsafe))
&& (self.is_trait_with_maybe_impl_restriction_in_front(1)
|| self.is_keyword_ahead(1, &[kw::Auto])
&& self.is_trait_with_maybe_impl_restriction_in_front(2))
{
return true;
}
// `const` ...
if !self.check_keyword(exp!(Const)) {
return false;
}
// `const [ impl(in? path) ]? trait`
if self.is_trait_with_maybe_impl_restriction_in_front(1) {
return true;
}
// `const (unsafe | auto) [ impl(in? path) ]? trait`
if self.is_keyword_ahead(1, &[kw::Unsafe, kw::Auto])
&& self.is_trait_with_maybe_impl_restriction_in_front(2)
{
return true;
const SUFFIXES: &[&[Symbol]] = &[
&[kw::Trait],
&[kw::Auto, kw::Trait],
&[kw::Unsafe, kw::Trait],
&[kw::Unsafe, kw::Auto, kw::Trait],
&[kw::Const, kw::Trait],
&[kw::Const, kw::Auto, kw::Trait],
&[kw::Const, kw::Unsafe, kw::Trait],
&[kw::Const, kw::Unsafe, kw::Auto, kw::Trait],
];
// `impl(`
if self.check_keyword(exp!(Impl)) && self.look_ahead(1, |t| t == &token::OpenParen) {
// Since the `path` in `impl(in? path)` can be arbitrarily long,

@fmease fmease Apr 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand the need to perform all this suffix checking if we have in, the sequence impl(in should unambiguously introduce an impl restriction that's its whole purpose; it's a disambiguator. Therefore I'm not sure why we have to assume the worst and perform a tree lookahead.

If the prefix is impl(crate, impl(super or impl(self then and only then do we need to check that it's followed by ) (easy) followed by NOT({) // SUFFIXES.

Or is there a case where impl(in may refer to something other than an impl restriction that I don't know about?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(For comparison, this is how the keyword order change looks in my own Rust parser: https://github.com/fmease/rasur/pull/34/changes)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, when there is an in, we can immediately return true here.
Still, to recover the cases where in is lost like impl(path::to::module) and suggest to insert the in, I think using tree_look_ahead and check it's suffix is needed for positive tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe adding early returns like the following would be good?

// `impl(in` unambiguously introduces an `impl` restriction
if self.is_keyword_ahead(2, &[kw::In]) {
    return true;
}
// `impl(crate | self | super)` + SUFFIX
if self.is_keyword_ahead(2, &[kw::Crate, kw::SelfLower, kw::Super])
    && self.look_ahead(3, |t| t == &token::CloseParen)
    && SUFFIXES.iter().any(|suffix| {
        suffix.iter().enumerate().all(|(i, kw)| self.is_keyword_ahead(i + 4, &[*kw]))
    })
{
    return true;
}
// Recover cases like `impl(path::to::module)` + SUFFIX to suggest inserting `in`.
SUFFIXES.iter().any(|suffix| {
    suffix.iter().enumerate().all(|(i, kw)| {
        self.tree_look_ahead(i + 2, |t| {
            if let TokenTree::Token(token, _) = t {
                token.is_keyword(*kw)
            } else {
                false
            }
        })
        .unwrap_or(false)
    })
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

cbba81e
Added early returns for now.

// we treat `(in? path)` as a `TokenTree::Delimited`,
// so we look ahead over token trees rather than tokens.
SUFFIXES.iter().any(|suffix| {
suffix.iter().enumerate().all(|(i, kw)| {
self.tree_look_ahead(i + 2, |t| {
if let TokenTree::Token(token, _) = t {
token.is_keyword(*kw)
} else {
false
}
})
.unwrap_or(false)
})
})
} else {
SUFFIXES.iter().any(|suffix| {
suffix.iter().enumerate().all(|(i, kw)| {
// We use `check_keyword` for the first token to include it in the expected tokens.
if i == 0 {
match *kw {
kw::Const => self.check_keyword(exp!(Const)),
kw::Unsafe => self.check_keyword(exp!(Unsafe)),
kw::Auto => self.check_keyword(exp!(Auto)),
kw::Trait => self.check_keyword(exp!(Trait)),
_ => unreachable!(),
}
} else {
self.is_keyword_ahead(i, &[*kw])
}
})
})
}
Comment thread
Urgau marked this conversation as resolved.
// `const unsafe auto [ impl(in? path) ]? trait`
self.is_keyword_ahead(1, &[kw::Unsafe])
&& self.is_keyword_ahead(2, &[kw::Auto])
&& self.is_trait_with_maybe_impl_restriction_in_front(3)
}

/// Parses `const? unsafe? auto? [impl(in? path)]? trait Foo { ... }` or `trait Foo = Bar;`.
///
/// FIXME(restrictions): The current keyword order follows the grammar specified in RFC 3323.
/// However, whether the restriction should be grouped closer to the visibility modifier
/// (e.g., `pub impl(crate) const unsafe auto trait`) remains an unresolved design question.
/// This ordering must be kept in sync with the logic in `check_trait_front_matter`.
/// Parses `[impl(in? path)]? const? unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemKind> {
let impl_restriction = self.parse_impl_restriction()?;
let constness = self.parse_constness(Case::Sensitive);
if let Const::Yes(span) = constness {
self.psess.gated_spans.gate(sym::const_trait_impl, span);
Expand All @@ -1139,8 +1117,6 @@ impl<'a> Parser<'a> {
IsAuto::No
};

let impl_restriction = self.parse_impl_restriction()?;

self.expect_keyword(exp!(Trait))?;
let ident = self.parse_ident()?;
let mut generics = self.parse_generics()?;
Expand Down Expand Up @@ -1181,10 +1157,10 @@ impl<'a> Parser<'a> {
generics.where_clause = self.parse_where_clause()?;
let items = self.parse_item_list(attrs, |p| p.parse_trait_item(ForceCollect::No))?;
Ok(ItemKind::Trait(Box::new(Trait {
impl_restriction,
constness,
is_auto,
safety,
impl_restriction,
ident,
generics,
bounds,
Expand Down Expand Up @@ -2966,8 +2942,8 @@ impl<'a> Parser<'a> {
&& !self.is_unsafe_foreign_mod()
// Rule out `async gen {` and `async gen move {`
&& !self.is_async_gen_block()
// Rule out `const unsafe auto` and `const unsafe trait` and `const unsafe impl`.
&& !self.is_keyword_ahead(2, &[kw::Auto, kw::Trait, kw::Impl])
// Rule out `const unsafe auto` and `const unsafe trait`
&& !self.is_keyword_ahead(2, &[kw::Auto, kw::Trait])

@fmease fmease Apr 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will regress

#![feature(const_trait_impl)]

const unsafe impl Trait for () {}

const unsafe trait Trait {}

Can you please revert that change and add a test case for this syntax somewhere in tests/ui/traits/const-traits/?

I know that you added this check in PR #152943 for unrelated reasons but it did silently fix this const-trait-impl issue that I unbeknownst to you originally reported in #148434 (comment) / #146122 (comment). I was aware of that when I reviewed your other PR but didn't mention it which I probably should've.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

470e462
Reverted the change and added a test case.

)
})
// `extern ABI fn`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3888,7 +3888,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let mut is_auto_trait = false;
match tcx.hir_get_if_local(data.impl_or_alias_def_id) {
Some(Node::Item(hir::Item {
kind: hir::ItemKind::Trait(_, is_auto, _, _, ident, _, _, _),
kind: hir::ItemKind::Trait(_, _, is_auto, _, ident, _, _, _),
..
})) => {
// FIXME: we should do something else so that it works even on crate foreign
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2916,7 +2916,7 @@ fn clean_maybe_renamed_item<'tcx>(
clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx)
}
// FIXME: rustdoc will need to handle `impl` restrictions at some point
ItemKind::Trait(_, _, _, _impl_restriction, _, generics, bounds, item_ids) => {
ItemKind::Trait(_impl_restriction, _, _, _, _, generics, bounds, item_ids) => {
let items = item_ids
.iter()
.map(|&ti| clean_trait_item(cx.tcx.hir_trait_item(ti), cx))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,10 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
}
},
ItemKind::Trait(
_impl_restriction,
_constness,
is_auto,
_safety,
_impl_restriction,
_ident,
_generics,
_generic_bounds,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_lints/src/doc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
{
missing_headers::check(cx, item.owner_id, sig, headers, Some(body), self.check_private_items);
},
ItemKind::Trait(_, _, unsafety, ..) => match (headers.safety, unsafety) {
ItemKind::Trait(_, _, _, unsafety, ..) => match (headers.safety, unsafety) {
(false, Safety::Unsafe) => span_lint(
cx,
MISSING_SAFETY_DOC,
Expand Down
Loading
Loading