From 087ea24b274ef0c523062619d820acd8ce9e2076 Mon Sep 17 00:00:00 2001 From: Jonathan Giddy Date: Tue, 13 Aug 2024 19:50:53 +0100 Subject: [PATCH] Support use of `typle_fold!` in expressions When `typle_fold!` is used in an expression, it must be enclosed in parentheses. If there is no iteration, then the initial expression is returned, and we need to ensure that operator precedence holds: `typle_fold!(1 + 0; i in .. => |c| c + 1) * 3` must become `(1 + 0) * 3`. If there is iteration, then `typle_fold!` expands to a `loop` that must be in parentheses to use in an expression: `(loop {...; break c;}) * 3`. --- src/context.rs | 26 ++++++++++++++------- src/lib.rs | 24 +++++++++---------- tests/compile/mod.expanded.rs | 44 +++++++++++++++++++++++++---------- tests/compile/typle_fold.rs | 12 +++++++++- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/src/context.rs b/src/context.rs index d51c54d..acec0e2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1746,7 +1746,11 @@ impl<'a> TypleContext<'a> { self.replace_expr(&mut init_expr, &mut inner_state)?; let (pattern, range) = self.parse_pattern_range(&mut tokens, default_span)?; if range.is_empty() { - return Ok(init_expr); + return Ok(Expr::Paren(syn::ExprParen { + attrs, + paren_token: token::Paren::default(), + expr: Box::new(init_expr), + })); } let fold_ident = Self::parse_fold_ident(&mut tokens, default_span)?; let fold_pat = Pat::Ident(syn::PatIdent { @@ -1814,14 +1818,20 @@ impl<'a> TypleContext<'a> { }), Some(token::Semi::default()), )); - Ok(Expr::Loop(syn::ExprLoop { + // The fold may be used in an expression `typle_fold!() + ...`, in which + // case the loop needs to be in parentheses. + Ok(Expr::Paren(syn::ExprParen { attrs, - label: None, - loop_token: token::Loop::default(), - body: Block { - brace_token: token::Brace::default(), - stmts, - }, + paren_token: token::Paren::default(), + expr: Box::new(Expr::Loop(syn::ExprLoop { + attrs: Vec::new(), + label: None, + loop_token: token::Loop::default(), + body: Block { + brace_token: token::Brace::default(), + stmts, + }, + })), })) } diff --git a/src/lib.rs b/src/lib.rs index 6b6004d..55ccaed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,8 +109,8 @@ //! - `T<0>: Copy` - the first component of the tuple implements `Copy` //! - `T<{1..=2}>: Copy` - the second and third components implement `Copy` //! - `typle_bound!` - the most general way to bound components, allowing -//! arbitrary expressions using the typle index variable on both sides of the -//! colon, as shown below: +//! arbitrary expressions using the typle index variable on both sides of +//! the colon, as shown below: //! //! ```rust //! # use typle::typle; @@ -350,11 +350,11 @@ //! # Limitations //! //! - The typle trait bound (`Tuple` in the examples) can only be applied to an -//! unqualified type identifier, not to non-path types or associated types. +//! unqualified type identifier, not to non-path types or associated types. //! - `typle` does not work when the tuple types are only associated types -//! because [associated types cannot distinguish implementations](https://github.com/rust-lang/rust/issues/20400). -//! See [this file](https://github.com/jongiddy/typle/blob/main/tests/compile/unzip.rs) -//! for workarounds. +//! because [associated types cannot distinguish implementations](https://github.com/rust-lang/rust/issues/20400). +//! See [this file](https://github.com/jongiddy/typle/blob/main/tests/compile/unzip.rs) +//! for workarounds. //! ```rust ignore //! // ERROR: conflicting implementations of trait `TryUnzip` //! # use typle::typle; @@ -409,9 +409,9 @@ //! # } //! ``` //! - Due to interaction of `typle` with other macros, passing some types and -//! expressions to a macro may produce unexpected results. To help work around -//! this, inside a macro invocation the `typle_ty!` macro expands types and the -//! `typle_expr!` macro expands expressions. +//! expressions to a macro may produce unexpected results. To help work around +//! this, inside a macro invocation the `typle_ty!` macro expands types and the +//! `typle_expr!` macro expands expressions. //! //! ```rust //! # use typle::typle; @@ -717,11 +717,11 @@ pub fn typle_args(item: proc_macro::TokenStream) -> proc_macro::TokenStream { /// followed by the expression. This makes it look similar to a closure, which /// it usually acts like. But there are some differences: /// - the "closure parameter" naming the accumulator can only contain a single -/// identifier; +/// identifier; /// - a `break` in the expression terminates the fold early with the value of -/// the `break`; +/// the `break`; /// - a `return` in the expression returns from the enclosing function (since -/// the expression is not actually in a closure). +/// the expression is not actually in a closure). /// /// The previous example could have been implemented using a `for` loop. /// However, unlike a `for` loop, the `typle_fold!` macro allows the accumulator diff --git a/tests/compile/mod.expanded.rs b/tests/compile/mod.expanded.rs index cc04ef5..4300707 100644 --- a/tests/compile/mod.expanded.rs +++ b/tests/compile/mod.expanded.rs @@ -2271,37 +2271,49 @@ pub mod typle_fold { pub trait UsefulTrait { type UsefulType: IsUseful; const SIZE: usize; + #[allow(dead_code)] + fn display(&self) -> String; } impl UsefulTrait for (T0,) where - T0: UsefulTrait, + T0: UsefulTrait + std::fmt::Display, { type UsefulType = ::UsefulType; - const SIZE: usize = loop { + const SIZE: usize = (loop { let total = 0; let total = total + ::SIZE; break total; - }; + }); + fn display(&self) -> String { + ("[".to_string() + &self.0.to_string()) + "]" + } } impl UsefulTrait for (T0, T1) where - T0: UsefulTrait, - T1: UsefulTrait, + T0: UsefulTrait + std::fmt::Display, + T1: UsefulTrait + std::fmt::Display, ::UsefulType: IsUseful<::UsefulType>, { type UsefulType = <::UsefulType as IsUseful<::UsefulType>>::State; - const SIZE: usize = loop { + const SIZE: usize = (loop { let total = 0; let total = total + ::SIZE; let total = total + ::SIZE; break total; - }; + }); + fn display(&self) -> String { + (loop { + let s = "[".to_string() + &self.0.to_string(); + let s = s + "," + &self.1.to_string(); + break s; + }) + "]" + } } impl UsefulTrait for (T0, T1, T2) where - T0: UsefulTrait, - T1: UsefulTrait, - T2: UsefulTrait, + T0: UsefulTrait + std::fmt::Display, + T1: UsefulTrait + std::fmt::Display, + T2: UsefulTrait + std::fmt::Display, ::UsefulType: IsUseful<::UsefulType>, ::UsefulType: IsUseful< <::UsefulType as IsUseful<::UsefulType>>::State, @@ -2310,13 +2322,21 @@ pub mod typle_fold { type UsefulType = <::UsefulType as IsUseful< <::UsefulType as IsUseful<::UsefulType>>::State, >>::State; - const SIZE: usize = loop { + const SIZE: usize = (loop { let total = 0; let total = total + ::SIZE; let total = total + ::SIZE; let total = total + ::SIZE; break total; - }; + }); + fn display(&self) -> String { + (loop { + let s = "[".to_string() + &self.0.to_string(); + let s = s + "," + &self.1.to_string(); + let s = s + "," + &self.2.to_string(); + break s; + }) + "]" + } } } pub mod unzip { diff --git a/tests/compile/typle_fold.rs b/tests/compile/typle_fold.rs index df95e95..b28a8ef 100644 --- a/tests/compile/typle_fold.rs +++ b/tests/compile/typle_fold.rs @@ -10,12 +10,15 @@ pub trait UsefulTrait { type UsefulType: IsUseful; const SIZE: usize; + + #[allow(dead_code)] + fn display(&self) -> String; } #[typle(Tuple for 1..=3)] impl UsefulTrait for T where - T<_>: UsefulTrait, + T<_>: UsefulTrait + std::fmt::Display, typle_bound!(i in 1.. => T<{i}>::UsefulType):IsUseful< typle_fold!( T<0>::UsefulType; @@ -29,4 +32,11 @@ where ); const SIZE: usize = typle_fold!(0; i in .. => |total| total + T::<{i}>::SIZE); + + fn display(&self) -> String { + typle_fold!( + "[".to_string() + &self[[0]].to_string(); + i in 1.. => |s| s + "," + &self[[i]].to_string() + ) + "]" + } }