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

2023-11-25

我需要获取每个方法的调用者的源位置。我正在尝试创建一个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 and 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)
}

确保将其放入板条箱中并将这些行添加到其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"

深入解释

宏只能扩展到可以手动编写的代码。知道了这一点,我在这里看到两个问题:

  1. How can I write a function that tracks the location of its caller?
    • See How can I access a function's calling location each time it's called?

      简短的回答:要获取调用函数的位置,请将其标记为#[track_caller]并使用std::panic::Location::caller在它的身体里。

  2. 如何编写创建此类函数的过程宏?

初步尝试

我们想要一个程序宏

  • 接受一个函数,
  • 标记它#[track_caller],
  • 并添加一行打印Location::caller.

例如,它将转换这样的函数:

fn foo() {
    // body of foo
}

into

#[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 fns:

    • 它不是打印调用者位置,而是打印我们的宏 (#[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 fns

可以通过以下方式解决该问题async fns using -> 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
}

我们可以添加一个特殊情况,将此转换应用于我们的宏。但是,该转换将函数的公共 API 从async fn foo() to fn foo() -> impl Future<Output = ()>除了影响返回的未来可以具有的自动特征之外。

因此,我建议我们允许用户根据需要使用该解决方法,并且如果我们的宏用于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());
}

游乐场链接

为了解决这个问题,我们需要打破链条#[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());
}

游乐场链接

现在我们知道如何打破链条#[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)
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 在 Linq 查询中调用类方法

    我有一个名为 GetAge DateTimebirthDay 的方法 我想在 Linq 查询中通过传递生日来使用此方法 并根据返回的年龄值需要执行一些逻辑 我想要以下 LINQ 格式的查询 from customer in contetx
  • 迭代@IntDef、@StringDef或任何@Def类中的值

    考虑这个类 public class MyClassOfMystery public static final int NO FLAGS 0 public static final int FIRST FLAG 1 public stati
  • Galaxy S4 上的浮动触摸

    三星 Galaxy S4 有 浮动触摸 功能 即使没有触摸屏幕也可以检测到手指 我想在按钮上触发一个事件 btn1 当手指经过时将其悬停 我尝试使用OnHoverListener but onHover当MotionEvent is Mot
  • 如何撤消 Git 中最近的本地提交?

    我不小心提交了错误的文件Git 但尚未将提交推送到服务器 我如何撤消这些提交local存储库 撤消提交并重做 git commit m Something terribly misguided 0 Your Accident git res
  • Oracle:Order by Union 返回 ORA-00933:SQL 命令未正确结束

    我在同时使用 Oracle 的 union 和 order by 子句时遇到问题 我有两个复杂的查询 其中包含子查询 每个查询都有一个 order by 子句 我需要合并两者的输出并返回结果 当我运行它时 我收到错误 ORA 00933 S
  • 使用扩展语法创建数组

    这是我在 React 书中找到的一个 JavaScript 箭头函数 const createArray length gt Array length 为什么不简单地返回一个新数组呢 const createArray length gt
  • 从故事板中的外部 xib 文件加载视图

    我想在故事板中的多个视图控制器中使用视图 因此 我考虑在外部 xib 中设计视图 以便更改反映在每个视图控制器中 但是如何从故事板中的外部 xib 加载视图 这是否可能 如果不是这种情况 还有哪些其他替代方案可以满足上述情况 我的完整示例是
  • 从具有行优先顺序的数组创建特征矩阵

    我有一个双精度数组 我想使用 Eigen 库创建一个 4 4 矩阵 我还想指定数据按行优先顺序存储 我怎样才能做到这一点 我已尝试以下操作 但无法编译 double data 16 Eigen Matrix4d M Eigen Map
  • 在 python 中将 XML 编辑为字典?

    我正在尝试从 python 中的模板 xml 文件生成自定义 xml 文件 从概念上讲 我想读入模板 xml 删除一些元素 更改一些文本属性 然后将新的 xml 写入文件 我希望它能像这样工作 conf base ConvertXmlToD
  • file_get_contents 会正常失败吗?

    I need file get contents具有容错能力 例如 如果 url喂给它返回一个404 在它发出警告之前告诉我 这可以做到吗 任何使用 HTTP 包装器访问远程文件 就好像它是本地文件一样 的函数都会自动生成一个名为的本地变量
  • 在java文件外部定义内部类

    我想创建一个类 ClassB 作为内部类ClassA 但我想写在外面ClassA java file 我怎样才能做到这一点 这将是很多内部类 并且ClassA java文件将是enormous UPDATE我真正想做的是定义十个类 它们只能
  • 有没有办法为 Spring HATEOAS `ControllerLinkBuilder` 设置主机和端口?

    Spring HATEOAS 提供了方便ControllerLinkBuilder创建指向控制器方法的链接 这些方法将作为 href 添加到返回给客户端的 JSON XML 中 例如 resource add linkTo methodOn
  • 跟踪(直接)文件下载的最佳方法

    跟踪直接文件下载的最佳方法是什么 Google Analytics 仅适用于 JavaScript 无法跟踪直接文件下载 最好的是安全且自己的托管解决方案 放心使用 htaccess RewriteEngine on RewriteRule
  • 为什么ORACLE很多表默认12c?

    创建一个新的数据库 基础和高级 这是我第一次接触Oracle 我不知道为什么有那么多表 触发器 视图和其他对象 而只想创建一个空的关系数据库 有没有其他方法可以做到这一点 或者有什么我错过理解的事情 谢谢 Capture 这些对象的所有者是
  • 如何删除 vscode 右侧滚动条上的符号?

    How can I remove the symbols on the right scrollbar in VSCode As per 这个 Github 问题 最近的解决方案是使用 useworkbench colorCustomiza
  • 特征中的 typedef 与类中的 typedef

    我正在出于教育目的查看 Eigen 源代码 我注意到对于每个具体的类模板X在层次结构中 有一个internal traits
  • 使用带有 Doctrine 2 的装置时发生致命错误

    我是 Symblog 2 初学者 我正在关注本教程适用于 Symblog2 我已经创建了我的数据模型并尝试使用将测试数据填充到我的数据库中学说 2 赛程 我下载了必要的包并将以下内容添加到我的autoload php Doctrine Co
  • PowerMockito 模拟单个静态方法并在另一个静态方法中返回对象

    我已经编写了测试用例来使用 PowerMockito 的 mockStatic 功能来模拟静态类和方法 但我正在努力在另一个静态方法中模拟一个静态方法 我确实看到了一些例子 包括this但他们都没有真正帮助我 或者我不理解实际功能 我一无所
  • 如何使用Python在Mac中控制鼠标?

    在 OS X 上使用 Python 移动鼠标 并可能单击 的最简单方法是什么 这只是为了快速原型设计 它不必很优雅 尝试以下代码 这一页 它定义了几个函数 mousemove and mouseclick 它与 Apple 在 Python
  • 我们可以在过程宏属性中获取调用者的源代码位置吗?

    我需要获取每个方法的调用者的源位置 我正在尝试创建一个proc macro attribute捕获位置并打印它 proc macro attribute pub fn get location attr TokenStream item T