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`
    }