diff --git a/CHANGELOG.md b/CHANGELOG.md index d722b8d..d577f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.13 + +## Backwards-incompatible changes +- Remove `typle_for!`. Use `typle!`. + +## Other changes +- Allow `typle!` macro with no range in singleton position. + # 0.12 ## Backwards-incompatible changes diff --git a/src/context.rs b/src/context.rs index 847484e..6afda7e 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1106,52 +1106,6 @@ impl TypleContext { Ok(()) } - fn replace_typle_for_expr<'a>( - &'a self, - mac: &mut Macro, - state: &'a mut BlockState, - ) -> syn::Result> + 'a> { - let token_stream = std::mem::take(&mut mac.tokens); - let mut tokens = token_stream.into_iter(); - let (pattern, range) = self.parse_pattern_range(&mut tokens)?; - let token_stream = tokens.collect::(); - let mut context = self.clone(); - if let Some(ident) = &pattern { - context.constants.insert(ident.clone(), 0); - } - Ok(range - .zip_clone(token_stream) - .flat_map(move |(index, token_stream)| { - if let Some(ident) = &pattern { - *context.constants.get_mut(ident).unwrap() = index; - } - let token_stream = match context.evaluate_if(token_stream) { - Ok(Some(token_stream)) => token_stream, - Ok(None) => { - return None; - } - Err(e) => { - return Some(Err(e)); - } - }; - let mut expr = match syn::parse2::(token_stream) { - Ok(expr) => expr, - Err(e) => return Some(Err(e)), - }; - match context.replace_expr(&mut expr, state) { - Ok(()) => { - if let Expr::Verbatim(_token_stream) = expr { - // Verbatim causes typle to omit the component from the tuple - None - } else { - Some(Ok(expr)) - } - } - Err(error) => Some(Err(error)), - } - })) - } - fn replace_typle_fold_expr( &self, mac: &mut Macro, @@ -1262,25 +1216,19 @@ impl TypleContext { attrs: &mut Vec, state: &mut BlockState, ) -> syn::Result> { - // typle_for!(i in .. => Some (Some, Some) - // typle_for!(i in .. => t.to_string()) -> (t.0.to_string(), t.1.to_string()) - // as opposed to - // Some(t) -> Some((t.0, t.1)) - // t.to_string() -> (t.0, t.1).to_string() self.replace_attrs(attrs)?; if let Some(macro_name) = mac.path.get_ident() { let default_span = macro_name.span(); - if macro_name == "typle_for" { - let expr = { - let elems = self.replace_typle_for_expr(mac, state)?; - let tuple = syn::ExprTuple { - attrs: std::mem::take(attrs), - paren_token: token::Paren::default(), - elems: elems.collect::>()?, - }; - Expr::Tuple(tuple) - }; - return Ok(Some(expr)); + if macro_name == "typle" { + // This is outside a comma-separated sequence so only no-range form is accepted + // typle!(=> if T::LEN == 0 {} else {}) + let token_stream = std::mem::take(&mut mac.tokens); + return self.expand_typle_macro_singleton(token_stream, |context, token_stream| { + let mut expr = syn::parse2::(token_stream)?; + let mut state = BlockState::default(); + context.replace_expr(&mut expr, &mut state)?; + Ok(Some(expr)) + }); } else if macro_name == "typle_fold" { let expr = self.replace_typle_fold_expr(mac, std::mem::take(attrs), state, default_span)?; diff --git a/src/context/pat.rs b/src/context/pat.rs index 689fd25..fcdf79a 100644 --- a/src/context/pat.rs +++ b/src/context/pat.rs @@ -142,144 +142,19 @@ impl TypleContext { fn replace_macro_pat(&self, m: &mut syn::PatMacro) -> syn::Result> { self.replace_attrs(&mut m.attrs)?; if let Some(macro_name) = m.mac.path.get_ident() { - if macro_name == "typle_for" { - let mut tuple = syn::PatTuple { - attrs: std::mem::take(&mut m.attrs), - paren_token: token::Paren::default(), - elems: Punctuated::new(), - }; + if macro_name == "typle" { + // This is outside a comma-separated sequence so only no-range form is accepted + // typle!(=> if T::LEN == 0 {} else {}) let token_stream = std::mem::take(&mut m.mac.tokens); - let mut tokens = token_stream.into_iter(); - let (pattern, range) = self.parse_pattern_range(&mut tokens)?; - if range.is_empty() { - return Ok(Some(Pat::Tuple(tuple))); - } - let token_stream = tokens.collect::(); - let body_span = token_stream.span(); - match m.mac.delimiter { - MacroDelimiter::Paren(_) => { - let pat = Pat::parse_single.parse2(token_stream)?; - let mut context = self.clone(); - if let Some(ident) = &pattern { - context.constants.insert(ident.clone(), 0); - } - for (index, mut component) in range.zip_clone(pat) { - if let Some(ident) = &pattern { - *context.constants.get_mut(ident).unwrap() = index; - } - let mut state = BlockState::default(); - context.replace_pat(&mut component, &mut state)?; - tuple.elems.push(component); - } - } - MacroDelimiter::Brace(_) => { - let Ok(expr) = syn::parse2::(token_stream) else { - return Err(syn::Error::new( - body_span, - "cannot parse, wrap types in `typle_pat!()`", - )); - }; - let mut context = self.clone(); - context.const_if = true; - if let Some(ident) = &pattern { - context.constants.insert(ident.clone(), 0); - } - for (index, mut expr) in range.zip_clone(expr) { - if let Some(ident) = &pattern { - *context.constants.get_mut(ident).unwrap() = index; - } - let mut state = BlockState::default(); - context.replace_expr(&mut expr, &mut state)?; - if let Expr::Verbatim(_) = expr { - // Verbatim omits the pattern from the tuple. - } else { - let mut pat = expr_to_pat(expr)?; - context.replace_pat(&mut pat, &mut state)?; - tuple.elems.push(pat); - } - } - } - MacroDelimiter::Bracket(_) => { - abort!(m, "expected parentheses or braces"); - } - } - return Ok(Some(Pat::Tuple(tuple))); + return self.expand_typle_macro_singleton(token_stream, |context, token_stream| { + let mut pat = Pat::parse_single.parse2(token_stream)?; + let mut state = BlockState::default(); + context.replace_pat(&mut pat, &mut state)?; + Ok(Some(pat)) + }); } } m.mac.tokens = self.replace_macro_token_stream(std::mem::take(&mut m.mac.tokens))?; Ok(None) } } - -fn expr_to_pat(expr: Expr) -> syn::Result { - let default_span = expr.span(); - match expr { - Expr::Block(expr) => { - let mut iter = expr.block.stmts.into_iter(); - let Some(stmt) = iter.next() else { - // empty block represents missing pattern - return Ok(Pat::Verbatim(TokenStream::new())); - }; - if iter.next().is_some() { - return Err(syn::Error::new( - default_span, - "typle requires a block with a single pattern", - )); - } - match stmt { - Stmt::Local(local) => Err(syn::Error::new( - local.span(), - "let statement not supported here", - )), - Stmt::Item(item) => Err(syn::Error::new(item.span(), "item not supported here")), - Stmt::Expr(expr, None) => expr_to_pat(expr), - Stmt::Expr(_, Some(semi)) => { - Err(syn::Error::new(semi.span(), "unexpected semicolon")) - } - Stmt::Macro(m) => match m.mac.path.get_ident() { - Some(ident) if ident == "typle_pat" => Pat::parse_single.parse2(m.mac.tokens), - _ => Ok(Pat::Macro(syn::PatMacro { - attrs: m.attrs, - mac: m.mac, - })), - }, - } - } - Expr::Const(expr) => Ok(Pat::Const(expr)), - Expr::Infer(expr) => Ok(Pat::Wild(syn::PatWild { - attrs: expr.attrs, - underscore_token: expr.underscore_token, - })), - Expr::Lit(expr) => Ok(Pat::Lit(expr)), - Expr::Macro(expr) => match expr.mac.path.get_ident() { - Some(ident) if ident == "typle_pat" => Pat::parse_single.parse2(expr.mac.tokens), - _ => Ok(Pat::Macro(expr)), - }, - Expr::Paren(expr) => Ok(Pat::Paren(syn::PatParen { - attrs: expr.attrs, - paren_token: expr.paren_token, - pat: Box::new(expr_to_pat(*expr.expr)?), - })), - Expr::Path(expr) => Ok(Pat::Path(expr)), - Expr::Reference(expr) => Ok(Pat::Reference(syn::PatReference { - attrs: expr.attrs, - and_token: expr.and_token, - mutability: expr.mutability, - pat: Box::new(expr_to_pat(*expr.expr)?), - })), - Expr::Tuple(expr) => Ok(Pat::Tuple(syn::PatTuple { - attrs: expr.attrs, - paren_token: expr.paren_token, - elems: expr - .elems - .into_iter() - .map(expr_to_pat) - .collect::>()?, - })), - Expr::Verbatim(token_stream) => Ok(Pat::Verbatim(token_stream)), - _ => Err(syn::Error::new( - default_span, - "typle does not support this pattern", - )), - } -} diff --git a/src/context/shared.rs b/src/context/shared.rs index e359c71..68d36be 100644 --- a/src/context/shared.rs +++ b/src/context/shared.rs @@ -284,8 +284,10 @@ impl TypleContext { Ok(()) } - pub(super) fn evaluate_if(&self, ts: TokenStream) -> syn::Result> { - let mut tokens = ts.into_iter(); + pub(super) fn evaluate_if( + &self, + mut tokens: impl Iterator, + ) -> syn::Result> { match tokens.next() { Some(TokenTree::Ident(ident)) if ident == "if" => { let mut tokens = tokens.collect::>(); @@ -302,9 +304,9 @@ impl TypleContext { self.replace_expr(&mut cond, &mut state)?; let b = evaluate_bool(&cond)?; self.evaluate_if(if b { - group0.stream() + group0.stream().into_iter() } else { - group1.stream() + group1.stream().into_iter() }) } Some(tt) => { @@ -321,7 +323,7 @@ impl TypleContext { self.replace_expr(&mut cond, &mut state)?; let b = evaluate_bool(&cond)?; if b { - self.evaluate_if(group1.stream()) + self.evaluate_if(group1.stream().into_iter()) } else { Ok(None) } @@ -385,7 +387,13 @@ impl TypleContext { { // Tuple::LEN or T::LEN let Some(typle_len) = self.typle_len else { - abort!(ident2, "LEN not defined outside fn or impl"); + abort!( + ident2, + format!( + "LEN only defined for fn or impl using {} bound", + self.typle_macro.trait_ident + ) + ); }; return Ok(Some(Lit::Int(syn::LitInt::new( &typle_len.to_string(), @@ -402,7 +410,13 @@ impl TypleContext { { // Tuple::LAST or T::LAST let Some(typle_len) = self.typle_len else { - abort!(ident2, "LAST not defined outside fn or impl"); + abort!( + ident2, + format!( + "LAST only defined for fn or impl using {} bound", + self.typle_macro.trait_ident + ) + ); }; if typle_len == 0 { abort!(ident2, "LAST not defined when LEN == 0"); @@ -478,7 +492,7 @@ impl TypleContext { if let Some(ident) = &pattern { *context.constants.get_mut(ident).unwrap() = index; } - match context.evaluate_if(token_stream) { + match context.evaluate_if(token_stream.into_iter()) { Ok(Some(token_stream)) => match f(&context, token_stream) { Ok(iter) => iter, Err(err) => Replacements::Singleton(Err(err)), @@ -489,4 +503,34 @@ impl TypleContext { }, )) } + + pub(super) fn expand_typle_macro_singleton( + &self, + token_stream: TokenStream, + f: F, + ) -> syn::Result> + where + T: 'static, + F: Fn(&TypleContext, TokenStream) -> syn::Result>, + { + let span = token_stream.span(); + let mut tokens = token_stream.into_iter(); + match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => { + match self.evaluate_if(tokens)? { + Some(token_stream) => { + return f(&self, token_stream); + } + None => { + return Ok(None); + } + } + } + _ => {} + }, + _ => {} + } + abort!(span, "Expected => at start of macro"); + } } diff --git a/src/context/type.rs b/src/context/type.rs index fee9b4c..215d8ef 100644 --- a/src/context/type.rs +++ b/src/context/type.rs @@ -182,46 +182,21 @@ impl TypleContext { } fn replace_macro_type(&self, m: &mut syn::TypeMacro) -> syn::Result> { - // typle_for!(i in .. => Option) -> (Option, Option) - // typle_for!(i in .. => T::<{i}>::default()) -> (T0::default(), T1::default()) + // (typle! {i in .. => Option}) -> (Option, Option) + // (typle! {i in .. => T::<{i}>::default()}) -> (T0::default(), T1::default()) // as opposed to // Option -> Option<(T0, T1)> // T::default() -> <(T0, T1)>::default() if let Some(macro_name) = m.mac.path.get_ident() { - if macro_name == "typle_for" { - let mut tuple = syn::TypeTuple { - paren_token: token::Paren::default(), - elems: Punctuated::new(), - }; + if macro_name == "typle" { let token_stream = std::mem::take(&mut m.mac.tokens); - let mut tokens = token_stream.into_iter(); - let (pattern, range) = self.parse_pattern_range(&mut tokens)?; - if range.is_empty() { - return Ok(Some(Type::Tuple(tuple))); - } - let token_stream = tokens.collect::(); - let mut context = self.clone(); - if let Some(ident) = &pattern { - context.constants.insert(ident.clone(), 0); - } - for (index, token_stream) in range.zip_clone(token_stream) { - if let Some(ident) = &pattern { - *context.constants.get_mut(ident).unwrap() = index; - } - let token_stream = match context.evaluate_if(token_stream) { - Ok(Some(token_stream)) => token_stream, - Ok(None) => { - continue; - } - Err(e) => { - return Err(e); - } - }; + // This is outside a comma-separated sequence so only no-range form is accepted + // typle!(=> if T::LEN == 0 {} else {}) + return self.expand_typle_macro_singleton(token_stream, |context, token_stream| { let mut ty = syn::parse2::(token_stream)?; context.replace_type(&mut ty)?; - tuple.elems.push(ty); - } - return Ok(Some(Type::Tuple(tuple))); + Ok(Some(ty)) + }); } else if macro_name == "typle_fold" { let default_span = macro_name.span(); let ty = self.replace_typle_fold_type(&mut m.mac, default_span)?; diff --git a/src/lib.rs b/src/lib.rs index 678ebcd..e9267e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,35 @@ //! assert_eq!(even_to_string((0, vec![1], 2, 3)), ("0".to_owned(), vec![1], "2".to_owned(), 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 +//! expressions are otherwise invalid: +//! +//! ```rust +//! # use typle::typle; +//! # struct MyStruct { +//! # t: T, +//! # } +//! trait Trait { +//! type Input; +//! type Output; +//! fn method(&self, input: Self::Input) -> Self::Output; +//! } +//! +//! #[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 }) +//! } +//! } +//! ``` +//! //! 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/conditional.rs b/tests/compile/conditional.rs new file mode 100644 index 0000000..1f66692 --- /dev/null +++ b/tests/compile/conditional.rs @@ -0,0 +1,28 @@ +#![allow(unused)] + +use typle::typle; + +trait Trait { + type Input; + type Output; + + fn method(&self, input: Self::Input) -> Self::Output; +} + +struct MyStruct { + t: T, +} + +#[typle(Tuple for 0..=3)] +#[typle_attr_if(T::LEN == 1, rustfmt::skip)] +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 }) + } +} diff --git a/tests/compile/mod.expanded.rs b/tests/compile/mod.expanded.rs index 048a9f7..adff5ba 100644 --- a/tests/compile/mod.expanded.rs +++ b/tests/compile/mod.expanded.rs @@ -1,3 +1,44 @@ +pub mod conditional { + #![allow(unused)] + use typle::typle; + trait Trait { + type Input; + type Output; + fn method(&self, input: Self::Input) -> Self::Output; + } + struct MyStruct { + t: T, + } + impl Trait for MyStruct<()> { + type Input = (); + type Output = (); + fn method(&self, input: ()) -> () { + () + } + } + #[rustfmt::skip] + impl Trait for MyStruct<(T0,)> { + type Input = T0; + type Output = T0; + fn method(&self, input: T0) -> T0 { + input + } + } + impl Trait for MyStruct<(T0, T1)> { + type Input = T0; + type Output = T0; + fn method(&self, input: T0) -> T0 { + input + } + } + impl Trait for MyStruct<(T0, T1, T2)> { + type Input = T0; + type Output = T0; + fn method(&self, input: T0) -> T0 { + input + } + } +} pub mod const_generic { #![allow(dead_code)] use typle::typle; diff --git a/tests/compile/mod.rs b/tests/compile/mod.rs index 2b85fea..5167af6 100644 --- a/tests/compile/mod.rs +++ b/tests/compile/mod.rs @@ -1,3 +1,4 @@ +pub mod conditional; pub mod const_generic; pub mod doc_typle; pub mod for_loop; diff --git a/tests/expand/typle-for.rs b/tests/expand/typle-for.rs index 7310013..95cd7d6 100644 --- a/tests/expand/typle-for.rs +++ b/tests/expand/typle-for.rs @@ -1,8 +1,7 @@ use typle::typle; -struct S -{ - t: T +struct S { + t: T, } #[typle(Tuple for 0..=2)] @@ -10,16 +9,16 @@ impl S where T: Tuple, { - fn new(t: typle_for!(i in .. => &T<{i}>)) { + fn new(t: (typle! {i in .. => &T<{i}>})) { // Square brackets create an array let a: [u32; T::LEN] = [typle!(i in .. => *t[[i]] * 2)]; // Parentheses create a tuple // The default bounds of the range are 0..Tuple::LEN - let b = typle_for!(i in .. => *t[[i]] * 2); + let b = (typle!(i in .. => *t[[i]] * 2)); // Arbitrary expressions can be used for the indices and // the iterator variable can be left out if not needed let init: [Option; T::LEN] = [typle!(T::LEN * 2..T::LEN * 3 => None)]; // const-if can be used to filter components in `typle_for!`. - let c = typle_for!(i in .. => if i == 0 {b[[i]]}); + let c = (typle!(i in .. => if i == 0 {b[[i]]})); } }