From 9c49112ca442800dfd746826c3fe8c07c98a23aa Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:42:08 -0400 Subject: [PATCH 1/6] Lint doc comments in cfg_select branches --- .../src/attributes/cfg_select.rs | 45 +++++++++++++++++-- .../unused/unused-doc-comments-for-macros.rs | 8 ++++ .../unused-doc-comments-for-macros.stderr | 10 ++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 918fd0a4582b7..99d8f557238cb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -1,15 +1,15 @@ -use rustc_ast::token::Token; +use rustc_ast::token::{CommentKind, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrStyle, NodeId, token}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::Diagnostic; +use rustc_errors::{DiagDecorator, Diagnostic}; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, Target}; use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; -use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES; +use rustc_session::lint::builtin::{UNREACHABLE_CFG_SELECT_PREDICATES, UNUSED_DOC_COMMENTS}; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; use crate::attributes::AttributeSafety; @@ -78,12 +78,15 @@ pub fn parse_cfg_select( let mut branches = CfgSelectBranches::default(); while p.token != token::Eof { + let doc_comment = eat_outer_doc_comments(p); + if p.eat_keyword(exp!(Underscore)) { let underscore = p.prev_token; p.expect(exp!(FatArrow)).map_err(|e| e.emit())?; let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?; let span = underscore.span.to(p.token.span); + lint_unused_doc_comment(p, doc_comment, lint_node_id); match branches.wildcard { None => branches.wildcard = Some((underscore, tts, span)), @@ -123,6 +126,7 @@ pub fn parse_cfg_select( let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?; let span = cfg_span.to(p.token.span); + lint_unused_doc_comment(p, doc_comment, lint_node_id); match branches.wildcard { None => branches.reachable.push((cfg, tts, span)), @@ -143,6 +147,41 @@ pub fn parse_cfg_select( Ok(branches) } +fn eat_outer_doc_comments(p: &mut Parser<'_>) -> Option<(Span, CommentKind)> { + let mut doc_comment: Option<(Span, CommentKind)> = None; + + while let token::DocComment(comment_kind, AttrStyle::Outer, _) = p.token.kind { + let span = p.token.span; + doc_comment = Some(match doc_comment { + Some((prev_span, _)) => (prev_span.with_hi(span.hi()), comment_kind), + None => (span, comment_kind), + }); + p.bump(); + } + + doc_comment +} + +fn lint_unused_doc_comment( + p: &mut Parser<'_>, + doc_comment: Option<(Span, CommentKind)>, + lint_node_id: NodeId, +) { + let Some((span, comment_kind)) = doc_comment else { return }; + let help = match comment_kind { + CommentKind::Line => "use `//` for a plain comment", + CommentKind::Block => "use `/* */` for a plain comment", + }; + p.psess.buffer_lint( + UNUSED_DOC_COMMENTS, + span, + lint_node_id, + DiagDecorator(move |diag| { + diag.primary_message("unused doc comment").help(help); + }), + ); +} + fn lint_unreachable( p: &mut Parser<'_>, predicates: impl Iterator, diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs index 05828ebb2c353..8c5e2a8be4f5c 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs @@ -14,4 +14,12 @@ fn main() { /// line2 /// line3 foo!(); + + // Regression test for https://github.com/rust-lang/rust/issues/155701. + cfg_select! { + /// line1 + //~^ ERROR: unused doc comment + debug_assertions => (), + _ => (), + } } diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr index 26b1c2b058c1f..91517677c1d33 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr @@ -27,5 +27,13 @@ LL | | /// line3 | = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion -error: aborting due to 2 previous errors +error: unused doc comment + --> $DIR/unused-doc-comments-for-macros.rs:20:9 + | +LL | /// line1 + | ^^^^^^^^^ + | + = help: use `//` for a plain comment + +error: aborting due to 3 previous errors From 4bb2b07fc248133a64c8ea8c8ae1f4a654156af8 Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:36:28 -0400 Subject: [PATCH 2/6] test with multi-line doc comment Co-authored-by: Copilot --- .../unused/unused-doc-comments-for-macros.rs | 8 ++++++-- .../unused-doc-comments-for-macros.stderr | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs index 8c5e2a8be4f5c..96de81399633d 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs @@ -17,9 +17,13 @@ fn main() { // Regression test for https://github.com/rust-lang/rust/issues/155701. cfg_select! { - /// line1 - //~^ ERROR: unused doc comment + /// line1 //~ ERROR: unused doc comment + /// line2 + /// line3 debug_assertions => (), + /// line1 //~ ERROR: unused doc comment + /// line2 + /// line3 _ => (), } } diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr index 91517677c1d33..295e328e67724 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr @@ -30,10 +30,22 @@ LL | | /// line3 error: unused doc comment --> $DIR/unused-doc-comments-for-macros.rs:20:9 | -LL | /// line1 - | ^^^^^^^^^ +LL | / /// line1 +LL | | /// line2 +LL | | /// line3 + | |_________________^ | = help: use `//` for a plain comment -error: aborting due to 3 previous errors +error: unused doc comment + --> $DIR/unused-doc-comments-for-macros.rs:24:9 + | +LL | / /// line1 +LL | | /// line2 +LL | | /// line3 + | |_________________^ + | + = help: use `//` for a plain comment + +error: aborting due to 4 previous errors From f0d49fa39eadc771798ed74c071b72f6aa75af83 Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:40:47 -0400 Subject: [PATCH 3/6] Reject outer attributes on `cfg_select` branches --- .../src/attributes/cfg_select.rs | 61 ++++++++----------- compiler/rustc_parse/src/parser/cfg_select.rs | 11 ++++ .../unused/unused-doc-comments-for-macros.rs | 12 ---- .../unused-doc-comments-for-macros.stderr | 22 +------ tests/ui/macros/cfg_select.rs | 26 ++++++++ tests/ui/macros/cfg_select.stderr | 29 ++++++++- 6 files changed, 90 insertions(+), 71 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 99d8f557238cb..3d32e5b25ebdf 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -1,15 +1,15 @@ -use rustc_ast::token::{CommentKind, Token}; +use rustc_ast::token::Token; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrStyle, NodeId, token}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagDecorator, Diagnostic}; +use rustc_errors::{Diagnostic, MultiSpan}; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, Target}; use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; -use rustc_session::lint::builtin::{UNREACHABLE_CFG_SELECT_PREDICATES, UNUSED_DOC_COMMENTS}; +use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; use crate::attributes::AttributeSafety; @@ -76,9 +76,12 @@ pub fn parse_cfg_select( lint_node_id: NodeId, ) -> Result { let mut branches = CfgSelectBranches::default(); + let mut branch_attr_error: Option = None; while p.token != token::Eof { - let doc_comment = eat_outer_doc_comments(p); + if let Some(guar) = reject_branch_outer_attrs(p)? { + branch_attr_error.get_or_insert(guar); + } if p.eat_keyword(exp!(Underscore)) { let underscore = p.prev_token; @@ -86,7 +89,6 @@ pub fn parse_cfg_select( let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?; let span = underscore.span.to(p.token.span); - lint_unused_doc_comment(p, doc_comment, lint_node_id); match branches.wildcard { None => branches.wildcard = Some((underscore, tts, span)), @@ -126,7 +128,6 @@ pub fn parse_cfg_select( let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?; let span = cfg_span.to(p.token.span); - lint_unused_doc_comment(p, doc_comment, lint_node_id); match branches.wildcard { None => branches.reachable.push((cfg, tts, span)), @@ -135,6 +136,10 @@ pub fn parse_cfg_select( } } + if let Some(guar) = branch_attr_error { + return Err(guar); + } + let it = branches .reachable .iter() @@ -147,39 +152,21 @@ pub fn parse_cfg_select( Ok(branches) } -fn eat_outer_doc_comments(p: &mut Parser<'_>) -> Option<(Span, CommentKind)> { - let mut doc_comment: Option<(Span, CommentKind)> = None; - - while let token::DocComment(comment_kind, AttrStyle::Outer, _) = p.token.kind { - let span = p.token.span; - doc_comment = Some(match doc_comment { - Some((prev_span, _)) => (prev_span.with_hi(span.hi()), comment_kind), - None => (span, comment_kind), - }); - p.bump(); - } - - doc_comment -} - -fn lint_unused_doc_comment( +fn reject_branch_outer_attrs( p: &mut Parser<'_>, - doc_comment: Option<(Span, CommentKind)>, - lint_node_id: NodeId, -) { - let Some((span, comment_kind)) = doc_comment else { return }; - let help = match comment_kind { - CommentKind::Line => "use `//` for a plain comment", - CommentKind::Block => "use `/* */` for a plain comment", +) -> Result, ErrorGuaranteed> { + let Some(spans) = p.parse_cfg_select_branch_outer_attrs().map_err(|e| e.emit())? else { + return Ok(None); }; - p.psess.buffer_lint( - UNUSED_DOC_COMMENTS, - span, - lint_node_id, - DiagDecorator(move |diag| { - diag.primary_message("unused doc comment").help(help); - }), - ); + + Ok(Some( + p.dcx() + .struct_span_err( + MultiSpan::from_spans(spans), + "outer attributes are not allowed on `cfg_select` branches", + ) + .emit(), + )) } fn lint_unreachable( diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index b12209f0b92ee..982aa571c1df5 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -2,6 +2,7 @@ use rustc_ast::token; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; use rustc_errors::PResult; +use rustc_span::Span; use crate::exp; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos}; @@ -36,4 +37,14 @@ impl<'a> Parser<'a> { } Ok(TokenStream::from_ast(&expr)) } + + /// Parses outer attributes before a `cfg_select!` branch for recovery. + pub fn parse_cfg_select_branch_outer_attrs(&mut self) -> PResult<'a, Option>> { + let attrs = self.parse_outer_attributes()?; + if attrs.is_empty() { + return Ok(None); + } + + Ok(Some(attrs.take_for_recovery(self.psess).into_iter().map(|attr| attr.span).collect())) + } } diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs index 96de81399633d..05828ebb2c353 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs @@ -14,16 +14,4 @@ fn main() { /// line2 /// line3 foo!(); - - // 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 - _ => (), - } } diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr index 295e328e67724..26b1c2b058c1f 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr @@ -27,25 +27,5 @@ LL | | /// line3 | = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion -error: unused doc comment - --> $DIR/unused-doc-comments-for-macros.rs:20:9 - | -LL | / /// line1 -LL | | /// line2 -LL | | /// line3 - | |_________________^ - | - = help: use `//` for a plain comment - -error: unused doc comment - --> $DIR/unused-doc-comments-for-macros.rs:24:9 - | -LL | / /// line1 -LL | | /// line2 -LL | | /// line3 - | |_________________^ - | - = help: use `//` for a plain comment - -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 113cff38a3a93..6afdad871f86c 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -202,3 +202,29 @@ cfg_select! { //~^ ERROR expected one of `(`, `::`, `=>`, or `=`, found `!` //~| WARN unexpected `cfg` condition name } + +// Regression test for https://github.com/rust-lang/rust/issues/155701. +cfg_select! { + /// doc comment + //~^ ERROR outer attributes are not allowed on `cfg_select` branches + debug_assertions => {} + /// doc comment + //~^ ERROR outer attributes are not allowed on `cfg_select` branches + _ => {} +} + +cfg_select! { + #[cfg(false)] + //~^ ERROR outer attributes are not allowed on `cfg_select` branches + debug_assertions => {} + _ => {} +} + +cfg_select! { + debug_assertions => {} + /// line1 + //~^ ERROR outer attributes are not allowed on `cfg_select` branches + // line2 + /// line3 + _ => {} +} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index e20028a29d527..fd02def3bca6b 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -55,6 +55,33 @@ error: expected one of `(`, `::`, `=>`, or `=`, found `!` LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` +error: outer attributes are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:208:5 + | +LL | /// doc comment + | ^^^^^^^^^^^^^^^ + +error: outer attributes are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:211:5 + | +LL | /// doc comment + | ^^^^^^^^^^^^^^^ + +error: outer attributes are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:217:5 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + +error: outer attributes are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:225:5 + | +LL | /// line1 + | ^^^^^^^^^ +... +LL | /// line3 + | ^^^^^^^^^ + warning: unreachable configuration predicate --> $DIR/cfg_select.rs:136:5 | @@ -115,7 +142,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 9 previous errors; 7 warnings emitted +error: aborting due to 13 previous errors; 7 warnings emitted Some errors have detailed explanations: E0537, E0539. For more information about an error, try `rustc --explain E0537`. From 96cb64acbe2e4e5863909f93ba054a73b0978361 Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Sun, 26 Apr 2026 09:04:45 -0400 Subject: [PATCH 4/6] address nits and gotchas Co-authored-by: Copilot --- .../src/attributes/cfg_select.rs | 28 ++++++++------- compiler/rustc_parse/src/parser/cfg_select.rs | 31 ++++++++++++++-- tests/ui/macros/cfg_select.rs | 22 +++++++++--- tests/ui/macros/cfg_select.stderr | 36 +++++++++++++++---- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 3d32e5b25ebdf..f6b49fe6880ed 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -79,9 +79,7 @@ pub fn parse_cfg_select( let mut branch_attr_error: Option = None; while p.token != token::Eof { - if let Some(guar) = reject_branch_outer_attrs(p)? { - branch_attr_error.get_or_insert(guar); - } + reject_branch_outer_attrs(p, &mut branch_attr_error)?; if p.eat_keyword(exp!(Underscore)) { let underscore = p.prev_token; @@ -154,19 +152,23 @@ pub fn parse_cfg_select( fn reject_branch_outer_attrs( p: &mut Parser<'_>, -) -> Result, ErrorGuaranteed> { + branch_attr_error: &mut Option, +) -> Result<(), ErrorGuaranteed> { let Some(spans) = p.parse_cfg_select_branch_outer_attrs().map_err(|e| e.emit())? else { - return Ok(None); + return Ok(()); }; - Ok(Some( - p.dcx() - .struct_span_err( - MultiSpan::from_spans(spans), - "outer attributes are not allowed on `cfg_select` branches", - ) - .emit(), - )) + for (spans, msg) in [ + (spans.doc_comments, "doc comments are not allowed on `cfg_select` branches"), + (spans.attrs, "attributes are not allowed on `cfg_select` branches"), + ] { + if !spans.is_empty() { + branch_attr_error + .get_or_insert(p.dcx().struct_span_err(MultiSpan::from_spans(spans), msg).emit()); + } + } + + Ok(()) } fn lint_unreachable( diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index 982aa571c1df5..0b26c94a4c1ee 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,12 +1,18 @@ -use rustc_ast::token; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; +use rustc_ast::{AttrKind, AttrStyle, token}; use rustc_errors::PResult; use rustc_span::Span; use crate::exp; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos}; +#[derive(Default)] +pub struct CfgSelectBranchAttrSpans { + pub attrs: Vec, + pub doc_comments: Vec, +} + impl<'a> Parser<'a> { /// Parses a `TokenTree` consisting either of `{ /* ... */ }` optionally followed by a comma /// (and strip the braces and the optional comma) or an expression followed by a comma @@ -39,12 +45,31 @@ impl<'a> Parser<'a> { } /// Parses outer attributes before a `cfg_select!` branch for recovery. - pub fn parse_cfg_select_branch_outer_attrs(&mut self) -> PResult<'a, Option>> { + pub fn parse_cfg_select_branch_outer_attrs( + &mut self, + ) -> PResult<'a, Option> { + let inner_doc_comment_span = + if let token::DocComment(_, AttrStyle::Inner, _) = self.token.kind { + Some(self.token.span) + } else { + None + }; let attrs = self.parse_outer_attributes()?; if attrs.is_empty() { return Ok(None); } - Ok(Some(attrs.take_for_recovery(self.psess).into_iter().map(|attr| attr.span).collect())) + let mut spans = CfgSelectBranchAttrSpans::default(); + for attr in attrs.take_for_recovery(self.psess) { + match attr.kind { + AttrKind::Normal(..) => spans.attrs.push(attr.span), + // `parse_outer_attributes` already emitted E0753 for this inner doc comment + // before recovering it as an outer doc-comment attribute. + AttrKind::DocComment(..) if Some(attr.span) == inner_doc_comment_span => {} + AttrKind::DocComment(..) => spans.doc_comments.push(attr.span), + } + } + + Ok(Some(spans)) } } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 6afdad871f86c..8c2abdce55e0d 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -206,16 +206,30 @@ cfg_select! { // Regression test for https://github.com/rust-lang/rust/issues/155701. cfg_select! { /// doc comment - //~^ ERROR outer attributes are not allowed on `cfg_select` branches + //~^ ERROR doc comments are not allowed on `cfg_select` branches debug_assertions => {} /// doc comment - //~^ ERROR outer attributes are not allowed on `cfg_select` branches + //~^ ERROR doc comments are not allowed on `cfg_select` branches _ => {} } cfg_select! { #[cfg(false)] - //~^ ERROR outer attributes are not allowed on `cfg_select` branches + //~^ ERROR attributes are not allowed on `cfg_select` branches + debug_assertions => {} + _ => {} +} + +cfg_select! { + #![cfg(false)] + //~^ ERROR an inner attribute is not permitted in this context + debug_assertions => {} + _ => {} +} + +cfg_select! { + //! inner doc comment + //~^ ERROR expected outer doc comment debug_assertions => {} _ => {} } @@ -223,7 +237,7 @@ cfg_select! { cfg_select! { debug_assertions => {} /// line1 - //~^ ERROR outer attributes are not allowed on `cfg_select` branches + //~^ ERROR doc comments are not allowed on `cfg_select` branches // line2 /// line3 _ => {} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index fd02def3bca6b..ab62eab0eda39 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -55,26 +55,48 @@ error: expected one of `(`, `::`, `=>`, or `=`, found `!` LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` -error: outer attributes are not allowed on `cfg_select` branches +error: doc comments are not allowed on `cfg_select` branches --> $DIR/cfg_select.rs:208:5 | LL | /// doc comment | ^^^^^^^^^^^^^^^ -error: outer attributes are not allowed on `cfg_select` branches +error: doc comments are not allowed on `cfg_select` branches --> $DIR/cfg_select.rs:211:5 | LL | /// doc comment | ^^^^^^^^^^^^^^^ -error: outer attributes are not allowed on `cfg_select` branches +error: attributes are not allowed on `cfg_select` branches --> $DIR/cfg_select.rs:217:5 | LL | #[cfg(false)] | ^^^^^^^^^^^^^ -error: outer attributes are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:225:5 +error: an inner attribute is not permitted in this context + --> $DIR/cfg_select.rs:224:5 + | +LL | #![cfg(false)] + | ^^^^^^^^^^^^^^ + | + = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files + = note: outer attributes, like `#[test]`, annotate the item following them + +error[E0753]: expected outer doc comment + --> $DIR/cfg_select.rs:231:5 + | +LL | //! inner doc comment + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: inner doc comments like this (starting with `//!` or `/*!`) can only appear before items +help: you might have meant to write a regular comment + | +LL - //! inner doc comment +LL + // inner doc comment + | + +error: doc comments are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:239:5 | LL | /// line1 | ^^^^^^^^^ @@ -142,7 +164,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 13 previous errors; 7 warnings emitted +error: aborting due to 15 previous errors; 7 warnings emitted -Some errors have detailed explanations: E0537, E0539. +Some errors have detailed explanations: E0537, E0539, E0753. For more information about an error, try `rustc --explain E0537`. From 40dcc5ddcd62a9872faa81798c3bafe146312902 Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:46:08 -0400 Subject: [PATCH 5/6] one more test Co-authored-by: Copilot --- compiler/rustc_parse/src/parser/cfg_select.rs | 29 ++++++++++++++----- tests/ui/macros/cfg_select.rs | 9 ++++++ tests/ui/macros/cfg_select.stderr | 21 +++++++++++++- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index 0b26c94a4c1ee..bfe3c052efb60 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -5,6 +5,7 @@ use rustc_errors::PResult; use rustc_span::Span; use crate::exp; +use crate::parser::attr::InnerAttrPolicy; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos}; #[derive(Default)] @@ -48,12 +49,26 @@ impl<'a> Parser<'a> { pub fn parse_cfg_select_branch_outer_attrs( &mut self, ) -> PResult<'a, Option> { - let inner_doc_comment_span = - if let token::DocComment(_, AttrStyle::Inner, _) = self.token.kind { - Some(self.token.span) + // `parse_outer_attributes` recovers inner doc comments as outer ones, so collect their + // spans first and suppress the follow-up `cfg_select` branch diagnostic for them. + let mut inner_doc_comment_spans = Vec::new(); + let mut snapshot = self.create_snapshot_for_diagnostic(); + loop { + if snapshot.check(exp!(Pound)) { + if let Err(err) = snapshot.parse_attribute(InnerAttrPolicy::Permitted) { + err.cancel(); + break; + } + } else if let token::DocComment(_, attr_style, _) = snapshot.token.kind { + if attr_style == AttrStyle::Inner { + inner_doc_comment_spans.push(snapshot.token.span); + } + snapshot.bump(); } else { - None - }; + break; + } + } + let attrs = self.parse_outer_attributes()?; if attrs.is_empty() { return Ok(None); @@ -63,9 +78,9 @@ impl<'a> Parser<'a> { for attr in attrs.take_for_recovery(self.psess) { match attr.kind { AttrKind::Normal(..) => spans.attrs.push(attr.span), - // `parse_outer_attributes` already emitted E0753 for this inner doc comment + // `parse_outer_attributes` already emitted E0753 for each inner doc comment // before recovering it as an outer doc-comment attribute. - AttrKind::DocComment(..) if Some(attr.span) == inner_doc_comment_span => {} + AttrKind::DocComment(..) if inner_doc_comment_spans.contains(&attr.span) => {} AttrKind::DocComment(..) => spans.doc_comments.push(attr.span), } } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 8c2abdce55e0d..9fd92ad668c2b 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -242,3 +242,12 @@ cfg_select! { /// line3 _ => {} } + +cfg_select! { + /// outer doc comment + //~^ ERROR doc comments are not allowed on `cfg_select` branches + //! inner doc comment + //~^ ERROR expected outer doc comment + debug_assertions => {} + _ => {} +} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index ab62eab0eda39..b8fbe103d2cf1 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -104,6 +104,25 @@ LL | /// line1 LL | /// line3 | ^^^^^^^^^ +error[E0753]: expected outer doc comment + --> $DIR/cfg_select.rs:249:5 + | +LL | //! inner doc comment + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: inner doc comments like this (starting with `//!` or `/*!`) can only appear before items +help: you might have meant to write a regular comment + | +LL - //! inner doc comment +LL + // inner doc comment + | + +error: doc comments are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:247:5 + | +LL | /// outer doc comment + | ^^^^^^^^^^^^^^^^^^^^^ + warning: unreachable configuration predicate --> $DIR/cfg_select.rs:136:5 | @@ -164,7 +183,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 15 previous errors; 7 warnings emitted +error: aborting due to 17 previous errors; 7 warnings emitted Some errors have detailed explanations: E0537, E0539, E0753. For more information about an error, try `rustc --explain E0537`. From c04993f955563fc24695ecac138cd29043419a4d Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:04:18 -0400 Subject: [PATCH 6/6] avoid snapshotting and classify recovered doc comments directly --- compiler/rustc_parse/src/parser/cfg_select.rs | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index bfe3c052efb60..a4aeb919b106a 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,11 +1,10 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; -use rustc_ast::{AttrKind, AttrStyle, token}; +use rustc_ast::{AttrKind, token}; use rustc_errors::PResult; use rustc_span::Span; use crate::exp; -use crate::parser::attr::InnerAttrPolicy; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos}; #[derive(Default)] @@ -49,26 +48,6 @@ impl<'a> Parser<'a> { pub fn parse_cfg_select_branch_outer_attrs( &mut self, ) -> PResult<'a, Option> { - // `parse_outer_attributes` recovers inner doc comments as outer ones, so collect their - // spans first and suppress the follow-up `cfg_select` branch diagnostic for them. - let mut inner_doc_comment_spans = Vec::new(); - let mut snapshot = self.create_snapshot_for_diagnostic(); - loop { - if snapshot.check(exp!(Pound)) { - if let Err(err) = snapshot.parse_attribute(InnerAttrPolicy::Permitted) { - err.cancel(); - break; - } - } else if let token::DocComment(_, attr_style, _) = snapshot.token.kind { - if attr_style == AttrStyle::Inner { - inner_doc_comment_spans.push(snapshot.token.span); - } - snapshot.bump(); - } else { - break; - } - } - let attrs = self.parse_outer_attributes()?; if attrs.is_empty() { return Ok(None); @@ -78,9 +57,15 @@ impl<'a> Parser<'a> { for attr in attrs.take_for_recovery(self.psess) { match attr.kind { AttrKind::Normal(..) => spans.attrs.push(attr.span), - // `parse_outer_attributes` already emitted E0753 for each inner doc comment - // before recovering it as an outer doc-comment attribute. - AttrKind::DocComment(..) if inner_doc_comment_spans.contains(&attr.span) => {} + // `parse_outer_attributes` already emitted E0753 for inner doc comments before + // recovering them as outer doc-comment attributes. + AttrKind::DocComment(comment_kind, _) + if self.span_to_snippet(attr.span).ok().is_some_and( + |snippet| match comment_kind { + token::CommentKind::Line => snippet.starts_with("//!"), + token::CommentKind::Block => snippet.starts_with("/*!"), + }, + ) => {} AttrKind::DocComment(..) => spans.doc_comments.push(attr.span), } }