From d049a89e2e5ff0b52519f37502dbf0c17cb9455f Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Mon, 10 Aug 2026 10:28:42 +0100 Subject: Add type parameters to record declaration in Java AST --- hobgoblin/src/output/lang/java/ast.hob | 1 + .../javatransient0/JavaTransient0Generator.java | 1 + .../generators/javatypes/JavaTypesGenerator.java | 2 ++ .../compiler/output/lang/java/JavaWriter.java | 31 +++++++++------- .../lang/java/ast/JavaRecordDeclaration.java | 42 +++++++++++++++------- .../compiler/output/lang/java/JavaWriterTests.java | 28 +++++++++++++++ .../java/ast/JavaRecordDeclarationMatcher.java | 6 ++++ 7 files changed, 86 insertions(+), 25 deletions(-) diff --git a/hobgoblin/src/output/lang/java/ast.hob b/hobgoblin/src/output/lang/java/ast.hob index d3b5eef..a732b67 100644 --- a/hobgoblin/src/output/lang/java/ast.hob +++ b/hobgoblin/src/output/lang/java/ast.hob @@ -55,6 +55,7 @@ struct JavaRecordComponent { struct JavaRecordDeclaration { field type: JavaTypeRef; + field typeParameters: List[JavaTypeParameter]; field components: List[JavaRecordComponent]; field implementsTypes: List[JavaTypeRef]; field body: List[JavaClassBodyDeclaration]; diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatransient0/JavaTransient0Generator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatransient0/JavaTransient0Generator.java index a8e4972..4dab4da 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatransient0/JavaTransient0Generator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatransient0/JavaTransient0Generator.java @@ -91,6 +91,7 @@ public class JavaTransient0Generator implements Generator { return new JavaCompilationUnit(new JavaRecordDeclaration( this.taggedSharedValueRef(), + List.of(), List.of( new JavaRecordComponent(JavaTypeRef.LONG, typeId), new JavaRecordComponent(JavaTypeRef.OBJECT, value) diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java index 709ef12..62f24b6 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java @@ -173,6 +173,7 @@ public class JavaTypesGenerator implements Generator { if (structDefinition.fields().isPresent()) { return new JavaRecordDeclaration( javaTypeRef, + List.of(), components, implementsTypes, body, @@ -249,6 +250,7 @@ public class JavaTypesGenerator implements Generator { return new JavaRecordDeclaration( builderJavaTypeRef, + List.of(), components, implementsTypes, body, diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java index bc1fe01..721cd14 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java @@ -150,19 +150,7 @@ public class JavaWriter implements AutoCloseable { this.writer.write("interface "); this.writeIdentifier(interfaceDeclaration.name()); - if (!interfaceDeclaration.typeParameters().isEmpty()) { - this.writer.write("<"); - writeWithSeparator( - interfaceDeclaration.typeParameters(), - typeParameter -> { - this.writeIdentifier(typeParameter.name()); - }, - () -> { - this.writer.write(", "); - } - ); - this.writer.write(">"); - } + this.writeTypeParameters(interfaceDeclaration.typeParameters()); this.writer.write(" "); @@ -196,6 +184,7 @@ public class JavaWriter implements AutoCloseable { this.writer.write("public record "); this.writeIdentifier(recordDeclaration.name()); + this.writeTypeParameters(recordDeclaration.typeParameters()); this.writer.write("("); if (!recordDeclaration.components().isEmpty()) { @@ -239,6 +228,22 @@ public class JavaWriter implements AutoCloseable { this.writer.write("}"); } + private void writeTypeParameters(List typeParameters) throws IOException { + if (!typeParameters.isEmpty()) { + this.writer.write("<"); + writeWithSeparator( + typeParameters, + typeParameter -> { + this.writeIdentifier(typeParameter.name()); + }, + () -> { + this.writer.write(", "); + } + ); + this.writer.write(">"); + } + } + private void writeImplements(List implementsTypes) throws IOException { if (!implementsTypes.isEmpty()) { this.writer.write("implements "); diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclaration.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclaration.java index f864baa..5992e4b 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclaration.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclaration.java @@ -7,74 +7,92 @@ package org.zwobble.hobgoblin.compiler.output.lang.java.ast; public record JavaRecordDeclaration( org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeRef type, + java.util.List typeParameters, java.util.List components, java.util.List implementsTypes, java.util.List body, org.zwobble.hobgoblin.compiler.ast.DocComment docComment ) implements org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeDeclaration, org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaClassBodyDeclaration { public static org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder arbitrary() { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(org.zwobble.hobgoblin.compiler.output.lang.java.ast.HobgoblinNativeAst.arbitraryJavaTypeRef(), java.util.List.of(), java.util.List.of(), java.util.List.of(), org.zwobble.hobgoblin.compiler.ast.HobgoblinNativeAst.arbitraryDocComment()); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(org.zwobble.hobgoblin.compiler.output.lang.java.ast.HobgoblinNativeAst.arbitraryJavaTypeRef(), java.util.List.of(), java.util.List.of(), java.util.List.of(), java.util.List.of(), org.zwobble.hobgoblin.compiler.ast.HobgoblinNativeAst.arbitraryDocComment()); } public record Builder( org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeRef type, + java.util.List typeParameters, java.util.List components, java.util.List implementsTypes, java.util.List body, org.zwobble.hobgoblin.compiler.ast.DocComment docComment ) implements org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeDeclaration.Builder, org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaClassBodyDeclaration.Builder { public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration build() { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withType(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeRef type) { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); + } + + public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withTypeParameters(java.util.List typeParameters) { + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); + } + + public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addTypeParameter(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeParameter typeParameter) { + var typeParameters = new java.util.ArrayList(this.typeParameters); + typeParameters.add(typeParameter); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); + } + + public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addTypeParameter(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeParameter.Builder typeParameter) { + var typeParameters = new java.util.ArrayList(this.typeParameters); + typeParameters.add(typeParameter.build()); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withComponents(java.util.List components) { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addComponent(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordComponent component) { var components = new java.util.ArrayList(this.components); components.add(component); - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addComponent(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordComponent.Builder component) { var components = new java.util.ArrayList(this.components); components.add(component.build()); - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withImplementsTypes(java.util.List implementsTypes) { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addImplementsType(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaTypeRef implementsType) { var implementsTypes = new java.util.ArrayList(this.implementsTypes); implementsTypes.add(implementsType); - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withBody(java.util.List body) { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addBodyElement(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaClassBodyDeclaration bodyElement) { var body = new java.util.ArrayList(this.body); body.add(bodyElement); - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder addBodyElement(org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaClassBodyDeclaration.Builder bodyElement) { var body = new java.util.ArrayList(this.body); body.add(bodyElement.build()); - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder withDocComment(org.zwobble.hobgoblin.compiler.ast.DocComment docComment) { - return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, components, implementsTypes, body, docComment); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder(type, typeParameters, components, implementsTypes, body, docComment); } // Custom area start: org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration.Builder body diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java index 75a57a6..e8556bc 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java @@ -395,6 +395,7 @@ public class JavaWriterTests { List.of(), List.of(), List.of(), + List.of(), DocComment.EMPTY ); @@ -415,6 +416,7 @@ public class JavaWriterTests { List.of(), List.of(), List.of(), + List.of(), new DocComment("A 2D point.") ); @@ -429,11 +431,34 @@ public class JavaWriterTests { )); } + @Test + public void recordDeclarationWithTypeParams() throws IOException { + var java = JavaRecordDeclaration.arbitrary() + .withType(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("X"))) + .addTypeParameter(JavaTypeParameter.arbitrary() + .withName(JavaIdentifier.of("T1")) + ) + .addTypeParameter(JavaTypeParameter.arbitrary() + .withName(JavaIdentifier.of("T2")) + ) + .build(); + + var string = write(writer -> writer.writeTypeDeclaration(java)); + + assertThat(string, equalTo(""" + public record X() { + // Custom area start: X body + // Custom area end: X body + }""" + )); + } + @Test public void recordDeclarationImplementingOneInterface() throws IOException { var java = new JavaRecordDeclaration( JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Rectangle")), List.of(), + List.of(), List.of( JavaTypeRef.topLevel(JavaPackageName.of("com", "example"), JavaIdentifier.of("Shape")) ), @@ -456,6 +481,7 @@ public class JavaWriterTests { var java = new JavaRecordDeclaration( JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Rectangle")), List.of(), + List.of(), List.of( JavaTypeRef.topLevel(JavaPackageName.of("com", "example"), JavaIdentifier.of("Shape")), JavaTypeRef.topLevel(JavaPackageName.of("com", "example"), JavaIdentifier.of("Drawable")) @@ -480,6 +506,7 @@ public class JavaWriterTests { JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Rectangle")), List.of(), List.of(), + List.of(), List.of( JavaMethodDeclaration.arbitrary() .withName(JavaIdentifier.of("a")) @@ -1851,6 +1878,7 @@ public class JavaWriterTests { List.of(), List.of(), List.of(), + List.of(), DocComment.EMPTY ); diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclarationMatcher.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclarationMatcher.java index 56ac030..8661a62 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclarationMatcher.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/ast/JavaRecordDeclarationMatcher.java @@ -36,6 +36,12 @@ public final class JavaRecordDeclarationMatcher implements org.zwobble.precisely return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclarationMatcher(submatchers); } + public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclarationMatcher withTypeParameters(org.zwobble.precisely.Matcher> typeParameters) { + var submatchers = new java.util.ArrayList>(this.submatchers); + submatchers.add(org.zwobble.precisely.Matchers.has("typeParameters", org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration::typeParameters, typeParameters)); + return new org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclarationMatcher(submatchers); + } + public org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclarationMatcher withComponents(org.zwobble.precisely.Matcher> components) { var submatchers = new java.util.ArrayList>(this.submatchers); submatchers.add(org.zwobble.precisely.Matchers.has("components", org.zwobble.hobgoblin.compiler.output.lang.java.ast.JavaRecordDeclaration::components, components)); -- cgit v1.2.3