Improve diagnostics for concat_bytes! with C string literals

Use the same error as other invalid types for `concat_bytes!`, rather
than using `ConcatCStrLit` from `concat!`. Also add more information
with a note about why this doesn't work, and a suggestion to use a
null-terminated byte string instead.
This commit is contained in:
Trevor Gross
2025-06-18 20:08:15 -04:00
parent 342f07ab31
commit 044c99df78
5 changed files with 86 additions and 43 deletions

View File

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

View File

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