Skip to content

Commit a850b66

Browse files
authored
Add support for Final[...] in dataclasses (#21334)
Fixes #5608 This is a surprisingly popular feature (and btw part of the typing spec). The idea is simple, since we already allow: ```python class C: x: Final[int] def __init__(self, x: int) -> None: self.x = x ``` we can manually set `final_set_in_init` for ```python @DataClass class C: x: Final[int] ``` because this is what the generated `__init__()` will do.
1 parent 273e8c8 commit a850b66

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

mypy/plugins/dataclasses.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,14 @@ def collect_attributes(self) -> list[DataclassAttribute] | None:
660660
else:
661661
self._api.fail('"kw_only" argument must be a boolean literal', stmt.rvalue)
662662

663+
# Allow bare x: Final[int] in class body, since it will be set in the generated
664+
# __init__() method (unless it is an InitVar), to match regular class semantics.
665+
if node.is_final and not node.is_classvar and node.final_unset_in_class:
666+
if is_init_var:
667+
self._api.fail("InitVar cannot be final", stmt.rvalue)
668+
else:
669+
node.final_set_in_init = True
670+
663671
if sym.type is None and node.is_final and node.is_inferred:
664672
# This is a special case, assignment like x: Final = 42 is classified
665673
# annotated above, but mypy strips the `Final` turning it into x = 42.

test-data/unit/check-dataclasses.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,3 +2698,30 @@ class ClassB(ClassA):
26982698
def value(self) -> int:
26992699
return 0
27002700
[builtins fixtures/dict.pyi]
2701+
2702+
[case testDataclassAllowsFinalField]
2703+
# flags: --python-version=3.13
2704+
from dataclasses import InitVar, dataclass
2705+
from typing import Final, ClassVar
2706+
2707+
@dataclass
2708+
class C:
2709+
x: int
2710+
y: Final[int]
2711+
z: ClassVar[Final[int]] = 4
2712+
2713+
c = C(1, 2)
2714+
c.y = 3 # E: Cannot assign to final attribute "y"
2715+
2716+
@dataclass
2717+
class D(C):
2718+
x: Final[int] # E: Cannot override writable attribute "x" with a final one
2719+
2720+
@dataclass
2721+
class Bad1:
2722+
x: Final[InitVar[int]] # E: InitVar cannot be final
2723+
2724+
@dataclass
2725+
class Bad2:
2726+
x: InitVar[Final[int]] # E: Final can be only used as an outermost qualifier in a variable annotation
2727+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)