diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 300b1486f9ba0..04baed15a37ed 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -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. */ + /// `=` 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, } diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 207ae8ad844bb..cae81fe0f8c52 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -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 diff --git a/compiler/rustc_builtin_macros/src/cfg_match.rs b/compiler/rustc_builtin_macros/src/cfg_match.rs new file mode 100644 index 0000000000000..178da4093b7a2 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/cfg_match.rs @@ -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 { + 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], + items_offsets: &[ItemOffset], + sp: Span, + mut cb: impl FnMut(&mut ExtCtxt<'session>, &[ItemOffset], &TokenStream, &mut [P]), + mut cb_last: impl FnMut(&mut ExtCtxt<'session>, &[ItemOffset], &TokenStream, &mut [P]), +) -> 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>, Vec)> { + 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); + 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>, + 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>, + 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>, + 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>); + +impl MacResult for CfgMatchOutput { + fn make_impl_items(self: Box) -> Option; 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) -> Option; 1]>> { + Some(<_>::from(self.0)) + } + + fn make_stmts(self: Box) -> Option> { + 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) + } +} diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 1238773d58b55..30f69382bdb29 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -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, +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index ebf1448f55c99..a586c9e477628 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -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, diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index c4d2a374f0c67..1640266f44963 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -478,6 +478,7 @@ make_MacEager! { trait_items: SmallVec<[P; 1]>, foreign_items: SmallVec<[P; 1]>, stmts: SmallVec<[ast::Stmt; 1]>, + token_stream: P, ty: P, } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e4fafbc12d355..1d72fd3179d26 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -472,6 +472,7 @@ symbols! { cfg_doctest, cfg_eval, cfg_hide, + cfg_match, cfg_overflow_checks, cfg_panic, cfg_relocation_model, diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index c27be8d2ffd31..eb9edbef1ce44 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -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. /// diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index 10525a16f3a66..7842f572107c9 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -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; diff --git a/library/core/tests/macros.rs b/library/core/tests/macros.rs index eb886def164ac..ff3632e3550c2 100644 --- a/library/core/tests/macros.rs +++ b/library/core/tests/macros.rs @@ -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 { Some(1) } - } - _ => { fn works1() -> Option { 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 { 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(); - } -} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 6f78778f01a54..bbc687d6e646c 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -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; diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs index 7a7a773763559..ac52de5698871 100644 --- a/library/std/src/prelude/v1.rs +++ b/library/std/src/prelude/v1.rs @@ -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`. diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 2b828e58d5418..dcf218bc18954 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -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 diff --git a/tests/ui/cfg-match/input.rs b/tests/ui/cfg-match/input.rs new file mode 100644 index 0000000000000..007b689cd1284 --- /dev/null +++ b/tests/ui/cfg-match/input.rs @@ -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() {} diff --git a/tests/ui/cfg-match/input.stdout b/tests/ui/cfg-match/input.stdout new file mode 100644 index 0000000000000..4c2181f07e467 --- /dev/null +++ b/tests/ui/cfg-match/input.stdout @@ -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() {} diff --git a/tests/ui/cfg-match/runtime.rs b/tests/ui/cfg-match/runtime.rs new file mode 100644 index 0000000000000..aa5cb07ef55b2 --- /dev/null +++ b/tests/ui/cfg-match/runtime.rs @@ -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 { Some(1) } + } + _ => { fn works1() -> Option { 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(); +} diff --git a/tests/ui/cfg-match/syntax.rs b/tests/ui/cfg-match/syntax.rs new file mode 100644 index 0000000000000..34cecbdb530d7 --- /dev/null +++ b/tests/ui/cfg-match/syntax.rs @@ -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() {} diff --git a/tests/ui/cfg-match/syntax.stderr b/tests/ui/cfg-match/syntax.stderr new file mode 100644 index 0000000000000..923fb7763e2d3 --- /dev/null +++ b/tests/ui/cfg-match/syntax.stderr @@ -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 +