Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ env:
jobs:
format:
name: Format
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

Expand All @@ -33,7 +33,7 @@ jobs:
clippy:
name: Clippy
needs: format
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

Expand All @@ -58,7 +58,7 @@ jobs:
tests:
name: Tests
needs: clippy
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
//! overrides the default behavior and allows specifying a custom error type, but all the error
//! types resulting from conversions must be convertible to this type.
//!
//! # Conversion type list
//!
//! Regardless of the conversion performed, the list of types provided to the attribute must contain
//! at least two types. A compilation error takes place otherwise:
//!
//! ```compile_fail
//! use transitive::Transitive;
//!
//! struct A;
//! #[derive(Transitive)]
//! #[transitive(from(A))] // fails to compile
//! struct B;
//! ```
//!
//! # Examples:
//!
//! ```
Expand Down
35 changes: 30 additions & 5 deletions src/transitive/fallible/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,47 @@ use syn::{
pub use try_from::TryTransitionFrom;
pub use try_into::TryTransitionInto;

use crate::transitive::TOO_FEW_TYPES_ERR_MSG;

/// A path list that may contain a custom error type.
pub struct FalliblePathList {
type_list: Vec<Type>,
/// First type in the transitive conversion. ie. `A` in
/// `#[transitive(try_from(A, B, C, D, E))]`
first_type: Type,
/// Intermediate types for the transitive conversion. ie. `[B, .., D]` in
/// `#[transitive(try_from(A, B, C, D, E))]`
intermediate_types: Vec<Type>,
/// Last type in the transitive conversion. ie. `E` in
/// `#[transitive(try_from(A, B, C, D, E))]`
last_type: Type,
error: Option<Type>,
}

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

let mut type_list = Vec::with_capacity(attr_list.len());
let mut attr_list_iter = attr_list.into_iter();
let (first_type, mut last_type) = match (attr_list_iter.next(), attr_list_iter.next()) {
(Some(Item::Type(ft)), Some(Item::Type(lt))) => (ft, lt),
_ => return Err(SynError::new(error_span, TOO_FEW_TYPES_ERR_MSG)),
};

let mut intermediate_types = Vec::with_capacity(attr_list_iter.len());
let mut error = None;

for attr in attr_list {
for attr in attr_list_iter {
match attr {
Item::Type(ty) if error.is_some() => {
let msg = "types not allowed after 'error'";
return Err(SynError::new_spanned(ty, msg));
}
// Just a regular type path in the conversion path
Item::Type(ty) => type_list.push(ty),
Item::Type(ty) => {
intermediate_types.push(last_type);
last_type = ty;
}
Item::Error(err) if error.is_some() => {
let msg = "'error' not allowed multiple times";
return Err(SynError::new_spanned(err, msg));
Expand All @@ -39,7 +59,12 @@ impl Parse for FalliblePathList {
}
}

let output = Self { type_list, error };
let output = Self {
first_type,
intermediate_types,
last_type,
error,
};

Ok(output)
}
Expand Down
12 changes: 5 additions & 7 deletions src/transitive/fallible/try_from.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::iter::once;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
Expand All @@ -23,17 +21,17 @@ impl ToTokens for TokenizablePath<'_, &TryTransitionFrom> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let first = self.path.0.type_list.first();
let last = self.path.0.type_list.last();
let first = &self.path.0.first_type;
let last = &self.path.0.last_type;

let stmts = self
.path
.0
.type_list
.intermediate_types
.iter()
.skip(1)
.chain(std::iter::once(last))
.map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;})
.chain(once(
.chain(std::iter::once(
quote! {let val = core::convert::TryFrom::try_from(val)?;},
));

Expand Down
13 changes: 5 additions & 8 deletions src/transitive/fallible/try_into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@ impl ToTokens for TokenizablePath<'_, &TryTransitionInto> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let last = self.path.0.type_list.last();
let second_last = self.path.0.type_list.get(self.path.0.type_list.len() - 2);
let first = &self.path.0.first_type;
let last = &self.path.0.last_type;
let second_last = self.path.0.intermediate_types.last().unwrap_or(first);

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

let error = self
Expand Down
17 changes: 9 additions & 8 deletions src/transitive/infallible/from.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
use std::iter::once;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Result as SynResult, Token, Type,
Result as SynResult,
};

use super::PathList;
use crate::transitive::TokenizablePath;

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

impl Parse for TransitionFrom {
fn parse(input: ParseStream) -> SynResult<Self> {
Punctuated::parse_terminated(input).map(Self)
PathList::parse(input).map(Self)
}
}

impl ToTokens for TokenizablePath<'_, &TransitionFrom> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let first = self.path.0.first();
let first = &self.path.0.first_type;
let last = &self.path.0.last_type;

let stmts = self
.path
.0
.intermediate_types
.iter()
.chain(std::iter::once(last))
.map(|ty| quote! {let val: #ty = core::convert::From::from(val);})
.chain(once(quote! {core::convert::From::from(val)}));
.chain(std::iter::once(quote! {core::convert::From::from(val)}));

let expanded = quote! {
impl #impl_generics core::convert::From<#first> for #name #ty_generics #where_clause {
Expand Down
18 changes: 8 additions & 10 deletions src/transitive/infallible/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,30 @@ use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Result as SynResult, Token, Type,
Result as SynResult,
};

use super::PathList;
use crate::transitive::TokenizablePath;

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

impl Parse for TransitionInto {
fn parse(input: ParseStream) -> SynResult<Self> {
Punctuated::parse_terminated(input).map(Self)
PathList::parse(input).map(Self)
}
}

impl ToTokens for TokenizablePath<'_, &TransitionInto> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let last = self.path.0.last();
let first = &self.path.0.first_type;
let last = &self.path.0.last_type;

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

let expanded = quote! {
Expand Down
43 changes: 43 additions & 0 deletions src/transitive/infallible/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,46 @@ mod into;

pub use from::TransitionFrom;
pub use into::TransitionInto;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Error as SynError, Result as SynResult, Token, Type,
};

use crate::transitive::TOO_FEW_TYPES_ERR_MSG;

pub struct PathList {
/// First type in the transitive conversion. ie. `A` in
/// `#[transitive(from(A, B, C, D, E))]`
first_type: Type,
/// Intermediate types for the transitive conversion. ie. `[B, .., D]` in
/// `#[transitive(from(A, B, C, D, E))]`
intermediate_types: Vec<Type>,
/// Last type in the transitive conversion. ie. `E` in
/// `#[transitive(from(A, B, C, D, E))]`
last_type: Type,
}

impl Parse for PathList {
fn parse(input: ParseStream) -> SynResult<Self> {
let error_span = input.span();
let attr_list = Punctuated::<Type, Token![,]>::parse_terminated(input)?;

let mut attr_list_iter = attr_list.into_iter();
let (first_type, mut last_type) = match (attr_list_iter.next(), attr_list_iter.next()) {
(Some(first_type), Some(last_type)) => (first_type, last_type),
_ => return Err(SynError::new(error_span, TOO_FEW_TYPES_ERR_MSG)),
};
let intermediate_types = attr_list_iter
.map(|ty| std::mem::replace(&mut last_type, ty))
.collect();

let output = Self {
first_type,
intermediate_types,
last_type,
};

Ok(output)
}
}
2 changes: 2 additions & 0 deletions src/transitive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use syn::{
DeriveInput, Error as SynError, Generics, Ident, MetaList, Result as SynResult, Token,
};

static TOO_FEW_TYPES_ERR_MSG: &str = "at least two types required";

/// The input to the [`crate::Transitive`] derive macro.
pub struct TransitiveInput {
ident: Ident,
Expand Down
Loading