Skip to content

Commit

Permalink
add more lifetime/generics support for o2o (#17)
Browse files Browse the repository at this point in the history
* add more lifetime support for o2o

* add support for template parameters in into

* add example reference with lifetime

* add new lifetime for from_ref

* add new lifetime o2o for ref convert

* update test for reference with lifetime
  • Loading branch information
vnghia authored Aug 30, 2024
1 parent ebd9cc7 commit 15c5980
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 27 deletions.
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,75 @@ struct EntityDto{
```
</details>

### Reference with lifetime

```rust
use o2o::o2o;

pub struct Entity {
pub some_a: String,
pub some_b: String,
}

#[derive(o2o)]
#[from_ref(Entity)]
pub struct EntityDto<'a, 'b> {
#[from(~.as_str())]
pub some_a: &'a str,
#[from(~.as_str())]
pub some_b: &'b str,
}
```
<details>
<summary>View generated code</summary>

``` rust ignore
impl<'a, 'b, 'o2o> ::core::convert::From<&'o2o Entity> for EntityDto<'a, 'b>
where
'o2o: 'a + 'b,
{
fn from(value: &'o2o Entity) -> EntityDto<'a, 'b> {
EntityDto {
some_a: value.some_a.as_str(),
some_b: value.some_b.as_str(),
}
}
}
```
</details>

### Lifetime both ways

```rust
use o2o::o2o;

struct Entity<'a> {
some_str: &'a str,
}

#[derive(o2o)]
#[map_owned(Entity<'a>)]
struct EntityDto<'a> {
some_str: &'a str,
}
```
<details>
<summary>View generated code</summary>

``` rust ignore
impl<'a> ::core::convert::From<Entity<'a>> for EntityDto<'a> {
fn from(value: Entity<'a>) -> EntityDto<'a> {
EntityDto { some_str: value.some_str }
}
}
impl<'a> ::core::convert::Into<Entity<'a>> for EntityDto<'a> {
fn into(self) -> Entity<'a> {
Entity { some_str: self.some_str }
}
}
```
</details>

### Generics

``` rust
Expand Down Expand Up @@ -1483,6 +1552,42 @@ struct EntityDto {
```
</details>

### Generics both ways

```rust
use o2o::o2o;

struct Entity<T> {
something: T,
}

#[derive(o2o)]
#[map_owned(Entity<T>)]
struct EntityDto<T> {
something: T,
}
```
<details>
<summary>View generated code</summary>

``` rust ignore
impl<T> ::core::convert::From<Entity<T>> for EntityDto<T> {
fn from(value: Entity<T>) -> EntityDto<T> {
EntityDto {
something: value.something,
}
}
}
impl<T> ::core::convert::Into<Entity<T>> for EntityDto<T> {
fn into(self) -> Entity<T> {
Entity {
something: self.something,
}
}
}
```
</details>

### Where clauses

``` rust
Expand Down
93 changes: 66 additions & 27 deletions o2o-impl/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
attr::{ApplicableAttr, ChildData, ChildPath, DataTypeAttrs, GhostData, GhostIdent, InitData, Kind, TraitAttrCore, TypeHint}, validate::validate
};
use proc_macro2::{TokenStream, Span};
use syn::{punctuated::Punctuated, Data, DeriveInput, Error, Index, Member, Result, Token};
use syn::{parse2, parse_quote, punctuated::Punctuated, Data, DeriveInput, Error, Index, Member, Result, Token, Type};
use quote::{format_ident, quote, ToTokens};

pub fn derive(node: &DeriveInput) -> Result<TokenStream> {
Expand Down Expand Up @@ -333,9 +333,15 @@ fn struct_main_code_block(input: &Struct, ctx: &ImplContext) -> TokenStream {
quote!(#dst #struct_init_block)
},
Kind::OwnedInto | Kind::RefInto => {
let dst = if ctx.struct_attr.ty.nameless_tuple || ctx.has_post_init { TokenStream::new() } else { ctx.dst_ty.clone() };
let dst = if ctx.struct_attr.ty.nameless_tuple || ctx.has_post_init {
TokenStream::new()
} else if let Ok(Type::Path(path)) = parse2::<Type>(ctx.dst_ty.clone()) {
path.path.segments.first().unwrap().ident.to_token_stream()
} else {
ctx.dst_ty.clone()
};
quote!(#dst #struct_init_block)
},
}
Kind::OwnedIntoExisting | Kind::RefIntoExisting => struct_init_block,
}
}
Expand Down Expand Up @@ -1052,31 +1058,64 @@ struct QuoteTraitParams<'a> {
pub dst: &'a TokenStream,
pub src: &'a TokenStream,
pub gens: TokenStream,
pub gens_impl: TokenStream,
pub where_clause: Option<TokenStream>,
pub r: Option<TokenStream>,
}

fn get_quote_trait_params<'a>(input: &DataType, ctx: &'a ImplContext) -> QuoteTraitParams<'a> {
QuoteTraitParams {
attr: ctx.struct_attr.attribute.as_ref(),
impl_attr: ctx.struct_attr.impl_attribute.as_ref(),
inner_attr: ctx.struct_attr.inner_attribute.as_ref(),
dst: ctx.dst_ty,
src: ctx.src_ty,
gens: input.get_generics().to_token_stream(),
where_clause: input.get_attrs().where_attr(&ctx.struct_attr.ty).map(|x| {
let where_clause = x.where_clause.to_token_stream();
quote!(where #where_clause)
}),
r: ctx.kind.is_ref().then_some(quote!(&))
// 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 = generics.clone();
generics.params.push(parse_quote!('o2o));

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 )+*));

(
generics.to_token_stream(),
Some(quote!(where #where_clause)),
Some(quote!(&'o2o)),
)
} else {
(
input.get_generics().to_token_stream(),
input.get_attrs().where_attr(&ctx.struct_attr.ty).map(|x| {
let where_clause = x.where_clause.to_token_stream();
quote!(where #where_clause)
}),
ctx.kind.is_ref().then_some(quote!(&)),
)
};

QuoteTraitParams {
attr: ctx.struct_attr.attribute.as_ref(),
impl_attr: ctx.struct_attr.impl_attribute.as_ref(),
inner_attr: ctx.struct_attr.inner_attribute.as_ref(),
dst: ctx.dst_ty,
src: ctx.src_ty,
gens: generics.to_token_stream(),
gens_impl,
where_clause,
r,
}
}

fn quote_from_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);
quote! {
#impl_attr
impl #gens ::core::convert::From<#r #src> for #dst #gens #where_clause {
impl #gens_impl ::core::convert::From<#r #src> for #dst #gens #where_clause {
#attr
fn from(value: #r #src) -> #dst #gens {
#inner_attr
Expand All @@ -1088,11 +1127,11 @@ fn quote_from_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenS
}

fn quote_try_from_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);
let err_ty = &ctx.struct_attr.err_ty.as_ref().unwrap().path;
quote! {
#impl_attr
impl #gens ::core::convert::TryFrom<#r #src> for #dst #gens #where_clause {
impl #gens_impl ::core::convert::TryFrom<#r #src> for #dst #gens #where_clause {
type Error = #err_ty;
#attr
fn try_from(value: #r #src) -> Result<#dst #gens, #err_ty> {
Expand All @@ -1105,7 +1144,7 @@ fn quote_try_from_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<To
}

fn quote_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream, post_init: Option<TokenStream>) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);

let body = match post_init {
Some(post_init) => quote! {
Expand All @@ -1122,7 +1161,7 @@ fn quote_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenS

quote!{
#impl_attr
impl #gens ::core::convert::Into<#dst> for #r #src #gens #where_clause {
impl #gens_impl ::core::convert::Into<#dst> for #r #src #gens #where_clause {
#attr
fn into(self) -> #dst {
#inner_attr
Expand All @@ -1133,7 +1172,7 @@ fn quote_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenS
}

fn quote_try_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream, post_init: Option<TokenStream>) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);
let err_ty = &ctx.struct_attr.err_ty.as_ref().unwrap().path;

let body = match post_init {
Expand All @@ -1151,7 +1190,7 @@ fn quote_try_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<To

quote! {
#impl_attr
impl #gens ::core::convert::TryInto<#dst> for #r #src #gens #where_clause {
impl #gens_impl ::core::convert::TryInto<#dst> for #r #src #gens #where_clause {
type Error = #err_ty;
#attr
fn try_into(self) -> Result<#dst, #err_ty> {
Expand All @@ -1163,10 +1202,10 @@ fn quote_try_into_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<To
}

fn quote_into_existing_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream, post_init: Option<TokenStream>) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);
quote! {
#impl_attr
impl #gens o2o::traits::IntoExisting<#dst> for #r #src #gens #where_clause {
impl #gens_impl o2o::traits::IntoExisting<#dst> for #r #src #gens #where_clause {
#attr
fn into_existing(self, other: &mut #dst) {
#inner_attr
Expand All @@ -1179,11 +1218,11 @@ fn quote_into_existing_trait(input: &DataType, ctx: &ImplContext, pre_init: Opti
}

fn quote_try_into_existing_trait(input: &DataType, ctx: &ImplContext, pre_init: Option<TokenStream>, init: TokenStream, post_init: Option<TokenStream>) -> TokenStream {
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, where_clause, r } = get_quote_trait_params(input, ctx);
let QuoteTraitParams { attr, impl_attr, inner_attr, dst, src, gens, gens_impl, where_clause, r } = get_quote_trait_params(input, ctx);
let err_ty = &ctx.struct_attr.err_ty.as_ref().unwrap().path;
quote! {
#impl_attr
impl #gens o2o::traits::TryIntoExisting<#dst> for #r #src #gens #where_clause {
impl #gens_impl o2o::traits::TryIntoExisting<#dst> for #r #src #gens #where_clause {
type Error = #err_ty;
#attr
fn try_into_existing(self, other: &mut #dst) -> Result<(), #err_ty> {
Expand Down

0 comments on commit 15c5980

Please sign in to comment.