Skip to content

Commit 92e2f76

Browse files
committed
fix: string annotations ClassVar bug
1 parent 52a7a22 commit 92e2f76

File tree

5 files changed

+104
-18
lines changed

5 files changed

+104
-18
lines changed

Lib/dataclasses.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -753,21 +753,33 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
753753
# that's defined. It was judged not worth it.
754754

755755
match = _MODULE_IDENTIFIER_RE.match(annotation)
756-
if match:
757-
ns = None
758-
module_name = match.group(1)
759-
if not module_name:
760-
# No module name, assume the class's module did
761-
# "from dataclasses import InitVar".
762-
ns = sys.modules.get(cls.__module__).__dict__
763-
else:
764-
# Look up module_name in the class's module.
765-
module = sys.modules.get(cls.__module__)
766-
if module and module.__dict__.get(module_name) is a_module:
767-
ns = sys.modules.get(a_type.__module__).__dict__
768-
if ns and is_type_predicate(ns.get(match.group(2)), a_module):
769-
return True
770-
return False
756+
if not match:
757+
return False
758+
759+
ns = None
760+
module_name = match.group(1)
761+
type_name = match.group(2)
762+
763+
if not module_name:
764+
# No module name, assume the class's module did
765+
# "from dataclasses import InitVar".
766+
ns = sys.modules.get(cls.__module__).__dict__
767+
else:
768+
# Look up module_name in the class's module.
769+
cls_module = sys.modules.get(cls.__module__)
770+
if not cls_module:
771+
return False
772+
773+
a_type_module = cls_module.__dict__.get(module_name)
774+
if (
775+
isinstance(a_type_module, types.ModuleType)
776+
# Consider the case when a_type does not belong
777+
# to the namespace, e.g. 'dataclasses.ClassVar[int]'
778+
and a_type_module.__dict__.get(type_name) is a_type
779+
):
780+
ns = sys.modules.get(a_type.__module__).__dict__
781+
782+
return ns and is_type_predicate(ns.get(type_name), a_module)
771783

772784

773785
def _get_field(cls, a_name, a_type, default_kw_only):

Lib/test/test_dataclasses/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,10 +4093,14 @@ def test_classvar_module_level_import(self):
40934093
from test.test_dataclasses import dataclass_module_1_str
40944094
from test.test_dataclasses import dataclass_module_2
40954095
from test.test_dataclasses import dataclass_module_2_str
4096+
from test.test_dataclasses import dataclass_module_3
4097+
from test.test_dataclasses import dataclass_module_3_str
40964098

4097-
for m in (dataclass_module_1, dataclass_module_1_str,
4098-
dataclass_module_2, dataclass_module_2_str,
4099-
):
4099+
for m in (
4100+
dataclass_module_1, dataclass_module_1_str,
4101+
dataclass_module_2, dataclass_module_2_str,
4102+
dataclass_module_3, dataclass_module_3_str,
4103+
):
41004104
with self.subTest(m=m):
41014105
# There's a difference in how the ClassVars are
41024106
# interpreted when using string annotations or
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# We need this to test a case when a type
2+
# is imported via some other package,
3+
# like ClassVar from typing_extensions instead of typing.
4+
# https://github.com/python/cpython/issues/133956
5+
from typing import ClassVar
6+
from dataclasses import InitVar
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#from __future__ import annotations
2+
USING_STRINGS = False
3+
4+
# dataclass_module_3.py and dataclass_module_3_str.py are identical
5+
# except only the latter uses string annotations.
6+
7+
from dataclasses import dataclass
8+
import test.test_dataclasses._types_proxy as tp
9+
10+
T_CV2 = tp.ClassVar[int]
11+
T_CV3 = tp.ClassVar
12+
13+
T_IV2 = tp.InitVar[int]
14+
T_IV3 = tp.InitVar
15+
16+
@dataclass
17+
class CV:
18+
T_CV4 = tp.ClassVar
19+
cv0: tp.ClassVar[int] = 20
20+
cv1: tp.ClassVar = 30
21+
cv2: T_CV2
22+
cv3: T_CV3
23+
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
24+
25+
@dataclass
26+
class IV:
27+
T_IV4 = tp.InitVar
28+
iv0: tp.InitVar[int]
29+
iv1: tp.InitVar
30+
iv2: T_IV2
31+
iv3: T_IV3
32+
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from __future__ import annotations
2+
USING_STRINGS = True
3+
4+
# dataclass_module_3.py and dataclass_module_2_str.py are identical
5+
# except only the latter uses string annotations.
6+
7+
from dataclasses import dataclass
8+
import test.test_dataclasses._types_proxy as tp
9+
10+
T_CV2 = tp.ClassVar[int]
11+
T_CV3 = tp.ClassVar
12+
13+
T_IV2 = tp.InitVar[int]
14+
T_IV3 = tp.InitVar
15+
16+
@dataclass
17+
class CV:
18+
T_CV4 = tp.ClassVar
19+
cv0: tp.ClassVar[int] = 20
20+
cv1: tp.ClassVar = 30
21+
cv2: T_CV2
22+
cv3: T_CV3
23+
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
24+
25+
@dataclass
26+
class IV:
27+
T_IV4 = tp.InitVar
28+
iv0: tp.InitVar[int]
29+
iv1: tp.InitVar
30+
iv2: T_IV2
31+
iv3: T_IV3
32+
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

0 commit comments

Comments
 (0)