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<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);
+    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)
+    }
+}
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<ast::AssocItem>; 1]>,
     foreign_items: SmallVec<[P<ast::ForeignItem>; 1]>,
     stmts: SmallVec<[ast::Stmt; 1]>,
+    token_stream: P<TokenStream>,
     ty: P<ast::Ty>,
 }
 
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<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();
-    }
-}
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<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();
+}
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
+