Reject outer attributes on cfg_select branches rust-lang/rust#155734

Merged

38 comments and reviews loaded in 1.51s

qaijuang Avatar
folkertdev Avatar
tests/ui/lint/unused/unused-doc-comments-for-macros.rs · outdated
20 /// line1
21 //~^ ERROR: unused doc comment
22 debug_assertions => (),

can you make this have multiple lines of doc comments, and also test that the lint fires correctly on the _ => arm?

View changes since the review

rustbot Avatar
rustbot Avatar

r? @JonathanBrouwer

rustbot has assigned @JonathanBrouwer.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 73 candidates
  • Random selection from 17 candidates
JonathanBrouwer Avatar
compiler/rustc_attr_parsing/src/attributes/cfg_select.rs · outdated
84 86
85 87 let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?;
86 88 let span = underscore.span.to(p.token.span);
89 lint_unused_doc_comment(p, doc_comment, lint_node_id);

Why is the lint_unused_doc_comment here rather than immediately after the eat_outer_doc_comments call?

View changes since the review

I assumed linting only successfully parsed branches made sense to avoid noise from parsing failures

rustbot Avatar

Reminder, once the PR becomes ready for a review, use @rustbot ready.

JonathanBrouwer Avatar
rust-bors Avatar

📌 Commit 4bb2b07 has been approved by JonathanBrouwer

It is now in the queue for this repository.

fmease Avatar

This PR allows more code to compile, so it's a language change and technically needs a T-lang FCP.

cc @rust-lang/lang

fmease Avatar
rust-bors Avatar

This pull request was unapproved.

