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
65 changes: 24 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ For example, to define a function to zip a pair of tuples into a tuple of pairs:

```rust
#[typle(Tuple for 0..=12)]
pub fn zip<A: Tuple, B: Tuple>(
a: A,
b: B,
) -> (typle! {
pub fn zip<A: Tuple, B: Tuple>(a: A, b: B) -> (typle! {
i in .. => (A<{i}>, B<{i}>)
})
{
}) {
(typle! {
i in .. => (a[[i]], b[[i]])
})
}

```

The types `A` and `B` are generic but are constrained to be tuples. The tuples
Expand Down Expand Up @@ -59,46 +56,36 @@ struct MultipleHandlers<T> {
handlers: T,
}

#[typle(Tuple for 0..=3)]
#[typle(Tuple for 1..=3)]
impl<T> HandleStuff for MultipleHandlers<T>
where
T: Tuple, // `T` is a tuple with 0 to 3 components.
T<_>: HandleStuff, // All components implement `HandleStuff`.
T: Tuple, // `T` is a tuple with 1 to 3 components.
T<_>: HandleStuff, // All components implement `HandleStuff`.
{
// Return a tuple of output from each handler applied to the same input.
type Output = (typle! {i in .. => T<{i}>::Output});

// Return a tuple of output from each handler applied to the same input.
fn handle_stuff(&self, input: Input) -> Self::Output {
if typle_const!(T::LEN == 0) {
()
} else {
(
typle! {
i in ..T::LAST => self.handlers[[i]].handle_stuff(input.clone())
},
// Avoid expensive clone for the last handler.
self.handlers[[T::LAST]].handle_stuff(input),
)
}
(
typle! {
i in ..T::LAST => self.handlers[[i]].handle_stuff(input.clone())
},
// Avoid expensive clone for the last handler.
self.handlers[[T::LAST]].handle_stuff(input),
)
}
}
```

This generates the implementations
```rust
impl HandleStuff for MultipleHandlers<()> {
type Output = ();
fn handle_stuff(&self, input: Input) -> Self::Output {
{ () }
}
}
impl<T0> HandleStuff for MultipleHandlers<(T0,)>
where
T0: HandleStuff,
{
type Output = (T0::Output,);
fn handle_stuff(&self, input: Input) -> Self::Output {
{ (self.handlers.0.handle_stuff(input),) }
(self.handlers.0.handle_stuff(input),)
}
}
impl<T0, T1> HandleStuff for MultipleHandlers<(T0, T1)>
Expand All @@ -108,12 +95,10 @@ where
{
type Output = (T0::Output, T1::Output);
fn handle_stuff(&self, input: Input) -> Self::Output {
{
(
self.handlers.0.handle_stuff(input.clone()),
self.handlers.1.handle_stuff(input),
)
}
(
self.handlers.0.handle_stuff(input.clone()),
self.handlers.1.handle_stuff(input),
)
}
}
impl<T0, T1, T2> HandleStuff for MultipleHandlers<(T0, T1, T2)>
Expand All @@ -124,13 +109,11 @@ where
{
type Output = (T0::Output, T1::Output, T2::Output);
fn handle_stuff(&self, input: Input) -> Self::Output {
{
(
self.handlers.0.handle_stuff(input.clone()),
self.handlers.1.handle_stuff(input.clone()),
self.handlers.2.handle_stuff(input),
)
}
(
self.handlers.0.handle_stuff(input.clone()),
self.handlers.1.handle_stuff(input.clone()),
self.handlers.2.handle_stuff(input),
)
}
}
```
Expand Down
7 changes: 6 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,12 @@ impl TypleContext {
}
result = Some(Typle::Generic(Rc::new(
(0..self.typle_len.unwrap_or(self.typle_macro.max_len))
.map(|i| format!("{}{}", &type_ident, i))
.map(|i| match self.typle_macro.suffixes.get(i) {
Some(suffix) => {
format!("{}{}", &type_ident, suffix)
}
None => format!("{}{}", &type_ident, i),
})
.collect(),
)));
continue;
Expand Down
92 changes: 58 additions & 34 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
//! # Conditionals
//!
//! The `typle!` macro accepts an `if` statement with an optional `else` clause.
//! If there is no `else` clause the macro filters out components that do not match
//! If there is no `else` clause the macro filters out elements that do not match
//! the condition.
//!
//! The `typle_attr_if` attribute allows conditional inclusion of attributes. It works similarly to
Expand Down Expand Up @@ -714,7 +714,7 @@ use context::TypleContext;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::ToTokens;
use syn::spanned::Spanned as _;
use syn::{Error, Item, Type, TypeNever};
use syn::{Error, Expr, Item, Type, TypeNever};

#[doc(hidden)]
#[proc_macro_attribute]
Expand Down Expand Up @@ -756,6 +756,7 @@ struct TypleMacro {
min_len: usize,
max_len: usize,
never_type: Type,
suffixes: Vec<String>,
}

impl TryFrom<TokenStream> for TypleMacro {
Expand All @@ -767,6 +768,7 @@ impl TryFrom<TokenStream> for TypleMacro {
let mut never_type = Type::Never(TypeNever {
bang_token: syn::token::Not::default(),
});
let mut suffixes = Vec::new();
let mut args_iter = args.into_iter();
// Tuple
let Some(TokenTree::Ident(trait_ident)) = args_iter.next() else {
Expand All @@ -780,24 +782,27 @@ impl TryFrom<TokenStream> for TypleMacro {
}
}

let mut range_tokens = TokenStream::new();
let mut never_tokens = Vec::new();
let mut comma_seen = false;
for token in args_iter {
if comma_seen {
never_tokens.push(token);
} else {
if let TokenTree::Punct(punct) = &token {
if punct.as_char() == ',' {
comma_seen = true;
continue;
}
let mut sections = Vec::new();
let mut section = Vec::new();
for tt in args_iter {
match tt {
TokenTree::Punct(punct) if punct.as_char() == ',' => {
sections.push(std::mem::take(&mut section));
}
tt => {
section.push(tt);
}
range_tokens.extend([token]);
}
}
if !section.is_empty() {
sections.push(section);
}
let mut sections = sections.into_iter();
let Some(range_tokens) = sections.next() else {
return Err(Error::new(default_span, "expected range"));
};
// 2..=12
let range = syn::parse2::<syn::ExprRange>(range_tokens)?;
let range = syn::parse2::<syn::ExprRange>(range_tokens.into_iter().collect())?;
let min = range
.start
.as_ref()
Expand All @@ -822,32 +827,51 @@ impl TryFrom<TokenStream> for TypleMacro {
if max < min {
return Err(Error::new(range.span(), "range contains no values"));
}
if !never_tokens.is_empty() {
// never=some::Type
let mut iter = never_tokens.into_iter();
let Some(TokenTree::Ident(ident)) = iter.next() else {
for section in sections {
let mut tokens = section.into_iter();
let Some(TokenTree::Ident(ident)) = tokens.next() else {
return Err(Error::new(default_span, "expected identifier after comma"));
};
if ident != "never" {
return Err(Error::new(
default_span,
"expected identifier 'never' after comma",
));
}
let Some(TokenTree::Punct(punct)) = iter.next() else {
return Err(Error::new(default_span, "expected equals after never"));
};
if punct.as_char() != '=' {
return Err(Error::new(default_span, "expected equals after never"));
if ident == "never" {
let Some(TokenTree::Punct(punct)) = tokens.next() else {
return Err(Error::new(default_span, "expected equals after never"));
};
if punct.as_char() != '=' {
return Err(Error::new(default_span, "expected equals after never"));
}
never_type = syn::parse2::<Type>(tokens.collect())?;
} else if ident == "suffixes" {
let Some(TokenTree::Punct(punct)) = tokens.next() else {
return Err(Error::new(default_span, "expected equals after never"));
};
if punct.as_char() != '=' {
return Err(Error::new(default_span, "expected equals after never"));
}

let expr = syn::parse2::<Expr>(tokens.collect())?;
let Expr::Array(array) = expr else {
return Err(Error::new(expr.span(), "expected array"));
};
for elem in array.elems {
let Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(suffix),
..
}) = elem
else {
return Err(Error::new(elem.span(), "expected string"));
};
suffixes.push(suffix.value());
}
} else {
return Err(Error::new(ident.span(), "unexpected identifier"));
}
let type_stream = iter.collect();
never_type = syn::parse2::<Type>(type_stream)?;
}
Ok(TypleMacro {
trait_ident,
min_len: min,
max_len: max,
never_type,
suffixes,
})
}
}
Expand Down Expand Up @@ -1005,7 +1029,7 @@ pub fn typle_fold(item: proc_macro::TokenStream) -> proc_macro::TokenStream {

/// Create variants in an enum.
///
/// In an enum, the `typle_variant` macro allows the creation of variants for each component.
/// In an enum, the `typle_variant` macro allows the creation of variants for each element.
///
/// A variant is created for each index in the range provided.
///
Expand Down
9 changes: 4 additions & 5 deletions tests/compile/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,13 @@ fn heapify<T: Tuple>(params: T) -> (typle!(i in .. => Box<T<{i}>>)) {
(typle!(i in .. => Box::new(params[[i]])))
}

#[rustfmt::skip]
#[typle(Tuple for 0..=12)]
#[allow(non_camel_case_types)]
#[typle(Tuple for 0..=5, suffixes=["", "01", "x", "three"])]
#[typle_attr_if(Tuple::LEN == 0, allow(unused_assignments))]
pub fn zip<A: Tuple, B: Tuple>(
first: A,
second: B
) -> (typle!(i in ..Tuple::LEN => (A<{i}>, B<{i}>)))
{
second: B,
) -> (typle!(i in ..Tuple::LEN => (A<{i}>, B<{i}>))) {
(typle!(i in ..Tuple::LEN => (first[[i]], second[[i]])))
}

Expand Down
Loading