From ae21d462ca452a79c4e9ea8eec73e92db54015fa Mon Sep 17 00:00:00 2001 From: adhavan Date: Wed, 24 Jun 2026 15:51:58 +0530 Subject: [PATCH] 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 --- mypy/checker.py | 16 ++++++++++++++-- test-data/unit/check-final.test | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 33705c98e10c3..58f1feb8dd9c0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3398,9 +3398,21 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: s.is_final_def and s.type and not has_no_typevars(s.type) - and self.scope.active_class() is not None + and (active_class := self.scope.active_class()) is not None ): - self.fail(message_registry.DEPENDENT_FINAL_IN_CLASS_BODY, s) + # Final[T] instance fields in dataclasses are not class attributes, so + # they are allowed to depend on type variables (PEP 591 / dataclass spec). + # Only suppress the error when the field is a dataclass instance field + # (not explicitly ClassVar). + lv = s.lvalues[0] if s.lvalues else None + is_dataclass_instance_field = ( + "dataclass_tag" in active_class.metadata + and isinstance(lv, RefExpr) + and isinstance(lv.node, Var) + and not lv.node.is_classvar + ) + if not is_dataclass_instance_field: + self.fail(message_registry.DEPENDENT_FINAL_IN_CLASS_BODY, s) if s.unanalyzed_type and not self.in_checked_function(): self.msg.annotation_in_unchecked_function(context=s) diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test index 8608962e00a42..86e50bff38920 100644 --- a/test-data/unit/check-final.test +++ b/test-data/unit/check-final.test @@ -1322,3 +1322,35 @@ class S9(S2, S4): # E: Class "S9" has incompatible disjoint bases pass [builtins fixtures/tuple.pyi] + +[case testFinalDefiningTypevarsDataclassInstanceField] +# Regression test for https://github.com/python/mypy/issues/21637 +# Final[T] instance fields in dataclasses must NOT trigger the +# "cannot depend on type variables" error, because they are instance +# attributes, not class attributes. +from dataclasses import dataclass +from typing import Final, Generic, TypeVar + +T = TypeVar("T") + +@dataclass(frozen=True) +class A(Generic[T]): + value: Final[T] # OK — instance field in a dataclass + +@dataclass(frozen=True) +class B(Generic[T]): + value: T # OK — control case without Final +[builtins fixtures/dataclasses.pyi] +[typing fixtures/typing-medium.pyi] + +[case testFinalDefiningTypevarsNonDataclassStillErrors] +# Final[T] in a plain (non-dataclass) generic class body must still raise the error. +from typing import Any, Final, Generic, TypeVar + +T = TypeVar("T") +d: Any + +class C(Generic[T]): + x: Final[T] = d # E: Final name declared in class body cannot depend on type variables +[builtins fixtures/dataclasses.pyi] +[typing fixtures/typing-medium.pyi]