//! Methods for types and navigating lifetimes within methods. use std::collections::BTreeSet; use std::ops::Deref; use super::{ Attrs, Docs, Ident, IdentBuf, InputOnly, OutType, OutputOnly, SelfType, TraitPath, Type, TypeContext, }; use super::lifetimes::{Lifetime, LifetimeEnv, Lifetimes, MaybeStatic}; use super::ty_position::Sealed; use borrowing_field::BorrowingFieldVisitor; use borrowing_param::BorrowingParamVisitor; pub mod borrowing_field; pub mod borrowing_param; /// A method exposed to Diplomat. /// Used for representing both free functions ([`crate::ast::Function`]) and struct methods ([`crate::ast::Method`]). /// The only difference between a free function and a struct method in this struct is that [`Self::param_self`] will always be `None` for a free function. #[derive(Debug)] #[non_exhaustive] pub struct Method { /// Documentation specified on the method pub docs: Docs, /// The name of the method as initially declared. pub name: IdentBuf, /// The name of the generated `extern "C"` function pub abi_name: IdentBuf, /// The lifetimes introduced in this method and surrounding impl block. pub lifetime_env: LifetimeEnv, /// An &self, &mut self, or Self parameter pub param_self: Option, /// The parameters of the method pub params: Vec, /// The output type, including whether it returns a Result/Option/Writeable/etc pub output: ReturnType, /// Resolved (and inherited) diplomat::attr attributes on this method pub attrs: Attrs, } pub trait CallbackInstantiationFunctionality: Sealed { #[allow(clippy::result_unit_err)] fn get_inputs(&self) -> Result<&[CallbackParam], ()>; // the types of the parameters #[allow(clippy::result_unit_err)] fn get_output_type(&self) -> Result<&ReturnType, ()>; } #[derive(Debug, Clone)] #[non_exhaustive] // Note: we do not support borrowing across callbacks pub struct Callback { pub param_self: Option, // this is None for callbacks as method arguments pub params: Vec, pub output: Box>, // this will be used in Rust (note: can technically be a callback, or void) pub name: Option, pub attrs: Option, pub docs: Option, } // uninstantiatable; represents no callback allowed #[derive(Debug, Clone)] #[non_exhaustive] pub enum NoCallback {} impl Sealed for Callback {} impl Sealed for NoCallback {} impl CallbackInstantiationFunctionality for Callback { fn get_inputs(&self) -> Result<&[CallbackParam], ()> { Ok(&self.params) } fn get_output_type(&self) -> Result<&ReturnType, ()> { Ok(&self.output) } } impl CallbackInstantiationFunctionality for NoCallback { fn get_inputs(&self) -> Result<&[CallbackParam], ()> { Err(()) } fn get_output_type(&self) -> Result<&ReturnType, ()> { Err(()) } } /// Type that the method returns. #[derive(Debug, Clone)] #[non_exhaustive] pub enum SuccessType { /// Conceptually returns a string, which gets written to the `write: DiplomatWrite` argument Write, /// A Diplomat type. Some types can be outputs, but not inputs, which is expressed by the `OutType` parameter. OutType(Type

), /// A `()` type in Rust. Unit, } /// Whether or not the method returns a value or a result. #[derive(Debug, Clone)] #[allow(clippy::exhaustive_enums)] // this only exists for fallible/infallible, breaking changes for more complex returns are ok pub enum ReturnType { Infallible(SuccessType

), Fallible(SuccessType

, Option>), Nullable(SuccessType

), } /// The `self` parameter of a method. #[derive(Debug)] #[non_exhaustive] pub struct ParamSelf { pub ty: SelfType, pub attrs: Attrs, } #[derive(Debug, Clone)] #[non_exhaustive] pub struct TraitParamSelf { pub trait_path: TraitPath, } /// A parameter in a method. #[derive(Debug)] #[non_exhaustive] pub struct Param { pub name: IdentBuf, pub ty: Type, pub attrs: Attrs, } /// A parameter in a callback /// No name, since all we get is the callback type signature #[derive(Debug, Clone)] #[non_exhaustive] pub struct CallbackParam { pub ty: Type, pub name: Option, } impl SuccessType { /// Returns whether the variant is `Write`. pub fn is_write(&self) -> bool { matches!(self, SuccessType::Write) } /// Returns whether the variant is `Unit`. pub fn is_unit(&self) -> bool { matches!(self, SuccessType::Unit) } pub fn as_type(&self) -> Option<&OutType> { match self { SuccessType::OutType(ty) => Some(ty), _ => None, } } } impl Deref for ReturnType { type Target = SuccessType; fn deref(&self) -> &Self::Target { match self { ReturnType::Infallible(ret) | ReturnType::Fallible(ret, _) | Self::Nullable(ret) => ret, } } } impl ReturnType { /// Returns `true` if the FFI function returns `void`. Not that this is different from `is_unit`, /// which will be true for `DiplomatResult<(), E>` and false for infallible write. pub fn is_ffi_unit(&self) -> bool { matches!( self, ReturnType::Infallible(SuccessType::Unit | SuccessType::Write) ) } /// The "main" return type of this function: the Ok, Some, or regular type pub fn success_type(&self) -> &SuccessType { match &self { Self::Infallible(s) => s, Self::Fallible(s, _) => s, Self::Nullable(s) => s, } } /// Get the list of method lifetimes actually used by the method return type /// /// Most input lifetimes aren't actually used. An input lifetime is generated /// for each borrowing parameter but is only important if we use it in the return. pub fn used_method_lifetimes(&self) -> BTreeSet { let mut set = BTreeSet::new(); let mut add_to_set = |ty: &OutType| { for lt in ty.lifetimes() { if let MaybeStatic::NonStatic(lt) = lt { set.insert(lt); } } }; match self { ReturnType::Infallible(SuccessType::OutType(ref ty)) | ReturnType::Nullable(SuccessType::OutType(ref ty)) => add_to_set(ty), ReturnType::Fallible(ref ok, ref err) => { if let SuccessType::OutType(ref ty) = ok { add_to_set(ty) } if let Some(ref ty) = err { add_to_set(ty) } } _ => (), } set } pub fn with_contained_types(&self, mut f: impl FnMut(&OutType)) { match self { Self::Infallible(SuccessType::OutType(o)) | Self::Nullable(SuccessType::OutType(o)) | Self::Fallible(SuccessType::OutType(o), None) => f(o), Self::Fallible(SuccessType::OutType(o), Some(o2)) => { f(o); f(o2) } Self::Fallible(_, Some(o)) => f(o), _ => (), } } } impl ParamSelf { pub(super) fn new(ty: SelfType, attrs: Attrs) -> Self { Self { ty, attrs } } /// Return the number of fields and leaves that will show up in the [`BorrowingFieldVisitor`]. /// /// This method is used to calculate how much space to allocate upfront. fn field_leaf_lifetime_counts(&self, tcx: &TypeContext) -> (usize, usize) { match self.ty { SelfType::Opaque(_) => (1, 1), SelfType::Struct(ref ty) => ty.resolve(tcx).fields.iter().fold((1, 0), |acc, field| { let inner = field.ty.field_leaf_lifetime_counts(tcx); (acc.0 + inner.0, acc.1 + inner.1) }), SelfType::Enum(_) => (0, 0), } } } impl TraitParamSelf { pub(super) fn new(trait_path: TraitPath) -> Self { Self { trait_path } } } impl Param { pub(super) fn new(name: IdentBuf, ty: Type, attrs: Attrs) -> Self { Self { name, ty, attrs } } } impl Method { /// Returns a fresh [`Lifetimes`] corresponding to `self`. pub fn method_lifetimes(&self) -> Lifetimes { self.lifetime_env.lifetimes() } /// Returns a new [`BorrowingParamVisitor`], which can *shallowly* link output lifetimes /// to the parameters they borrow from. /// /// This is useful for backends which wish to have lifetime codegen for methods only handle the local /// method lifetime, and delegate to generated code on structs for handling the internals of struct lifetimes. /// /// `force_include_slices` is right now *just* for the JS backend. /// Because the JS backend requires us to know information about the allocation of each slice, /// then we need to grab that information in the [`BorrowingParamVisitor`]. /// See [`BorrowingParamVisitor::new`] for more. pub fn borrowing_param_visitor<'tcx>( &'tcx self, tcx: &'tcx TypeContext, force_include_slices: bool, ) -> BorrowingParamVisitor<'tcx> { BorrowingParamVisitor::new(self, tcx, force_include_slices) } /// Returns a new [`BorrowingFieldVisitor`], which allocates memory to /// efficiently represent all fields (and their paths!) of the inputs that /// have a lifetime. /// /// This is useful for backends which wish to "splat out" lifetime edge codegen for methods, /// linking each borrowed input param/field (however deep it may be in a struct) to a borrowed output param/field. /// /// ```ignore /// # use std::collections::BTreeMap; /// let visitor = method.borrowing_field_visitor(&tcx, "this".ck().unwrap()); /// let mut map = BTreeMap::new(); /// visitor.visit_borrowing_fields(|lifetime, field| { /// map.entry(lifetime).or_default().push(field); /// }) /// ``` pub fn borrowing_field_visitor<'m>( &'m self, tcx: &'m TypeContext, self_name: &'m Ident, ) -> BorrowingFieldVisitor<'m> { BorrowingFieldVisitor::new(self, tcx, self_name) } }