Rust - Syn 和 Quote

问题描述 投票:0回答:1

我正在尝试了解 syn 和 quote 库的工作原理以及解析和代码生成的基本原理。因此,我编写了一个简单的宏来将 Rust 函数转换为 .json 模式。下面给出了宏。除了有Where 子句的情况之外,它工作得很好。




use proc_macro::{self, TokenStream};
use quote::{quote, format_ident};
use syn::{
    parse_macro_input, ItemFn, ReturnType, Attribute, parse::Parse, parse::ParseStream, FnArg, Generics, Visibility,
    LitStr, Lit,Expr, Meta, Result
};


// Custom attribute parser
struct MacroAttributes {
    description: Option<String>,
}

impl Parse for MacroAttributes {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let description = if input.peek(LitStr) {
            let desc_lit = input.parse::<LitStr>()?;
            Some(desc_lit.value())
        } else {
            None
        };

        Ok(MacroAttributes { description })
    }
}

// Function to extract documentation comments from function attributes.
fn extract_doc_comments(attrs: &[Attribute]) -> Result<Option<String>> {
    let mut docs = Vec::new();
    for attr in attrs {
        match &attr.meta {
            Meta::NameValue(nv) if nv.path.is_ident("doc") => {
                if let Expr::Lit(expr_lit) = &nv.value {
                    if let Lit::Str(lit) = &expr_lit.lit {
                        docs.push(lit.value());
                    }
                }
            },
            _ => {}
        }
    }

    if docs.is_empty() {
        Ok(None)
    } else {
        Ok(Some(docs.join("\n")))
    }
}


