From 5d4321f1132e6e02adbe05e9ef031dde07a47fbf Mon Sep 17 00:00:00 2001 From: 55 <555> Date: Mon, 21 Sep 2026 00:33:42 +0800 Subject: [PATCH] fix: ClassVar[Final[...]] collapses to Any instead of inner type (#21906) Nested Final inside ClassVar on Python 3.13+ accepts the annotation (no valid-type error) but the variable's type still collapsed to AnyType(from_error), silently losing type information. In try_analyze_special_unbound_type's FINAL_TYPE_NAMES branch, when self.allow_final is True (i.e. the nested Final is legitimately accepted in this context), preserve and return the inner type instead of falling through to AnyType(from_error). Regression guard: testFinalUsedWithClassVarAfterPy313PreservesType. --- mypy/typeanal.py | 17 ++++++++++------- test-data/unit/check-final.test | 12 ++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 8d500c54364ae..de3a4cf2da97e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -639,13 +639,16 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ t, code=codes.VALID_TYPE, ) - else: - if not self.allow_final: - self.fail( - "Final can be only used as an outermost qualifier in a variable annotation", - t, - code=codes.VALID_TYPE, - ) + elif not self.allow_final: + self.fail( + "Final can be only used as an outermost qualifier in a variable annotation", + t, + code=codes.VALID_TYPE, + ) + elif t.args: + # Nested Final[...] is accepted here (e.g. ClassVar[Final[...]] on 3.13+); + # preserve and return the inner type instead of collapsing to Any. + return self.anal_type(t.args[0]) return AnyType(TypeOfAny.from_error) elif fullname in TUPLE_NAMES: # Tuple is special because it is involved in builtin import cycle diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test index 8608962e00a42..b2ba8e0228a78 100644 --- a/test-data/unit/check-final.test +++ b/test-data/unit/check-final.test @@ -1134,6 +1134,18 @@ class A: b: ClassVar[Final[int]] = 1 c: ClassVar[Final] = 1 +[case testFinalUsedWithClassVarAfterPy313PreservesType] +# flags: --python-version 3.13 +# Nested Final inside ClassVar should preserve the inner type (#21906). +from typing import ClassVar, Final, reveal_type + +class B: pass + +class A: + cv: ClassVar[Final[dict[type[B], A]] = {} + +reveal_type(A.cv) # N: Revealed type is "builtins.dict[type[B], __main__.A" + [case testFinalClassWithAbstractMethod] from typing import final from abc import ABC, abstractmethod