Skip to content

Commit 9b68218

Browse files
committed
add support for ref into with lifetime
1 parent a8f0136 commit 9b68218

File tree

2 files changed

+103
-20
lines changed

2 files changed

+103
-20
lines changed

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ And here's the code that `o2o` generates (from here on, generated code is produc
147147
- [Tuple structs](#tuple-structs)
148148
- [Tuples](#tuples)
149149
- [Type hints](#type-hints)
150+
- [From ref with lifetime](#from-ref-with-lifetime)
151+
- [Ref into with lifetime](#ref-into-with-lifetime)
152+
- [Lifetime both ways](#lifetime-both-ways)
150153
- [Generics](#generics)
154+
- [Generics both ways](#generics-both-ways)
151155
- [Where clauses](#where-clauses)
152156
- [Mapping to multiple structs](#mapping-to-multiple-structs)
153157
- [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
169173
- [Contributions](#contributions)
170174
- [License](#license)
171175

176+
172177
## Traits and `o2o` *trait instructions*
173178

174179
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{
14431448
```
14441449
</details>
14451450

1446-
### Reference with lifetime
1451+
### From ref with lifetime
14471452

14481453
```rust
14491454
use o2o::o2o;
@@ -1480,6 +1485,43 @@ pub struct EntityDto<'a, 'b> {
14801485
```
14811486
</details>
14821487

1488+
### Ref into with lifetime
1489+
1490+
```rust
1491+
use o2o::o2o;
1492+
1493+
#[derive(o2o)]
1494+
#[ref_into(EntityDto<'a, 'b>)]
1495+
pub struct Entity {
1496+
#[into(~.as_str())]
1497+
pub some_a: String,
1498+
#[into(~.as_str())]
1499+
pub some_b: String,
1500+
}
1501+
1502+
pub struct EntityDto<'a, 'b> {
1503+
pub some_a: &'a str,
1504+
pub some_b: &'b str,
1505+
}
1506+
```
1507+
<details>
1508+
<summary>View generated code</summary>
1509+
1510+
``` rust ignore
1511+
impl<'a, 'b, 'o2o> ::core::convert::Into<EntityDto<'a, 'b>> for &'o2o Entity
1512+
where
1513+
'o2o: 'a + 'b,
1514+
{
1515+
fn into(self) -> EntityDto<'a, 'b> {
1516+
EntityDto {
1517+
some_a: self.some_a.as_str(),
1518+
some_b: self.some_b.as_str(),
1519+
}
1520+
}
1521+
}
1522+
```
1523+
</details>
1524+
14831525
### Lifetime both ways
14841526

14851527
```rust

o2o-impl/src/expand.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{
1111
use proc_macro2::{Span, TokenStream};
1212
use quote::{format_ident, quote, ToTokens};
1313
use syn::{
14-
parse2, parse_quote, punctuated::Punctuated, Data, DeriveInput, Error, Index, Member, Result,
15-
Token, Type,
14+
parse2, parse_quote, punctuated::Punctuated, Data, DeriveInput, Error, GenericArgument,
15+
GenericParam, Index, Lifetime, Member, PathArguments, Result, Token, TypePath,
1616
};
1717

1818
pub fn derive(node: &DeriveInput) -> Result<TokenStream> {
@@ -417,11 +417,11 @@ fn struct_main_code_block(input: &Struct, ctx: &ImplContext) -> TokenStream {
417417
Kind::OwnedInto | Kind::RefInto => {
418418
let dst = if ctx.struct_attr.ty.nameless_tuple || ctx.has_post_init {
419419
TokenStream::new()
420-
} else if let Ok(Type::Path(mut path)) = parse2::<Type>(ctx.dst_ty.clone()) {
420+
} else if let Ok(mut path) = parse2::<TypePath>(ctx.dst_ty.clone()) {
421421
// In this case we want to transform something like `mod1::mod2::Entity<T>` to `mod1::mod2::Entity`.
422422
// So set all segments arguments to None.
423423
path.path.segments.iter_mut().for_each(|segment| {
424-
segment.arguments = syn::PathArguments::None;
424+
segment.arguments = PathArguments::None;
425425
});
426426
path.to_token_stream()
427427
} else {
@@ -1442,29 +1442,70 @@ struct QuoteTraitParams<'a> {
14421442
}
14431443

14441444
fn get_quote_trait_params<'a>(input: &DataType, ctx: &'a ImplContext) -> QuoteTraitParams<'a> {
1445-
// If there is at least one lifetime in generics,we add a new lifetime `'o2o` and add a bound `'o2o: 'a + 'b`.
14461445
let generics = input.get_generics();
1447-
let (gens_impl, where_clause, r) = if ctx.kind.is_ref() && generics.lifetimes().next().is_some()
1448-
{
1449-
let lifetimes: Vec<_> = generics
1450-
.lifetimes()
1451-
.map(|params| params.lifetime.clone())
1452-
.collect();
1446+
let mut generics_impl = generics.clone();
1447+
let mut lifetimes: Vec<Lifetime> = vec![];
1448+
1449+
if let Ok(dst_ty) = parse2::<syn::TypePath>(ctx.dst_ty.clone()) {
1450+
// The idea is to check if all lifetimes of the dst are included in the input generics or not.
1451+
// If not, we will add the missing ones to the input generics.
1452+
1453+
let dst_generics = match &dst_ty.path.segments.last().unwrap().arguments {
1454+
PathArguments::None => syn::punctuated::Punctuated::new(),
1455+
PathArguments::AngleBracketed(args) => args.args.clone(),
1456+
PathArguments::Parenthesized(_) => {
1457+
unimplemented!("Only Struct<T> syntax is supported")
1458+
}
1459+
};
1460+
1461+
for dst_generic in dst_generics {
1462+
if let GenericArgument::Lifetime(arg) = &dst_generic {
1463+
lifetimes.push(parse_quote!(#dst_generic));
1464+
if generics.params.iter().all(|param| {
1465+
if let GenericParam::Lifetime(param) = param {
1466+
&param.lifetime != arg
1467+
} else {
1468+
// Skip any other generic param
1469+
false
1470+
}
1471+
}) {
1472+
generics_impl.params.push(parse_quote!(#dst_generic));
1473+
}
1474+
}
1475+
}
1476+
}
14531477

1454-
let mut generics = generics.clone();
1455-
generics.params.push(parse_quote!('o2o));
1478+
// If there is at least one lifetime in generics, we add a new lifetime `'o2o` and add a bound `'o2o: 'a + 'b`.
1479+
let (gens_impl, where_clause, r) = if ctx.kind.is_ref() {
1480+
// If lifetime is empty, we assume that lifetime generics come from the other structure (src <-> dst).
1481+
let lifetimes: Vec<_> = if lifetimes.is_empty() {
1482+
generics
1483+
.lifetimes()
1484+
.map(|params| params.lifetime.clone())
1485+
.collect()
1486+
} else {
1487+
lifetimes
1488+
};
14561489

14571490
let mut where_clause = input
14581491
.get_attrs()
14591492
.where_attr(&ctx.struct_attr.ty)
1460-
.map(|x| x.where_clause.clone())
1461-
.unwrap_or_default();
1462-
where_clause.push(parse_quote!('o2o: #( #lifetimes )+*));
1493+
.map(|x| x.where_clause.clone());
1494+
1495+
let r = if !lifetimes.is_empty() {
1496+
generics_impl.params.push(parse_quote!('o2o));
1497+
let mut where_clause_punctuated = where_clause.unwrap_or_default();
1498+
where_clause_punctuated.push(parse_quote!('o2o: #( #lifetimes )+*));
1499+
where_clause = Some(where_clause_punctuated);
1500+
Some(quote!(&'o2o))
1501+
} else {
1502+
Some(quote!(&))
1503+
};
14631504

14641505
(
1465-
generics.to_token_stream(),
1466-
Some(quote!(where #where_clause)),
1467-
Some(quote!(&'o2o)),
1506+
generics_impl.to_token_stream(),
1507+
where_clause.map(|where_clause| quote!(where #where_clause)),
1508+
r,
14681509
)
14691510
} else {
14701511
(

0 commit comments

Comments
 (0)