diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index f22d5f255c292..9c2ea8c4334b5 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,20 +1,47 @@ +use rustc_ast::token::Token; use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{AttrStyle, token}; use rustc_attr_parsing as attr; +use rustc_attr_parsing::parser::MetaItemOrLitParser; +use rustc_attr_parsing::{AttributeParser, ParsedDescription, ShouldEmit, parse_cfg_entry}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; -use rustc_parse::parser::cfg_select::{CfgSelectBranches, CfgSelectPredicate, parse_cfg_select}; -use rustc_span::{Ident, Span, sym}; +use rustc_feature::AttributeTemplate; +use rustc_hir::AttrPath; +use rustc_hir::attrs::CfgEntry; +use rustc_parse::exp; +use rustc_parse::parser::Parser; +use rustc_span::{ErrorGuaranteed, Ident, Span, sym}; use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable}; +enum CfgSelectPredicate { + Cfg(CfgEntry), + Wildcard(Token), +} + +#[derive(Default)] +struct CfgSelectBranches { + /// All the conditional branches. + pub reachable: Vec<(CfgEntry, TokenStream, Span)>, + /// The first wildcard `_ => { ... }` branch. + pub wildcard: Option<(Token, TokenStream, Span)>, + /// All branches after the first wildcard, including further wildcards. + /// These branches are kept for formatting. + pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>, +} + /// Selects the first arm whose predicate evaluates to true. fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> { for (cfg, tt, arm_span) in branches.reachable { - if attr::cfg_matches( - &cfg, + if attr::eval_config_entry( &ecx.sess, + &cfg, ecx.current_expansion.lint_node_id, Some(ecx.ecfg.features), - ) { + ShouldEmit::ErrorsAndLints, + ) + .as_bool() + { return Some((tt, arm_span)); } } @@ -27,7 +54,7 @@ pub(super) fn expand_cfg_select<'cx>( sp: Span, tts: TokenStream, ) -> MacroExpanderResult<'cx> { - ExpandResult::Ready(match parse_cfg_select(&mut ecx.new_parser_from_tts(tts)) { + ExpandResult::Ready(match parse_cfg_select(&mut ecx.new_parser_from_tts(tts), ecx) { Ok(branches) => { if let Some((underscore, _, _)) = branches.wildcard { // Warn for every unreachable predicate. We store the fully parsed branch for rustfmt. @@ -55,9 +82,64 @@ pub(super) fn expand_cfg_select<'cx>( DummyResult::any(sp, guar) } } - Err(err) => { - let guar = err.emit(); - DummyResult::any(sp, guar) - } + Err(guar) => DummyResult::any(sp, guar), }) } + +fn parse_cfg_select<'a>( + p: &mut Parser<'a>, + cx: &ExtCtxt<'_>, +) -> Result { + let mut branches = CfgSelectBranches::default(); + + while p.token != token::Eof { + 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); + + match branches.wildcard { + None => branches.wildcard = Some((underscore, tts, span)), + Some(_) => { + branches.unreachable.push((CfgSelectPredicate::Wildcard(underscore), tts, span)) + } + } + } else { + let meta = MetaItemOrLitParser::parse_single(p, ShouldEmit::ErrorsAndLints) + .map_err(|diag| diag.emit())?; + let cfg_span = meta.span(); + let cfg = AttributeParser::parse_single_args( + cx.sess, + cfg_span, + cfg_span, + AttrStyle::Inner, + AttrPath { + segments: vec![Ident::from_str("cfg_select")].into_boxed_slice(), + span: cfg_span, + }, + ParsedDescription::Macro, + cfg_span, + cx.current_expansion.lint_node_id, + Some(cx.ecfg.features), + ShouldEmit::ErrorsAndLints, + &meta, + parse_cfg_entry, + &AttributeTemplate::default(), + )?; + + p.expect(exp!(FatArrow)).map_err(|e| e.emit())?; + + let tts = p.parse_delimited_token_tree().map_err(|e| e.emit())?; + let span = cfg_span.to(p.token.span); + + match branches.wildcard { + None => branches.reachable.push((cfg, tts, span)), + Some(_) => branches.unreachable.push((CfgSelectPredicate::Cfg(cfg), tts, span)), + } + } + } + + Ok(branches) +} diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index b1b872808c8f8..592825cf4220c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -194,6 +194,18 @@ pub enum CfgEntry { Version(Option, Span), } +impl CfgEntry { + pub fn span(&self) -> Span { + let (CfgEntry::All(_, span) + | CfgEntry::Any(_, span) + | CfgEntry::Not(_, span) + | CfgEntry::Bool(_, span) + | CfgEntry::NameValue { span, .. } + | CfgEntry::Version(_, span)) = self; + *span + } +} + /// Possible values for the `#[linkage]` attribute, allowing to specify the /// linkage type for a `MonoItem`. /// diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index 08a71db4de853..9692d2c708938 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,83 +1,34 @@ -use rustc_ast::token::Token; +use rustc_ast::token; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; -use rustc_ast::{MetaItemInner, token}; use rustc_errors::PResult; -use rustc_span::Span; use crate::exp; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos}; -pub enum CfgSelectPredicate { - Cfg(MetaItemInner), - Wildcard(Token), -} - -#[derive(Default)] -pub struct CfgSelectBranches { - /// All the conditional branches. - pub reachable: Vec<(MetaItemInner, TokenStream, Span)>, - /// The first wildcard `_ => { ... }` branch. - pub wildcard: Option<(Token, TokenStream, Span)>, - /// All branches after the first wildcard, including further wildcards. - /// These branches are kept for formatting. - pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>, -} - -/// Parses a `TokenTree` consisting either of `{ /* ... */ }` (and strip the braces) or an -/// expression followed by a comma (and strip the comma). -fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> { - if p.token == token::OpenBrace { - // Strip the outer '{' and '}'. - match p.parse_token_tree() { - TokenTree::Token(..) => unreachable!("because of the expect above"), - TokenTree::Delimited(.., tts) => return Ok(tts), - } - } - let expr = p.collect_tokens(None, AttrWrapper::empty(), ForceCollect::Yes, |p, _| { - p.parse_expr_res(Restrictions::STMT_EXPR, AttrWrapper::empty()) - .map(|(expr, _)| (expr, Trailing::No, UsePreAttrPos::No)) - })?; - if !classify::expr_is_complete(&expr) && p.token != token::CloseBrace && p.token != token::Eof { - p.expect(exp!(Comma))?; - } else { - let _ = p.eat(exp!(Comma)); - } - Ok(TokenStream::from_ast(&expr)) -} - -pub fn parse_cfg_select<'a>(p: &mut Parser<'a>) -> PResult<'a, CfgSelectBranches> { - let mut branches = CfgSelectBranches::default(); - - while p.token != token::Eof { - if p.eat_keyword(exp!(Underscore)) { - let underscore = p.prev_token; - p.expect(exp!(FatArrow))?; - - let tts = parse_token_tree(p)?; - let span = underscore.span.to(p.token.span); - - match branches.wildcard { - None => branches.wildcard = Some((underscore, tts, span)), - Some(_) => { - branches.unreachable.push((CfgSelectPredicate::Wildcard(underscore), tts, span)) - } +impl<'a> Parser<'a> { + /// Parses a `TokenTree` consisting either of `{ /* ... */ }` (and strip the braces) or an + /// expression followed by a comma (and strip the comma). + pub fn parse_delimited_token_tree(&mut self) -> PResult<'a, TokenStream> { + if self.token == token::OpenBrace { + // Strip the outer '{' and '}'. + match self.parse_token_tree() { + TokenTree::Token(..) => unreachable!("because of the expect above"), + TokenTree::Delimited(.., tts) => return Ok(tts), } + } + let expr = self.collect_tokens(None, AttrWrapper::empty(), ForceCollect::Yes, |p, _| { + p.parse_expr_res(Restrictions::STMT_EXPR, AttrWrapper::empty()) + .map(|(expr, _)| (expr, Trailing::No, UsePreAttrPos::No)) + })?; + if !classify::expr_is_complete(&expr) + && self.token != token::CloseBrace + && self.token != token::Eof + { + self.expect(exp!(Comma))?; } else { - let meta_item = p.parse_meta_item_inner()?; - p.expect(exp!(FatArrow))?; - - let tts = parse_token_tree(p)?; - let span = meta_item.span().to(p.token.span); - - match branches.wildcard { - None => branches.reachable.push((meta_item, tts, span)), - Some(_) => { - branches.unreachable.push((CfgSelectPredicate::Cfg(meta_item), tts, span)) - } - } + let _ = self.eat(exp!(Comma)); } + Ok(TokenStream::from_ast(&expr)) } - - Ok(branches) } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 9241141ef9a6c..2a627cc05b93b 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -60,3 +60,38 @@ cfg_select! { cfg_select! {} //~^ ERROR none of the predicates in this `cfg_select` evaluated to true + +cfg_select! { + => {} + //~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>` +} + +cfg_select! { + () => {} + //~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `(` +} + +cfg_select! { + "str" => {} + //~^ ERROR malformed `cfg_select` macro input [E0539] +} + +cfg_select! { + a::b => {} + //~^ ERROR malformed `cfg_select` macro input [E0539] +} + +cfg_select! { + a() => {} + //~^ ERROR invalid predicate `a` [E0537] +} + +cfg_select! { + a + 1 => {} + //~^ ERROR expected one of `(`, `::`, `=>`, or `=`, found `+` +} + +cfg_select! { + cfg!() => {} + //~^ ERROR expected one of `(`, `::`, `=>`, or `=`, found `!` +} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 7280f35c16f93..3a5d2b0a1e1ee 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -21,5 +21,51 @@ error: none of the predicates in this `cfg_select` evaluated to true LL | cfg_select! {} | ^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors; 1 warning emitted +error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>` + --> $DIR/cfg_select.rs:65:5 + | +LL | => {} + | ^^ + +error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `(` + --> $DIR/cfg_select.rs:70:5 + | +LL | () => {} + | ^ + +error[E0539]: malformed `cfg_select` macro input + --> $DIR/cfg_select.rs:75:5 + | +LL | "str" => {} + | ^^^^^ expected a valid identifier here + | + +error[E0539]: malformed `cfg_select` macro input + --> $DIR/cfg_select.rs:80:5 + | +LL | a::b => {} + | ^^^^ expected a valid identifier here + | + +error[E0537]: invalid predicate `a` + --> $DIR/cfg_select.rs:85:5 + | +LL | a() => {} + | ^^^ + +error: expected one of `(`, `::`, `=>`, or `=`, found `+` + --> $DIR/cfg_select.rs:90:7 + | +LL | a + 1 => {} + | ^ expected one of `(`, `::`, `=>`, or `=` + +error: expected one of `(`, `::`, `=>`, or `=`, found `!` + --> $DIR/cfg_select.rs:95:8 + | +LL | cfg!() => {} + | ^ expected one of `(`, `::`, `=>`, or `=` + +error: aborting due to 9 previous errors; 1 warning emitted +Some errors have detailed explanations: E0537, E0539. +For more information about an error, try `rustc --explain E0537`.