about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-06-14 16:41:39 +0000
committerbors <bors@rust-lang.org>2024-06-14 16:41:39 +0000
commitf8e566053207b4ecbcbc7a7d6ded82c43061e3da (patch)
treecf1ed832c3ef86f90448770fa7cfdb98c21b4f0c
parent7ac6c2fc685681824fbfc156b38035df743881dd (diff)
parent4b82afb40c91fafdc9e4d84d4782573f848d84d9 (diff)
downloadrust-f8e566053207b4ecbcbc7a7d6ded82c43061e3da.tar.gz
rust-f8e566053207b4ecbcbc7a7d6ded82c43061e3da.zip
Auto merge of #118958 - c410-f3r:concat-again, r=petrochenkov
Add a new concat metavar expr

Revival of #111930

Giving it another try now that #117050 was merged.

With the new rules, meta-variable expressions must be referenced with a dollar sign (`$`) and this can cause misunderstands with `$concat`.

```rust
macro_rules! foo {
    ( $bar:ident ) => {
        const ${concat(VAR, bar)}: i32 = 1;
    };
}

// Will produce `VARbar` instead of `VAR_123`
foo!(_123);
```

In other words, forgetting the dollar symbol can produce undesired outputs.

cc #29599
cc https://github.com/rust-lang/rust/issues/124225
-rw-r--r--compiler/rustc_expand/src/mbe/metavar_expr.rs93
-rw-r--r--compiler/rustc_expand/src/mbe/quoted.rs25
-rw-r--r--compiler/rustc_expand/src/mbe/transcribe.rs54
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.rs9
-rw-r--r--tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.stderr13
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs58
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/hygiene.rs13
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/hygiene.stderr14
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs81
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr95
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs50
-rw-r--r--tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr68
-rw-r--r--tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr4
15 files changed, 551 insertions, 29 deletions
diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs
index 128e9f48ff5..3295a91029e 100644
--- a/compiler/rustc_expand/src/mbe/metavar_expr.rs
+++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs
@@ -8,9 +8,14 @@ use rustc_session::parse::ParseSess;
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
 
+pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
+
 /// A meta-variable expression, for expansions based on properties of meta-variables.
