about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSmitty <me@smitop.com>2021-07-29 13:00:55 -0400
committerMark Rousskov <mark.simulacrum@gmail.com>2021-12-06 21:05:13 -0500
commiteb56693a370df5bb60f58f73586cded6a641b0cc (patch)
treefd8c3686dba481883f95e9f3807f11478b0171a5
parent0fb1c371d4a14f9ce7a721d8aea683a6e6774f6c (diff)
downloadrust-eb56693a370df5bb60f58f73586cded6a641b0cc.tar.gz
rust-eb56693a370df5bb60f58f73586cded6a641b0cc.zip
Implement concat_bytes!
The tracking issue for this is #87555.
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs167
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--library/core/src/macros/mod.rs28
-rw-r--r--library/core/src/prelude/v1.rs9
-rw-r--r--library/std/src/lib.rs9
-rw-r--r--library/std/src/prelude/v1.rs9
-rw-r--r--src/test/ui/feature-gates/feature-gate-concat_bytes.rs4
-rw-r--r--src/test/ui/feature-gates/feature-gate-concat_bytes.stderr12
-rw-r--r--src/test/ui/macros/concat-bytes-error.rs42
-rw-r--r--src/test/ui/macros/concat-bytes-error.stderr131
-rw-r--r--src/test/ui/macros/concat-bytes.rs7
12 files changed, 421 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
new file mode 100644
index 00000000000..a107f5993b5
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -0,0 +1,167 @@
+use rustc_ast as ast;
+use rustc_ast::{ptr::P, tokenstream::TokenStream};
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::Applicability;
+use rustc_expand::base::{self, DummyResult};
+
+/// Emits errors for literal expressions that are invalid inside and outside of an array.
+fn invalid_type_err(cx: &mut base::ExtCtxt<'_>, expr: &P<rustc_ast::Expr>, is_nested: bool) {
+    let lit = if let ast::ExprKind::Lit(lit) = &expr.kind {
+        lit
+    } else {
+        unreachable!();
+    };
+    match lit.kind {
+        ast::LitKind::Char(_) => {
+            let mut err = cx.struct_span_err(expr.span, "cannot concatenate character literals");
+            if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
+                err.span_suggestion(
+                    expr.span,
+                    "try using a byte character",
+                    format!("b{}", snippet),
+                    Applicability::MachineApplicable,
+                )
+                .emit();
+            }
+        }
+        ast::LitKind::Str(_, _) => {
+            let mut err = cx.struct_span_err(expr.span, "cannot concatenate string literals");
+            // suggestion would be invalid if we are nested
+            if !is_nested {
+                if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
+                    err.span_suggestion(
+                        expr.span,
+                        "try using a byte string",
+                        format!("b{}", snippet),
+                        Applicability::MachineApplicable,
+                    );
+                }
+            }
+            err.emit();
+        }
+        ast::LitKind::Float(_, _) => {
+            cx.span_err(expr.span, "cannot concatenate float literals");
+        }
+        ast::LitKind::Bool(_) => {
+            cx.span_err(expr.span, "cannot concatenate boolean literals");
+        }
+        ast::LitKind::Err(_) => {}
+        ast::LitKind::Int(_, _) if !is_nested => {
+            let mut err = cx.struct_span_err(expr.span, "cannot concatenate numeric literals");
+            if let Ok(snippet) = cx.sess.source_map().span_to_snippet(expr.span) {
+                err.span_suggestion(
+                    expr.span,
+                    "try wrapping the number in an array",
+                    format!("[{}]", snippet),
+                    Applicability::MachineApplicable,
+                );
+            }
+            err.emit();
+        }
+        ast::LitKind::Int(
+            val,
+            ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
+        ) => {
+            assert!(val > u8::MAX.into()); // must be an error
+            cx.span_err(expr.span, "numeric literal is out of bounds");
+        }
+        ast::LitKind::Int(_, _) => {
+            cx.span_err(expr.span, "numeric literal is not a `u8`");
+        }
+        _ => unreachable!(),
+    }
+}
+
+pub fn expand_concat_bytes(
+    cx: &mut base::ExtCtxt<'_>,
+    sp: rustc_span::Span,
+    tts: TokenStream,
+) -> Box<dyn base::MacResult + 'static> {
+    let es = match base::get_exprs_from_tts(cx, sp, tts) {
+        Some(e) => e,
+        None => return DummyResult::any(sp),
+    };
+    let mut accumulator = Vec::new();
+    let mut missing_literals = vec![];
+    let mut has_errors = false;
+    for e in es {
+        match e.kind {
+            ast::ExprKind::Array(ref exprs) => {
+                for expr in exprs {
+                    match expr.kind {
+                        ast::ExprKind::Array(_) => {
+                            if !has_errors {
+                                cx.span_err(expr.span, "cannot concatenate doubly nested array");
+                            }
+                            has_errors = true;
+                        }
+                        ast::ExprKind::Lit(ref lit) => match lit.kind {
+                            ast::LitKind::Int(
+                                val,
+                                ast::LitIntType::Unsuffixed
+                                | ast::LitIntType::Unsigned(ast::UintTy::U8),
+                            ) if val <= u8::MAX.into() => {
+                                accumulator.push(val as u8);
+                            }
+
+                            ast::LitKind::Byte(val) => {
+                                accumulator.push(val);
+                            }
+                            ast::LitKind::ByteStr(_) => {
+                                if !has_errors {
+                                    cx.struct_span_err(
+                                        expr.span,
+                                        "cannot concatenate doubly nested array",
+                                    )
+                                    .note("byte strings are treated as arrays of bytes")
+                                    .help("try flattening the array")
+                                    .emit();
+                                }
+                                has_errors = true;
+                            }
+                            _ => {
+                                if !has_errors {
+                                    invalid_type_err(cx, expr, true);
+                                }
+                                has_errors = true;
+                            }
+                        },
+                        _ => {
+                            missing_literals.push(expr.span);
+                        }
+                    }
+                }
+            }
+            ast::ExprKind::Lit(ref lit) => match lit.kind {
+                ast::LitKind::Byte(val) => {
+                    accumulator.push(val);
+                }
+                ast::LitKind::ByteStr(ref bytes) => {
+                    accumulator.extend_from_slice(&bytes);
+                }
+                _ => {
+                    if !has_errors {
+                        invalid_type_err(cx, &e, false);
+                    }
+                    has_errors = true;
+                }
+            },
+            ast::ExprKind::Err => {
+                has_errors = true;
+            }
+            _ => {
+                missing_literals.push(e.span);
+            }
+        }
+    }
+    if !missing_literals.is_empty() {
+        let mut err = cx.struct_span_err(missing_literals.clone(), "expected a byte literal");
+        err.note("only byte literals (like `b\"foo\"`, `b's'`, and `[3, 4, 5]`) can be passed to `concat_bytes!()`");
+        err.emit();
+        return base::MacEager::expr(DummyResult::raw_expr(sp, true));
+    } else if has_errors {
+        return base::MacEager::expr(DummyResult::raw_expr(sp, true));
+    }
+    let sp = cx.with_def_site_ctxt(sp);
+    base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(Lrc::from(accumulator))))
+}
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index d1d276930b9..f5acf9db085 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -27,6 +27,7 @@ mod cfg_accessible;
 mod cfg_eval;
 mod compile_error;
 mod concat;
+mod concat_bytes;
 mod concat_idents;
 mod derive;
 mod deriving;
@@ -65,6 +66,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
         cfg: cfg::expand_cfg,
         column: source_util::expand_column,
         compile_error: compile_error::expand_compile_error,
+        concat_bytes: concat_bytes::expand_concat_bytes,
         concat_idents: concat_idents::expand_concat_idents,
         concat: concat::expand_concat,
         env: env::expand_env,
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 309c305293f..6d300bac410 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -439,6 +439,7 @@ symbols! {
         compiler_builtins,
         compiler_fence,
         concat,
+        concat_bytes,
         concat_idents,
         conservative_impl_trait,
         console,
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index 993ae723229..b18508186a6 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -967,6 +967,34 @@ pub(crate) mod builtin {
         ($($e:ident),+ $(,)?) => {{ /* compiler built-in */ }};
     }
 
+    /// Concatenates literals into a byte slice.
+    ///
+    /// This macro takes any number of comma-separated literals, and concatenates them all into
+    /// one, yielding an expression of type `&[u8, _]`, which represents all of the literals
+    /// concatenated left-to-right. The literals passed can be any combination of:
+    ///
+    /// - byte literals (`b'r'`)
+    /// - byte strings (`b"Rust"`)
+    /// - arrays of bytes/numbers (`[b'A', 66, b'C']`)
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(concat_bytes)]
+    ///
+    /// # fn main() {
+    /// let s: &[u8; 6] = concat_bytes!(b'A', b"BC", [68, b'E', 70]);
+    /// assert_eq!(s, b"ABCDEF");
+    /// # }
+    /// ```
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "concat_bytes", issue = "87555")]
+    #[rustc_builtin_macro]
+    #[macro_export]
+    macro_rules! concat_bytes {
+        ($($e:literal),+ $(,)?) => {{ /* compiler built-in */ }};
+    }
+
     /// Concatenates literals into a static string slice.
     ///
     /// This macro takes any number of comma-separated literals, yielding an
diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs
index 6b51ef5b012..8705eb39468 100644
--- a/library/core/src/prelude/v1.rs
+++ b/library/core/src/prelude/v1.rs
@@ -61,6 +61,15 @@ pub use crate::{
 };
 
 #[unstable(
+    feature = "concat_bytes",
+    issue = "87555",
+    reason = "`concat_bytes` is not stable enough for use and is subject to change"
+)]
+#[cfg(not(bootstrap))]
+#[doc(no_inline)]
+pub use crate::concat_bytes;
+
+#[unstable(
     feature = "asm",
     issue = "72016",
     reason = "inline assembly is not stable enough for use and is subject to change"
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 67846e78835..367f072ffc7 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -250,6 +250,7 @@
 #![feature(cfg_target_thread_local)]
 #![feature(char_error_internals)]
 #![feature(char_internals)]
+#![cfg_attr(not(bootstrap), feature(concat_bytes))]
 #![feature(concat_idents)]
 #![feature(const_cstr_unchecked)]
 #![feature(const_fn_floating_point_arithmetic)]
@@ -576,6 +577,14 @@ pub use core::{
     log_syntax, module_path, option_env, stringify, trace_macros,
 };
 
+#[unstable(
+    feature = "concat_bytes",
+    issue = "87555",
+    reason = "`concat_bytes` is not stable enough for use and is subject to change"
+)]
+#[cfg(not(bootstrap))]
+pub use core::concat_bytes;
+
 #[stable(feature = "core_primitive", since = "1.43.0")]
 pub use core::primitive;
 
diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs
index 772044f0149..9b23aa37e31 100644
--- a/library/std/src/prelude/v1.rs
+++ b/library/std/src/prelude/v1.rs
@@ -46,6 +46,15 @@ pub use core::prelude::v1::{
 };
 
 #[unstable(
+    feature = "concat_bytes",
+    issue = "87555",
+    reason = "`concat_bytes` is not stable enough for use and is subject to change"
+)]
+#[cfg(not(bootstrap))]
+#[doc(no_inline)]
+pub use core::prelude::v1::concat_bytes;
+
+#[unstable(
     feature = "asm",
     issue = "72016",
     reason = "inline assembly is not stable enough for use and is subject to change"
diff --git a/src/test/ui/feature-gates/feature-gate-concat_bytes.rs b/src/test/ui/feature-gates/feature-gate-concat_bytes.rs
new file mode 100644
index 00000000000..07d63cb11e0
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-concat_bytes.rs
@@ -0,0 +1,4 @@
+fn main() {
+    let a = concat_bytes!(b'A', b"BC"); //~ ERROR use of unstable library feature 'concat_bytes'
+    assert_eq!(a, &[65, 66, 67]);
+}
diff --git a/src/test/ui/feature-gates/feature-gate-concat_bytes.stderr b/src/test/ui/feature-gates/feature-gate-concat_bytes.stderr
new file mode 100644
index 00000000000..4b3ee4c19ce
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-concat_bytes.stderr
@@ -0,0 +1,12 @@
+error[E0658]: use of unstable library feature 'concat_bytes'
+  --> $DIR/feature-gate-concat_bytes.rs:2:13
+   |
+LL |     let a = concat_bytes!(b'A', b"BC");
+   |             ^^^^^^^^^^^^
+   |
+   = note: see issue #87555 <https://github.com/rust-lang/rust/issues/87555> for more information
+   = help: add `#![feature(concat_bytes)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/macros/concat-bytes-error.rs b/src/test/ui/macros/concat-bytes-error.rs
new file mode 100644
index 00000000000..9b4a9c2cf81
--- /dev/null
+++ b/src/test/ui/macros/concat-bytes-error.rs
@@ -0,0 +1,42 @@
+#![feature(concat_bytes)]
+
+fn main() {
+    concat_bytes!(pie); //~ ERROR expected a byte literal
+    concat_bytes!(pie, pie); //~ ERROR expected a byte literal
+    concat_bytes!("tnrsi", "tnri"); //~ ERROR cannot concatenate string literals
+    concat_bytes!(2.8); //~ ERROR cannot concatenate float literals
+    concat_bytes!(300); //~ ERROR cannot concatenate numeric literals
+    concat_bytes!('a'); //~ ERROR cannot concatenate character literals
+    concat_bytes!(true, false); //~ ERROR cannot concatenate boolean literals
+    concat_bytes!(42, b"va", b'l'); //~ ERROR cannot concatenate numeric literals
+    concat_bytes!(42, b"va", b'l', [1, 2]); //~ ERROR cannot concatenate numeric literals
+    concat_bytes!([
+        "hi", //~ ERROR cannot concatenate string literals
+    ]);
+    concat_bytes!([
+        'a', //~ ERROR cannot concatenate character literals
+    ]);
+    concat_bytes!([
+        true, //~ ERROR cannot concatenate boolean literals
+    ]);
+    concat_bytes!([
+        false, //~ ERROR cannot concatenate boolean literals
+    ]);
+    concat_bytes!([
+        2.6, //~ ERROR cannot concatenate float literals
+    ]);
+    concat_bytes!([
+        265, //~ ERROR numeric literal is out of bounds
+    ]);
+    concat_bytes!([
+        -33, //~ ERROR expected a byte literal
+    ]);
+    concat_bytes!([
+        b"hi!", //~ ERROR cannot concatenate doubly nested array
+    ]);
+    concat_bytes!([
+        [5, 6, 7], //~ ERROR cannot concatenate doubly nested array
+    ]);
+    concat_bytes!(5u16); //~ ERROR cannot concatenate numeric literals
+    concat_bytes!([5u16]); //~ ERROR numeric literal is not a `u8`
+}
diff --git a/src/test/ui/macros/concat-bytes-error.stderr b/src/test/ui/macros/concat-bytes-error.stderr
new file mode 100644
index 00000000000..1fc2d5c4843
--- /dev/null
+++ b/src/test/ui/macros/concat-bytes-error.stderr
@@ -0,0 +1,131 @@
+error: expected a byte literal
+  --> $DIR/concat-bytes-error.rs:4:19
+   |
+LL |     concat_bytes!(pie);
+   |                   ^^^
+   |
+   = note: only byte literals (like `b"foo"`, `b's'`, and `[3, 4, 5]`) can be passed to `concat_bytes!()`
+
+error: expected a byte literal
+  --> $DIR/concat-bytes-error.rs:5:19
+   |
+LL |     concat_bytes!(pie, pie);
+   |                   ^^^  ^^^
+   |
+   = note: only byte literals (like `b"foo"`, `b's'`, and `[3, 4, 5]`) can be passed to `concat_bytes!()`
+
+error: cannot concatenate string literals
+  --> $DIR/concat-bytes-error.rs:6:19
+   |
+LL |     concat_bytes!("tnrsi", "tnri");
+   |                   ^^^^^^^ help: try using a byte string: `b"tnrsi"`
+
+error: cannot concatenate float literals
+  --> $DIR/concat-bytes-error.rs:7:19
+   |
+LL |     concat_bytes!(2.8);
+   |                   ^^^
+
+error: cannot concatenate numeric literals
+  --> $DIR/concat-bytes-error.rs:8:19
+   |
+LL |     concat_bytes!(300);
+   |                   ^^^ help: try wrapping the number in an array: `[300]`
+
+error: cannot concatenate character literals
+  --> $DIR/concat-bytes-error.rs:9:19
+   |
+LL |     concat_bytes!('a');
+   |                   ^^^ help: try using a byte character: `b'a'`
+
+error: cannot concatenate boolean literals
+  --> $DIR/concat-bytes-error.rs:10:19
+   |
+LL |     concat_bytes!(true, false);
+   |                   ^^^^
+
+error: cannot concatenate numeric literals
+  --> $DIR/concat-bytes-error.rs:11:19
+   |
+LL |     concat_bytes!(42, b"va", b'l');
+   |                   ^^ help: try wrapping the number in an array: `[42]`
+
+error: cannot concatenate numeric literals
+  --> $DIR/concat-bytes-error.rs:12:19
+   |
+LL |     concat_bytes!(42, b"va", b'l', [1, 2]);
+   |                   ^^ help: try wrapping the number in an array: `[42]`
+
+error: cannot concatenate string literals
+  --> $DIR/concat-bytes-error.rs:14:9
+   |
+LL |         "hi",
+   |         ^^^^
+
+error: cannot concatenate character literals
+  --> $DIR/concat-bytes-error.rs:17:9
+   |
+LL |         'a',
+   |         ^^^ help: try using a byte character: `b'a'`
+
+error: cannot concatenate boolean literals
+  --> $DIR/concat-bytes-error.rs:20:9
+   |
+LL |         true,
+   |         ^^^^
+
+error: cannot concatenate boolean literals
+  --> $DIR/concat-bytes-error.rs:23:9
+   |
+LL |         false,
+   |         ^^^^^
+
+error: cannot concatenate float literals
+  --> $DIR/concat-bytes-error.rs:26:9
+   |
+LL |         2.6,
+   |         ^^^
+
+error: numeric literal is out of bounds
+  --> $DIR/concat-bytes-error.rs:29:9
+   |
+LL |         265,
+   |         ^^^
+
+error: expected a byte literal
+  --> $DIR/concat-bytes-error.rs:32:9
+   |
+LL |         -33,
+   |         ^^^
+   |
+   = note: only byte literals (like `b"foo"`, `b's'`, and `[3, 4, 5]`) can be passed to `concat_bytes!()`
+
+error: cannot concatenate doubly nested array
+  --> $DIR/concat-bytes-error.rs:35:9
+   |
+LL |         b"hi!",
+   |         ^^^^^^
+   |
+   = note: byte strings are treated as arrays of bytes
+   = help: try flattening the array
+
+error: cannot concatenate doubly nested array
+  --> $DIR/concat-bytes-error.rs:38:9
+   |
+LL |         [5, 6, 7],
+   |         ^^^^^^^^^
+
+error: cannot concatenate numeric literals
+  --> $DIR/concat-bytes-error.rs:40:19
+   |
+LL |     concat_bytes!(5u16);
+   |                   ^^^^ help: try wrapping the number in an array: `[5u16]`
+
+error: numeric literal is not a `u8`
+  --> $DIR/concat-bytes-error.rs:41:20
+   |
+LL |     concat_bytes!([5u16]);
+   |                    ^^^^
+
+error: aborting due to 20 previous errors
+
diff --git a/src/test/ui/macros/concat-bytes.rs b/src/test/ui/macros/concat-bytes.rs
new file mode 100644
index 00000000000..5415cf3fe22
--- /dev/null
+++ b/src/test/ui/macros/concat-bytes.rs
@@ -0,0 +1,7 @@
+// run-pass
+#![feature(concat_bytes)]
+
+fn main() {
+    assert_eq!(concat_bytes!(), &[]);
+    assert_eq!(concat_bytes!(b'A', b"BC", [68, b'E', 70]), b"ABCDEF");
+}