Skip to content

Commit

Permalink
Add support for lifetime in ref_into (#19)
Browse files Browse the repository at this point in the history
* fix struct_main_code_block for owned into and ref into

* add support for ref into with lifetime
  • Loading branch information
vnghia authored Sep 4, 2024
1 parent beff388 commit be437b4
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 20 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -1443,7 +1448,7 @@ struct EntityDto{
```
</details>

### Reference with lifetime
### From ref with lifetime

```rust
use o2o::o2o;
Expand Down Expand Up @@ -1480,6 +1485,43 @@ pub struct EntityDto<'a, 'b> {
```
</details>

### 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,
}
```
<details>
<summary>View generated code</summary>

``` rust ignore
impl<'a, 'b, 'o2o> ::core::convert::Into<EntityDto<'a, 'b>> 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(),
}
}
}
```
</details>

### Lifetime both ways

```rust
Expand Down
84 changes: 65 additions & 19 deletions o2o-impl/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream> {
Expand Down Expand Up @@ -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::<Type>(ctx.dst_ty.clone()) {
path.path.segments.first().unwrap().ident.to_token_stream()
} else if let Ok(mut path) = parse2::<TypePath>(ctx.dst_ty.clone()) {
// In this case we want to transform something like `mod1::mod2::Entity<T>` 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()
};
Expand Down Expand Up @@ -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<Lifetime> = vec![];

if let Ok(dst_ty) = parse2::<syn::TypePath>(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<T> 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 {
&param.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 {
(
Expand Down

0 comments on commit be437b4

Please sign in to comment.