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 {
(