mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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>
		
	
			
		
			
				
	
	
		
			104 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
 | 
						|
use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
 | 
						|
 | 
						|
fn concat(tokens: &[TokenTree], group_span: Span) -> TokenTree {
 | 
						|
    let mut tokens = tokens.iter();
 | 
						|
    let mut segments = Vec::new();
 | 
						|
    let mut span = None;
 | 
						|
    loop {
 | 
						|
        match tokens.next() {
 | 
						|
            None => break,
 | 
						|
            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)) => {
 | 
						|
                let mut value = ident.to_string();
 | 
						|
                if value.starts_with("r#") {
 | 
						|
                    value.replace_range(0..2, "");
 | 
						|
                }
 | 
						|
                segments.push((value, ident.span()));
 | 
						|
            }
 | 
						|
            Some(TokenTree::Punct(p)) if p.as_char() == ':' => {
 | 
						|
                let Some(TokenTree::Ident(ident)) = tokens.next() else {
 | 
						|
                    panic!("expected identifier as modifier");
 | 
						|
                };
 | 
						|
 | 
						|
                let (mut value, sp) = segments.pop().expect("expected identifier before modifier");
 | 
						|
                match ident.to_string().as_str() {
 | 
						|
                    // Set the overall span of concatenated token as current span
 | 
						|
                    "span" => {
 | 
						|
                        assert!(
 | 
						|
                            span.is_none(),
 | 
						|
                            "span modifier should only appear at most once"
 | 
						|
                        );
 | 
						|
                        span = Some(sp);
 | 
						|
                    }
 | 
						|
                    "lower" => value = value.to_lowercase(),
 | 
						|
                    "upper" => value = value.to_uppercase(),
 | 
						|
                    v => panic!("unknown modifier `{v}`"),
 | 
						|
                };
 | 
						|
                segments.push((value, sp));
 | 
						|
            }
 | 
						|
            _ => panic!("unexpected token in paste segments"),
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    let pasted: String = segments.into_iter().map(|x| x.0).collect();
 | 
						|
    TokenTree::Ident(Ident::new(&pasted, span.unwrap_or(group_span)))
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expand(tokens: &mut Vec<TokenTree>) {
 | 
						|
    for token in tokens.iter_mut() {
 | 
						|
        if let TokenTree::Group(group) = token {
 | 
						|
            let delimiter = group.delimiter();
 | 
						|
            let span = group.span();
 | 
						|
            let mut stream: Vec<_> = group.stream().into_iter().collect();
 | 
						|
            // Find groups that looks like `[< A B C D >]`
 | 
						|
            if delimiter == Delimiter::Bracket
 | 
						|
                && stream.len() >= 3
 | 
						|
                && matches!(&stream[0], TokenTree::Punct(p) if p.as_char() == '<')
 | 
						|
                && matches!(&stream[stream.len() - 1], TokenTree::Punct(p) if p.as_char() == '>')
 | 
						|
            {
 | 
						|
                // Replace the group with concatenated token
 | 
						|
                *token = concat(&stream[1..stream.len() - 1], span);
 | 
						|
            } else {
 | 
						|
                // Recursively expand tokens inside the group
 | 
						|
                expand(&mut stream);
 | 
						|
                let mut group = Group::new(delimiter, stream.into_iter().collect());
 | 
						|
                group.set_span(span);
 | 
						|
                *token = TokenTree::Group(group);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Path segments cannot contain invisible delimiter group, so remove them if any.
 | 
						|
    for i in (0..tokens.len().saturating_sub(3)).rev() {
 | 
						|
        // Looking for a double colon
 | 
						|
        if matches!(
 | 
						|
            (&tokens[i + 1], &tokens[i + 2]),
 | 
						|
            (TokenTree::Punct(a), TokenTree::Punct(b))
 | 
						|
                if a.as_char() == ':' && a.spacing() == Spacing::Joint && b.as_char() == ':'
 | 
						|
        ) {
 | 
						|
            match &tokens[i + 3] {
 | 
						|
                TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
 | 
						|
                    tokens.splice(i + 3..i + 4, group.stream());
 | 
						|
                }
 | 
						|
                _ => (),
 | 
						|
            }
 | 
						|
 | 
						|
            match &tokens[i] {
 | 
						|
                TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
 | 
						|
                    tokens.splice(i..i + 1, group.stream());
 | 
						|
                }
 | 
						|
                _ => (),
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |