summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorda-woods <dw-git@d-woods.co.uk>2022-07-29 18:58:31 +0100
committerGitHub <noreply@github.com>2022-07-29 19:58:31 +0200
commit9b69292ee4f5340994776cac4ba18c27c9578ea1 (patch)
tree4c33700f40ca6ef9923513fac8a7c9abc6309417
parent11e4b1f11c3849622007481d92c3589ce5cfbec8 (diff)
downloadcython-9b69292ee4f5340994776cac4ba18c27c9578ea1.tar.gz
Fix initialization of init=False dataclass fields (GH-4908)
init=False (in a field name) means that it isn't an argument to `__init__`. It does not mean that the field isn't initialized if a default method of initialization is provided.
-rw-r--r--Cython/Compiler/Dataclass.py10
-rw-r--r--tests/run/pure_cdef_class_dataclass.py41
2 files changed, 47 insertions, 4 deletions
diff --git a/Cython/Compiler/Dataclass.py b/Cython/Compiler/Dataclass.py
index 0d0bb4768..87ef5e76a 100644
--- a/Cython/Compiler/Dataclass.py
+++ b/Cython/Compiler/Dataclass.py
@@ -341,8 +341,6 @@ def generate_init_code(init, node, fields, kw_only):
seen_default = False
for name, field in fields.items():
- if not field.init.value:
- continue
entry = node.scope.lookup(name)
if entry.annotation:
annotation = u": %s" % entry.annotation.string.value
@@ -357,18 +355,22 @@ def generate_init_code(init, node, fields, kw_only):
ph_name = get_placeholder_name()
placeholders[ph_name] = field.default # should be a node
assignment = u" = %s" % ph_name
- elif seen_default and not kw_only:
+ elif seen_default and not kw_only and field.init.value:
error(entry.pos, ("non-default argument '%s' follows default argument "
"in dataclass __init__") % name)
return "", {}, []
- args.append(u"%s%s%s" % (name, annotation, assignment))
+ if field.init.value:
+ args.append(u"%s%s%s" % (name, annotation, assignment))
if field.is_initvar:
continue
elif field.default_factory is MISSING:
if field.init.value:
function_body_code_lines.append(u" %s.%s = %s" % (selfname, name, name))
+ elif assignment:
+ # not an argument to the function, but is still initialized
+ function_body_code_lines.append(u" %s.%s%s" % (selfname, name, assignment))
else:
ph_name = get_placeholder_name()
placeholders[ph_name] = field.default_factory
diff --git a/tests/run/pure_cdef_class_dataclass.py b/tests/run/pure_cdef_class_dataclass.py
index 8a978d36f..b3892586f 100644
--- a/tests/run/pure_cdef_class_dataclass.py
+++ b/tests/run/pure_cdef_class_dataclass.py
@@ -29,3 +29,44 @@ class MyDataclass:
a: int = 1
self: list = cython.dataclasses.field(default_factory=list, hash=False) # test that arguments of init don't conflict
+
+
+class DummyObj:
+ def __repr__(self):
+ return "DummyObj()"
+
+
+@cython.dataclasses.dataclass
+@cython.cclass
+class NoInitFields:
+ """
+ >>> NoInitFields()
+ NoInitFields(has_default=DummyObj(), has_factory='From a lambda', neither=None)
+ >>> NoInitFields().has_default is NoInitFields().has_default
+ True
+
+ >>> NoInitFields(1) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ TypeError: NoInitFields.__init__() takes 1 positional argument but 2 were given
+
+ >>> NoInitFields(has_default=1) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...has_default...
+ >>> NoInitFields(has_factory=1) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...has_factory...
+ >>> NoInitFields(neither=1) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...neither...
+ """
+ has_default : object = cython.dataclasses.field(default=DummyObj(), init=False)
+ has_factory : object = cython.dataclasses.field(default_factory=lambda: "From a lambda", init=False)
+ # Cython will default-initialize to None
+ neither : object = cython.dataclasses.field(init=False)
+
+ def __post_init__(self):
+ if not cython.compiled:
+ # Cython will default-initialize this to None, while Python won't
+ # and not initializing it will mess up repr
+ assert not hasattr(self, "neither")
+ self.neither = None