diff --git a/src/context.rs b/src/context.rs index 9937754..68a3ea1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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; } diff --git a/src/context/expr.rs b/src/context/expr.rs index 095ca3b..5402827 100644 --- a/src/context/expr.rs +++ b/src/context/expr.rs @@ -696,7 +696,9 @@ impl TypleContext { let mut init_expr = syn::parse2::(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, @@ -841,8 +843,11 @@ impl TypleContext { op: BinOp, default: bool, ) -> syn::Result { + 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::(tokens.collect())?; let all = match range.next() { Some(index) => { diff --git a/src/context/shared.rs b/src/context/shared.rs index 68d36be..a43e695 100644 --- a/src/context/shared.rs +++ b/src/context/shared.rs @@ -66,7 +66,7 @@ impl TypleContext { pub(super) fn parse_pattern_range( &self, tokens: &mut impl Iterator, - ) -> syn::Result<(Option, Range)> { + ) -> syn::Result, Range)>> { let mut collect = TokenStream::new(); let mut pattern = None; let mut equals = None; @@ -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::(collect)?; let mut state = BlockState::default(); @@ -161,7 +161,7 @@ impl TypleContext { } }, }; - Ok((pattern, start..end)) + Ok(Some((pattern, start..end))) } pub(super) fn replace_qself_path( @@ -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)); } diff --git a/src/context/type.rs b/src/context/type.rs index 215d8ef..55ecad4 100644 --- a/src/context/type.rs +++ b/src/context/type.rs @@ -213,7 +213,9 @@ impl TypleContext { let mut init_type = syn::parse2::(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); } diff --git a/src/lib.rs b/src/lib.rs index e9267e4..0921cd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -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: 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; @@ -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)] @@ -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: 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` - all components of the tuple have type `C`. //! - `T<_>: Clone` - all components of the tuple implement the `Clone` trait. @@ -170,12 +211,11 @@ //! (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 @@ -183,26 +223,31 @@ //! # struct MyStruct { //! # 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 Trait for MyStruct { -//! 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 ReplaceLast for MyStruct { +//! 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. //! diff --git a/tests/compile/doc_typle.rs b/tests/compile/doc_typle.rs index e4640d5..adc2832 100644 --- a/tests/compile/doc_typle.rs +++ b/tests/compile/doc_typle.rs @@ -106,3 +106,14 @@ mod tuple { } } } + +#[allow(unused)] +#[typle(Tuple for 0..1)] +fn singleton(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}); +} diff --git a/tests/compile/mod.expanded.rs b/tests/compile/mod.expanded.rs index adff5ba..7164478 100644 --- a/tests/compile/mod.expanded.rs +++ b/tests/compile/mod.expanded.rs @@ -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,) 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)]