[PATCH 1/4] rust: macros: add `#[macro_export_scoped]`

Gary Guo <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,dev.linux.lists.driver-core,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Due to Rust macro scoping rules, macros that are exported with
`#[macro_export_scoped]` gets added to the crate root, and this causes the
crate root to get crowded with various macros.

We used a trick of `#[doc(hidden)]` + `#[doc(inline)] pub use` to make
these macros to appear to be only defined in the documentation, but this
does not actually prevent them from being referenced from crate root.

Create a macro `#[macro_export_scoped]`, that automates these definition
and re-export and also give these macro unique and non-guessable names.

Signed-off-by: Gary Guo <[email protected]>
---
 rust/macros/lib.rs                 | 25 ++++++++++++++++++
 rust/macros/macro_export_scoped.rs | 53 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+)

diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 0514fc7c0a55..6f8ef9043255 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -20,6 +20,7 @@
 mod for_lt;
 mod helpers;
 mod kunit;
+mod macro_export_scoped;
 mod module;
 mod paste;
 mod vtable;
@@ -320,6 +321,30 @@ pub fn concat_idents(input: TokenStream) -> TokenStream {
     concat_idents::concat_idents(parse_macro_input!(input)).into()
 }
 
+/// Export a macro publicly, but from the current module instead of crate root.
+///
+/// # Examples
+///
+/// ```
+/// mod foo {
+///     #[kernel::macros::macro_export_scoped]
+///     macro_rules! my_macro {
+///         () => {}
+///     }
+/// }
+///
+/// foo::my_macro!();
+/// ```
+#[doc(hidden)]
+#[proc_macro_attribute]
+#[allow(non_snake_case)]
+pub fn macro_export_scoped(attr: TokenStream, input: TokenStream) -> TokenStream {
+    parse_macro_input!(attr as syn::parse::Nothing);
+    macro_export_scoped::macro_export_scoped(parse_macro_input!(input))
+        .unwrap_or_else(|e| e.into_compile_error())
+        .into()
+}
+
 /// Paste identifiers together.
 ///
 /// Within the `paste!` macro, identifiers inside `[<` and `>]` are concatenated together to form a
diff --git a/rust/macros/macro_export_scoped.rs b/rust/macros/macro_export_scoped.rs
new file mode 100644
index 000000000000..a14ee51d2d93
--- /dev/null
+++ b/rust/macros/macro_export_scoped.rs
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use std::hash::{
+    DefaultHasher,
+    Hash,
+    Hasher, //
+};
+
+use proc_macro2::TokenStream;
+use quote::{
+    format_ident,
+    quote, //
+};
+use syn::{
+    Error,
+    ItemMacro,
+    Result, //
+};
+
+pub(crate) fn macro_export_scoped(mut input: ItemMacro) -> Result<TokenStream> {
+    if !input.mac.path.is_ident("macro_rules") {
+        return Err(Error::new_spanned(
+            input,
+            "#[macro_export_scoped] can only be used on `macro_rules!`",
+        ));
+    }
+
+    let Some(name) = input.ident else {
+        Err(Error::new_spanned(
+            input,
+            "`macro_rules!` definition missing an identifier",
+        ))?
+    };
+
+    // Hash together file name and macro name to create a hash that is likely to be unique. Use
+    // `DefaultHasher::new` which is free from RNG so the build is still reproducible.
+    let mut hasher = DefaultHasher::new();
+    crate::helpers::file().hash(&mut hasher);
+    name.hash(&mut hasher);
+    let hash = hasher.finish();
+
+    let unique_name = format_ident!("macro_{name}_{hash:x}");
+    input.ident = Some(unique_name.clone());
+
+    Ok(quote!(
+        #[doc(hidden)]
+        #[macro_export]
+        #input
+
+        #[doc(inline)]
+        pub use #unique_name as #name;
+    ))
+}

-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.