Repository navigation
Description
Fn vs. trait qualifiers
We fail to parse
const unsafe trait Trait {} //~ ERROR expected one of `extern` or `fn`, found keyword `trait`and
const unsafe auto trait Trait {} //~ ERROR expected one of `extern` or `fn`, found `auto`even though check_trait_front_matter would return true here in theory. That's because in parse_item_kind we check_fn_front_matter first which returns true once there are >=2 "fn" qualifiers (†) which most notably includes the sequence const unsafe. Two basic solutions:
- (†):
check_fn_front_matteralready contains certain exceptions likeunsafe extern {,async gen {andasync gen move {; we could extend this with!self.is_trait_front_matter()but that's kind of awful because we would basically check the sequenceconst unsafethree times over. Moreover, we gonna wanna have bothis_*(&self) andcheck_*(&mut self) variants but that means bad duplication. - We could move the
check_trait_front_mattercheck above/before the fn one since it's more strict. However, I wonder if this breaks some soft invariants:check_trait_front_matteris also used elsewhere to determine whether a fn item follows. With this change, it would have "false positives". Likely not a problem in practice though.
Fn ptr ty qualifiers vs. trait bound modifiers in bare trait object tys
What's more, we also use check_fn_front_matter inside parse_ty_common for fn ptr tys but later reject const and async qualifiers in parse_fn_front_matter. Now, since that happens before we check can_begin_bound we incorrectly reject bare trait object tys that start with >=2 trait bound modifiers if they coincide with "fn" qualifiers (thanks to check_fn_front_matter yet again), i.e., async const bare trait object tys:
#![feature(const_trait_impl, async_trait_bounds)]
#[cfg(false)]
type X = (const Trait, async Trait, dyn const async Trait); // OK!
#[cfg(false)]
type Y = const async Trait;
//~^ ERROR an `fn` pointer type cannot be `const`
//~| ERROR an `fn` pointer type cannot be `async`
//~| ERROR expected one of `extern`, `fn`, `gen`, `safe`, or `unsafe`, found `Trait`Array/slice ty vs. conditional constness inside bare trait object tys
For consistency, the following should get parsed as a bare trait object ty with a conditionally-const trait bound. However, we commit to parsing an array/slice ty before that:
#[cfg(false)] type X = dyn [const] Trait; // OK!
#[cfg(false)] type Y = [const] Trait; //~ ERROR expected identifier, found `]`
Activity
fmease commentedon Sep 2, 2025
I can't lie,
check_fn_front_matteris beyond scuffed and gnarly with its ">=2 qualifiers rule", just awful. It doesn't compose well and certain diags suffer, too. OTOH, whilecheck_trait_front_matteris way stricter, it's also quite illegible, verbose and otherwise hard to maintain. Qualifier parsing is hell.fmease commentedon Sep 2, 2025
Technically speaking not specific to
const_trait_implbut it's CTI that now suffers the consequences.estebank commentedon Sep 4, 2025
I think that the right strategy for qualifier parsing is to detect that there's any qualifier and then collect qualifiers in a loop, regardless of order, and then check the order after we get to the actual item. That would let us produce good diagnostics for any combination (including duplicates) and simplify the parsing logic.
Rollup merge of rust-lang#146422 - fmease:less-greedy-maybe-const-bou…
Unrolled build for #146422
fmease commentedon Oct 18, 2025
That would indeed be very nice but it's not that simple.
const(but notconst {) could start a const item, const fn or const trait (and we want to recover fromconst implforimpl const).const autocould be the start of a const auto trait or of a const item (e.g.,const auto: () = ();),externcould be start of an extern fn, crate or a block, so we must interpret the context-sensitive keywordsafeas a modifier without regressingsafeas a normal identifier (for item macro calls or if we're in stmt ctxt for arbitrary exprs1:safe!(),safe(),safe,safe + 1, …) and so on.Footnotes
Well, we already fail to parse
safe fn f() {}as an item if we're inside of a fn body which is bad; this just goes to show how awful this all is. ↩fmease commentedon Oct 18, 2025
Tho, I guess the approach is still possible even tho it needs to be a bit more involved. I'm thinking of sth. like
where
[ModifierCandidate::Const]would dictate that we should parse a const itemconst …: …;(disqualifying const fns and const traits). The question then is how much "processing" should that function do. E.g., shouldconst auto: () = ()result in[MC::Const, MC::Auto](→ const item b/c no MC followsAuto) or in[MC::Const](→ const item; cursor is still before normal identauto).fmease commentedon Oct 18, 2025
This could be very easily expressed using backtracking I believe (contrary to the look aheads we need to do rn) but as you know we're not meant to do that in rustc's parser esp. since snapshotting the parser is super expensive (cloning the entire parser state instead of simply resetting some
cursor: usize) and should only be used for error recovery in the bad path.Edit: Actually, it's not too bad using a bit of look-ahead in
parse_item_modifier_candidates.fmease commentedon Oct 18, 2025
Oh gawd, while the context-sensitive keyword
autois treated as a qualifier where possible,safeis super inflexible but I think we can be a lot more liberal and treat it as a modifier whenever it's followed byfnorextern.For reference, this is OK (which is good):
However, these aren't (which is bad IMO):
fmease commentedon Nov 3, 2025
More issues in the vicinity (I'm assuming Rust 2024 here):
async gen || {};doesn't parse even tho_ = async gen || {};andfor<> async gen || {};doconst async || {}doesn't parse at all despiteconst || {}andasync || {}being a thing (and the ordering mirroringconst async fn f() {})gen || {}doesn't parse unlikefor<> gen || {};(as an aside: it's unclear ifgen || {}should exist on its own IIRC vs.gen {}; errs mentioned that once)Overview of various closure expressions
Disclaimer: Doesn't include
useorstaticclosures.fmease commentedon Dec 6, 2025
In PR #147864, we added another special case (hack) to the "fn frontmatter" check to support
const unsafe trait Trait {}(etc.) from the issue description.Since PR #148434, we support
const impl … {}next toimpl const … {}and given the delicate nature & non-obvious details of the parser I've fully outlined above, we fail to parseconst unsafe impl … {}(while we do parseunsafe impl const … {}).