forked from mirrors/linux
		
	 2dc318ea96
			
		
	
	
		2dc318ea96
		
	
	
	
	
		
			
			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());
 | |
|                 }
 | |
|                 _ => (),
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |