Skip to content

Commit ae21d46

Browse files
committed
fix: allow Final[T] instance fields in dataclasses to depend on type variables
PEP 591 and the dataclass spec say that a Final field declared in a dataclass body is an *instance attribute*, not a class attribute, unless explicitly annotated with ClassVar. The checker was unconditionally raising 'Final name declared in class body cannot depend on type variables' for any Final[T] in a generic class body, which is correct for plain classes but wrong for dataclasses. fix: when the active class has 'dataclass_tag' in its metadata and the lvalue is a Var that is not marked is_classvar, skip the error. non-dataclass classes and explicit ClassVar fields are unaffected. fixes #21637
1 parent 3179030 commit ae21d46

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

mypy/checker.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3398,9 +3398,21 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
33983398
s.is_final_def
33993399
and s.type
34003400
and not has_no_typevars(s.type)
3401-
and self.scope.active_class() is not None
3401+
and (active_class := self.scope.active_class()) is not None
34023402
):
3403-
self.fail(message_registry.DEPENDENT_FINAL_IN_CLASS_BODY, s)
3403+
# Final[T] instance fields in dataclasses are not class attributes, so
3404+
# they are allowed to depend on type variables (PEP 591 / dataclass spec).
3405+
# Only suppress the error when the field is a dataclass instance field
3406+
# (not explicitly ClassVar).
3407+
lv = s.lvalues[0] if s.lvalues else None
3408+
is_dataclass_instance_field = (
3409+
"dataclass_tag" in active_class.metadata
3410+
and isinstance(lv, RefExpr)
3411+
and isinstance(lv.node, Var)
3412+
and not lv.node.is_classvar
3413+
)
3414+
if not is_dataclass_instance_field:
3415+
self.fail(message_registry.DEPENDENT_FINAL_IN_CLASS_BODY, s)
34043416

34053417
if s.unanalyzed_type and not self.in_checked_function():
34063418
self.msg.annotation_in_unchecked_function(context=s)

test-data/unit/check-final.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,3 +1322,35 @@ class S9(S2, S4): # E: Class "S9" has incompatible disjoint bases
13221322
pass
13231323

13241324
[builtins fixtures/tuple.pyi]
1325+
1326+
[case testFinalDefiningTypevarsDataclassInstanceField]
1327+
# Regression test for https://github.com/python/mypy/issues/21637
1328+
# Final[T] instance fields in dataclasses must NOT trigger the
1329+
# "cannot depend on type variables" error, because they are instance
1330+
# attributes, not class attributes.
1331+
from dataclasses import dataclass
1332+
from typing import Final, Generic, TypeVar
1333+
1334+
T = TypeVar("T")
1335+
1336+
@dataclass(frozen=True)
1337+
class A(Generic[T]):
1338+
value: Final[T] # OK — instance field in a dataclass
1339+
1340+
@dataclass(frozen=True)
1341+
class B(Generic[T]):
1342+
value: T # OK — control case without Final
1343+
[builtins fixtures/dataclasses.pyi]
1344+
[typing fixtures/typing-medium.pyi]
1345+
1346+
[case testFinalDefiningTypevarsNonDataclassStillErrors]
1347+
# Final[T] in a plain (non-dataclass) generic class body must still raise the error.
1348+
from typing import Any, Final, Generic, TypeVar
1349+
1350+
T = TypeVar("T")
1351+
d: Any
1352+
1353+
class C(Generic[T]):
1354+
x: Final[T] = d # E: Final name declared in class body cannot depend on type variables
1355+
[builtins fixtures/dataclasses.pyi]
1356+
[typing fixtures/typing-medium.pyi]

0 commit comments

Comments
 (0)