wick_operation/
lib.rs

1// !!START_LINTS
2// Wick lints
3// Do not change anything between the START_LINTS and END_LINTS line.
4// This is automatically generated. Add exceptions after this section.
5#![allow(unknown_lints)]
6#![deny(
7  clippy::await_holding_lock,
8  clippy::borrow_as_ptr,
9  clippy::branches_sharing_code,
10  clippy::cast_lossless,
11  clippy::clippy::collection_is_never_read,
12  clippy::cloned_instead_of_copied,
13  clippy::cognitive_complexity,
14  clippy::create_dir,
15  clippy::deref_by_slicing,
16  clippy::derivable_impls,
17  clippy::derive_partial_eq_without_eq,
18  clippy::equatable_if_let,
19  clippy::exhaustive_structs,
20  clippy::expect_used,
21  clippy::expl_impl_clone_on_copy,
22  clippy::explicit_deref_methods,
23  clippy::explicit_into_iter_loop,
24  clippy::explicit_iter_loop,
25  clippy::filetype_is_file,
26  clippy::flat_map_option,
27  clippy::format_push_string,
28  clippy::fn_params_excessive_bools,
29  clippy::future_not_send,
30  clippy::get_unwrap,
31  clippy::implicit_clone,
32  clippy::if_then_some_else_none,
33  clippy::impl_trait_in_params,
34  clippy::implicit_clone,
35  clippy::inefficient_to_string,
36  clippy::inherent_to_string,
37  clippy::iter_not_returning_iterator,
38  clippy::large_types_passed_by_value,
39  clippy::large_include_file,
40  clippy::let_and_return,
41  clippy::manual_assert,
42  clippy::manual_ok_or,
43  clippy::manual_split_once,
44  clippy::manual_let_else,
45  clippy::manual_string_new,
46  clippy::map_flatten,
47  clippy::map_unwrap_or,
48  clippy::missing_enforced_import_renames,
49  clippy::missing_assert_message,
50  clippy::missing_const_for_fn,
51  clippy::must_use_candidate,
52  clippy::mut_mut,
53  clippy::needless_for_each,
54  clippy::needless_option_as_deref,
55  clippy::needless_pass_by_value,
56  clippy::needless_collect,
57  clippy::needless_continue,
58  clippy::non_send_fields_in_send_ty,
59  clippy::nonstandard_macro_braces,
60  clippy::option_if_let_else,
61  clippy::option_option,
62  clippy::rc_mutex,
63  clippy::redundant_else,
64  clippy::same_name_method,
65  clippy::semicolon_if_nothing_returned,
66  clippy::str_to_string,
67  clippy::string_to_string,
68  clippy::too_many_lines,
69  clippy::trivially_copy_pass_by_ref,
70  clippy::trivial_regex,
71  clippy::try_err,
72  clippy::unnested_or_patterns,
73  clippy::unused_async,
74  clippy::unwrap_or_else_default,
75  clippy::useless_let_if_seq,
76  bad_style,
77  clashing_extern_declarations,
78  dead_code,
79  deprecated,
80  explicit_outlives_requirements,
81  improper_ctypes,
82  invalid_value,
83  missing_copy_implementations,
84  missing_debug_implementations,
85  mutable_transmutes,
86  no_mangle_generic_items,
87  non_shorthand_field_patterns,
88  overflowing_literals,
89  path_statements,
90  patterns_in_fns_without_body,
91  private_in_public,
92  trivial_bounds,
93  trivial_casts,
94  trivial_numeric_casts,
95  type_alias_bounds,
96  unconditional_recursion,
97  unreachable_pub,
98  unsafe_code,
99  unstable_features,
100  unused,
101  unused_allocation,
102  unused_comparisons,
103  unused_import_braces,
104  unused_parens,
105  unused_qualifications,
106  while_true,
107  missing_docs
108)]
109#![warn(clippy::exhaustive_enums)]
110#![allow(unused_attributes, clippy::derive_partial_eq_without_eq, clippy::box_default)]
111// !!END_LINTS
112// Add exceptions here
113#![allow(missing_docs)]
114
115use proc_macro::TokenStream;
116use proc_macro2::{Ident, Span};
117use proc_macro_crate::crate_name;
118use quote::{quote, ToTokens};
119use syn::parse::Parser;
120use syn::punctuated::Punctuated;
121use syn::token::PathSep;
122use syn::{parse_macro_input, FnArg, ItemFn, Meta, PathSegment, ReturnType, Token};
123
124#[derive(Debug, Clone, Copy)]
125enum Adapter {
126  BinaryInterleavedPairs,
127  BinaryPairedRightStream,
128  UnarySimple,
129  UnaryWithOutputs,
130  GenericRaw,
131}
132
133impl Adapter {
134  fn from_segments(s: &Punctuated<PathSegment, PathSep>) -> Option<Self> {
135    let mut s = s.iter().rev();
136    let last = s.next()?.ident.to_string();
137    match last.as_str() {
138      "binary_interleaved_pairs" => Some(Adapter::BinaryInterleavedPairs),
139      "binary_paired_right_stream" => Some(Adapter::BinaryPairedRightStream),
140      "unary_simple" => Some(Adapter::UnarySimple),
141      "unary_with_outputs" => Some(Adapter::UnaryWithOutputs),
142      "generic_raw" => Some(Adapter::GenericRaw),
143      _ => None,
144    }
145  }
146
147  fn available_adapters() -> Vec<Self> {
148    vec![
149      Adapter::BinaryInterleavedPairs,
150      Adapter::BinaryPairedRightStream,
151      Adapter::UnarySimple,
152      Adapter::UnaryWithOutputs,
153      Adapter::GenericRaw,
154    ]
155  }
156}
157
158impl std::fmt::Display for Adapter {
159  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160    match self {
161      Adapter::BinaryInterleavedPairs => write!(f, "binary_interleaved_pairs"),
162      Adapter::BinaryPairedRightStream => write!(f, "binary_paired_right_stream"),
163      Adapter::UnarySimple => write!(f, "unary_simple"),
164      Adapter::UnaryWithOutputs => write!(f, "unary_with_outputs"),
165      Adapter::GenericRaw => write!(f, "generic_raw"),
166    }
167  }
168}
169
170impl ToTokens for Adapter {
171  fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
172    Ident::new(&self.to_string(), Span::call_site()).to_tokens(tokens);
173  }
174}
175
176#[proc_macro_attribute]
177pub fn operation(attr: TokenStream, item: TokenStream) -> TokenStream {
178  let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
179  let args = match parser.parse2(attr.into()) {
180    Ok(args) => args,
181    Err(e) => panic!("{}", e),
182  };
183
184  let input = parse_macro_input!(item as ItemFn);
185
186  let args = args.into_iter().collect::<Vec<_>>();
187
188  let adapter = match args.as_slice() {
189    [Meta::Path(path)] => Adapter::from_segments(&path.segments),
190    _ => {
191      panic!(
192        "unsupported attributes supplied: {}, available adapters are {}",
193        quote! { args },
194        Adapter::available_adapters()
195          .iter()
196          .map(|a| a.to_string())
197          .collect::<Vec<_>>()
198          .join(", ")
199      );
200    }
201  }
202  .unwrap_or_else(|| {
203    panic!(
204      "unsupported attributes supplied: {}, available adapters are {}",
205      quote! { args },
206      Adapter::available_adapters()
207        .iter()
208        .map(|a| a.to_string())
209        .collect::<Vec<_>>()
210        .join(", ")
211    )
212  });
213  #[allow(clippy::expect_used)]
214  let _ = crate_name("wick-component").expect("wick-component needs to be added in `Cargo.toml`");
215
216  expand_wrapper(adapter, &input)
217}
218
219/// Emit code for a wrapper function around a test function.
220fn expand_wrapper(adapter: Adapter, wrappee: &ItemFn) -> TokenStream {
221  let attrs = &wrappee.attrs;
222  let async_ = &wrappee.sig.asyncness;
223  let fn_body = &wrappee.block;
224  let fn_name = &wrappee.sig.ident;
225  let fn_arg_types_outer = &wrappee
226    .sig
227    .inputs
228    .iter()
229    .cloned()
230    .map(|mut arg| {
231      if let FnArg::Typed(pat) = &mut arg {
232        #[allow(clippy::single_match)]
233        match pat.pat.as_mut() {
234          syn::Pat::Ident(id) => {
235            id.mutability = None;
236          }
237          _ => {}
238        }
239      }
240      arg
241    })
242    .collect::<Vec<_>>();
243  let fn_arg_types = &wrappee.sig.inputs.iter().collect::<Vec<_>>();
244  let fn_arg_names = &wrappee
245    .sig
246    .inputs
247    .iter()
248    .filter_map(|arg| {
249      if let FnArg::Typed(pat) = arg {
250        match pat.pat.as_ref() {
251          syn::Pat::Ident(id) => Some(&id.ident),
252          _ => None,
253        }
254      } else {
255        None
256      }
257    })
258    .collect::<Vec<_>>();
259
260  let fn_return = match &wrappee.sig.output {
261    ReturnType::Default => quote! {()},
262    ReturnType::Type(_, type_) => quote! {#type_},
263  };
264  let fn_wrapper_name = Ident::new(&format!("{}_wrapper", fn_name), Span::call_site());
265
266  let (async_, await_) = if async_.is_some() {
267    (Some(quote! {async}), Some(quote! {.await}))
268  } else {
269    (None, None)
270  };
271
272  let result = quote! {
273    wick_component::#adapter !(#fn_name => #fn_wrapper_name);
274
275    #[cfg(not(target_family = "wasm"))]
276    #(#attrs)*
277    fn #fn_wrapper_name(#(#fn_arg_types_outer),*) -> std::pin::Pin<Box<dyn std::future::Future<Output = #fn_return> + Send + Sync + 'static>> {
278      Box::pin(async move { #fn_name(#(#fn_arg_names),*)#await_ })
279    }
280
281    #[cfg(target_family = "wasm")]
282    #(#attrs)*
283    fn #fn_wrapper_name(#(#fn_arg_types_outer),*) -> std::pin::Pin<Box<dyn std::future::Future<Output = #fn_return> + 'static>> {
284      Box::pin(async move { #fn_name(#(#fn_arg_names),*)#await_ })
285    }
286
287    #async_ fn #fn_name(#(#fn_arg_types),*) -> #fn_return {
288      #fn_body
289    }
290
291  };
292
293  result.into()
294}