diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 918fd0a4582b7..f6b49fe6880ed 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -2,7 +2,7 @@ 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::Diagnostic; +use rustc_errors::{Diagnostic, MultiSpan}; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::CfgEntry; use rustc_hir::{AttrPath, Target}; @@ -76,8 +76,11 @@ 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 { + reject_branch_outer_attrs(p, &mut branch_attr_error)?; + if p.eat_keyword(exp!(Underscore)) { let underscore = p.prev_token; p.expect(exp!(FatArrow)).map_err(|e| e.emit())?; @@ -131,6 +134,10 @@ pub fn parse_cfg_select( } } + if let Some(guar) = branch_attr_error { + return Err(guar); + } + let it = branches .reachable .iter() @@ -143,6 +150,27 @@ pub fn parse_cfg_select( Ok(branches) } +fn reject_branch_outer_attrs( + p: &mut Parser<'_>, + 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(()); + }; + + 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( p: &mut Parser<'_>, predicates: impl Iterator, diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index b12209f0b92ee..a4aeb919b106a 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,11 +1,18 @@ -use rustc_ast::token; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; +use rustc_ast::{AttrKind, 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 @@ -36,4 +43,33 @@ 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); + } + + 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 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), + } + } + + Ok(Some(spans)) + } } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 113cff38a3a93..9fd92ad668c2b 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -202,3 +202,52 @@ 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 doc comments are not allowed on `cfg_select` branches + debug_assertions => {} + /// doc comment + //~^ ERROR doc comments are not allowed on `cfg_select` branches + _ => {} +} + +cfg_select! { + #[cfg(false)] + //~^ 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 => {} + _ => {} +} + +cfg_select! { + debug_assertions => {} + /// line1 + //~^ ERROR doc comments are not allowed on `cfg_select` branches + // line2 + /// 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 e20028a29d527..b8fbe103d2cf1 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -55,6 +55,74 @@ error: expected one of `(`, `::`, `=>`, or `=`, found `!` LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` +error: doc comments are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:208:5 + | +LL | /// doc comment + | ^^^^^^^^^^^^^^^ + +error: doc comments are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:211:5 + | +LL | /// doc comment + | ^^^^^^^^^^^^^^^ + +error: attributes are not allowed on `cfg_select` branches + --> $DIR/cfg_select.rs:217:5 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + +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 + | ^^^^^^^^^ +... +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 | @@ -115,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 9 previous errors; 7 warnings emitted +error: aborting due to 17 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`.