summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java11
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java13
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java65
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");