#![deny(rustdoc::invalid_rust_codeblocks)]
#![feature(let_chains)]
#![feature(proc_macro_diagnostic)]
#![recursion_limit = "256"]
#![allow(unused)]
use proc_macro::{Diagnostic, TokenStream};
use quote::quote;
use syn::{
parse::Parse, parse_macro_input, spanned::Spanned, FnArg, GenericParam, Generics, Ident,
ImplItem, ImplItemFn, ItemImpl, PatType, Receiver, ReturnType, TypeParam,
};
const FUNCTION_TEST_DATA_ATTRIBUTE_NAME: &str = "test_data";
fn get_generics(generics: Generics) -> Vec<TypeParam> {
generics
.params
.into_iter()
.filter_map(|generic| {
if let GenericParam::Type(function) = generic {
Some(function)
} else {
None
}
})
.collect()
}
#[derive(Debug)]
struct TestedFns {
generics: Vec<TypeParam>,
functions: Vec<ImplItemFn>,
}
impl Parse for TestedFns {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let tested_impl_block: ItemImpl = input.parse()?;
let generics = get_generics(tested_impl_block.generics);
let functions = tested_impl_block
.items
.into_iter()
.filter_map(|item| {
if let ImplItem::Fn(function) = item
&& function
.attrs
.iter()
.all(|attr| attr.path().is_ident(FUNCTION_TEST_DATA_ATTRIBUTE_NAME))
{
Some(function)
} else {
None
}
})
.collect();
Ok(TestedFns {
generics,
functions,
})
}
}
#[derive(Debug)]
enum TestedMethod {
StaticMethod {
name: Ident,
generics: Vec<TypeParam>,
args: Result<Vec<PatType>, Diagnostic>,
return_type: ReturnType,
},
InstanceMethod {
name: Ident,
generics: Vec<TypeParam>,
self_type: Receiver,
args: Result<Vec<PatType>, Diagnostic>,
return_type: ReturnType,
},
}
impl From<TestedFns> for Vec<TestedMethod> {
fn from(mut value: TestedFns) -> Self {
value
.functions
.into_iter()
.map(|function| function.sig)
.map(|mut signature| {
let name = signature.ident.clone();
let mut generics = get_generics(signature.generics.clone());
generics.append(&mut value.generics);
let return_type = signature.output.clone();
if let Some(self_type) = signature.receiver() {
TestedMethod::InstanceMethod {
name,
generics,
self_type: self_type.clone(),
args: {
signature.inputs.pop();
signature
.inputs
.clone()
.into_iter()
.map(|arg| {
if let FnArg::Receiver(_) = arg {
Err(signature.span().unwrap().error(
"An instance method cannot have 2 or more self parameters",
))
} else if let FnArg::Typed(arg_type) = arg {
Ok(arg_type)
} else {
unreachable!(
"Fnarg is either `Receiver` or `Typed`, not something else"
);
}
})
.collect()
},
return_type,
}
} else {
TestedMethod::StaticMethod {
name,
generics,
args: signature
.inputs
.clone()
.into_iter()
.map(|arg| {
if let FnArg::Typed(arg_type) = arg {
Ok(arg_type)
} else {
Err(signature
.span()
.unwrap()
.error("Cannot have a self type in a static method."))
}
})
.collect(),
return_type,
}
}
})
.collect()
}
}
#[proc_macro_attribute]
pub fn test_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
let all_method: Vec<TestedMethod> = parse_macro_input!(item as TestedFns).into();
println!("{all_method:#?}");
TokenStream::new()
}
mod sequence;