From df2a1e123ef17536825f7e94092929b9aeeb0c04 Mon Sep 17 00:00:00 2001 From: Dipanshu Ambilkar Date: Thu, 29 Oct 2026 00:01:13 +0530 Subject: [PATCH 1/2] Report final dataclass fields that are never initialized (#13119) A `Final` dataclass attribute can only be assigned inside `__init__()`. If the field is declared with `field(init=False)` and has neither a `default` nor a `default_factory`, nothing ever sets it, so reading the attribute fails with `AttributeError` at runtime. The `field(...)` call counted as an initializer, so the "Final name must be initialized with a value" check was skipped. Report the field instead when it is final, excluded from the generated `__init__()` and has no default value. --- mypy/plugins/dataclasses.py | 15 +++++++++++++++ test-data/unit/check-dataclasses.test | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 7a6a3876d9bfd..a97a7fdfafba0 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -654,6 +654,21 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: elif not isinstance(stmt.rvalue, TempNode): has_default = True + if ( + node.is_final + and has_field_call + and not is_in_init + and not has_default + and not is_init_var + and self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES + ): + # A final attribute can only be assigned in __init__(), so if it is + # excluded from __init__() and has no default value, it can never be set. + self._api.fail( + "Final field must have a default value if init=False", + stmt.rvalue, + ) + if not has_default and self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: # Make all non-default dataclass attributes implicit because they are de-facto # set on self in the generated __init__(), not in the class body. On the other diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index d269ad2bad0ec..0549f1d3e0a04 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2952,6 +2952,27 @@ class Bad2: x: InitVar[Final[int]] # E: Final can be only used as an outermost qualifier in a variable annotation [builtins fixtures/dict.pyi] +[case testDataclassFinalFieldNotInInit] +from dataclasses import dataclass, field +from typing import Final + +@dataclass +class Bad: + x: Final[int] = field(init=False) # E: Final field must have a default value if init=False + +@dataclass +class GoodDefault: + x: Final[int] = field(init=False, default=0) + +@dataclass +class GoodFactory: + x: Final[int] = field(init=False, default_factory=int) + +@dataclass +class GoodPlain: + x: Final[int] = 0 +[builtins fixtures/tuple.pyi] + [case testNoCrashOnNestedSpecialFormsWithAnnotations] import dataclasses from typing import ClassVar, NamedTuple From b885572e9aad2accd8ba6eeb4578d3ed793d8a0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:33:18 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/plugins/dataclasses.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index a97a7fdfafba0..1325f193bd1f2 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -664,10 +664,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: ): # A final attribute can only be assigned in __init__(), so if it is # excluded from __init__() and has no default value, it can never be set. - self._api.fail( - "Final field must have a default value if init=False", - stmt.rvalue, - ) + self._api.fail("Final field must have a default value if init=False", stmt.rvalue) if not has_default and self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: # Make all non-default dataclass attributes implicit because they are de-facto