Skip to content

Commit

Permalink
Support use of typle_fold! in expressions
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
jongiddy committed Aug 13, 2024
1 parent 7e3eedb commit 087ea24
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 33 deletions.
26 changes: 18 additions & 8 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
},
})),
}))
}

Expand Down
24 changes: 12 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
44 changes: 32 additions & 12 deletions tests/compile/mod.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2271,37 +2271,49 @@ pub mod typle_fold {
pub trait UsefulTrait {
type UsefulType: IsUseful<Something>;
const SIZE: usize;
#[allow(dead_code)]
fn display(&self) -> String;
}
impl<T0> UsefulTrait for (T0,)
where
T0: UsefulTrait,
T0: UsefulTrait + std::fmt::Display,
{
type UsefulType = <T0>::UsefulType;
const SIZE: usize = loop {
const SIZE: usize = (loop {
let total = 0;
let total = total + <T0>::SIZE;
break total;
};
});
fn display(&self) -> String {
("[".to_string() + &self.0.to_string()) + "]"
}
}
impl<T0, T1> UsefulTrait for (T0, T1)
where
T0: UsefulTrait,
T1: UsefulTrait,
T0: UsefulTrait + std::fmt::Display,
T1: UsefulTrait + std::fmt::Display,
<T1>::UsefulType: IsUseful<<T0>::UsefulType>,
{
type UsefulType = <<T1>::UsefulType as IsUseful<<T0>::UsefulType>>::State;
const SIZE: usize = loop {
const SIZE: usize = (loop {
let total = 0;
let total = total + <T0>::SIZE;
let total = total + <T1>::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<T0, T1, T2> 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,
<T1>::UsefulType: IsUseful<<T0>::UsefulType>,
<T2>::UsefulType: IsUseful<
<<T1>::UsefulType as IsUseful<<T0>::UsefulType>>::State,
Expand All @@ -2310,13 +2322,21 @@ pub mod typle_fold {
type UsefulType = <<T2>::UsefulType as IsUseful<
<<T1>::UsefulType as IsUseful<<T0>::UsefulType>>::State,
>>::State;
const SIZE: usize = loop {
const SIZE: usize = (loop {
let total = 0;
let total = total + <T0>::SIZE;
let total = total + <T1>::SIZE;
let total = total + <T2>::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 {
Expand Down
12 changes: 11 additions & 1 deletion tests/compile/typle_fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ pub trait UsefulTrait {
type UsefulType: IsUseful<Something>;

const SIZE: usize;

#[allow(dead_code)]
fn display(&self) -> String;
}

#[typle(Tuple for 1..=3)]
impl<T: Tuple> 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;
Expand All @@ -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()
) + "]"
}
}

0 comments on commit 087ea24

Please sign in to comment.