Repository: rust-itertools/itertools (language: rust)

Issue:
iproduct should allow empty product (yielding unit once)
To reproduce:

```
use itertools::iproduct;

fn main() {
    for e in iproduct!(
    ) {
        println!("{e:?}");
    }
}
```

Expected output:
```
()
```

Actual output:
```
error: unexpected end of macro invocation
   --> src/main.rs:4:14
    |
4   |       for e in iproduct!(
    |  ______________^
5   | |     ) {
    | |_____^ missing tokens in macro arguments
    |
note: while trying to match `@`
   --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/itertools-0.12.0/src/lib.rs:247:6
    |
247 |     (@flatten $I:expr,) => (
    |      ^
```

This wouldn't be particularly useful for humans, but macros that generate iproduct! invocations might produce this case sometimes.


Discussion / hints:
I'm not sure.

    iproduct!(0..2, 0..2) -> (0, 0), ...   yes tuples
    iproduct!(0..2) --> 0, ...             not tuples (change this would be a breaking change)
    iproduct!()                            then why would it be a tuple?
> but macros that generate iproduct! invocations might produce this case sometimes.

I think it would be perfectly fine to not do this until some specific macro that wants to use it shows up to say why and how it'd be useful.  Otherwise an error seems perfectly fine here.
> ```
> iproduct!(0..2, 0..2) -> (0, 0), ...   yes tuples
> iproduct!(0..2) --> 0, ...             not tuples (change this would be a breaking change)
> iproduct!()                            then why would it be a tuple?
> ```

Oh!  The lack of tuples for a single argument is strange.  My hypothetical macro generating iproduct! calls would already break.

I guess there's a workaround:
```
use itertools::Itertools;

macro_rules! iproduct_fully_general {
   { } => { std::iter::once(()) };
   { $e0:expr $(,)? } => { std::iter::IntoIterator::into_iter($e0).map(|v| (v,)) };
   { $e0:expr, $($e:expr),+ } => { itertools::iproduct!($e0, $($e),+) };
}  // the type depends funkily on the number of arguments, but that's true of iproduct! anyway

fn main() {
    println!("{:?}\n{:?}\n{:?}",
        iproduct_fully_general!().collect_vec(),
        iproduct_fully_general!([1,2]).collect_vec(),
        iproduct_fully_general!([1,2], [3,4]).collect_vec(),
    )
}
```

I really think we should fix this in a future incompatible version.
I rewrote `Itertools::multi_cartesian_product` internals so the next release is a breaking one (it will finally produce a single vector for an empty product ^^). I guess it has some sense to do that as well.

Fix patch:
diff --git a/src/lib.rs b/src/lib.rs
index 1f39379e2..5d49b7fc6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -249,13 +249,19 @@ macro_rules! iproduct {
     (@flatten $I:expr, $J:expr, $($K:expr,)*) => (
         $crate::iproduct!(@flatten $crate::cons_tuples($crate::iproduct!($I, $J)), $($K,)*)
     );
-    ($I:expr) => (
-        $crate::__std_iter::IntoIterator::into_iter($I)
+    () => (
+        $crate::__std_iter::once(())
     );
-    ($I:expr, $J:expr) => (
-        $crate::Itertools::cartesian_product($crate::iproduct!($I), $crate::iproduct!($J))
+    ($I:expr $(,)?) => (
+        $crate::__std_iter::IntoIterator::into_iter($I).map(|elt| (elt,))
     );
-    ($I:expr, $J:expr, $($K:expr),+) => (
+    ($I:expr, $J:expr $(,)?) => (
+        $crate::Itertools::cartesian_product(
+            $crate::__std_iter::IntoIterator::into_iter($I),
+            $crate::__std_iter::IntoIterator::into_iter($J),
+        )
+    );
+    ($I:expr, $J:expr, $($K:expr),+ $(,)?) => (
         $crate::iproduct!(@flatten $crate::iproduct!($I, $J), $($K,)+)
     );
 }


Test patch:
diff --git a/tests/macros_hygiene.rs b/tests/macros_hygiene.rs
index d1111245d..20b59fba8 100644
--- a/tests/macros_hygiene.rs
+++ b/tests/macros_hygiene.rs
@@ -1,5 +1,6 @@
 #[test]
 fn iproduct_hygiene() {
+    let _ = itertools::iproduct!();
     let _ = itertools::iproduct!(0..6);
     let _ = itertools::iproduct!(0..6, 0..9);
     let _ = itertools::iproduct!(0..6, 0..9, 0..12);
diff --git a/tests/test_core.rs b/tests/test_core.rs
index 45a8df438..db77a1e3e 100644
--- a/tests/test_core.rs
+++ b/tests/test_core.rs
@@ -18,6 +18,23 @@ use crate::it::Itertools;
 use core::iter;
 use itertools as it;
 
+#[test]
+fn product0() {
+    let mut prod = iproduct!();
+    assert_eq!(prod.next(), Some(()));
+    assert!(prod.next().is_none());
+}
+
+#[test]
+fn iproduct1() {
+    let s = "αβ";
+
+    let mut prod = iproduct!(s.chars());
+    assert_eq!(prod.next(), Some(('α',)));
+    assert_eq!(prod.next(), Some(('β',)));
+    assert!(prod.next().is_none());
+}
+
 #[test]
 fn product2() {
     let s = "αβ";