Skip to content

Commit 8a35083

Browse files
authored
Improve type parameter parsing (#12)
1 parent b760e25 commit 8a35083

12 files changed

Lines changed: 250 additions & 106 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,52 @@
1-
21
# Change Log
2+
33
All notable changes to this project will be documented in this file.
4-
4+
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [1.1.0] - 2025-03-13
9+
10+
### Changed
11+
12+
- Allow more complex types in the macro attribute's type path.
13+
14+
### Added
15+
16+
- [#12](https://github.com/bobozaur/transitive/pull/12): Improves type parameters parsing to allow complex types,
17+
such as the ones with generics.
18+
819
## [1.0.1] - 2024-05-02
920

1021
### Changed
11-
22+
1223
- Clear out derive macro documentation.
1324

1425
## [1.0.0] - 2024-05-02
1526

1627
### Added
17-
- [#6](https://github.com/bobozaur/transitive/issues/6): Added generics support on the derived type.
28+
29+
- [#6](https://github.com/bobozaur/transitive/issues/6): Added generics support on the derived type.
1830

1931
### Changed
20-
32+
2133
- Updated dependencies.
2234
- Removed usage of `darling`.
2335
- Improved path direction parsing.
24-
36+
2537
## [0.5.0] - 2023-07-03
26-
38+
2739
### Added
28-
40+
2941
- [#8](https://github.com/bobozaur/transitive/issues/8): Added the ability to specify custom error types for fallible conversions.
3042

3143
### Changed
32-
44+
3345
- Updated dependencies.
3446
- Refactored library using `darling`.
3547
- Merged the two `TransitiveFrom` and `TransitiveTryFrom` derive macros into a single `Transitive` macro (the behavior is now controlled through the attribute modifiers).
3648
- Dropped the `all` macro attribute modifier.
37-
49+
3850
## [0.4.3] - 2023-03-09
3951

40-
First "feature complete" release.
52+
First "feature complete" release.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "transitive"
3-
version = "1.0.1"
3+
version = "1.1.0"
44
edition = "2021"
55
authors = ["bobozaur"]
66
description = "Transitive derive macros for Rust."
@@ -16,6 +16,6 @@ proc-macro = true
1616
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1717

1818
[dependencies]
19-
proc-macro2 = "1.0.81"
20-
quote = "1.0.36"
21-
syn = "2.0.60"
19+
proc-macro2 = "1"
20+
quote = "1"
21+
syn = "2"

src/transitive/fallible/mod.rs

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,71 @@ mod try_from;
22
mod try_into;
33

44
use syn::{
5-
parse::{Parse, ParseStream},
5+
parse::{discouraged::Speculative, Parse, ParseStream},
66
punctuated::Punctuated,
7-
spanned::Spanned,
8-
Error as SynError, Expr, Meta, Path, Result as SynResult, Token,
7+
Error as SynError, Ident, Result as SynResult, Token, Type,
98
};
109
pub use try_from::TryTransitionFrom;
1110
pub use try_into::TryTransitionInto;
1211

1312
/// A path list that may contain a custom error type.
1413
pub struct FalliblePathList {
15-
path_list: Vec<Path>,
16-
error: Option<Path>,
14+
type_list: Vec<Type>,
15+
error: Option<Type>,
1716
}
1817

1918
impl Parse for FalliblePathList {
2019
fn parse(input: ParseStream) -> SynResult<Self> {
21-
let meta_list = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
20+
let attr_list = Punctuated::<Item, Token![,]>::parse_terminated(input)?;
2221

23-
let mut path_list = Vec::with_capacity(meta_list.len());
22+
let mut type_list = Vec::with_capacity(attr_list.len());
2423
let mut error = None;
2524

26-
let mut iter = meta_list.into_iter().peekable();
27-
28-
while let Some(meta) = iter.next() {
29-
// If this is not the last element then it's definitely part of the path list
30-
if iter.peek().is_some() {
31-
match meta {
32-
Meta::Path(path) => path_list.push(path),
33-
_ => return Err(SynError::new(meta.span(), "invalid value in path list")),
34-
};
35-
} else {
36-
// We reached the last element which could be the custom error type
37-
match meta {
38-
// Just a regular type path in the conversion path
39-
Meta::Path(p) => path_list.push(p),
40-
// Custom error, but must check that it's a type path
41-
Meta::NameValue(n) if n.path.is_ident("error") => match n.value {
42-
Expr::Path(p) => error = Some(p.path),
43-
_ => return Err(SynError::new(n.value.span(), "error must be a type")),
44-
},
45-
_ => return Err(SynError::new(meta.span(), "invalid value in path list")),
25+
for attr in attr_list {
26+
match attr {
27+
Item::Type(ty) if error.is_some() => {
28+
let msg = "types not allowed after 'error'";
29+
return Err(SynError::new_spanned(ty, msg));
4630
}
31+
// Just a regular type path in the conversion path
32+
Item::Type(ty) => type_list.push(ty),
33+
Item::Error(err) if error.is_some() => {
34+
let msg = "'error' not allowed multiple times";
35+
return Err(SynError::new_spanned(err, msg));
36+
}
37+
// Custom error, but must check that it's a type path
38+
Item::Error(err) => error = Some(err),
4739
}
4840
}
4941

50-
let output = Self { path_list, error };
42+
let output = Self { type_list, error };
43+
5144
Ok(output)
5245
}
5346
}
47+
48+
/// An item in the parameters list of an attribute.
49+
pub enum Item {
50+
Type(Type),
51+
Error(Type),
52+
}
53+
54+
impl Parse for Item {
55+
fn parse(input: ParseStream) -> SynResult<Self> {
56+
let fork = input.fork();
57+
// Parse the ident name and the equal sign after it
58+
let res = fork
59+
.parse::<Ident>()
60+
.and_then(|ident| fork.parse::<Token![=]>().map(|_| ident));
61+
62+
match res {
63+
// We got an `error = MyType` argument
64+
Ok(path) if path == "error" => {
65+
input.advance_to(&fork);
66+
input.parse().map(Self::Error)
67+
}
68+
// Try to parse anything else as a type in the path list
69+
_ => input.parse().map(Self::Type),
70+
}
71+
}
72+
}

src/transitive/fallible/try_from.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ impl ToTokens for TokenizablePath<'_, &TryTransitionFrom> {
2323
fn to_tokens(&self, tokens: &mut TokenStream) {
2424
let name = self.ident;
2525
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
26-
let first = self.path.0.path_list.first();
27-
let last = self.path.0.path_list.last();
26+
let first = self.path.0.type_list.first();
27+
let last = self.path.0.type_list.last();
2828

2929
let stmts = self
3030
.path
3131
.0
32-
.path_list
32+
.type_list
3333
.iter()
3434
.skip(1)
3535
.map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;})

src/transitive/fallible/try_into.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ impl ToTokens for TokenizablePath<'_, &TryTransitionInto> {
2121
fn to_tokens(&self, tokens: &mut TokenStream) {
2222
let name = self.ident;
2323
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
24-
let last = self.path.0.path_list.last();
25-
let second_last = self.path.0.path_list.get(self.path.0.path_list.len() - 2);
24+
let last = self.path.0.type_list.last();
25+
let second_last = self.path.0.type_list.get(self.path.0.type_list.len() - 2);
2626

2727
let stmts = self
2828
.path
2929
.0
30-
.path_list
30+
.type_list
3131
.iter()
32-
.take(self.path.0.path_list.len() - 1)
32+
.take(self.path.0.type_list.len() - 1)
3333
.map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;});
3434

3535
let error = self

src/transitive/infallible/from.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use quote::{quote, ToTokens};
55
use syn::{
66
parse::{Parse, ParseStream},
77
punctuated::Punctuated,
8-
Path, Result as SynResult, Token,
8+
Result as SynResult, Token, Type,
99
};
1010

1111
use crate::transitive::TokenizablePath;
1212

1313
/// Path corresponding to a [`#[transitive(from(..))`] path.
14-
pub struct TransitionFrom(Punctuated<Path, Token![,]>);
14+
pub struct TransitionFrom(Punctuated<Type, Token![,]>);
1515

1616
impl Parse for TransitionFrom {
1717
fn parse(input: ParseStream) -> SynResult<Self> {

src/transitive/infallible/into.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use quote::{quote, ToTokens};
33
use syn::{
44
parse::{Parse, ParseStream},
55
punctuated::Punctuated,
6-
Path, Result as SynResult, Token,
6+
Result as SynResult, Token, Type,
77
};
88

99
use crate::transitive::TokenizablePath;
1010

1111
/// Path corresponding to a [`#[transitive(into(..))`] path.
12-
pub struct TransitionInto(Punctuated<Path, Token![,]>);
12+
pub struct TransitionInto(Punctuated<Type, Token![,]>);
1313

1414
impl Parse for TransitionInto {
1515
fn parse(input: ParseStream) -> SynResult<Self> {

tests/combined.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ mod macros;
33
use transitive::Transitive;
44

55
#[derive(Transitive)]
6-
// impl From<D>for A and impl From<C> for A
7-
#[transitive(from(D, C, B), from(C, B))]
6+
#[allow(clippy::duplicated_attributes)]
7+
#[transitive(from(D, C, B), from(C, B))] // impl From<D>for A and impl From<C> for A
88
#[transitive(try_into(B, C, D))] // impl TryFrom<A> for D
99
struct A;
1010
struct B;

tests/from.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ impl_from!(B to A);
1818
impl_from!(C to B);
1919
impl_from!(D to C);
2020

21+
#[allow(clippy::duplicated_attributes)]
2122
#[derive(Transitive)]
2223
#[transitive(from(D, C, B, A))] // impl From<D> for Z<T>
2324
#[transitive(from(C, B))] // impl From<D> for Z<T>
@@ -39,7 +40,7 @@ impl<T> From<B> for Z<T> {
3940
#[transitive(from(D, C, B, A))] // impl From<D> for Y<'a>
4041
struct Y<'a>(PhantomData<&'a ()>);
4142

42-
impl<'a> From<A> for Y<'a> {
43+
impl From<A> for Y<'_> {
4344
fn from(_value: A) -> Self {
4445
Self(PhantomData)
4546
}
@@ -57,14 +58,11 @@ impl<const N: usize> From<A> for W<N> {
5758

5859
#[derive(Transitive)]
5960
#[transitive(from(D, C, B, A))] // impl From<D> for Q<'a, 'b, N, T, U>
60-
struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send, U: 'b = &'b str>(PhantomData<(&'a T, &'b U)>)
61-
where
62-
T: Sync;
61+
struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>(
62+
PhantomData<(&'a T, &'b U)>,
63+
);
6364

64-
impl<'a, 'b: 'a, const N: usize, T: 'a + Send, U: 'b> From<A> for Q<'a, 'b, N, T, U>
65-
where
66-
T: Sync,
67-
{
65+
impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> From<A> for Q<'a, 'b, N, T, U> {
6866
fn from(_value: A) -> Self {
6967
Self(PhantomData)
7068
}

tests/into.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,11 @@ impl<const N: usize> From<W<N>> for A {
5050

5151
#[derive(Transitive)]
5252
#[transitive(into(A, B, C, D))] // impl From<Q<'a, 'b, N, T, U>> for D
53-
struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send, U: 'b = &'b str>(PhantomData<(&'a T, &'b U)>)
54-
where
55-
T: Sync;
53+
struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>(
54+
PhantomData<(&'a T, &'b U)>,
55+
);
5656

57-
impl<'a, 'b: 'a, const N: usize, T: 'a + Send, U: 'b> From<Q<'a, 'b, N, T, U>> for A
58-
where
59-
T: Sync,
60-
{
57+
impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> From<Q<'a, 'b, N, T, U>> for A {
6158
fn from(_value: Q<'a, 'b, N, T, U>) -> Self {
6259
Self
6360
}

0 commit comments

Comments
 (0)