gpt4 book ai didi

rust - 我们可以在过程宏属性中获取调用者的源代码位置吗?

转载 作者:行者123 更新时间:2023-12-03 11:26:01 28 4
gpt4 key购买 nike

我需要获取每个方法的调用者的源位置。我正在尝试创建一个 proc_macro_attribute捕获位置并打印它。

#[proc_macro_attribute]
pub fn get_location(attr: TokenStream, item: TokenStream) -> TokenStream {
// Get and print file!(), line!() of source
// Should print line no. 11
item
}

#[get_location]
fn add(x: u32, y: u32) -> u32 {
x + y
}

fn main() {
add(1, 5); // Line No. 11
}

最佳答案

TL;DR

这是一个使用 syn 的过程宏和 quote 做你所描述的:

// print_caller_location/src/lib.rs

use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;

// Create a procedural attribute macro
//
// Notably, this must be placed alone in its own crate
#[proc_macro_attribute]
pub fn print_caller_location(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the passed item as a function
let func = syn::parse_macro_input!(item as syn::ItemFn);

// Break the function down into its parts
let syn::ItemFn {
attrs,
vis,
sig,
block,
} = func;

// Ensure that it isn't an `async fn`
if let Some(async_token) = sig.asyncness {
// Error out if so
let error = syn::Error::new(
async_token.span(),
"async functions do not support caller tracking functionality
help: consider returning `impl Future` instead",
);

return TokenStream::from(error.to_compile_error());
}

// Wrap body in a closure only if function doesn't already have #[track_caller]
let block = if attrs.iter().any(|attr| attr.path.is_ident("track_caller")) {
quote! { #block }
} else {
quote! {
(move || #block)()
}
};

// Extract function name for prettier output
let name = format!("{}", sig.ident);

// Generate the output, adding `#[track_caller]` as well as a `println!`
let output = quote! {
#[track_caller]
#(#attrs)*
#vis #sig {
println!(
"entering `fn {}`: called from `{}`",
#name,
::core::panic::Location::caller()
);
#block
}
};

// Convert the output from a `proc_macro2::TokenStream` to a `proc_macro::TokenStream`
TokenStream::from(output)
}

确保将其放入 crate 并将这些行添加到其 Cargo.toml :

# print_caller_location/Cargo.toml

[lib]
proc-macro = true

[dependencies]
syn = {version = "1.0.16", features = ["full"]}
quote = "1.0.3"
proc-macro2 = "1.0.9"

深入解释

宏只能扩展为可以手动编写的代码。知道了这一点,我在这里看到两个问题:
  • 如何编写一个跟踪调用者位置的函数?
  • How can I access a function's calling location each time it's called?

    Short answer: to obtain the location in which your function gets called, mark it with #[track_caller] and use std::panic::Location::caller in its body.

  • 如何编写创建此类函数的程序宏?

  • 初次尝试

    我们想要一个程序宏
  • 取一个函数,
  • 标记它#[track_caller] ,
  • 并添加一行打印 Location::caller .

  • 例如,它会像这样转换一个函数:

    fn foo() {
    // body of foo
    }

    进入

    #[track_caller]
    fn foo() {
    println!("{}", std::panic::Location::caller());
    // body of foo
    }

    下面,我展示了一个完全执行该转换的过程宏——尽管,正如您将在以后的版本中看到的,您可能想要一些不同的东西。要尝试此代码,就像之前在 TL;DR 部分中一样,将其放入自己的 crate 并将其依赖项添加到 Cargo.toml .

    // print_caller_location/src/lib.rs

    use proc_macro::TokenStream;
    use quote::quote;

    // Create a procedural attribute macro
    //
    // Notably, this must be placed alone in its own crate
    #[proc_macro_attribute]
    pub fn print_caller_location(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // Parse the passed item as a function
    let func = syn::parse_macro_input!(item as syn::ItemFn);

    // Break the function down into its parts
    let syn::ItemFn {
    attrs,
    vis,
    sig,
    block,
    } = func;

    // Extract function name for prettier output
    let name = format!("{}", sig.ident);

    // Generate the output, adding `#[track_caller]` as well as a `println!`
    let output = quote! {
    #[track_caller]
    #(#attrs)*
    #vis #sig {
    println!(
    "entering `fn {}`: called from `{}`",
    #name,
    ::core::panic::Location::caller()
    );
    #block
    }
    };

    // Convert the output from a `proc_macro2::TokenStream` to a `proc_macro::TokenStream`
    TokenStream::from(output)
    }

    示例用法:

    // example1/src/main.rs

    #![feature(track_caller)]

    #[print_caller_location::print_caller_location]
    fn add(x: u32, y: u32) -> u32 {
    x + y
    }

    fn main() {
    add(1, 5); // entering `fn add`: called from `example1/src/main.rs:11:5`
    add(1, 5); // entering `fn add`: called from `example1/src/main.rs:12:5`
    }

    不幸的是,我们无法摆脱那个简单的版本。该版本至少存在两个问题:
  • 它如何与 async fn 组成年代:
  • 它不是打印调用者位置,而是打印调用我们的宏 (#[print_caller_location]) 的位置。例如:

  • // example2/src/main.rs

    #![feature(track_caller)]

    #[print_caller_location::print_caller_location]
    async fn foo() {}

    fn main() {
    let future = foo();
    // ^ oops! prints nothing
    futures::executor::block_on(future);
    // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"
    let future = foo();
    // ^ oops! prints nothing
    futures::executor::block_on(future);
    // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"
    }
  • 它如何与自身的其他调用一起工作,或者一般来说,#[track_caller] :
  • 带有 #[print_caller_location] 的嵌套函数将打印根调用者的位置,而不是给定函数的直接调用者。例如:

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

    寻址 async fn s

    可以使用 async fn 解决此问题。使用 -> impl Future ,例如,如果我们想要我们的 async fn反例要正常工作,我们可以改为:

    // example4/src/main.rs

    #![feature(track_caller)]

    use std::future::Future;

    #[print_caller_location::print_caller_location]
    fn foo() -> impl Future<Output = ()> {
    async move {
    // body of foo
    }
    }

    fn main() {
    let future = foo();
    // ^ prints "entering `fn foo`: called from `example4/src/main.rs:15:18`"
    futures::executor::block_on(future);
    // ^ prints nothing
    let future = foo();
    // ^ prints "entering `fn foo`: called from `example4/src/main.rs:19:18`"
    futures::executor::block_on(future);
    // ^ prints nothing
    }

    我们可以添加一个特殊情况,将这种转换应用于我们的宏。但是,该转换将函数的公共(public) API 从 async fn foo() 更改为至 fn foo() -> impl Future<Output = ()>除了影响返回的 future 可能具有的自动特征。

    因此,我建议我们允许用户根据需要使用该解决方法,如果我们的宏用于 async fn,则简单地发出错误。 .我们可以通过将这些行添加到我们的宏代码中来做到这一点:

    // Ensure that it isn't an `async fn`
    if let Some(async_token) = sig.asyncness {
    // Error out if so
    let error = syn::Error::new(
    async_token.span(),
    "async functions do not support caller tracking functionality
    help: consider returning `impl Future` instead",
    );

    return TokenStream::from(error.to_compile_error());
    }

    修复 #[print_caller_location] 的嵌套行为职能

    有问题的行为减少到这个事实:当 #[track_caller]函数, foo ,直接调用另一个 #[track_caller]函数, bar , Location::caller将使他们都可以访问 foo的来电者。换句话说, Location::caller在嵌套 #[track_caller] 的情况下提供对根调用者的访问权限职能:

    #![feature(track_caller)]

    fn main() {
    foo(); // prints `src/main.rs:4:5` instead of the line number in `foo`
    }

    #[track_caller]
    fn foo() {
    bar();
    }

    #[track_caller]
    fn bar() {
    println!("{}", std::panic::Location::caller());
    }

    playground link

    为了解决这个问题,我们需要打破 #[track_caller] 的链。来电。我们可以通过隐藏对 bar 的嵌套调用来打破链条。在闭包内:

    #![feature(track_caller)]

    fn main() {
    foo();
    }

    #[track_caller]
    fn foo() {
    (move || {
    bar(); // prints `src/main.rs:10:9`
    })()
    }

    #[track_caller]
    fn bar() {
    println!("{}", std::panic::Location::caller());
    }

    playground link

    现在我们知道如何打破 #[track_caller] 的链条了功能,我们可以解决这个问题。我们只需要确定用户是否真的用 #[track_caller] 标记了他们的功能。我们故意避免插入闭包并破坏链条。

    我们可以将这些行添加到我们的解决方案中:

    // Wrap body in a closure only if function doesn't already have #[track_caller]
    let block = if attrs.iter().any(|attr| attr.path.is_ident("track_caller")) {
    quote! { #block }
    } else {
    quote! {
    (move || #block)()
    }
    };

    最终解决方案

    在这两个更改之后,我们最终得到了以下代码:

    // print_caller_location/src/lib.rs

    use proc_macro::TokenStream;
    use quote::quote;
    use syn::spanned::Spanned;

    // Create a procedural attribute macro
    //
    // Notably, this must be placed alone in its own crate
    #[proc_macro_attribute]
    pub fn print_caller_location(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // Parse the passed item as a function
    let func = syn::parse_macro_input!(item as syn::ItemFn);

    // Break the function down into its parts
    let syn::ItemFn {
    attrs,
    vis,
    sig,
    block,
    } = func;

    // Ensure that it isn't an `async fn`
    if let Some(async_token) = sig.asyncness {
    // Error out if so
    let error = syn::Error::new(
    async_token.span(),
    "async functions do not support caller tracking functionality
    help: consider returning `impl Future` instead",
    );

    return TokenStream::from(error.to_compile_error());
    }

    // Wrap body in a closure only if function doesn't already have #[track_caller]
    let block = if attrs.iter().any(|attr| attr.path.is_ident("track_caller")) {
    quote! { #block }
    } else {
    quote! {
    (move || #block)()
    }
    };

    // Extract function name for prettier output
    let name = format!("{}", sig.ident);

    // Generate the output, adding `#[track_caller]` as well as a `println!`
    let output = quote! {
    #[track_caller]
    #(#attrs)*
    #vis #sig {
    println!(
    "entering `fn {}`: called from `{}`",
    #name,
    ::core::panic::Location::caller()
    );
    #block
    }
    };

    // Convert the output from a `proc_macro2::TokenStream` to a `proc_macro::TokenStream`
    TokenStream::from(output)
    }

    关于rust - 我们可以在过程宏属性中获取调用者的源代码位置吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60692131/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com