diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-08-09 22:06:17 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-08-09 22:06:17 +0100 |
| commit | 1e2d86b9d7f2f659f4c53f8543f7931ea81c5781 (patch) | |
| tree | 53658f9c95fadabf8169b730c0de3d2dd54cd63c /src | |
| parent | 4722db99dcc14457e505ee988c0622c65d4039cc (diff) | |
Type check generic struct definition
Diffstat (limited to 'src')
4 files changed, 99 insertions, 24 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java index e61cb7a..97008b6 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java @@ -208,7 +208,17 @@ public class TypeChecker { TypeCheckerNamespaceContext context ) { var structType = new SimpleStructType(context.namespaceName(), untyped.name()); - var metaType = new TypeLevelValueType(structType); + + var structTypeOrConstructor = untyped.typeParams().isEmpty() + ? new TypeOrConstructor.Type<>(structType) + : new TypeOrConstructor.Constructor<>(new StructTypeConstructor( + untyped.typeParams().get().stream() + .map(typeParam -> new TypeParam(structType, typeParam.name())) + .toList(), + structType + )); + var metaType = new TypeLevelValueType(structTypeOrConstructor.value()); + context.declare(untyped.name(), metaType, untyped.source()); return metaType; } @@ -217,7 +227,17 @@ public class TypeChecker { UntypedStructDefinitionNode untyped, TypeCheckerNamespaceContext context ) { - var structType = (SimpleStructType) lookupMetaType(untyped.name(), untyped.source(), context); + var metaType = lookupTypeLevelValue(untyped.name(), untyped.source(), context); + var typeOrConstructor = switch (metaType) { + case SimpleStructType type -> + new TypeOrConstructor.Type<>(type); + + case StructTypeConstructor typeConstructor -> + new TypeOrConstructor.Constructor<>(typeConstructor); + + default -> + throw new UnsupportedOperationException("TODO"); + }; var typeCheckedFieldDefinitions = untyped.fields().isPresent() ? Optional.of(typeCheckFieldDefinitions(untyped.fields().get(), context)) @@ -225,21 +245,24 @@ public class TypeChecker { var typedFields = typeCheckedFieldDefinitions.map(TypeCheckFieldDefinitionsResult::fields); var typedFieldNodes = typeCheckedFieldDefinitions.map(TypeCheckFieldDefinitionsResult::typedFieldNodes); - context.defineStructType(structType, typedFields); + context.defineStructType(typeOrConstructor.unboundType(), typedFields); - for (var sumType : context.variantOf(structType)) { - var sumTypeFields = context.fieldsOf(sumType); - typeCheckVariantType( - sumType, - sumTypeFields, - structType, - typedFields, - context.toTypesInfo() - ); + // TODO: handle sums with generic structs + if (typeOrConstructor instanceof TypeOrConstructor.Type<SimpleStructType> type) { + for (var sumType : context.variantOf(type.value())) { + var sumTypeFields = context.fieldsOf(sumType); + typeCheckVariantType( + sumType, + sumTypeFields, + type.value(), + typedFields, + context.toTypesInfo() + ); + } } return new TypedStructDefinitionNode( - TypeOrConstructor.type(structType), + typeOrConstructor, typedFieldNodes, untyped.docComment(), untyped.source() 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 3cffbc6..7904d29 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeOrConstructor.java @@ -14,6 +14,11 @@ public sealed interface TypeOrConstructor<T extends ConstructibleType> { public String name() { return this.value.name(); } + + @Override + public T unboundType() { + return value; + } } record Constructor<T extends ConstructibleType>(TypeConstructor<T> value) implements TypeOrConstructor<T> { @@ -21,8 +26,14 @@ public sealed interface TypeOrConstructor<T extends ConstructibleType> { public String name() { return this.value.name(); } + + @Override + public T unboundType() { + return this.value.genericType(); + } } String name(); TypeLevelValue value(); + T unboundType(); } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/Types.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/Types.java index 094a419..cfbb5ba 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/types/Types.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/Types.java @@ -18,4 +18,12 @@ public class Types { new StructTypeConstructor(params, simpleStructType); }; } + + public static Type metaType(Type type) { + return new TypeLevelValueType(type); + } + + public static Type metaType(TypeConstructor<?> constructor) { + return new TypeLevelValueType(constructor); + } } diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java index 9bdf9f8..91839f6 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java @@ -5,13 +5,8 @@ import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; import org.zwobble.hobgoblin.compiler.ast.typed.TypedStructDefinitionNode; import org.zwobble.hobgoblin.compiler.ast.typed.TypedStructFieldDefinitionNode; import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelExpressionNode; -import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedArb; -import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedNamespaceNode; -import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedStructDefinitionNode; -import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedStructFieldDefinitionNode; -import org.zwobble.hobgoblin.compiler.types.NamespaceName; -import org.zwobble.hobgoblin.compiler.types.SimpleNativeType; -import org.zwobble.hobgoblin.compiler.types.SimpleStructType; +import org.zwobble.hobgoblin.compiler.ast.untyped.*; +import org.zwobble.hobgoblin.compiler.types.*; import java.util.List; import java.util.Optional; @@ -23,7 +18,7 @@ import static org.zwobble.precisely.Matchers.*; public class TypeCheckerStructDefinitionTests { @Test - public void structHasNamespaceFromContext() { + public void whenStructDefinitionHasNoTypeParamsThenNameIsBoundToType() { var untyped = UntypedStructDefinitionNode.arbitrary() .withName("X") .build(); @@ -31,14 +26,52 @@ public class TypeCheckerStructDefinitionTests { var typed = typeCheckNamespaceStatement(untyped, context); + var expectedType = new SimpleStructType(NamespaceName.of("a", "b"), "X"); assertThat(typed, instanceOf( TypedStructDefinitionNode.class, has( - "type", - TypedStructDefinitionNode::typeOrThrow, - equalTo(new SimpleStructType(NamespaceName.of("a", "b"), "X")) + "typeOrConstructor", + TypedStructDefinitionNode::typeOrConstructor, + equalTo(TypeOrConstructor.type(expectedType)) ) )); + assertThat(context.lookupType("X"), isOptionalOf( + equalTo(Types.metaType(expectedType)) + )); + } + + @Test + public void whenStructDefinitionHasTypeParamsThenNameIsBoundToTypeConstructor() { + var untyped = UntypedStructDefinitionNode.arbitrary() + .withName("X") + .withTypeParams(Optional.of(List.of( + UntypedTypeParamNode.arbitrary().withName("A").build(), + UntypedTypeParamNode.arbitrary().withName("B").build() + ))) + .build(); + var context = TypeCheckerContextArb.namespaceContext(NamespaceName.of("a", "b")); + + var typed = typeCheckNamespaceStatement(untyped, context); + + var expectedInnerType = new SimpleStructType(NamespaceName.of("a", "b"), "X"); + var expectedTypeConstructor = Types.constructor( + List.of( + new TypeParam(expectedInnerType, "A"), + new TypeParam(expectedInnerType, "B") + ), + expectedInnerType + ); + assertThat(typed, instanceOf( + TypedStructDefinitionNode.class, + has( + "typeOrConstructor", + TypedStructDefinitionNode::typeOrConstructor, + equalTo(TypeOrConstructor.constructor(expectedTypeConstructor)) + ) + )); + assertThat(context.lookupType("X"), isOptionalOf( + equalTo(Types.metaType(expectedTypeConstructor)) + )); } @Test |
