Rollup merge of #87599 - Smittyvb:concat_bytes, r=Mark-Simulacrum

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.
This commit is contained in:
Matthias Krüger 2021-12-09 05:08:30 +01:00 committed by GitHub
commit 3fc5bd7abc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 421 additions and 0 deletions

View File

@ -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))))
}

View File

@ -27,6 +27,7 @@
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,

View File

@ -439,6 +439,7 @@
compiler_builtins,
compiler_fence,
concat,
concat_bytes,
concat_idents,
conservative_impl_trait,
console,

View File

@ -967,6 +967,34 @@ macro_rules! concat_idents {
($($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

View File

@ -60,6 +60,15 @@
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",

View File

@ -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 mod task {
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;

View File

@ -45,6 +45,15 @@
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",

View File

@ -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]);
}

View File

@ -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`.

View File

@ -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`
}

View File

@ -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

View File

@ -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");
}