23
23
Context ,
24
24
DataclassTransformSpec ,
25
25
Expression ,
26
+ FuncDef ,
26
27
JsonDict ,
27
28
NameExpr ,
28
29
Node ,
73
74
frozen_default = False ,
74
75
field_specifiers = ("dataclasses.Field" , "dataclasses.field" ),
75
76
)
77
+ _INTERNAL_REPLACE_METHOD = "__mypy_replace"
76
78
77
79
78
80
class DataclassAttribute :
@@ -325,6 +327,7 @@ def transform(self) -> bool:
325
327
add_attribute_to_class (self ._api , self ._cls , "__match_args__" , match_args_type )
326
328
327
329
self ._add_dataclass_fields_magic_attribute ()
330
+ self ._add_internal_replace_method (attributes )
328
331
329
332
info .metadata ["dataclass" ] = {
330
333
"attributes" : [attr .serialize () for attr in attributes ],
@@ -333,6 +336,30 @@ def transform(self) -> bool:
333
336
334
337
return True
335
338
339
+ def _add_internal_replace_method (self , attributes : list [DataclassAttribute ]) -> None :
340
+ arg_types = [Instance (self ._cls .info , [])]
341
+ arg_kinds = [ARG_POS ]
342
+ arg_names = [None ]
343
+ for attr in attributes :
344
+ arg_types .append (attr .type )
345
+ arg_names .append (attr .name )
346
+ arg_kinds .append (
347
+ ARG_NAMED_OPT if attr .has_default or not attr .is_init_var else ARG_NAMED
348
+ )
349
+
350
+ signature = CallableType (
351
+ arg_types = arg_types ,
352
+ arg_kinds = arg_kinds ,
353
+ arg_names = arg_names ,
354
+ ret_type = Instance (self ._cls .info , []),
355
+ fallback = self ._api .named_type ("builtins.function" ),
356
+ name = f"replace of { self ._cls .info .name } " ,
357
+ )
358
+
359
+ self ._cls .info .names [_INTERNAL_REPLACE_METHOD ] = SymbolTableNode (
360
+ kind = MDEF , node = FuncDef (typ = signature ), plugin_generated = True
361
+ )
362
+
336
363
def add_slots (
337
364
self , info : TypeInfo , attributes : list [DataclassAttribute ], * , correct_version : bool
338
365
) -> None :
@@ -797,36 +824,16 @@ def replace_function_sig_callback(ctx: FunctionSigContext) -> CallableType:
797
824
obj_type = get_proper_type (obj_type )
798
825
if not isinstance (obj_type , Instance ):
799
826
return ctx .default_signature
800
- inst_type_str = format_type_bare (obj_type )
801
827
802
- metadata = obj_type .type .metadata
803
- dataclass = metadata . get ( "dataclass" )
804
- if not dataclass :
828
+ repl = obj_type .type .get_method ( _INTERNAL_REPLACE_METHOD )
829
+ if repl is None :
830
+ inst_type_str = format_type_bare ( obj_type )
805
831
ctx .api .fail (
806
832
f'Argument 1 to "replace" has incompatible type "{ inst_type_str } "; expected a dataclass' ,
807
833
ctx .context ,
808
834
)
809
835
return ctx .default_signature
810
836
811
- arg_names = [None ]
812
- arg_kinds = [ARG_POS ]
813
- arg_types = [obj_type ]
814
- for attr in dataclass ["attributes" ]:
815
- if not attr ["is_in_init" ]:
816
- continue
817
- arg_names .append (attr ["name" ])
818
- arg_kinds .append (
819
- ARG_NAMED if not attr ["has_default" ] and attr ["is_init_var" ] else ARG_NAMED_OPT
820
- )
821
- arg_types .append (ctx .api .named_type (attr ["type" ]))
822
-
823
- return ctx .default_signature .copy_modified (
824
- arg_names = arg_names ,
825
- arg_kinds = arg_kinds ,
826
- arg_types = arg_types ,
827
- ret_type = obj_type ,
828
- name = f"{ ctx .default_signature .name } of { inst_type_str } " ,
829
- # prevent 'dataclasses.pyi:...: note: "replace" of "A" defined here' notes
830
- # since they are misleading: the definition is dynamic, not from a definition
831
- definition = None ,
832
- )
837
+ repl_type = repl .type
838
+ assert isinstance (repl_type , CallableType )
839
+ return repl_type
0 commit comments