From eb145887ab57b75299a55c85c753d3b4707105be Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 9 Aug 2026 22:16:47 +0100 Subject: Introduce variables for type params --- .../compiler/typechecker/TypeChecker.java | 11 +++- .../typechecker/TypeCheckerNamespaceContext.java | 13 +++++ .../TypeCheckerStructDefinitionTests.java | 65 +++++++++++++++++++++- 3 files changed, 86 insertions(+), 3 deletions(-) (limited to 'src') 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 _ -> + List.of(); + + case TypeOrConstructor.Constructor 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.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 fieldsOf(SumType type) { return this.typesInfo.fieldsOf(type); } + + public TypeCheckerNamespaceContext enter(List 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( @@ -159,6 +159,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"); -- cgit v1.2.3