py-comp 0.1.3

A macro implementing a Python-like generator expression
Documentation
//! The tests in this file are just simple use cases and general usability
//! checks and examples.
//!
//! For more complex tests, drill down to the sub modules.

use py_comp::comp;

mod test_if_chains;

/// This is a stand-in for any type that does not implement Copy or Clone.
/// Using this type we can know that our implementation does not depend on
/// the implicit semantics of these traits and works for all types.
#[derive(Debug, PartialEq, Eq)]
struct Foo(i32);

/// An Iterator that is not Copy.
#[derive(Debug)]
struct UncopyableIterator {}

impl Iterator for UncopyableIterator {
    type Item = ();

    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

/// An Iterator that is not Copy of Iterators that are not Copy.
#[derive(Debug)]
struct UncopyableIteratorOfUncopyableIterators {}

impl Iterator for UncopyableIteratorOfUncopyableIterators {
    type Item = UncopyableIterator;

    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

#[test]
fn basic_implementation_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[Foo(1), Foo(2)];

    let mut xyz1 = Vec::new();
    for a in x {
        xyz1.push(a)
    }

    #[rustfmt::skip]
        #[allow(clippy::into_iter_on_array)]
        let xyz2 =
            x
            .into_iter()
            .map(move |a| a)
            .collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn basic_implementation_with_if_condition_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[Foo(1), Foo(2)];

    let mut xyz1 = Vec::new();
    for a in x {
        if a.0 % 10 == 2 {
            xyz1.push(a)
        }
    }

    #[rustfmt::skip]
        #[allow(clippy::into_iter_on_array)]
        let xyz2 =
            x
            .into_iter()
            .filter(|a| a.0 % 10 == 2)
            .map(move |a| a)
            .collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn basic_implementation_cartesian_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[Foo(1), Foo(2)];
    let x = &[Foo(11), Foo(12)];
    let y = &[Foo(21), Foo(22)];
    let z = &[Foo(31), Foo(32)];

    let mut xyz1 = Vec::new();
    for a in w {
        for b in x {
            for c in y {
                for d in z {
                    xyz1.push((a, b, c, d))
                }
            }
        }
    }

    #[rustfmt::skip]
        #[allow(clippy::into_iter_on_array)]
        let xyz2 =
            w
            .into_iter()
            .flat_map(move |a| {
                x
                .into_iter()
                .flat_map(move |b| {
                    y
                    .into_iter()
                    .flat_map(move |c| {
                        z
                        .into_iter()
                        .map(move |d| {
                            (a, b, c, d)
                        })
                    })
                })
            })
            .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn basic_implementation_cartesian_with_if_conditions_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[Foo(1), Foo(2)];
    let x = &[Foo(11), Foo(12)];
    let y = &[Foo(21), Foo(22)];
    let z = &[Foo(31), Foo(32)];

