diplomat_core/ast/
methods.rs

1use serde::Serialize;
2use std::ops::ControlFlow;
3
4use super::docs::Docs;
5use super::{Attrs, Ident, Lifetime, LifetimeEnv, Mutability, PathType, TypeName};
6
7/// A method declared in the `impl` associated with an FFI struct.
8/// Includes both static and non-static methods, which can be distinguished
9/// by inspecting [`Method::self_param`].
10#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
11#[non_exhaustive]
12pub struct Method {
13    /// The name of the method as initially declared.
14    pub name: Ident,
15
16    /// Lines of documentation for the method.
17    pub docs: Docs,
18
19    /// The name of the FFI function wrapping around the method.
20    pub full_path_name: Ident,
21
22    /// The `self` param of the method, if any.
23    pub self_param: Option<SelfParam>,
24
25    /// All non-`self` params taken by the method.
26    pub params: Vec<Param>,
27
28    /// The return type of the method, if any.
29    pub return_type: Option<TypeName>,
30
31    /// The lifetimes introduced in this method and surrounding impl block.
32    pub lifetime_env: LifetimeEnv,
33
34    /// The list of `cfg` attributes (if any).
35    ///
36    /// These are strings instead of `syn::Attribute` or `proc_macro2::TokenStream`
37    /// because those types are not `PartialEq`, `Hash`, `Serialize`, etc.
38    pub attrs: Attrs,
39}
40
41impl Method {
42    /// Extracts a [`Method`] from an AST node inside an `impl`.
43    pub fn from_syn(
44        m: &syn::ImplItemFn,
45        self_path_type: PathType,
46        impl_generics: Option<&syn::Generics>,
47        impl_attrs: &Attrs,
48    ) -> Method {
49        let mut attrs = impl_attrs.clone();
50        attrs.add_attrs(&m.attrs);
51
52        let self_ident = self_path_type.path.elements.last().unwrap();
53        let method_ident = &m.sig.ident;
54        let concat_method_ident = format!("{self_ident}_{method_ident}");
55        let extern_ident = syn::Ident::new(
56            &attrs.abi_rename.apply(concat_method_ident.into()),
57            m.sig.ident.span(),
58        );
59
60        let all_params = m
61            .sig
62            .inputs
63            .iter()
64            .filter_map(|a| match a {
65                syn::FnArg::Receiver(_) => None,
66                syn::FnArg::Typed(ref t) => Some(Param::from_syn(t, self_path_type.clone())),
67            })
68            .collect::<Vec<_>>();
69
70        let self_param = m
71            .sig
72            .receiver()
73            .map(|rec| SelfParam::from_syn(rec, self_path_type.clone()));
74
75        let return_ty = match &m.sig.output {
76            syn::ReturnType::Type(_, return_typ) => {
77                // When we allow lifetime elision, this is where we would want to
78                // support it so we can insert the expanded explicit lifetimes.
79                Some(TypeName::from_syn(
80                    return_typ.as_ref(),
81                    Some(self_path_type),
82                ))
83            }
84            syn::ReturnType::Default => None,
85        };
86
87        let lifetime_env = LifetimeEnv::from_method_item(
88            m,
89            impl_generics,
90            self_param.as_ref(),
91            &all_params[..],
92            return_ty.as_ref(),
93        );
94
95        Method {
96            name: Ident::from(method_ident),
97            docs: Docs::from_attrs(&m.attrs),
98            full_path_name: Ident::from(&extern_ident),
99            self_param,
100            params: all_params,
101            return_type: return_ty,
102            lifetime_env,
103            attrs,
104        }
105    }
106
107    /// Returns the parameters that the output is lifetime-bound to.
108    ///
109    /// # Examples
110    ///
111    /// Given the following method:
112    /// ```ignore
113    /// fn foo<'a, 'b: 'a, 'c>(&'a self, bar: Bar<'b>, baz: Baz<'c>) -> FooBar<'a> { ... }
114    /// ```
115    /// Then this method would return the `&'a self` and `bar: Bar<'b>` params
116    /// because `'a` is in the return type, and `'b` must live at least as long
117    /// as `'a`. It wouldn't include `baz: Baz<'c>` though, because the return
118    /// type isn't bound by `'c` in any way.
119    ///
120    /// # Panics
121    ///
122    /// This method may panic if `TypeName::check_result_type_validity` (called by
123    /// `Method::check_validity`) doesn't pass first, since the result type may
124    /// contain elided lifetimes that we depend on for this method. The validity
125    /// checks ensure that the return type doesn't elide any lifetimes, ensuring
126    /// that this method will produce correct results.
127    pub fn borrowed_params(&self) -> BorrowedParams {
128        // To determine which params the return type is bound to, we just have to
129        // find the params that contain a lifetime that's also in the return type.
130        if let Some(ref return_type) = self.return_type {
131            // The lifetimes that must outlive the return type
132            let lifetimes = return_type.longer_lifetimes(&self.lifetime_env);
133
134            let held_self_param = self.self_param.as_ref().filter(|self_param| {
135                // Check if `self` is a reference with a lifetime in the return type.
136                if let Some((Lifetime::Named(ref name), _)) = self_param.reference {
137                    if lifetimes.contains(&name) {
138                        return true;
139                    }
140                }
141                self_param.path_type.lifetimes.iter().any(|lt| {
142                    if let Lifetime::Named(name) = lt {
143                        lifetimes.contains(&name)
144                    } else {
145                        false
146                    }
147                })
148            });
149
150            // Collect all the params that contain a named lifetime that's also
151            // in the return type.
152            let held_params = self
153                .params
154                .iter()
155                .filter_map(|param| {
156                    let mut lt_kind = LifetimeKind::ReturnValue;
157                    param
158                        .ty
159                        .visit_lifetimes(&mut |lt, _| {
160                            // Thanks to `TypeName::visit_lifetimes`, we can
161                            // traverse the lifetimes without allocations and
162                            // short-circuit if we find a match.
163                            match lt {
164                                Lifetime::Named(name) if lifetimes.contains(&name) => {
165                                    return ControlFlow::Break(());
166                                }
167                                Lifetime::Static => {
168                                    lt_kind = LifetimeKind::Static;
169                                    return ControlFlow::Break(());
170                                }
171                                _ => {}
172                            };
173                            ControlFlow::Continue(())
174                        })
175                        .is_break()
176                        .then(|| (param, lt_kind))
177                })
178                .collect();
179
180            BorrowedParams(held_self_param, held_params)
181        } else {
182            BorrowedParams(None, vec![])
183        }
184    }
185
186    /// Checks whether the method qualifies for special writeable handling.
187    /// To qualify, a method must:
188    ///  - not return any value
189    ///  - have the last argument be an `&mut diplomat_runtime::DiplomatWriteable`
190    ///
191    /// Typically, methods of this form will be transformed in the bindings to a
192    /// method that doesn't take the writeable as an argument but instead creates
193    /// one locally and just returns the final string.
194    pub fn is_writeable_out(&self) -> bool {
195        let return_compatible = self
196            .return_type
197            .as_ref()
198            .map(|return_type| match return_type {
199                TypeName::Unit => true,
200                TypeName::Result(ok, _, _) => {
201                    matches!(ok.as_ref(), TypeName::Unit)
202                }
203                _ => false,
204            })
205            .unwrap_or(true);
206
207        return_compatible && self.params.last().map(Param::is_writeable).unwrap_or(false)
208    }
209
210    /// Checks if any parameters are writeable (regardless of other compatibilities for writeable output)
211    pub fn has_writeable_param(&self) -> bool {
212        self.params.iter().any(|p| p.is_writeable())
213    }
214
215    /// Returns the documentation block
216    pub fn docs(&self) -> &Docs {
217        &self.docs
218    }
219}
220
221/// The `self` parameter taken by a [`Method`].
222#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
223#[non_exhaustive]
224pub struct SelfParam {
225    /// The lifetime and mutability of the `self` param, if it's a reference.
226    pub reference: Option<(Lifetime, Mutability)>,
227
228    /// The type of the parameter, which will be a named reference to
229    /// the associated struct,
230    pub path_type: PathType,
231}
232
233impl SelfParam {
234    pub fn to_typename(&self) -> TypeName {
235        let typ = TypeName::Named(self.path_type.clone());
236        if let Some((ref lifetime, ref mutability)) = self.reference {
237            return TypeName::Reference(lifetime.clone(), *mutability, Box::new(typ));
238        }
239        typ
240    }
241
242    pub fn from_syn(rec: &syn::Receiver, path_type: PathType) -> Self {
243        SelfParam {
244            reference: rec
245                .reference
246                .as_ref()
247                .map(|(_, lt)| (lt.into(), Mutability::from_syn(&rec.mutability))),
248            path_type,
249        }
250    }
251}
252
253/// A parameter taken by a [`Method`], not including `self`.
254#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
255#[non_exhaustive]
256pub struct Param {
257    /// The name of the parameter in the original method declaration.
258    pub name: Ident,
259
260    /// The type of the parameter.
261    pub ty: TypeName,
262}
263
264impl Param {
265    /// Check if this parameter is a Writeable
266    pub fn is_writeable(&self) -> bool {
267        match self.ty {
268            TypeName::Reference(_, Mutability::Mutable, ref w) => **w == TypeName::Writeable,
269            _ => false,
270        }
271    }
272
273    pub fn from_syn(t: &syn::PatType, self_path_type: PathType) -> Self {
274        let ident = match t.pat.as_ref() {
275            syn::Pat::Ident(ident) => ident,
276            _ => panic!("Unexpected param type"),
277        };
278
279        Param {
280            name: (&ident.ident).into(),
281            ty: TypeName::from_syn(&t.ty, Some(self_path_type)),
282        }
283    }
284}
285
286/// The type of lifetime.
287#[derive(Debug, Copy, Clone, PartialEq, Eq)]
288pub enum LifetimeKind {
289    /// Param must live at least as long as the returned object.
290    ReturnValue,
291    /// Param must live for the duration of the program.
292    Static,
293}
294
295#[derive(Default, Debug)]
296/// Parameters in a method that might be borrowed in the return type.
297#[non_exhaustive]
298pub struct BorrowedParams<'a>(
299    pub Option<&'a SelfParam>,
300    pub Vec<(&'a Param, LifetimeKind)>,
301);
302
303impl BorrowedParams<'_> {
304    /// Returns an [`Iterator`] through the names of the parameters that are borrowed
305    /// for the lifetime of the return value, accepting an `Ident` that the `self`
306    /// param will be called if present.
307    pub fn return_names<'a>(&'a self, self_name: &'a Ident) -> impl Iterator<Item = &'a Ident> {
308        self.0.iter().map(move |_| self_name).chain(
309            self.1
310                .iter()
311                .filter(|(_, ltk)| (*ltk == LifetimeKind::ReturnValue))
312                .map(|(param, _)| &param.name),
313        )
314    }
315
316    /// Returns an [`Iterator`] through the names of the parameters that are borrowed for a
317    /// static lifetime.
318    pub fn static_names(&self) -> impl Iterator<Item = &'_ Ident> {
319        self.1
320            .iter()
321            .filter(|(_, ltk)| (*ltk == LifetimeKind::Static))
322            .map(|(param, _)| &param.name)
323    }
324
325    /// Returns `true` if a provided param name is included in the borrowed params,
326    /// otherwise `false`.
327    ///
328    /// This method doesn't check the `self` parameter. Use
329    /// [`BorrowedParams::borrows_self`] instead.
330    pub fn contains(&self, param_name: &Ident) -> bool {
331        self.1.iter().any(|(param, _)| &param.name == param_name)
332    }
333
334    /// Returns `true` if there are no borrowed parameters, otherwise `false`.
335    pub fn is_empty(&self) -> bool {
336        self.0.is_none() && self.1.is_empty()
337    }
338
339    /// Returns `true` if the `self` param is borrowed, otherwise `false`.
340    pub fn borrows_self(&self) -> bool {
341        self.0.is_some()
342    }
343
344    /// Returns `true` if there are any borrowed params, otherwise `false`.
345    pub fn borrows_params(&self) -> bool {
346        !self.1.is_empty()
347    }
348
349    /// Returns the number of borrowed params.
350    pub fn len(&self) -> usize {
351        self.1.len() + usize::from(self.0.is_some())
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use insta;
358
359    use syn;
360
361    use crate::ast::{Attrs, Ident, Method, Path, PathType};
362
363    #[test]
364    fn static_methods() {
365        insta::assert_yaml_snapshot!(Method::from_syn(
366            &syn::parse_quote! {
367                /// Some docs.
368                #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
369                fn foo(x: u64, y: MyCustomStruct) {
370
371                }
372            },
373            PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
374            None,
375            &Attrs::default()
376        ));
377
378        insta::assert_yaml_snapshot!(Method::from_syn(
379            &syn::parse_quote! {
380                /// Some docs.
381                /// Some more docs.
382                ///
383                /// Even more docs.
384                #[diplomat::rust_link(foo::Bar::batz, FnInEnum)]
385                fn foo(x: u64, y: MyCustomStruct) -> u64 {
386                    x
387                }
388            },
389            PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
390            None,
391            &Attrs::default()
392        ));
393    }
394
395    #[test]
396    fn cfged_method() {
397        insta::assert_yaml_snapshot!(Method::from_syn(
398            &syn::parse_quote! {
399                /// Some docs.
400                #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
401                #[cfg(any(feature = "foo", not(feature = "bar")))]
402                fn foo(x: u64, y: MyCustomStruct) {
403
404                }
405            },
406            PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
407            None,
408            &Attrs::default()
409        ));
410    }
411
412    #[test]
413    fn nonstatic_methods() {
414        insta::assert_yaml_snapshot!(Method::from_syn(
415            &syn::parse_quote! {
416                fn foo(&self, x: u64, y: MyCustomStruct) {
417
418                }
419            },
420            PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
421            None,
422            &Attrs::default()
423        ));
424
425        insta::assert_yaml_snapshot!(Method::from_syn(
426            &syn::parse_quote! {
427                #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
428                fn foo(&mut self, x: u64, y: MyCustomStruct) -> u64 {
429                    x
430                }
431            },
432            PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
433            None,
434            &Attrs::default()
435        ));
436    }
437
438    macro_rules! assert_borrowed_params {
439        ([$($return_param:ident),*] $(, [$($static_param:ident),*])? => $($tokens:tt)* ) => {{
440            let method = Method::from_syn(
441                &syn::parse_quote! { $($tokens)* },
442                PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
443                None,
444                &Attrs::default()
445            );
446
447            let borrowed_params = method.borrowed_params();
448            // The ident parser in syn doesn't allow `self`, so we use "this" as a placeholder
449            // and then change it.
450            let mut actual_return: Vec<&str> = borrowed_params.return_names(&Ident::THIS).map(|ident| ident.as_str()).collect();
451            if borrowed_params.0.is_some() {
452                actual_return[0] = "self";
453            }
454            let expected_return: &[&str] = &[$(stringify!($return_param)),*];
455            assert_eq!(actual_return, expected_return);
456            let actual_static: Vec<&str> = borrowed_params.static_names().map(|ident| ident.as_str()).collect();
457            let expected_static: &[&str] = &[$($(stringify!($static_param)),*)?];
458            assert_eq!(actual_static, expected_static);
459        }};
460    }
461
462    #[test]
463    fn static_params_held_by_return_type() {
464        assert_borrowed_params! { [first, second] =>
465            #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
466            fn foo<'a, 'b>(first: &'a First, second: &'b Second, third: &Third) -> Foo<'a, 'b> {
467                unimplemented!()
468            }
469        }
470
471        assert_borrowed_params! { [hold] =>
472            #[diplomat::rust_link(Foo, FnInStruct)]
473            fn transitivity<'a, 'b: 'a, 'c: 'b, 'd: 'c, 'e: 'd, 'x>(hold: &'x One<'e>, nohold: &One<'x>) -> Box<Foo<'a>> {
474                unimplemented!()
475            }
476        }
477
478        assert_borrowed_params! { [hold] =>
479            #[diplomat::rust_link(Foo, FnInStruct)]
480            fn a_le_b_and_b_le_a<'a: 'b, 'b: 'a>(hold: &'b Bar, nohold: &'c Bar) -> Box<Foo<'a>> {
481                unimplemented!()
482            }
483        }
484
485        assert_borrowed_params! { [a, b, c, d] =>
486            #[diplomat::rust_link(Foo, FnInStruct)]
487            fn many_dependents<'a, 'b: 'a, 'c: 'a, 'd: 'b, 'x, 'y>(a: &'x One<'a>, b: &'b One<'a>, c: &Two<'x, 'c>, d: &'x Two<'d, 'y>, nohold: &'x Two<'x, 'y>) -> Box<Foo<'a>> {
488                unimplemented!()
489            }
490        }
491
492        assert_borrowed_params! { [hold] =>
493            #[diplomat::rust_link(Foo, FnInStruct)]
494            fn return_outlives_param<'short, 'long: 'short>(hold: &Two<'long, 'short>, nohold: &'short One<'short>) -> Box<Foo<'long>> {
495                unimplemented!()
496            }
497        }
498
499        assert_borrowed_params! { [hold] =>
500            #[diplomat::rust_link(Foo, FnInStruct)]
501            fn transitivity_deep_types<'a, 'b: 'a, 'c: 'b, 'd: 'c>(hold: Option<Box<Bar<'d>>>, nohold: &'a Box<Option<Baz<'a>>>) -> Result<Box<Foo<'b>>, Error> {
502                unimplemented!()
503            }
504        }
505
506        assert_borrowed_params! { [top, left, right, bottom] =>
507            #[diplomat::rust_link(Foo, FnInStruct)]
508            fn diamond_top<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'top>> {
509                unimplemented!()
510            }
511        }
512
513        assert_borrowed_params! { [left, bottom] =>
514            #[diplomat::rust_link(Foo, FnInStruct)]
515            fn diamond_left<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'left>> {
516                unimplemented!()
517            }
518        }
519
520        assert_borrowed_params! { [right, bottom] =>
521            #[diplomat::rust_link(Foo, FnInStruct)]
522            fn diamond_right<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'right>> {
523                unimplemented!()
524            }
525        }
526
527        assert_borrowed_params! { [bottom] =>
528            #[diplomat::rust_link(Foo, FnInStruct)]
529            fn diamond_bottom<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'bottom>> {
530                unimplemented!()
531            }
532        }
533
534        assert_borrowed_params! { [a, b, c, d] =>
535            #[diplomat::rust_link(Foo, FnInStruct)]
536            fn diamond_and_nested_types<'a, 'b: 'a, 'c: 'b, 'd: 'b + 'c, 'x, 'y>(a: &'x One<'a>, b: &'y One<'b>, c: &One<'c>, d: &One<'d>, nohold: &One<'x>) -> Box<Foo<'a>> {
537                unimplemented!()
538            }
539        }
540    }
541
542    #[test]
543    fn nonstatic_params_held_by_return_type() {
544        assert_borrowed_params! { [self] =>
545            #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
546            fn foo<'a>(&'a self) -> Foo<'a> {
547                unimplemented!()
548            }
549        }
550
551        assert_borrowed_params! { [self, foo, bar] =>
552            #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
553            fn foo<'x, 'y>(&'x self, foo: &'x Foo, bar: &Bar<'y>, baz: &Baz) -> Foo<'x, 'y> {
554                unimplemented!()
555            }
556        }
557
558        assert_borrowed_params! { [self, bar] =>
559            #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
560            fn foo<'a, 'b>(&'a self, bar: Bar<'b>) -> Foo<'a, 'b> {
561                unimplemented!()
562            }
563        }
564
565        assert_borrowed_params! { [self, bar], [baz] =>
566            #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
567            fn foo<'a, 'b>(&'a self, bar: Bar<'b>, baz: &'static str) -> Foo<'a, 'b, 'static> {
568                unimplemented!()
569            }
570        }
571    }
572}