Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn cfg_match into a builtin #116323

Closed
wants to merge 1 commit into from
Closed
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
Turn cfg_match into a builtin
c410-f3r committed Oct 2, 2023
commit 9423daa4736d224ba65597fa418acab1befee2f9
27 changes: 27 additions & 0 deletions compiler/rustc_ast/src/token.rs
Original file line number Diff line number Diff line change
@@ -229,35 +229,61 @@ fn ident_can_begin_type(name: Symbol, span: Span, is_raw: bool) -> bool {
#[derive(PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum TokenKind {
/* Expression-operator symbols. */
/// `=`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have often wondered about the meaning of some variants, so let's just comment what they actually are.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change and the // tidy-alphabetical stuff can be submitted separately and I'll approve them right away.

Eq,
/// `<`
Lt,
/// `<=`
Le,
/// `==`
EqEq,
/// `!=`
Ne,
/// `>`
Ge,
/// `>=`
Gt,
/// `&&`
AndAnd,
/// `||`
OrOr,
/// `!`
Not,
/// `~`
Tilde,
BinOp(BinOpToken),
BinOpEq(BinOpToken),

/* Structural symbols */
/// `@`
At,
/// `.`
Dot,
/// `..`
DotDot,
/// `...`
DotDotDot,
/// `..=`
DotDotEq,
/// `,`
Comma,
/// `;`
Semi,
/// `:`
Colon,
/// `::`
ModSep,
/// `->`
RArrow,
/// `<-`
LArrow,
/// `=>`
FatArrow,
/// `#`
Pound,
/// `$`
Dollar,
/// `?`
Question,
/// Used by proc macros for representing lifetimes, not generated by lexer right now.
SingleQuote,
@@ -296,6 +322,7 @@ pub enum TokenKind {
/// similarly to symbols in string literal tokens.
DocComment(CommentKind, ast::AttrStyle, Symbol),

/// End Of File
Eof,
}

5 changes: 5 additions & 0 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
@@ -69,6 +69,11 @@ builtin_macros_cfg_accessible_indeterminate = cannot determine whether the path
builtin_macros_cfg_accessible_literal_path = `cfg_accessible` path cannot be a literal
builtin_macros_cfg_accessible_multiple_paths = multiple `cfg_accessible` paths are specified
builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not specified
builtin_macros_cfg_match_bad_arm = conditional arm must be declared with a trailing `=>`
builtin_macros_cfg_match_bad_single_arm = arms without brackets are only allowed for functions at the current time
builtin_macros_cfg_match_bad_wildcard = the last arm is expected to be a wildcard
builtin_macros_cfg_match_meaningless_arms = single arm with a single element has the same effect of a standalone `cfg`
builtin_macros_cfg_match_missing_comma = conditional arms with a single element must end with a comma
builtin_macros_concat_bytes_array = cannot concatenate doubly nested array
.note = byte strings are treated as arrays of bytes
.help = try flattening the array
293 changes: 293 additions & 0 deletions compiler/rustc_builtin_macros/src/cfg_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
use crate::errors;
use rustc_ast::{
attr::mk_attr,
ptr::P,
token,
tokenstream::{DelimSpan, TokenStream, TokenTree},
AssocItem, AssocItemKind, AttrArgs, AttrStyle, Attribute, DelimArgs, Item, ItemKind, Path,
Stmt, StmtKind,
};
use rustc_errors::PResult;
use rustc_expand::base::{DummyResult, ExtCtxt, MacResult};
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_span::{
symbol::{kw, sym, Ident},
Span,
};
use smallvec::SmallVec;

type ItemOffset = (usize, bool, TokenStream);

// ```rust
// cfg_match! {
// cfg(unix) => { fn foo() -> i32 { 1 } },
// _ => { fn foo() -> i32 { 2 } },
// }
// ```
//
// The above `cfg_match!` is expanded to the following elements:
//
// ```rust
// #[cfg(all(unix, not(any())))]
// fn foo() -> i32 { 1 }
//
// #[cfg(all(not(any(unix))))]
// fn foo() -> i32 { 2 }
// ```
pub fn expand_cfg_match(
cx: &mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> Box<dyn MacResult + 'static> {
let rslt = || {
let (does_not_have_wildcard, mut items, mut items_offsets) = parse(cx, tts)?;
iter(
cx,
&mut items,
&mut items_offsets,
sp,
|local_cx, no, yes, items| {
let attr = mk_cfg(local_cx, no, sp, Some(yes));
items.iter_mut().for_each(|item| item.attrs.push(attr.clone()));
},
|local_cx, no, yes, items| {
let attr = mk_cfg(local_cx, no, sp, does_not_have_wildcard.then_some(yes));
items.iter_mut().for_each(|item| item.attrs.push(attr.clone()));
},
)?;
PResult::Ok(items)
};
match rslt() {
Err(mut err) => {
err.emit();
return DummyResult::any(sp);
}
Ok(items) => Box::new(CfgMatchOutput(items)),
}
}

fn iter<'session>(
cx: &mut ExtCtxt<'session>,
items: &mut [P<Item>],
items_offsets: &[ItemOffset],
sp: Span,
mut cb: impl FnMut(&mut ExtCtxt<'session>, &[ItemOffset], &TokenStream, &mut [P<Item>]),
mut cb_last: impl FnMut(&mut ExtCtxt<'session>, &[ItemOffset], &TokenStream, &mut [P<Item>]),
) -> PResult<'session, ()> {
match items_offsets {
[] => {}
[first] => {
if first.1 {
return Err(cx.sess.create_err(errors::CfgMatchMeaninglessArms { span: sp }));
}
cb_last(cx, &[], &first.2, items.get_mut(..first.0).unwrap_or_default());
}
[first, rest @ .., last] => {
let mut no_idx = 1;
let mut offset = first.0;
let mut prev_offset = 0;
cb(cx, &[], &first.2, items.get_mut(prev_offset..offset).unwrap_or_default());
for elem in rest {
prev_offset = offset;
offset = elem.0;
cb(
cx,
items_offsets.get(..no_idx).unwrap_or_default(),
&elem.2,
items.get_mut(prev_offset..offset).unwrap_or_default(),
);
no_idx = no_idx.wrapping_add(1);
}
prev_offset = offset;
offset = last.0;
cb_last(
cx,
items_offsets.get(..no_idx).unwrap_or_default(),
&last.2,
items.get_mut(prev_offset..offset).unwrap_or_default(),
);
}
}
Ok(())
}

// #[cfg(all(** YES **, not(any(** NO **, ** NO **, ..))))]
fn mk_cfg(
cx: &mut ExtCtxt<'_>,
no: &[ItemOffset],
sp: Span,
yes: Option<&TokenStream>,
) -> Attribute {
let mut any_tokens = TokenStream::new(Vec::with_capacity(4));
if let [first, ref rest @ ..] = no {
any_tokens.push_stream(first.2.clone());
for elem in rest.iter() {
any_tokens.push_tree(TokenTree::token_alone(token::Comma, sp));
any_tokens.push_stream(elem.2.clone());
}
}

let mut not_tokens = TokenStream::new(Vec::with_capacity(2));
not_tokens.push_tree(TokenTree::token_alone(token::Ident(sym::any, false), sp));
not_tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
any_tokens,
));

