尝试实现proc-macro-workshop的seq宏,但是在第三步要求中的输出指定错误信息卡住了
lib.rs
use proc_macro::TokenStream;
use proc_macro2::{Group, Literal, TokenStream as TokenStream2, TokenTree};
use quote::{quote, quote_spanned, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
Block, Ident, LitInt, Result, Token,
};
#[derive(Debug, Clone)]
struct Seq {
ident: Ident,
lower_bound: i32,
higher_bound: i32,
body: TokenStream2,
}
impl Parse for Seq {
fn parse(input: ParseStream) -> Result<Self> {
let ident: Ident = input.parse()?;
input.parse::<Token![in]>()?;
let lower_bound = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Token![..]>()?;
let higher_bound = input.parse::<LitInt>()?.base10_parse()?;
let b = input.parse::<Block>()?;
let body = if let Some(TokenTree::Group(g)) = b.to_token_stream().into_iter().next() {
g.stream()
} else {
unreachable!()
};
Ok(Self {
ident,
lower_bound,
higher_bound,
body,
})
}
}
fn substitute_ident(input: TokenStream2, ident: Ident, target: TokenTree) -> TokenStream2 {
let mut out = TokenStream2::new();
for t in input {
match t {
TokenTree::Group(g) => TokenTree::Group(Group::new(
g.delimiter(),
substitute_ident(g.stream(), ident.clone(), target.clone()),
)),
TokenTree::Ident(i) => {
if i == ident {
target.clone()
} else {
TokenTree::Ident(i)
}
}
x => x,
}
.to_tokens(&mut out)
}
out
}
#[proc_macro]
pub fn seq(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Seq);
dbg!(&input);
let s = (input.lower_bound..input.higher_bound).map(|i| {
substitute_ident(
input.body.clone(),
input.ident.clone(),
TokenTree::Literal(Literal::i32_unsuffixed(i)),
)
});
quote_spanned! {input.body.span()=> #(#s)*}.into()
}
test文件
use seq::seq;
seq!(N in 0..4 {
compile_error!(concat!("error number ", stringify!(N)));
});
fn main() {}
虽然使用了quote_spanned!
宏,但是输出错误信息是
error: error number 0
--> main.rs:2:1
|
2 | / seq!(N in 0..4 {
3 | | compile_error!(concat!("error number ", stringify!(N)));
4 | | });
| |__^
|
= note: this error originates in the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)
但是我的span确实是正确的,如果宏输出以下内容
#[proc_macro]
pub fn seq(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Seq);
dbg!(&input);
let s = (input.lower_bound..input.higher_bound).map(|i| {
substitute_ident(
input.body.clone(),
input.ident.clone(),
TokenTree::Literal(Literal::i32_unsuffixed(i)),
)
});
let e = syn::Error::new(input.body.span(), "test").to_compile_error();
//quote_spanned! {input.body.span()=> #(#s)*}.into()
quote!(#e).into()
}
就可以在正确的地方给出错误
error: test
--> main.rs:3:5
|
3 | compile_error!(concat!("error number ", stringify!(N)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
这里的quote_spanned
应该如何使用?
1
共 3 条评论, 1 页
评论区
写评论你使用
group.set_span(g.span());
之后可以通过测试,但这与quote_spanned!
无关,你把它换成quote!
,也一样通过。在这里它们几乎没有差别,因为标记的 Span 都原封不动地保留下来了。你使用
quote_spanned!
是个好习惯,因为你记得把生成的标记对应到原文该存在的位置上去。但实际操作中,我们无需考虑那些从原文拿过来的标记的 Span(它们已经存在,而且你无法修改),而应该考虑新生成的标记的 Span(因为这种情况下,syn/quote 默认给新标记过程宏调用处的 Span)。
比如你在新生成的 Group 没有给它原有的标记,导致测试不能通过。
此外,因为
Seq!
在这里只对某个标识符进行替换或者重组,你还可以考虑因为替换或者重组导致的新标记,是否需要给它定位到原文的某处,比如a..b
中的a
或者b
。说得再具体一点:
Seq!
把某个标识符进行替换成Literal
类型时,你可以给它设置 Span。但由于这里产生了额外的数据,a..b
中的a+1
你可能要考虑怎么赋予 Span(这个地方没有测试涉及到,所以这个问题你不太需要考虑)。Seq!
把某些标识符重组时,你如果直接使用Ident
类型(别忘了,syn 中的 Ident 是 proc_macro2 的重导出) +set_span
方法,也没问题。但很多时候quote::format_ident!
帮你处理了一种常见情况下的卫生性问题。是的,考虑 Span 就是考虑卫生性问题。
你可以从我写的这个案例中看到:
这就是
quote!
和quote_spanned!
的区别。在 proc-macro-workshop 中,大部分情况下使用
quote!
就足够了,而使用quote_spanned!
优于使用quote!
的地方,dtolnay 已经用测试来帮助你学习编写了。感谢,是我把旧
Group
替换Ident
后构造的新Group
不包含原有的Span
信息,所以默认Span
在宏调用处了。能请教一下quote_spanned
的使用限制吗,为什么我这里使用就没有效果呢--
👇
苦瓜小仔: 这里无需使用
quote_spanned
,如果只通过前三个测试,下面的代码就足够了:playground
要通过完整的测试,可参考我的解答
提示:解析自原文的 TokenStream 自带 Span。
这里无需使用
quote_spanned
,如果只通过前三个测试,下面的代码就足够了:playground
要通过完整的测试,可参考我的解答
提示:解析自原文的 TokenStream 自带 Span。