Skip to content

Commit 80e5e8b

Browse files
aliwotriumph1hauntsaninja
authored
Allow to use Final and ClassVar after Python 3.13 (#18358)
This PR allows to use Final and ClassVar after python 3.13 I saw this [PR](#10478) and I saw recent changes of python 3.13 https://docs.python.org/3/library/typing.html#typing.Final Final now can be nested with ClassVar. so I added a version check! --------- Co-authored-by: triumph1 <seungwon.jeong@wesang.com> Co-authored-by: hauntsaninja <hauntsaninja@gmail.com>
1 parent 9e40be6 commit 80e5e8b

File tree

3 files changed

+32
-7
lines changed

3 files changed

+32
-7
lines changed

mypy/semanal.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3658,7 +3658,11 @@ def unwrap_final(self, s: AssignmentStmt) -> bool:
36583658
else:
36593659
s.type = s.unanalyzed_type.args[0]
36603660

3661-
if s.type is not None and self.is_classvar(s.type):
3661+
if (
3662+
s.type is not None
3663+
and self.options.python_version < (3, 13)
3664+
and self.is_classvar(s.type)
3665+
):
36623666
self.fail("Variable should not be annotated with both ClassVar and Final", s)
36633667
return False
36643668

@@ -7358,6 +7362,7 @@ def type_analyzer(
73587362
allow_unbound_tvars: bool = False,
73597363
allow_placeholder: bool = False,
73607364
allow_typed_dict_special_forms: bool = False,
7365+
allow_final: bool = False,
73617366
allow_param_spec_literals: bool = False,
73627367
allow_unpack: bool = False,
73637368
report_invalid_types: bool = True,
@@ -7379,6 +7384,7 @@ def type_analyzer(
73797384
report_invalid_types=report_invalid_types,
73807385
allow_placeholder=allow_placeholder,
73817386
allow_typed_dict_special_forms=allow_typed_dict_special_forms,
7387+
allow_final=allow_final,
73827388
allow_param_spec_literals=allow_param_spec_literals,
73837389
allow_unpack=allow_unpack,
73847390
prohibit_self_type=prohibit_self_type,
@@ -7403,6 +7409,7 @@ def anal_type(
74037409
allow_unbound_tvars: bool = False,
74047410
allow_placeholder: bool = False,
74057411
allow_typed_dict_special_forms: bool = False,
7412+
allow_final: bool = False,
74067413
allow_param_spec_literals: bool = False,
74077414
allow_unpack: bool = False,
74087415
report_invalid_types: bool = True,
@@ -7439,6 +7446,7 @@ def anal_type(
74397446
allow_tuple_literal=allow_tuple_literal,
74407447
allow_placeholder=allow_placeholder,
74417448
allow_typed_dict_special_forms=allow_typed_dict_special_forms,
7449+
allow_final=allow_final,
74427450
allow_param_spec_literals=allow_param_spec_literals,
74437451
allow_unpack=allow_unpack,
74447452
report_invalid_types=report_invalid_types,

mypy/typeanal.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def __init__(
226226
allow_unbound_tvars: bool = False,
227227
allow_placeholder: bool = False,
228228
allow_typed_dict_special_forms: bool = False,
229+
allow_final: bool = True,
229230
allow_param_spec_literals: bool = False,
230231
allow_unpack: bool = False,
231232
report_invalid_types: bool = True,
@@ -261,6 +262,8 @@ def __init__(
261262
self.allow_placeholder = allow_placeholder
262263
# Are we in a context where Required[] is allowed?
263264
self.allow_typed_dict_special_forms = allow_typed_dict_special_forms
265+
# Set True when we analyze ClassVar else False
266+
self.allow_final = allow_final
264267
# Are we in a context where ParamSpec literals are allowed?
265268
self.allow_param_spec_literals = allow_param_spec_literals
266269
# Are we in context where literal "..." specifically is allowed?
@@ -607,11 +610,12 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ
607610
code=codes.VALID_TYPE,
608611
)
609612
else:
610-
self.fail(
611-
"Final can be only used as an outermost qualifier in a variable annotation",
612-
t,
613-
code=codes.VALID_TYPE,
614-
)
613+
if not self.allow_final:
614+
self.fail(
615+
"Final can be only used as an outermost qualifier in a variable annotation",
616+
t,
617+
code=codes.VALID_TYPE,
618+
)
615619
return AnyType(TypeOfAny.from_error)
616620
elif fullname == "typing.Tuple" or (
617621
fullname == "builtins.tuple"
@@ -692,7 +696,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ
692696
"ClassVar[...] must have at most one type argument", t, code=codes.VALID_TYPE
693697
)
694698
return AnyType(TypeOfAny.from_error)
695-
return self.anal_type(t.args[0])
699+
return self.anal_type(t.args[0], allow_final=self.options.python_version >= (3, 13))
696700
elif fullname in NEVER_NAMES:
697701
return UninhabitedType()
698702
elif fullname in LITERAL_TYPE_NAMES:
@@ -1878,11 +1882,13 @@ def anal_type(
18781882
allow_unpack: bool = False,
18791883
allow_ellipsis: bool = False,
18801884
allow_typed_dict_special_forms: bool = False,
1885+
allow_final: bool = False,
18811886
) -> Type:
18821887
if nested:
18831888
self.nesting_level += 1
18841889
old_allow_typed_dict_special_forms = self.allow_typed_dict_special_forms
18851890
self.allow_typed_dict_special_forms = allow_typed_dict_special_forms
1891+
self.allow_final = allow_final
18861892
old_allow_ellipsis = self.allow_ellipsis
18871893
self.allow_ellipsis = allow_ellipsis
18881894
old_allow_unpack = self.allow_unpack

test-data/unit/check-final.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def g(x: int) -> Final[int]: ... # E: Final can be only used as an outermost qu
194194
[out]
195195

196196
[case testFinalDefiningNotInMethodExtensions]
197+
# flags: --python-version 3.14
197198
from typing_extensions import Final
198199

199200
def f(x: Final[int]) -> int: ... # E: Final can be only used as an outermost qualifier in a variable annotation
@@ -1128,6 +1129,7 @@ class A:
11281129
[builtins fixtures/tuple.pyi]
11291130

11301131
[case testFinalUsedWithClassVar]
1132+
# flags: --python-version 3.12
11311133
from typing import Final, ClassVar
11321134

11331135
class A:
@@ -1136,6 +1138,15 @@ class A:
11361138
c: ClassVar[Final] = 1 # E: Final can be only used as an outermost qualifier in a variable annotation
11371139
[out]
11381140

1141+
[case testFinalUsedWithClassVarAfterPy313]
1142+
# flags: --python-version 3.13
1143+
from typing import Final, ClassVar
1144+
1145+
class A:
1146+
a: Final[ClassVar[int]] = 1
1147+
b: ClassVar[Final[int]] = 1
1148+
c: ClassVar[Final] = 1
1149+
11391150
[case testFinalClassWithAbstractMethod]
11401151
from typing import final
11411152
from abc import ABC, abstractmethod

0 commit comments

Comments
 (0)