let mut all_tokens = TokenStream::new(Vec::with_capacity(4));
if let Some(elem) = yes {
all_tokens.push_stream(elem.clone());
all_tokens.push_tree(TokenTree::token_alone(token::Comma, sp));
}
all_tokens.push_tree(TokenTree::token_alone(token::Ident(sym::not, false), sp));
all_tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
not_tokens,
));

let mut tokens = TokenStream::new(Vec::with_capacity(2));
tokens.push_tree(TokenTree::token_alone(token::Ident(sym::all, false), sp));
tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
all_tokens,
));

mk_attr(
&cx.sess.parse_sess.attr_id_generator,
AttrStyle::Outer,
Path::from_ident(Ident::new(sym::cfg, sp)),
AttrArgs::Delimited(DelimArgs {
dspan: DelimSpan::from_single(sp),
delim: token::Delimiter::Parenthesis,
tokens,
}),
sp,
)
}

fn parse<'session>(
cx: &mut ExtCtxt<'session>,
tts: TokenStream,
) -> PResult<'session, (bool, Vec<P<Item>>, Vec<ItemOffset>)> {
let mut parser = cx.new_parser_from_tts(tts);
if parser.token == token::Eof {
return parser.unexpected();
}
let mut does_not_have_wildcard = true;
let mut items = Vec::with_capacity(4);
let mut items_offsets = Vec::with_capacity(4);
Copy link
Contributor Author

@c410-f3r c410-f3r Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This auxiliary items_offsets vector is used to avoid creating Vec<Vec<P<Item>>>, i.e., all items are stored in the same contiguous items memory.

`items`: [ Items of first arm, Items of second arm, .. ]
          |_____0 to 10______| |_____10 to 25_____|    
                   |      ______________/
                   |     /
`items_offsets`: [ 10, 25, ..]

loop {
match parse_cfg_arm(&mut items, &mut parser)? {
None => break,
Some((has_single_elem, ts)) => {
items_offsets.push((items.len(), has_single_elem, ts));
}
}
}
if parser.token != token::Eof && !items.is_empty() {
let has_single_elem = parse_wildcard_arm(&mut items, &mut parser)?;
does_not_have_wildcard = false;
items_offsets.push((items.len(), has_single_elem, TokenStream::new(vec![])));
}
if parser.token != token::Eof {
return parser.unexpected();
}
Ok((does_not_have_wildcard, items, items_offsets))
}

fn parse_arbitrary_arm_block<'session>(
items: &mut Vec<P<Item>>,
mandatory_comma: bool,
parser: &mut Parser<'session>,
) -> PResult<'session, bool> {
if parser.eat(&token::OpenDelim(token::Delimiter::Brace)) {
loop {
let item = match parser.parse_item(ForceCollect::No) {
Ok(Some(elem)) => elem,
_ => break,
};
items.push(item);
}
parser.expect(&token::CloseDelim(token::Delimiter::Brace))?;
Ok(false)
} else {
let Ok(Some(item)) = parser.parse_item(ForceCollect::No) else {
return parser.unexpected();
};
if !matches!(item.kind, ItemKind::Fn(_)) {
return Err(parser
.sess
.create_err(errors::CfgMatchBadSingleArm { span: parser.token.span }));
}
let has_comma = parser.eat(&token::Comma);
if mandatory_comma && !has_comma {
return Err(parser
.sess
.create_err(errors::CfgMatchMissingComma { span: parser.token.span }));
}
items.push(item);
Ok(true)
}
}

fn parse_cfg_arm<'session>(
items: &mut Vec<P<Item>>,
parser: &mut Parser<'session>,
) -> PResult<'session, Option<(bool, TokenStream)>> {
if !parser.eat_keyword(sym::cfg) {
return Ok(None);
}
let TokenTree::Delimited(_, delim, tokens) = parser.parse_token_tree() else {
return parser.unexpected();
};
if delim != token::Delimiter::Parenthesis || !parser.eat(&token::FatArrow) {
return Err(parser.sess.create_err(errors::CfgMatchBadArm { span: parser.token.span }));
}
let has_single_elem = parse_arbitrary_arm_block(items, true, parser)?;
Ok(Some((has_single_elem, tokens)))
}

fn parse_wildcard_arm<'session>(
items: &mut Vec<P<Item>>,
parser: &mut Parser<'session>,
) -> PResult<'session, bool> {
if !parser.eat_keyword(kw::Underscore) || !parser.eat(&token::FatArrow) {
return Err(parser
.sess
.create_err(errors::CfgMatchBadWildcard { span: parser.token.span }));
}
parse_arbitrary_arm_block(items, false, parser)
}

struct CfgMatchOutput(Vec<P<Item>>);

impl MacResult for CfgMatchOutput {
fn make_impl_items(self: Box<Self>) -> Option<SmallVec<[P<AssocItem>; 1]>> {
let mut rslt = SmallVec::with_capacity(self.0.len());
rslt.extend(self.0.into_iter().filter_map(|item| {
let Item { attrs, id, span, vis, ident, kind, tokens } = item.into_inner();
let ItemKind::Fn(fun) = kind else {
return None;
};
Some(P(Item { attrs, id, ident, kind: AssocItemKind::Fn(fun), span, tokens, vis }))
}));
Some(rslt)
}

fn make_items(self: Box<Self>) -> Option<SmallVec<[P<Item>; 1]>> {
Some(<_>::from(self.0))
}

fn make_stmts(self: Box<Self>) -> Option<SmallVec<[Stmt; 1]>> {
let mut rslt = SmallVec::with_capacity(self.0.len());
rslt.extend(self.0.into_iter().map(|item| {
let id = item.id;
let span = item.span;
Stmt { id, kind: StmtKind::Item(item), span }
}));
Some(rslt)
}
}
Comment on lines +265 to +293
Copy link
Contributor Author

