Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 92 additions & 10 deletions compiler/rustc_builtin_macros/src/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -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<CfgSelectBranches, ErrorGuaranteed> {
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)
}
12 changes: 12 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ pub enum CfgEntry {
Version(Option<RustcVersion>, 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`.
///
Expand Down
93 changes: 22 additions & 71 deletions compiler/rustc_parse/src/parser/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -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)
}
35 changes: 35 additions & 0 deletions tests/ui/macros/cfg_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 `!`
}
48 changes: 47 additions & 1 deletion tests/ui/macros/cfg_select.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Loading