diff options
Diffstat (limited to 'src/info_structures.rs')
-rw-r--r-- | src/info_structures.rs | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/src/info_structures.rs b/src/info_structures.rs new file mode 100644 index 0000000..05dc734 --- /dev/null +++ b/src/info_structures.rs @@ -0,0 +1,304 @@ +use crate::utils::{make_generic_arguments, make_generic_consumers, replace_this_with_lifetime}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + punctuated::Punctuated, token::Comma, Attribute, ConstParam, Error, GenericParam, Generics, + LifetimeDef, Type, TypeParam, Visibility, +}; + +#[derive(Clone, Copy)] +pub struct Options { + pub do_no_doc: bool, + pub do_pub_extras: bool, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum FieldType { + /// Not borrowed by other parts of the struct. + Tail, + /// Immutably borrowed by at least one other field. + Borrowed, + /// Mutably borrowed by one other field. + BorrowedMut, +} + +impl FieldType { + pub fn is_tail(self) -> bool { + self == Self::Tail + } +} + +#[derive(Clone)] +pub struct BorrowRequest { + pub index: usize, + pub mutable: bool, +} + +#[derive(Clone)] +pub enum Derive { + Debug, + PartialEq, + Eq, +} + +#[derive(Copy, Clone)] +pub enum BuilderType { + Sync, + Async, + AsyncSend, +} + +impl BuilderType { + pub fn is_async(&self) -> bool { + match self { + BuilderType::Sync => false, + _ => true, + } + } +} + +#[derive(Clone)] +pub struct StructInfo { + pub derives: Vec<Derive>, + pub ident: Ident, + pub generics: Generics, + pub vis: Visibility, + pub fields: Vec<StructFieldInfo>, + pub first_lifetime: Ident, + pub attributes: Vec<Attribute>, +} + +impl StructInfo { + // The lifetime to use in place of 'this for internal implementations, + // should never be exposed to the user. + pub fn fake_lifetime(&self) -> Ident { + return self.first_lifetime.clone(); + } + + pub fn generic_params(&self) -> &Punctuated<GenericParam, Comma> { + &self.generics.params + } + + /// Same as generic_params but with 'this and 'outer_borrow prepended. + pub fn borrowed_generic_params(&self) -> TokenStream { + if self.generic_params().is_empty() { + quote! { <'outer_borrow, 'this> } + } else { + let mut new_generic_params = self.generic_params().clone(); + new_generic_params.insert(0, syn::parse_quote! { 'this }); + new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow }); + quote! { <#new_generic_params> } + } + } + + /// Same as generic_params but without bounds and with '_ prepended twice. + pub fn borrowed_generic_params_inferred(&self) -> TokenStream { + use GenericParam::*; + let params = self.generic_params().iter().map(|p| match p { + Type(TypeParam { ident, .. }) | Const(ConstParam { ident, .. }) => { + ident.to_token_stream() + } + Lifetime(LifetimeDef { lifetime, .. }) => lifetime.to_token_stream(), + }); + quote! { <'_, '_, #(#params,)*> } + } + + pub fn generic_arguments(&self) -> Vec<TokenStream> { + make_generic_arguments(&self.generics) + } + + /// Same as generic_arguments but with 'outer_borrow and 'this prepended. + pub fn borrowed_generic_arguments(&self) -> Vec<TokenStream> { + let mut args = self.generic_arguments(); + args.insert(0, quote! { 'this }); + args.insert(0, quote! { 'outer_borrow }); + args + } + + pub fn generic_consumers(&self) -> impl Iterator<Item = (TokenStream, Ident)> { + make_generic_consumers(&self.generics) + } +} + +#[derive(Clone)] +pub struct StructFieldInfo { + pub name: Ident, + pub typ: Type, + pub field_type: FieldType, + pub vis: Visibility, + pub borrows: Vec<BorrowRequest>, + /// If this is true and borrows is empty, the struct will borrow from self in the future but + /// does not require a builder to be initialized. It should not be able to be removed from the + /// struct with into_heads. + pub self_referencing: bool, + /// If it is None, the user has not specified whether or not the field is covariant. If this is + /// Some(false), we should avoid making borrow_* or borrow_*_mut functions as they will not + /// be able to compile. + pub covariant: Option<bool>, +} + +#[derive(Clone)] +pub enum ArgType { + /// Used when the initial value of a field can be passed directly into the constructor. + Plain(TokenStream), + /// Used when a field requires self references and thus requires something that implements + /// a builder function trait instead of a simple plain type. + TraitBound(TokenStream), +} + +impl StructFieldInfo { + pub fn builder_name(&self) -> Ident { + format_ident!("{}_builder", self.name) + } + + pub fn illegal_ref_name(&self) -> Ident { + format_ident!("{}_illegal_static_reference", self.name) + } + + pub fn is_borrowed(&self) -> bool { + self.field_type != FieldType::Tail + } + + pub fn is_mutably_borrowed(&self) -> bool { + self.field_type == FieldType::BorrowedMut + } + + pub fn boxed(&self) -> TokenStream { + let name = &self.name; + quote! { ::ouroboros::macro_help::aliasable_boxed(#name) } + } + + pub fn stored_type(&self) -> TokenStream { + let t = &self.typ; + if self.is_borrowed() { + quote! { ::ouroboros::macro_help::AliasableBox<#t> } + } else { + quote! { #t } + } + } + + /// Returns code which takes a variable with the same name and type as this field and turns it + /// into a static reference to its dereffed contents. + pub fn make_illegal_static_reference(&self) -> TokenStream { + let field_name = &self.name; + let ref_name = self.illegal_ref_name(); + quote! { + let #ref_name = unsafe { + ::ouroboros::macro_help::change_lifetime(&*#field_name) + }; + } + } + + /// Like make_illegal_static_reference, but provides a mutable reference instead. + pub fn make_illegal_static_mut_reference(&self) -> TokenStream { + let field_name = &self.name; + let ref_name = self.illegal_ref_name(); + quote! { + let #ref_name = unsafe { + ::ouroboros::macro_help::change_lifetime_mut(&mut *#field_name) + }; + } + } + + /// Generates an error requesting that the user explicitly specify whether or not the + /// field's type is covariant. + pub fn covariance_error(&self) { + let error = concat!( + "Ouroboros cannot automatically determine if this type is covariant.\n\n", + "If it is covariant, it should be legal to convert any instance of that type to an ", + "instance of that type where all usages of 'this are replaced with a smaller ", + "lifetime. For example, Box<&'this i32> is covariant because it is legal to use it as ", + "a Box<&'a i32> where 'this: 'a. In contrast, Fn(&'this i32) cannot be used as ", + "Fn(&'a i32).\n\n", + "To resolve this error, add #[covariant] or #[not_covariant] to the field.\n", + ); + proc_macro_error::emit_error!(self.typ, error); + } + + pub fn make_constructor_arg_type_impl( + &self, + info: &StructInfo, + make_builder_return_type: impl FnOnce() -> TokenStream, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let fake_lifetime = info.fake_lifetime(); + if self.borrows.is_empty() { + // Even if self_referencing is true, as long as borrows is empty, we don't need to use a + // builder to construct it. + let field_type = + replace_this_with_lifetime(field_type.into_token_stream(), fake_lifetime.clone()); + Ok(ArgType::Plain(quote! { #field_type })) + } else { + let mut field_builder_params = Vec::new(); + for borrow in &self.borrows { + if borrow.mutable { + let field = &info.fields[borrow.index]; + let field_type = &field.typ; + field_builder_params.push(quote! { + &'this mut #field_type + }); + } else { + let field = &info.fields[borrow.index]; + let field_type = &field.typ; + field_builder_params.push(quote! { + &'this #field_type + }); + } + } + let return_type = make_builder_return_type(); + let bound = quote! { for<'this> ::core::ops::FnOnce(#(#field_builder_params),*) -> #return_type }; + Ok(ArgType::TraitBound(bound)) + } + } + + /// Returns a trait bound if `for_field` refers to any other fields, and a plain type if not. This + /// is the type used in the constructor to initialize the value of `for_field`. + pub fn make_constructor_arg_type( + &self, + info: &StructInfo, + builder_type: BuilderType, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=#field_type> + ::core::marker::Send + 'this>> + } + } + BuilderType::Async => { + quote! { ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=#field_type> + 'this>> } + } + BuilderType::Sync => quote! { #field_type }, + }; + self.make_constructor_arg_type_impl(info, return_ty_constructor) + } + + /// Like make_constructor_arg_type, but used for the try_new constructor. + pub fn make_try_constructor_arg_type( + &self, + info: &StructInfo, + builder_type: BuilderType, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=::core::result::Result<#field_type, Error_>> + + ::core::marker::Send + 'this>> + } + } + BuilderType::Async => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=::core::result::Result<#field_type, Error_>> + + 'this>> + } + } + BuilderType::Sync => quote! { ::core::result::Result<#field_type, Error_> }, + }; + self.make_constructor_arg_type_impl(info, return_ty_constructor) + } +} |