diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-08-13 19:41:16 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-08-13 19:41:16 +0100 |
| commit | 1070928d39728d2aa4ae6d43aee20cb595f8584a (patch) | |
| tree | 81135e5668e8d13a6218c5e9d652e37c69874170 | |
| parent | de9e0f9162b625b3008b261967d303da3cc4ae41 (diff) | |
Support generic structs in java-precisely-matchers
6 files changed, 118 insertions, 25 deletions
diff --git a/examples/03-struct-generic/hobgoblin.json5 b/examples/03-struct-generic/hobgoblin.json5 index 744e6d6..39e039d 100644 --- a/examples/03-struct-generic/hobgoblin.json5 +++ b/examples/03-struct-generic/hobgoblin.json5 @@ -10,10 +10,10 @@ generator: "java-types", path: "output/java/src/gen/java", }, -// { -// generator: "java-precisely-matchers", -// path: "output/java/src/gen/java", -// }, + { + generator: "java-precisely-matchers", + path: "output/java/src/gen/java", + }, // { // generator: "rust-types", // path: "output/rust/src", diff --git a/examples/03-struct-generic/output/java/src/test/java/org/zwobble/example/PointTests.java b/examples/03-struct-generic/output/java/src/test/java/org/zwobble/example/PointTests.java index 36810d4..fba31c7 100644 --- a/examples/03-struct-generic/output/java/src/test/java/org/zwobble/example/PointTests.java +++ b/examples/03-struct-generic/output/java/src/test/java/org/zwobble/example/PointTests.java @@ -1,18 +1,43 @@ package org.zwobble.example; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.zwobble.example.types.point.Line; -import org.zwobble.example.types.point.Point; +import org.zwobble.example.types.point.LineDrawing; +import org.zwobble.example.types.point.Point2D; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.zwobble.example.types.point.LineDrawingMatcher.isLineDrawing; +import static org.zwobble.example.types.point.LineMatcher.isLine; +import static org.zwobble.example.types.point.Point2DMatcher.isPoint2D; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.equalTo; +import static org.zwobble.precisely.Matchers.isSequence; public class PointTests { @Test public void line() { var line = new Line<>( - new Point(10, 25), - new Point(42, 47) + new Point2D(10, 25), + new Point2D(42, 47) ); assertEquals(line.start().x(), 10); assertEquals(line.end().y(), 47); } + + @Test + public void matchers() { + var lineDrawing = new LineDrawing<>(List.of( + new Line<>(new Point2D(10, 25), new Point2D(42, 47)) + )); + + assertThat(lineDrawing, isLineDrawing() + .withLines(isSequence( + isLine() + .withStart(isPoint2D().withX(equalTo(10)).withY(equalTo(25))) + .withEnd(isPoint2D().withX(equalTo(42)).withY(equalTo(47))) + )) + ); + } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java index 901171c..5421e58 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/java/JavaGenerator.java @@ -140,23 +140,34 @@ public class JavaGenerator { } public JavaTypeRef generateTypeRef(Type type) { - return this.generateTypeRef(type, false); + return this.generateTypeRef(type, false, Set.of()); } public JavaTypeRef generateReferenceTypeRef(Type type) { - return this.generateTypeRef(type, true); + return this.generateTypeRef(type, true, Set.of()); } - private JavaTypeRef generateTypeRef(Type type, boolean referenceType) { + public JavaTypeRef generateReferenceTypeRef(Type type, Set<TypeParam> wildcardTypeParams) { + return this.generateTypeRef(type, true, wildcardTypeParams); + } + + private JavaTypeRef generateTypeRef(Type type, boolean referenceType, Set<TypeParam> wildcardTypeParams) { return switch (collapseType(type)) { case ConstructedType constructedType -> { var innerTypeRef = generateTypeRef( constructedType.constructor().genericType(), - referenceType + referenceType, + wildcardTypeParams ); var javaArgs = constructedType.args().stream() - .map(arg -> JavaTypeArg.invariant(generateTypeRef(arg, true))) + .map(arg -> { + if (wildcardTypeParams.contains(arg)) { + return JavaTypeArg.wildcard(); + } else { + return JavaTypeArg.invariant(generateTypeRef(arg, true, wildcardTypeParams)); + } + }) .toList(); yield JavaTypeRef.generic(innerTypeRef.packageName(), innerTypeRef.typeNames(), javaArgs); @@ -200,8 +211,13 @@ public class JavaGenerator { case TypeLevelValueType typeLevelValueType -> throw new UnsupportedOperationException("TODO"); - case TypeParam typeParam -> - JavaTypeRef.local(this.generateTypeName(typeParam.name())); + case TypeParam typeParam -> { + if (wildcardTypeParams.contains(typeParam)) { + yield JavaTypeRef.OBJECT; + } else { + yield JavaTypeRef.local(this.generateTypeName(typeParam.name())); + } + } }; } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javapreciselymatchers/JavaPreciselyMatchersGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javapreciselymatchers/JavaPreciselyMatchersGenerator.java index 760e01e..2a00d2b 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javapreciselymatchers/JavaPreciselyMatchersGenerator.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javapreciselymatchers/JavaPreciselyMatchersGenerator.java @@ -16,6 +16,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; import static org.zwobble.hobgoblin.compiler.util.Casing.lowerCamelCaseToUpperCamelCase; @@ -89,12 +90,25 @@ public class JavaPreciselyMatchersGenerator implements Generator { TypedStructDefinitionNode structDefinition, Context context ) { - var structType = structDefinition.typeOrThrow(); + var javaStructTypeRef = switch (structDefinition.typeOrConstructor()) { + case TypeOrConstructor.Constructor<SimpleStructType> constructor -> { + var args = constructor.value().params().stream() + .map(_ -> JavaTypeArg.wildcard()) + .toList(); + + yield this.javaGenerator.generateTypeRef(constructor.unboundType()) + .withArgs(args); + } + + case TypeOrConstructor.Type<SimpleStructType> type -> { + yield this.javaGenerator.generateTypeRef(type.value()); + } + }; - var javaStructTypeRef = this.javaGenerator.generateTypeRef(structType); + var javaStructRawTypeRef = this.javaGenerator.generateTypeRef(structDefinition.typeOrConstructor().unboundType()); - var javaMatcherTypeRef = this.structMatcherRef(structType); - var matcherTypeName = this.structMatcherTypeName(structType); + var javaMatcherTypeRef = this.structMatcherRef(structDefinition.typeOrConstructor()); + var matcherTypeName = this.structMatcherTypeName(structDefinition.typeOrConstructor()); var submatchersIdentifier = JavaIdentifier.of("submatchers"); var methods = new ArrayList<JavaClassBodyDeclaration>(List.of( @@ -191,8 +205,19 @@ public class JavaPreciselyMatchersGenerator implements Generator { List.of(), JavaIdentifier.of("instanceOf"), List.of( - new JavaClassLiteral(javaStructTypeRef), - new JavaFieldAccess(JavaRef.THIS, submatchersIdentifier) + new JavaClassLiteral(javaStructRawTypeRef), + switch (structDefinition.typeOrConstructor()) { + case TypeOrConstructor.Constructor<SimpleStructType> _ -> + // This should be a safe cast since X and X<?, ..., ?> + // should be compatible. + new JavaCast( + JavaTypeRef.LIST, + new JavaFieldAccess(JavaRef.THIS, submatchersIdentifier) + ); + + case TypeOrConstructor.Type<SimpleStructType> _ -> + new JavaFieldAccess(JavaRef.THIS, submatchersIdentifier); + } ) )) )) @@ -201,7 +226,14 @@ public class JavaPreciselyMatchersGenerator implements Generator { for (var field : structDefinition.fields().orElse(List.of())) { var fieldType = field.type().value(); - var javaFieldTypeRef = this.javaGenerator.generateReferenceTypeRef(fieldType); + var typeParams = switch (structDefinition.typeOrConstructor()) { + case TypeOrConstructor.Constructor<SimpleStructType> constructor -> + Set.copyOf(constructor.value().params()); + + case TypeOrConstructor.Type<SimpleStructType> _ -> + Set.<TypeParam>of(); + }; + var javaFieldTypeRef = this.javaGenerator.generateReferenceTypeRef(fieldType, typeParams); var javaFieldName = this.javaGenerator.generateFieldName(field.name()); methods.add(new JavaMethodDeclaration( @@ -233,7 +265,15 @@ public class JavaPreciselyMatchersGenerator implements Generator { List.of( new JavaStringLiteral(field.name()), new JavaMethodRef(javaStructTypeRef, javaFieldName), - new JavaRef(javaFieldName) + // TODO: the use of wildcards should make this safe, + // but it would be nice to avoid the cast if possible. + new JavaCast( + JavaTypeRef.topLevel( + PRECISELY_PACKAGE_NAME, + JavaIdentifier.of("Matcher") + ), + new JavaRef(javaFieldName) + ) ) )) )), @@ -291,7 +331,7 @@ public class JavaPreciselyMatchersGenerator implements Generator { ); } - private JavaTypeRef structMatcherRef(SimpleStructType structType) { + private JavaTypeRef structMatcherRef(TypeOrConstructor<SimpleStructType> structType) { var matcherTypeName = structMatcherTypeName(structType); return JavaTypeRef.topLevel( @@ -300,7 +340,7 @@ public class JavaPreciselyMatchersGenerator implements Generator { ); } - private JavaIdentifier structMatcherTypeName(SimpleStructType structType) { + private JavaIdentifier structMatcherTypeName(TypeOrConstructor<SimpleStructType> structType) { return this.javaGenerator.generateTypeName(structType.name() + "Matcher"); } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstructibleType.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstructibleType.java index 05fd042..8f3b931 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstructibleType.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstructibleType.java @@ -1,4 +1,5 @@ package org.zwobble.hobgoblin.compiler.types; public sealed interface ConstructibleType extends Type permits SimpleNativeType, SimpleStructType { + NamespaceName namespaceName(); } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java index 7904d29..4269686 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java @@ -11,6 +11,11 @@ public sealed interface TypeOrConstructor<T extends ConstructibleType> { record Type<T extends ConstructibleType>(T value) implements TypeOrConstructor<T> { @Override + public NamespaceName namespaceName() { + return this.value.namespaceName(); + } + + @Override public String name() { return this.value.name(); } @@ -23,6 +28,11 @@ public sealed interface TypeOrConstructor<T extends ConstructibleType> { record Constructor<T extends ConstructibleType>(TypeConstructor<T> value) implements TypeOrConstructor<T> { @Override + public NamespaceName namespaceName() { + return value.genericType().namespaceName(); + } + + @Override public String name() { return this.value.name(); } @@ -33,6 +43,7 @@ public sealed interface TypeOrConstructor<T extends ConstructibleType> { } } + NamespaceName namespaceName(); String name(); TypeLevelValue value(); T unboundType(); |
