簡體   English   中英

如何在程序宏中將 arguments 從生成的 function 傳遞給另一個 function?

[英]How do I pass arguments from a generated function to another function in a procedural macro?

我正在嘗試創建一個從 function 簽名生成兩個函數的宏。 由於函數的簽名取決於傳遞給宏的內容,因此 function arguments 的數量是變化的。

我想要創建的函數是 (i) 一個內部 function ,它實際上做了一些事情(我可以根據需要生成)和 (ii) 一個公共 function應該環繞內部 function。function 簽名是相同的關於參數。 因此,將傳遞給公共 function 的所有 arguments 傳遞到內部 function 應該很容易。

以下片段顯示了公共 function 的簡化宏生成。我如何才能正確傳遞 arguments? #params_from_public_function

let public_function_ident = ...;
let internal_function_ident = ...;

// ast_function_stub-Type: ForeignItemFn
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();


quote! {
  pub fn #public_name(#params_from_public_function) {
    #internal_function_ident(#params_from_public_function);
  }
}

這里有兩個例子,結果宏應該如何工作:

generate_both_functions!(fn foo(someParam: &str) -> u8;);

// What the macro should generate:

fn _foo(someParam: &str) -> u8 { // Internal function
  // Some internal stuff happens here
}

pub fn foo(someParam: &str) { // Public function
  _foo(someParam);
}

// Second example (having two instead of one argument and different types)

generate_both_functions!(fn bar(param1: i32, param2: String) -> String;);

// What the macro should generate
fn _bar(param1: i32, param2: String) -> String {
  // Internal stuff again
}

fn bar(param1: i32, param2: String) {
  _bar(param1, param2);
}

澄清一下:我能夠正確解析宏的輸入( generate_both_functions! )並從 AST 中提取 function 參數元數據。 但是,我無法將實際輸入動態傳遞給內部 function。

這在一般情況下是不可能的。 考慮以下 function 簽名,它在參數 position 中使用解構來提取其第一個參數的字段:

fn bar(Foo { a, b, .. }: Foo)

由於Foo已被解構,因此宏無法生成將其傳遞給另一個具有完全相同簽名的 function 的代碼。 您可能會嘗試重建Foo並像_bar(Foo { a, b })一樣調用_bar ,但是由於..一些字段已經永久丟失並且無法重建。

這里唯一剩下的選項是從模式中提取綁定集(這不是微不足道的),並生成一個 function 將每個綁定作為單獨的參數。 但這也是不可能的。 我們必須生成類似fn _bar(a: A, b: B)的簽名,其中ABab的類型。 但是宏看不到類型Foo的聲明,因此它無法確定這些類型是什么。

備擇方案

  • 可以支持每個參數的形式$ident: $type的常見情況。 在這種情況下,您需要做的就是檢查每個syn::FnArg上的pat字段是否為Pat::Ident並獲取參數的名稱。 然后,您可以為_bar生成匹配的簽名並將每個參數傳入。

  • 如果沒有外部代碼需要調用_bar ,您可以將其設為移動閉包:

     fn bar(/* any signature you like */) { let _bar = move || { /* body goes here, using any arguments from bar */ }; _bar(); }

    這應該無縫繼承外部 function 的所有綁定,但外部 function 之后將無法再使用它們,這可能會破壞交易。

首先:謝謝@AlphaModder!

因為我不想支持解構案例,所以我選擇了你的第一個選擇。 我想解釋一下我是如何最終實現我想要的,以便有類似問題的人能夠比我更快地解決它。

首先,我使用ItemFn -Struct 而不是使用 parse_quote 構造了 function 本身parse_quote! -宏:

// The idents from both functions
let internal_function_ident = ...;
let public_function_ident = ...;

// The arguments with the following pattern: $ident: $type, $ident: $type, ...
let params_from_public_function: Punctuated<FnArg, Comma> = ast_function_stub.sig.inputs.clone();

// Transform the arguments to the pattern: $ident, $ident, ...
let transformed_params = transform_params(params_from_public_function.clone());

// Assemble the function
ItemFn {
  ...
  sig: syn::Signature {
    ...
    ident: public_function_ident,
    inputs: params_from_public_function
  },
  block: parse_quote!({
    #internal_function_ident#transformed_params;
  })
}

transform_params -函數看起來像這樣:

fn transform_params(params: Punctuated<syn::FnArg, syn::token::Comma>) -> Expr {
    // 1. Filter the params, so that only typed arguments remain
    // 2. Extract the ident (in case the pattern type is ident)
    let idents = params.iter().filter_map(|param|{
        if let syn::FnArg::Typed(pat_type) = param {
            if let syn::Pat::Ident(pat_ident) = *pat_type.pat.clone() {
                return Some(pat_ident.ident)
            }
        }
        None
    });

    // Add all idents to a Punctuated => param1, param2, ...
    let mut punctuated: Punctuated<syn::Ident, Comma> = Punctuated::new();
    idents.for_each(|ident| punctuated.push(ident));

    // Generate expression from Punctuated (and wrap with parentheses)
    let transformed_params = parse_quote!((#punctuated));
    transformed_params
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM