Skip to content

Commit 8e8c129

Browse files
committed
add the module path to the mangled name of an internal dataclass symbol
1 parent b2a63ad commit 8e8c129

File tree

3 files changed

+40
-12
lines changed

3 files changed

+40
-12
lines changed

mypy/plugins/dataclasses.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@
9191
frozen_default=False,
9292
field_specifiers=DATACLASS_FIELD_SPECIFIERS,
9393
)
94-
_INTERNAL_REPLACE_SYM_NAME: Final = "__mypy-replace"
95-
_INTERNAL_POST_INIT_SYM_NAME: Final = "__mypy-post_init"
94+
_INTERNAL_REPLACE_SYM_NAME: Final = "mypy-replace"
95+
_INTERNAL_POST_INIT_SYM_NAME: Final = "mypy-post_init"
9696

9797

9898
class DataclassAttribute:
@@ -422,7 +422,7 @@ def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) ->
422422
Stashes the signature of 'dataclasses.replace(...)' for this specific dataclass
423423
to be used later whenever 'dataclasses.replace' is called for this dataclass.
424424
"""
425-
mangled_name = f"_{self._cls.name.lstrip('_')}{_INTERNAL_REPLACE_SYM_NAME}"
425+
mangled_name = _mangle_internal_sym_name(self._cls.fullname, _INTERNAL_REPLACE_SYM_NAME)
426426
add_method_to_class(
427427
self._api,
428428
self._cls,
@@ -433,7 +433,7 @@ def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) ->
433433
)
434434

435435
def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -> None:
436-
mangled_name = f"_{self._cls.name.lstrip('_')}{_INTERNAL_POST_INIT_SYM_NAME}"
436+
mangled_name = _mangle_internal_sym_name(self._cls.fullname, _INTERNAL_POST_INIT_SYM_NAME)
437437
add_method_to_class(
438438
self._api,
439439
self._cls,
@@ -971,6 +971,15 @@ def dataclass_class_maker_callback(ctx: ClassDefContext) -> bool:
971971
return transformer.transform()
972972

973973

974+
def _mangle_internal_sym_name(type_fullname: str, member_name: str) -> str:
975+
"""Create an internal symbol name with the class name mangled in as usual, but that also
976+
contains the class module path to avoid false positives when subclassing a dataclass with
977+
same name."""
978+
module_name, type_name = type_fullname.rsplit(".")
979+
module_name = module_name.replace(".", "-")
980+
type_name = type_name.lstrip('_')
981+
return f"_{type_name}__{module_name}__{member_name}"
982+
974983
def _get_transform_spec(reason: Expression) -> DataclassTransformSpec:
975984
"""Find the relevant transform parameters from the decorator/parent class/metaclass that
976985
triggered the dataclasses plugin.
@@ -1029,7 +1038,7 @@ def _get_expanded_dataclasses_fields(
10291038
ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ
10301039
)
10311040
elif isinstance(typ, Instance):
1032-
mangled_name = f"_{typ.type.name.lstrip('_')}{_INTERNAL_REPLACE_SYM_NAME}"
1041+
mangled_name = _mangle_internal_sym_name(typ.type.fullname, _INTERNAL_REPLACE_SYM_NAME)
10331042
replace_sym = typ.type.get_method(mangled_name)
10341043
if replace_sym is None:
10351044
return None
@@ -1114,7 +1123,7 @@ def check_post_init(api: TypeChecker, defn: FuncItem, info: TypeInfo) -> None:
11141123
return
11151124
assert isinstance(defn.type, FunctionLike)
11161125

1117-
mangled_name = f"_{info.name.lstrip('_')}{_INTERNAL_POST_INIT_SYM_NAME}"
1126+
mangled_name = _mangle_internal_sym_name(info.fullname, _INTERNAL_POST_INIT_SYM_NAME)
11181127
ideal_sig_method = info.get_method(mangled_name)
11191128
assert ideal_sig_method is not None and ideal_sig_method.type is not None
11201129
ideal_sig = ideal_sig_method.type

test-data/unit/check-dataclasses.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,25 @@ reveal_type(d.foo) # N: Revealed type is "builtins.str"
226226
reveal_type(d.bar) # N: Revealed type is "builtins.int"
227227
[builtins fixtures/dataclasses.pyi]
228228

229+
[case testDataclassCompatibleOverrideEqualName]
230+
from dataclasses import dataclass, InitVar
231+
import m
232+
233+
@dataclass
234+
class D(m.D):
235+
x: InitVar[int]
236+
237+
[file m.py]
238+
from dataclasses import dataclass
239+
240+
@dataclass
241+
class D:
242+
y: int
243+
244+
[out]
245+
246+
[builtins fixtures/dataclasses.pyi]
247+
229248
[case testDataclassIncompatibleFrozenOverride]
230249
from dataclasses import dataclass
231250

test-data/unit/deps.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,16 +1389,16 @@ class B(A):
13891389

13901390
[out]
13911391
<m.A.(abstract)> -> <m.B.__init__>, m
1392-
<m.A._A__mypy-replace> -> <m.B._A__mypy-replace>, m
1393-
<m.A._B__mypy-replace> -> m.B._B__mypy-replace
1392+
<m.A._A__m__mypy-replace> -> <m.B._A__m__mypy-replace>, m
1393+
<m.A._B__m__mypy-replace> -> m.B._B__m__mypy-replace
13941394
<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
13951395
<m.A.__init__> -> <m.B.__init__>, m.B.__init__
13961396
<m.A.__new__> -> <m.B.__new__>
13971397
<m.A.x> -> <m.B.x>
13981398
<m.A.y> -> <m.B.y>
13991399
<m.A> -> m, m.A, m.B
14001400
<m.A[wildcard]> -> m
1401-
<m.B._B__mypy-replace> -> m
1401+
<m.B._B__m__mypy-replace> -> m
14021402
<m.B.y> -> m
14031403
<m.B> -> m.B
14041404
<m.Z> -> m
@@ -1422,8 +1422,8 @@ class B(A):
14221422

14231423
[out]
14241424
<m.A.(abstract)> -> <m.B.__init__>, m
1425-
<m.A._A__mypy-replace> -> <m.B._A__mypy-replace>, m
1426-
<m.A._B__mypy-replace> -> m.B._B__mypy-replace
1425+
<m.A._A__m__mypy-replace> -> <m.B._A__m__mypy-replace>, m
1426+
<m.A._B__m__mypy-replace> -> m.B._B__m__mypy-replace
14271427
<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
14281428
<m.A.__init__> -> <m.B.__init__>, m.B.__init__
14291429
<m.A.__match_args__> -> <m.B.__match_args__>
@@ -1432,7 +1432,7 @@ class B(A):
14321432
<m.A.y> -> <m.B.y>
14331433
<m.A> -> m, m.A, m.B
14341434
<m.A[wildcard]> -> m
1435-
<m.B._B__mypy-replace> -> m
1435+
<m.B._B__m__mypy-replace> -> m
14361436
<m.B.y> -> m
14371437
<m.B> -> m.B
14381438
<m.Z> -> m

0 commit comments

Comments
 (0)