rust: pin-init: internal: handle item and expr diagnostics differently

Currently `DiagCtxt` is used to allow error recovery in the pin-init
macros, by generating both a `compile_error!()` macro item and continue
expansion, and then merge token streams together. This works very well for
items, however for expressions, this will produce invalid expression and
result in a confusing error message.

    error: macro expansion ignores `::` and any tokens following
      --> tests/ui/compile-fail/zeroable/invalid_spread.rs:15:9
       |
    13 |       let _ = init!(Foo {
       |  _____________-
    14 | |         a: 0,
    15 | |         ..MyZeroable::init_zeroed()
       | |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    16 | |     });
       | |______- caused by the macro expansion here
       |
       = note: the usage of `init!` is likely invalid in expression context

Fix this by wrap the concatenated diagnostics in a block, with the
generated expression in its tail position. This results in the desired
error message.

    error: expected nothing or `..Zeroable::init_zeroed()`.
      --> tests/ui/compile-fail/zeroable/invalid_spread.rs:15:9
       |
    15 |         ..MyZeroable::init_zeroed()
       |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^

Reviewed-by: Benno Lossin <lossin@kernel.org>
Link: https://patch.msgid.link/20260923140618.1978181-1-gary@kernel.org
Signed-off-by: Gary Guo <gary@garyguo.net>
This commit is contained in:
Gary Guo 2026-09-23 15:06:13 +01:00
parent 666b3e7cfa
commit 01c3d218e8
2 changed files with 53 additions and 14 deletions

View File

@ -3,7 +3,7 @@
use std::fmt::Display;
use proc_macro2::TokenStream;
use quote::quote_spanned;
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Error};
pub(crate) struct DiagCtxt(TokenStream);
@ -29,16 +29,55 @@ const fn warn() {}
));
}
pub(crate) fn with(
fun: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
fn with(
f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
merge_diag: impl FnOnce(TokenStream, TokenStream) -> TokenStream,
convert_diag: impl FnOnce(TokenStream) -> TokenStream,
) -> TokenStream {
let mut dcx = Self(TokenStream::new());
match fun(&mut dcx) {
Ok(mut stream) => {
stream.extend(dcx.0);
stream
match f(&mut dcx) {
Ok(stream) => {
if dcx.0.is_empty() {
stream
} else {
merge_diag(stream, dcx.0)
}
}
Err(ErrorGuaranteed(())) => dcx.0,
Err(ErrorGuaranteed(())) => convert_diag(dcx.0),
}
}
pub(crate) fn for_item(
f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
) -> TokenStream {
Self::with(
f,
|mut out, diag| {
out.extend(diag);
out
},
std::convert::identity,
)
}
pub(crate) fn for_expr(
f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
) -> TokenStream {
Self::with(
f,
|out, diag| {
// Diagnostics that we generate are always items.
// So for expressions create a block to place diagnostics in item position.
quote!({
#diag
#out
})
},
|diag| {
quote!({
#diag
})
},
)
}
}

View File

@ -25,31 +25,31 @@
pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args);
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| pin_data::pin_data(args, input, dcx)).into()
DiagCtxt::for_item(|dcx| pin_data::pin_data(args, input, dcx)).into()
}
#[proc_macro_attribute]
pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args);
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| pinned_drop::pinned_drop(args, input, dcx)).into()
DiagCtxt::for_item(|dcx| pinned_drop::pinned_drop(args, input, dcx)).into()
}
#[proc_macro_derive(Zeroable)]
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| zeroable::derive(input, dcx)).into()
DiagCtxt::for_item(|dcx| zeroable::derive(input, dcx)).into()
}
#[proc_macro_derive(MaybeZeroable)]
pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| zeroable::maybe_derive(input, dcx)).into()
DiagCtxt::for_item(|dcx| zeroable::maybe_derive(input, dcx)).into()
}
#[proc_macro]
pub fn init(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| {
DiagCtxt::for_expr(|dcx| {
init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx)
})
.into()
@ -58,7 +58,7 @@ pub fn init(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn pin_init(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
DiagCtxt::with(|dcx| {
DiagCtxt::for_expr(|dcx| {
init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx)
})
.into()