Skip to content

Commit

Permalink
Document how to work with typle associated types
Browse files Browse the repository at this point in the history
  • Loading branch information
jongiddy committed Apr 22, 2024
1 parent 1d35bdc commit 95cd0ec
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 3 deletions.
3 changes: 1 addition & 2 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1834,11 +1834,10 @@ impl<'a> TypleContext<'a> {
}
let mut all = expr.clone();
context.replace_expr(&mut all, state)?;
for index in range {
for (index, mut expr) in range.zip_clone(expr) {
if let Some(ident) = &pattern {
*context.constants.get_mut(ident).unwrap() = index;
}
let mut expr = expr.clone();
context.replace_expr(&mut expr, state)?;
all = Expr::Binary(syn::ExprBinary {
attrs: Vec::new(),
Expand Down
17 changes: 16 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,23 @@
//!
//! # Limitations
//!
//! - The typle trait (`Tuple` in the examples) can only be applied to an
//! - 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.
//! - `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.
//! ```rust ignore
//! // ERROR: conflicting implementations of trait `TryUnzip`
//! # use typle::typle;
//! # trait TryUnzip {}
//! #[typle(Tuple for 2..=3)]
//! impl<I, T, E> TryUnzip for I
//! where
//! I: Iterator<Item = Result<T, E>>, // T only appears as associated type of Self
//! T: Tuple,
//! {}
//! ```
//! - Standalone `async` and `unsafe` functions are not supported.
//! - Standalone functions require explicit lifetimes on references:
//! ```rust
Expand Down
123 changes: 123 additions & 0 deletions tests/compile/mod.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2089,3 +2089,126 @@ pub mod typle_fold {
};
}
}
pub mod unzip {
use typle::typle;
pub trait TryUnzip {
type Output;
type Error;
fn try_unzip(self) -> Result<Self::Output, Self::Error>;
}
pub trait TryUnzipModified<Output> {
type Error;
fn try_unzip(self) -> Result<Output, Self::Error>;
}
impl<I, T0, T1, E> TryUnzipModified<(Vec<T0>, Vec<T1>)> for I
where
I: Iterator<Item = Result<(T0, T1), E>>,
{
type Error = E;
fn try_unzip(self) -> Result<(Vec<T0>, Vec<T1>), Self::Error> {
let mut vecs = (Vec::new(), Vec::new());
for result in self {
let t = result?;
loop {
{
vecs.0.push(t.0);
}
{
vecs.1.push(t.1);
}
break;
}
}
Ok(vecs)
}
}
impl<I, T0, T1, T2, E> TryUnzipModified<(Vec<T0>, Vec<T1>, Vec<T2>)> for I
where
I: Iterator<Item = Result<(T0, T1, T2), E>>,
{
type Error = E;
fn try_unzip(self) -> Result<(Vec<T0>, Vec<T1>, Vec<T2>), Self::Error> {
let mut vecs = (Vec::new(), Vec::new(), Vec::new());
for result in self {
let t = result?;
loop {
{
vecs.0.push(t.0);
}
{
vecs.1.push(t.1);
}
{
vecs.2.push(t.2);
}
break;
}
}
Ok(vecs)
}
}
pub trait TryUnzipTuple<T, E> {
type Output;
fn try_unzip<I>(iter: I) -> Result<Self::Output, E>
where
I: Iterator<Item = Result<T, E>>;
}
impl<T0, T1, E> TryUnzipTuple<(T0, T1), E> for (T0, T1) {
type Output = (Vec<T0>, Vec<T1>);
fn try_unzip<I>(iter: I) -> Result<Self::Output, E>
where
I: Iterator<Item = Result<(T0, T1), E>>,
{
let mut vecs = (Vec::new(), Vec::new());
for result in iter {
let t = result?;
loop {
{
vecs.0.push(t.0);
}
{
vecs.1.push(t.1);
}
break;
}
}
Ok(vecs)
}
}
impl<T0, T1, T2, E> TryUnzipTuple<(T0, T1, T2), E> for (T0, T1, T2) {
type Output = (Vec<T0>, Vec<T1>, Vec<T2>);
fn try_unzip<I>(iter: I) -> Result<Self::Output, E>
where
I: Iterator<Item = Result<(T0, T1, T2), E>>,
{
let mut vecs = (Vec::new(), Vec::new(), Vec::new());
for result in iter {
let t = result?;
loop {
{
vecs.0.push(t.0);
}
{
vecs.1.push(t.1);
}
{
vecs.2.push(t.2);
}
break;
}
}
Ok(vecs)
}
}
impl<I, T, E> TryUnzip for I
where
I: Iterator<Item = Result<T, E>>,
T: TryUnzipTuple<T, E>,
{
type Output = <T as TryUnzipTuple<T, E>>::Output;
type Error = E;
fn try_unzip(self) -> Result<Self::Output, Self::Error> {
<T as TryUnzipTuple<T, E>>::try_unzip(self)
}
}
}
1 change: 1 addition & 0 deletions tests/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod method;
pub mod pattern;
pub mod type_alias;
pub mod typle_fold;
pub mod unzip;
118 changes: 118 additions & 0 deletions tests/compile/unzip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use typle::typle;

// How to convert `Iterator<Item = Result<T, E>` to `Result<(Vec<T<0>>, Vec<T<1>>), E>`
// for any tuple size, generalizing the 2-tuple case described in
// https://users.rust-lang.org/t/unzip-with-error-handling/110250
// That is, implement the following `TryUnzip` trait for `Iterator<Item = Result<Tuple, Error>>`.
pub trait TryUnzip {
type Output;
type Error;

fn try_unzip(self) -> Result<Self::Output, Self::Error>;
}

// typle` does not work when the tuple types are only associated types because
// associated types cannot distinguish implementations. The implementations for
// each tuple length conflict because they are all on the same type (in this
// case `Iterator`): https://github.com/rust-lang/rust/issues/20400

// #[typle(Tuple for 2..=3)]
// impl<I, T, E> TryUnzip for I
// where
// I: Iterator<Item = Result<T, E>>,
// T: Tuple,
// {
// type Output = typle_for!(i in .. => Vec<T<{i}>>);
// type Error = E;
//
// fn try_unzip(self) -> Result<Self::Output, Self::Error> {
// let mut vecs = typle_for!(i in .. => Vec::new());
// for result in self {
// let t = result?;
// for typle_index!(i) in 0..T::LEN {
// vecs[[i]].push(t[[i]]);
// }
// }
// Ok(vecs)
// }
// }

// If the trait can be modified, then we can use a generic type for the tuple
// type instead of an associated type:

pub trait TryUnzipModified<Output> {
type Error;

fn try_unzip(self) -> Result<Output, Self::Error>;
}

#[typle(Tuple for 2..=3)]
impl<I, T, E> TryUnzipModified<typle_for!(i in .. => Vec<T<{i}>>)> for I
where
I: Iterator<Item = Result<T, E>>,
T: Tuple,
{
type Error = E;

fn try_unzip(self) -> Result<typle_for!(i in .. => Vec<T<{i}>>), Self::Error> {
#[typle_attr_if(T::LEN == 0, allow(unused_mut))]
let mut vecs = typle_for!(i in .. => Vec::new());
for result in self {
#[typle_attr_if(T::LEN == 0, allow(clippy::let_unit_value, unused_variables))]
let t = result?;
for typle_index!(i) in 0..T::LEN {
vecs[[i]].push(t[[i]]);
}
}
Ok(vecs)
}
}

// A solution that does not modify the original trait is to add a new trait with
// an associated method, implement it on the tuple type, and call it from an
// impl for the original trait:

pub trait TryUnzipTuple<T, E> {
type Output;

fn try_unzip<I>(iter: I) -> Result<Self::Output, E>
where
I: Iterator<Item = Result<T, E>>;
}

#[typle(Tuple for 2..=3)]
impl<T, E> TryUnzipTuple<T, E> for T
where
T: Tuple,
{
type Output = typle_for!(i in .. => Vec<T<{i}>>);

fn try_unzip<I>(iter: I) -> Result<Self::Output, E>
where
I: Iterator<Item = Result<T, E>>,
{
#[typle_attr_if(T::LEN == 0, allow(unused_mut))]
let mut vecs = typle_for!(i in .. => Vec::new());
for result in iter {
#[typle_attr_if(T::LEN == 0, allow(clippy::let_unit_value, unused_variables))]
let t = result?;
for typle_index!(i) in 0..T::LEN {
vecs[[i]].push(t[[i]]);
}
}
Ok(vecs)
}
}

impl<I, T, E> TryUnzip for I
where
I: Iterator<Item = Result<T, E>>,
T: TryUnzipTuple<T, E>,
{
type Output = <T as TryUnzipTuple<T, E>>::Output;
type Error = E;

fn try_unzip(self) -> Result<Self::Output, Self::Error> {
<T as TryUnzipTuple<T, E>>::try_unzip(self)
}
}
24 changes: 24 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,27 @@ fn test_min_max() {
fn test_associated_method() {
assert_eq!(compile::method::X::associated((1, 2, 3), 2), (3, 4, 5));
}

#[test]
fn test_try_unzip_modified() {
use compile::unzip::TryUnzipModified;
let vec: Vec<Result<_, &str>> = vec![Ok(('a', 3, 1)), Ok(('b', 6, 2))];
assert_eq!(
vec.into_iter().try_unzip(),
Ok((vec!['a', 'b'], vec![3, 6], vec![1, 2]))
);
let vec = vec![Ok(('a', 3)), Ok(('b', 6)), Ok(('i', 1)), Err("bad")];
assert_eq!(vec.into_iter().try_unzip(), Err("bad"));
}

#[test]
fn test_try_unzip_tuple() {
use compile::unzip::TryUnzip;
let vec: Vec<Result<_, &str>> = vec![Ok(('a', 3, 1)), Ok(('b', 6, 2))];
assert_eq!(
vec.into_iter().try_unzip(),
Ok((vec!['a', 'b'], vec![3, 6], vec![1, 2]))
);
let vec = vec![Ok(('a', 3)), Ok(('b', 6)), Ok(('i', 1)), Err("bad")];
assert_eq!(vec.into_iter().try_unzip(), Err("bad"));
}

0 comments on commit 95cd0ec

Please sign in to comment.