Fix init=false for transform and exclude fields on base transform

This commit is contained in:
Eric Masseran
2025-03-15 16:23:32 +01:00
parent e0797be681
commit 50778c390f
3 changed files with 38 additions and 27 deletions

View File

@@ -259,11 +259,12 @@ class ClassMixin:
for meta in self.get_metaclasses(): # type: ignore[attr-defined] for meta in self.get_metaclasses(): # type: ignore[attr-defined]
if ( if (
# Not sure if necessary # Not sure if necessary
isinstance(meta, DataclassWrapper) (isinstance(meta, DataclassWrapper) and meta.dataclass_init)
or ( or (
isinstance(meta, Decoratee) isinstance(meta, Decoratee)
# Internal leakage :| # Internal leakage :|
and isinstance(meta._wrapped_value, DataclassWrapper) and isinstance(meta._wrapped_value, DataclassWrapper)
and meta._wrapped_value.dataclass_init
) )
): ):
return True return True
@@ -276,31 +277,31 @@ class ClassMixin:
empty list. empty list.
""" """
param_names = [] param_names = []
is_dataclass_transform = False is_dataclass_transform_with_init = False
for cls in reversed(list(self.py__mro__())): for cls in reversed(list(self.py__mro__())):
if not is_dataclass_transform and ( if not is_dataclass_transform_with_init and (
# If dataclass_transform is applied to a class, dataclass-like semantics # If dataclass_transform is applied to a class, dataclass-like semantics
# will be assumed for any class that directly or indirectly derives from # will be assumed for any class that directly or indirectly derives from
# the decorated class or uses the decorated class as a metaclass. # the decorated class or uses the decorated class as a metaclass.
isinstance(cls, DataclassWrapper) (isinstance(cls, DataclassWrapper) and cls.dataclass_init)
or ( or (
# Some object like CompiledValues would not be compatible # Some object like CompiledValues would not be compatible
isinstance(cls, ClassMixin) isinstance(cls, ClassMixin)
and cls._has_dataclass_transform_metaclasses() and cls._has_dataclass_transform_metaclasses()
) )
): ):
is_dataclass_transform = True is_dataclass_transform_with_init = True
# Attributes on the decorated class and its base classes are not # Attributes on the decorated class and its base classes are not
# considered to be fields. # considered to be fields.
continue continue
# All inherited behave like dataclass # All inherited behave like dataclass
if is_dataclass_transform: if is_dataclass_transform_with_init:
param_names.extend( param_names.extend(
get_dataclass_param_names(cls) get_dataclass_param_names(cls)
) )
if is_dataclass_transform: if is_dataclass_transform_with_init:
return [DataclassSignature(cls, param_names)] return [DataclassSignature(cls, param_names)]
else: else:
[] []
@@ -468,13 +469,25 @@ class DataclassDecorator(ValueWrapper, FunctionMixin):
class DataclassWrapper(ValueWrapper, ClassMixin): class DataclassWrapper(ValueWrapper, ClassMixin):
def __init__(
self, wrapped_value, dataclass_init: bool, is_dataclass_transform: bool = False
):
super().__init__(wrapped_value)
self.dataclass_init = dataclass_init
self.is_dataclass_transform = is_dataclass_transform
def get_signatures(self): def get_signatures(self):
param_names = [] param_names = []
for cls in reversed(list(self.py__mro__())): for cls in reversed(list(self.py__mro__())):
if isinstance(cls, DataclassWrapper): if (
param_names.extend( isinstance(cls, DataclassWrapper)
get_dataclass_param_names(cls) and cls.dataclass_init
) # Attributes on the decorated class and its base classes are not
# considered to be fields.
and not cls.is_dataclass_transform
):
param_names.extend(get_dataclass_param_names(cls))
return [DataclassSignature(cls, param_names)] return [DataclassSignature(cls, param_names)]

View File

@@ -597,19 +597,26 @@ def _dataclass(value, arguments, callback):
""" """
for c in _follow_param(value.inference_state, arguments, 0): for c in _follow_param(value.inference_state, arguments, 0):
if c.is_class(): if c.is_class():
# Decorate the class # Decorate a class
dataclass_init = ( dataclass_init = (
# Customized decorator # Customized decorator, init may be disabled
not value.has_dataclass_init_false not value.has_dataclass_init_false
if isinstance(value, DataclassDecorator) if isinstance(value, DataclassDecorator)
# Bare dataclass decorator # Bare dataclass decorator, always with init
else True else True
) )
if dataclass_init: return ValueSet(
return ValueSet([DataclassWrapper(c)]) [
else: DataclassWrapper(
return ValueSet([c]) c,
dataclass_init,
is_dataclass_transform=value.name.string_name
== "dataclass_transform",
)
]
)
else: else:
# Decorator customization # Decorator customization
return ValueSet( return ValueSet(

View File

@@ -439,14 +439,6 @@ dataclass_transform_cases = [
y: int y: int
@dataclass_transform @dataclass_transform
class X(Y):'''), [], False], class X(Y):'''), [], False],
# Both classes
[dedent('''
@dataclass_transform
class Y():
y: int
z = 5
@dataclass_transform
class X(Y):'''), [], False],
# 2/ Declare dataclass transformed # 2/ Declare dataclass transformed
# Class based # Class based
[dedent(''' [dedent('''
@@ -502,7 +494,6 @@ ids = [
"direct_transformer", "direct_transformer",
"transformer_with_params", "transformer_with_params",
"subclass_transformer", "subclass_transformer",
"both_transformer",
"base_transformed", "base_transformed",
"decorator_transformed", "decorator_transformed",
"metaclass_transformed", "metaclass_transformed",