This PR was contained in a rollup (#155773), which was unapproved.

View changes since this unapproval

folkertdev Avatar
folkertdev left a comment · edited
View on GitHub

The FCP is very technically here, given this was just stabilized and I think not erroring on a doc comment in that position is very much in the spirit of the feature stabilization.

Nevertheless, let's at least let T-lang decide what they want to do here.

View changes since this review

tests/ui/lint/unused/unused-doc-comments-for-macros.rs · outdated
18 // Regression test for https://github.com/rust-lang/rust/issues/155701.
19 cfg_select! {
20 /// line1 //~ ERROR: unused doc comment
21 /// line2
22 /// line3
23 debug_assertions => (),
24 /// line1 //~ ERROR: unused doc comment
25 /// line2
26 /// line3
27 _ => (),
28 }

What happens if you mix normal comments and doc comments here? E.g.

    // Regression test for https://github.com/rust-lang/rust/issues/155701.
    cfg_select! {
        /// line1 //~ ERROR: unused doc comment
        // line2
        /// line3
        debug_assertions => (),
        /// line1 //~ ERROR: unused doc comment
        // line2
        /// line3
        _ => (),
    }
JonathanBrouwer Avatar

This PR allows more code to compile, so it's a language change and technically needs a T-lang FCP.

Right I missed that, I'm so sorry

fmease Avatar

@folkertdev

The FCP is very technically here, given this was just stabilized and I think not erroring on a doc comment in that position is very much in the spirit of the feature stabilization.

The fact that this was stabilized recently doesn't imply that the feature is malleable at will. The grammar of cfg_select is literally written down in the Reference, so any extensions are new and separate feature requests.

I would say it's very much not in the spirit to assume that certain extensions are 'natural' and implicitly follow from a past FCP that has since completed since that would defeat the whole purpose of (exhaustive) stabilization reports and it would circumnavigate the entire stabilization process.

More concretely, regarding the change at hand, I would state that is doesn't make any sense to allow outer doc comments (a special kind of outer attribute) without also allowing regular outer attributes, that's unprecedented. Now, allowing regular outer attributes comes with various non-trivial questions like which attributes should be allowed (cfg, lint control, ...; all of that would need to be implemented).

JonathanBrouwer Avatar

Indeed, the language design question is not trivial here.
I think the easy way out for now is to make this PR emit an error, rather than a lint.

fmease Avatar
compiler/rustc_attr_parsing/src/attributes/cfg_select.rs · outdated
143 147 Ok(branches)
144 148 }
145 149
150 fn eat_outer_doc_comments(p: &mut Parser<'_>) -> Option<(Span, CommentKind)> {

We have the same confusing diagnostic + incorrect suggestion for regular attributes:

cfg_select! {
    #[cfg(false)]
    debug_assertions => println!("debug"), //~ ERROR expected a literal
    _ => println!("not debug"),
};

So this shouldn't manually parse outer doc comments (proof that this is unprecedented), it should just parse outer attributes with parse_outer_attributes and hard reject them afterwards for a good diagnostic.

This option also wouldn't need a lang-nomination.

View changes since the review

Hmm yea, I assumed that we'd be doing something similar for macro_rules! but they don't accept attributes in that position either:

macro_rules! foo {
    /// foo
    ($ty:ty) => {
        $ty
    }
}

emits

error: invalid macro matcher; matchers must be contained in balanced delimiters
 --> src/main.rs:5:5
  |
5 |     /// foo
  |     ^^^^^^^

error: expected `=>`, found `(`
 --> src/main.rs:6:5
  |
5 |     /// foo
  |            - expected `=>`
6 |     ($ty:ty) => {
  |     ^ unexpected token

And similar for #[...] attributes.

traviscross Avatar

This PR allows more code to compile, so it's a language change and technically needs a T-lang FCP... The grammar of cfg_select is literally written down in the Reference, so any extensions are new and separate feature requests.

Thanks @fmease for catching this and navigating it.

rustbot Avatar

The parser was modified, potentially altering the grammar of (stable) Rust
which would be a breaking change.

cc @fmease

JonathanBrouwer Avatar
tests/ui/macros/cfg_select.stderr · outdated
61 LL | /// doc comment
62 | ^^^^^^^^^^^^^^^
63
64 error: outer attributes are not allowed on `cfg_select` branches

I think it would be nice to special-case the error for doc comments, since it's a bit weird to call a doc comment an "outer attribute".

Not sure how that interacts with your nice MultiSpan trick tho, an alternative might be wording the error as "attributes and doc comments are not allowed on cfg_select branches"

View changes since the review

tests/ui/macros/cfg_select.rs · resolved
214 }
215
216 cfg_select! {
217 #[cfg(false)]

Could you also add a test with an inner attribute #![..] and with inner doc comments //!

View changes since the review

JonathanBrouwer Avatar
tests/ui/macros/cfg_select.stderr · resolved
76 error: an inner attribute is not permitted in this context
77 --> $DIR/cfg_select.rs:224:5
78 |
79 LL | #![cfg(false)]

The error here says an inner attribute is not permitted in this context but it is pointing to an outer attribute (#![..]). Can we just make the error "an attribute is not permitted in this context"?

View changes since the review

i think it's accurate because #![...] is an inner attribute

compiler/rustc_parse/src/parser/cfg_select.rs · outdated
49 &mut self,
50 ) -> PResult<'a, Option<CfgSelectBranchAttrSpans>> {
51 let inner_doc_comment_span =
52 if let token::DocComment(_, AttrStyle::Inner, _) = self.token.kind {

Hmmm this logic doesn't work if the inner doc comment is not the first one, right?

View changes since the review

Good catch, I'll add a test for it

fmease Avatar
compiler/rustc_parse/src/parser/cfg_select.rs · outdated
52 // `parse_outer_attributes` recovers inner doc comments as outer ones, so collect their
53 // spans first and suppress the follow-up `cfg_select` branch diagnostic for them.
54 let mut inner_doc_comment_spans = Vec::new();
55 let mut snapshot = self.create_snapshot_for_diagnostic();

IINM this is creating a parser snapshot in the happy path, right? this is an expensive operation and should only happen in error paths.

View changes since the review

Why is this using a snapshot in the first place? Can't you just use parse_inner_attributes and reject inner attrs afterwards? That function is smart enough not to bail out when it encounters outer attributes so you don't need to worry about that.

calling parse_inner_attributes first for /// outer followed by //! inner would return empty at the /// and would never notice the later //!

The test case:

cfg_select! {
    /// outer doc comment
    //! inner doc comment
    debug_assertions => {}
    _ => {}
}

IINM this is creating a parser snapshot in the happy path, right? this is an expensive operation and should only happen in error paths.

That’s a good point. I’m thinking of classifying recovered doc comments directly from their source snippet in the error path, so we can avoid the snapshot cost

JonathanBrouwer Avatar
rust-bors Avatar
rust-bors on 2026-05-08 09:14:09 UTC · hidden as outdated
rust-bors Avatar
rust-bors on 2026-05-08 09:14:09 UTC · hidden as outdated
View on GitHub

🚧 Squashing... this can take a few minutes.

rust-bors Avatar
JonathanBrouwer Avatar

@bors r+ rollup
I think all concerns are resolved now, since it's an error

rust-bors Avatar

📌 Commit d42a92b has been approved by JonathanBrouwer

It is now in the queue for this repository.