Skip to content

Commit 99b04ca

Browse files
authored
[dataclass_transform] support subclass/metaclass-based transforms (#14657)
Support dataclass_transforms that use inheritance or metaclasses rather than decorators. This only needs plumbing changes so that we can get the correct metadata for a given class and trigger the dataclasses transform plugin; logic should otherwise remain the same. The code changes here are a little invasive because of how the dataclasses plugin handles it's "reason" (ie, the AST node that triggered the plugin). Currently it takes a `ClassDefContext` where `reason: Expression`, but in the case of inheritance/metaclass-based transforms, it makes more sense for the class definition itself to be the reason (since the parent class and keyword args are supplied in the class definition itself). To accommodate for this, I refactored the `DataclassTransformer` class to take a `reason: Expression | Statement` while leaving the plugin API itself alone. This mostly involved updating the identifiers used throughout the class.
1 parent 4261e51 commit 99b04ca

File tree

7 files changed

+241
-65
lines changed

7 files changed

+241
-65
lines changed

Diff for: mypy/nodes.py

+14
Original file line numberDiff line numberDiff line change
@@ -2830,6 +2830,7 @@ class is generic then it will be a type constructor of higher kind.
28302830
"type_var_tuple_prefix",
28312831
"type_var_tuple_suffix",
28322832
"self_type",
2833+
"dataclass_transform_spec",
28332834
)
28342835

28352836
_fullname: str # Fully qualified name
@@ -2977,6 +2978,9 @@ class is generic then it will be a type constructor of higher kind.
29772978
# Shared type variable for typing.Self in this class (if used, otherwise None).
29782979
self_type: mypy.types.TypeVarType | None
29792980

2981+
# Added if the corresponding class is directly decorated with `typing.dataclass_transform`
2982+
dataclass_transform_spec: DataclassTransformSpec | None
2983+
29802984
FLAGS: Final = [
29812985
"is_abstract",
29822986
"is_enum",
@@ -3032,6 +3036,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
30323036
self.is_intersection = False
30333037
self.metadata = {}
30343038
self.self_type = None
3039+
self.dataclass_transform_spec = None
30353040

30363041
def add_type_vars(self) -> None:
30373042
self.has_type_var_tuple_type = False
@@ -3251,6 +3256,11 @@ def serialize(self) -> JsonDict:
32513256
"slots": list(sorted(self.slots)) if self.slots is not None else None,
32523257
"deletable_attributes": self.deletable_attributes,
32533258
"self_type": self.self_type.serialize() if self.self_type is not None else None,
3259+
"dataclass_transform_spec": (
3260+
self.dataclass_transform_spec.serialize()
3261+
if self.dataclass_transform_spec is not None
3262+
else None
3263+
),
32543264
}
32553265
return data
32563266

@@ -3314,6 +3324,10 @@ def deserialize(cls, data: JsonDict) -> TypeInfo:
33143324
set_flags(ti, data["flags"])
33153325
st = data["self_type"]
33163326
ti.self_type = mypy.types.TypeVarType.deserialize(st) if st is not None else None
3327+
if data.get("dataclass_transform_spec") is not None:
3328+
ti.dataclass_transform_spec = DataclassTransformSpec.deserialize(
3329+
data["dataclass_transform_spec"]
3330+
)
33173331
return ti
33183332

33193333

0 commit comments

Comments
 (0)