diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-08-09 22:16:47 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-08-09 22:16:47 +0100 |
| commit | eb145887ab57b75299a55c85c753d3b4707105be (patch) | |
| tree | 0cb1c1cf9668a90accc95fe59c211f1599f28ade /src | |
| parent | 1e2d86b9d7f2f659f4c53f8543f7931ea81c5781 (diff) | |
Introduce variables for type params
Diffstat (limited to 'src')
3 files changed, 86 insertions, 3 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 97008b6..63fc357 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java @@ -239,8 +239,17 @@ public class TypeChecker { throw new UnsupportedOperationException("TODO"); }; + var typeParams = switch (typeOrConstructor) { + case TypeOrConstructor.Type<SimpleStructType> _ -> + List.<TypeParam>of(); + + case TypeOrConstructor.Constructor<SimpleStructType> constructor -> + constructor.value().params(); + }; + var bodyContext = context.enter(typeParams); + var typeCheckedFieldDefinitions = untyped.fields().isPresent() - ? Optional.of(typeCheckFieldDefinitions(untyped.fields().get(), context)) + ? Optional.of(typeCheckFieldDefinitions(untyped.fields().get(), bodyContext)) : Optional.<TypeCheckFieldDefinitionsResult>empty(); var typedFields = typeCheckedFieldDefinitions.map(TypeCheckFieldDefinitionsResult::fields); var typedFieldNodes = typeCheckedFieldDefinitions.map(TypeCheckFieldDefinitionsResult::typedFieldNodes); diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java index 2c8ad67..ce7153f 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java @@ -89,4 +89,17 @@ public class TypeCheckerNamespaceContext { public List<Field> fieldsOf(SumType type) { return this.typesInfo.fieldsOf(type); } + + public TypeCheckerNamespaceContext enter(List<TypeParam> typeParams) { + var newVariables = new HashMap<>(this.variables); + for (var typeParam : typeParams) { + newVariables.put(typeParam.name(), Types.metaType(typeParam)); + } + + return new TypeCheckerNamespaceContext( + this.namespaceName, + newVariables, + this.typesInfo + ); + } } 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 91839f6..1de6cbf 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java @@ -18,7 +18,7 @@ import static org.zwobble.precisely.Matchers.*; public class TypeCheckerStructDefinitionTests { @Test - public void whenStructDefinitionHasNoTypeParamsThenNameIsBoundToType() { + public void whenStructDefinitionHasNoTypeParamsThenStructNameIsBoundToType() { var untyped = UntypedStructDefinitionNode.arbitrary() .withName("X") .build(); @@ -41,7 +41,7 @@ public class TypeCheckerStructDefinitionTests { } @Test - public void whenStructDefinitionHasTypeParamsThenNameIsBoundToTypeConstructor() { + public void whenStructDefinitionHasTypeParamsThenStructNameIsBoundToTypeConstructor() { var untyped = UntypedStructDefinitionNode.arbitrary() .withName("X") .withTypeParams(Optional.of(List.of( @@ -160,6 +160,67 @@ public class TypeCheckerStructDefinitionTests { } @Test + public void whenStructDefinitionHasTypeParamsThenTypeParamsAreBoundWithinStructDefinition() { + var untyped = UntypedStructDefinitionNode.arbitrary() + .withName("X") + .withTypeParams(Optional.of(List.of( + UntypedTypeParamNode.arbitrary().withName("A").build(), + UntypedTypeParamNode.arbitrary().withName("B").build() + ))) + .withFields(List.of( + UntypedStructFieldDefinitionNode.arbitrary() + .withName("a") + .withType(UntypedArb.typeLevelReference("A")) + .build(), + UntypedStructFieldDefinitionNode.arbitrary() + .withName("b") + .withType(UntypedArb.typeLevelReference("B")) + .build() + )) + .build(); + var globalContext = TypeCheckerGlobalContext.initial(); + var namespaceName = NamespaceName.of("a", "b"); + var namespaceContext = globalContext.enterNamespace(namespaceName); + + var typed = typeCheckNamespaceStatement(untyped, namespaceContext); + + var expectedInnerType = new SimpleStructType(NamespaceName.of("a", "b"), "X"); + assertThat(typed, instanceOf( + TypedStructDefinitionNode.class, + has( + "fields", + TypedStructDefinitionNode::fields, + isOptionalOf(isSequence( + allOf( + has("name", TypedStructFieldDefinitionNode::name, equalTo("a")), + has("type", TypedStructFieldDefinitionNode::type, has( + "value", + TypedTypeLevelExpressionNode::value, + equalTo(new TypeParam(expectedInnerType, "A")) + )) + ), + allOf( + has("name", TypedStructFieldDefinitionNode::name, equalTo("b")), + has("type", TypedStructFieldDefinitionNode::type, has( + "value", + TypedTypeLevelExpressionNode::value, + equalTo(new TypeParam(expectedInnerType, "B")) + )) + ) + )) + ) + )); + var typesInfo = globalContext.toTypesInfo(); + assertThat( + typesInfo.fieldsOf(new SimpleStructType(namespaceName, "X")), + isOptionalOf(isSequence( + isField("a", new TypeParam(expectedInnerType, "A")), + isField("b", new TypeParam(expectedInnerType, "B")) + )) + ); + } + + @Test public void structCanUseTypeDefinedLater() { var int32Type = SimpleNativeType.builtin("Int32"); var namespaceName = NamespaceName.of("a", "b"); |
