From cbbcca0d84a60063ed916c34797a607aead33533 Mon Sep 17 00:00:00 2001 From: translatenix <119817707+translatenix@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:29:08 -0700 Subject: [PATCH] codegen-java: Make stateless classes instantiable (#734) Motivation: Currently, the condition for making a generated Java class instantiable is "class is neither abstract nor stateless". (An instantiable class receives a public constructor and equals/hashCode/toString/with methods.) This is inconsistent with Pkl and codegen-kotlin, both of which support instantiation of stateless classes. Changes: Widen condition for making a class instantiable to "class is neither abstract nor a stateless final module class". This is consistent with codegen-kotlin, which generates object declarations (only) for stateless final module classes. Result: Stateless classes are instantiable in Pkl, codegen-java, and codegen-kotlin. --- .../org/pkl/codegen/java/JavaCodeGenerator.kt | 5 +- .../pkl/codegen/java/JavaCodeGeneratorTest.kt | 91 ++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt b/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt index 965c2143..8f22e461 100644 --- a/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt +++ b/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt @@ -541,8 +541,9 @@ class JavaCodeGenerator( val builder = TypeSpec.classBuilder(javaPoetClassName.simpleName()).addModifiers(Modifier.PUBLIC) - // stateless classes aren't instantiable by choice, i.e., no public ctor is generated - val isInstantiable = !pClass.isAbstract && allProperties.isNotEmpty() + // stateless final module classes are non-instantiable by choice + val isInstantiable = + !(pClass.isAbstract || (isModuleClass && !pClass.isOpen && allProperties.isEmpty())) if (codegenOptions.implementSerializable && isInstantiable) { builder.addSuperinterface(java.io.Serializable::class.java) diff --git a/pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/JavaCodeGeneratorTest.kt b/pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/JavaCodeGeneratorTest.kt index 811112d9..a4eb1239 100644 --- a/pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/JavaCodeGeneratorTest.kt +++ b/pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/JavaCodeGeneratorTest.kt @@ -775,6 +775,83 @@ class JavaCodeGeneratorTest { .isEqualToResourceFile("Inheritance.jva") } + @Test + fun `stateless classes`() { + val javaCode = + generateJavaCode( + """ + module my.mod + + class Foo + abstract class Bar + class Baz extends Bar + """ + .trimIndent() + ) + + assertThat(javaCode) + .contains( + """ + | public static final class Foo { + | public Foo() { + | } + """ + .trimMargin() + ) + .contains( + """ + | public abstract static class Bar { + | protected Bar() { + | } + """ + .trimMargin() + ) + .contains( + """ + | public static final class Baz extends Bar { + | public Baz() { + | } + """ + .trimMargin() + ) + } + + @Test + fun `stateless module classes`() { + var javaCode = generateJavaCode("module my.mod") + assertThat(javaCode) + .contains( + """ + |public final class Mod { + | private Mod() { + | } + """ + .trimMargin() + ) + + javaCode = generateJavaCode("abstract module my.mod") + assertThat(javaCode) + .contains( + """ + |public abstract class Mod { + | protected Mod() { + | } + """ + .trimMargin() + ) + + javaCode = generateJavaCode("open module my.mod") + assertThat(javaCode) + .contains( + """ + |public class Mod { + | public Mod() { + | } + """ + .trimMargin() + ) + } + @Test fun `reserved words`() { val props = javaReservedWords.joinToString("\n") { "`$it`: Int" } @@ -1765,12 +1842,22 @@ class JavaCodeGeneratorTest { @Test fun `non-instantiable classes aren't made serializable`() { - val javaCode = + var javaCode = generateJavaCode( """ module my.mod abstract class Foo { str: String } - class Bar // non-instantiable because no constructor is generated for stateless class + """ + .trimIndent(), + implementSerializable = true + ) + + assertThat(javaCode).doesNotContain("Serializable") + + javaCode = + generateJavaCode( + """ + module my.mod """ .trimIndent(), implementSerializable = true