Repository: pallets/click (python)

Issue:
Flags with `multiple=True` trigger `TypeError`
Adding an option with `is_flag=True` and `multiple=True` will trigger a `TypeError` in `Parameter.consume_value()` due to bad default value.

```py
import click

@click.command()
@click.option("-v", is_flag=True, multiple=True)
def main(v):
    click.echo(v)

if __name__ == "__main__":
    main()
```

When run without the flag, a TypeError is raised:

```
(venv39) jreese@butterfree ~/scratch » python foo.py
Traceback (most recent call last):
  File "/Users/jreese/scratch/foo.py", line 25, in <module>
    main()
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 1054, in main
    with self.make_context(prog_name, args, **extra) as ctx:
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 920, in make_context
    self.parse_args(ctx, args)
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 1378, in parse_args
    value, args = param.handle_parse_result(ctx, opts, args)
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 2356, in handle_parse_result
    value, source = self.consume_value(ctx, opts)
  File "/Users/jreese/scratch/venv39/lib/python3.9/site-packages/click/core.py", line 2903, in consume_value
    and any(v is _flag_needs_value for v in value)
TypeError: 'bool' object is not iterable
```

Manually setting a better default value, like `default=()`, avoids the type error, but this used to work on 7.x without setting the default value.

Environment:

- Python version: tested on 3.8.6 and 3.9.3
- Click version: worked on 7.1.2; errors on 8.0.4 and 8.1.2


Pull request:
Disallow use of is_flag and multiple in options
Raises a TypeError similar to when specifying both `count=True` and `is_flag=True`.

- fixes #2246

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [ ] Add or update relevant docs, in the docs folder and in code.
- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index a618f5a..16a982d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,9 @@ Unreleased
 
 -   Use verbose form of ``typing.Callable`` for ``@command`` and
     ``@group``. :issue:`2255`
+-   Show error when attempting to create an option with
+    ``multiple=True, is_flag=True``. Use ``count`` instead.
+    :issue:`2246`
 
 
 Version 8.1.2
diff --git a/src/click/core.py b/src/click/core.py
index a9a72c5..5abfb0f 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -2580,6 +2580,9 @@ class Option(Parameter):
                 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_options.py b/tests/test_options.py
index 2e1f291..d4090c7 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -904,3 +904,21 @@ def test_type_from_flag_value():
 )
 def test_is_bool_flag_is_correctly_set(option, expected):
     assert option.is_bool_flag is expected
+
+
+@pytest.mark.parametrize(
+    ("kwargs", "message"),
+    [
+        ({"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):
+    with pytest.raises(TypeError) as e:
+        click.Option(["-a"], **kwargs)
+
+    assert message in str(e.value)