use quote::ToTokens; use syn::parse::Parse; use syn::punctuated::Punctuated; use syn::token::Paren; use syn::{parenthesized, parse_str, Ident, Token, TraitBound, Type}; /// A function trait. `dyn Fn(u32) -> String` #[derive(Debug, Clone, PartialEq, Eq)] pub struct FnTrait { pub dyn_token: Token![dyn], pub trait_ident: Ident, pub paren_token: Paren, pub inputs: Punctuated, pub r_arrow_token: Token![->], pub output: Type, pub trait_bounds: Punctuated, } impl FnTrait { pub fn add_trait_bound(&mut self, trait_bound: TraitBound) { self.trait_bounds.push(trait_bound); } } impl Parse for FnTrait { fn parse(input: syn::parse::ParseStream) -> syn::Result { let dyn_token = input.parse::()?; let trait_ident = input.parse::()?; if trait_ident.to_string().as_str() != "Fn" { return Err(syn::Error::new(trait_ident.span(), "Expected 'Fn'")); } let content; let paren_token = parenthesized!(content in input); let inputs = content.parse_terminated(Type::parse)?; let r_arrow_token = input.parse::]>()?; let output = input.parse::()?; Ok(Self { dyn_token, trait_ident, paren_token, inputs, r_arrow_token, output, trait_bounds: Punctuated::new(), }) } } impl ToTokens for FnTrait { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.dyn_token.to_tokens(tokens); self.trait_ident.to_tokens(tokens); self.paren_token.surround(tokens, |tokens_inner| { self.inputs.to_tokens(tokens_inner); }); self.r_arrow_token.to_tokens(tokens); self.output.to_tokens(tokens); if !self.trait_bounds.is_empty() { let plus: Token![+] = parse_str("+").unwrap(); plus.to_tokens(tokens); self.trait_bounds.to_tokens(tokens); } } } #[cfg(test)] mod tests { use std::error::Error; use quote::{format_ident, quote}; use syn::token::{Dyn, RArrow}; use syn::{parse2, Path, PathSegment, TypePath}; use super::*; fn create_path(segments: &[PathSegment]) -> Path { Path { leading_colon: None, segments: segments.iter().cloned().collect(), } } fn create_type(path: Path) -> Type { Type::Path(TypePath { qself: None, path }) } #[test] fn can_parse_fn_trait() -> Result<(), Box> { assert_eq!( parse2::(quote! { dyn Fn(String, u32) -> Handle })?, FnTrait { dyn_token: Dyn::default(), trait_ident: format_ident!("Fn"), paren_token: Paren::default(), inputs: Punctuated::from_iter(vec![ create_type(create_path(&[PathSegment::from(format_ident!( "String" ))])), create_type(create_path(&[PathSegment::from(format_ident!("u32"))])) ]), r_arrow_token: RArrow::default(), output: create_type(create_path(&[PathSegment::from(format_ident!( "Handle" ))])), trait_bounds: Punctuated::new() } ); assert!(parse2::(quote! { Fn(u32) -> Handle }) .is_err()); Ok(()) } #[test] fn can_parse_fn_trait_to_tokens() { assert_eq!( FnTrait { dyn_token: Dyn::default(), trait_ident: format_ident!("Fn"), paren_token: Paren::default(), inputs: Punctuated::from_iter(vec![ create_type(create_path(&[PathSegment::from(format_ident!( "Bread" ))])), create_type(create_path(&[PathSegment::from(format_ident!( "Cheese" ))])), create_type(create_path(&[PathSegment::from(format_ident!( "Tomatoes" ))])) ]), r_arrow_token: RArrow::default(), output: create_type(create_path(&[PathSegment::from(format_ident!( "Taco" ))])), trait_bounds: Punctuated::new() } .into_token_stream() .to_string(), "dyn Fn (Bread , Cheese , Tomatoes) -> Taco".to_string() ); } }