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
8 changes: 6 additions & 2 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,9 +904,13 @@ impl TypleContext {
if let Some(ident) = r#macro.mac.path.get_ident() {
if ident == "typle_variant" {
let token_stream = std::mem::take(&mut r#macro.mac.tokens);
let default_span = token_stream.span();
let mut tokens = token_stream.into_iter();
let (pattern, range) =
context.parse_pattern_range(&mut tokens)?;
let Some((pattern, range)) =
context.parse_pattern_range(&mut tokens)?
else {
abort!(default_span, "typle_variant! must have range")
};
if range.is_empty() {
continue;
}
Expand Down
9 changes: 7 additions & 2 deletions src/context/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,9 @@ impl TypleContext {
let mut init_expr =
syn::parse2::<Expr>(Self::extract_to_semicolon(&mut tokens, default_span)?)?;
self.replace_expr(&mut init_expr, &mut inner_state)?;
let (pattern, range) = self.parse_pattern_range(&mut tokens)?;
let Some((pattern, range)) = self.parse_pattern_range(&mut tokens)? else {
abort!(default_span, "typle_fold! must have range")
};
if range.is_empty() {
return Ok(Expr::Paren(syn::ExprParen {
attrs,
Expand Down Expand Up @@ -841,8 +843,11 @@ impl TypleContext {
op: BinOp,
default: bool,
) -> syn::Result<Expr> {
let default_span = token_stream.span();
let mut tokens = token_stream.into_iter();
let (pattern, mut range) = self.parse_pattern_range(&mut tokens)?;
let Some((pattern, mut range)) = self.parse_pattern_range(&mut tokens)? else {
abort!(default_span, "macro must have range")
};
let expr = syn::parse2::<Expr>(tokens.collect())?;
let all = match range.next() {
Some(index) => {
Expand Down
30 changes: 26 additions & 4 deletions src/context/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl TypleContext {
pub(super) fn parse_pattern_range(
&self,
tokens: &mut impl Iterator<Item = TokenTree>,
) -> syn::Result<(Option<Ident>, Range<usize>)> {
) -> syn::Result<Option<(Option<Ident>, Range<usize>)>> {
let mut collect = TokenStream::new();
let mut pattern = None;
let mut equals = None;
Expand Down Expand Up @@ -112,7 +112,7 @@ impl TypleContext {
}
if collect.is_empty() {
// single iteration replacement: typle!(=> if T::LEN > 1 {C: Clone})
return Ok((None, 0..1));
return Ok(None);
}
let mut expr = syn::parse2::<Expr>(collect)?;
let mut state = BlockState::default();
Expand Down Expand Up @@ -161,7 +161,7 @@ impl TypleContext {
}
},
};
Ok((pattern, start..end))
Ok(Some((pattern, start..end)))
}

pub(super) fn replace_qself_path(
Expand Down Expand Up @@ -474,7 +474,29 @@ impl TypleContext {
{
let mut tokens = token_stream.into_iter();
let (pattern, range) = match self.parse_pattern_range(&mut tokens) {
Ok(t) => t,
Ok(Some(t)) => t,
Ok(None) => match self.evaluate_if(tokens) {
Ok(Some(token_stream)) => {
let span = token_stream.span();
match f(&self, token_stream) {
Ok(mut iter) => match iter.next() {
Some(value) => {
if iter.next().is_some() {
return Replacements::Singleton(Err(syn::Error::new(
span,
"Un-ranged typle! expansion can only produce one value",
)));
}
return Replacements::Singleton(value);
}
None => return Replacements::Empty,
},
Err(err) => return Replacements::Singleton(Err(err)),
}
}
Ok(None) => return Replacements::Empty,
Err(err) => return Replacements::Singleton(Err(err)),
},
Err(e) => {
return Replacements::Singleton(Err(e));
}
Expand Down
4 changes: 3 additions & 1 deletion src/context/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ impl TypleContext {
let mut init_type =
syn::parse2::<Type>(Self::extract_to_semicolon(&mut tokens, default_span)?)?;
self.replace_type(&mut init_type)?;
let (pattern, mut range) = self.parse_pattern_range(&mut tokens)?;
let Some((pattern, mut range)) = self.parse_pattern_range(&mut tokens)? else {
abort!(default_span, "typle_fold! must have range")
};
if range.is_empty() {
return Ok(init_type);
}
Expand Down
105 changes: 75 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@
//! (t[[0]], (t[[1..]])) // (t.0, (t.1, t.2,...))
//! }
//!
//! assert_eq!(split_first(('1', 2, 3.0)), ('1', (2, 3.0)));
//! assert_eq!(split_first((2, 3.0)), (2, (3.0,)));
//! assert_eq!(split_first((3.0,)), (3.0, ()));
//! let t = ('1', 2, 3.0);
//! let (first, rest) = split_first(t); // first = '1', rest = (2, 3.0)
//! assert_eq!(first, '1');
//! let (first, rest) = split_first(rest); // first = 2, rest = (3.0,)
//! assert_eq!(first, 2);
//! let (first, rest) = split_first(rest); // first = 3.0, rest = ()
//! assert_eq!(first, 3.0);
//! assert_eq!(rest, ());
//! ```
//!
//! The associated constant `LEN` provides the length of the tuple in each
//! generated item. This value can be used in typle index expressions.
//! The default bounds for a macro range are `0..Tuple::LEN`, that is, for all
//! The default bounds for a macro range are `0..Tuple::LEN`, encapsulating all
//! components of the tuple.
//!
//! ```rust
Expand All @@ -80,9 +83,25 @@
//!
//! # `typle!`
//!
//! The `typle!` macro creates a new sequence of types or expressions. A
//! `typle!` macro can only appear where a comma-separated sequence is valid
//! (e.g. a tuple, array, argument list, or where clause).
//! The `typle!` macro generally iterates over a range to create a new comma-separated sequence of
//! elements. A `typle!` macro with a range can only appear where a comma-separated sequence is
//! valid (e.g. a tuple, array, argument list, or where clause).
//!
//! The `typle!` macro can provide an optional typle index variable for each iteration to use in the
//! macro expansion.
//!
//! ```rust
//! # use typle::typle;
//! #[typle(Tuple for 0..=12)]
//! fn indices<T: Tuple>(t: T) -> (typle! {.. => usize}) { // (usize, usize,...)
//! (typle! {i in .. => i}) // (0, 1,...)
//! }
//!
//! assert_eq!(indices(('a', Some(true), "test", 9)), (0, 1, 2, 3));
//! ```
//!
//! The associated constant `LEN` provides the length of the tuple in each
//! generated item and can be used in typle index expressions.
//!
//! ```rust
//! # use typle::typle;
Expand All @@ -94,7 +113,7 @@
//! assert_eq!(reverse((Some(3), "four", 5)), (5, "four", Some(3)));
//! ```
//!
//! Each iteration can add multiple components to the new structure.
//! Each iteration can add multiple elements to the new sequence.
//! ```rust
//! # use typle::typle;
//! #[typle(Tuple for 0..=12)]
Expand All @@ -110,10 +129,32 @@
//! assert_eq!(duplicate_components(("one", 2, 3.0)), ("one", "one", 2, 2, 3.0, 3.0));
//! ```
//!
//! # Constraints
//! The `typle!` macro can be used without a range. In this case it must return
//! a single value but can be used outside a comma-separated sequence. Remember that `typle!`
//! with a range effectively adds a trailing comma to each element that may affect the created type.
//!
//! Specify constraints on the tuple components using one of the following
//! forms. Except for the first form, these constraints can only appear in the
//! ```rust
//! # use typle::typle;
//! # #[typle(Tuple for 0..1)]
//! # fn singleton<T: Tuple>(t: T) {
//! // outside a sequence create a single `bool`
//! let result: bool = typle! {=> true};
//! // typle! with a range outside a sequence is an error
//! // let result = typle! {0..1 => true}; // ERROR
//! // inside parentheses without a range create a parenthesized `bool`
//! let result: (bool) = (typle! {=> true});
//! // inside parentheses with a 1-element range create a `bool` 1-tuple
//! let result: (bool,) = (typle! {0..1 => true});
//! // inside brackets both forms create a `bool` array
//! let result: [bool; 1] = [typle! {=> true}];
//! let result: [bool; 1] = [typle! {0..1 => true}];
//! # }
//! ```
//!
//! # Bounds
//!
//! Specify bounds on the tuple components using one of the following
//! forms. Except for the first form, these bounds can only appear in the
//! `where` clause.
//! - `T: Tuple<C>` - all components of the tuple have type `C`.
//! - `T<_>: Clone` - all components of the tuple implement the `Clone` trait.
Expand Down Expand Up @@ -170,39 +211,43 @@
//! (typle!(i in .. => if i % 2 == 0 { t[[i]].to_string() } else { t[[i]] }))
//! }
//!
//! // `Vec` does not implement `ToString` but, with an odd position, it doesn't need to
//! assert_eq!(even_to_string((0, vec![1], 2, 3)), ("0".to_owned(), vec![1], "2".to_owned(), 3));
//! // `Vec` does not implement `ToString` but, in an odd position, it doesn't need to
//! assert_eq!(even_to_string((0, vec![1], 2, 3)), (0.to_string(), vec![1], 2.to_string(), 3));
//! ```
//!
//! The `typle!` macro can appear outside a comma-separated sequence if it has no range. This will
//! replace the single value at the position. This can be used to add an `if` statement where
//! An un-ranged `typle!` macro can be used to add an `if` statement where
//! expressions are otherwise invalid:
//!
//! ```rust
//! # use typle::typle;
//! # struct MyStruct<T> {
//! # t: T,
//! # }
//! trait Trait {
//! type Input;
//! type Output;
//! fn method(&self, input: Self::Input) -> Self::Output;
//! trait ReplaceLast {
//! type Last;
//!
//! // Replace the "last" value with a new value, returning the old value
//! fn replace_last(&mut self, new: Self::Last) -> Self::Last;
//! }
//!
//! #[typle(Tuple for 0..=3)]
//! impl<T: Tuple> Trait for MyStruct<T> {
//! type Input = typle!(=> if T::LEN == 0 { () } else { T<{0}> });
//! type Output = typle!(=> if T::LEN == 0 { () } else { T<{0}> });
//!
//! fn method(
//! &self,
//! input: typle!(=> if T::LEN == 0 { () } else { T<{0}>}),
//! ) -> typle!(=> if T::LEN == 0 { () } else { T<{0}> }) {
//! typle!(=> if T::LEN == 0 { () } else { input })
//! impl<T: Tuple> ReplaceLast for MyStruct<T> {
//! type Last = typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}> });
//!
//! fn replace_last(
//! &mut self,
//! new: typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}>}),
//! ) -> typle!(=> if T::LEN == 0 { () } else { T<{T::LAST}> }) {
//! typle!(=> if T::LEN == 0 { () } else {
//! std::mem::replace(&mut self.t[[T::LAST]], new)
//! })
//! }
//! }
//! ```
//!
//! Note, real code like the above can avoid all these `if`s by using a specific `impl` for `()` and
//! a simplified typle `impl` for `1..=3`.
//!
//! The `typle_const!` macro supports const-if on a boolean typle index expression. const-if allows
//! conditional branches that do not compile, as long as the invalid branch is `false` at compile time.
//!
Expand Down
11 changes: 11 additions & 0 deletions tests/compile/doc_typle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,14 @@ mod tuple {
}
}
}

#[allow(unused)]
#[typle(Tuple for 0..1)]
fn singleton<T: Tuple>(t: T) {
// outside a sequence create a single `bool`
let result: bool = typle! {=> true};
// inside parentheses without a range create a parenthesized `bool`
let result: (bool) = (typle! {=> true});
// inside paretheses with a 1-element range create a `bool` 1-tuple
let result: (bool,) = (typle! {0..1 => true});
}
24 changes: 24 additions & 0 deletions tests/compile/mod.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,30 @@ pub mod doc_typle {
}
}
}
#[allow(non_camel_case_types)]
trait _typle_fn_singleton {
type Return;
fn apply(self) -> Self::Return;
}
impl _typle_fn_singleton for ((),) {
type Return = ();
#[allow(unused)]
fn apply(self) -> Self::Return {
#[allow(unused_variables)]
let (t,) = self;
{
let result: bool = true;
let result: (bool) = (true);
let result: (bool,) = (true,);
}
}
}
fn singleton<T>(t: T) -> <(T,) as _typle_fn_singleton>::Return
where
(T,): _typle_fn_singleton,
{
<(T,) as _typle_fn_singleton>::apply((t,))
}
}
pub mod for_loop {
#![allow(unreachable_code, unused)]
Expand Down