Skip to main content

unipipe_macros/
lib.rs

1use convert_case::{Case, Casing};
2use proc_macro::TokenStream;
3use quote::{ToTokens, format_ident, quote};
4use syn::{
5    AngleBracketedGenericArguments, FnArg, GenericParam, Generics, Ident, ImplGenerics, ImplItem,
6    ImplItemFn, ItemImpl, LitStr, Pat, PatType, PathArguments, Token, Type, TypeGenerics,
7    Visibility, WhereClause, WherePredicate,
8    parse::{Parse, ParseStream},
9    parse_macro_input, parse_quote,
10    punctuated::Punctuated,
11    token::Comma,
12};
13
14struct UniPipeArgs {
15    name: Option<String>,
16    visibility: Option<Visibility>,
17    extensions: Vec<Ident>,
18}
19
20impl Parse for UniPipeArgs {
21    fn parse(input: ParseStream) -> syn::Result<Self> {
22        let name = if input.peek(LitStr) {
23            let string_literal: LitStr = input.parse()?;
24
25            if input.peek(Token![,]) {
26                input.parse::<Token![,]>()?;
27            }
28
29            Some(string_literal.value())
30        } else {
31            None
32        };
33
34        let visibility = if input.peek(Token![pub]) || input.peek(Token![crate]) {
35            Some(input.parse()?)
36        } else {
37            None
38        };
39
40        let extensions = Punctuated::<Ident, Token![,]>::parse_terminated(input)?
41            .into_iter()
42            .collect();
43
44        Ok(UniPipeArgs {
45            name,
46            visibility,
47            extensions,
48        })
49    }
50}
51
52#[proc_macro_attribute]
53pub fn unipipe(attr: TokenStream, item: TokenStream) -> TokenStream {
54    let args = parse_macro_input!(attr as UniPipeArgs);
55    let input = parse_macro_input!(item as ItemImpl);
56
57    let (struct_name, impl_struct_generics, impl_struct_impl_generics) =
58        match &input.self_ty.as_ref() {
59            Type::Path(type_path) => {
60                let segment = type_path
61                    .path
62                    .segments
63                    .last()
64                    .expect("Expected struct name");
65
66                let struct_generics =
67                    if let PathArguments::AngleBracketed(generics) = &segment.arguments {
68                        Some(generics)
69                    } else {
70                        None
71                    };
72
73                (&segment.ident, struct_generics, &input.generics)
74            }
75            _ => panic!("Expected a simple struct name in impl"),
76        };
77
78    let mut constructor_methods = Vec::new();
79
80    for item in &input.items {
81        if let ImplItem::Fn(method) = item
82            && (method.vis == Visibility::Inherited || matches!(method.vis, Visibility::Public(_)))
83        {
84            constructor_methods.push(method);
85        }
86    }
87
88    if constructor_methods.is_empty() {
89        panic!("No public constructor methods found in impl block");
90    }
91
92    let visibility = args.visibility.unwrap_or_else(|| parse_quote!(pub));
93
94    let mut output = quote! {
95        #[allow(clippy::new_without_default)]
96        #input
97    };
98
99    for extension_type in args.extensions {
100        let extension = match extension_type.to_string().as_str() {
101            "iterator" => IteratorExtension::generate(
102                args.name.as_deref(),
103                &visibility,
104                struct_name,
105                impl_struct_generics,
106                impl_struct_impl_generics,
107                &constructor_methods,
108            ),
109            "try_iterator" => TryIteratorExtension::generate(
110                args.name.as_deref(),
111                &visibility,
112                struct_name,
113                impl_struct_generics,
114                impl_struct_impl_generics,
115                &constructor_methods,
116            ),
117            "stream" => StreamExtension::generate(
118                args.name.as_deref(),
119                &visibility,
120                struct_name,
121                impl_struct_generics,
122                impl_struct_impl_generics,
123                &constructor_methods,
124            ),
125            "try_stream" => TryStreamExtension::generate(
126                args.name.as_deref(),
127                &visibility,
128                struct_name,
129                impl_struct_generics,
130                impl_struct_impl_generics,
131                &constructor_methods,
132            ),
133            _ => panic!("Unknown extension type: {extension_type}"),
134        };
135
136        output.extend(extension);
137    }
138
139    output.into()
140}
141
142trait Extension {
143    fn get_name() -> &'static str;
144
145    fn get_pipe_method_name_prefix() -> &'static str;
146
147    fn get_extra_trait_params() -> Vec<proc_macro2::TokenStream>;
148
149    fn get_extra_impl_for_params() -> Vec<proc_macro2::TokenStream>;
150
151    fn generate_trait(
152        visibility: &Visibility,
153        trait_name: &Ident,
154        trait_generics: proc_macro2::TokenStream,
155        trait_ty_generics: &TypeGenerics,
156        struct_path_with_generics: &proc_macro2::TokenStream,
157        where_clause: Option<&WhereClause>,
158        where_clause_predicates: Option<&Punctuated<WherePredicate, Comma>>,
159        impl_generics: &ImplGenerics,
160        method_signatures: Vec<proc_macro2::TokenStream>,
161        methods: Vec<proc_macro2::TokenStream>,
162    ) -> proc_macro2::TokenStream;
163
164    fn generate_trait_method_signature(
165        pipe_method_name: &Ident,
166        method_impl_generics: &ImplGenerics,
167        where_clause: Option<&WhereClause>,
168        args: &[PatType],
169    ) -> proc_macro2::TokenStream;
170
171    fn generate_trait_method(
172        pipe_method_name: &Ident,
173        method_name: &Ident,
174        method_impl_generics: &ImplGenerics,
175        where_clause: Option<&WhereClause>,
176        args: &[PatType],
177        arg_names: &[&Ident],
178        struct_with_generics: &proc_macro2::TokenStream,
179        struct_path_with_generics: &proc_macro2::TokenStream,
180    ) -> proc_macro2::TokenStream;
181
182    fn generate(
183        specified_name: Option<&str>,
184        visibility: &Visibility,
185        struct_name: &Ident,
186        impl_struct_generics: Option<&AngleBracketedGenericArguments>,
187        impl_struct_impl_generics: &Generics,
188        constructor_methods: &[&ImplItemFn],
189    ) -> proc_macro2::TokenStream {
190        let trait_name = format_ident!(
191            "{}UniPipe{}Ext",
192            specified_name.unwrap_or(struct_name.to_string().as_str()),
193            Self::get_name()
194        );
195
196        let where_clause = impl_struct_impl_generics.where_clause.as_ref();
197
198        let (struct_with_generics, struct_path_with_generics) = if impl_struct_generics.is_none() {
199            (quote! { #struct_name }, quote! { #struct_name })
200        } else {
201            (
202                quote! { #struct_name #impl_struct_generics },
203                quote! { #struct_name::#impl_struct_generics },
204            )
205        };
206
207        let trait_generics = {
208            let mut generics = impl_struct_impl_generics.clone();
209
210            let extra_params = Self::get_extra_trait_params();
211
212            generics
213                .params
214                .extend::<Punctuated<GenericParam, Comma>>(parse_quote! {
215                    #(#extra_params),*
216                });
217
218            generics
219        };
220
221        let impl_for_generics = {
222            let mut generics = trait_generics.clone();
223
224            let extra_params = Self::get_extra_impl_for_params();
225
226            generics
227                .params
228                .extend::<Punctuated<GenericParam, Comma>>(parse_quote! {
229                    #(#extra_params),*
230                });
231
232            generics
233        };
234
235        let (trait_generics, trait_ty_generics, _) = trait_generics.split_for_impl();
236
237        // We need to pass the self type down to pick up its implied bounds in the extension trait
238        // and impl. Using a default value will avoid needing to repeat it in every trait impl.
239        //
240        // Unfortunately, this default value isn't retained once the generics go through
241        // `split_for_impl`, and the trait generics setup above may be incomplete. To work around
242        // this, we thus push this new generic parameter where it's needed (only at the end of the
243        // *trait*'s generics list), but it's slightly inefficient.
244        let mut trait_generics: Generics = parse_quote! { #trait_generics };
245        let self_ty_param: GenericParam = parse_quote! {
246            _SELF: ::unipipe::UniPipe = #struct_with_generics
247        };
248        trait_generics.params.push(self_ty_param);
249
250        let (impl_generics, _, _) = impl_for_generics.split_for_impl();
251
252        let where_clause_predicates = where_clause.map(|clause| &clause.predicates);
253
254        let mut trait_method_signatures = Vec::new();
255        let mut trait_method_bodies = Vec::new();
256
257        for method in constructor_methods {
258            let args: Vec<_> = method
259                .sig
260                .inputs
261                .iter()
262                .enumerate()
263                .filter_map(|(index, arg)| match arg {
264                    FnArg::Typed(pat_type) => Some(simplify_arg_pat_type(pat_type, index)),
265                    FnArg::Receiver(_) => None,
266                })
267                .collect();
268
269            if args.len() != method.sig.inputs.len() {
270                continue;
271            }
272
273            let arg_names: Vec<_> = args
274                .iter()
275                .map(|arg| match &*arg.pat {
276                    Pat::Ident(pat_ident) => &pat_ident.ident,
277                    _ => panic!("Expected PatIdent in simplified args"),
278                })
279                .collect();
280
281            let pipe_method_name = format_ident!(
282                "{}{}",
283                Self::get_pipe_method_name_prefix(),
284                method_name_to_pipe_method(&method.sig.ident, struct_name)
285            );
286
287            let (method_impl_generics, _, where_clause) = method.sig.generics.split_for_impl();
288
289            trait_method_signatures.push(Self::generate_trait_method_signature(
290                &pipe_method_name,
291                &method_impl_generics,
292                where_clause,
293                &args,
294            ));
295            trait_method_bodies.push(Self::generate_trait_method(
296                &pipe_method_name,
297                &method.sig.ident,
298                &method_impl_generics,
299                where_clause,
300                &args,
301                &arg_names,
302                &struct_with_generics,
303                &struct_path_with_generics,
304            ));
305        }
306
307        Self::generate_trait(
308            visibility,
309            &trait_name,
310            trait_generics.to_token_stream(),
311            &trait_ty_generics,
312            &struct_path_with_generics,
313            where_clause,
314            where_clause_predicates,
315            &impl_generics,
316            trait_method_signatures,
317            trait_method_bodies,
318        )
319    }
320}
321
322struct IteratorExtension;
323
324impl Extension for IteratorExtension {
325    fn get_name() -> &'static str {
326        "Iterator"
327    }
328
329    fn get_pipe_method_name_prefix() -> &'static str {
330        ""
331    }
332
333    fn get_extra_trait_params() -> Vec<proc_macro2::TokenStream> {
334        vec![]
335    }
336
337    fn get_extra_impl_for_params() -> Vec<proc_macro2::TokenStream> {
338        vec![quote! { TIterator }]
339    }
340
341    fn generate_trait(
342        visibility: &Visibility,
343        trait_name: &Ident,
344        trait_generics: proc_macro2::TokenStream,
345        trait_ty_generics: &TypeGenerics,
346        struct_path_with_generics: &proc_macro2::TokenStream,
347        where_clause: Option<&WhereClause>,
348        where_clause_predicates: Option<&Punctuated<WherePredicate, Comma>>,
349        impl_generics: &ImplGenerics,
350        method_signatures: Vec<proc_macro2::TokenStream>,
351        method_bodies: Vec<proc_macro2::TokenStream>,
352    ) -> proc_macro2::TokenStream {
353        quote! {
354            #visibility trait #trait_name #trait_generics:
355                Iterator<Item = <_SELF as ::unipipe::UniPipe>::Input> + Sized
356            #where_clause
357            {
358                #(#method_signatures)*
359            }
360
361            impl #impl_generics #trait_name #trait_ty_generics for TIterator
362            where
363                TIterator: Iterator<Item = <#struct_path_with_generics as ::unipipe::UniPipe>::Input>,
364                #where_clause_predicates
365            {
366                #(#method_bodies)*
367            }
368        }
369    }
370
371    fn generate_trait_method_signature(
372        pipe_method_name: &Ident,
373        method_impl_generics: &ImplGenerics,
374        where_clause: Option<&WhereClause>,
375        args: &[PatType],
376    ) -> proc_macro2::TokenStream {
377        quote! {
378            fn #pipe_method_name #method_impl_generics(
379                self,
380                #(#args),*
381            ) -> impl Iterator<Item = <_SELF as ::unipipe::UniPipe>::Output>
382            #where_clause;
383        }
384    }
385
386    fn generate_trait_method(
387        pipe_method_name: &Ident,
388        method_name: &Ident,
389        method_impl_generics: &ImplGenerics,
390        where_clause: Option<&WhereClause>,
391        args: &[PatType],
392        arg_names: &[&Ident],
393        struct_with_generics: &proc_macro2::TokenStream,
394        struct_path_with_generics: &proc_macro2::TokenStream,
395    ) -> proc_macro2::TokenStream {
396        quote! {
397            fn #pipe_method_name #method_impl_generics(
398                mut self,
399                #(#args),*
400            ) -> impl Iterator<Item = <#struct_with_generics as ::unipipe::UniPipe>::Output>
401            #where_clause {
402                use ::unipipe::*;
403
404                let mut pipe = #struct_path_with_generics::#method_name(#(#arg_names),*);
405
406                let mut pending = std::collections::VecDeque::new();
407                let mut done = false;
408
409                std::iter::from_fn(move || {
410                    if let Some(output) = pending.pop_front() {
411                        return Some(output);
412                    }
413
414                    loop {
415                        if done {
416                            return None;
417                        }
418
419                        let input = self.next();
420
421                        if input.is_none() {
422                            done = true;
423                        }
424
425                        let next_output: Output<_> = pipe.next(input).into();
426
427                        if next_output.is_done() {
428                            done = true;
429                        }
430
431                        match next_output {
432                            Output::One(output) | Output::DoneWithOne(output) => return Some(output),
433                            Output::Many(outputs) | Output::DoneWithMany(outputs) => {
434                                let mut outputs = outputs.into_iter();
435
436                                if let Some(output) = outputs.next() {
437                                    pending.extend(outputs);
438                                    return Some(output);
439                                }
440                            }
441                            Output::Next | Output::Done => {}
442                        }
443                    }
444                })
445            }
446        }
447    }
448}
449
450struct TryIteratorExtension;
451
452impl Extension for TryIteratorExtension {
453    fn get_name() -> &'static str {
454        "TryIterator"
455    }
456
457    fn get_pipe_method_name_prefix() -> &'static str {
458        "try_"
459    }
460
461    fn get_extra_trait_params() -> Vec<proc_macro2::TokenStream> {
462        vec![quote! { TError }]
463    }
464
465    fn get_extra_impl_for_params() -> Vec<proc_macro2::TokenStream> {
466        vec![quote! { TIterator }]
467    }
468
469    fn generate_trait(
470        visibility: &Visibility,
471        trait_name: &Ident,
472        trait_generics: proc_macro2::TokenStream,
473        trait_ty_generics: &TypeGenerics,
474        struct_path_with_generics: &proc_macro2::TokenStream,
475        where_clause: Option<&WhereClause>,
476        where_clause_predicates: Option<&Punctuated<WherePredicate, Comma>>,
477        impl_generics: &ImplGenerics,
478        method_signatures: Vec<proc_macro2::TokenStream>,
479        method_bodies: Vec<proc_macro2::TokenStream>,
480    ) -> proc_macro2::TokenStream {
481        quote! {
482            #visibility trait #trait_name #trait_generics:
483                Iterator<Item = Result<<_SELF as ::unipipe::UniPipe>::Input, TError>> + Sized
484            #where_clause
485            {
486                #(#method_signatures)*
487            }
488
489            impl #impl_generics #trait_name #trait_ty_generics for TIterator
490            where
491                TIterator: Iterator<Item = Result<<#struct_path_with_generics as ::unipipe::UniPipe>::Input, TError>>,
492                #where_clause_predicates
493            {
494                #(#method_bodies)*
495            }
496        }
497    }
498
499    fn generate_trait_method_signature(
500        pipe_method_name: &Ident,
501        method_impl_generics: &ImplGenerics,
502        where_clause: Option<&WhereClause>,
503        args: &[PatType],
504    ) -> proc_macro2::TokenStream {
505        quote! {
506            fn #pipe_method_name #method_impl_generics(
507                self,
508                #(#args),*
509            ) -> impl Iterator<Item = Result<<_SELF as ::unipipe::UniPipe>::Output, TError>>
510            #where_clause;
511        }
512    }
513
514    fn generate_trait_method(
515        pipe_method_name: &Ident,
516        method_name: &Ident,
517        method_impl_generics: &ImplGenerics,
518        where_clause: Option<&WhereClause>,
519        args: &[PatType],
520        arg_names: &[&Ident],
521        struct_with_generics: &proc_macro2::TokenStream,
522        struct_path_with_generics: &proc_macro2::TokenStream,
523    ) -> proc_macro2::TokenStream {
524        quote! {
525            fn #pipe_method_name #method_impl_generics(
526                mut self,
527                #(#args),*
528            ) -> impl Iterator<Item = Result<<#struct_with_generics as ::unipipe::UniPipe>::Output, TError>>
529            #where_clause {
530                use ::unipipe::*;
531
532                let mut pipe = #struct_path_with_generics::#method_name(#(#arg_names),*);
533
534                let mut pending = std::collections::VecDeque::new();
535                let mut done = false;
536
537                std::iter::from_fn(move || {
538                    if let Some(output) = pending.pop_front() {
539                        return Some(Ok(output));
540                    }
541
542                    loop {
543                        if done {
544                            return None;
545                        }
546
547                        let input = self.next();
548
549                        if input.is_none() {
550                            done = true;
551                        }
552
553                        let input = match input {
554                            Some(Err(error)) => return Some(Err(error)),
555                            Some(Ok(input)) => Some(input),
556                            None => None,
557                        };
558
559                        let next_output: Output<_> = pipe.next(input).into();
560
561                        if next_output.is_done() {
562                            done = true;
563                        }
564
565                        match next_output {
566                            Output::One(output) | Output::DoneWithOne(output) => return Some(Ok(output)),
567                            Output::Many(outputs) | Output::DoneWithMany(outputs) => {
568                                let mut outputs = outputs.into_iter();
569
570                                if let Some(output) = outputs.next() {
571                                    pending.extend(outputs);
572                                    return Some(Ok(output));
573                                }
574                            }
575                            Output::Next | Output::Done => {}
576                        }
577                    }
578                })
579            }
580        }
581    }
582}
583
584struct StreamExtension;
585
586impl Extension for StreamExtension {
587    fn get_name() -> &'static str {
588        "Stream"
589    }
590
591    fn get_pipe_method_name_prefix() -> &'static str {
592        ""
593    }
594
595    fn get_extra_trait_params() -> Vec<proc_macro2::TokenStream> {
596        vec![]
597    }
598
599    fn get_extra_impl_for_params() -> Vec<proc_macro2::TokenStream> {
600        vec![quote! { TStream }]
601    }
602
603    fn generate_trait(
604        visibility: &Visibility,
605        trait_name: &Ident,
606        trait_generics: proc_macro2::TokenStream,
607        trait_ty_generics: &TypeGenerics,
608        struct_path_with_generics: &proc_macro2::TokenStream,
609        where_clause: Option<&WhereClause>,
610        where_clause_predicates: Option<&Punctuated<WherePredicate, Comma>>,
611        impl_generics: &ImplGenerics,
612        method_signatures: Vec<proc_macro2::TokenStream>,
613        method_bodies: Vec<proc_macro2::TokenStream>,
614    ) -> proc_macro2::TokenStream {
615        quote! {
616            #visibility trait #trait_name #trait_generics:
617                ::unipipe::Stream<Item = <_SELF as ::unipipe::UniPipe>::Input> + Sized
618            #where_clause
619            {
620                #(#method_signatures)*
621            }
622
623            impl #impl_generics #trait_name #trait_ty_generics for TStream
624            where
625                TStream: ::unipipe::Stream<Item = <#struct_path_with_generics as ::unipipe::UniPipe>::Input>,
626                #where_clause_predicates
627            {
628                #(#method_bodies)*
629            }
630        }
631    }
632
633    fn generate_trait_method_signature(
634        pipe_method_name: &Ident,
635        method_impl_generics: &ImplGenerics,
636        where_clause: Option<&WhereClause>,
637        args: &[PatType],
638    ) -> proc_macro2::TokenStream {
639        quote! {
640            fn #pipe_method_name #method_impl_generics(
641                self,
642                #(#args),*
643            ) -> impl ::unipipe::Stream<Item = <_SELF as ::unipipe::UniPipe>::Output> + Unpin
644            #where_clause;
645        }
646    }
647
648    fn generate_trait_method(
649        pipe_method_name: &Ident,
650        method_name: &Ident,
651        method_impl_generics: &ImplGenerics,
652        where_clause: Option<&WhereClause>,
653        args: &[PatType],
654        arg_names: &[&Ident],
655        struct_with_generics: &proc_macro2::TokenStream,
656        struct_path_with_generics: &proc_macro2::TokenStream,
657    ) -> proc_macro2::TokenStream {
658        quote! {
659            fn #pipe_method_name #method_impl_generics(
660                mut self,
661                #(#args),*
662            ) -> impl ::unipipe::Stream<Item = <#struct_with_generics as ::unipipe::UniPipe>::Output> + Unpin
663            #where_clause {
664                use ::unipipe::*;
665
666                Box::pin(::unipipe::stream!({
667                    let mut pipe = #struct_path_with_generics::#method_name(#(#arg_names),*);
668
669                    let mut source = Box::pin(self);
670                    let mut done = false;
671
672                    loop {
673                        if done {
674                            break;
675                        }
676
677                        let input = source.next().await;
678
679                        if input.is_none() {
680                            done = true;
681                        }
682
683                        let next_output: Output<_> = pipe.next(input).into();
684
685                        if next_output.is_done() {
686                            done = true;
687                        }
688
689                        match next_output {
690                            Output::One(output) | Output::DoneWithOne(output) => yield output,
691                            Output::Many(outputs) | Output::DoneWithMany(outputs) => {
692                                for output in outputs {
693                                    yield output;
694                                }
695                            }
696                            Output::Next | Output::Done => {}
697                        }
698                    }
699                }))
700            }
701        }
702    }
703}
704
705struct TryStreamExtension;
706
707impl Extension for TryStreamExtension {
708    fn get_name() -> &'static str {
709        "TryStream"
710    }
711
712    fn get_pipe_method_name_prefix() -> &'static str {
713        "try_"
714    }
715
716    fn get_extra_trait_params() -> Vec<proc_macro2::TokenStream> {
717        vec![quote! { TError }]
718    }
719
720    fn get_extra_impl_for_params() -> Vec<proc_macro2::TokenStream> {
721        vec![quote! { TStream }]
722    }
723
724    fn generate_trait(
725        visibility: &Visibility,
726        trait_name: &Ident,
727        trait_generics: proc_macro2::TokenStream,
728        trait_ty_generics: &TypeGenerics,
729        struct_path_with_generics: &proc_macro2::TokenStream,
730        where_clause: Option<&WhereClause>,
731        where_clause_predicates: Option<&Punctuated<WherePredicate, Comma>>,
732        impl_generics: &ImplGenerics,
733        method_signatures: Vec<proc_macro2::TokenStream>,
734        method_bodies: Vec<proc_macro2::TokenStream>,
735    ) -> proc_macro2::TokenStream {
736        quote! {
737            #visibility trait #trait_name #trait_generics:
738                ::unipipe::Stream<Item = Result<<_SELF as ::unipipe::UniPipe>::Input, TError>> + Sized
739            #where_clause
740            {
741                #(#method_signatures)*
742            }
743
744            impl #impl_generics #trait_name #trait_ty_generics for TStream
745            where
746                TStream: ::unipipe::Stream<Item = Result<<#struct_path_with_generics as ::unipipe::UniPipe>::Input, TError>>,
747                #where_clause_predicates
748            {
749                #(#method_bodies)*
750            }
751        }
752    }
753
754    fn generate_trait_method_signature(
755        pipe_method_name: &Ident,
756        method_impl_generics: &ImplGenerics,
757        where_clause: Option<&WhereClause>,
758        args: &[PatType],
759    ) -> proc_macro2::TokenStream {
760        quote! {
761            fn #pipe_method_name #method_impl_generics(
762                self,
763                #(#args),*
764            ) -> impl ::unipipe::Stream<Item = Result<<_SELF as ::unipipe::UniPipe>::Output, TError>> + Unpin
765            #where_clause;
766        }
767    }
768
769    fn generate_trait_method(
770        pipe_method_name: &Ident,
771        method_name: &Ident,
772        method_impl_generics: &ImplGenerics,
773        where_clause: Option<&WhereClause>,
774        args: &[PatType],
775        arg_names: &[&Ident],
776        struct_with_generics: &proc_macro2::TokenStream,
777        struct_path_with_generics: &proc_macro2::TokenStream,
778    ) -> proc_macro2::TokenStream {
779        quote! {
780            fn #pipe_method_name #method_impl_generics(
781                mut self,
782                #(#args),*
783            ) -> impl ::unipipe::Stream<Item = Result<<#struct_with_generics as ::unipipe::UniPipe>::Output, TError>> + Unpin
784            #where_clause {
785                use ::unipipe::*;
786
787                Box::pin(::unipipe::stream!({
788                    let mut pipe = #struct_path_with_generics::#method_name(#(#arg_names),*);
789
790                    let mut source = Box::pin(self);
791                    let mut done = false;
792
793                    loop {
794                        if done {
795                            break;
796                        }
797
798                        let input = source.next().await;
799
800                        if input.is_none() {
801                            done = true;
802                        }
803
804                        let input = match input {
805                            Some(Err(error)) => {
806                                yield Err(error);
807                                continue;
808                            }
809                            Some(Ok(input)) => Some(input),
810                            None => None,
811                        };
812
813                        let next_output: Output<_> = pipe.next(input).into();
814
815                        if next_output.is_done() {
816                            done = true;
817                        }
818
819                        match next_output {
820                            Output::One(output) | Output::DoneWithOne(output) => yield Ok(output),
821                            Output::Many(outputs) | Output::DoneWithMany(outputs) => {
822                                for output in outputs {
823                                    yield Ok(output);
824                                }
825                            }
826                            Output::Next | Output::Done => {}
827                        }
828                    }
829                }))
830            }
831        }
832    }
833}
834
835fn method_name_to_pipe_method(method_name: &Ident, struct_name: &Ident) -> Ident {
836    let method_str = method_name.to_string();
837    let struct_snake = struct_name.to_string().to_case(Case::Snake);
838
839    if method_str == "new" {
840        return format_ident!("{}", struct_snake);
841    }
842
843    if let Some(suffix) = method_str.strip_prefix("new_") {
844        return format_ident!("{}_{}", struct_snake, suffix);
845    }
846
847    format_ident!("{}", method_str.to_case(Case::Snake))
848}
849
850fn simplify_arg_pat_type(pat_type: &PatType, index: usize) -> PatType {
851    let mut simplified = pat_type.clone();
852
853    let ident = match &*pat_type.pat {
854        Pat::Ident(pat_ident) => pat_ident.ident.clone(),
855        _ => format_ident!("arg_{}", index),
856    };
857
858    simplified.pat = Box::new(Pat::Ident(syn::PatIdent {
859        attrs: vec![],
860        by_ref: None,
861        mutability: None,
862        ident,
863        subpat: None,
864    }));
865
866    simplified
867}