* [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 21:50 ` Christian Schrefl
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
` (9 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Fiona Behrens, Christian Schrefl
Cc: rust-for-linux, Tamir Duberstein, linux-kernel
From: Benno Lossin <lossin@kernel.org>
The kernel only had the `proc-macro` library available, whereas the
user-space version also used `proc-macro2` and `quote`. Now both are
available to the kernel, making it possible to remove the workarounds.
Signed-off-by: Benno Lossin <lossin@kernel.org>
Co-developed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/Makefile | 8 +++++---
rust/pin-init/internal/src/helpers.rs | 5 +----
rust/pin-init/internal/src/lib.rs | 16 ----------------
rust/pin-init/internal/src/pin_data.rs | 6 ++----
rust/pin-init/internal/src/pinned_drop.rs | 6 ++----
rust/pin-init/internal/src/zeroable.rs | 6 ++----
scripts/generate_rust_analyzer.py | 2 +-
7 files changed, 13 insertions(+), 36 deletions(-)
diff --git a/rust/Makefile b/rust/Makefile
index 5d357dce1704d..c816ca8663e42 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -212,7 +212,8 @@ rustdoc-ffi: $(src)/ffi.rs rustdoc-core FORCE
rustdoc-pin_init_internal: private rustdoc_host = yes
rustdoc-pin_init_internal: private rustc_target_flags = --cfg kernel \
- --extern proc_macro --crate-type proc-macro
+ --extern proc_macro --extern proc_macro2 --extern quote --extern syn \
+ --crate-type proc-macro
rustdoc-pin_init_internal: $(src)/pin-init/internal/src/lib.rs \
rustdoc-clean FORCE
+$(call if_changed,rustdoc)
@@ -273,7 +274,7 @@ rusttestlib-macros: $(src)/macros/lib.rs \
+$(call if_changed,rustc_test_library)
rusttestlib-pin_init_internal: private rustc_target_flags = --cfg kernel \
- --extern proc_macro
+ --extern proc_macro --extern proc_macro2 --extern quote --extern syn
rusttestlib-pin_init_internal: private rustc_test_library_proc = yes
rusttestlib-pin_init_internal: $(src)/pin-init/internal/src/lib.rs FORCE
+$(call if_changed,rustc_test_library)
@@ -547,7 +548,8 @@ $(obj)/$(libmacros_name): $(src)/macros/lib.rs $(obj)/libproc_macro2.rlib \
$(obj)/libquote.rlib $(obj)/libsyn.rlib FORCE
+$(call if_changed_dep,rustc_procmacro)
-$(obj)/$(libpin_init_internal_name): private rustc_target_flags = --cfg kernel
+$(obj)/$(libpin_init_internal_name): private rustc_target_flags = --cfg kernel \
+ --extern proc_macro2 --extern quote --extern syn
$(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs FORCE
+$(call if_changed_dep,rustc_procmacro)
diff --git a/rust/pin-init/internal/src/helpers.rs b/rust/pin-init/internal/src/helpers.rs
index 236f989a50f2f..1d2dd27c888c8 100644
--- a/rust/pin-init/internal/src/helpers.rs
+++ b/rust/pin-init/internal/src/helpers.rs
@@ -1,9 +1,6 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
-#[cfg(not(kernel))]
-use proc_macro2 as proc_macro;
-
-use proc_macro::{TokenStream, TokenTree};
+use proc_macro2::{TokenStream, TokenTree};
/// Parsed generics.
///
diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs
index 297b0129a5bfd..4c4dc639ce823 100644
--- a/rust/pin-init/internal/src/lib.rs
+++ b/rust/pin-init/internal/src/lib.rs
@@ -7,27 +7,11 @@
//! `pin-init` proc macros.
#![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))]
-// Allow `.into()` to convert
-// - `proc_macro2::TokenStream` into `proc_macro::TokenStream` in the user-space version.
-// - `proc_macro::TokenStream` into `proc_macro::TokenStream` in the kernel version.
-// Clippy warns on this conversion, but it's required by the user-space version.
-//
-// Remove once we have `proc_macro2` in the kernel.
-#![allow(clippy::useless_conversion)]
// Documentation is done in the pin-init crate instead.
#![allow(missing_docs)]
use proc_macro::TokenStream;
-#[cfg(kernel)]
-#[path = "../../../macros/quote.rs"]
-#[macro_use]
-#[cfg_attr(not(kernel), rustfmt::skip)]
-mod quote;
-#[cfg(not(kernel))]
-#[macro_use]
-extern crate quote;
-
mod helpers;
mod pin_data;
mod pinned_drop;
diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
index 87d4a7eb1d35e..12d9b02c38837 100644
--- a/rust/pin-init/internal/src/pin_data.rs
+++ b/rust/pin-init/internal/src/pin_data.rs
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
-#[cfg(not(kernel))]
-use proc_macro2 as proc_macro;
-
use crate::helpers::{parse_generics, Generics};
-use proc_macro::{Group, Punct, Spacing, TokenStream, TokenTree};
+use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree};
+use quote::quote;
pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
// This proc-macro only does some pre-parsing and then delegates the actual parsing to
diff --git a/rust/pin-init/internal/src/pinned_drop.rs b/rust/pin-init/internal/src/pinned_drop.rs
index c4ca7a70b726a..978c2594243ba 100644
--- a/rust/pin-init/internal/src/pinned_drop.rs
+++ b/rust/pin-init/internal/src/pinned_drop.rs
@@ -1,9 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
-#[cfg(not(kernel))]
-use proc_macro2 as proc_macro;
-
-use proc_macro::{TokenStream, TokenTree};
+use proc_macro2::{TokenStream, TokenTree};
+use quote::quote;
pub(crate) fn pinned_drop(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut toks = input.into_iter().collect::<Vec<_>>();
diff --git a/rust/pin-init/internal/src/zeroable.rs b/rust/pin-init/internal/src/zeroable.rs
index e0ed3998445cf..d8a5ef3883f4b 100644
--- a/rust/pin-init/internal/src/zeroable.rs
+++ b/rust/pin-init/internal/src/zeroable.rs
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#[cfg(not(kernel))]
-use proc_macro2 as proc_macro;
-
use crate::helpers::{parse_generics, Generics};
-use proc_macro::{TokenStream, TokenTree};
+use proc_macro2::{TokenStream, TokenTree};
+use quote::quote;
pub(crate) fn parse_zeroable_derive_input(
input: TokenStream,
diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
index 147d0cc940681..d31d938886589 100755
--- a/scripts/generate_rust_analyzer.py
+++ b/scripts/generate_rust_analyzer.py
@@ -123,7 +123,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edit
append_crate(
"pin_init_internal",
srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs",
- [],
+ ["std", "proc_macro", "proc_macro2", "quote", "syn"],
cfg=["kernel"],
is_proc_macro=True,
)
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds
2025-12-11 18:56 ` [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds Gary Guo
@ 2025-12-11 21:50 ` Christian Schrefl
2025-12-15 13:01 ` Gary Guo
0 siblings, 1 reply; 17+ messages in thread
From: Christian Schrefl @ 2025-12-11 21:50 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Fiona Behrens
Cc: rust-for-linux, Tamir Duberstein, linux-kernel
Hi Gary,
On 12/11/25 7:56 PM, Gary Guo wrote:
> From: Benno Lossin <lossin@kernel.org>
>
> The kernel only had the `proc-macro` library available, whereas the
> user-space version also used `proc-macro2` and `quote`. Now both are
> available to the kernel, making it possible to remove the workarounds.
>
> Signed-off-by: Benno Lossin <lossin@kernel.org>
> Co-developed-by: Gary Guo <gary@garyguo.net>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
Its good to see some workarounds/hacks get removed!
Overall seems fine, but maybe the imports should be changed to the
multiline kernel style?
Anyways:
Reviewed-by: Christian Schrefl <chrisi.schrefl@gmail.com>
Cheers
Christian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds
2025-12-11 21:50 ` Christian Schrefl
@ 2025-12-15 13:01 ` Gary Guo
2025-12-16 9:43 ` Benno Lossin
0 siblings, 1 reply; 17+ messages in thread
From: Gary Guo @ 2025-12-15 13:01 UTC (permalink / raw)
To: Christian Schrefl
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Fiona Behrens, rust-for-linux, Tamir Duberstein, linux-kernel
On Thu, 11 Dec 2025 22:50:35 +0100
Christian Schrefl <chrisi.schrefl@gmail.com> wrote:
> Hi Gary,
>
> On 12/11/25 7:56 PM, Gary Guo wrote:
> > From: Benno Lossin <lossin@kernel.org>
> >
> > The kernel only had the `proc-macro` library available, whereas the
> > user-space version also used `proc-macro2` and `quote`. Now both are
> > available to the kernel, making it possible to remove the workarounds.
> >
> > Signed-off-by: Benno Lossin <lossin@kernel.org>
> > Co-developed-by: Gary Guo <gary@garyguo.net>
> > Signed-off-by: Gary Guo <gary@garyguo.net>
> > ---
>
> Its good to see some workarounds/hacks get removed!
>
> Overall seems fine, but maybe the imports should be changed to the
> multiline kernel style?
>
> Anyways:
>
> Reviewed-by: Christian Schrefl <chrisi.schrefl@gmail.com>
`pin-init` is its own subproject as it's also intended for userspace (or
other baremetal/embedded users). I'll let Benno to decide if he would like
to use kernel multiline import for `pin-init`.
The rest of this series convert files to use kernel import style when
depednencies are changed.
Best,
Gary
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds
2025-12-15 13:01 ` Gary Guo
@ 2025-12-16 9:43 ` Benno Lossin
0 siblings, 0 replies; 17+ messages in thread
From: Benno Lossin @ 2025-12-16 9:43 UTC (permalink / raw)
To: Gary Guo, Christian Schrefl
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Fiona Behrens,
rust-for-linux, Tamir Duberstein, linux-kernel
On Mon Dec 15, 2025 at 2:01 PM CET, Gary Guo wrote:
> On Thu, 11 Dec 2025 22:50:35 +0100
> Christian Schrefl <chrisi.schrefl@gmail.com> wrote:
>
>> Hi Gary,
>>
>> On 12/11/25 7:56 PM, Gary Guo wrote:
>> > From: Benno Lossin <lossin@kernel.org>
>> >
>> > The kernel only had the `proc-macro` library available, whereas the
>> > user-space version also used `proc-macro2` and `quote`. Now both are
>> > available to the kernel, making it possible to remove the workarounds.
>> >
>> > Signed-off-by: Benno Lossin <lossin@kernel.org>
>> > Co-developed-by: Gary Guo <gary@garyguo.net>
>> > Signed-off-by: Gary Guo <gary@garyguo.net>
>> > ---
>>
>> Its good to see some workarounds/hacks get removed!
>>
>> Overall seems fine, but maybe the imports should be changed to the
>> multiline kernel style?
>>
>> Anyways:
>>
>> Reviewed-by: Christian Schrefl <chrisi.schrefl@gmail.com>
>
> `pin-init` is its own subproject as it's also intended for userspace (or
> other baremetal/embedded users). I'll let Benno to decide if he would like
> to use kernel multiline import for `pin-init`.
I haven't thought about this too much yet. In pin-init, I rarely get
merge conflicts (in fact I don't recall any time when I had one), since
I author most of the patches. Now if the kernel were to change the
rustfmt config, then I'd change it as well.
If I convert it, then I will do so in a separate series as well.
Cheers,
Benno
> The rest of this series convert files to use kernel import style when
> depednencies are changed.
>
> Best,
> Gary
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 02/11] rust: macros: use `quote!` from vendored crate
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
2025-12-11 18:56 ` [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-16 9:47 ` Benno Lossin
2025-12-11 18:56 ` [PATCH 03/11] rust: macros: convert `#[vtable]` macro to use `syn` Gary Guo
` (8 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 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,
Greg Kroah-Hartman, Tamir Duberstein, Fiona Behrens, Igor Korotin,
José Expósito, Guilherme Giacomo Simoes
Cc: rust-for-linux, linux-kernel, linux-kselftest, kunit-dev
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.
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 | 2 +-
rust/macros/kunit.rs | 3 +-
rust/macros/lib.rs | 21 ++--
rust/macros/module.rs | 6 +-
rust/macros/paste.rs | 2 +-
rust/macros/quote.rs | 182 -----------------------------------
rust/macros/vtable.rs | 3 +-
10 files changed, 28 insertions(+), 201 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 e2602be402c10..853527b5d9567 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() {
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index b395bb0536959..7427c17ee5f5c 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();
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 793f712dbf7c0..8e0f49ac783b2 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;
@@ -101,7 +99,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.
@@ -176,7 +174,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.
@@ -199,7 +197,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`].
@@ -217,7 +215,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.
@@ -275,7 +273,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.
@@ -413,9 +411,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.
@@ -442,5 +443,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 8cef6cc958b54..6974fb04f58fa 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..f209216785745 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();
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 02/11] rust: macros: use `quote!` from vendored crate
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2025-12-16 9:47 ` Benno Lossin
0 siblings, 0 replies; 17+ messages in thread
From: Benno Lossin @ 2025-12-16 9:47 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, Greg Kroah-Hartman,
Tamir Duberstein, Fiona Behrens, Igor Korotin,
José Expósito, Guilherme Giacomo Simoes
Cc: rust-for-linux, linux-kernel, linux-kselftest, kunit-dev
On Thu Dec 11, 2025 at 7:56 PM CET, Gary Guo 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.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
> ---
> rust/macros/concat_idents.rs | 2 +-
> rust/macros/export.rs | 4 +-
> rust/macros/fmt.rs | 4 +-
> rust/macros/helpers.rs | 2 +-
> rust/macros/kunit.rs | 3 +-
> rust/macros/lib.rs | 21 ++--
> rust/macros/module.rs | 6 +-
> rust/macros/paste.rs | 2 +-
> rust/macros/quote.rs | 182 -----------------------------------
> rust/macros/vtable.rs | 3 +-
> 10 files changed, 28 insertions(+), 201 deletions(-)
> delete mode 100644 rust/macros/quote.rs
> @@ -275,7 +273,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.
> @@ -413,9 +411,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()
There is no `FromIterator` impl on `proc_macro::TokenStream`? Sad :(
Cheers,
Benno
> }
>
> /// Registers a KUnit test suite and its test cases using a user-space like syntax.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 03/11] rust: macros: convert `#[vtable]` macro to use `syn`
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
2025-12-11 18:56 ` [PATCH 01/11] rust: pin-init: internal: remove proc-macro[2] and quote workarounds Gary Guo
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
` (7 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guilherme Giacomo Simoes,
José Expósito, Tamir Duberstein
Cc: rust-for-linux, linux-kernel
From: Gary Guo <gary@garyguo.net>
`#[vtable]` is converted to use syn. This is more robust than the
previous heuristic-based searching of defined methods and functions.
When doing so, the trait and impl are split into two code paths as the
types are distinct when parsed by `syn`.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/lib.rs | 9 ++-
rust/macros/vtable.rs | 155 +++++++++++++++++++++---------------------
2 files changed, 85 insertions(+), 79 deletions(-)
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 8e0f49ac783b2..33935b38d11c0 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -22,6 +22,8 @@
use proc_macro::TokenStream;
+use syn::parse_macro_input;
+
/// Declares a kernel module.
///
/// The `type` argument should be a type which implements the [`Module`]
@@ -173,8 +175,11 @@ 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.into(), ts.into()).into()
+pub fn vtable(attr: TokenStream, input: TokenStream) -> TokenStream {
+ parse_macro_input!(attr as syn::parse::Nothing);
+ vtable::vtable(parse_macro_input!(input))
+ .unwrap_or_else(|e| e.into_compile_error())
+ .into()
}
/// Export a function so that C code can call it via a header file.
diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
index f209216785745..51a7990fd51f3 100644
--- a/rust/macros/vtable.rs
+++ b/rust/macros/vtable.rs
@@ -1,97 +1,98 @@
// SPDX-License-Identifier: GPL-2.0
use std::collections::HashSet;
-use std::fmt::Write;
-use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+use proc_macro2::{Ident, TokenStream};
+use quote::ToTokens;
+use syn::{parse_quote, Error, ImplItem, Item, ItemImpl, ItemTrait, Result, TraitItem};
-pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
- let mut tokens: Vec<_> = ts.into_iter().collect();
-
- // Scan for the `trait` or `impl` keyword.
- let is_trait = tokens
- .iter()
- .find_map(|token| match token {
- TokenTree::Ident(ident) => match ident.to_string().as_str() {
- "trait" => Some(true),
- "impl" => Some(false),
- _ => None,
- },
- _ => None,
- })
- .expect("#[vtable] attribute should only be applied to trait or impl block");
-
- // 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 trait or impl block"),
- };
-
- let mut body_it = body.stream().into_iter();
+fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
let mut functions = Vec::new();
let mut consts = HashSet::new();
- while let Some(token) = body_it.next() {
- match token {
- TokenTree::Ident(ident) if ident.to_string() == "fn" => {
- let fn_name = match body_it.next() {
- Some(TokenTree::Ident(ident)) => ident.to_string(),
- // Possibly we've encountered a fn pointer type instead.
- _ => continue,
- };
- functions.push(fn_name);
+ for item in &item.items {
+ match item {
+ TraitItem::Fn(fn_item) => {
+ functions.push(fn_item.sig.ident.clone());
}
- TokenTree::Ident(ident) if ident.to_string() == "const" => {
- let const_name = match body_it.next() {
- Some(TokenTree::Ident(ident)) => ident.to_string(),
- // Possibly we've encountered an inline const block instead.
- _ => continue,
- };
- consts.insert(const_name);
+ TraitItem::Const(const_item) => {
+ consts.insert(const_item.ident.clone());
}
- _ => (),
+ _ => {}
}
}
- let mut const_items;
- if is_trait {
- const_items = "
- /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
- /// attribute when implementing this trait.
- const USE_VTABLE_ATTR: ();
- "
- .to_owned();
+ item.items.push(parse_quote! {
+ /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
+ /// attribute when implementing this trait.
+ const USE_VTABLE_ATTR: ();
+ });
- for f in functions {
- let gen_const_name = format!("HAS_{}", f.to_uppercase());
- // Skip if it's declared already -- this allows user override.
- if consts.contains(&gen_const_name) {
- continue;
- }
- // We don't know on the implementation-site whether a method is required or provided
- // so we have to generate a const for all methods.
- write!(
- const_items,
- "/// Indicates if the `{f}` method is overridden by the implementor.
- const {gen_const_name}: bool = false;",
- )
- .unwrap();
- consts.insert(gen_const_name);
+ for name in functions {
+ let gen_const_name = Ident::new(
+ &format!("HAS_{}", name.to_string().to_uppercase()),
+ name.span(),
+ );
+ // Skip if it's declared already -- this allows user override.
+ if consts.contains(&gen_const_name) {
+ continue;
}
- } else {
- const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
+ // We don't know on the implementation-site whether a method is required or provided
+ // so we have to generate a const for all methods.
+ let comment = format!("Indicates if the `{name}` method is overridden by the implementor.");
+ item.items.push(parse_quote! {
+ #[doc = #comment]
+ const #gen_const_name: bool = false;
+ });
+ consts.insert(gen_const_name);
+ }
- for f in functions {
- let gen_const_name = format!("HAS_{}", f.to_uppercase());
- if consts.contains(&gen_const_name) {
- continue;
+ Ok(item)
+}
+
+fn handle_impl(mut item: ItemImpl) -> Result<ItemImpl> {
+ let mut functions = Vec::new();
+ let mut consts = HashSet::new();
+ for item in &item.items {
+ match item {
+ ImplItem::Fn(fn_item) => {
+ functions.push(fn_item.sig.ident.clone());
}
- write!(const_items, "const {gen_const_name}: bool = true;").unwrap();
+ ImplItem::Const(const_item) => {
+ consts.insert(const_item.ident.clone());
+ }
+ _ => {}
+ }
+ }
+
+ item.items.push(parse_quote! {
+ const USE_VTABLE_ATTR: () = ();
+ });
+
+ for name in functions {
+ let gen_const_name = Ident::new(
+ &format!("HAS_{}", name.to_string().to_uppercase()),
+ name.span(),
+ );
+ // Skip if it's declared already -- this allows user override.
+ if consts.contains(&gen_const_name) {
+ continue;
}
+ item.items.push(parse_quote! {
+ const #gen_const_name: bool = true;
+ });
+ consts.insert(gen_const_name);
}
- let new_body = vec![const_items.parse().unwrap(), body.stream()]
- .into_iter()
- .collect();
- tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
- tokens.into_iter().collect()
+ Ok(item)
+}
+
+pub(crate) fn vtable(input: Item) -> Result<TokenStream> {
+ match input {
+ Item::Trait(item) => Ok(handle_trait(item)?.into_token_stream()),
+ Item::Impl(item) => Ok(handle_impl(item)?.into_token_stream()),
+ _ => Err(Error::new_spanned(
+ input,
+ "`#[vtable]` attribute should only be applied to trait or impl block",
+ ))?,
+ }
}
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 04/11] rust: macros: use `syn` to parse `module!` macro
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (2 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 03/11] rust: macros: convert `#[vtable]` macro to use `syn` Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 05/11] rust: macros: use `quote!` for " Gary Guo
` (6 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Fiona Behrens, Tamir Duberstein, Patrick Miller,
José Expósito, Greg Kroah-Hartman,
Guilherme Giacomo Simoes
Cc: rust-for-linux, Igor Korotin, linux-kernel
From: Gary Guo <gary@garyguo.net>
With `syn` being available in the kernel, use it to parse the complex
custom `module!` macro to replace existing helpers. Only parsing is
changed in this commit, the code generation is untouched.
This has the benefit of better error message when the macro is used
incorrectly, as it can point to a concrete span on what's going wrong.
For example, if a field is specified twice, previously it reads:
error: proc macro panicked
--> samples/rust/rust_minimal.rs:7:1
|
7 | / module! {
8 | | type: RustMinimal,
9 | | name: "rust_minimal",
10 | | author: "Rust for Linux Contributors",
11 | | description: "Rust minimal sample",
12 | | license: "GPL",
13 | | license: "GPL",
14 | | }
| |_^
|
= help: message: Duplicated key "license". Keys can only be specified once.
now it reads:
error: duplicated key "license". Keys can only be specified once.
--> samples/rust/rust_minimal.rs:13:5
|
13 | license: "GPL",
| ^^^^^^^
Co-developed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/helpers.rs | 84 ++++++----------
rust/macros/lib.rs | 9 +-
rust/macros/module.rs | 220 ++++++++++++++++++++++++++++++-----------
3 files changed, 202 insertions(+), 111 deletions(-)
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 853527b5d9567..f6bbdb02d9d7d 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,42 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
-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() {
- Some(ident.to_string())
- } else {
- None
- }
-}
-
-pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
- if let Some(TokenTree::Literal(literal)) = it.next() {
- Some(literal.to_string())
- } else {
- None
- }
-}
-
-pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
- try_literal(it).and_then(|string| {
- if string.starts_with('\"') && string.ends_with('\"') {
- let content = &string[1..string.len() - 1];
- if content.contains('\\') {
- panic!("Escape sequences in string literals not yet handled");
- }
- Some(content.to_string())
- } else if string.starts_with("r\"") {
- panic!("Raw string literals are not yet handled");
- } else {
- None
- }
- })
-}
-
-pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
- try_ident(it).expect("Expected Ident")
-}
+use proc_macro2::{
+ token_stream,
+ Ident,
+ TokenStream,
+ TokenTree, //
+};
+use quote::ToTokens;
+use syn::{
+ parse::{
+ Parse,
+ ParseStream, //
+ },
+ Error,
+ LitStr,
+ Result, //
+};
pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
@@ -46,27 +25,28 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
}
}
-pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
- try_string(it).expect("Expected string")
-}
+/// A string literal that is required to have ASCII value only.
+pub(crate) struct AsciiLitStr(LitStr);
-pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
- let string = try_string(it).expect("Expected string");
- assert!(string.is_ascii(), "Expected ASCII string");
- string
+impl Parse for AsciiLitStr {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let s: LitStr = input.parse()?;
+ if !s.value().is_ascii() {
+ return Err(Error::new_spanned(s, "expected ASCII-only string literal"));
+ }
+ Ok(Self(s))
+ }
}
-pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
- if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
- group
- } else {
- panic!("Expected Group");
+impl ToTokens for AsciiLitStr {
+ fn to_tokens(&self, ts: &mut TokenStream) {
+ self.0.to_tokens(ts);
}
}
-pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
- if it.next().is_some() {
- panic!("Expected end");
+impl AsciiLitStr {
+ pub(crate) fn value(&self) -> String {
+ self.0.value()
}
}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 33935b38d11c0..4e440deaed853 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -10,6 +10,9 @@
// which was added in Rust 1.88.0. This is why `cfg_attr` is used here, i.e.
// 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))]
+//
+// Stable since Rust 1.81.0.
+#![feature(lint_reasons)]
mod concat_idents;
mod export;
@@ -100,8 +103,10 @@
/// - `firmware`: array of ASCII string literals of the firmware files of
/// the kernel module.
#[proc_macro]
-pub fn module(ts: TokenStream) -> TokenStream {
- module::module(ts.into()).into()
+pub fn module(input: TokenStream) -> TokenStream {
+ module::module(parse_macro_input!(input))
+ .unwrap_or_else(|e| e.into_compile_error())
+ .into()
}
/// Declares or implements a vtable trait.
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 6974fb04f58fa..e4d51878a5360 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -2,28 +2,27 @@
use std::fmt::Write;
-use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+use proc_macro2::{
+ Literal,
+ TokenStream, //
+};
+use syn::{
+ bracketed,
+ parse::{
+ Parse,
+ ParseStream, //
+ },
+ punctuated::Punctuated,
+ token::Bracket,
+ Error,
+ Ident,
+ LitStr,
+ Result,
+ Token, //
+};
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);
- let mut values = Vec::new();
- let mut it = group.stream().into_iter();
-
- while let Some(val) = try_string(&mut it) {
- assert!(val.is_ascii(), "Expected ASCII string");
- values.push(val);
- match it.next() {
- Some(TokenTree::Punct(punct)) => assert_eq!(punct.as_char(), ','),
- None => break,
- _ => panic!("Expected ',' or end of array"),
- }
- }
- values
-}
-
struct ModInfoBuilder<'a> {
module: &'a str,
counter: usize,
@@ -91,8 +90,107 @@ fn emit(&mut self, field: &str, content: &str) {
}
}
+mod kw {
+ syn::custom_keyword!(name);
+ syn::custom_keyword!(authors);
+ syn::custom_keyword!(description);
+ syn::custom_keyword!(license);
+ syn::custom_keyword!(alias);
+ syn::custom_keyword!(firmware);
+}
+
+#[allow(dead_code, reason = "some fields are only parsed into")]
+enum ModInfoField {
+ Type(Token![type], Token![:], Ident),
+ Name(kw::name, Token![:], AsciiLitStr),
+ Authors(
+ kw::authors,
+ Token![:],
+ Bracket,
+ Punctuated<LitStr, Token![,]>,
+ ),
+ Description(kw::description, Token![:], LitStr),
+ License(kw::license, Token![:], AsciiLitStr),
+ Alias(
+ kw::authors,
+ Token![:],
+ Bracket,
+ Punctuated<LitStr, Token![,]>,
+ ),
+ Firmware(
+ kw::firmware,
+ Token![:],
+ Bracket,
+ Punctuated<LitStr, Token![,]>,
+ ),
+}
+
+impl ModInfoField {
+ /// Obtain the key identifying the field.
+ fn key(&self) -> Ident {
+ match self {
+ ModInfoField::Type(key, ..) => Ident::new("type", key.span),
+ ModInfoField::Name(key, ..) => Ident::new("name", key.span),
+ ModInfoField::Authors(key, ..) => Ident::new("authors", key.span),
+ ModInfoField::Description(key, ..) => Ident::new("description", key.span),
+ ModInfoField::License(key, ..) => Ident::new("license", key.span),
+ ModInfoField::Alias(key, ..) => Ident::new("alias", key.span),
+ ModInfoField::Firmware(key, ..) => Ident::new("firmware", key.span),
+ }
+ }
+}
+
+impl Parse for ModInfoField {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let key = input.lookahead1();
+ if key.peek(Token![type]) {
+ Ok(Self::Type(input.parse()?, input.parse()?, input.parse()?))
+ } else if key.peek(kw::name) {
+ Ok(Self::Name(input.parse()?, input.parse()?, input.parse()?))
+ } else if key.peek(kw::authors) {
+ let list;
+ Ok(Self::Authors(
+ input.parse()?,
+ input.parse()?,
+ bracketed!(list in input),
+ Punctuated::parse_terminated(&list)?,
+ ))
+ } else if key.peek(kw::description) {
+ Ok(Self::Description(
+ input.parse()?,
+ input.parse()?,
+ input.parse()?,
+ ))
+ } else if key.peek(kw::license) {
+ Ok(Self::License(
+ input.parse()?,
+ input.parse()?,
+ input.parse()?,
+ ))
+ } else if key.peek(kw::alias) {
+ let list;
+ Ok(Self::Alias(
+ input.parse()?,
+ input.parse()?,
+ bracketed!(list in input),
+ Punctuated::parse_terminated(&list)?,
+ ))
+ } else if key.peek(kw::firmware) {
+ let list;
+ Ok(Self::Firmware(
+ input.parse()?,
+ input.parse()?,
+ bracketed!(list in input),
+ Punctuated::parse_terminated(&list)?,
+ ))
+ } else {
+ Err(key.error())
+ }
+ }
+}
+
#[derive(Debug, Default)]
-struct ModuleInfo {
+pub(crate) struct ModuleInfo {
type_: String,
license: String,
name: String,
@@ -102,9 +200,13 @@ struct ModuleInfo {
firmware: Option<Vec<String>>,
}
-impl ModuleInfo {
- fn parse(it: &mut token_stream::IntoIter) -> Self {
- let mut info = ModuleInfo::default();
+impl Parse for ModuleInfo {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let mut info = Self::default();
+
+ let span = input.span();
+ let fields = Punctuated::<ModInfoField, Token![,]>::parse_terminated(input)?;
+ let mut errors = Vec::new();
const EXPECTED_KEYS: &[&str] = &[
"type",
@@ -118,40 +220,38 @@ fn parse(it: &mut token_stream::IntoIter) -> Self {
const REQUIRED_KEYS: &[&str] = &["type", "name", "license"];
let mut seen_keys = Vec::new();
- loop {
- let key = match it.next() {
- Some(TokenTree::Ident(ident)) => ident.to_string(),
- Some(_) => panic!("Expected Ident or end"),
- None => break,
- };
+ for field in fields {
+ let key = field.key();
if seen_keys.contains(&key) {
- panic!("Duplicated key \"{key}\". Keys can only be specified once.");
+ errors.push(Error::new_spanned(
+ &key,
+ format!(r#"duplicated key "{key}". Keys can only be specified once."#),
+ ));
+ continue;
}
+ seen_keys.push(key);
- assert_eq!(expect_punct(it), ':');
-
- match key.as_str() {
- "type" => info.type_ = expect_ident(it),
- "name" => info.name = expect_string_ascii(it),
- "authors" => info.authors = Some(expect_string_array(it)),
- "description" => info.description = Some(expect_string(it)),
- "license" => info.license = expect_string_ascii(it),
- "alias" => info.alias = Some(expect_string_array(it)),
- "firmware" => info.firmware = Some(expect_string_array(it)),
- _ => panic!("Unknown key \"{key}\". Valid keys are: {EXPECTED_KEYS:?}."),
+ match field {
+ ModInfoField::Type(_, _, ty) => info.type_ = ty.to_string(),
+ ModInfoField::Name(_, _, name) => info.name = name.value(),
+ ModInfoField::Authors(_, _, _, list) => {
+ info.authors = Some(list.into_iter().map(|x| x.value()).collect())
+ }
+ ModInfoField::Description(_, _, desc) => info.description = Some(desc.value()),
+ ModInfoField::License(_, _, license) => info.license = license.value(),
+ ModInfoField::Alias(_, _, _, list) => {
+ info.alias = Some(list.into_iter().map(|x| x.value()).collect())
+ }
+ ModInfoField::Firmware(_, _, _, list) => {
+ info.firmware = Some(list.into_iter().map(|x| x.value()).collect())
+ }
}
-
- assert_eq!(expect_punct(it), ',');
-
- seen_keys.push(key);
}
- expect_end(it);
-
for key in REQUIRED_KEYS {
if !seen_keys.iter().any(|e| e == key) {
- panic!("Missing required key \"{key}\".");
+ errors.push(Error::new(span, format!(r#"missing required key "{key}""#)));
}
}
@@ -163,18 +263,24 @@ fn parse(it: &mut token_stream::IntoIter) -> Self {
}
if seen_keys != ordered_keys {
- panic!("Keys are not ordered as expected. Order them like: {ordered_keys:?}.");
+ errors.push(Error::new(
+ span,
+ format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
+ ));
+ }
+
+ if let Some(err) = errors.into_iter().reduce(|mut e1, e2| {
+ e1.combine(e2);
+ e1
+ }) {
+ return Err(err);
}
- info
+ Ok(info)
}
}
-pub(crate) fn module(ts: TokenStream) -> TokenStream {
- let mut it = ts.into_iter();
-
- let info = ModuleInfo::parse(&mut it);
-
+pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
// Rust does not allow hyphens in identifiers, use underscore instead.
let ident = info.name.replace('-', "_");
let mut modinfo = ModInfoBuilder::new(ident.as_ref());
@@ -203,7 +309,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable");
modinfo.emit_only_builtin("file", &file);
- format!(
+ Ok(format!(
"
/// The module name.
///
@@ -377,5 +483,5 @@ unsafe fn __exit() {{
initcall_section = ".initcall6.init"
)
.parse()
- .expect("Error parsing formatted string into token stream.")
+ .expect("Error parsing formatted string into token stream."))
}
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 05/11] rust: macros: use `quote!` for `module!` macro
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (3 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 06/11] rust: macros: convert `#[export]` to use `syn` Gary Guo
` (5 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Tamir Duberstein,
Guilherme Giacomo Simoes
Cc: rust-for-linux, linux-kernel
From: Gary Guo <gary@garyguo.net>
This has no behavioural change, but is good for maintainability. With
`quote!`, we're no longer using string templates, so we don't need to
quote " and {} inside the template anymore. Further more, editors can
now highlight the code template.
This also improves the robustness as it eliminates the need for string
quoting and escaping.
Co-developed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/Makefile | 8 +-
rust/macros/module.rs | 427 ++++++++++++++++++++++--------------------
2 files changed, 229 insertions(+), 206 deletions(-)
diff --git a/rust/Makefile b/rust/Makefile
index c816ca8663e42..96f0ac53ec0e8 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -215,7 +215,7 @@ rustdoc-pin_init_internal: private rustc_target_flags = --cfg kernel \
--extern proc_macro --extern proc_macro2 --extern quote --extern syn \
--crate-type proc-macro
rustdoc-pin_init_internal: $(src)/pin-init/internal/src/lib.rs \
- rustdoc-clean FORCE
+ rustdoc-clean rustdoc-proc_macro2 rustdoc-quote rustdoc-syn FORCE
+$(call if_changed,rustdoc)
rustdoc-pin_init: private rustdoc_host = yes
@@ -276,7 +276,8 @@ rusttestlib-macros: $(src)/macros/lib.rs \
rusttestlib-pin_init_internal: private rustc_target_flags = --cfg kernel \
--extern proc_macro --extern proc_macro2 --extern quote --extern syn
rusttestlib-pin_init_internal: private rustc_test_library_proc = yes
-rusttestlib-pin_init_internal: $(src)/pin-init/internal/src/lib.rs FORCE
+rusttestlib-pin_init_internal: $(src)/pin-init/internal/src/lib.rs \
+ rusttestlib-proc_macro2 rusttestlib-quote rusttestlib-syn FORCE
+$(call if_changed,rustc_test_library)
rusttestlib-pin_init: private rustc_target_flags = --extern pin_init_internal \
@@ -550,7 +551,8 @@ $(obj)/$(libmacros_name): $(src)/macros/lib.rs $(obj)/libproc_macro2.rlib \
$(obj)/$(libpin_init_internal_name): private rustc_target_flags = --cfg kernel \
--extern proc_macro2 --extern quote --extern syn
-$(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs FORCE
+$(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs \
+ $(obj)/libproc_macro2.rlib $(obj)/libquote.rlib $(obj)/libsyn.rlib FORCE
+$(call if_changed_dep,rustc_procmacro)
quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L $@
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index e4d51878a5360..4a02aadef25a7 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,11 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
-use std::fmt::Write;
+use std::ffi::CString;
use proc_macro2::{
Literal,
+ Span,
TokenStream, //
};
+use quote::{
+ format_ident,
+ quote, //
+};
use syn::{
bracketed,
parse::{
@@ -26,7 +31,7 @@
struct ModInfoBuilder<'a> {
module: &'a str,
counter: usize,
- buffer: String,
+ ts: TokenStream,
}
impl<'a> ModInfoBuilder<'a> {
@@ -34,7 +39,7 @@ fn new(module: &'a str) -> Self {
ModInfoBuilder {
module,
counter: 0,
- buffer: String::new(),
+ ts: TokenStream::new(),
}
}
@@ -51,27 +56,26 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) {
// Loadable modules' modinfo strings go as-is.
format!("{field}={content}\0")
};
+ let length = string.len();
+ let string = Literal::byte_string(string.as_bytes());
+ let cfg = if builtin {
+ quote!(#[cfg(not(MODULE))])
+ } else {
+ quote!(#[cfg(MODULE)])
+ };
- write!(
- &mut self.buffer,
- "
- {cfg}
- #[doc(hidden)]
- #[cfg_attr(not(target_os = \"macos\"), link_section = \".modinfo\")]
- #[used(compiler)]
- pub static __{module}_{counter}: [u8; {length}] = *{string};
- ",
- cfg = if builtin {
- "#[cfg(not(MODULE))]"
- } else {
- "#[cfg(MODULE)]"
- },
+ let counter = format_ident!(
+ "__{module}_{counter}",
module = self.module.to_uppercase(),
- counter = self.counter,
- length = string.len(),
- string = Literal::byte_string(string.as_bytes()),
- )
- .unwrap();
+ counter = self.counter
+ );
+ self.ts.extend(quote! {
+ #cfg
+ #[doc(hidden)]
+ #[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
+ #[used(compiler)]
+ pub static #counter: [u8; #length] = *#string;
+ });
self.counter += 1;
}
@@ -281,24 +285,36 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
}
pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
+ let ModuleInfo {
+ type_,
+ license,
+ name,
+ authors,
+ description,
+ alias,
+ firmware,
+ } = info;
+
+ let type_ = Ident::new(&type_, Span::mixed_site());
+
// Rust does not allow hyphens in identifiers, use underscore instead.
- let ident = info.name.replace('-', "_");
+ let ident = name.replace('-', "_");
let mut modinfo = ModInfoBuilder::new(ident.as_ref());
- if let Some(authors) = info.authors {
+ if let Some(authors) = authors {
for author in authors {
modinfo.emit("author", &author);
}
}
- if let Some(description) = info.description {
+ if let Some(description) = description {
modinfo.emit("description", &description);
}
- modinfo.emit("license", &info.license);
- if let Some(aliases) = info.alias {
+ modinfo.emit("license", &license);
+ if let Some(aliases) = alias {
for alias in aliases {
modinfo.emit("alias", &alias);
}
}
- if let Some(firmware) = info.firmware {
+ if let Some(firmware) = firmware {
for fw in firmware {
modinfo.emit("firmware", &fw);
}
@@ -309,179 +325,184 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable");
modinfo.emit_only_builtin("file", &file);
- Ok(format!(
- "
- /// The module name.
- ///
- /// Used by the printing macros, e.g. [`info!`].
- const __LOG_PREFIX: &[u8] = b\"{name}\\0\";
-
- // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
- // freed until the module is unloaded.
- #[cfg(MODULE)]
- static THIS_MODULE: ::kernel::ThisModule = unsafe {{
- extern \"C\" {{
- static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
- }}
-
- ::kernel::ThisModule::from_ptr(__this_module.get())
- }};
- #[cfg(not(MODULE))]
- static THIS_MODULE: ::kernel::ThisModule = unsafe {{
- ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
- }};
-
- /// The `LocalModule` type is the type of the module created by `module!`,
- /// `module_pci_driver!`, `module_platform_driver!`, etc.
- type LocalModule = {type_};
-
- impl ::kernel::ModuleMetadata for {type_} {{
- const NAME: &'static ::kernel::str::CStr = c\"{name}\";
- }}
-
- // Double nested modules, since then nobody can access the public items inside.
- mod __module_init {{
- mod __module_init {{
- use super::super::{type_};
- use pin_init::PinInit;
-
- /// The \"Rust loadable module\" mark.
- //
- // This may be best done another way later on, e.g. as a new modinfo
- // key or a new section. For the moment, keep it simple.
- #[cfg(MODULE)]
- #[doc(hidden)]
- #[used(compiler)]
- static __IS_RUST_MODULE: () = ();
-
- static mut __MOD: ::core::mem::MaybeUninit<{type_}> =
- ::core::mem::MaybeUninit::uninit();
-
- // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
- /// # Safety
- ///
- /// This function must not be called after module initialization, because it may be
- /// freed after that completes.
- #[cfg(MODULE)]
- #[doc(hidden)]
- #[no_mangle]
- #[link_section = \".init.text\"]
- pub unsafe extern \"C\" fn init_module() -> ::kernel::ffi::c_int {{
- // SAFETY: This function is inaccessible to the outside due to the double
- // module wrapping it. It is called exactly once by the C side via its
- // unique name.
- unsafe {{ __init() }}
- }}
-
- #[cfg(MODULE)]
- #[doc(hidden)]
- #[used(compiler)]
- #[link_section = \".init.data\"]
- static __UNIQUE_ID___addressable_init_module: unsafe extern \"C\" fn() -> i32 = init_module;
-
- #[cfg(MODULE)]
- #[doc(hidden)]
- #[no_mangle]
- #[link_section = \".exit.text\"]
- pub extern \"C\" fn cleanup_module() {{
- // SAFETY:
- // - This function is inaccessible to the outside due to the double
- // module wrapping it. It is called exactly once by the C side via its
- // unique name,
- // - furthermore it is only called after `init_module` has returned `0`
- // (which delegates to `__init`).
- unsafe {{ __exit() }}
- }}
-
- #[cfg(MODULE)]
- #[doc(hidden)]
- #[used(compiler)]
- #[link_section = \".exit.data\"]
- static __UNIQUE_ID___addressable_cleanup_module: extern \"C\" fn() = cleanup_module;
-
- // Built-in modules are initialized through an initcall pointer
- // and the identifiers need to be unique.
- #[cfg(not(MODULE))]
- #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
- #[doc(hidden)]
- #[link_section = \"{initcall_section}\"]
- #[used(compiler)]
- pub static __{ident}_initcall: extern \"C\" fn() ->
- ::kernel::ffi::c_int = __{ident}_init;
-
- #[cfg(not(MODULE))]
- #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
- ::core::arch::global_asm!(
- r#\".section \"{initcall_section}\", \"a\"
- __{ident}_initcall:
- .long __{ident}_init - .
- .previous
- \"#
- );
-
- #[cfg(not(MODULE))]
- #[doc(hidden)]
- #[no_mangle]
- pub extern \"C\" fn __{ident}_init() -> ::kernel::ffi::c_int {{
- // SAFETY: This function is inaccessible to the outside due to the double
- // module wrapping it. It is called exactly once by the C side via its
- // placement above in the initcall section.
- unsafe {{ __init() }}
- }}
-
- #[cfg(not(MODULE))]
- #[doc(hidden)]
- #[no_mangle]
- pub extern \"C\" fn __{ident}_exit() {{
- // SAFETY:
- // - This function is inaccessible to the outside due to the double
- // module wrapping it. It is called exactly once by the C side via its
- // unique name,
- // - furthermore it is only called after `__{ident}_init` has
- // returned `0` (which delegates to `__init`).
- unsafe {{ __exit() }}
- }}
-
- /// # Safety
- ///
- /// This function must only be called once.
- unsafe fn __init() -> ::kernel::ffi::c_int {{
- let initer =
- <{type_} as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
- // SAFETY: No data race, since `__MOD` can only be accessed by this module
- // and there only `__init` and `__exit` access it. These functions are only
- // called once and `__exit` cannot be called before or during `__init`.
- match unsafe {{ initer.__pinned_init(__MOD.as_mut_ptr()) }} {{
- Ok(m) => 0,
- Err(e) => e.to_errno(),
- }}
- }}
-
- /// # Safety
- ///
- /// This function must
- /// - only be called once,
- /// - be called after `__init` has been called and returned `0`.
- unsafe fn __exit() {{
- // SAFETY: No data race, since `__MOD` can only be accessed by this module
- // and there only `__init` and `__exit` access it. These functions are only
- // called once and `__init` was already called.
- unsafe {{
- // Invokes `drop()` on `__MOD`, which should be used for cleanup.
- __MOD.assume_init_drop();
- }}
- }}
-
- {modinfo}
- }}
- }}
- ",
- type_ = info.type_,
- name = info.name,
- ident = ident,
- modinfo = modinfo.buffer,
- initcall_section = ".initcall6.init"
- )
- .parse()
- .expect("Error parsing formatted string into token stream."))
+ let modinfo = modinfo.ts;
+
+ let ident_init = format_ident!("__{ident}_init");
+ let ident_exit = format_ident!("__{ident}_exit");
+ let ident_initcall = format_ident!("__{ident}_initcall");
+ let initcall_section = ".initcall6.init";
+
+ let global_asm = format!(
+ r#".section "{initcall_section}", "a"
+ __{ident}_initcall:
+ .long __{ident}_init - .
+ .previous
+ "#
+ );
+
+ let name_cstr =
+ Literal::c_string(&CString::new(name.as_str()).expect("name contains NUL-terminator"));
+
+ Ok(quote! {
+ /// The module name.
+ ///
+ /// Used by the printing macros, e.g. [`info!`].
+ const __LOG_PREFIX: &[u8] = #name_cstr.to_bytes_with_nul();
+
+ // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
+ // freed until the module is unloaded.
+ #[cfg(MODULE)]
+ static THIS_MODULE: ::kernel::ThisModule = unsafe {
+ extern "C" {
+ static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
+ };
+
+ ::kernel::ThisModule::from_ptr(__this_module.get())
+ };
+
+ #[cfg(not(MODULE))]
+ static THIS_MODULE: ::kernel::ThisModule = unsafe {
+ ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
+ };
+
+ /// The `LocalModule` type is the type of the module created by `module!`,
+ /// `module_pci_driver!`, `module_platform_driver!`, etc.
+ type LocalModule = #type_;
+
+ impl ::kernel::ModuleMetadata for #type_ {
+ const NAME: &'static ::kernel::str::CStr = #name_cstr;
+ }
+
+ // Double nested modules, since then nobody can access the public items inside.
+ mod __module_init {
+ mod __module_init {
+ use super::super::#type_;
+ use pin_init::PinInit;
+
+ /// The "Rust loadable module" mark.
+ //
+ // This may be best done another way later on, e.g. as a new modinfo
+ // key or a new section. For the moment, keep it simple.
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[used(compiler)]
+ static __IS_RUST_MODULE: () = ();
+
+ static mut __MOD: ::core::mem::MaybeUninit<#type_> =
+ ::core::mem::MaybeUninit::uninit();
+
+ // Loadable modules need to export the `{init,cleanup}_module` identifiers.
+ /// # Safety
+ ///
+ /// This function must not be called after module initialization, because it may be
+ /// freed after that completes.
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[no_mangle]
+ #[link_section = ".init.text"]
+ pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
+ // SAFETY: This function is inaccessible to the outside due to the double
+ // module wrapping it. It is called exactly once by the C side via its
+ // unique name.
+ unsafe { __init() }
+ }
+
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[used(compiler)]
+ #[link_section = ".init.data"]
+ static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
+ init_module;
+
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[no_mangle]
+ #[link_section = ".exit.text"]
+ pub extern "C" fn cleanup_module() {
+ // SAFETY:
+ // - This function is inaccessible to the outside due to the double
+ // module wrapping it. It is called exactly once by the C side via its
+ // unique name,
+ // - furthermore it is only called after `init_module` has returned `0`
+ // (which delegates to `__init`).
+ unsafe { __exit() }
+ }
+
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[used(compiler)]
+ #[link_section = ".exit.data"]
+ static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
+
+ // Built-in modules are initialized through an initcall pointer
+ // and the identifiers need to be unique.
+ #[cfg(not(MODULE))]
+ #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
+ #[doc(hidden)]
+ #[link_section = #initcall_section]
+ #[used(compiler)]
+ pub static #ident_initcall: extern "C" fn() ->
+ ::kernel::ffi::c_int = #ident_initcall;
+
+ #[cfg(not(MODULE))]
+ #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
+ ::core::arch::global_asm!(#global_asm);
+
+ #[cfg(not(MODULE))]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
+ // SAFETY: This function is inaccessible to the outside due to the double
+ // module wrapping it. It is called exactly once by the C side via its
+ // placement above in the initcall section.
+ unsafe { __init() }
+ }
+
+ #[cfg(not(MODULE))]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #ident_exit() {
+ // SAFETY:
+ // - This function is inaccessible to the outside due to the double
+ // module wrapping it. It is called exactly once by the C side via its
+ // unique name,
+ // - furthermore it is only called after `#ident_init` has
+ // returned `0` (which delegates to `__init`).
+ unsafe { __exit() }
+ }
+
+ /// # Safety
+ ///
+ /// This function must only be called once.
+ unsafe fn __init() -> ::kernel::ffi::c_int {
+ let initer =
+ <#type_ as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
+ // SAFETY: No data race, since `__MOD` can only be accessed by this module
+ // and there only `__init` and `__exit` access it. These functions are only
+ // called once and `__exit` cannot be called before or during `__init`.
+ match unsafe { initer.__pinned_init(__MOD.as_mut_ptr()) } {
+ Ok(m) => 0,
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// This function must
+ /// - only be called once,
+ /// - be called after `__init` has been called and returned `0`.
+ unsafe fn __exit() {
+ // SAFETY: No data race, since `__MOD` can only be accessed by this module
+ // and there only `__init` and `__exit` access it. These functions are only
+ // called once and `__init` was already called.
+ unsafe {
+ // Invokes `drop()` on `__MOD`, which should be used for cleanup.
+ __MOD.assume_init_drop();
+ }
+ }
+
+ #modinfo
+ }
+ }
+ })
}
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 06/11] rust: macros: convert `#[export]` to use `syn`
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (4 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 05/11] rust: macros: use `quote!` for " Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 07/11] rust: macros: convert `concat_idents!` " Gary Guo
` (4 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Fiona Behrens,
José Expósito, Igor Korotin, Tamir Duberstein
Cc: rust-for-linux, David Gow, linux-kernel
From: Gary Guo <gary@garyguo.net>
This eliminates the custom `function_name` helper.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/export.rs | 24 +++++++++---------------
rust/macros/helpers.rs | 18 ------------------
rust/macros/lib.rs | 5 +++--
3 files changed, 12 insertions(+), 35 deletions(-)
diff --git a/rust/macros/export.rs b/rust/macros/export.rs
index 92d9b30971929..6d53521f62fc7 100644
--- a/rust/macros/export.rs
+++ b/rust/macros/export.rs
@@ -3,19 +3,14 @@
use proc_macro2::TokenStream;
use quote::quote;
-use crate::helpers::function_name;
-
/// Please see [`crate::export`] for documentation.
-pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
- let Some(name) = function_name(ts.clone()) else {
- return "::core::compile_error!(\"The #[export] attribute must be used on a function.\");"
- .parse::<TokenStream>()
- .unwrap();
- };
+pub(crate) fn export(f: syn::ItemFn) -> TokenStream {
+ let name = &f.sig.ident;
- // This verifies that the function has the same signature as the declaration generated by
- // bindgen. It makes use of the fact that all branches of an if/else must have the same type.
- let signature_check = quote!(
+ quote! {
+ // This verifies that the function has the same signature as the declaration generated by
+ // bindgen. It makes use of the fact that all branches of an if/else must have the same
+ // type.
const _: () = {
if true {
::kernel::bindings::#name
@@ -23,9 +18,8 @@ pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
#name
};
};
- );
-
- let no_mangle = quote!(#[no_mangle]);
- TokenStream::from_iter([signature_check, no_mangle, ts])
+ #[no_mangle]
+ #f
+ }
}
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index f6bbdb02d9d7d..754c827cc21e1 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -2,7 +2,6 @@
use proc_macro2::{
token_stream,
- Ident,
TokenStream,
TokenTree, //
};
@@ -50,23 +49,6 @@ pub(crate) fn value(&self) -> String {
}
}
-/// Given a function declaration, finds the name of the function.
-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" => {
- if let Some(TokenTree::Ident(i)) = input.next() {
- return Some(i);
- }
- return None;
- }
- _ => continue,
- }
- }
- None
-}
-
pub(crate) fn file() -> String {
#[cfg(not(CONFIG_RUSTC_HAS_SPAN_FILE))]
{
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 4e440deaed853..870a42d41ac91 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -206,8 +206,9 @@ pub fn vtable(attr: TokenStream, input: TokenStream) -> TokenStream {
/// This macro is *not* the same as the C macros `EXPORT_SYMBOL_*`. All Rust symbols are currently
/// automatically exported with `EXPORT_SYMBOL_GPL`.
#[proc_macro_attribute]
-pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
- export::export(attr.into(), ts.into()).into()
+pub fn export(attr: TokenStream, input: TokenStream) -> TokenStream {
+ parse_macro_input!(attr as syn::parse::Nothing);
+ export::export(parse_macro_input!(input)).into()
}
/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 07/11] rust: macros: convert `concat_idents!` to use `syn`
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (5 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 06/11] rust: macros: convert `#[export]` to use `syn` Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro " Gary Guo
` (3 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Fiona Behrens, Guilherme Giacomo Simoes,
José Expósito, Tamir Duberstein
Cc: rust-for-linux, David Gow, linux-kernel
From: Gary Guo <gary@garyguo.net>
This eliminates the need for `expect_punct` helper.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/concat_idents.rs | 39 ++++++++++++++++++++++++------------
rust/macros/helpers.rs | 14 +------------
rust/macros/lib.rs | 4 ++--
3 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/rust/macros/concat_idents.rs b/rust/macros/concat_idents.rs
index 12cb231c3d715..47b6add378d2c 100644
--- a/rust/macros/concat_idents.rs
+++ b/rust/macros/concat_idents.rs
@@ -1,23 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro2::{token_stream, Ident, TokenStream, TokenTree};
+use proc_macro2::{
+ Ident,
+ TokenStream,
+ TokenTree, //
+};
+use syn::{
+ parse::{
+ Parse,
+ ParseStream, //
+ },
+ Result,
+ Token, //
+};
-use crate::helpers::expect_punct;
+pub(crate) struct Input {
+ a: Ident,
+ _comma: Token![,],
+ b: Ident,
+}
-fn expect_ident(it: &mut token_stream::IntoIter) -> Ident {
- if let Some(TokenTree::Ident(ident)) = it.next() {
- ident
- } else {
- panic!("Expected Ident")
+impl Parse for Input {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(Self {
+ a: input.parse()?,
+ _comma: input.parse()?,
+ b: input.parse()?,
+ })
}
}
-pub(crate) fn concat_idents(ts: TokenStream) -> TokenStream {
- let mut it = ts.into_iter();
- let a = expect_ident(&mut it);
- assert_eq!(expect_punct(&mut it), ',');
- let b = expect_ident(&mut it);
- assert!(it.next().is_none(), "only two idents can be concatenated");
+pub(crate) fn concat_idents(Input { a, b, .. }: Input) -> TokenStream {
let res = Ident::new(&format!("{a}{b}"), b.span());
TokenStream::from_iter([TokenTree::Ident(res)])
}
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 754c827cc21e1..adfa60d8f42d8 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,10 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro2::{
- token_stream,
- TokenStream,
- TokenTree, //
-};
+use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parse::{
@@ -16,14 +12,6 @@
Result, //
};
-pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
- if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
- punct.as_char()
- } else {
- panic!("Expected Punct");
- }
-}
-
/// A string literal that is required to have ASCII value only.
pub(crate) struct AsciiLitStr(LitStr);
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 870a42d41ac91..bb2dfd4a4dafc 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -283,8 +283,8 @@ pub fn fmt(input: TokenStream) -> TokenStream {
/// assert_eq!(BR_OK, binder_driver_return_protocol_BR_OK);
/// ```
#[proc_macro]
-pub fn concat_idents(ts: TokenStream) -> TokenStream {
- concat_idents::concat_idents(ts.into()).into()
+pub fn concat_idents(input: TokenStream) -> TokenStream {
+ concat_idents::concat_idents(parse_macro_input!(input)).into()
}
/// Paste identifiers together.
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn`
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (6 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 07/11] rust: macros: convert `concat_idents!` " Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 09/11] rust: macros: allow arbitrary types to be used in `module!` macro Gary Guo
` (2 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 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,
Guilherme Giacomo Simoes, Patrick Miller, José Expósito,
Tamir Duberstein
Cc: rust-for-linux, linux-kselftest, kunit-dev, linux-kernel
From: Gary Guo <gary@garyguo.net>
This allows significant cleanups.
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 7427c17ee5f5c..516219f5b1356 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.to_string() == "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.
+ // Remove `#[test]` attributes applied on the function and count if any.
+ if !f.attrs.iter().any(|attr| attr.path().is_ident("test")) {
+ processed_items.push(Item::Fn(f));
+ continue;
+ }
+ f.attrs.retain(|attr| !attr.path().is_ident("test"));
+
+ 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 bb2dfd4a4dafc..9cfac9fce0d36 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -453,6 +453,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] 17+ messages in thread* [PATCH 09/11] rust: macros: allow arbitrary types to be used in `module!` macro
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (7 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro " Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 10/11] rust: macros: rearrange `#[doc(hidden)]` " Gary Guo
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Guilherme Giacomo Simoes,
Tamir Duberstein
Cc: rust-for-linux, linux-kernel
From: Gary Guo <gary@garyguo.net>
Previously this only accepts an identifier, but now with `syn` it is
easy to make it accepts any type.
With this change, the span of types are preserved -- as a benefit, Rust
analyzer will be able to use the "navigate to definition" feature on
type name inside `module!` macro invocation.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/module.rs | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 4a02aadef25a7..d6298d04c86f4 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -4,7 +4,6 @@
use proc_macro2::{
Literal,
- Span,
TokenStream, //
};
use quote::{
@@ -23,7 +22,8 @@
Ident,
LitStr,
Result,
- Token, //
+ Token,
+ Type, //
};
use crate::helpers::*;
@@ -105,7 +105,7 @@ mod kw {
#[allow(dead_code, reason = "some fields are only parsed into")]
enum ModInfoField {
- Type(Token![type], Token![:], Ident),
+ Type(Token![type], Token![:], Type),
Name(kw::name, Token![:], AsciiLitStr),
Authors(
kw::authors,
@@ -193,9 +193,9 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
}
}
-#[derive(Debug, Default)]
+#[derive(Default)]
pub(crate) struct ModuleInfo {
- type_: String,
+ type_: Option<Type>,
license: String,
name: String,
authors: Option<Vec<String>>,
@@ -237,7 +237,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
seen_keys.push(key);
match field {
- ModInfoField::Type(_, _, ty) => info.type_ = ty.to_string(),
+ ModInfoField::Type(_, _, ty) => info.type_ = Some(ty),
ModInfoField::Name(_, _, name) => info.name = name.value(),
ModInfoField::Authors(_, _, _, list) => {
info.authors = Some(list.into_iter().map(|x| x.value()).collect())
@@ -286,16 +286,17 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
let ModuleInfo {
- type_,
+ type_: Some(type_),
license,
name,
authors,
description,
alias,
firmware,
- } = info;
-
- let type_ = Ident::new(&type_, Span::mixed_site());
+ } = info
+ else {
+ unreachable!();
+ };
// Rust does not allow hyphens in identifiers, use underscore instead.
let ident = name.replace('-', "_");
@@ -376,7 +377,6 @@ impl ::kernel::ModuleMetadata for #type_ {
// Double nested modules, since then nobody can access the public items inside.
mod __module_init {
mod __module_init {
- use super::super::#type_;
use pin_init::PinInit;
/// The "Rust loadable module" mark.
@@ -388,7 +388,7 @@ mod __module_init {
#[used(compiler)]
static __IS_RUST_MODULE: () = ();
- static mut __MOD: ::core::mem::MaybeUninit<#type_> =
+ static mut __MOD: ::core::mem::MaybeUninit<super::super::LocalModule> =
::core::mem::MaybeUninit::uninit();
// Loadable modules need to export the `{init,cleanup}_module` identifiers.
@@ -475,8 +475,9 @@ pub extern "C" fn #ident_exit() {
///
/// This function must only be called once.
unsafe fn __init() -> ::kernel::ffi::c_int {
- let initer =
- <#type_ as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
+ let initer = <super::super::LocalModule as ::kernel::InPlaceModule>::init(
+ &super::super::THIS_MODULE
+ );
// SAFETY: No data race, since `__MOD` can only be accessed by this module
// and there only `__init` and `__exit` access it. These functions are only
// called once and `__exit` cannot be called before or during `__init`.
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 10/11] rust: macros: rearrange `#[doc(hidden)]` in `module!` macro
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (8 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 09/11] rust: macros: allow arbitrary types to be used in `module!` macro Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
10 siblings, 0 replies; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Guilherme Giacomo Simoes,
Tamir Duberstein
Cc: rust-for-linux, linux-kernel
From: Gary Guo <gary@garyguo.net>
This `#[doc(hidden)]` can just be applied on a module to hide anything
within.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/module.rs | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index d6298d04c86f4..75a4eae5610cd 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -71,7 +71,6 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) {
);
self.ts.extend(quote! {
#cfg
- #[doc(hidden)]
#[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
#[used(compiler)]
pub static #counter: [u8; #length] = *#string;
@@ -375,6 +374,7 @@ impl ::kernel::ModuleMetadata for #type_ {
}
// Double nested modules, since then nobody can access the public items inside.
+ #[doc(hidden)]
mod __module_init {
mod __module_init {
use pin_init::PinInit;
@@ -384,7 +384,6 @@ mod __module_init {
// This may be best done another way later on, e.g. as a new modinfo
// key or a new section. For the moment, keep it simple.
#[cfg(MODULE)]
- #[doc(hidden)]
#[used(compiler)]
static __IS_RUST_MODULE: () = ();
@@ -397,7 +396,6 @@ mod __module_init {
/// This function must not be called after module initialization, because it may be
/// freed after that completes.
#[cfg(MODULE)]
- #[doc(hidden)]
#[no_mangle]
#[link_section = ".init.text"]
pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
@@ -408,14 +406,12 @@ mod __module_init {
}
#[cfg(MODULE)]
- #[doc(hidden)]
#[used(compiler)]
#[link_section = ".init.data"]
static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
init_module;
#[cfg(MODULE)]
- #[doc(hidden)]
#[no_mangle]
#[link_section = ".exit.text"]
pub extern "C" fn cleanup_module() {
@@ -429,7 +425,6 @@ pub extern "C" fn cleanup_module() {
}
#[cfg(MODULE)]
- #[doc(hidden)]
#[used(compiler)]
#[link_section = ".exit.data"]
static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
@@ -438,7 +433,6 @@ pub extern "C" fn cleanup_module() {
// and the identifiers need to be unique.
#[cfg(not(MODULE))]
#[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
- #[doc(hidden)]
#[link_section = #initcall_section]
#[used(compiler)]
pub static #ident_initcall: extern "C" fn() ->
@@ -449,7 +443,6 @@ pub extern "C" fn cleanup_module() {
::core::arch::global_asm!(#global_asm);
#[cfg(not(MODULE))]
- #[doc(hidden)]
#[no_mangle]
pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
// SAFETY: This function is inaccessible to the outside due to the double
@@ -459,7 +452,6 @@ pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
}
#[cfg(not(MODULE))]
- #[doc(hidden)]
#[no_mangle]
pub extern "C" fn #ident_exit() {
// SAFETY:
--
2.51.2
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value
2025-12-11 18:56 [PATCH 00/11] refactor Rust proc macros with `syn` Gary Guo
` (9 preceding siblings ...)
2025-12-11 18:56 ` [PATCH 10/11] rust: macros: rearrange `#[doc(hidden)]` " Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-16 12:48 ` Benno Lossin
10 siblings, 1 reply; 17+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 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, 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.
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 516219f5b1356..fd2cfabfaef76 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] 17+ messages in thread* Re: [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
@ 2025-12-16 12:48 ` Benno Lossin
0 siblings, 0 replies; 17+ messages in thread
From: Benno Lossin @ 2025-12-16 12:48 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
Cc: rust-for-linux, linux-kselftest, kunit-dev, linux-kernel
On Thu Dec 11, 2025 at 7:56 PM CET, Gary Guo 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.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Cheers,
Benno
> ---
> rust/kernel/kunit.rs | 26 +-------------------------
> rust/macros/kunit.rs | 4 ++--
> 2 files changed, 3 insertions(+), 27 deletions(-)
^ permalink raw reply [flat|nested] 17+ messages in thread