mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
7e06561fcd
According to the rustdoc for the proc_macro crate[1], tokens captured from a "macro variable" (e.g. from within macro_rules!) may be delimited by invisible tokens and be contained within a proc_macro::Group. Previously, this scenario was not handled by macros::paste, which caused a proc-macro panic when the corresponding tests are enabled. Enable the tests, and handle this case by making macros::paste::concat recursive. Link: https://doc.rust-lang.org/stable/proc_macro/enum.Delimiter.html [1] Signed-off-by: Ethan D. Twardy <ethan.twardy@gmail.com> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Link: https://github.com/Rust-for-Linux/linux/issues/1076 Link: https://lore.kernel.org/r/20240704145607.17732-4-ethan.twardy@gmail.com [ Rebased (one fix was already applied) and reworded. Remove unneeded `rust` as language in examples. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
114 lines
4.5 KiB
Rust
114 lines
4.5 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
|
|
|
|
fn concat_helper(tokens: &[TokenTree]) -> Vec<(String, Span)> {
|
|
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));
|
|
}
|
|
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::None => {
|
|
let tokens = group.stream().into_iter().collect::<Vec<TokenTree>>();
|
|
segments.append(&mut concat_helper(tokens.as_slice()));
|
|
}
|
|
token => panic!("unexpected token in paste segments: {:?}", token),
|
|
};
|
|
}
|
|
|
|
segments
|
|
}
|
|
|
|
fn concat(tokens: &[TokenTree], group_span: Span) -> TokenTree {
|
|
let segments = concat_helper(tokens);
|
|
let pasted: String = segments.into_iter().map(|x| x.0).collect();
|
|
TokenTree::Ident(Ident::new(&pasted, 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());
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
}
|