Skip to content

Commit

Permalink
Rollup merge of #87599 - Smittyvb:concat_bytes, r=Mark-Simulacrum
Browse files Browse the repository at this point in the history
Implement concat_bytes!

This implements the unstable `concat_bytes!` macro, which has tracking issue #87555. It can be used like:
```rust
#![feature(concat_bytes)]

fn main() {
    assert_eq!(concat_bytes!(), &[]);
    assert_eq!(concat_bytes!(b'A', b"BC", [68, b'E', 70]), b"ABCDEF");
}
```
If strings or characters are used where byte strings or byte characters are required, it suggests adding a `b` prefix. If a number is used outside of an array it suggests arrayifying it. If a boolean is used it suggests replacing it with the numeric value of that number. Doubly nested arrays of bytes are disallowed.
  • Loading branch information
matthiaskrgr authored Dec 9, 2021
2 parents 3c857f4 + eb56693 commit 3fc5bd7
Show file tree
Hide file tree
Showing 12 changed files with 421 additions and 0 deletions.
167 changes: 167 additions & 0 deletions compiler/rustc_builtin_macros/src/concat_bytes.rs
Original file line number Diff line number Diff line change
@@ -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))))
}
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ symbols! {
compiler_builtins,
compiler_fence,
concat,
concat_bytes,
concat_idents,
conservative_impl_trait,
console,
Expand Down
28 changes: 28 additions & 0 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ pub use crate::{
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))]
#[doc(no_inline)]
pub use crate::concat_bytes;

#[unstable(
feature = "asm",
issue = "72016",
Expand Down
9 changes: 9 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;

Expand Down
9 changes: 9 additions & 0 deletions library/std/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ pub use core::prelude::v1::{
PartialOrd,
};

#[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",
Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/feature-gates/feature-gate-concat_bytes.rs
Original file line number Diff line number Diff line change
@@ -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]);
}
12 changes: 12 additions & 0 deletions src/test/ui/feature-gates/feature-gate-concat_bytes.stderr
Original file line number Diff line number Diff line change
@@ -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`.
42 changes: 42 additions & 0 deletions src/test/ui/macros/concat-bytes-error.rs
Original file line number Diff line number Diff line change
@@ -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`
}
Loading

0 comments on commit 3fc5bd7

Please sign in to comment.