@c410-f3r c410-f3r Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only items are acceptable as input but the output can be three different things at the current time and I don't know if there are more expected variants. This is a best effort.

35 changes: 35 additions & 0 deletions compiler/rustc_builtin_macros/src/errors.rs
Original file line number Diff line number Diff line change
@@ -831,3 +831,38 @@ pub(crate) struct ExpectedRegisterClassOrExplicitRegister {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_match_bad_arm)]
pub(crate) struct CfgMatchBadArm {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_match_bad_single_arm)]
pub(crate) struct CfgMatchBadSingleArm {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_match_bad_wildcard)]
pub(crate) struct CfgMatchBadWildcard {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_match_meaningless_arms)]
pub(crate) struct CfgMatchMeaninglessArms {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_match_missing_comma)]
pub(crate) struct CfgMatchMissingComma {
#[primary_span]
pub(crate) span: Span,
}
18 changes: 11 additions & 7 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ mod assert;
mod cfg;
mod cfg_accessible;
mod cfg_eval;
mod cfg_match;
mod compile_error;
mod concat;
mod concat_bytes;
@@ -71,33 +72,36 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
}

register_bang! {
// tidy-alphabetical-start
asm: asm::expand_asm,
assert: assert::expand_assert,
cfg: cfg::expand_cfg,
cfg_match: cfg_match::expand_cfg_match,
column: source_util::expand_column,
compile_error: compile_error::expand_compile_error,
concat: concat::expand_concat,
concat_bytes: concat_bytes::expand_concat_bytes,
concat_idents: concat_idents::expand_concat_idents,
concat: concat::expand_concat,
const_format_args: format::expand_format_args,
core_panic: edition_panic::expand_panic,
env: env::expand_env,
file: source_util::expand_file,
format_args_nl: format::expand_format_args_nl,
format_args: format::expand_format_args,
const_format_args: format::expand_format_args,
format_args_nl: format::expand_format_args_nl,
global_asm: asm::expand_global_asm,
include: source_util::expand_include,
include_bytes: source_util::expand_include_bytes,
include_str: source_util::expand_include_str,
include: source_util::expand_include,
line: source_util::expand_line,
log_syntax: log_syntax::expand_log_syntax,
module_path: source_util::expand_mod,
option_env: env::expand_option_env,
core_panic: edition_panic::expand_panic,
std_panic: edition_panic::expand_panic,
unreachable: edition_panic::expand_unreachable,
stringify: source_util::expand_stringify,
trace_macros: trace_macros::expand_trace_macros,
type_ascribe: type_ascribe::expand_type_ascribe,
unreachable: edition_panic::expand_unreachable,
// tidy-alphabetical-end
}

