From be437b4308a1ea05ff14c6640fda3868dcf9c5ec Mon Sep 17 00:00:00 2001 From: Nghia Date: Wed, 4 Sep 2024 09:06:43 +0000 Subject: [PATCH] Add support for lifetime in `ref_into` (#19) * fix struct_main_code_block for owned into and ref into * add support for ref into with lifetime --- README.md | 44 +++++++++++++++++++++- o2o-impl/src/expand.rs | 84 ++++++++++++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index be36c84..0d431e4 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,11 @@ And here's the code that `o2o` generates (from here on, generated code is produc - [Tuple structs](#tuple-structs) - [Tuples](#tuples) - [Type hints](#type-hints) + - [From ref with lifetime](#from-ref-with-lifetime) + - [Ref into with lifetime](#ref-into-with-lifetime) + - [Lifetime both ways](#lifetime-both-ways) - [Generics](#generics) + - [Generics both ways](#generics-both-ways) - [Where clauses](#where-clauses) - [Mapping to multiple structs](#mapping-to-multiple-structs) - [Avoiding proc macro attribute name collisions (alternative instruction syntax)](#avoiding-proc-macro-attribute-name-collisions-alternative-instruction-syntax) @@ -169,6 +173,7 @@ And here's the code that `o2o` generates (from here on, generated code is produc - [Contributions](#contributions) - [License](#license) + ## Traits and `o2o` *trait instructions* To let o2o know what traits you want implemented, you have to use type-level `o2o` *trait instructions* (i.e. proc macro attributes): @@ -1443,7 +1448,7 @@ struct EntityDto{ ``` -### Reference with lifetime +### From ref with lifetime ```rust use o2o::o2o; @@ -1480,6 +1485,43 @@ pub struct EntityDto<'a, 'b> { ``` +### Ref into with lifetime + +```rust +use o2o::o2o; + +#[derive(o2o)] +#[ref_into(EntityDto<'a, 'b>)] +pub struct Entity { + #[into(~.as_str())] + pub some_a: String, + #[into(~.as_str())] + pub some_b: String, +} + +pub struct EntityDto<'a, 'b> { + pub some_a: &'a str, + pub some_b: &'b str, +} +``` +
+ View generated code + + ``` rust ignore + impl<'a, 'b, 'o2o> ::core::convert::Into> for &'o2o Entity + where + 'o2o: 'a + 'b, + { + fn into(self) -> EntityDto<'a, 'b> { + EntityDto { + some_a: self.some_a.as_str(), + some_b: self.some_b.as_str(), + } + } + } + ``` +
+ ### Lifetime both ways ```rust diff --git a/o2o-impl/src/expand.rs b/o2o-impl/src/expand.rs index aa4f5b8..cad8339 100644 --- a/o2o-impl/src/expand.rs +++ b/o2o-impl/src/expand.rs @@ -11,8 +11,8 @@ use crate::{ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse2, parse_quote, punctuated::Punctuated, Data, DeriveInput, Error, Index, Member, Result, - Token, Type, + parse2, parse_quote, punctuated::Punctuated, Data, DeriveInput, Error, GenericArgument, + GenericParam, Index, Lifetime, Member, PathArguments, Result, Token, TypePath, }; pub fn derive(node: &DeriveInput) -> Result { @@ -417,8 +417,13 @@ fn struct_main_code_block(input: &Struct, ctx: &ImplContext) -> TokenStream { Kind::OwnedInto | Kind::RefInto => { let dst = if ctx.struct_attr.ty.nameless_tuple || ctx.has_post_init { TokenStream::new() - } else if let Ok(Type::Path(path)) = parse2::(ctx.dst_ty.clone()) { - path.path.segments.first().unwrap().ident.to_token_stream() + } else if let Ok(mut path) = parse2::(ctx.dst_ty.clone()) { + // In this case we want to transform something like `mod1::mod2::Entity` to `mod1::mod2::Entity`. + // So set all segments arguments to None. + path.path.segments.iter_mut().for_each(|segment| { + segment.arguments = PathArguments::None; + }); + path.to_token_stream() } else { ctx.dst_ty.clone() }; @@ -1437,29 +1442,70 @@ struct QuoteTraitParams<'a> { } fn get_quote_trait_params<'a>(input: &DataType, ctx: &'a ImplContext) -> QuoteTraitParams<'a> { - // If there is at least one lifetime in generics,we add a new lifetime `'o2o` and add a bound `'o2o: 'a + 'b`. let generics = input.get_generics(); - let (gens_impl, where_clause, r) = if ctx.kind.is_ref() && generics.lifetimes().next().is_some() - { - let lifetimes: Vec<_> = generics - .lifetimes() - .map(|params| params.lifetime.clone()) - .collect(); + let mut generics_impl = generics.clone(); + let mut lifetimes: Vec = vec![]; + + if let Ok(dst_ty) = parse2::(ctx.dst_ty.clone()) { + // The idea is to check if all lifetimes of the dst are included in the input generics or not. + // If not, we will add the missing ones to the input generics. + + let dst_generics = match &dst_ty.path.segments.last().unwrap().arguments { + PathArguments::None => syn::punctuated::Punctuated::new(), + PathArguments::AngleBracketed(args) => args.args.clone(), + PathArguments::Parenthesized(_) => { + unimplemented!("Only Struct syntax is supported") + } + }; - let mut generics = generics.clone(); - generics.params.push(parse_quote!('o2o)); + for dst_generic in dst_generics { + if let GenericArgument::Lifetime(arg) = &dst_generic { + lifetimes.push(parse_quote!(#dst_generic)); + if generics.params.iter().all(|param| { + if let GenericParam::Lifetime(param) = param { + ¶m.lifetime != arg + } else { + // Skip any other generic param + false + } + }) { + generics_impl.params.push(parse_quote!(#dst_generic)); + } + } + } + } + + // If there is at least one lifetime in generics, we add a new lifetime `'o2o` and add a bound `'o2o: 'a + 'b`. + let (gens_impl, where_clause, r) = if ctx.kind.is_ref() { + // If lifetime is empty, we assume that lifetime generics come from the other structure (src <-> dst). + let lifetimes: Vec<_> = if lifetimes.is_empty() { + generics + .lifetimes() + .map(|params| params.lifetime.clone()) + .collect() + } else { + lifetimes + }; let mut where_clause = input .get_attrs() .where_attr(&ctx.struct_attr.ty) - .map(|x| x.where_clause.clone()) - .unwrap_or_default(); - where_clause.push(parse_quote!('o2o: #( #lifetimes )+*)); + .map(|x| x.where_clause.clone()); + + let r = if !lifetimes.is_empty() { + generics_impl.params.push(parse_quote!('o2o)); + let mut where_clause_punctuated = where_clause.unwrap_or_default(); + where_clause_punctuated.push(parse_quote!('o2o: #( #lifetimes )+*)); + where_clause = Some(where_clause_punctuated); + Some(quote!(&'o2o)) + } else { + Some(quote!(&)) + }; ( - generics.to_token_stream(), - Some(quote!(where #where_clause)), - Some(quote!(&'o2o)), + generics_impl.to_token_stream(), + where_clause.map(|where_clause| quote!(where #where_clause)), + r, ) } else { (