Rust 我们可以在过程宏属性中获取调用方的源代码位置吗?
我需要获取每个方法调用方的源位置。我正在尝试创建一个Rust 我们可以在过程宏属性中获取调用方的源代码位置吗?,rust,rust-macros,rust-proc-macros,Rust,Rust Macros,Rust Proc Macros,我需要获取每个方法调用方的源位置。我正在尝试创建一个proc\u macro\u属性,以捕获位置并打印它 #[proc_宏_属性] pub fn get_位置(attr:TokenStream,item:TokenStream)->TokenStream{ //获取并打印源代码的文件!(),行!() //应该打印第11行 项目 } #[获取位置] fn添加(x:u32,y:u32)->u32{ x+y } fn main(){ 添加(1,5);//第11行 } 提供了现成的解决方案(请参见@t
proc\u macro\u属性
,以捕获位置并打印它
#[proc_宏_属性]
pub fn get_位置(attr:TokenStream,item:TokenStream)->TokenStream{
//获取并打印源代码的文件!(),行!()
//应该打印第11行
项目
}
#[获取位置]
fn添加(x:u32,y:u32)->u32{
x+y
}
fn main(){
添加(1,5);//第11行
}
提供了现成的解决方案(请参见@timotree的评论)。如果你想自己做这件事,有更多的灵活性或者学习,你可以编写一个过程宏来解析回溯(从被调用的函数内部获得)并打印你需要的信息。下面是lib.rs
中的一个过程宏:
extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};
#[proc_macro_attribute]
pub fn get_location(_attr: TokenStream, item: TokenStream) -> TokenStream {
// prefix code to be added to the function's body
let mut prefix: TokenStream = "
// find earliest symbol in source file using backtrace
let ps = Backtrace::new().frames().iter()
.flat_map(BacktraceFrame::symbols)
.skip_while(|s| s.filename()
.map(|p|!p.ends_with(file!())).unwrap_or(true))
.nth(1 as usize).unwrap();
println!(\"Called from {:?} at line {:?}\",
ps.filename().unwrap(), ps.lineno().unwrap());
".parse().unwrap(); // parse string into TokenStream
item.into_iter().map(|tt| { // edit input TokenStream
match tt {
TokenTree::Group(ref g) // match the function's body
if g.delimiter() == proc_macro::Delimiter::Brace => {
prefix.extend(g.stream()); // add parsed string
TokenTree::Group(proc_macro::Group::new(
proc_macro::Delimiter::Brace, prefix.clone()))
},
other => other, // else just forward TokenTree
}
}).collect()
}
分析回溯以查找源文件中最早的符号(使用另一个宏file!()
检索)。我们需要添加到函数中的代码在字符串中定义,然后将其解析为TokenStream
,并添加到函数体的开头。我们可以在最后添加这个逻辑,但是如果返回一个不带分号的值,那么就不再有效了。然后,您可以在main.rs
中使用程序宏,如下所示:
extern crate backtrace;
use backtrace::{Backtrace, BacktraceFrame};
use mylib::get_location;
#[get_location]
fn add(x: u32, y: u32) -> u32 { x + y }
fn main() {
add(1, 41);
add(41, 1);
}
输出为:
> Called from "src/main.rs" at line 10
> Called from "src/main.rs" at line 11
不要忘记通过将这两行添加到您的货物中来指定您的lib
板条箱提供了过程宏。toml
:
[lib]
proc-macro = true
TL;DR
下面是一个过程宏,它使用并执行您所描述的操作:
//打印调用方位置/src/lib.rs
使用proc_macro::TokenStream;
使用quote::quote;
使用syn::span::span;
//创建程序属性宏
//
//值得注意的是,必须将其单独放置在自己的板条箱中
#[过程宏属性]
pub fn print\u caller\u location(\u attr:TokenStream,item:TokenStream)->TokenStream{
//将传递的项作为函数进行分析
让func=syn::parse_macro_input!(项为syn::ItemFn);
//将函数分解为几个部分
让syn::ItemFn{
属性,
可见,
西格,
块
}=func;
//确保它不是一个“异步fn”`
如果让某些(异步_令牌)=sig.asyncy{
//如果是的话,就错了
让error=syn::error::new(
async_token.span(),
“异步函数不支持调用方跟踪功能
帮助:考虑返回“IMPL未来”,而不是“
);
返回TokenStream::from(error.to_compile_error());
}
//仅当函数尚未具有#[track_caller]时,才在闭包中包装正文
设block=if attrs.iter().any(|attr | attr.path.is_ident(“track_caller”)){
引用!{#block}
}否则{
引用{
(移动| |#块)()
}
};
//为更漂亮的输出提取函数名
让name=format!(“{}”,sig.ident);
//生成输出,添加“#[track_caller]”和“println!”`
让输出=报价{
#[追踪来电者]
#(#属性)*
#vis#sig{
普林顿(
“输入`fn{}`:从`{}`'调用”,
#名字,
::core::panic::Location::caller()
);
#挡块
}
};
//将输出从'proc_macro2::TokenStream'转换为'proc_macro::TokenStream'`
令牌流::来自(输出)
}
确保将其放入板条箱中,并将这些行添加到其货物中。toml
:
[lib]
proc-macro = true
#打印呼叫方位置/Cargo.toml
[lib]
proc宏=真
[依赖关系]
syn={version=“1.0.16”,features=[“full”]}
quote=“1.0.3”
proc-macro2=“1.0.9”
深入解释
宏只能扩展到可以手动编写的代码。知道了这一点,我在这里看到了两个问题:
- 看
简短回答:要获取调用函数的位置,请使用
标记函数,并在函数体中使用#[track_caller]
- 作为一个函数
- 将其标记为
#[track_caller]
- 并添加一条打印线
fn foo(){
//傅体
}
进入
#[track#u caller]
fn foo(){
println!(“{}”,std::panic::Location::caller());
//傅体
}
下面,我将介绍一个过程宏,它精确地执行该转换——尽管,正如您在以后的版本中所看到的,您可能需要一些不同的东西。如前所述,在TL中尝试此代码;DR部分,将其放入自己的板条箱中,并将其依赖项添加到Cargo.toml
//打印调用方位置/src/lib.rs
使用proc_macro::TokenStream;
使用quote::quote;
//创建程序属性宏
//
//值得注意的是,必须将其单独放置在自己的板条箱中
#[过程宏属性]
pub fn print\u caller\u location(\u attr:TokenStream,item:TokenStream)->TokenStream{
//将传递的项作为函数进行分析
让func=syn::parse_macro_input!(项为syn::ItemFn);
//将函数分解为几个部分
让syn::ItemFn{
属性,
可见,
西格,
块
}=func;
//为更漂亮的输出提取函数名
让name=format!(“{}”,sig.ident);
//生成输出,添加“#[track_caller]”和“println!”`
让输出=报价{
#[追踪来电者]
#(#属性)*
#vis#sig{
普林顿(
“输入`fn{}`:从`{}`'调用”,
#名字,
::core::panic::Location::caller()
);
#挡块
}
};
//将输出从
// example3/src/main.rs
#![feature(track_caller)]
#[print_caller_location::print_caller_location]
fn add(x: u32, y: u32) -> u32 {
x + y
}
#[print_caller_location::print_caller_location]
fn add_outer(x: u32, y: u32) -> u32 {
add(x, y)
// ^ we would expect "entering `fn add`: called from `example3/src/main.rs:12:5`"
}
fn main() {
add(1, 5);
// ^ "entering `fn add`: called from `example3/src/main.rs:17:5`"
add(1, 5);
// ^ "entering `fn add`: called from `example3/src/main.rs:19:5`"
add_outer(1, 5);
// ^ "entering `fn add_outer`: called from `example3/src/main.rs:21:5`"
// ^ oops! "entering `fn add`: called from `example3/src/main.rs:21:5`"
//
// In reality, `add` was called on line 12, from within the body of `add_outer`
add_outer(1, 5);
// ^ "entering `fn add_outer`: called from `example3/src/main.rs:26:5`"
// oops! ^ entering `fn add`: called from `example3/src/main.rs:26:5`
//
// In reality, `add` was called on line 12, from within the body of `add_outer`
}