[mypy] `--strict` で有効にならないオプションまとめ

今まで適当に `mypy --strict` を使ってきたが，本当にstrictなのか気になったので，何か有効になっていないオプションで大事なものがないか調べることにしました．

**以下は投稿日時現在で最新のバージョン 0.770 のものです**

## 結論

`--strict` で有効になっていない警告系のオプションは以下の6つ．

- `--disallow-any-unimported`
- `--disallow-any-expr`
- `--disallow-any-decorated`
- `--disallow-any-explicit`
- `--warn-unreachable`
- `--warn-incomplete-stub`

## それぞれのオプションが有効かどうか

まず，`mypy --help` で表示される警告系のオプションについて `--strict` で有効になるかどうかを以下にまとめました．


ただし，以下のようにデフォルトでオンになっているオプションは含めていません（ `mypy --help` だとそれぞれを抑制するためのオプションが表示されます）．

- `--strict-optional`
- `--warn-no-return`
- `--disallow-untyped-globals`
- `--disallow-redefinition`

`--strict` で有効：✅ / 無効：❌

### Config file

||option|description
|---|---|---
|✅|`--warn-unused-configs`|Warn about unused '[mypy-<pattern>]' config sections

### Disallow dynamic typing

||option|description
|---|---|---
|❌|`--disallow-any-unimported`|Disallow `Any` types resulting from unfollowed imports
|❌|`--disallow-any-expr`|Disallow all expressions that have type `Any`
|❌|`--disallow-any-decorated`|Disallow functions that have `Any` in their signature after decorator transformation
|❌|`--disallow-any-explicit`|Disallow explicit `Any` in type positions
|✅|`--disallow-any-generics`|Disallow usage of generic types that do not specify explicit type parameters
|✅|`--disallow-subclassing-any`|Disallow subclassing values of type `Any` when defining classes

### Untyped definitions and calls

||option|description
|---|---|---
|✅|`--disallow-untyped-calls`|Disallow calling functions without type  annotations from functions with type annotations
|✅|`--disallow-untyped-defs`|Disallow defining functions without type annotations or with incomplete type annotations
|✅|`--disallow-incomplete-defs`|Disallow defining functions with incomplete type annotations
|✅|`--check-untyped-defs`|Type check the interior of functions without type annotations
|✅|`--disallow-untyped-decorators`|Disallow decorating typed functions with untyped decorators

### None and Optional handling

||option|description
|---|---|---
|✅|`--no-implicit-optional`|Don't assume arguments with default values of `None` are `Optional`

### Configuring warnings

||option|description
|---|---|---
|✅|`--warn-redundant-casts`|Warn about casting an expression to its inferred type
|✅|`--warn-unused-ignores`|Warn about unneeded `# type: ignore` comments
|✅|`--warn-return-any`|Warn about returning values of type `Any` from non-`Any` typed functions
|❌|`--warn-unreachable`|Warn about statements or expressions inferred to be unreachable or redundant

### Miscellaneous strictness flags

||option|description
|---|---|---
|✅|`--no-implicit-reexport`|Treat imports as private unless aliased
|✅|`--strict-equality`|Prohibit equality, identity, and container checks for non-overlapping types

### Advanced options

||option|description
|---|---|---
|❌|`--warn-incomplete-stub`|Warn if missing type annotation in typeshed, only relevant with `--disallow-untyped-defs` or `--disallow-incomplete-defs` enabled

## 有効になっていないオプションについて

### `--disallow-any-unimported`

スタブがないと，外部ライブラリの型は `typing.Any` のエイリアスになりますが，それを使用していると警告．

```sample.py
import numpy as np  # type: ignore

def func() -> np.ndarray:
    return np.array(1)
```

```terminal
$ mypy --strict --show-error-code --pretty sample.py
Success: no issues found in 1 source file
$ mypy --disallow-any-unimported --show-error-code --pretty sample.py
sample.py:3: error: Return type becomes "Any" due to an unfollowed import  [no-any-unimported]
    def func() -> np.ndarray:
    ^
Found 1 error in 1 file (checked 1 source file)
```

以下のように変更するとOK．

```diff
- def func() -> np.ndarray:
+ def func() -> t.Any:
```

`Any` だらけになるからどうかとも思ったが， `np.ndarray` も結局のところ錯覚でしかないので結構ありかもしれません．
コメントと割り切って， `np.ndarray` とか書く場合もあると思うので，そこら辺は個人の判断だと思います． 

### `--disallow-any-expr`

`typing.Any` を含む式に警告．
`np.array(1) + np.array(2)` とかでアウト．スタブを作らないとほとんど何もできない．

### `--disallow-any-decorated`

デコレータを通したあとの関数が引数or返り値に `typing.Any` を持っていると警告．

```sample.py
from abc import ABC, abstractmethod
from functools import wraps
import typing as t


class A(ABC):
    @abstractmethod
    def func_a(a: t.Any) -> None:
        pass


def deco(f: t.Callable[[int], int]) -> t.Callable[[int], t.Any]:
    @wraps(f)
    def wrapper(a: int) -> t.Any:
        return t.cast(t.Any, f(a))
    
    return wrapper


@deco
def func_b(a: int) -> int:
    return 0
```

```terminal
$ mypy --strict --show-error-code --pretty sample.py
Success: no issues found in 1 source file
$ mypy --disallow-any-decorated --show-error-code --pretty sample.py
sample.py:8: error: Type of decorated function contains type "Any" ("Callable[[Any], None]")  [misc]
        def func_a(a: t.Any) -> None:
        ^
sample.py:21: error: Type of decorated function contains type "Any" ("Callable[[int], Any]")  [misc]
    def func_b(a: int) -> int:
    ^
Found 2 errors in 1 file (checked 1 source file)
```

普通の関数が `--disallow-any-explicit` 付けない限り，`typing.Any` を引数等に使ってもよいのに，デコレータだけ特別視する意味がよくわからない（そして， `--disallow-any-explicit` はできる気がしない）．

### `--disallow-any-explicit`

明示的な `typing.Any` に警告．厳しい．

### `--warn-unreachable`

不達コードに対して警告．

```sample.py
def process(x: int) -> None:
    if isinstance(x, int) or x > 7:
        print(str(x) + "bad")
    else:
        print(str(x) + "bad")
```

（https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-warn-unreachable のコードを少し変更した．）


```terminal
$ mypy --strict --show-error-code --pretty sample.py
Success: no issues found in 1 source file
$ mypy --warn-unreachable --show-error-code --pretty sample.py
sample.py:2: error: Right operand of 'or' is never evaluated  [unreachable]
        if isinstance(x, int) or x > 7:
                                 ^
sample.py:5: error: Statement is unreachable  [unreachable]
            print(str(x) + "bad")
            ^
Found 2 errors in 1 file (checked 1 source file)
```

あらゆる不達コードを検出できるわけではないが付けて悪いことはなさそう？

### `--warn-incomplete-stub`

スタブに対する警告．そもそも `stubgen` がうまくいかないのでよくわからない．
ドキュメントに

> This flag is mainly intended to be used by people who want contribute to typeshed and would like a convenient way to find gaps and omissions.

って書いてあるから別にいいのかな？

## 感想

個人的には，`--strict` に加えて，以下の2つは一考の余地があるかなと思いました．

- `--disallow-any-unimported`
- `--warn-unreachable`

そもそもmypyを使っているのは単なる惰性なので，Pyright, Pyre, pytype辺りで使いやすいのがあれば乗り換えたい．

雑比較

|ツール名|言語|GitHubリポジトリ|Star数 (2020/5/2 22:41現在)
|---|---|---|---|---
|mypy|Python|https://github.com/python/mypy|8379
|pytype|Python|https://github.com/google/pytype|2629
|Pyre|OCaml|https://github.com/facebook/pyre-check|3334
|Pyright|TypeScript|https://github.com/microsoft/pyright|5107


## 参考

- 公式ドキュメント
  - https://mypy.readthedocs.io/en/stable/command_line.html