diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index e916ded01dd2..57c1ee410e8a 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -651,6 +651,13 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: elif not isinstance(stmt.rvalue, TempNode): has_default = True + if node.is_final and not is_in_init and not has_default: + has_post_init = cls.info.get("__post_init__") is not None + if not has_post_init: + self._api.fail( + "Final field with init=False must have a default value", 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 c7744e4a82a9..4bbccac78269 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2737,3 +2737,59 @@ class ClassB(ClassA): def value(self) -> int: return 0 [builtins fixtures/dict.pyi] + +[case testErrorFinalFieldNoInitNoArgumentPassed] +from typing import Final +from dataclasses import dataclass, field +@dataclass +class Foo: + a: Final[int] = field(init=False) # E: Final field with init=False must have a default value +Foo().a +[builtins fixtures/dataclasses.pyi] + +[case testErrorFinalFieldInitNoArgumentPassed] +from typing import Final +from dataclasses import dataclass, field + +@dataclass +class Foo: + a: Final[int] = field() + +Foo().a # E: Missing positional argument "a" in call to "Foo" +[builtins fixtures/dataclasses.pyi] + +[case testFinalFieldGeneratedInitArgumentPassed] +from typing import Final +from dataclasses import dataclass, field + +@dataclass +class Foo: + a: Final[int] = field() + +Foo(1).a +[builtins fixtures/dataclasses.pyi] + +[case testFinalFieldPostInit] +from typing import Final +from dataclasses import dataclass, field + +@dataclass +class Foo: + a: Final[int] = field(init=False) + + def __post_init__(self): + self.a = 1 + +Foo().a +[builtins fixtures/dataclasses.pyi] + +[case testFinalFieldInitFalseWithDefault] +from typing import Final +from dataclasses import dataclass, field + +@dataclass +class Foo: + a: Final[int] = field(init=False, default=1) + +Foo().a +[builtins fixtures/dataclasses.pyi]