* [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate
[not found] <20260112170919.1888584-1-gary@kernel.org>
@ 2026-01-12 17:07 ` Gary Guo
2026-01-19 7:09 ` David Gow
2026-01-12 17:07 ` [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
2026-01-12 17:07 ` [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2 siblings, 1 reply; 7+ messages in thread
From: Gary Guo @ 2026-01-12 17:07 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Tamir Duberstein, Greg Kroah-Hartman,
José Expósito
Cc: rust-for-linux, linux-kernel, linux-kselftest, kunit-dev,
linux-modules
From: Gary Guo <gary@garyguo.net>
With `quote` crate now vendored in the kernel, we can remove our custom
`quote!` macro implementation and just rely on that crate instead.
The `quote` crate uses types from the `proc-macro2` library so we also
update to use that, and perform conversion in the top-level lib.rs.
Clippy complains about unnecessary `.to_string()` as `proc-macro2`
provides additional `PartialEq` impl, so they are removed.
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/concat_idents.rs | 2 +-
rust/macros/export.rs | 4 +-
rust/macros/fmt.rs | 4 +-
rust/macros/helpers.rs | 4 +-
rust/macros/kunit.rs | 5 +-
rust/macros/lib.rs | 21 ++--
rust/macros/module.rs | 6 +-
rust/macros/paste.rs | 2 +-
rust/macros/quote.rs | 182 -----------------------------------
rust/macros/vtable.rs | 7 +-
10 files changed, 32 insertions(+), 205 deletions(-)
delete mode 100644 rust/macros/quote.rs
diff --git a/rust/macros/concat_idents.rs b/rust/macros/concat_idents.rs
index 7e4b450f3a507..12cb231c3d715 100644
--- a/rust/macros/concat_idents.rs
+++ b/rust/macros/concat_idents.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Ident, TokenStream, TokenTree};
use crate::helpers::expect_punct;
diff --git a/rust/macros/export.rs b/rust/macros/export.rs
index a08f6337d5c8d..92d9b30971929 100644
--- a/rust/macros/export.rs
+++ b/rust/macros/export.rs
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
+use proc_macro2::TokenStream;
+use quote::quote;
+
use crate::helpers::function_name;
-use proc_macro::TokenStream;
/// Please see [`crate::export`] for documentation.
pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
diff --git a/rust/macros/fmt.rs b/rust/macros/fmt.rs
index 2f4b9f6e22110..19f709262552b 100644
--- a/rust/macros/fmt.rs
+++ b/rust/macros/fmt.rs
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Ident, TokenStream, TokenTree};
use std::collections::BTreeSet;
+use proc_macro2::{Ident, TokenStream, TokenTree};
+use quote::quote_spanned;
+
/// Please see [`crate::fmt`] for documentation.
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
let mut input = input.into_iter();
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 365d7eb499c08..13fafaba12261 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
if let Some(TokenTree::Ident(ident)) = it.next() {
@@ -86,7 +86,7 @@ pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
let mut input = input.into_iter();
while let Some(token) = input.next() {
match token {
- TokenTree::Ident(i) if i.to_string() == "fn" => {
+ TokenTree::Ident(i) if i == "fn" => {
if let Some(TokenTree::Ident(i)) = input.next() {
return Some(i);
}
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index b395bb0536959..5cd6aa5eef07d 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -4,10 +4,11 @@
//!
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use std::collections::HashMap;
use std::fmt::Write;
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
let attr = attr.to_string();
@@ -59,7 +60,7 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
}
_ => (),
},
- TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
+ TokenTree::Ident(i) if i == "fn" && attributes.contains_key("test") => {
if let Some(TokenTree::Ident(test_name)) = body_it.next() {
tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index b38002151871a..945982c21f703 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -11,8 +11,6 @@
// to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0.
#![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
-#[macro_use]
-mod quote;
mod concat_idents;
mod export;
mod fmt;
@@ -132,7 +130,7 @@
/// the kernel module.
#[proc_macro]
pub fn module(ts: TokenStream) -> TokenStream {
- module::module(ts)
+ module::module(ts.into()).into()
}
/// Declares or implements a vtable trait.
@@ -207,7 +205,7 @@ pub fn module(ts: TokenStream) -> TokenStream {
/// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
#[proc_macro_attribute]
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
- vtable::vtable(attr, ts)
+ vtable::vtable(attr.into(), ts.into()).into()
}
/// Export a function so that C code can call it via a header file.
@@ -230,7 +228,7 @@ pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
/// automatically exported with `EXPORT_SYMBOL_GPL`.
#[proc_macro_attribute]
pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
- export::export(attr, ts)
+ export::export(attr.into(), ts.into()).into()
}
/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
@@ -248,7 +246,7 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
/// [`pr_info!`]: ../kernel/macro.pr_info.html
#[proc_macro]
pub fn fmt(input: TokenStream) -> TokenStream {
- fmt::fmt(input)
+ fmt::fmt(input.into()).into()
}
/// Concatenate two identifiers.
@@ -306,7 +304,7 @@ pub fn fmt(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn concat_idents(ts: TokenStream) -> TokenStream {
- concat_idents::concat_idents(ts)
+ concat_idents::concat_idents(ts.into()).into()
}
/// Paste identifiers together.
@@ -444,9 +442,12 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {
/// [`paste`]: https://docs.rs/paste/
#[proc_macro]
pub fn paste(input: TokenStream) -> TokenStream {
- let mut tokens = input.into_iter().collect();
+ let mut tokens = proc_macro2::TokenStream::from(input).into_iter().collect();
paste::expand(&mut tokens);
- tokens.into_iter().collect()
+ tokens
+ .into_iter()
+ .collect::<proc_macro2::TokenStream>()
+ .into()
}
/// Registers a KUnit test suite and its test cases using a user-space like syntax.
@@ -473,5 +474,5 @@ pub fn paste(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
- kunit::kunit_tests(attr, ts)
+ kunit::kunit_tests(attr.into(), ts.into()).into()
}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 80cb9b16f5aaf..b855a2b586e18 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-use crate::helpers::*;
-use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
use std::fmt::Write;
+use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+
+use crate::helpers::*;
+
fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
let group = expect_group(it);
assert_eq!(group.delimiter(), Delimiter::Bracket);
diff --git a/rust/macros/paste.rs b/rust/macros/paste.rs
index cce712d19855b..2181e312a7d32 100644
--- a/rust/macros/paste.rs
+++ b/rust/macros/paste.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
+use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
fn concat_helper(tokens: &[TokenTree]) -> Vec<(String, Span)> {
let mut tokens = tokens.iter();
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
deleted file mode 100644
index ddfc21577539c..0000000000000
--- a/rust/macros/quote.rs
+++ /dev/null
@@ -1,182 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 OR MIT
-
-use proc_macro::{TokenStream, TokenTree};
-
-pub(crate) trait ToTokens {
- fn to_tokens(&self, tokens: &mut TokenStream);
-}
-
-impl<T: ToTokens> ToTokens for Option<T> {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- if let Some(v) = self {
- v.to_tokens(tokens);
- }
- }
-}
-
-impl ToTokens for proc_macro::Group {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([TokenTree::from(self.clone())]);
- }
-}
-
-impl ToTokens for proc_macro::Ident {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([TokenTree::from(self.clone())]);
- }
-}
-
-impl ToTokens for TokenTree {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([self.clone()]);
- }
-}
-
-impl ToTokens for TokenStream {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend(self.clone());
- }
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// the given span.
-///
-/// This is a similar to the
-/// [`quote_spanned!`](https://docs.rs/quote/latest/quote/macro.quote_spanned.html) macro from the
-/// `quote` crate but provides only just enough functionality needed by the current `macros` crate.
-macro_rules! quote_spanned {
- ($span:expr => $($tt:tt)*) => {{
- let mut tokens = ::proc_macro::TokenStream::new();
- {
- #[allow(unused_variables)]
- let span = $span;
- quote_spanned!(@proc tokens span $($tt)*);
- }
- tokens
- }};
- (@proc $v:ident $span:ident) => {};
- (@proc $v:ident $span:ident #$id:ident $($tt:tt)*) => {
- $crate::quote::ToTokens::to_tokens(&$id, &mut $v);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident #(#$id:ident)* $($tt:tt)*) => {
- for token in $id {
- $crate::quote::ToTokens::to_tokens(&token, &mut $v);
- }
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ( $($inner:tt)* ) $($tt:tt)*) => {
- #[allow(unused_mut)]
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Parenthesis,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident [ $($inner:tt)* ] $($tt:tt)*) => {
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Bracket,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident { $($inner:tt)* } $($tt:tt)*) => {
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Brace,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident :: $($tt:tt)*) => {
- $v.extend([::proc_macro::Spacing::Joint, ::proc_macro::Spacing::Alone].map(|spacing| {
- ::proc_macro::TokenTree::Punct(::proc_macro::Punct::new(':', spacing))
- }));
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident : $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(':', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident , $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(',', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident @ $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('@', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ! $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('!', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ; $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(';', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident + $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('+', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident = $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident # $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('#', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident & $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident _ $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Ident(
- ::proc_macro::Ident::new("_", $span),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident $id:ident $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Ident(
- ::proc_macro::Ident::new(stringify!($id), $span),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// mixed site span ([`Span::mixed_site()`]).
-///
-/// This is a similar to the [`quote!`](https://docs.rs/quote/latest/quote/macro.quote.html) macro
-/// from the `quote` crate but provides only just enough functionality needed by the current
-/// `macros` crate.
-///
-/// [`Span::mixed_site()`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.mixed_site
-macro_rules! quote {
- ($($tt:tt)*) => {
- quote_spanned!(::proc_macro::Span::mixed_site() => $($tt)*)
- }
-}
diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
index ee06044fcd4f3..a67d1cc81a2d3 100644
--- a/rust/macros/vtable.rs
+++ b/rust/macros/vtable.rs
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use std::collections::HashSet;
use std::fmt::Write;
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
let mut tokens: Vec<_> = ts.into_iter().collect();
@@ -31,7 +32,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
let mut consts = HashSet::new();
while let Some(token) = body_it.next() {
match token {
- TokenTree::Ident(ident) if ident.to_string() == "fn" => {
+ TokenTree::Ident(ident) if ident == "fn" => {
let fn_name = match body_it.next() {
Some(TokenTree::Ident(ident)) => ident.to_string(),
// Possibly we've encountered a fn pointer type instead.
@@ -39,7 +40,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
};
functions.push(fn_name);
}
- TokenTree::Ident(ident) if ident.to_string() == "const" => {
+ TokenTree::Ident(ident) if ident == "const" => {
let const_name = match body_it.next() {
Some(TokenTree::Ident(ident)) => ident.to_string(),
// Possibly we've encountered an inline const block instead.
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn`
[not found] <20260112170919.1888584-1-gary@kernel.org>
2026-01-12 17:07 ` [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2026-01-12 17:07 ` Gary Guo
2026-01-17 20:09 ` Benno Lossin
2026-01-19 7:10 ` David Gow
2026-01-12 17:07 ` [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2 siblings, 2 replies; 7+ messages in thread
From: Gary Guo @ 2026-01-12 17:07 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar,
Tamir Duberstein, Igor Korotin, José Expósito
Cc: rust-for-linux, Guilherme Giacomo Simoes, linux-kselftest,
kunit-dev, linux-kernel
From: Gary Guo <gary@garyguo.net>
Make use of `syn` to parse the module structurally and thus improve the
robustness of parsing.
String interpolation is avoided by generating tokens directly using
`quote!`.
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
rust/macros/lib.rs | 6 +-
2 files changed, 123 insertions(+), 157 deletions(-)
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index 5cd6aa5eef07d..afbc708cbdc50 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -4,81 +4,50 @@
//!
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
-use std::collections::HashMap;
-use std::fmt::Write;
-
-use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
-
-pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
- let attr = attr.to_string();
-
- if attr.is_empty() {
- panic!("Missing test name in `#[kunit_tests(test_name)]` macro")
- }
-
- if attr.len() > 255 {
- panic!("The test suite name `{attr}` exceeds the maximum length of 255 bytes")
+use std::ffi::CString;
+
+use proc_macro2::TokenStream;
+use quote::{
+ format_ident,
+ quote,
+ ToTokens, //
+};
+use syn::{
+ parse_quote,
+ Error,
+ Ident,
+ Item,
+ ItemMod,
+ LitCStr,
+ Result, //
+};
+
+pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<TokenStream> {
+ if test_suite.to_string().len() > 255 {
+ return Err(Error::new_spanned(
+ test_suite,
+ "test suite names cannot exceed the maximum length of 255 bytes",
+ ));
}
- let mut tokens: Vec<_> = ts.into_iter().collect();
-
- // Scan for the `mod` keyword.
- tokens
- .iter()
- .find_map(|token| match token {
- TokenTree::Ident(ident) => match ident.to_string().as_str() {
- "mod" => Some(true),
- _ => None,
- },
- _ => None,
- })
- .expect("`#[kunit_tests(test_name)]` attribute should only be applied to modules");
-
- // Retrieve the main body. The main body should be the last token tree.
- let body = match tokens.pop() {
- Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
- _ => panic!("Cannot locate main body of module"),
+ // We cannot handle modules that defer to another file (e.g. `mod foo;`).
+ let Some((module_brace, module_items)) = module.content.take() else {
+ Err(Error::new_spanned(
+ module,
+ "`#[kunit_tests(test_name)]` attribute should only be applied to inline modules",
+ ))?
};
- // Get the functions set as tests. Search for `[test]` -> `fn`.
- let mut body_it = body.stream().into_iter();
- let mut tests = Vec::new();
- let mut attributes: HashMap<String, TokenStream> = HashMap::new();
- while let Some(token) = body_it.next() {
- match token {
- TokenTree::Punct(ref p) if p.as_char() == '#' => match body_it.next() {
- Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
- if let Some(TokenTree::Ident(name)) = g.stream().into_iter().next() {
- // Collect attributes because we need to find which are tests. We also
- // need to copy `cfg` attributes so tests can be conditionally enabled.
- attributes
- .entry(name.to_string())
- .or_default()
- .extend([token, TokenTree::Group(g)]);
- }
- continue;
- }
- _ => (),
- },
- TokenTree::Ident(i) if i == "fn" && attributes.contains_key("test") => {
- if let Some(TokenTree::Ident(test_name)) = body_it.next() {
- tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
- }
- }
-
- _ => (),
- }
- attributes.clear();
- }
+ // Make the entire module gated behind `CONFIG_KUNIT`.
+ module
+ .attrs
+ .insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
- // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
- let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap();
- tokens.insert(
- 0,
- TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
- );
+ let mut processed_items = Vec::new();
+ let mut test_cases = Vec::new();
// Generate the test KUnit test suite and a test case for each `#[test]`.
+ //
// The code generated for the following test module:
//
// ```
@@ -110,98 +79,93 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
//
// ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
// ```
- let mut kunit_macros = "".to_owned();
- let mut test_cases = "".to_owned();
- let mut assert_macros = "".to_owned();
- let path = crate::helpers::file();
- let num_tests = tests.len();
- for (test, cfg_attr) in tests {
- let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
- // Append any `cfg` attributes the user might have written on their tests so we don't
- // attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
- // the length of the assert message.
- let kunit_wrapper = format!(
- r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
- {{
- (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
- {cfg_attr} {{
- (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
- use ::kernel::kunit::is_test_result_ok;
- assert!(is_test_result_ok({test}()));
+ //
+ // Non-function items (e.g. imports) are preserved.
+ for item in module_items {
+ let Item::Fn(mut f) = item else {
+ processed_items.push(item);
+ continue;
+ };
+
+ // TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
+ let before_len = f.attrs.len();
+ f.attrs.retain(|attr| !attr.path().is_ident("test"));
+ if f.attrs.len() == before_len {
+ processed_items.push(Item::Fn(f));
+ continue;
+ }
+
+ let test = f.sig.ident.clone();
+
+ // Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
+ let cfg_attrs: Vec<_> = f
+ .attrs
+ .iter()
+ .filter(|attr| attr.path().is_ident("cfg"))
+ .cloned()
+ .collect();
+
+ // Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
+ // KUnit instead.
+ let test_str = test.to_string();
+ let path = crate::helpers::file();
+ processed_items.push(parse_quote! {
+ #[allow(unused)]
+ macro_rules! assert {
+ ($cond:expr $(,)?) => {{
+ kernel::kunit_assert!(#test_str, #path, 0, $cond);
+ }}
+ }
+ });
+ processed_items.push(parse_quote! {
+ #[allow(unused)]
+ macro_rules! assert_eq {
+ ($left:expr, $right:expr $(,)?) => {{
+ kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
}}
- }}"#,
+ }
+ });
+
+ // Add back the test item.
+ processed_items.push(Item::Fn(f));
+
+ let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
+ let test_cstr = LitCStr::new(
+ &CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
+ test.span(),
);
- writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
- writeln!(
- test_cases,
- " ::kernel::kunit::kunit_case(::kernel::c_str!(\"{test}\"), {kunit_wrapper_fn_name}),"
- )
- .unwrap();
- writeln!(
- assert_macros,
- r#"
-/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
-#[allow(unused)]
-macro_rules! assert {{
- ($cond:expr $(,)?) => {{{{
- kernel::kunit_assert!("{test}", "{path}", 0, $cond);
- }}}}
-}}
-
-/// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
-#[allow(unused)]
-macro_rules! assert_eq {{
- ($left:expr, $right:expr $(,)?) => {{{{
- kernel::kunit_assert_eq!("{test}", "{path}", 0, $left, $right);
- }}}}
-}}
- "#
- )
- .unwrap();
- }
+ processed_items.push(parse_quote! {
+ unsafe extern "C" fn #kunit_wrapper_fn_name(_test: *mut ::kernel::bindings::kunit) {
+ (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
- writeln!(kunit_macros).unwrap();
- writeln!(
- kunit_macros,
- "static mut TEST_CASES: [::kernel::bindings::kunit_case; {}] = [\n{test_cases} ::kernel::kunit::kunit_case_null(),\n];",
- num_tests + 1
- )
- .unwrap();
-
- writeln!(
- kunit_macros,
- "::kernel::kunit_unsafe_test_suite!({attr}, TEST_CASES);"
- )
- .unwrap();
-
- // Remove the `#[test]` macros.
- // We do this at a token level, in order to preserve span information.
- let mut new_body = vec![];
- let mut body_it = body.stream().into_iter();
-
- while let Some(token) = body_it.next() {
- match token {
- TokenTree::Punct(ref c) if c.as_char() == '#' => match body_it.next() {
- Some(TokenTree::Group(group)) if group.to_string() == "[test]" => (),
- Some(next) => {
- new_body.extend([token, next]);
- }
- _ => {
- new_body.push(token);
+ // Append any `cfg` attributes the user might have written on their tests so we
+ // don't attempt to call them when they are `cfg`'d out. An extra `use` is used
+ // here to reduce the length of the assert message.
+ #(#cfg_attrs)*
+ {
+ (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
+ use ::kernel::kunit::is_test_result_ok;
+ assert!(is_test_result_ok(#test()));
}
- },
- _ => {
- new_body.push(token);
}
- }
- }
+ });
- let mut final_body = TokenStream::new();
- final_body.extend::<TokenStream>(assert_macros.parse().unwrap());
- final_body.extend(new_body);
- final_body.extend::<TokenStream>(kunit_macros.parse().unwrap());
-
- tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, final_body)));
+ test_cases.push(quote!(
+ ::kernel::kunit::kunit_case(#test_cstr, #kunit_wrapper_fn_name)
+ ));
+ }
- tokens.into_iter().collect()
+ let num_tests_plus_1 = test_cases.len() + 1;
+ processed_items.push(parse_quote! {
+ static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
+ #(#test_cases,)*
+ ::kernel::kunit::kunit_case_null(),
+ ];
+ });
+ processed_items.push(parse_quote! {
+ ::kernel::kunit_unsafe_test_suite!(#test_suite, TEST_CASES);
+ });
+
+ module.content = Some((module_brace, processed_items));
+ Ok(module.to_token_stream())
}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 12467bfc703a8..75ac60abe6ffa 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -481,6 +481,8 @@ pub fn paste(input: TokenStream) -> TokenStream {
/// }
/// ```
#[proc_macro_attribute]
-pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
- kunit::kunit_tests(attr.into(), ts.into()).into()
+pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
+ kunit::kunit_tests(parse_macro_input!(attr), parse_macro_input!(input))
+ .unwrap_or_else(|e| e.into_compile_error())
+ .into()
}
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value
[not found] <20260112170919.1888584-1-gary@kernel.org>
2026-01-12 17:07 ` [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate Gary Guo
2026-01-12 17:07 ` [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
@ 2026-01-12 17:07 ` Gary Guo
2026-01-19 7:10 ` David Gow
2 siblings, 1 reply; 7+ messages in thread
From: Gary Guo @ 2026-01-12 17:07 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar
Cc: rust-for-linux, Tamir Duberstein, linux-kselftest, kunit-dev,
linux-kernel
From: Gary Guo <gary@garyguo.net>
The last null element can be created (constly) using `pin_init::zeroed`,
so prefer to use it instead of adding a custom way of building it.
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/kunit.rs | 26 +-------------------------
rust/macros/kunit.rs | 4 ++--
2 files changed, 3 insertions(+), 27 deletions(-)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 79436509dd73d..034158cdaf06b 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -192,9 +192,6 @@ pub fn is_test_result_ok(t: impl TestResult) -> bool {
}
/// Represents an individual test case.
-///
-/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of valid test cases.
-/// Use [`kunit_case_null`] to generate such a delimiter.
#[doc(hidden)]
pub const fn kunit_case(
name: &'static kernel::str::CStr,
@@ -215,27 +212,6 @@ pub const fn kunit_case(
}
}
-/// Represents the NULL test case delimiter.
-///
-/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of test cases. This
-/// function returns such a delimiter.
-#[doc(hidden)]
-pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
- kernel::bindings::kunit_case {
- run_case: None,
- name: core::ptr::null_mut(),
- generate_params: None,
- attr: kernel::bindings::kunit_attributes {
- speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
- },
- status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
- module_name: core::ptr::null_mut(),
- log: core::ptr::null_mut(),
- param_init: None,
- param_exit: None,
- }
-}
-
/// Registers a KUnit test suite.
///
/// # Safety
@@ -254,7 +230,7 @@ pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
///
/// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
/// kernel::kunit::kunit_case(kernel::c_str!("name"), test_fn),
-/// kernel::kunit::kunit_case_null(),
+/// pin_init::zeroed(),
/// ];
/// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
/// ```
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index afbc708cbdc50..cb29f350d5b2b 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -74,7 +74,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
// static mut TEST_CASES: [::kernel::bindings::kunit_case; 3] = [
// ::kernel::kunit::kunit_case(::kernel::c_str!("foo"), kunit_rust_wrapper_foo),
// ::kernel::kunit::kunit_case(::kernel::c_str!("bar"), kunit_rust_wrapper_bar),
- // ::kernel::kunit::kunit_case_null(),
+ // ::pin_init::zeroed(),
// ];
//
// ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
@@ -159,7 +159,7 @@ macro_rules! assert_eq {
processed_items.push(parse_quote! {
static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
#(#test_cases,)*
- ::kernel::kunit::kunit_case_null(),
+ ::pin_init::zeroed(),
];
});
processed_items.push(parse_quote! {
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn`
2026-01-12 17:07 ` [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
@ 2026-01-17 20:09 ` Benno Lossin
2026-01-19 7:10 ` David Gow
1 sibling, 0 replies; 7+ messages in thread
From: Benno Lossin @ 2026-01-17 20:09 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, Tamir Duberstein,
Igor Korotin, José Expósito
Cc: rust-for-linux, Guilherme Giacomo Simoes, linux-kselftest,
kunit-dev, linux-kernel
On Mon Jan 12, 2026 at 6:07 PM CET, Gary Guo wrote:
> From: Gary Guo <gary@garyguo.net>
>
> Make use of `syn` to parse the module structurally and thus improve the
> robustness of parsing.
>
> String interpolation is avoided by generating tokens directly using
> `quote!`.
>
> Reviewed-by: Tamir Duberstein <tamird@gmail.com>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Cheers,
Benno
> ---
> rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
> rust/macros/lib.rs | 6 +-
> 2 files changed, 123 insertions(+), 157 deletions(-)
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate
2026-01-12 17:07 ` [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2026-01-19 7:09 ` David Gow
0 siblings, 0 replies; 7+ messages in thread
From: David Gow @ 2026-01-19 7:09 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, Rae Moar, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Tamir Duberstein,
Greg Kroah-Hartman, José Expósito, rust-for-linux,
linux-kernel, linux-kselftest, kunit-dev, linux-modules
[-- Attachment #1: Type: text/plain, Size: 18529 bytes --]
On Tue, 13 Jan 2026 at 01:11, Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> With `quote` crate now vendored in the kernel, we can remove our custom
> `quote!` macro implementation and just rely on that crate instead.
>
> The `quote` crate uses types from the `proc-macro2` library so we also
> update to use that, and perform conversion in the top-level lib.rs.
>
> Clippy complains about unnecessary `.to_string()` as `proc-macro2`
> provides additional `PartialEq` impl, so they are removed.
>
> Reviewed-by: Tamir Duberstein <tamird@gmail.com>
> Reviewed-by: Benno Lossin <lossin@kernel.org>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
Acked-by: David Gow <davidgow@google.com> # for kunit
Cheers,
-- David
-- David
> rust/macros/concat_idents.rs | 2 +-
> rust/macros/export.rs | 4 +-
> rust/macros/fmt.rs | 4 +-
> rust/macros/helpers.rs | 4 +-
> rust/macros/kunit.rs | 5 +-
> rust/macros/lib.rs | 21 ++--
> rust/macros/module.rs | 6 +-
> rust/macros/paste.rs | 2 +-
> rust/macros/quote.rs | 182 -----------------------------------
> rust/macros/vtable.rs | 7 +-
> 10 files changed, 32 insertions(+), 205 deletions(-)
> delete mode 100644 rust/macros/quote.rs
>
> diff --git a/rust/macros/concat_idents.rs b/rust/macros/concat_idents.rs
> index 7e4b450f3a507..12cb231c3d715 100644
> --- a/rust/macros/concat_idents.rs
> +++ b/rust/macros/concat_idents.rs
> @@ -1,6 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
> +use proc_macro2::{token_stream, Ident, TokenStream, TokenTree};
>
> use crate::helpers::expect_punct;
>
> diff --git a/rust/macros/export.rs b/rust/macros/export.rs
> index a08f6337d5c8d..92d9b30971929 100644
> --- a/rust/macros/export.rs
> +++ b/rust/macros/export.rs
> @@ -1,7 +1,9 @@
> // SPDX-License-Identifier: GPL-2.0
>
> +use proc_macro2::TokenStream;
> +use quote::quote;
> +
> use crate::helpers::function_name;
> -use proc_macro::TokenStream;
>
> /// Please see [`crate::export`] for documentation.
> pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
> diff --git a/rust/macros/fmt.rs b/rust/macros/fmt.rs
> index 2f4b9f6e22110..19f709262552b 100644
> --- a/rust/macros/fmt.rs
> +++ b/rust/macros/fmt.rs
> @@ -1,8 +1,10 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro::{Ident, TokenStream, TokenTree};
> use std::collections::BTreeSet;
>
> +use proc_macro2::{Ident, TokenStream, TokenTree};
> +use quote::quote_spanned;
> +
> /// Please see [`crate::fmt`] for documentation.
> pub(crate) fn fmt(input: TokenStream) -> TokenStream {
> let mut input = input.into_iter();
> diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
> index 365d7eb499c08..13fafaba12261 100644
> --- a/rust/macros/helpers.rs
> +++ b/rust/macros/helpers.rs
> @@ -1,6 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
> +use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
>
> pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
> if let Some(TokenTree::Ident(ident)) = it.next() {
> @@ -86,7 +86,7 @@ pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
> let mut input = input.into_iter();
> while let Some(token) = input.next() {
> match token {
> - TokenTree::Ident(i) if i.to_string() == "fn" => {
> + TokenTree::Ident(i) if i == "fn" => {
> if let Some(TokenTree::Ident(i)) = input.next() {
> return Some(i);
> }
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> index b395bb0536959..5cd6aa5eef07d 100644
> --- a/rust/macros/kunit.rs
> +++ b/rust/macros/kunit.rs
> @@ -4,10 +4,11 @@
> //!
> //! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
>
> -use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
> use std::collections::HashMap;
> use std::fmt::Write;
>
> +use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
> +
> pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> let attr = attr.to_string();
>
> @@ -59,7 +60,7 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> }
> _ => (),
> },
> - TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
> + TokenTree::Ident(i) if i == "fn" && attributes.contains_key("test") => {
> if let Some(TokenTree::Ident(test_name)) = body_it.next() {
> tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
> }
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index b38002151871a..945982c21f703 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -11,8 +11,6 @@
> // to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0.
> #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
>
> -#[macro_use]
> -mod quote;
> mod concat_idents;
> mod export;
> mod fmt;
> @@ -132,7 +130,7 @@
> /// the kernel module.
> #[proc_macro]
> pub fn module(ts: TokenStream) -> TokenStream {
> - module::module(ts)
> + module::module(ts.into()).into()
> }
>
> /// Declares or implements a vtable trait.
> @@ -207,7 +205,7 @@ pub fn module(ts: TokenStream) -> TokenStream {
> /// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
> #[proc_macro_attribute]
> pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - vtable::vtable(attr, ts)
> + vtable::vtable(attr.into(), ts.into()).into()
> }
>
> /// Export a function so that C code can call it via a header file.
> @@ -230,7 +228,7 @@ pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
> /// automatically exported with `EXPORT_SYMBOL_GPL`.
> #[proc_macro_attribute]
> pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - export::export(attr, ts)
> + export::export(attr.into(), ts.into()).into()
> }
>
> /// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
> @@ -248,7 +246,7 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
> /// [`pr_info!`]: ../kernel/macro.pr_info.html
> #[proc_macro]
> pub fn fmt(input: TokenStream) -> TokenStream {
> - fmt::fmt(input)
> + fmt::fmt(input.into()).into()
> }
>
> /// Concatenate two identifiers.
> @@ -306,7 +304,7 @@ pub fn fmt(input: TokenStream) -> TokenStream {
> /// ```
> #[proc_macro]
> pub fn concat_idents(ts: TokenStream) -> TokenStream {
> - concat_idents::concat_idents(ts)
> + concat_idents::concat_idents(ts.into()).into()
> }
>
> /// Paste identifiers together.
> @@ -444,9 +442,12 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {
> /// [`paste`]: https://docs.rs/paste/
> #[proc_macro]
> pub fn paste(input: TokenStream) -> TokenStream {
> - let mut tokens = input.into_iter().collect();
> + let mut tokens = proc_macro2::TokenStream::from(input).into_iter().collect();
> paste::expand(&mut tokens);
> - tokens.into_iter().collect()
> + tokens
> + .into_iter()
> + .collect::<proc_macro2::TokenStream>()
> + .into()
> }
>
> /// Registers a KUnit test suite and its test cases using a user-space like syntax.
> @@ -473,5 +474,5 @@ pub fn paste(input: TokenStream) -> TokenStream {
> /// ```
> #[proc_macro_attribute]
> pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - kunit::kunit_tests(attr, ts)
> + kunit::kunit_tests(attr.into(), ts.into()).into()
> }
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index 80cb9b16f5aaf..b855a2b586e18 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -1,9 +1,11 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use crate::helpers::*;
> -use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
> use std::fmt::Write;
>
> +use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
> +
> +use crate::helpers::*;
> +
> fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
> let group = expect_group(it);
> assert_eq!(group.delimiter(), Delimiter::Bracket);
> diff --git a/rust/macros/paste.rs b/rust/macros/paste.rs
> index cce712d19855b..2181e312a7d32 100644
> --- a/rust/macros/paste.rs
> +++ b/rust/macros/paste.rs
> @@ -1,6 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
> +use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
>
> fn concat_helper(tokens: &[TokenTree]) -> Vec<(String, Span)> {
> let mut tokens = tokens.iter();
> diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
> deleted file mode 100644
> index ddfc21577539c..0000000000000
> --- a/rust/macros/quote.rs
> +++ /dev/null
> @@ -1,182 +0,0 @@
> -// SPDX-License-Identifier: Apache-2.0 OR MIT
> -
> -use proc_macro::{TokenStream, TokenTree};
> -
> -pub(crate) trait ToTokens {
> - fn to_tokens(&self, tokens: &mut TokenStream);
> -}
> -
> -impl<T: ToTokens> ToTokens for Option<T> {
> - fn to_tokens(&self, tokens: &mut TokenStream) {
> - if let Some(v) = self {
> - v.to_tokens(tokens);
> - }
> - }
> -}
> -
> -impl ToTokens for proc_macro::Group {
> - fn to_tokens(&self, tokens: &mut TokenStream) {
> - tokens.extend([TokenTree::from(self.clone())]);
> - }
> -}
> -
> -impl ToTokens for proc_macro::Ident {
> - fn to_tokens(&self, tokens: &mut TokenStream) {
> - tokens.extend([TokenTree::from(self.clone())]);
> - }
> -}
> -
> -impl ToTokens for TokenTree {
> - fn to_tokens(&self, tokens: &mut TokenStream) {
> - tokens.extend([self.clone()]);
> - }
> -}
> -
> -impl ToTokens for TokenStream {
> - fn to_tokens(&self, tokens: &mut TokenStream) {
> - tokens.extend(self.clone());
> - }
> -}
> -
> -/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
> -/// the given span.
> -///
> -/// This is a similar to the
> -/// [`quote_spanned!`](https://docs.rs/quote/latest/quote/macro.quote_spanned.html) macro from the
> -/// `quote` crate but provides only just enough functionality needed by the current `macros` crate.
> -macro_rules! quote_spanned {
> - ($span:expr => $($tt:tt)*) => {{
> - let mut tokens = ::proc_macro::TokenStream::new();
> - {
> - #[allow(unused_variables)]
> - let span = $span;
> - quote_spanned!(@proc tokens span $($tt)*);
> - }
> - tokens
> - }};
> - (@proc $v:ident $span:ident) => {};
> - (@proc $v:ident $span:ident #$id:ident $($tt:tt)*) => {
> - $crate::quote::ToTokens::to_tokens(&$id, &mut $v);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident #(#$id:ident)* $($tt:tt)*) => {
> - for token in $id {
> - $crate::quote::ToTokens::to_tokens(&token, &mut $v);
> - }
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident ( $($inner:tt)* ) $($tt:tt)*) => {
> - #[allow(unused_mut)]
> - let mut tokens = ::proc_macro::TokenStream::new();
> - quote_spanned!(@proc tokens $span $($inner)*);
> - $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
> - ::proc_macro::Delimiter::Parenthesis,
> - tokens,
> - ))]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident [ $($inner:tt)* ] $($tt:tt)*) => {
> - let mut tokens = ::proc_macro::TokenStream::new();
> - quote_spanned!(@proc tokens $span $($inner)*);
> - $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
> - ::proc_macro::Delimiter::Bracket,
> - tokens,
> - ))]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident { $($inner:tt)* } $($tt:tt)*) => {
> - let mut tokens = ::proc_macro::TokenStream::new();
> - quote_spanned!(@proc tokens $span $($inner)*);
> - $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
> - ::proc_macro::Delimiter::Brace,
> - tokens,
> - ))]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident :: $($tt:tt)*) => {
> - $v.extend([::proc_macro::Spacing::Joint, ::proc_macro::Spacing::Alone].map(|spacing| {
> - ::proc_macro::TokenTree::Punct(::proc_macro::Punct::new(':', spacing))
> - }));
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident : $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new(':', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident , $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new(',', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident @ $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('@', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident ! $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('!', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident ; $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new(';', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident + $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('+', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident = $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident # $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('#', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident & $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Punct(
> - ::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident _ $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Ident(
> - ::proc_macro::Ident::new("_", $span),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> - (@proc $v:ident $span:ident $id:ident $($tt:tt)*) => {
> - $v.extend([::proc_macro::TokenTree::Ident(
> - ::proc_macro::Ident::new(stringify!($id), $span),
> - )]);
> - quote_spanned!(@proc $v $span $($tt)*);
> - };
> -}
> -
> -/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
> -/// mixed site span ([`Span::mixed_site()`]).
> -///
> -/// This is a similar to the [`quote!`](https://docs.rs/quote/latest/quote/macro.quote.html) macro
> -/// from the `quote` crate but provides only just enough functionality needed by the current
> -/// `macros` crate.
> -///
> -/// [`Span::mixed_site()`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.mixed_site
> -macro_rules! quote {
> - ($($tt:tt)*) => {
> - quote_spanned!(::proc_macro::Span::mixed_site() => $($tt)*)
> - }
> -}
> diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
> index ee06044fcd4f3..a67d1cc81a2d3 100644
> --- a/rust/macros/vtable.rs
> +++ b/rust/macros/vtable.rs
> @@ -1,9 +1,10 @@
> // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
> use std::collections::HashSet;
> use std::fmt::Write;
>
> +use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
> +
> pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
> let mut tokens: Vec<_> = ts.into_iter().collect();
>
> @@ -31,7 +32,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
> let mut consts = HashSet::new();
> while let Some(token) = body_it.next() {
> match token {
> - TokenTree::Ident(ident) if ident.to_string() == "fn" => {
> + TokenTree::Ident(ident) if ident == "fn" => {
> let fn_name = match body_it.next() {
> Some(TokenTree::Ident(ident)) => ident.to_string(),
> // Possibly we've encountered a fn pointer type instead.
> @@ -39,7 +40,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
> };
> functions.push(fn_name);
> }
> - TokenTree::Ident(ident) if ident.to_string() == "const" => {
> + TokenTree::Ident(ident) if ident == "const" => {
> let const_name = match body_it.next() {
> Some(TokenTree::Ident(ident)) => ident.to_string(),
> // Possibly we've encountered an inline const block instead.
> --
> 2.51.2
>
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5281 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn`
2026-01-12 17:07 ` [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
2026-01-17 20:09 ` Benno Lossin
@ 2026-01-19 7:10 ` David Gow
1 sibling, 0 replies; 7+ messages in thread
From: David Gow @ 2026-01-19 7:10 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, Rae Moar, Tamir Duberstein, Igor Korotin,
José Expósito, rust-for-linux, Guilherme Giacomo Simoes,
linux-kselftest, kunit-dev, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 13793 bytes --]
On Tue, 13 Jan 2026 at 01:11, Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> Make use of `syn` to parse the module structurally and thus improve the
> robustness of parsing.
>
> String interpolation is avoided by generating tokens directly using
> `quote!`.
>
> Reviewed-by: Tamir Duberstein <tamird@gmail.com>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
Sorry this took so long to review: it's taken a while for me to wrap
my head around `syn`.
I'm pretty happy with this overall, though, and think I understand it
well enough now.
Reviewed-by: David Gow <davidgow@google.com>
Cheers,
-- David
> rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
> rust/macros/lib.rs | 6 +-
> 2 files changed, 123 insertions(+), 157 deletions(-)
>
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> index 5cd6aa5eef07d..afbc708cbdc50 100644
> --- a/rust/macros/kunit.rs
> +++ b/rust/macros/kunit.rs
> @@ -4,81 +4,50 @@
> //!
> //! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
>
> -use std::collections::HashMap;
> -use std::fmt::Write;
> -
> -use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
> -
> -pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - let attr = attr.to_string();
> -
> - if attr.is_empty() {
> - panic!("Missing test name in `#[kunit_tests(test_name)]` macro")
> - }
I'm assuming it's okay for us to get rid of this as we get an
"unexpected end of input" error instead.
> -
> - if attr.len() > 255 {
> - panic!("The test suite name `{attr}` exceeds the maximum length of 255 bytes")
> +use std::ffi::CString;
> +
> +use proc_macro2::TokenStream;
> +use quote::{
> + format_ident,
> + quote,
> + ToTokens, //
> +};
> +use syn::{
> + parse_quote,
> + Error,
> + Ident,
> + Item,
> + ItemMod,
> + LitCStr,
> + Result, //
> +};
> +
> +pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<TokenStream> {
> + if test_suite.to_string().len() > 255 {
> + return Err(Error::new_spanned(
> + test_suite,
> + "test suite names cannot exceed the maximum length of 255 bytes",
> + ));
> }
>
> - let mut tokens: Vec<_> = ts.into_iter().collect();
> -
> - // Scan for the `mod` keyword.
> - tokens
> - .iter()
> - .find_map(|token| match token {
> - TokenTree::Ident(ident) => match ident.to_string().as_str() {
> - "mod" => Some(true),
> - _ => None,
> - },
> - _ => None,
> - })
> - .expect("`#[kunit_tests(test_name)]` attribute should only be applied to modules");
> -
> - // Retrieve the main body. The main body should be the last token tree.
> - let body = match tokens.pop() {
> - Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
> - _ => panic!("Cannot locate main body of module"),
> + // We cannot handle modules that defer to another file (e.g. `mod foo;`).
> + let Some((module_brace, module_items)) = module.content.take() else {
> + Err(Error::new_spanned(
> + module,
> + "`#[kunit_tests(test_name)]` attribute should only be applied to inline modules",
> + ))?
> };
>
> - // Get the functions set as tests. Search for `[test]` -> `fn`.
> - let mut body_it = body.stream().into_iter();
> - let mut tests = Vec::new();
> - let mut attributes: HashMap<String, TokenStream> = HashMap::new();
> - while let Some(token) = body_it.next() {
> - match token {
> - TokenTree::Punct(ref p) if p.as_char() == '#' => match body_it.next() {
> - Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
> - if let Some(TokenTree::Ident(name)) = g.stream().into_iter().next() {
> - // Collect attributes because we need to find which are tests. We also
> - // need to copy `cfg` attributes so tests can be conditionally enabled.
> - attributes
> - .entry(name.to_string())
> - .or_default()
> - .extend([token, TokenTree::Group(g)]);
> - }
> - continue;
> - }
> - _ => (),
> - },
> - TokenTree::Ident(i) if i == "fn" && attributes.contains_key("test") => {
> - if let Some(TokenTree::Ident(test_name)) = body_it.next() {
> - tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
> - }
> - }
> -
> - _ => (),
> - }
> - attributes.clear();
> - }
> + // Make the entire module gated behind `CONFIG_KUNIT`.
> + module
> + .attrs
> + .insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
>
> - // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
> - let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap();
> - tokens.insert(
> - 0,
> - TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
> - );
> + let mut processed_items = Vec::new();
> + let mut test_cases = Vec::new();
>
> // Generate the test KUnit test suite and a test case for each `#[test]`.
> + //
> // The code generated for the following test module:
> //
> // ```
> @@ -110,98 +79,93 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> //
> // ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
> // ```
> - let mut kunit_macros = "".to_owned();
> - let mut test_cases = "".to_owned();
> - let mut assert_macros = "".to_owned();
> - let path = crate::helpers::file();
> - let num_tests = tests.len();
> - for (test, cfg_attr) in tests {
> - let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
> - // Append any `cfg` attributes the user might have written on their tests so we don't
> - // attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
> - // the length of the assert message.
> - let kunit_wrapper = format!(
> - r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
> - {{
> - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
> - {cfg_attr} {{
> - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
> - use ::kernel::kunit::is_test_result_ok;
> - assert!(is_test_result_ok({test}()));
> + //
> + // Non-function items (e.g. imports) are preserved.
> + for item in module_items {
> + let Item::Fn(mut f) = item else {
> + processed_items.push(item);
> + continue;
> + };
> +
> + // TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
> + let before_len = f.attrs.len();
> + f.attrs.retain(|attr| !attr.path().is_ident("test"));
> + if f.attrs.len() == before_len {
> + processed_items.push(Item::Fn(f));
> + continue;
> + }
> +
> + let test = f.sig.ident.clone();
> +
> + // Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
> + let cfg_attrs: Vec<_> = f
> + .attrs
> + .iter()
> + .filter(|attr| attr.path().is_ident("cfg"))
> + .cloned()
> + .collect();
> +
> + // Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
> + // KUnit instead.
> + let test_str = test.to_string();
> + let path = crate::helpers::file();
> + processed_items.push(parse_quote! {
> + #[allow(unused)]
> + macro_rules! assert {
> + ($cond:expr $(,)?) => {{
> + kernel::kunit_assert!(#test_str, #path, 0, $cond);
> + }}
> + }
> + });
> + processed_items.push(parse_quote! {
> + #[allow(unused)]
> + macro_rules! assert_eq {
> + ($left:expr, $right:expr $(,)?) => {{
> + kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
> }}
> - }}"#,
> + }
> + });
> +
> + // Add back the test item.
> + processed_items.push(Item::Fn(f));
> +
> + let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
> + let test_cstr = LitCStr::new(
> + &CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
> + test.span(),
> );
> - writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
> - writeln!(
> - test_cases,
> - " ::kernel::kunit::kunit_case(::kernel::c_str!(\"{test}\"), {kunit_wrapper_fn_name}),"
> - )
> - .unwrap();
> - writeln!(
> - assert_macros,
> - r#"
> -/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
> -#[allow(unused)]
> -macro_rules! assert {{
> - ($cond:expr $(,)?) => {{{{
> - kernel::kunit_assert!("{test}", "{path}", 0, $cond);
> - }}}}
> -}}
> -
> -/// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
> -#[allow(unused)]
> -macro_rules! assert_eq {{
> - ($left:expr, $right:expr $(,)?) => {{{{
> - kernel::kunit_assert_eq!("{test}", "{path}", 0, $left, $right);
> - }}}}
> -}}
> - "#
> - )
> - .unwrap();
> - }
> + processed_items.push(parse_quote! {
> + unsafe extern "C" fn #kunit_wrapper_fn_name(_test: *mut ::kernel::bindings::kunit) {
> + (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
>
> - writeln!(kunit_macros).unwrap();
> - writeln!(
> - kunit_macros,
> - "static mut TEST_CASES: [::kernel::bindings::kunit_case; {}] = [\n{test_cases} ::kernel::kunit::kunit_case_null(),\n];",
> - num_tests + 1
> - )
> - .unwrap();
> -
> - writeln!(
> - kunit_macros,
> - "::kernel::kunit_unsafe_test_suite!({attr}, TEST_CASES);"
> - )
> - .unwrap();
> -
> - // Remove the `#[test]` macros.
> - // We do this at a token level, in order to preserve span information.
> - let mut new_body = vec![];
> - let mut body_it = body.stream().into_iter();
> -
> - while let Some(token) = body_it.next() {
> - match token {
> - TokenTree::Punct(ref c) if c.as_char() == '#' => match body_it.next() {
> - Some(TokenTree::Group(group)) if group.to_string() == "[test]" => (),
> - Some(next) => {
> - new_body.extend([token, next]);
> - }
> - _ => {
> - new_body.push(token);
> + // Append any `cfg` attributes the user might have written on their tests so we
> + // don't attempt to call them when they are `cfg`'d out. An extra `use` is used
> + // here to reduce the length of the assert message.
> + #(#cfg_attrs)*
> + {
> + (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
> + use ::kernel::kunit::is_test_result_ok;
> + assert!(is_test_result_ok(#test()));
> }
> - },
> - _ => {
> - new_body.push(token);
> }
> - }
> - }
> + });
>
> - let mut final_body = TokenStream::new();
> - final_body.extend::<TokenStream>(assert_macros.parse().unwrap());
> - final_body.extend(new_body);
> - final_body.extend::<TokenStream>(kunit_macros.parse().unwrap());
> -
> - tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, final_body)));
> + test_cases.push(quote!(
> + ::kernel::kunit::kunit_case(#test_cstr, #kunit_wrapper_fn_name)
> + ));
> + }
>
> - tokens.into_iter().collect()
> + let num_tests_plus_1 = test_cases.len() + 1;
> + processed_items.push(parse_quote! {
> + static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
> + #(#test_cases,)*
> + ::kernel::kunit::kunit_case_null(),
> + ];
> + });
> + processed_items.push(parse_quote! {
> + ::kernel::kunit_unsafe_test_suite!(#test_suite, TEST_CASES);
> + });
> +
> + module.content = Some((module_brace, processed_items));
> + Ok(module.to_token_stream())
> }
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 12467bfc703a8..75ac60abe6ffa 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -481,6 +481,8 @@ pub fn paste(input: TokenStream) -> TokenStream {
> /// }
> /// ```
> #[proc_macro_attribute]
> -pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - kunit::kunit_tests(attr.into(), ts.into()).into()
> +pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
> + kunit::kunit_tests(parse_macro_input!(attr), parse_macro_input!(input))
> + .unwrap_or_else(|e| e.into_compile_error())
> + .into()
> }
> --
> 2.51.2
>
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5281 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value
2026-01-12 17:07 ` [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
@ 2026-01-19 7:10 ` David Gow
0 siblings, 0 replies; 7+ messages in thread
From: David Gow @ 2026-01-19 7:10 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, Rae Moar, rust-for-linux, Tamir Duberstein,
linux-kselftest, kunit-dev, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 3693 bytes --]
On Tue, 13 Jan 2026 at 01:12, Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> The last null element can be created (constly) using `pin_init::zeroed`,
> so prefer to use it instead of adding a custom way of building it.
>
> Reviewed-by: Tamir Duberstein <tamird@gmail.com>
> Reviewed-by: Benno Lossin <lossin@kernel.org>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
Reviewed-by: David Gow <davidgow@google.com>
(Do note, however, that this conflicts with
https://lore.kernel.org/rust-for-linux/20251130211233.367946-1-seimun018r@gmail.com/)
Cheers,
-- David
> rust/kernel/kunit.rs | 26 +-------------------------
> rust/macros/kunit.rs | 4 ++--
> 2 files changed, 3 insertions(+), 27 deletions(-)
>
> diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> index 79436509dd73d..034158cdaf06b 100644
> --- a/rust/kernel/kunit.rs
> +++ b/rust/kernel/kunit.rs
> @@ -192,9 +192,6 @@ pub fn is_test_result_ok(t: impl TestResult) -> bool {
> }
>
> /// Represents an individual test case.
> -///
> -/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of valid test cases.
> -/// Use [`kunit_case_null`] to generate such a delimiter.
> #[doc(hidden)]
> pub const fn kunit_case(
> name: &'static kernel::str::CStr,
> @@ -215,27 +212,6 @@ pub const fn kunit_case(
> }
> }
>
> -/// Represents the NULL test case delimiter.
> -///
> -/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of test cases. This
> -/// function returns such a delimiter.
> -#[doc(hidden)]
> -pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
> - kernel::bindings::kunit_case {
> - run_case: None,
> - name: core::ptr::null_mut(),
> - generate_params: None,
> - attr: kernel::bindings::kunit_attributes {
> - speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
> - },
> - status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
> - module_name: core::ptr::null_mut(),
> - log: core::ptr::null_mut(),
> - param_init: None,
> - param_exit: None,
> - }
> -}
> -
> /// Registers a KUnit test suite.
> ///
> /// # Safety
> @@ -254,7 +230,7 @@ pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
> ///
> /// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
> /// kernel::kunit::kunit_case(kernel::c_str!("name"), test_fn),
> -/// kernel::kunit::kunit_case_null(),
> +/// pin_init::zeroed(),
> /// ];
> /// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
> /// ```
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> index afbc708cbdc50..cb29f350d5b2b 100644
> --- a/rust/macros/kunit.rs
> +++ b/rust/macros/kunit.rs
> @@ -74,7 +74,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
> // static mut TEST_CASES: [::kernel::bindings::kunit_case; 3] = [
> // ::kernel::kunit::kunit_case(::kernel::c_str!("foo"), kunit_rust_wrapper_foo),
> // ::kernel::kunit::kunit_case(::kernel::c_str!("bar"), kunit_rust_wrapper_bar),
> - // ::kernel::kunit::kunit_case_null(),
> + // ::pin_init::zeroed(),
> // ];
> //
> // ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
> @@ -159,7 +159,7 @@ macro_rules! assert_eq {
> processed_items.push(parse_quote! {
> static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
> #(#test_cases,)*
> - ::kernel::kunit::kunit_case_null(),
> + ::pin_init::zeroed(),
> ];
> });
> processed_items.push(parse_quote! {
> --
> 2.51.2
>
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5281 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-01-19 7:10 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260112170919.1888584-1-gary@kernel.org>
2026-01-12 17:07 ` [PATCH v3 02/12] rust: macros: use `quote!` from vendored crate Gary Guo
2026-01-19 7:09 ` David Gow
2026-01-12 17:07 ` [PATCH v3 08/12] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
2026-01-17 20:09 ` Benno Lossin
2026-01-19 7:10 ` David Gow
2026-01-12 17:07 ` [PATCH v3 11/12] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2026-01-19 7:10 ` David Gow
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox