phlow_derive/
lib.rs

1#[macro_use]
2extern crate quote;
3#[macro_use]
4extern crate syn;
5
6use proc_macro::TokenStream;
7
8use proc_macro2::Literal;
9use rust_format::Formatter;
10use syn::parse::Parser;
11use syn::punctuated::Punctuated;
12use syn::{ImplItem, ImplItemFn, ItemImpl, Path, PathArguments, Type};
13
14#[proc_macro_attribute]
15pub fn extensions(args: TokenStream, item: TokenStream) -> TokenStream {
16    let input = parse_macro_input!(item as ItemImpl);
17
18    let tokens = args.clone();
19    let parser = Punctuated::<Path, Token![,]>::parse_separated_nonempty;
20    let mut parsed = parser
21        .parse(tokens)
22        .unwrap()
23        .into_iter()
24        .collect::<Vec<Path>>();
25    if parsed.len() != 2 {
26        panic!("Must contain two arguments: extensions package and a target type");
27    }
28
29    let category = parsed.remove(0);
30    let target_type = parsed.remove(0);
31
32    let reflection_impl =
33        generate_phlow_implementation_for_external_type(input, category, target_type);
34
35    TokenStream::from(reflection_impl)
36}
37
38fn extract_generics(t: &Type) -> Option<proc_macro2::TokenStream> {
39    let tokens = match t {
40        Type::Path(path) => {
41            let segments = &path.path.segments;
42            if segments.len() != 1 {
43                panic!(
44                    "Only supports single segments path, but was: {:?}",
45                    segments
46                );
47            }
48            match &segments[0].arguments {
49                PathArguments::None => None,
50                PathArguments::AngleBracketed(angle) => Some(quote! { #angle }),
51                PathArguments::Parenthesized(_) => None,
52            }
53        }
54        _ => {
55            panic!("Unsupported type: {:?}", t)
56        }
57    };
58
59    tokens
60}
61
62fn generate_phlow_implementation_for_external_type(
63    implementation: ItemImpl,
64    extension_category: Path,
65    extension_target_type: Path,
66) -> proc_macro2::TokenStream {
67    let self_type = &implementation.self_ty;
68    let extension_struct_name = quote! { #self_type };
69    let extension_category_type_name = quote! { #extension_category };
70    let target_type_name = quote! { #extension_target_type };
71
72    let generics_with_bounds = &implementation.generics;
73    let generics = extract_generics(&implementation.self_ty);
74
75    let struct_impl = if generics.is_some() {
76        quote! { pub struct #extension_struct_name(std::marker::PhantomData #generics ); }
77    } else {
78        quote! { pub struct #extension_struct_name; }
79    };
80
81    let phlow_methods = generate_phlow_methods(
82        extension_struct_name.clone(),
83        target_type_name.clone(),
84        &implementation,
85    );
86
87    quote! {
88        #struct_impl
89        #implementation
90
91        impl #generics_with_bounds phlow::Phlow<crate::#extension_category_type_name> for #target_type_name {
92            #phlow_methods
93
94            fn phlow_extension() -> Option<phlow::PhlowExtension> {
95                Some(phlow::PhlowExtension::new::<crate::#extension_category_type_name, Self>())
96            }
97        }
98    }
99}
100
101fn generate_phlow_methods(
102    extension_container_type: proc_macro2::TokenStream,
103    target_type: proc_macro2::TokenStream,
104    implementation: &ItemImpl,
105) -> proc_macro2::TokenStream {
106    let view_methods: Vec<&ImplItemFn> = implementation
107        .items
108        .iter()
109        .map(|each| match each {
110            ImplItem::Fn(method) => Some(method),
111            _ => None,
112        })
113        .filter(|each| each.is_some())
114        .map(|each| each.unwrap())
115        .filter(is_view_method)
116        .collect();
117
118    let get_views = view_methods
119        .into_iter()
120        .map(|each_method| {
121            let name_ident = &each_method.sig.ident;
122            let method_name = quote! { #name_ident };
123
124            let method_name_string = Literal::string(&method_name.to_string());
125
126            let full_method_name_string = Literal::string(&format!(
127                "{}::{}",
128                extension_container_type.to_string(),
129                method_name.to_string()
130            ));
131
132            let formatted = get_source_code(each_method);
133            let source_code = Literal::string(formatted.as_str());
134
135            quote! {
136                phlow::PhlowViewMethod {
137                    method: std::sync::Arc::new(| object: &phlow::PhlowObject, method: &phlow::PhlowViewMethod | {
138                        if let Some(typed_reference) = object.value_ref::<#target_type>() {
139                            let view = <#extension_container_type> :: #method_name (
140                                &typed_reference,
141                                phlow::PhlowProtoView::new(object.clone(), method.clone()));
142                            Some(Box::new(view))
143                        } else {
144                            phlow::log::warn!("Failed to cast object of type {} to {} when building a view {}",
145                                object.value_type_name(),
146                                std::any::type_name::<#target_type>(),
147                                #full_method_name_string);
148                            None
149                        }
150                    }),
151                    extension: extension.clone(),
152                    full_method_name:  #full_method_name_string.to_string(),
153                    method_name:  #method_name_string.to_string(),
154                    source_code: #source_code.to_string()
155                }
156            }
157        })
158        .collect::<Vec<proc_macro2::TokenStream>>();
159
160    quote! {
161        fn phlow_view_methods(extension: &phlow::PhlowExtension) -> Vec<phlow::PhlowViewMethod> {
162            vec![#(#get_views),*]
163        }
164    }
165}
166
167fn is_view_method(method: &&ImplItemFn) -> bool {
168    method.attrs.iter().any(|_each| true)
169}
170
171fn get_source_code(each_method: &ImplItemFn) -> String {
172    let token_stream = quote! { #each_method };
173
174    let config = rust_format::Config::new_str()
175        .edition(rust_format::Edition::Rust2021)
176        .option("reorder_imports", "false")
177        .option("reorder_modules", "false")
178        .option("max_width", "85");
179    let rust_fmt = rust_format::RustFmt::from_config(config);
180    rust_fmt.format_tokens(token_stream).unwrap()
181}
182
183#[proc_macro_attribute]
184pub fn view(_attr: TokenStream, item: TokenStream) -> TokenStream {
185    item
186}