Repository: python/mypy

Issue:
Regression on `master`: mypy unconditionally crashes when used with `--enable-error-code` for multiple error codes
**Crash Report**

On the `master` branch, mypy unconditionally crashes if used with the command-line options `--enable-error-code ignore-without-code`. I've bisected the regression to:
- #13502

**Traceback**

```
Traceback (most recent call last):
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Users\alexw\coding\mypy\venv\Scripts\mypy.exe\__main__.py", line 7, in <module>
  File "C:\Users\alexw\coding\mypy\mypy\__main__.py", line 15, in console_entry
    main()
  File "C:\Users\alexw\coding\mypy\mypy\main.py", line 95, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "C:\Users\alexw\coding\mypy\mypy\main.py", line 174, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 186, in build
    result = _build(
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 269, in _build
    graph = dispatch(sources, manager, stdout)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 2873, in dispatch
    process_graph(graph, manager)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 3257, in process_graph
    process_stale_scc(graph, scc, manager)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 3378, in process_stale_scc
    graph[id].write_cache()
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 2447, in write_cache
    new_interface_hash, self.meta = write_cache(
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 1639, in write_cache
    meta_str = json_dumps(meta, manager.options.debug_cache)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 1510, in json_dumps
    return json.dumps(obj, sort_keys=True)
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\json\__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type ErrorCode is not JSON serializable
```

**To Reproduce**

- `cd` into a local clone of mypy, activate a local environment with an editable install of mypy.
- Checkout the `master` branch.
- Run `mypy --enable-error-code ignore-without-code` on any repository. I've reproduced it using the selfcheck (`python -m mypy --config-file mypy_self_check.ini -p mypy --enable-error-code ignore-without-code`) and also on my local clone of `flake8-pyi`, which has the `ignore-without-code` error code enabled as part of its default mypy configuration.


Discussion:
`--enable-error-code` also crashes with other error codes (but with different tracebacks). E.g. for `--enable-error-code no-untyped-call`, I get:

```
mypy pyi.py --enable-error-code no-untyped-call
Traceback (most recent call last):
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\alexw\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Users\alexw\coding\mypy\venv\Scripts\mypy.exe\__main__.py", line 7, in <module>
  File "C:\Users\alexw\coding\mypy\mypy\__main__.py", line 15, in console_entry
    main()
  File "C:\Users\alexw\coding\mypy\mypy\main.py", line 95, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "C:\Users\alexw\coding\mypy\mypy\main.py", line 174, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 186, in build
    result = _build(
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 269, in _build
    graph = dispatch(sources, manager, stdout)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 2875, in dispatch
    process_graph(graph, manager)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 3259, in process_graph
    process_stale_scc(graph, scc, manager)
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 3380, in process_stale_scc
    graph[id].write_cache()
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 2449, in write_cache
    new_interface_hash, self.meta = write_cache(
  File "C:\Users\alexw\coding\mypy\mypy\build.py", line 1631, in write_cache
    "options": options.select_options_affecting_cache(),
  File "C:\Users\alexw\coding\mypy\mypy\options.py", line 460, in select_options_affecting_cache
    val = sorted(val)
TypeError: '<' not supported between instances of 'ErrorCode' and 'ErrorCode'
```
Yeah, I was too lazy to add an incremental test. Need to convert those codes to strings when serializing 

Resolving patch:
diff --git a/mypy/options.py b/mypy/options.py
--- a/mypy/options.py
+++ b/mypy/options.py
@@ -456,7 +456,7 @@ def select_options_affecting_cache(self) -> Mapping[str, object]:
         result: Dict[str, object] = {}
         for opt in OPTIONS_AFFECTING_CACHE:
             val = getattr(self, opt)
-            if isinstance(val, set):
-                val = sorted(val)
+            if opt in ("disabled_error_codes", "enabled_error_codes"):
+                val = sorted([code.code for code in val])
             result[opt] = val
         return result


Test patch:
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -6012,3 +6012,13 @@ foo(name='Jennifer', age="38")
 [out]
 [out2]
 tmp/m.py:2: error: Argument "age" to "foo" has incompatible type "str"; expected "int"
+
+[case testDisableEnableErrorCodesIncremental]
+# flags: --disable-error-code truthy-bool
+# flags2: --enable-error-code truthy-bool
+def foo() -> int: ...
+if foo:
+    ...
+[out]
+[out2]
+main:4: error: Function "Callable[[], int]" could always be true in boolean context