use proc_macro2::TokenStream;
use unsynn::*;
use super::func_params::Parameter;
use super::generics::GenericParams;
use super::ret_type::ReturnType;
use crate::{Attribute, AttributeInner};
keyword! {
pub KFn = "fn";
}
unsynn! {
pub struct FnSignature {
pub attributes: Option<Many<Attribute>>,
pub _fn_keyword: KFn,
pub name: Ident,
pub generics: Option<GenericParams>,
pub params: ParenthesisGroup,
pub return_type: Option<ReturnType>,
pub body: BraceGroup,
}
}
pub struct FunctionSignature {
pub name: Ident,
pub generics: Option<TokenStream>,
pub parameters: Vec<Parameter>,
pub return_type: TokenStream,
pub body: TokenStream,
pub documentation: Vec<String>,
}
pub fn extract_documentation(attributes: &Option<Many<Attribute>>) -> Vec<String> {
let attrs = match attributes {
Some(many_attrs) => many_attrs.as_slice(),
None => return Vec::new(),
};
let mut doc_lines = Vec::new();
for attr in attrs.iter() {
match &attr.value.body.content {
AttributeInner::Doc(doc_attr) => {
let doc_str = doc_attr.value.as_str().replace("\\\"", "\"");
doc_lines.push(doc_str);
}
AttributeInner::Facet(_) | AttributeInner::Repr(_) | AttributeInner::Any(_) => {
}
}
}
doc_lines
}
pub fn parse_function_signature(input: TokenStream) -> FunctionSignature {
let mut it = input.to_token_iter();
match it.parse::<FnSignature>() {
Ok(sig) => {
let params_content = {
let params_tokens = sig.params.to_token_stream();
let mut it = params_tokens.to_token_iter();
if let Ok(TokenTree::Group(group)) = it.parse::<TokenTree>() {
group.stream()
} else {
TokenStream::new()
}
};
let parameters = super::func_params::parse_fn_parameters(params_content);
let generics = sig.generics.map(|g| g.to_token_stream());
let return_type = sig
.return_type
.map(|rt| rt.return_type.to_token_stream())
.unwrap_or_else(|| quote::quote! { () });
let body = sig.body.to_token_stream();
let documentation = extract_documentation(&sig.attributes);
FunctionSignature {
name: sig.name,
generics,
parameters,
return_type,
body,
documentation,
}
}
Err(err) => {
panic!("Failed to parse function signature: {err}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_simple_function() {
let input = quote! {
fn add(x: i32, y: i32) -> i32 {
x + y
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "add");
assert!(parsed.generics.is_none());
assert_eq!(parsed.parameters.len(), 2);
assert_eq!(parsed.parameters[0].name.to_string(), "x");
assert_eq!(parsed.parameters[1].name.to_string(), "y");
assert_eq!(parsed.return_type.to_string().trim(), "i32");
}
#[test]
fn test_generic_function() {
let input = quote! {
fn generic_add<T>(x: T, y: T) -> T {
x + y
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "generic_add");
assert!(parsed.generics.is_some());
assert_eq!(parsed.parameters.len(), 2);
assert_eq!(parsed.return_type.to_string().trim(), "T");
}
#[test]
fn test_no_params_function() {
let input = quote! {
fn no_params() -> &'static str {
"hello"
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "no_params");
assert_eq!(parsed.parameters.len(), 0);
assert_eq!(parsed.return_type.to_string().trim(), "& 'static str");
}
#[test]
fn test_no_return_type() {
let input = quote! {
fn no_return(x: i32) {
println!("{}", x);
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "no_return");
assert_eq!(parsed.parameters.len(), 1);
assert_eq!(parsed.return_type.to_string().trim(), "()");
}
#[test]
fn test_function_with_doc_comments() {
let input = quote! {
#[doc = " This is a test function"]
#[doc = " that does addition of two numbers"]
fn add(x: i32, y: i32) -> i32 {
x + y
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "add");
assert!(parsed.generics.is_none());
assert_eq!(parsed.parameters.len(), 2);
assert_eq!(parsed.parameters[0].name.to_string(), "x");
assert_eq!(parsed.parameters[1].name.to_string(), "y");
assert_eq!(parsed.return_type.to_string().trim(), "i32");
assert!(!parsed.documentation.is_empty());
assert_eq!(parsed.documentation.len(), 2); assert_eq!(parsed.documentation[0], " This is a test function");
assert_eq!(
parsed.documentation[1],
" that does addition of two numbers"
);
}
#[test]
fn test_function_with_single_doc_comment() {
let input = quote! {
#[doc = " Single line documentation"]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
};
let parsed = parse_function_signature(input);
assert!(!parsed.documentation.is_empty());
assert_eq!(parsed.documentation.len(), 1);
assert_eq!(parsed.documentation[0], " Single line documentation");
}
#[test]
fn test_function_without_doc_comments() {
let input = quote! {
fn no_docs(x: i32) -> i32 {
x * 2
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "no_docs");
assert!(parsed.documentation.is_empty());
}
#[test]
fn test_function_with_mixed_attributes() {
let input = quote! {
#[doc = " Documentation comment"]
#[derive(Debug)]
#[doc = " More documentation"]
fn mixed_attrs() {
println!("test");
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "mixed_attrs");
assert!(!parsed.documentation.is_empty());
assert_eq!(parsed.documentation.len(), 2);
assert_eq!(parsed.documentation[0], " Documentation comment");
assert_eq!(parsed.documentation[1], " More documentation");
}
#[test]
fn test_generic_function_with_docs() {
let input = quote! {
#[doc = " Generic function that adds two values"]
fn generic_add<T: Add<Output = T>>(x: T, y: T) -> T {
x + y
}
};
let parsed = parse_function_signature(input);
assert_eq!(parsed.name.to_string(), "generic_add");
assert!(parsed.generics.is_some());
assert!(!parsed.documentation.is_empty());
assert_eq!(parsed.documentation.len(), 1);
assert_eq!(
parsed.documentation[0],
" Generic function that adds two values"
);
}
}