diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index cb87378e54263..c310e47fd3dd6 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -660,6 +660,14 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: else: self._api.fail('"kw_only" argument must be a boolean literal', stmt.rvalue) + # Allow bare x: Final[int] in class body, since it will be set in the generated + # __init__() method (unless it is an InitVar), to match regular class semantics. + if node.is_final and not node.is_classvar and node.final_unset_in_class: + if is_init_var: + self._api.fail("InitVar cannot be final", stmt.rvalue) + else: + node.final_set_in_init = True + if sym.type is None and node.is_final and node.is_inferred: # This is a special case, assignment like x: Final = 42 is classified # annotated above, but mypy strips the `Final` turning it into x = 42. diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index f853271117456..762585806b85d 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2698,3 +2698,30 @@ class ClassB(ClassA): def value(self) -> int: return 0 [builtins fixtures/dict.pyi] + +[case testDataclassAllowsFinalField] +# flags: --python-version=3.13 +from dataclasses import InitVar, dataclass +from typing import Final, ClassVar + +@dataclass +class C: + x: int + y: Final[int] + z: ClassVar[Final[int]] = 4 + +c = C(1, 2) +c.y = 3 # E: Cannot assign to final attribute "y" + +@dataclass +class D(C): + x: Final[int] # E: Cannot override writable attribute "x" with a final one + +@dataclass +class Bad1: + x: Final[InitVar[int]] # E: InitVar cannot be final + +@dataclass +class Bad2: + x: InitVar[Final[int]] # E: Final can be only used as an outermost qualifier in a variable annotation +[builtins fixtures/dict.pyi]