    let mut xyz1 = Vec::new();
    for a in w.iter() {
        if a.0 % 10 == 2 {
            for b in x.iter() {
                if b.0 % 10 == 2 {
                    for c in y.iter() {
                        if c.0 % 10 == 2 {
                            for d in z.iter() {
                                if d.0 % 10 == 2 {
                                    xyz1.push((a, b, c, d))
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    #[rustfmt::skip]
        #[allow(clippy::into_iter_on_array)]
        let xyz2 =
            w
            .into_iter()
            .filter(|a| a.0 % 10 == 2)
            .flat_map(move |a| {
                x
                .into_iter()
                .filter(|b| b.0 % 10 == 2)
                .flat_map(move |b| {
                    y
                    .into_iter()
                    .filter(|c| c.0 % 10 == 2)
                    .flat_map(move |c| {
                        z
                        .into_iter()
                        .filter(|d| d.0 % 10 == 2)
                        .map(move |d| {
                            (a, b, c, d)
                        })
                    })
                })
            })
            .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

/// Check that various mixed forms of invocation *compile*.
/// This is not meant to be exhaustive, (it can't be, this macro can
/// accept infinitely long inputs) but it should be representative and cover
/// all/most "parse paths" in the macro, to make sure they are not regressed.
#[test]
fn various_forms_of_usage() {
    let x = &[Foo(1), Foo(2)];
    let y = &[[Foo(1), Foo(2)], [Foo(3), Foo(4)]];
    let z = &[
        [[Foo(1), Foo(2)], [Foo(3), Foo(4)]],
        [[Foo(1), Foo(2)], [Foo(3), Foo(4)]],
    ];

    // importantly:
    // * trailing semicolon is optional.
    // * you can nest as many `for in` clauses as you want.
    // * you may use an `if` or `if let` clause after any `for in` clause.
    let _ = comp!(a; for a in x);
    let _ = comp!(a; for a in x;);

    let _ = comp!(a; for a in x; if *a == Foo(123));
    let _ = comp!(a; for a in x; if *a == Foo(123););

    let _ = comp!(a; for x in y; for a in x);
    let _ = comp!(a; for x in y; for a in x;);

    let _ = comp!(a; for x in y; if x[0] == Foo(123); for a in x);
    let _ = comp!(a; for x in y; if x[0] == Foo(123); for a in x;);

    let _ = comp!(a; for x in y; for a in x; if x[0] == Foo(123));
    let _ = comp!(a; for x in y; for a in x; if x[0] == Foo(123););

    let _ = comp!(a; for y in z; for x in y; for a in x);
    let _ = comp!(a; for y in z; for x in y; for a in x;);

    let _ = comp!(a; for x in y; for a in x; if let Foo(1) | Foo(2) = a);
    let _ = comp!(a; for x in y; for a in x; if let Foo(1) | Foo(2) = a;);

    let _ = comp!(
        (a, b);
        for a in x; if let Foo(1) | Foo(2) = a;
        for b in x; if let Foo(1) | Foo(2) = b
    );
    let _ = comp!(
        (a, b);
        for a in x; if let Foo(1) | Foo(2) = a;
        for b in x; if let Foo(1) | Foo(2) = b;
    );
}

#[test]
fn comp_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[Foo(1), Foo(2)];

    let mut xyz1 = Vec::new();
    for a in x {
        xyz1.push(a)
    }

    let xyz2 = comp!(a; for a in x).collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_with_if_condition_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[Foo(1), Foo(2)];

    let mut xyz1 = Vec::new();
    for a in x {
        if a.0 % 10 == 2 {
            xyz1.push(a)
        }
    }

    let xyz2 = comp!(
        a;
        for a in x;
        if a.0 % 10 == 2;
    )
    .collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_with_if_let_condition_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[Foo(4), Foo(8)];

    let mut xyz1 = Vec::new();
    for a in x {
        if let Foo(_inner @ 1...6) = a {
            xyz1.push(a)
        }
    }

    let xyz2 = comp!(
        a;
        for a in x;
        if let Foo(_inner @ 1...6) = a;
    )
    .collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_with_pattern_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[(Foo(1), Foo(2)), (Foo(3), Foo(4))];

    let mut xyz1 = Vec::new();
    for (a, _b) in x {
        xyz1.push(a)
    }

    let xyz2 = comp!(a; for (a, _b) in x).collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_with_pattern_with_if_condition_1_layer() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let x = &[(Foo(1), Foo(2)), (Foo(3), Foo(4))];

    let mut xyz1 = Vec::new();
    for (a, _b) in x {
        if a.0 % 10 == 2 {
            xyz1.push(a)
        }
    }

    let xyz2 = comp!(
        a;
        for (a, _b) in x;
        if a.0 % 10 == 2;
    )
    .collect::<Vec<&Foo>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_cartesian_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[Foo(1), Foo(2)];
    let x = &[Foo(11), Foo(12)];
    let y = &[Foo(21), Foo(22)];
    let z = &[Foo(31), Foo(32)];

    let mut xyz1 = Vec::new();
    for a in w {
        for b in x {
            for c in y {
                for d in z {
                    xyz1.push((a, b, c, d))
                }
            }
        }
    }

    let xyz2 = comp!(
        (a, b, c, d);
        for a in w;
        for b in x;
        for c in y;
        for d in z;
    )
    .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_cartesian_with_if_conditions_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[Foo(1), Foo(2)];
    let x = &[Foo(11), Foo(12)];
    let y = &[Foo(21), Foo(22)];
    let z = &[Foo(31), Foo(32)];

    let mut xyz1 = Vec::new();
    for a in w.iter() {
        if a.0 % 10 == 2 {
            for b in x.iter() {
                if b.0 % 10 == 2 {
                    for c in y.iter() {
                        if c.0 % 10 == 2 {
                            for d in z.iter() {
                                if d.0 % 10 == 2 {
                                    xyz1.push((a, b, c, d))
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    let xyz2 = comp!(
        (a, b, c, d);
        for a in w;
        if a.0 % 10 == 2;
        for b in x;
        if b.0 % 10 == 2;
        for c in y;
        if c.0 % 10 == 2;
        for d in z;
        if d.0 % 10 == 2;
    )
    .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_cartesian_with_if_and_if_let_conditions_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[Foo(1), Foo(2)];
    let x = &[Foo(14), Foo(18)];
    let y = &[Foo(21), Foo(22)];
    let z = &[Foo(34), Foo(38)];

    let mut xyz1 = Vec::new();
    for a in w.iter() {
        if a.0 % 10 == 2 {
            for b in x.iter() {
                if let Foo(_inner @ 11...16) = b {
                    for c in y.iter() {
                        if c.0 % 10 == 2 {
                            for d in z.iter() {
                                if let Foo(_inner @ 31...36) = d {
                                    xyz1.push((a, b, c, d))
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    let xyz2 = comp!(
        (a, b, c, d);
        for a in w;
        if a.0 % 10 == 2;
        for b in x;
        if let Foo(_inner @ 11...16) = b;
        for c in y;
        if c.0 % 10 == 2;
        for d in z;
        if let Foo(_inner @ 31...36) = d;
    )
    .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_cartesian_with_pattern_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[(Foo(1), Foo(99)), (Foo(2), Foo(99))];
    let x = &[(Foo(11), Foo(99)), (Foo(12), Foo(99))];
    let y = &[(Foo(21), Foo(99)), (Foo(22), Foo(99))];
    let z = &[(Foo(31), Foo(99)), (Foo(32), Foo(99))];

    let mut xyz1 = Vec::new();
    for (a, _a) in w {
        for (b, _b) in x {
            for (c, _c) in y {
                for (d, _d) in z {
                    xyz1.push((a, b, c, d))
                }
            }
        }
    }

    let xyz2 = comp!(
        (a, b, c, d);
        for (a, _a) in w;
        for (b, _b) in x;
        for (c, _c) in y;
        for (d, _d) in z;
    )
    .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn comp_cartesian_with_pattern_with_if_conditions_4_layers() {
    // These need to be references to arrays because of how the closures
    // capture their environment
    let w = &[(Foo(1), Foo(99)), (Foo(2), Foo(99))];
    let x = &[(Foo(11), Foo(99)), (Foo(12), Foo(99))];
    let y = &[(Foo(21), Foo(99)), (Foo(22), Foo(99))];
    let z = &[(Foo(31), Foo(99)), (Foo(32), Foo(99))];

    let mut xyz1 = Vec::new();
    for (a, _a) in w.iter() {
        if a.0 % 10 == 2 {
            for (b, _b) in x.iter() {
                if b.0 % 10 == 2 {
                    for (c, _c) in y.iter() {
                        if c.0 % 10 == 2 {
                            for (d, _d) in z.iter() {
                                if d.0 % 10 == 2 {
                                    xyz1.push((a, b, c, d))
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    let xyz2 = comp!(
        (a, b, c, d);
        for (a, _a) in w;
        if a.0 % 10 == 2;
        for (b, _b) in x;
        if b.0 % 10 == 2;
        for (c, _c) in y;
        if c.0 % 10 == 2;
        for (d, _d) in z;
        if d.0 % 10 == 2;
    )
    .collect::<Vec<(&Foo, &Foo, &Foo, &Foo)>>();

    assert_eq!(xyz1, xyz2);
}

#[test]
fn triple_nested_structure() {
    // This needs to be a reference to an array because of how the closures
    // capture their environment
    let nested_3 = &[
        [
            [Foo(0), Foo(1), Foo(2)],
            [Foo(3), Foo(4), Foo(5)],
            [Foo(6), Foo(7), Foo(8)],
        ],
        [
            [Foo(9), Foo(10), Foo(11)],
            [Foo(12), Foo(13), Foo(14)],
            [Foo(15), Foo(16), Foo(17)],
        ],
        [
            [Foo(18), Foo(19), Foo(20)],
            [Foo(21), Foo(22), Foo(23)],
            [Foo(24), Foo(25), Foo(26)],
        ],
    ];

    let nested_objects = comp!(
        {
            let inner = nested.0;
            Foo(inner + 1)
        };
        for nested_2 in nested_3;
        for nested_1 in nested_2;
        for nested in nested_1;
    )
    .collect::<Vec<Foo>>();

    let expected_values = (1..28).map(Foo).collect::<Vec<Foo>>();

    assert_eq!(expected_values, nested_objects);
}

#[test]
fn uncopyable_iterator() {
    let _ = comp!(x; for x in UncopyableIterator {});
}

#[test]
fn uncopyable_iterator_of_uncopyable_iterators() {
    let _ = comp!(
        item;
        for uncopyable_iterator in UncopyableIteratorOfUncopyableIterators {};
        for item in uncopyable_iterator;
    );
}

mod renamed_comp_import {
    use py_comp::comp as c;

    #[test]
    fn use_renamed_macro() {
        let _ = c!(y; for x in &[[0]]; for y in x);
    }
}