Skip to content

Improve diagnostics for concat_bytes! with C string literals #142698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ builtin_macros_concat_bytes_bad_repeat = repeat count is not a positive number
builtin_macros_concat_bytes_invalid = cannot concatenate {$lit_kind} literals
.byte_char = try using a byte character
.byte_str = try using a byte string
.c_str = try using a null-terminated byte string
.c_str_note = concatenating C strings is ambiguous about including the '\0'
.number_array = try wrapping the number in an array
builtin_macros_concat_bytes_missing_literal = expected a byte literal
Expand Down
40 changes: 30 additions & 10 deletions compiler/rustc_builtin_macros/src/concat_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rustc_ast::ptr::P;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{ExprKind, LitIntType, LitKind, UintTy, token};
use rustc_ast::{ExprKind, LitIntType, LitKind, StrStyle, UintTy, token};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
use rustc_session::errors::report_lit_error;
use rustc_span::{ErrorGuaranteed, Span};
Expand All @@ -21,15 +21,32 @@ fn invalid_type_err(
let snippet = cx.sess.source_map().span_to_snippet(span).ok();
let dcx = cx.dcx();
match LitKind::from_token_lit(token_lit) {
Ok(LitKind::CStr(_, _)) => {
Ok(LitKind::CStr(_, style)) => {
// Avoid ambiguity in handling of terminal `NUL` by refusing to
// concatenate C string literals as bytes.
dcx.emit_err(errors::ConcatCStrLit { span })
let sugg = if let Some(mut as_bstr) = snippet
&& style == StrStyle::Cooked
&& as_bstr.starts_with('c')
&& as_bstr.ends_with('"')
{
// Suggest`c"foo"` -> `b"foo\0"` if we can
as_bstr.replace_range(0..1, "b");
as_bstr.pop();
as_bstr.push_str(r#"\0""#);
Some(ConcatBytesInvalidSuggestion::CStrLit { span, as_bstr })
} else {
// No suggestion for a missing snippet, raw strings, or if for some reason we have
// a span that doesn't match `c"foo"` (possible if a proc macro assigns a span
// that doesn't actually point to a C string).
None
};
// We can only provide a suggestion if we have a snip and it is not a raw string
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "C string", sugg, cs_note: Some(()) })
}
Ok(LitKind::Char(_)) => {
let sugg =
snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet });
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg })
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg, cs_note: None })
}
Ok(LitKind::Str(_, _)) => {
// suggestion would be invalid if we are nested
Expand All @@ -38,18 +55,21 @@ fn invalid_type_err(
} else {
None
};
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg })
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg, cs_note: None })
}
Ok(LitKind::Float(_, _)) => {
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None })
}
Ok(LitKind::Bool(_)) => {
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None })
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None, cs_note: None })
}
Ok(LitKind::Bool(_)) => dcx.emit_err(ConcatBytesInvalid {
span,
lit_kind: "boolean",
sugg: None,
cs_note: None,
}),
Ok(LitKind::Int(_, _)) if !is_nested => {
let sugg =
snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span, snippet });
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg })
dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg, cs_note: None })
}
Ok(LitKind::Int(val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8))) => {
assert!(val.get() > u8::MAX.into()); // must be an error
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_builtin_macros/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ pub(crate) struct ConcatBytesInvalid {
pub(crate) lit_kind: &'static str,
#[subdiagnostic]
pub(crate) sugg: Option<ConcatBytesInvalidSuggestion>,
#[note(builtin_macros_c_str_note)]
pub(crate) cs_note: Option<()>,
}

#[derive(Subdiagnostic)]
Expand All @@ -239,6 +241,13 @@ pub(crate) enum ConcatBytesInvalidSuggestion {
span: Span,
snippet: String,
},
#[note(builtin_macros_c_str_note)]
#[suggestion(builtin_macros_c_str, code = "{as_bstr}", applicability = "machine-applicable")]
CStrLit {
#[primary_span]
span: Span,
as_bstr: String,
},
#[suggestion(
builtin_macros_number_array,
code = "[{snippet}]",
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/macros/concat-bytes-error.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
//@ edition: 2021
// 2021 edition for C string literals

#![feature(concat_bytes)]

fn main() {
// Identifiers
concat_bytes!(pie); //~ ERROR expected a byte literal
concat_bytes!(pie, pie); //~ ERROR expected a byte literal

// String literals
concat_bytes!("tnrsi", "tnri"); //~ ERROR cannot concatenate string literals
//~^ SUGGESTION b"tnrsi"
concat_bytes!(r"tnrsi", r"tnri"); //~ ERROR cannot concatenate string literals
//~^ SUGGESTION br"tnrsi"
concat_bytes!(r#"tnrsi"#, r###"tnri"###); //~ ERROR cannot concatenate string literals
//~^ SUGGESTION br#"tnrsi"#
concat_bytes!(c"tnrsi", c"tnri"); //~ ERROR cannot concatenate C string literals
//~^ SUGGESTION b"tnrsi\0"
concat_bytes!(cr"tnrsi", cr"tnri"); //~ ERROR cannot concatenate C string literals
concat_bytes!(cr#"tnrsi"#, cr###"tnri"###); //~ ERROR cannot concatenate C string literals

// Other literals
concat_bytes!(2.8); //~ ERROR cannot concatenate float literals
concat_bytes!(300); //~ ERROR cannot concatenate numeric literals
//~^ SUGGESTION [300]
concat_bytes!('a'); //~ ERROR cannot concatenate character literals
//~^ SUGGESTION b'a'
concat_bytes!(true, false); //~ ERROR cannot concatenate boolean literals
concat_bytes!(42, b"va", b'l'); //~ ERROR cannot concatenate numeric literals
//~^ SUGGESTION [42]
concat_bytes!(42, b"va", b'l', [1, 2]); //~ ERROR cannot concatenate numeric literals
//~^ SUGGESTION [42]

// Nested items
concat_bytes!([
"hi", //~ ERROR cannot concatenate string literals
]);
concat_bytes!([
'a', //~ ERROR cannot concatenate character literals
//~^ SUGGESTION b'a'
]);
concat_bytes!([
true, //~ ERROR cannot concatenate boolean literals
Expand All @@ -38,6 +62,7 @@ fn main() {
[5, 6, 7], //~ ERROR cannot concatenate doubly nested array
]);
concat_bytes!(5u16); //~ ERROR cannot concatenate numeric literals
//~^ SUGGESTION [5u16]
concat_bytes!([5u16]); //~ ERROR numeric literal is not a `u8`
concat_bytes!([3; ()]); //~ ERROR repeat count is not a positive number
concat_bytes!([3; -2]); //~ ERROR repeat count is not a positive number
Expand Down
Loading
Loading