手続き型マクロ属性で呼び出し元のソースコードの場所を取得できますか?

3
Ayush Mishra 2020-03-15 10:56.

すべてのメソッドの呼び出し元のソースの場所を取得する必要があります。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
}

2 answers

2
Victor Deleau 2020-03-18 07: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!()別のマクロを使用して取得)。関数に追加する必要のあるコードは文字列で定義され、文字列はaとして解析され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次の2行を次の行に追加して、クレートが手続き型マクロを提供していることを指定することを忘れないでくださいCargo.toml

[lib]
proc-macro = true
2
timotree 2020-03-18 08:20.

TL; DR

ここで使用している手続きのマクロであるsynquoteあなたが説明してきた何をすべきかは:

// 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"

詳細な説明

マクロは、最初から手作業で記述できるコードにのみ拡張できます。これを知っていると、ここに2つの質問があります。

  1. 呼び出し元の場所を追跡する関数を作成するにはどうすればよいですか?
    • 関数が呼び出されるたびに、関数の呼び出し場所にアクセスするにはどうすればよいですか?を参照してください。

      簡単な答え:関数が呼び出される場所を取得するには、関数にマークを付けて本体で#[track_caller]使用std::panic::Location::callerします。

  2. このような関数を作成する手続き型マクロを作成するにはどうすればよいですか?

最初の試み

手続き型マクロが必要です

  • 機能を取り、
  • それをマークします#[track_caller]
  • を印刷する行を追加しますLocation::caller

たとえば、次のような関数を変換します。

fn foo() {
    // body of foo
}

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

以下に、その変換を正確に実行する手続き型マクロを示します。ただし、後のバージョンで見られるように、おそらく別のものが必要になるでしょう。このコードを試すには、以前のTL; DRセクションのように、コードを独自のクレートに入れ、依存関係をに追加し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`
}

残念ながら、その単純なバージョンで逃げることはできません。そのバージョンには少なくとも2つの問題があります。

  • 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 fn

async fnを使用してsの問題を回避することは可能-> 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()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]、機能barLocation::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)()
    }
};

最終的解決

これらの2つの変更の後、次のコードになりました。

// 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)
}

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

グッドジョブ、ESPN

グッドジョブ、ESPN

今日のホワイトハウスの記者会見で、報道官のサラ・ハッカビー・サンダースは、スポーツセンターのホストであるイェメル・ヒルのドナルド・トランプに関する最近のツイートについてコメントするよう求められ、大統領と彼の政策を白人至上主義者として説明した。ヒルは彼女のつぶやきのためにESPNによって公に叱責されました。

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

基本的に頭のあらゆる部分から髪の毛を整えたり、ブロードライしたり、まっすぐにしたり、脱毛したりする必要がある場合は、このレミントンゴールドボックスが最適です。今日だけ、Amazonは、すでに人気のあるShortcut Pro Self-HaircutKitやPearlPro Ceramic Flat Ironのように、グルーミングをはるかに簡単にするヘアツールをマークダウンしています。

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

トゥパックシャクール(ティムモーゼンフェルダー/ゲッティイメージズ); マヤアンジェロウ(マーティンゴッドウィン/ゲッティイメージズ)移動、テイラースウィフト。ケンドールとカイリーは、必要な数の座席を持っています。

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

写真:Tesla Motorsフロリダに住んでいて、Tesla Model S、Model X 60、またはModel 60Dを使用している場合、車の自律性は50 km(約30マイル)高くなります。これは失敗ではなく、ハリケーンイルマによる避難作業を容易にするために会社自身が命じた自治権の一時的な延長です。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

投資ノート:Bioscout AU$300万シード

投資ノート:Bioscout AU$300万シード

Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。

ムーアの法則を超えて

ムーアの法則を超えて

計算に対する私たちの欲求とムーアの法則が提供できるものとの間には、指数関数的に増大するギャップがあります。私たちの文明は計算に基づいています—建築と想像力の現在の限界を超える技術を見つけなければなりません。

Language