register_attr! {
@@ -114,8 +118,8 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {

register_derive! {
Clone: clone::expand_deriving_clone,
Copy: bounds::expand_deriving_copy,
ConstParamTy: bounds::expand_deriving_const_param_ty,
Copy: bounds::expand_deriving_copy,
Debug: debug::expand_deriving_debug,
Default: default::expand_deriving_default,
Eq: eq::expand_deriving_eq,
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/base.rs
Original file line number Diff line number Diff line change
@@ -478,6 +478,7 @@ make_MacEager! {
trait_items: SmallVec<[P<ast::AssocItem>; 1]>,
foreign_items: SmallVec<[P<ast::ForeignItem>; 1]>,
stmts: SmallVec<[ast::Stmt; 1]>,
token_stream: P<TokenStream>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

ty: P<ast::Ty>,
}

1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -472,6 +472,7 @@ symbols! {
cfg_doctest,
cfg_eval,
cfg_hide,
cfg_match,
cfg_overflow_checks,
cfg_panic,
cfg_relocation_model,
35 changes: 35 additions & 0 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
@@ -349,6 +349,7 @@ pub macro debug_assert_matches($($arg:tt)*) {
/// }
/// }
/// ```
#[cfg(bootstrap)]
#[macro_export]
#[unstable(feature = "cfg_match", issue = "115585")]
#[rustc_diagnostic_item = "cfg_match"]
@@ -870,6 +871,40 @@ macro_rules! todo {
/// with exception of expansion functions transforming macro inputs into outputs,
/// those functions are provided by the compiler.
pub(crate) mod builtin {
/// A macro for defining `#[cfg]` match-like statements.
///
/// It is similar to the `if/elif` C preprocessor macro by allowing definition of a cascade of
/// `#[cfg]` cases, emitting the implementation which matches first.
///
/// This allows you to conveniently provide a long list `#[cfg]`'d blocks of code
/// without having to rewrite each clause multiple times.
///
/// Trailing `_` wildcard match arms are **optional** and they indicate a fallback branch when
/// all previous declarations do not evaluate to true.
///
/// # Example
///
/// ```
/// #![feature(cfg_match)]
///
/// cfg_match! {
/// cfg(unix) => {
/// fn foo() { /* unix specific functionality */ }
/// }
/// cfg(target_pointer_width = "32") => {
/// fn foo() { /* non-unix, 32-bit functionality */ }
/// }
/// _ => {
/// fn foo() { /* fallback implementation */ }
/// }
/// }
/// ```
#[cfg(not(bootstrap))]
#[rustc_builtin_macro]
#[unstable(feature = "cfg_match", issue = "115585")]
pub macro cfg_match($($tt:tt)*) {
/* compiler built-in */
}

/// Causes compilation to fail with the given error message when encountered.
///
4 changes: 4 additions & 0 deletions library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
@@ -103,3 +103,7 @@ pub use crate::macros::builtin::cfg_eval;
reason = "placeholder syntax for type ascription"
)]
pub use crate::macros::builtin::type_ascribe;

#[unstable(feature = "cfg_match", issue = "115585")]
#[cfg(not(bootstrap))]
pub use crate::macros::builtin::cfg_match;
154 changes: 0 additions & 154 deletions library/core/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
trait Trait {
fn blah(&self);
}

#[allow(dead_code)]
struct Struct;

impl Trait for Struct {
cfg_match! {
cfg(feature = "blah") => {
fn blah(&self) {
unimplemented!();
}
}
_ => {
fn blah(&self) {
unimplemented!();
}
}
}
}

#[test]
fn assert_eq_trailing_comma() {
assert_eq!(1, 1,);
@@ -40,135 +18,3 @@ fn assert_ne_trailing_comma() {
fn matches_leading_pipe() {
matches!(1, | 1 | 2 | 3);
}

#[test]
fn cfg_match_basic() {
cfg_match! {
cfg(target_pointer_width = "64") => { fn f0_() -> bool { true }}
}

cfg_match! {
cfg(unix) => { fn f1_() -> bool { true }}
cfg(any(target_os = "macos", target_os = "linux")) => { fn f1_() -> bool { false }}
}

cfg_match! {
cfg(target_pointer_width = "32") => { fn f2_() -> bool { false }}
cfg(target_pointer_width = "64") => { fn f2_() -> bool { true }}
}

cfg_match! {
cfg(target_pointer_width = "16") => { fn f3_() -> i32 { 1 }}
_ => { fn f3_() -> i32 { 2 }}
}

#[cfg(target_pointer_width = "64")]
assert!(f0_());

#[cfg(unix)]
assert!(f1_());

#[cfg(target_pointer_width = "32")]
assert!(!f2_());
#[cfg(target_pointer_width = "64")]
assert!(f2_());

#[cfg(not(target_pointer_width = "16"))]
assert_eq!(f3_(), 2);
}

#[test]
fn cfg_match_debug_assertions() {
cfg_match! {
cfg(debug_assertions) => {
assert!(cfg!(debug_assertions));
assert_eq!(4, 2+2);
}
_ => {
assert!(cfg!(not(debug_assertions)));
assert_eq!(10, 5+5);
}
}
}

#[cfg(target_pointer_width = "64")]
#[test]
fn cfg_match_no_duplication_on_64() {
cfg_match! {
cfg(windows) => {
fn foo() {}
}
cfg(unix) => {
fn foo() {}
}
cfg(target_pointer_width = "64") => {
fn foo() {}
}
}
foo();
}

#[test]
fn cfg_match_options() {
cfg_match! {
cfg(test) => {
use core::option::Option as Option2;
fn works1() -> Option2<u32> { Some(1) }
}
_ => { fn works1() -> Option<u32> { None } }
}

cfg_match! {
cfg(feature = "foo") => { fn works2() -> bool { false } }
cfg(test) => { fn works2() -> bool { true } }
_ => { fn works2() -> bool { false } }
}

cfg_match! {
cfg(feature = "foo") => { fn works3() -> bool { false } }
_ => { fn works3() -> bool { true } }
}

cfg_match! {
cfg(test) => {
use core::option::Option as Option3;
fn works4() -> Option3<u32> { Some(1) }
}
}

cfg_match! {
cfg(feature = "foo") => { fn works5() -> bool { false } }
cfg(test) => { fn works5() -> bool { true } }
}

assert!(works1().is_some());
assert!(works2());
assert!(works3());
assert!(works4().is_some());
assert!(works5());
}

#[test]
fn cfg_match_two_functions() {
cfg_match! {
cfg(target_pointer_width = "64") => {
fn foo1() {}
fn bar1() {}
}
_ => {
fn foo2() {}
fn bar2() {}
}
}

#[cfg(target_pointer_width = "64")]
{
foo1();
bar1();
}
#[cfg(not(target_pointer_width = "64"))]
{
foo2();
bar2();
}
}
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
@@ -665,6 +665,7 @@ pub use core::{
)]
pub use core::concat_bytes;

#[cfg(bootstrap)]
#[unstable(feature = "cfg_match", issue = "115585")]
pub use core::cfg_match;

4 changes: 4 additions & 0 deletions library/std/src/prelude/v1.rs
Original file line number Diff line number Diff line change
@@ -91,6 +91,10 @@ pub use core::prelude::v1::cfg_eval;
)]
pub use core::prelude::v1::type_ascribe;

#[unstable(feature = "cfg_match", issue = "115585")]
#[cfg(not(bootstrap))]
pub use core::prelude::v1::cfg_match;

// The file so far is equivalent to core/src/prelude/v1.rs. It is duplicated
// rather than glob imported because we want docs to show these re-exports as
// pointing to within `std`.
2 changes: 1 addition & 1 deletion src/tools/tidy/src/ui_tests.rs
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
const ENTRY_LIMIT: usize = 900;
// FIXME: The following limits should be reduced eventually.
const ISSUES_ENTRY_LIMIT: usize = 1854;
const ROOT_ENTRY_LIMIT: usize = 865;
const ROOT_ENTRY_LIMIT: usize = 866;

const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
"rs", // test source files
29 changes: 29 additions & 0 deletions tests/ui/cfg-match/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// check-pass
// compile-flags: -Z unpretty=expanded

#![feature(cfg_match)]

trait Trait {
fn impl_fn(&self);
}
struct Struct;
impl Trait for Struct {
cfg_match! {
cfg(feature = "blah") => { fn impl_fn(&self) {} }
_ => { fn impl_fn(&self) {} }
}
}

cfg_match! {
cfg(unix) => { fn item() {} }
_ => { fn item() {} }
}

fn statement() {
cfg_match! {
cfg(unix) => { fn statement() {} }
_ => { fn statement() {} }
}
}

pub fn main() {}
29 changes: 29 additions & 0 deletions tests/ui/cfg-match/input.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![feature(prelude_import)]
#![no_std]
// check-pass
// compile-flags: -Z unpretty=expanded

#![feature(cfg_match)]
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;

trait Trait {
fn impl_fn(&self);
}
struct Struct;
impl Trait for Struct {
#[cfg(all(not(any(feature = "blah"))))]
fn impl_fn(&self) {}
}

#[cfg(all(unix, not(any())))]
fn item() {}

fn statement() {
#[cfg(all(unix, not(any())))]
fn statement() {}
}

pub fn main() {}
126 changes: 126 additions & 0 deletions tests/ui/cfg-match/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// run-pass

#![feature(cfg_match)]

fn basic() {
cfg_match! {
cfg(unix) => { fn f1() -> bool { true }}
cfg(any(target_os = "macos", target_os = "linux")) => { fn f1() -> bool { false }}
}

cfg_match! {
cfg(target_pointer_width = "32") => { fn f2() -> bool { false }}
cfg(target_pointer_width = "64") => { fn f2() -> bool { true }}
}

cfg_match! {
cfg(target_pointer_width = "16") => { fn f3() -> i32 { 1 }}
_ => { fn f3() -> i32 { 2 }}
}

cfg_match! {
cfg(test) => {
use core::option::Option as Option2;
fn works1() -> Option2<u32> { Some(1) }
}
_ => { fn works1() -> Option<u32> { None } }
}

cfg_match! {
cfg(feature = "foo") => { fn works2() -> bool { false } }
cfg(test) => { fn works2() -> bool { false } }
_ => { fn works2() -> bool { true } }
}

cfg_match! {
cfg(feature = "foo") => { fn works3() -> bool { false } }
_ => { fn works3() -> bool { true } }
}

#[cfg(unix)]
assert!(f1());

#[cfg(target_pointer_width = "32")]
assert!(!f2());
#[cfg(target_pointer_width = "64")]
assert!(f2());

#[cfg(not(target_pointer_width = "16"))]
assert_eq!(f3(), 2);

assert!(works1().is_none());

assert!(works2());

assert!(works3());
}

fn debug_assertions() {
cfg_match! {
cfg(debug_assertions) => {
assert!(cfg!(debug_assertions));
assert_eq!(4, 2+2);
}
_ => {
assert!(cfg!(not(debug_assertions)));
assert_eq!(10, 5+5);
}
}
}

fn no_bracket() {
cfg_match! {
cfg(unix) => fn f0() -> bool { true },
_ => fn f0() -> bool { true },
}
assert!(f0())
}

fn no_duplication_on_64() {
#[cfg(target_pointer_width = "64")]
cfg_match! {
cfg(windows) => {
fn foo() {}
}
cfg(unix) => {
fn foo() {}
}
cfg(target_pointer_width = "64") => {
fn foo() {}
}
}
#[cfg(target_pointer_width = "64")]
foo();
}

fn two_functions() {
cfg_match! {
cfg(target_pointer_width = "64") => {
fn foo1() {}
fn bar1() {}
}
_ => {
fn foo2() {}
fn bar2() {}
}
}

#[cfg(target_pointer_width = "64")]
{
foo1();
bar1();
}
#[cfg(not(target_pointer_width = "64"))]
{
foo2();
bar2();
}
}

pub fn main() {
basic();
debug_assertions();
no_bracket();
no_duplication_on_64();
two_functions();
}
38 changes: 38 additions & 0 deletions tests/ui/cfg-match/syntax.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![feature(cfg_match)]

cfg_match! {
cfg(unix) => const BAD_SINGLE_ELEMENT: () = ();,
//~^ ERROR arms without brackets are only allowed for function
_ => const BAD_SINGLE_ELEMENT: () = ();,
}

cfg_match! {
cfg(unix) => fn missing_comma() {}
_ => fn missing_comma() {}
//~^ ERROR conditional arms with a single element must end with a com
}

cfg_match! {
cfg(unix) {
//~^ ERROR conditional arm must be declared with a trailing
fn regular_arm() {}
}
_ => { fn regular_arm() {} }
}

cfg_match! {
cfg(unix) => { fn wildcard() {} }
{
//~^ ERROR the last arm is expected to be a wildcard
fn wildcard() {}
}
}

fn meaningless() {
cfg_match! {
//~^ ERROR single arm with a single element has the same effect of a st
cfg(feature = "foo") => fn foo() {},
}
}

pub fn main() {}
35 changes: 35 additions & 0 deletions tests/ui/cfg-match/syntax.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
error: arms without brackets are only allowed for functions at the current time
--> $DIR/syntax.rs:4:52
|
LL | cfg(unix) => const BAD_SINGLE_ELEMENT: () = ();,
| ^

error: conditional arms with a single element must end with a comma
--> $DIR/syntax.rs:11:5
|
LL | _ => fn missing_comma() {}
| ^

error: conditional arm must be declared with a trailing `=>`
--> $DIR/syntax.rs:16:15
|
LL | cfg(unix) {
| ^

error: the last arm is expected to be a wildcard
--> $DIR/syntax.rs:25:5
|
LL | {
| ^

error: single arm with a single element has the same effect of a standalone `cfg`
--> $DIR/syntax.rs:32:5
|
LL | / cfg_match! {
LL | |
LL | | cfg(feature = "foo") => fn foo() {},
LL | | }
| |_____^

error: aborting due to 5 previous errors