// Function to generate JSON-like schema information for generics in a function.
fn process_generics(generics: &Generics) -> Option<proc_macro2::TokenStream> {
    let type_params: Vec<_> = generics.type_params().map(|param| {
        let ident = &param.ident;                       // Identifier of the type parameter.
        let bounds = param.bounds.iter().map(|b| quote! {#b}).collect::<Vec<_>>();
        quote! {
            {"name": stringify!(#ident), "bounds": [#(#bounds),*]}
        }
    }).collect();

    if type_params.is_empty() {
        None
    } else {
        Some(quote! { [#(#type_params),*] })
    }
}

// Similar function for where clauses.
fn process_where_clause(generics: &Generics) -> Option<proc_macro2::TokenStream> {
    let where_clauses: Vec<_> = generics.where_clause.as_ref().map_or(Vec::new(), |where_clause| {
        where_clause.predicates.iter().map(|predicate| {
            quote! { stringify!(#predicate) }
        }).collect()
    });

    if where_clauses.is_empty() {
        None
    } else {
        Some(quote! {
            "where_clauses": [#(#where_clauses),*]
        })
    }
}


// Similar function for lifetimes.
fn process_lifetimes(generics: &Generics) -> Option<proc_macro2::TokenStream> {
    let lifetimes: Vec<_> = generics.lifetimes().map(|lifetime| {
        let ident = &lifetime.lifetime.ident;
        quote! {stringify!(#ident)}
    }).collect();

    if lifetimes.is_empty() {
        None
    } else {
        Some(quote! { [#(#lifetimes),*] })
    }
}



// The `myschema` macro function that processes attributes and the function to which it is applied.
#[proc_macro_attribute]
pub fn myschema(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attrs = parse_macro_input!(attr as MacroAttributes);

    let input_fn = parse_macro_input!(item as ItemFn);
    let fn_name = &input_fn.sig.ident;
    let visibility_str = match &input_fn.vis {
        Visibility::Public(_) => "public",
        Visibility::Restricted(res) => if res.path.is_ident("crate") { "crate" } else { "restricted" },
        Visibility::Inherited => "private",
    };
    let generics_info = process_generics(&input_fn.sig.generics);
    let lifetimes_info = process_lifetimes(&input_fn.sig.generics);
    let where_info = process_where_clause(&input_fn.sig.generics);
    let return_type = match &input_fn.sig.output {
        ReturnType::Default => None,
        ReturnType::Type(_, t) => Some(quote! {#t}),
    };
    let is_async = input_fn.sig.asyncness.is_some();
    let doc_comments = match extract_doc_comments(&input_fn.attrs) {
        Ok(Some(docs)) => docs,
        Ok(None) => String::new(),
        Err(_) => return TokenStream::from(quote! { compile_error!("Error parsing doc comments"); })
    };

    // Generate the JSON-like schema for the function parameters.
    let inputs: Vec<_> = input_fn.sig.inputs.iter().map(|arg| {
        if let FnArg::Typed(pat_type) = arg {
            let pat = &pat_type.pat;
            let ty = &pat_type.ty;
            quote! {
                {
                    "name": stringify!(#pat),
                    "type": stringify!(#ty)
                }
            }
        } else {
            quote! {}
        }
    }).collect();

    let mut schema_fields = vec![
        quote! { "name": stringify!(#fn_name) },
        quote! { "visibility": #visibility_str },
        quote! { "documentation": #doc_comments },

    ];

    if let Some(desc) = attrs.description {
        schema_fields.push(quote! { "description": #desc });
    }

    if !inputs.is_empty() {
        schema_fields.push(quote! { "parameters": [#(#inputs),*] });
    }

    if let Some(rt) = return_type {
        schema_fields.push(quote! { "return_type": stringify!(#rt) });
    }

    if is_async {
        schema_fields.push(quote! { "is_async": true });
    }

    if let Some(generics) = generics_info {
        schema_fields.push(quote! { "generics": #generics });
    }


    if let Some(where_clause) = where_info {
        schema_fields.push(quote! { "where_clause": #where_clause });
    }

    if let Some(lifetimes) = lifetimes_info {
        schema_fields.push(quote! { "lifetimes": #lifetimes });
    }

    let schema_function_name = format_ident!("schema_{}", fn_name);

    let schema_fn = quote! {
        pub fn #schema_function_name() -> ::std::result::Result<String, serde_json::Error> {
            let schema = serde_json::json!({ #(#schema_fields),* });
            serde_json::to_string_pretty(&schema)
        }
    };

    let output = quote! {
        #input_fn
        #schema_fn
    };

    output.into()
}


我的测试如下所示。只有第一种情况失败了。我能做什么?

use rust_macro::myschema; // Import the schema macro

// This fails
#[myschema("Generic multiplication")]
pub fn multiply<T>(x: T, y: T) -> T
where
    T: std::ops::Mul<Output = T> + Copy,
{
    x * y
}

/// My async function
#[myschema("Asynchronous fetch")]
pub async fn fetch_data(url: &str) -> Result<String, &'static str> {
    Ok("data".to_string())
}

#[myschema("Multiplies two numbers")]
pub fn multiply_doc(x: i32, y: i32) -> i32 {
    x * y
}


#[myschema("Determines the longest of two string slices")]
pub fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

#[myschema("Private function")]
fn private_function() -> bool {
    true
}

fn main() {
    if let Ok(schema_output) = schema_multiply::<i32>() {
        println!("Generated schema: {}", schema_output);
    }

    if let Ok(schema_output) = schema_fetch_data() {
        println!("Generated schema: {}", schema_output);
    }

    if let Ok(schema_output) = schema_multiply_doc() {
        println!("Generated schema: {}", schema_output);
    }

    if let Ok(schema_output) = schema_longest() {
        println!("Generated schema: {}", schema_output);
    }

    if let Ok(schema_output) = schema_private_function() {
        println!("Generated schema: {}", schema_output);
    }
}

错误是:

error[E0107]: function takes 0 generic arguments but 1 generic argument was supplied
  --> src/main.rs:34:32
   |
34 |     if let Ok(schema_output) = schema_multiply::<i32>() {
   |                                ^^^^^^^^^^^^^^^------- help: remove these generics
   |                                |
   |                                expected 0 generic arguments
   |
note: function defined here, with 0 generic parameters
  --> src/main.rs:4:8
   |
4  | pub fn multiply<T>(x: T, y: T) -> T
   |        ^^^^^^^^
generics rust rust-diesel
1个回答
0
投票

要解决此问题,您需要调整 schema_multiply 函数,以便在调用时不需要任何通用参数。您已经在宏输出中正确完成了此操作,因此所需的主要更改是如何在主函数中调用 schema_multiply。

if let Ok(schema_output) = schema_multiply() {
    println!("Generated schema: {}", schema_output);
}
© www.soinside.com 2019 - 2024. All rights reserved.