如何将参数从生成的函数传递给过程宏中的另一个函数?

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

我正在尝试创建一个从函数签名生成两个函数的宏。由于函数的签名取决于传递给宏的内容,因此函数参数的数量是变化的。

我想创建的函数是 (i) 一个 内部函数 ,它实际上做了一些事情(我能够根据需要生成)和 (ii) public function 应该环绕内部函数。函数签名在参数方面是相同的。因此,将传递给 public 函数的所有参数传递到内部函数应该很容易。

以下代码片段显示了 public 函数的简化宏生成。我怎样才能正确传递参数? (#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 中提取函数参数元数据。但是,我无法将实际输入动态传递给内部函数。

这在一般情况下是不可能的。考虑以下函数签名,它在参数位置使用解构来提取其第一个参数的字段:

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

由于 Foo 已被解构,宏无法生成将其传递给具有完全相同签名的另一个函数的代码。您可能会尝试重建 Foo 并像 _bar(Foo { a, b }) 一样调用 _bar,但由于 .. 某些字段已永久丢失且无法重建。

这里唯一剩下的选项是从模式中提取绑定集(这不是微不足道的),并生成一个函数,将每个绑定作为单独的参数。但这也是不可能的。我们必须生成像 fn _bar(a: A, b: B) 这样的签名,其中 ABab 的类型。但是宏看不到类型 Foo 的声明,因此无法确定这些类型是什么。

备选方案

  • 可以支持每个参数都具有 $ident: $type 形式的常见情况。在这种情况下,您需要做的就是检查每个 syn::FnArg 上的 pat 字段是否为 Pat::Ident 并获取参数的名称。然后你可以为 _bar 生成一个匹配的签名并将每个参数传入。

  • 如果没有外部代码需要调用_bar,可以做成by-move闭包:

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

    这应该继承外层函数的所有绑定 无缝,但外部函数将不再能够使用它们 之后,这可能会破坏交易。

首先:谢谢@AlphaModder!

因为我不想支持解构案例,所以我选择了您的第一个替代方案。我想解释一下我是如何最终实现我想要的,以便有类似问题的人能够比我更快地解决它。

首先,我使用 ItemFn-Struct 而不是使用 parse_quote!-Macro 构造函数本身:

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