Repository: python/mypy

Issue:
cache bug switching to disallow_incomplete_defs = true
### version info

```console
$ mypy --version
mypy 0.620
```

### reproduction script

```bash
#!/usr/bin/env bash
set -euxo pipefail

mypy --version
rm -rf .mypy_cache

: should pass, no special setting
echo -e 'def f(x) -> str:\n    return ""\n' > t.py
echo -e '[mypy]\n' > mypy.ini
mypy t.py

: should fail, does not
echo -e '[mypy]\ndisallow_incomplete_defs = true' > mypy.ini
mypy t.py

: now it properly fails
rm -rf .mypy_cache
mypy t.py
```

### reproduction output

```
+ mypy --version
mypy 0.620
+ rm -rf .mypy_cache
+ : should pass, no special setting
+ echo -e 'def f(x) -> str:\n    return ""\n'
+ echo -e '[mypy]\n'
+ mypy t.py
+ : should fail, does not
+ echo -e '[mypy]\ndisallow_incomplete_defs = true'
+ mypy t.py
+ : now it properly fails
+ rm -rf .mypy_cache
+ mypy t.py
t.py:1: error: Function is missing a type annotation for one or more arguments
```

### more obvious file contents

#### before

```ini
[mypy]
```

```python
def f(x) -> str:
    return ""
```

#### after

```ini
[mypy]
disallow_incomplete_defs = true
```

```python
def f(x) -> str:
    return ""
```


Discussion:
Confirmed. Manual repro instructions: with the t.py file above, and starting with an empty .mypy_cache:

- run mypy t.py --disallow-untyped-defs (gives an error)
- run mypy t.py (no error)
- run mypy t.py --disallow-untyped-defs (no error!)

It seems that when the flag value changes the cache is not ignored. A possible fix would be to add "disallow_incomplete_defs" to the list of per-module options (PER_MODULE_OPTIONS) in mypy/options.py (thus also causing it to be added to OPTIONS_AFFECTING_CACHE), though I'm not sure why this option is currently not per-module.

Resolving patch:
diff --git a/mypy/options.py b/mypy/options.py
--- a/mypy/options.py
+++ b/mypy/options.py
@@ -18,32 +18,34 @@ class Options:
     """Options collected from flags."""
 
     PER_MODULE_OPTIONS = {
-        "ignore_missing_imports",
-        "follow_imports",
-        "follow_imports_for_stubs",
-        "disallow_any_generics",
-        "disallow_any_unimported",
-        "disallow_any_expr",
+        # Please keep this list sorted
+        "always_false",
+        "always_true",
+        "check_untyped_defs",
+        "debug_cache",
         "disallow_any_decorated",
         "disallow_any_explicit",
+        "disallow_any_expr",
+        "disallow_any_generics",
+        "disallow_any_unimported",
+        "disallow_incomplete_defs",
         "disallow_subclassing_any",
         "disallow_untyped_calls",
+        "disallow_untyped_decorators",
         "disallow_untyped_defs",
-        "check_untyped_defs",
-        "debug_cache",
-        "strict_optional_whitelist",
+        "follow_imports",
+        "follow_imports_for_stubs",
+        "ignore_errors",
+        "ignore_missing_imports",
+        "local_partial_types",
+        "no_implicit_optional",
         "show_none_errors",
+        "strict_boolean",
+        "strict_optional",
+        "strict_optional_whitelist",
         "warn_no_return",
         "warn_return_any",
         "warn_unused_ignores",
-        "ignore_errors",
-        "strict_boolean",
-        "no_implicit_optional",
-        "always_true",
-        "always_false",
-        "strict_optional",
-        "disallow_untyped_decorators",
-        "local_partial_types",
     }
 
     OPTIONS_AFFECTING_CACHE = ((PER_MODULE_OPTIONS |


Test patch:
diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test
--- a/test-data/unit/check-flags.test
+++ b/test-data/unit/check-flags.test
@@ -471,6 +471,23 @@ if z:  # E: Condition must be a boolean
 [builtins fixtures/bool.pyi]
 
 
+[case testPerFileIncompleteDefsBasic]
+# flags: --config-file tmp/mypy.ini
+import standard, incomplete
+
+[file standard.py]
+def incomplete(x) -> int:
+    return 0
+[file incomplete.py]
+def incomplete(x) -> int:  # E: Function is missing a type annotation for one or more arguments
+    return 0
+[file mypy.ini]
+[[mypy]
+disallow_incomplete_defs = False
+[[mypy-incomplete]
+disallow_incomplete_defs = True
+
+
 [case testPerFileStrictOptionalBasic]
 # flags: --config-file tmp/mypy.ini
 import standard, optional