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}