Repository: python/mypy

Issue:
Let's drop --strict-boolean
See discussion in #3185 -- it's not working perfectly and it is encouraging bad style. It also makes `--strict` less useful: it means you can't use `--strict` if you don't believe in `--strict-optional`, else you'd get a ton of false positive from the latter.


Discussion:
Or at least start deprecating it and remove it from `--strict`.
Before we proceed with this, I just wanted to clear up one thing based on the earlier discussion on gitter.

Would this flag become useful if it was restricted just to the cases where it's actually unsafe? Specifically the usage of non-boolean variable in a boolean context is unsafe only if it has more than 1 values that is interpreted as `False`:

```
def f(x: int) -> None:
  if x:  ... # safe
 
def g(x: int = None) -> None:
  if x:  ... # unsafe; is x 0 or None?
 
def h(x: set) -> None:
  if x: ... # safe

def i(x: set = None) -> None:
  if x: ... # unsafe; is x an empty set or None?

def j(x: Callable[[int], int] = None) -> None:
  if x: ... # safe

def k(x: bool = None) -> None:
  if x: ... # unsafe; is x False or None?
```

It seems very easy to identify unsafe cases, using the [truth value testing](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) rules. The usage is unsafe if the variable type is a `Union` that includes more than one of the following types:

- `None`
- a numeric type
- a subtype of Sized
- a type that has `__bool__` method defined

(Edit: The last bullet point can wait until we have structural types)

Do you want me to check if restricting `--strict-boolean` to just these cases would make sense, perhaps using mypy own source code?
Agreed -- it's not worth having `--strict` enable it, and the entire option is questionable.

@pkch We can discuss that as a separate feature (which might perhaps share a name with the current option). Feel free to open an issue for it.
When `--strict-boolean` was added I tried it on my codebase and found it way too pedantic to be useful. Maybe some people really like writing `if len(lst) > 0:` instead of `if lst:`, but I don't think mypy needs to enforce that.

However, there are some related checks that mypy could do that are actually useful, like warning if you do `if some_object.is_foo` instead of `if some_object.is_foo()` (where `is_foo` is a method). But those kinds of things can be discussed in a separate issue.
Regarding the idea that conflating an empty something (or zero) with None is a bad idea, it is actually quite common to have code that does this intentionally, and uglifying it just doesn't seem right.

So let someone please volunteer for a PR that deprecates it and removes it from the `--strict` set. (Explicit `--strict-boolean` or the corresponding config flag should still work for one more release, but with a deprecation message.)

Resolving patch:
diff --git a/mypy/main.py b/mypy/main.py
--- a/mypy/main.py
+++ b/mypy/main.py
@@ -272,8 +272,6 @@ def add_invertible_flag(flag: str,
     parser.add_argument('--find-occurrences', metavar='CLASS.MEMBER',
                         dest='special-opts:find_occurrences',
                         help="print out all usages of a class member (experimental)")
-    add_invertible_flag('--strict-boolean', default=False, strict_flag=True,
-                        help='enable strict boolean checks in conditions')
     strict_help = "Strict mode. Enables the following flags: {}".format(
         ", ".join(strict_flag_names))
     parser.add_argument('--strict', action='store_true', dest='special-opts:strict',
@@ -292,6 +290,8 @@ def add_invertible_flag(flag: str,
     # --dump-graph will dump the contents of the graph of SCCs and exit.
     parser.add_argument('--dump-graph', action='store_true', help=argparse.SUPPRESS)
     # deprecated options
+    add_invertible_flag('--strict-boolean', default=False,
+                        help=argparse.SUPPRESS)
     parser.add_argument('-f', '--dirty-stubs', action='store_true',
                         dest='special-opts:dirty_stubs',
                         help=argparse.SUPPRESS)
@@ -364,6 +364,9 @@ def add_invertible_flag(flag: str,
                      )
 
     # Process deprecated options
+    if options.strict_boolean:
+        print("Warning: --strict-boolean is deprecated; "
+              "see https://github.com/python/mypy/issues/3195", file=sys.stderr)
     if special_opts.almost_silent:
         print("Warning: --almost-silent has been replaced by "
               "--follow-imports=errors", file=sys.stderr)


Test patch: