Skip to content

Commit 758aabf

Browse files
A-Manningbobozaur
andauthored
Fix parsing for single-type transitive paths (#13) (#14)
* Fix parsing for single-type transitive paths * Add doc test against single type paths * Bump Ubuntu GH runner version --------- Co-authored-by: Bogdan Mircea <mirceapetrebogdan@gmail.com>
1 parent 8a35083 commit 758aabf

9 files changed

Lines changed: 119 additions & 41 deletions

File tree

.github/workflows/main.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ env:
1313
jobs:
1414
format:
1515
name: Format
16-
runs-on: ubuntu-20.04
16+
runs-on: ubuntu-22.04
1717
steps:
1818
- uses: actions/checkout@v3
1919

@@ -33,7 +33,7 @@ jobs:
3333
clippy:
3434
name: Clippy
3535
needs: format
36-
runs-on: ubuntu-20.04
36+
runs-on: ubuntu-22.04
3737
steps:
3838
- uses: actions/checkout@v3
3939

@@ -58,7 +58,7 @@ jobs:
5858
tests:
5959
name: Tests
6060
needs: clippy
61-
runs-on: ubuntu-20.04
61+
runs-on: ubuntu-22.04
6262

6363
steps:
6464
- uses: actions/checkout@v3

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@
3434
//! overrides the default behavior and allows specifying a custom error type, but all the error
3535
//! types resulting from conversions must be convertible to this type.
3636
//!
37+
//! # Conversion type list
38+
//!
39+
//! Regardless of the conversion performed, the list of types provided to the attribute must contain
40+
//! at least two types. A compilation error takes place otherwise:
41+
//!
42+
//! ```compile_fail
43+
//! use transitive::Transitive;
44+
//!
45+
//! struct A;
46+
//! #[derive(Transitive)]
47+
//! #[transitive(from(A))] // fails to compile
48+
//! struct B;
49+
//! ```
50+
//!
3751
//! # Examples:
3852
//!
3953
//! ```

src/transitive/fallible/mod.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,47 @@ use syn::{
99
pub use try_from::TryTransitionFrom;
1010
pub use try_into::TryTransitionInto;
1111

12+
use crate::transitive::TOO_FEW_TYPES_ERR_MSG;
13+
1214
/// A path list that may contain a custom error type.
1315
pub struct FalliblePathList {
14-
type_list: Vec<Type>,
16+
/// First type in the transitive conversion. ie. `A` in
17+
/// `#[transitive(try_from(A, B, C, D, E))]`
18+
first_type: Type,
19+
/// Intermediate types for the transitive conversion. ie. `[B, .., D]` in
20+
/// `#[transitive(try_from(A, B, C, D, E))]`
21+
intermediate_types: Vec<Type>,
22+
/// Last type in the transitive conversion. ie. `E` in
23+
/// `#[transitive(try_from(A, B, C, D, E))]`
24+
last_type: Type,
1525
error: Option<Type>,
1626
}
1727

1828
impl Parse for FalliblePathList {
1929
fn parse(input: ParseStream) -> SynResult<Self> {
30+
let error_span = input.span();
2031
let attr_list = Punctuated::<Item, Token![,]>::parse_terminated(input)?;
2132

22-
let mut type_list = Vec::with_capacity(attr_list.len());
33+
let mut attr_list_iter = attr_list.into_iter();
34+
let (first_type, mut last_type) = match (attr_list_iter.next(), attr_list_iter.next()) {
35+
(Some(Item::Type(ft)), Some(Item::Type(lt))) => (ft, lt),
36+
_ => return Err(SynError::new(error_span, TOO_FEW_TYPES_ERR_MSG)),
37+
};
38+
39+
let mut intermediate_types = Vec::with_capacity(attr_list_iter.len());
2340
let mut error = None;
2441

25-
for attr in attr_list {
42+
for attr in attr_list_iter {
2643
match attr {
2744
Item::Type(ty) if error.is_some() => {
2845
let msg = "types not allowed after 'error'";
2946
return Err(SynError::new_spanned(ty, msg));
3047
}
3148
// Just a regular type path in the conversion path
32-
Item::Type(ty) => type_list.push(ty),
49+
Item::Type(ty) => {
50+
intermediate_types.push(last_type);
51+
last_type = ty;
52+
}
3353
Item::Error(err) if error.is_some() => {
3454
let msg = "'error' not allowed multiple times";
3555
return Err(SynError::new_spanned(err, msg));
@@ -39,7 +59,12 @@ impl Parse for FalliblePathList {
3959
}
4060
}
4161

42-
let output = Self { type_list, error };
62+
let output = Self {
63+
first_type,
64+
intermediate_types,
65+
last_type,
66+
error,
67+
};
4368

4469
Ok(output)
4570
}

src/transitive/fallible/try_from.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::iter::once;
2-
31
use proc_macro2::TokenStream;
42
use quote::{quote, ToTokens};
53
use syn::{
@@ -23,17 +21,17 @@ impl ToTokens for TokenizablePath<'_, &TryTransitionFrom> {
2321
fn to_tokens(&self, tokens: &mut TokenStream) {
2422
let name = self.ident;
2523
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
26-
let first = self.path.0.type_list.first();
27-
let last = self.path.0.type_list.last();
24+
let first = &self.path.0.first_type;
25+
let last = &self.path.0.last_type;
2826

2927
let stmts = self
3028
.path
3129
.0
32-
.type_list
30+
.intermediate_types
3331
.iter()
34-
.skip(1)
32+
.chain(std::iter::once(last))
3533
.map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;})
36-
.chain(once(
34+
.chain(std::iter::once(
3735
quote! {let val = core::convert::TryFrom::try_from(val)?;},
3836
));
3937

src/transitive/fallible/try_into.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ 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.type_list.last();
25-
let second_last = self.path.0.type_list.get(self.path.0.type_list.len() - 2);
24+
let first = &self.path.0.first_type;
25+
let last = &self.path.0.last_type;
26+
let second_last = self.path.0.intermediate_types.last().unwrap_or(first);
2627

27-
let stmts = self
28-
.path
29-
.0
30-
.type_list
31-
.iter()
32-
.take(self.path.0.type_list.len() - 1)
28+
let stmts = std::iter::once(first)
29+
.chain(&self.path.0.intermediate_types)
3330
.map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;});
3431

3532
let error = self

src/transitive/infallible/from.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
1-
use std::iter::once;
2-
31
use proc_macro2::TokenStream;
42
use quote::{quote, ToTokens};
53
use syn::{
64
parse::{Parse, ParseStream},
7-
punctuated::Punctuated,
8-
Result as SynResult, Token, Type,
5+
Result as SynResult,
96
};
107

8+
use super::PathList;
119
use crate::transitive::TokenizablePath;
1210

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

1614
impl Parse for TransitionFrom {
1715
fn parse(input: ParseStream) -> SynResult<Self> {
18-
Punctuated::parse_terminated(input).map(Self)
16+
PathList::parse(input).map(Self)
1917
}
2018
}
2119

2220
impl ToTokens for TokenizablePath<'_, &TransitionFrom> {
2321
fn to_tokens(&self, tokens: &mut TokenStream) {
2422
let name = self.ident;
2523
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
26-
let first = self.path.0.first();
24+
let first = &self.path.0.first_type;
25+
let last = &self.path.0.last_type;
2726

2827
let stmts = self
2928
.path
3029
.0
30+
.intermediate_types
3131
.iter()
32+
.chain(std::iter::once(last))
3233
.map(|ty| quote! {let val: #ty = core::convert::From::from(val);})
33-
.chain(once(quote! {core::convert::From::from(val)}));
34+
.chain(std::iter::once(quote! {core::convert::From::from(val)}));
3435

3536
let expanded = quote! {
3637
impl #impl_generics core::convert::From<#first> for #name #ty_generics #where_clause {

src/transitive/infallible/into.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,30 @@ use proc_macro2::TokenStream;
22
use quote::{quote, ToTokens};
33
use syn::{
44
parse::{Parse, ParseStream},
5-
punctuated::Punctuated,
6-
Result as SynResult, Token, Type,
5+
Result as SynResult,
76
};
87

8+
use super::PathList;
99
use crate::transitive::TokenizablePath;
1010

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

1414
impl Parse for TransitionInto {
1515
fn parse(input: ParseStream) -> SynResult<Self> {
16-
Punctuated::parse_terminated(input).map(Self)
16+
PathList::parse(input).map(Self)
1717
}
1818
}
1919

2020
impl ToTokens for TokenizablePath<'_, &TransitionInto> {
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.last();
24+
let first = &self.path.0.first_type;
25+
let last = &self.path.0.last_type;
2526

26-
let stmts = self
27-
.path
28-
.0
29-
.iter()
30-
.take(self.path.0.len() - 1)
27+
let stmts = std::iter::once(first)
28+
.chain(&self.path.0.intermediate_types)
3129
.map(|ty| quote! {let val: #ty = core::convert::From::from(val);});
3230

3331
let expanded = quote! {

src/transitive/infallible/mod.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,46 @@ mod into;
33

44
pub use from::TransitionFrom;
55
pub use into::TransitionInto;
6+
use syn::{
7+
parse::{Parse, ParseStream},
8+
punctuated::Punctuated,
9+
Error as SynError, Result as SynResult, Token, Type,
10+
};
11+
12+
use crate::transitive::TOO_FEW_TYPES_ERR_MSG;
13+
14+
pub struct PathList {
15+
/// First type in the transitive conversion. ie. `A` in
16+
/// `#[transitive(from(A, B, C, D, E))]`
17+
first_type: Type,
18+
/// Intermediate types for the transitive conversion. ie. `[B, .., D]` in
19+
/// `#[transitive(from(A, B, C, D, E))]`
20+
intermediate_types: Vec<Type>,
21+
/// Last type in the transitive conversion. ie. `E` in
22+
/// `#[transitive(from(A, B, C, D, E))]`
23+
last_type: Type,
24+
}
25+
26+
impl Parse for PathList {
27+
fn parse(input: ParseStream) -> SynResult<Self> {
28+
let error_span = input.span();
29+
let attr_list = Punctuated::<Type, Token![,]>::parse_terminated(input)?;
30+
31+
let mut attr_list_iter = attr_list.into_iter();
32+
let (first_type, mut last_type) = match (attr_list_iter.next(), attr_list_iter.next()) {
33+
(Some(first_type), Some(last_type)) => (first_type, last_type),
34+
_ => return Err(SynError::new(error_span, TOO_FEW_TYPES_ERR_MSG)),
35+
};
36+
let intermediate_types = attr_list_iter
37+
.map(|ty| std::mem::replace(&mut last_type, ty))
38+
.collect();
39+
40+
let output = Self {
41+
first_type,
42+
intermediate_types,
43+
last_type,
44+
};
45+
46+
Ok(output)
47+
}
48+
}

src/transitive/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use syn::{
1111
DeriveInput, Error as SynError, Generics, Ident, MetaList, Result as SynResult, Token,
1212
};
1313

14+
static TOO_FEW_TYPES_ERR_MSG: &str = "at least two types required";
15+
1416
/// The input to the [`crate::Transitive`] derive macro.
1517
pub struct TransitiveInput {
1618
ident: Ident,

0 commit comments

Comments
 (0)