-#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
+#[derive(Debug, PartialEq, Encodable, Decodable)]
 pub(crate) enum MetaVarExpr {
+    /// Unification of two or more identifiers.
+    Concat(Box<[MetaVarExprConcatElem]>),
+
     /// The number of repetitions of an identifier.
     Count(Ident, usize),
 
@@ -42,6 +47,31 @@ impl MetaVarExpr {
         check_trailing_token(&mut tts, psess)?;
         let mut iter = args.trees();
         let rslt = match ident.as_str() {
+            "concat" => {
+                let mut result = Vec::new();
+                loop {
+                    let is_var = try_eat_dollar(&mut iter);
+                    let element_ident = parse_ident(&mut iter, psess, outer_span)?;
+                    let element = if is_var {
+                        MetaVarExprConcatElem::Var(element_ident)
+                    } else {
+                        MetaVarExprConcatElem::Ident(element_ident)
+                    };
+                    result.push(element);
+                    if iter.look_ahead(0).is_none() {
+                        break;
+                    }
+                    if !try_eat_comma(&mut iter) {
+                        return Err(psess.dcx.struct_span_err(outer_span, "expected comma"));
+                    }
+                }
+                if result.len() < 2 {
+                    return Err(psess
+                        .dcx
+                        .struct_span_err(ident.span, "`concat` must have at least two elements"));
+                }
+                MetaVarExpr::Concat(result.into())
+            }
             "count" => parse_count(&mut iter, psess, ident.span)?,
             "ignore" => {
                 eat_dollar(&mut iter, psess, ident.span)?;
@@ -68,11 +98,21 @@ impl MetaVarExpr {
     pub(crate) fn ident(&self) -> Option<Ident> {
         match *self {
             MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
-            MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None,
+            MetaVarExpr::Concat { .. } | MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None,
         }
     }
 }
 
+#[derive(Debug, Decodable, Encodable, PartialEq)]
+pub(crate) enum MetaVarExprConcatElem {
+    /// There is NO preceding dollar sign, which means that this identifier should be interpreted
+    /// as a literal.
+    Ident(Ident),
+    /// There is a preceding dollar sign, which means that this identifier should be expanded
+    /// and interpreted as a variable.
+    Var(Ident),
+}
+
 // Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
 fn check_trailing_token<'psess>(
     iter: &mut RefTokenTreeCursor<'_>,
@@ -138,26 +178,30 @@ fn parse_depth<'psess>(
 fn parse_ident<'psess>(
     iter: &mut RefTokenTreeCursor<'_>,
     psess: &'psess ParseSess,
-    span: Span,
+    fallback_span: Span,
 ) -> PResult<'psess, Ident> {
-    if let Some(tt) = iter.next()
-        && let TokenTree::Token(token, _) = tt
-    {
-        if let Some((elem, IdentIsRaw::No)) = token.ident() {
-            return Ok(elem);
+    let Some(tt) = iter.next() else {
+        return Err(psess.dcx.struct_span_err(fallback_span, "expected identifier"));
+    };
+    let TokenTree::Token(token, _) = tt else {
+        return Err(psess.dcx.struct_span_err(tt.span(), "expected identifier"));
+    };
+    if let Some((elem, is_raw)) = token.ident() {
+        if let IdentIsRaw::Yes = is_raw {
+            return Err(psess.dcx.struct_span_err(elem.span, RAW_IDENT_ERR));
         }
-        let token_str = pprust::token_to_string(token);
-        let mut err =
-            psess.dcx.struct_span_err(span, format!("expected identifier, found `{}`", &token_str));
-        err.span_suggestion(
-            token.span,
-            format!("try removing `{}`", &token_str),
-            "",
-            Applicability::MaybeIncorrect,
-        );
-        return Err(err);
+        return Ok(elem);
     }
-    Err(psess.dcx.struct_span_err(span, "expected identifier"))
+    let token_str = pprust::token_to_string(token);
+    let mut err =
+        psess.dcx.struct_span_err(token.span, format!("expected identifier, found `{token_str}`"));
+    err.span_suggestion(
+        token.span,
+        format!("try removing `{token_str}`"),
+        "",
+        Applicability::MaybeIncorrect,
+    );
+    Err(err)
 }
 
 /// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
@@ -170,6 +214,17 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool {
     false
 }
 
+/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
+/// iterator is not modified and the result is `false`.
+fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool {
+    if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0)
+    {
+        let _ = iter.next();
+        return true;
+    }
+    false
+}
+
 /// Expects that the next item is a dollar sign.
 fn eat_dollar<'psess>(
     iter: &mut RefTokenTreeCursor<'_>,
diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs
index 8ad7cb15c92..74f78c0ef78 100644
--- a/compiler/rustc_expand/src/mbe/quoted.rs
+++ b/compiler/rustc_expand/src/mbe/quoted.rs
@@ -155,6 +155,13 @@ fn maybe_emit_macro_metavar_expr_feature(features: &Features, sess: &Session, sp
     }
 }
 
+fn maybe_emit_macro_metavar_expr_concat_feature(features: &Features, sess: &Session, span: Span) {
+    if !features.macro_metavar_expr_concat {
+        let msg = "the `concat` meta-variable expression is unstable";
+        feature_err(sess, sym::macro_metavar_expr_concat, span, msg).emit();
+    }
+}
+
 /// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a
 /// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree`
 /// for use in parsing a macro.
@@ -217,11 +224,19 @@ fn parse_tree<'a>(
                                         return TokenTree::token(token::Dollar, dollar_span);
                                     }
                                     Ok(elem) => {
-                                        maybe_emit_macro_metavar_expr_feature(
-                                            features,
-                                            sess,
-                                            delim_span.entire(),
-                                        );
+                                        if let MetaVarExpr::Concat(_) = elem {
+                                            maybe_emit_macro_metavar_expr_concat_feature(
+                                                features,
+                                                sess,
+                                                delim_span.entire(),
+                                            );
+                                        } else {
+                                            maybe_emit_macro_metavar_expr_feature(
+                                                features,
+                                                sess,
+                                                delim_span.entire(),
+                                            );
+                                        }
                                         return TokenTree::MetaVarExpr(delim_span, elem);
                                     }
                                 }
diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs
index 25e961d6009..445255e933d 100644
--- a/compiler/rustc_expand/src/mbe/transcribe.rs
+++ b/compiler/rustc_expand/src/mbe/transcribe.rs
@@ -3,18 +3,19 @@ use crate::errors::{
     NoSyntaxVarsExprRepeat, VarStillRepeating,
 };
 use crate::mbe::macro_parser::{NamedMatch, NamedMatch::*};
+use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR};
 use crate::mbe::{self, KleeneOp, MetaVarExpr};
 use rustc_ast::mut_visit::{self, MutVisitor};
+use rustc_ast::token::IdentIsRaw;
 use rustc_ast::token::{self, Delimiter, Token, TokenKind};
 use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{pluralize, Diag, DiagCtxt, PResult};
 use rustc_parse::parser::ParseNtResult;
+use rustc_session::parse::ParseSess;
 use rustc_span::hygiene::{LocalExpnId, Transparency};
 use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
-use rustc_span::{with_metavar_spans, Span, SyntaxContext};
-
-use rustc_session::parse::ParseSess;
+use rustc_span::{with_metavar_spans, Span, Symbol, SyntaxContext};
 use smallvec::{smallvec, SmallVec};
 use std::mem;
 
@@ -675,6 +676,23 @@ fn transcribe_metavar_expr<'a>(
         span
     };
     match *expr {
+        MetaVarExpr::Concat(ref elements) => {
+            let mut concatenated = String::new();
+            for element in elements.into_iter() {
+                let string = match element {
+                    MetaVarExprConcatElem::Ident(ident) => ident.to_string(),
+                    MetaVarExprConcatElem::Var(ident) => extract_ident(dcx, *ident, interp)?,
+                };
+                concatenated.push_str(&string);
+            }
+            // The current implementation marks the span as coming from the macro regardless of
+            // contexts of the concatenated identifiers but this behavior may change in the
+            // future.
+            result.push(TokenTree::Token(
+                Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())),
+                Spacing::Alone,
+            ));
+        }
         MetaVarExpr::Count(original_ident, depth) => {
             let matched = matched_from_ident(dcx, original_ident, interp)?;
             let count = count_repetitions(dcx, depth, matched, repeats, sp)?;
@@ -709,3 +727,33 @@ fn transcribe_metavar_expr<'a>(
     }
     Ok(())
 }
+
+/// Extracts an identifier that can be originated from a `$var:ident` variable or from a token tree.
+fn extract_ident<'a>(
+    dcx: &'a DiagCtxt,
+    ident: Ident,
+    interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
+) -> PResult<'a, String> {
+    if let NamedMatch::MatchedSingle(pnr) = matched_from_ident(dcx, ident, interp)? {
+        if let ParseNtResult::Ident(nt_ident, is_raw) = pnr {
+            if let IdentIsRaw::Yes = is_raw {
+                return Err(dcx.struct_span_err(ident.span, RAW_IDENT_ERR));
+            }
+            return Ok(nt_ident.to_string());
+        }
+        if let ParseNtResult::Tt(TokenTree::Token(
+            Token { kind: TokenKind::Ident(token_ident, is_raw), .. },
+            _,
+        )) = pnr
+        {
+            if let IdentIsRaw::Yes = is_raw {
+                return Err(dcx.struct_span_err(ident.span, RAW_IDENT_ERR));
+            }
+            return Ok(token_ident.to_string());
+        }
+    }
+    Err(dcx.struct_span_err(
+        ident.span,
+        "`${concat(..)}` currently only accepts identifiers or meta-variables as parameters",
+    ))
+}
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 2d13f430cfc..58832cb1087 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -516,6 +516,8 @@ declare_features! (
     (unstable, lint_reasons, "1.31.0", Some(54503)),
     /// Give access to additional metadata about declarative macro meta-variables.
     (unstable, macro_metavar_expr, "1.61.0", Some(83527)),
+    /// Provides a way to concatenate identifiers using metavariable expressions.
+    (unstable, macro_metavar_expr_concat, "CURRENT_RUSTC_VERSION", Some(124225)),
     /// Allows `#[marker]` on certain traits allowing overlapping implementations.
     (unstable, marker_trait_attr, "1.30.0", Some(29864)),
     /// Allows exhaustive pattern matching on types that contain uninhabited types in cases that are
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index c89323b7f16..f44fa1bcb4f 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1118,6 +1118,7 @@ symbols! {
         macro_lifetime_matcher,
         macro_literal_matcher,
         macro_metavar_expr,
+        macro_metavar_expr_concat,
         macro_reexport,
         macro_use,
         macro_vis_matcher,
diff --git a/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.rs b/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.rs
new file mode 100644
index 00000000000..e700999ae4b
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.rs
@@ -0,0 +1,9 @@
+macro_rules! join {
+    ($lhs:ident, $rhs:ident) => {
+        let ${concat($lhs, $rhs)}: () = ();
+        //~^ ERROR the `concat` meta-variable expression is unstable
+    };
+}
+
+fn main() {
+}
diff --git a/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.stderr b/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.stderr
new file mode 100644
index 00000000000..5b2589d8c89
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-macro-metavar-expr-concat.stderr
@@ -0,0 +1,13 @@
+error[E0658]: the `concat` meta-variable expression is unstable
+  --> $DIR/feature-gate-macro-metavar-expr-concat.rs:3:14
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |              ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #124225 <https://github.com/rust-lang/rust/issues/124225> for more information
+   = help: add `#![feature(macro_metavar_expr_concat)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs b/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs
new file mode 100644
index 00000000000..e44eeffb01b
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs
@@ -0,0 +1,58 @@
+//@ run-pass
+
+#![allow(dead_code, non_camel_case_types, non_upper_case_globals)]
+#![feature(macro_metavar_expr_concat)]
+
+macro_rules! create_things {
+    ($lhs:ident) => {
+        struct ${concat($lhs, _separated_idents_in_a_struct)} {
+            foo: i32,
+            ${concat($lhs, _separated_idents_in_a_field)}: i32,
+        }
+
+        mod ${concat($lhs, _separated_idents_in_a_module)} {
+            pub const FOO: () = ();
+        }
+
+        fn ${concat($lhs, _separated_idents_in_a_fn)}() {}
+    };
+}
+
+macro_rules! many_idents {
+    ($a:ident, $c:ident) => {
+        const ${concat($a, B, $c, D)}: i32 = 1;
+    };
+}
+
+macro_rules! valid_tts {
+    ($_0:tt, $_1:tt) => {
+        const ${concat($_0, $_1)}: i32 = 1;
+    }
+}
+
+macro_rules! without_dollar_sign_is_an_ident {
+    ($ident:ident) => {
+        const ${concat(VAR, ident)}: i32 = 1;
+        const ${concat(VAR, $ident)}: i32 = 2;
+    };
+}
+
+fn main() {
+    create_things!(behold);
+    behold_separated_idents_in_a_fn();
+    let _ = behold_separated_idents_in_a_module::FOO;
+    let _ = behold_separated_idents_in_a_struct {
+        foo: 1,
+        behold_separated_idents_in_a_field: 2,
+    };
+
+    many_idents!(A, C);
+    assert_eq!(ABCD, 1);
+
+    valid_tts!(X, YZ);
+    assert_eq!(XYZ, 1);
+
+    without_dollar_sign_is_an_ident!(_123);
+    assert_eq!(VARident, 1);
+    assert_eq!(VAR_123, 2);
+}
diff --git a/tests/ui/macros/macro-metavar-expr-concat/hygiene.rs b/tests/ui/macros/macro-metavar-expr-concat/hygiene.rs
new file mode 100644
index 00000000000..24b0e36498a
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/hygiene.rs
@@ -0,0 +1,13 @@
+#![feature(macro_metavar_expr_concat)]
+
+macro_rules! join {
+    ($lhs:ident, $rhs:ident) => {
+        ${concat($lhs, $rhs)}
+        //~^ ERROR cannot find value `abcdef` in this scope
+    };
+}
+
+fn main() {
+    let abcdef = 1;
+    let _another = join!(abc, def);
+}
diff --git a/tests/ui/macros/macro-metavar-expr-concat/hygiene.stderr b/tests/ui/macros/macro-metavar-expr-concat/hygiene.stderr
new file mode 100644
index 00000000000..ef2326dce85
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/hygiene.stderr
@@ -0,0 +1,14 @@
+error[E0425]: cannot find value `abcdef` in this scope
+  --> $DIR/hygiene.rs:5:10
+   |
+LL |         ${concat($lhs, $rhs)}
+   |          ^^^^^^^^^^^^^^^^^^^^ not found in this scope
+...
+LL |     let _another = join!(abc, def);
+   |                    --------------- in this macro invocation
+   |
+   = note: this error originates in the macro `join` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0425`.
diff --git a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs
new file mode 100644
index 00000000000..f72b9baca89
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs
@@ -0,0 +1,81 @@
+#![feature(macro_metavar_expr_concat)]
+
+macro_rules! idents_01 {
+    ($rhs:ident) => {
+        let ${concat(abc, $rhs)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! idents_10 {
+    ($lhs:ident) => {
+        let ${concat($lhs, abc)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! idents_11 {
+    ($lhs:ident, $rhs:ident) => {
+        let ${concat($lhs, $rhs)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+        //~| ERROR `${concat(..)}` currently does not support raw identifiers
+        //~| ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! no_params {
+    () => {
+        let ${concat(r#abc, abc)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+        //~| ERROR expected pattern, found `$`
+
+        let ${concat(abc, r#abc)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+
+        let ${concat(r#abc, r#abc)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! tts_01 {
+    ($rhs:tt) => {
+        let ${concat(abc, $rhs)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! tts_10 {
+    ($lhs:tt) => {
+        let ${concat($lhs, abc)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+macro_rules! tts_11 {
+    ($lhs:tt, $rhs:tt) => {
+        let ${concat($lhs, $rhs)}: () = ();
+        //~^ ERROR `${concat(..)}` currently does not support raw identifiers
+        //~| ERROR `${concat(..)}` currently does not support raw identifiers
+        //~| ERROR `${concat(..)}` currently does not support raw identifiers
+    };
+}
+
+fn main() {
+    idents_01!(r#_c);
+
+    idents_10!(r#_c);
+
+    idents_11!(r#_c, d);
+    idents_11!(_e, r#f);
+    idents_11!(r#_g, r#h);
+
+    tts_01!(r#_c);
+
+    tts_10!(r#_c);
+
+    tts_11!(r#_c, d);
+    tts_11!(_e, r#f);
+    tts_11!(r#_g, r#h);
+
+    no_params!();
+}
diff --git a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr
new file mode 100644
index 00000000000..dd525cf0801
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr
@@ -0,0 +1,95 @@
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:28:22
+   |
+LL |         let ${concat(r#abc, abc)}: () = ();
+   |                      ^^^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:32:27
+   |
+LL |         let ${concat(abc, r#abc)}: () = ();
+   |                           ^^^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:35:22
+   |
+LL |         let ${concat(r#abc, r#abc)}: () = ();
+   |                      ^^^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:5:28
+   |
+LL |         let ${concat(abc, $rhs)}: () = ();
+   |                            ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:12:23
+   |
+LL |         let ${concat($lhs, abc)}: () = ();
+   |                       ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:19:23
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                       ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:19:29
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                             ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:19:23
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                       ^^^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:42:28
+   |
+LL |         let ${concat(abc, $rhs)}: () = ();
+   |                            ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:49:23
+   |
+LL |         let ${concat($lhs, abc)}: () = ();
+   |                       ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:56:23
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                       ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:56:29
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                             ^^^
+
+error: `${concat(..)}` currently does not support raw identifiers
+  --> $DIR/raw-identifiers.rs:56:23
+   |
+LL |         let ${concat($lhs, $rhs)}: () = ();
+   |                       ^^^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: expected pattern, found `$`
+  --> $DIR/raw-identifiers.rs:28:13
+   |
+LL |         let ${concat(r#abc, abc)}: () = ();
+   |             ^ expected pattern
+...
+LL |     no_params!();
+   |     ------------ in this macro invocation
+   |
+   = note: this error originates in the macro `no_params` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 14 previous errors
+
diff --git a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs
new file mode 100644
index 00000000000..bf47442ea76
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs
@@ -0,0 +1,50 @@
+#![feature(macro_metavar_expr_concat)]
+
+macro_rules! wrong_concat_declarations {
+    ($ex:expr) => {
+        ${concat()}
+        //~^ ERROR expected identifier
+
+        ${concat(aaaa)}
+        //~^ ERROR `concat` must have at least two elements
+
+        ${concat(aaaa,)}
+        //~^ ERROR expected identifier
+
+        ${concat(aaaa, 1)}
+        //~^ ERROR expected identifier
+
+        ${concat(_, aaaa)}
+
+        ${concat(aaaa aaaa)}
+        //~^ ERROR expected comma
+
+        ${concat($ex)}
+        //~^ ERROR `concat` must have at least two elements
+
+        ${concat($ex, aaaa)}
+        //~^ ERROR `${concat(..)}` currently only accepts identifiers
+
+        ${concat($ex, aaaa 123)}
+        //~^ ERROR expected comma
+
+        ${concat($ex, aaaa,)}
+        //~^ ERROR expected identifier
+
+        ${concat($ex, aaaa, 123)}
+        //~^ ERROR expected identifier
+    };
+}
+
+macro_rules! dollar_sign_without_referenced_ident {
+    ($ident:ident) => {
+        const ${concat(FOO, $foo)}: i32 = 2;
+        //~^ ERROR variable `foo` is not recognized in meta-variable expression
+    };
+}
+
+fn main() {
+    wrong_concat_declarations!(1);
+
+    dollar_sign_without_referenced_ident!(VAR);
+}
diff --git a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr
new file mode 100644
index 00000000000..b216a86d59a
--- /dev/null
+++ b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr
@@ -0,0 +1,68 @@
+error: expected identifier
+  --> $DIR/syntax-errors.rs:5:10
+   |
+LL |         ${concat()}
+   |          ^^^^^^^^^^
+
+error: `concat` must have at least two elements
+  --> $DIR/syntax-errors.rs:8:11
+   |
+LL |         ${concat(aaaa)}
+   |           ^^^^^^
+
+error: expected identifier
+  --> $DIR/syntax-errors.rs:11:10
+   |
+LL |         ${concat(aaaa,)}
+   |          ^^^^^^^^^^^^^^^
+
+error: expected identifier, found `1`
+  --> $DIR/syntax-errors.rs:14:24
+   |
+LL |         ${concat(aaaa, 1)}
+   |                        ^ help: try removing `1`
+
+error: expected comma
+  --> $DIR/syntax-errors.rs:19:10
+   |
+LL |         ${concat(aaaa aaaa)}
+   |          ^^^^^^^^^^^^^^^^^^^
+
+error: `concat` must have at least two elements
+  --> $DIR/syntax-errors.rs:22:11
+   |
+LL |         ${concat($ex)}
+   |           ^^^^^^
+
+error: expected comma
+  --> $DIR/syntax-errors.rs:28:10
+   |
+LL |         ${concat($ex, aaaa 123)}
+   |          ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: expected identifier
+  --> $DIR/syntax-errors.rs:31:10
+   |
+LL |         ${concat($ex, aaaa,)}
+   |          ^^^^^^^^^^^^^^^^^^^^
+
+error: expected identifier, found `123`
+  --> $DIR/syntax-errors.rs:34:29
+   |
+LL |         ${concat($ex, aaaa, 123)}
+   |                             ^^^ help: try removing `123`
+
+error: `${concat(..)}` currently only accepts identifiers or meta-variables as parameters
+  --> $DIR/syntax-errors.rs:25:19
+   |
+LL |         ${concat($ex, aaaa)}
+   |                   ^^
+
+error: variable `foo` is not recognized in meta-variable expression
+  --> $DIR/syntax-errors.rs:41:30
+   |
+LL |         const ${concat(FOO, $foo)}: i32 = 2;
+   |                              ^^^
+
+error: aborting due to 11 previous errors
+
diff --git a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
index 3fa3839bae2..8e4ba192d79 100644
--- a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
+++ b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr
@@ -191,10 +191,10 @@ LL |     ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } };
    |                                 ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and len
 
 error: expected identifier
-  --> $DIR/syntax-errors.rs:118:31
+  --> $DIR/syntax-errors.rs:118:33
    |
 LL |     ( $( $i:ident ),* ) => { ${ {} } };
-   |                               ^^^^^^
+   |                                 ^^
 
 error: `count` can not be placed inside the inner-most repetition
   --> $DIR/syntax-errors.rs:12:24