1

rust: macros: update 'paste!' macro to accept string literals

Enable combining identifiers with literals in the 'paste!' macro. This
allows combining user-specified strings with affixes to create
namespaced identifiers.

This sample code:

    macro_rules! m {
        ($name:lit) => {
            paste!(struct [<_some_ $name _struct_>] {})
        }
    }

    m!("foo_bar");

Would previously cause a compilation error. It will now generate:

    struct _some_foo_bar_struct_ {}

Signed-off-by: Trevor Gross <tmgross@umich.edu>
Reviewed-by: Martin Rodriguez Reboredo <yakoyoku@gmail.com>
Reviewed-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Benno Lossin <benno.lossin@proton.me>
Reviewed-by: Gary Guo <gary@garyguo.net>
Link: https://lore.kernel.org/r/20231118013959.37384-1-tmgross@umich.edu
[ Added `:` before example block. ]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Trevor Gross 2023-11-17 20:39:59 -05:00 committed by Miguel Ojeda
parent 743766565d
commit 2dc318ea96
2 changed files with 29 additions and 3 deletions

View File

@ -254,8 +254,8 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// Within the `paste!` macro, identifiers inside `[<` and `>]` are concatenated together to form a /// Within the `paste!` macro, identifiers inside `[<` and `>]` are concatenated together to form a
/// single identifier. /// single identifier.
/// ///
/// This is similar to the [`paste`] crate, but with pasting feature limited to identifiers /// This is similar to the [`paste`] crate, but with pasting feature limited to identifiers and
/// (literals, lifetimes and documentation strings are not supported). There is a difference in /// literals (lifetimes and documentation strings are not supported). There is a difference in
/// supported modifiers as well. /// supported modifiers as well.
/// ///
/// # Example /// # Example
@ -337,6 +337,24 @@ pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
/// assert_eq!(br_ok(), binder_driver_return_protocol_BR_OK); /// assert_eq!(br_ok(), binder_driver_return_protocol_BR_OK);
/// ``` /// ```
/// ///
/// # Literals
///
/// Literals can also be concatenated with other identifiers:
///
/// ```ignore
/// macro_rules! create_numbered_fn {
/// ($name:literal, $val:literal) => {
/// kernel::macros::paste! {
/// fn [<some_ $name _fn $val>]() -> u32 { $val }
/// }
/// };
/// }
///
/// create_numbered_fn!("foo", 100);
///
/// assert_eq!(some_foo_fn100(), 100)
/// ```
///
/// [`paste`]: https://docs.rs/paste/ /// [`paste`]: https://docs.rs/paste/
#[proc_macro] #[proc_macro]
pub fn paste(input: TokenStream) -> TokenStream { pub fn paste(input: TokenStream) -> TokenStream {

View File

@ -9,7 +9,15 @@ fn concat(tokens: &[TokenTree], group_span: Span) -> TokenTree {
loop { loop {
match tokens.next() { match tokens.next() {
None => break, None => break,
Some(TokenTree::Literal(lit)) => segments.push((lit.to_string(), lit.span())), Some(TokenTree::Literal(lit)) => {
// Allow us to concat string literals by stripping quotes
let mut value = lit.to_string();
if value.starts_with('"') && value.ends_with('"') {
value.remove(0);
value.pop();
}
segments.push((value, lit.span()));
}
Some(TokenTree::Ident(ident)) => { Some(TokenTree::Ident(ident)) => {
let mut value = ident.to_string(); let mut value = ident.to_string();
if value.starts_with("r#") { if value.starts_with("r#") {