Repository: python/mypy
Pull request: Report that `NamedTuple` and `dataclass` are incompatile instead of crashing.
Fixes #18527

The fix is pretty simple.  I could not find a situation where combining `NamedTuple` and `dataclass` makes sense, so emitting an error and just not applying the dataclass transformations seems sensible.


Problem statement:
Crash on namedtuple + @dataclass
<!--
  Use this form only if mypy reports an "INTERNAL ERROR" and/or gives a traceback.
  Please include the traceback and all other messages below (use `mypy --show-traceback`).
-->

**Crash Report**

running under python 3.13.1 results in the following crash when it does not crash under 3.12.8

**Traceback**

```
Traceback (most recent call last):
  File "<redacted>/.venv/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
             ~~~~~~~~~~~~~^^
  File "<redacted>/.venv/lib/python3.13/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
    ~~~~^^
  File "mypy/main.py", line 109, in main
  File "mypy/main.py", line 193, in run_build
  File "mypy/build.py", line 194, in build
  File "mypy/build.py", line 269, in _build
  File "mypy/build.py", line 2940, in dispatch
  File "mypy/build.py", line 3338, in process_graph
  File "mypy/build.py", line 3433, in process_stale_scc
  File "mypy/semanal_main.py", line 99, in semantic_analysis_for_scc
  File "mypy/semanal_main.py", line 443, in apply_class_plugin_hooks
  File "mypy/semanal_main.py", line 479, in apply_hooks_to_class
  File "mypy/plugins/dataclasses.py", line 966, in dataclass_class_maker_callback
  File "mypy/plugins/dataclasses.py", line 389, in transform
  File "mypy/plugins/dataclasses.py", line 409, in _add_dunder_replace
  File "mypy/plugins/common.py", line 238, in add_method_to_class
  File "mypy/plugins/common.py", line 315, in _prepare_class_namespace
ValueError: list.remove(x): x not in list
```

**To Reproduce**

`mypy <source dir>`

**Your Environment**

This error occurs on a large proprietary code base.  I have removed all custom configuration options and plugins normally specified in pyproject.toml, cleaned .mypy_cache dir and reproduced the error with a vanilla mypy invocation.  No crash when using python 3.12.8, but traceback above occurs when switching to python 3.13.1

- Mypy version used: 1.14.1
- Mypy command-line flags: none
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: tested successfully under python 3.12.8, fails under 3.13.1
- Operating system and version: OS X Sequoia

<!--
You can freely edit this text, please remove all the lines
you believe are unnecessary.
-->

Hints from issue discussion:
Thanks for the report @ericbuehl! Are you able to narrow the crash down to a particular file, ideally an example you can post here? Without it, this would be quite difficult to resolve.

For what it's worth, I know at least one large project which is also already running on `3.13.1` and hasn't encountered the crash yet.
@cdce8p I was able to narrow the crash down to a concise repro:

```
from dataclasses import dataclass
from typing import NamedTuple

@dataclass
class Imageable(NamedTuple):
    id: int
```

I'm not sure of the practical need for this dataclass+namedtuple but alas mypy probably shouldn't crash nonetheless
```python
from dataclasses import dataclass
from typing import NamedTuple

@dataclass
class Imageable(NamedTuple):
    id: int

Imageable(1)  # AttributeError: can't set attribute  (Python)
```

It seems that `dataclass` and `NamedTuple` are not really compatible, so besides not crashing Mypy reporting a warning would be nice.

Commit message: [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Patch to review:
diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py
index acb785aad70a..766e4b0ff0f4 100644
--- a/mypy/plugins/dataclasses.py
+++ b/mypy/plugins/dataclasses.py
@@ -965,6 +965,9 @@ def dataclass_tag_callback(ctx: ClassDefContext) -> None:
 
 def dataclass_class_maker_callback(ctx: ClassDefContext) -> bool:
     """Hooks into the class typechecking process to add support for dataclasses."""
+    if any(i.is_named_tuple for i in ctx.cls.info.mro):
+        ctx.api.fail("A NamedTuple cannot be a dataclass.", ctx=ctx.cls.info)
+        return True
     transformer = DataclassTransformer(
         ctx.cls, ctx.reason, _get_transform_spec(ctx.reason), ctx.api
     )
diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test
index 26c81812ab62..2c386a86c99a 100644
--- a/test-data/unit/check-dataclasses.test
+++ b/test-data/unit/check-dataclasses.test
@@ -2576,3 +2576,20 @@ reveal_type(m.b)  # N: Revealed type is "builtins.int"
 m.a = 1  # E: Cannot assign to final attribute "a"
 m.b = 2  # E: Cannot assign to final attribute "b"
 [builtins fixtures/tuple.pyi]
+
+[case testNoCrashForDataclassNamedTupleCombination]
+# flags: --python-version 3.13
+from dataclasses import dataclass
+from typing import NamedTuple
+
+@dataclass
+class A(NamedTuple):  # E: A NamedTuple cannot be a dataclass.
+    i: int
+
+class B1(NamedTuple):
+    i: int
+@dataclass
+class B2(B1):  # E: A NamedTuple cannot be a dataclass.
+    pass
+
+[builtins fixtures/tuple.pyi]


Code review comments:
diff_hunk: @@ -965,6 +965,9 @@ def dataclass_tag_callback(ctx: ClassDefContext) -> None:
 
 def dataclass_class_maker_callback(ctx: ClassDefContext) -> bool:
     """Hooks into the class typechecking process to add support for dataclasses."""
+    if any(i.is_named_tuple for i in ctx.cls.info.mro):
+        ctx.api.fail("A NamedTuple cannot be a dataclass.", ctx=ctx.cls.info)
path: mypy/plugins/dataclasses.py
text: @user1:
```suggestion
        ctx.api.fail("A NamedTuple cannot be a dataclass", ctx=ctx.cls.info)
```

small nitpick, everything else LGTM :)

@author:
Yes, you're right. Thanks!

diff_hunk: @@ -2576,3 +2576,20 @@ reveal_type(m.b)  # N: Revealed type is "builtins.int"
 m.a = 1  # E: Cannot assign to final attribute "a"
 m.b = 2  # E: Cannot assign to final attribute "b"
 [builtins fixtures/tuple.pyi]
+
+[case testNoCrashForDataclassNamedTupleCombination]
+# flags: --python-version 3.13
+from dataclasses import dataclass
+from typing import NamedTuple
+
+@dataclass
+class A(NamedTuple):  # E: A NamedTuple cannot be a dataclass.
path: test-data/unit/check-dataclasses.test
text: @user1:
```suggestion
class A(NamedTuple):  # E: A NamedTuple cannot be a dataclass
```

diff_hunk: @@ -2576,3 +2576,20 @@ reveal_type(m.b)  # N: Revealed type is "builtins.int"
 m.a = 1  # E: Cannot assign to final attribute "a"
 m.b = 2  # E: Cannot assign to final attribute "b"
 [builtins fixtures/tuple.pyi]
+
+[case testNoCrashForDataclassNamedTupleCombination]
+# flags: --python-version 3.13
+from dataclasses import dataclass
+from typing import NamedTuple
+
+@dataclass
+class A(NamedTuple):  # E: A NamedTuple cannot be a dataclass.
+    i: int
+
+class B1(NamedTuple):
+    i: int
+@dataclass
+class B2(B1):  # E: A NamedTuple cannot be a dataclass.
path: test-data/unit/check-dataclasses.test
text: @user1:
```suggestion
class B2(B1):  # E: A NamedTuple cannot be a dataclass
```