Repository: pallets/click

Issue:
can't get list of bools using multiple flag
I have an option that is a list of `bool`s, but I'm pairing each boolean value with another `multiple` option. So I do actually need a list of booleans and not just a count of values because order matters.
I'm not using `is_flag`, but I assume this translates to the same under the hood.

```
@click.option(
    "--collection/--single",
    "-c/-s",
    type=bool,
    default=[False],
    count=True,
    help=(
        "Is a file a collection of books? Add once for every file included. "
        "Consumed in order of files."
    ),
)
@click.option(
    "--file",
    "-f",
    required=True,
    type=click.Path(
        exists=True, readable=True, dir_okay=False, allow_dash=True, path_type=str
    ),
    multiple=True,
    help=(
        "The path of the input file.  Note: Every file must be paired with a language. "
        "And the pairs will be processed left to right."
    ),
)

```

Environment:

Python version: 3.9.7.1
Click version: 8.1.3

Is there an alternative way I should be doing this? If not #2246 breaks my code.


Discussion:

Resolving patch:
diff --git a/src/click/core.py b/src/click/core.py
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -2570,8 +2570,13 @@ def __init__(
             # flag if flag_value is set.
             self._flag_needs_value = flag_value is not None
 
+        self.default: t.Union[t.Any, t.Callable[[], t.Any]]
+
         if is_flag and default_is_missing and not self.required:
-            self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False
+            if multiple:
+                self.default = ()
+            else:
+                self.default = False
 
         if flag_value is None:
             flag_value = not self.default
@@ -2622,9 +2627,6 @@ def __init__(
                 if self.is_flag:
                     raise TypeError("'count' is not valid with 'is_flag'.")
 
-            if self.multiple and self.is_flag:
-                raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.")
-
     def to_info_dict(self) -> t.Dict[str, t.Any]:
         info_dict = super().to_info_dict()
         info_dict.update(


Test patch:
diff --git a/tests/test_defaults.py b/tests/test_defaults.py
--- a/tests/test_defaults.py
+++ b/tests/test_defaults.py
@@ -38,3 +38,24 @@ def cli(arg):
     result = runner.invoke(cli, [])
     assert not result.exception
     assert result.output.splitlines() == ["<1|2>", "<3|4>"]
+
+
+def test_multiple_flag_default(runner):
+    """Default default for flags when multiple=True should be empty tuple."""
+
+    @click.command
+    # flag due to secondary token
+    @click.option("-y/-n", multiple=True)
+    # flag due to is_flag
+    @click.option("-f", is_flag=True, multiple=True)
+    # flag due to flag_value
+    @click.option("-v", "v", flag_value=1, multiple=True)
+    @click.option("-q", "v", flag_value=-1, multiple=True)
+    def cli(y, f, v):
+        return y, f, v
+
+    result = runner.invoke(cli, standalone_mode=False)
+    assert result.return_value == ((), (), ())
+
+    result = runner.invoke(cli, ["-y", "-n", "-f", "-v", "-q"], standalone_mode=False)
+    assert result.return_value == ((True, False), (True,), (1, -1))
diff --git a/tests/test_options.py b/tests/test_options.py
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -911,10 +911,6 @@ def test_is_bool_flag_is_correctly_set(option, expected):
     [
         ({"count": True, "multiple": True}, "'count' is not valid with 'multiple'."),
         ({"count": True, "is_flag": True}, "'count' is not valid with 'is_flag'."),
-        (
-            {"multiple": True, "is_flag": True},
-            "'multiple' is not valid with 'is_flag', use 'count'.",
-        ),
     ],
 )
 def test_invalid_flag_combinations(runner, kwargs, message):