summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-06-26 16:25:34 +0100
committerMichael Williamson <mike@zwobble.org>2026-06-26 16:25:34 +0100
commit9c2421d317068021c64a42be9f785e5ec73afd43 (patch)
treee5284a4ffa6a65117c3c84458fe1560ec0a713f5
parent7e632842ecdc2a3b72b3e5f0b575166c6c5e8b4b (diff)
Treat variant as subtype of sum
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java25
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java6
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java5
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtyping.java14
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypesInfo.java2
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtypingTests.java40
6 files changed, 80 insertions, 12 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 34c05cb..8b930aa 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
@@ -189,7 +189,8 @@ public class TypeChecker {
sumType,
sumTypeFields,
structType,
- typeCheckedFieldDefinitions.fields
+ typeCheckedFieldDefinitions.fields,
+ context.toTypesInfo()
);
}
@@ -265,7 +266,8 @@ public class TypeChecker {
sumType,
typeCheckedFieldDefinitions.fields,
variantType,
- context.fieldsOf(variantType)
+ context.fieldsOf(variantType),
+ context.toTypesInfo()
);
}
}
@@ -299,7 +301,8 @@ public class TypeChecker {
SumType sumType,
List<Field> sumTypeFields,
StructType variantType,
- List<Field> variantTypeFields
+ List<Field> variantTypeFields,
+ TypesInfo typesInfo
) {
for (var sumTypeField : sumTypeFields) {
var variantTypeField = variantTypeFields.stream()
@@ -310,7 +313,12 @@ public class TypeChecker {
throw new SubtypeIsMissingFieldError(sumType, variantType, sumTypeField.name(), sumTypeField.source());
}
- checkIsSubtype(variantTypeField.get().type(), sumTypeField.type(), variantTypeField.get().source());
+ checkIsSubtype(
+ variantTypeField.get().type(),
+ sumTypeField.type(),
+ variantTypeField.get().source(),
+ typesInfo
+ );
}
}
@@ -384,8 +392,13 @@ public class TypeChecker {
return value;
}
- private static void checkIsSubtype(Type subtype, Type supertype, Source source) {
- if (!isSubtype(subtype, supertype)) {
+ private static void checkIsSubtype(
+ Type subtype,
+ Type supertype,
+ Source source,
+ TypesInfo typesInfo
+ ) {
+ if (!isSubtype(subtype, supertype, typesInfo)) {
throw new UnexpectedTypeError(
new TypeSet.SingleType(supertype),
subtype,
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java
index cf61a22..c66c999 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java
@@ -1,9 +1,11 @@
package org.zwobble.hobgoblin.compiler.typechecker;
import org.zwobble.hobgoblin.compiler.types.*;
-import org.zwobble.hobgoblin.compiler.util.ManyToMany;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
public class TypeCheckerGlobalContext {
private final Map<String, Type> nativeTypes = new HashMap<>();
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 0ffbe66..82c8273 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
@@ -34,6 +34,11 @@ public class TypeCheckerNamespaceContext {
this.typesInfo = typesInfo;
}
+ public TypesInfo toTypesInfo() {
+ return this.typesInfo;
+ }
+
+
public NamespaceName namespaceName() {
return this.namespaceName;
}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtyping.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtyping.java
index 673d931..335fa8d 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtyping.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtyping.java
@@ -1,12 +1,22 @@
package org.zwobble.hobgoblin.compiler.typechecker;
+import org.zwobble.hobgoblin.compiler.types.SumType;
import org.zwobble.hobgoblin.compiler.types.Type;
class TypeCheckerSubtyping {
private TypeCheckerSubtyping() {
}
- static boolean isSubtype(Type subtype, Type supertype) {
- return subtype.equals(supertype);
+ static boolean isSubtype(Type subtype, Type supertype, TypesInfo typesInfo) {
+ if (subtype.equals(supertype)) {
+ return true;
+ }
+
+ if (supertype instanceof SumType supertypeSum) {
+ return typesInfo.sumVariants(supertypeSum).stream()
+ .anyMatch(variantType -> isSubtype(variantType, subtype, typesInfo));
+ }
+
+ return false;
}
}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypesInfo.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypesInfo.java
index 4e896dd..814466c 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypesInfo.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypesInfo.java
@@ -5,6 +5,8 @@ import org.zwobble.hobgoblin.compiler.types.*;
import java.util.List;
public interface TypesInfo {
+ TypesInfo EMPTY = TypesInfoInMemory.empty();
+
boolean isDefined(StructType type);
List<Field> fieldsOf(StructType structType);
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtypingTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtypingTests.java
index 35779fe..e2b43d0 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtypingTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSubtypingTests.java
@@ -1,7 +1,12 @@
package org.zwobble.hobgoblin.compiler.typechecker;
import org.junit.jupiter.api.Test;
+import org.zwobble.hobgoblin.compiler.types.NamespaceName;
import org.zwobble.hobgoblin.compiler.types.SimpleNativeType;
+import org.zwobble.hobgoblin.compiler.types.StructType;
+import org.zwobble.hobgoblin.compiler.types.SumType;
+
+import java.util.List;
import static org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerSubtyping.isSubtype;
import static org.zwobble.precisely.AssertThat.assertThat;
@@ -13,7 +18,7 @@ public class TypeCheckerSubtypingTests {
var nativeType1 = SimpleNativeType.builtin("Int32");
var nativeType2 = SimpleNativeType.builtin("Int32");
- var isSubtype = isSubtype(nativeType1, nativeType2);
+ var isSubtype = isSubtype(nativeType1, nativeType2, TypesInfo.EMPTY);
assertThat(isSubtype, equalTo(true));
}
@@ -23,7 +28,38 @@ public class TypeCheckerSubtypingTests {
var nativeType1 = SimpleNativeType.builtin("Int32");
var nativeType2 = SimpleNativeType.builtin("Int64");
- var isSubtype = isSubtype(nativeType1, nativeType2);
+ var isSubtype = isSubtype(nativeType1, nativeType2, TypesInfo.EMPTY);
+
+ assertThat(isSubtype, equalTo(false));
+ }
+
+ @Test
+ public void variantIsSubtypeOfSumType() {
+ var variantType1 = new StructType(NamespaceName.of(), "Rectangle");
+ var variantType2 = new StructType(NamespaceName.of(), "Circle");
+ var sumType = new SumType(NamespaceName.of(), "Shape");
+ var typesInfo = TypesInfoInMemory.empty();
+ typesInfo.defineSumType(sumType, List.of(variantType1, variantType2), List.of());
+
+ var isSubtype = isSubtype(variantType1, sumType, typesInfo);
+
+ assertThat(isSubtype, equalTo(true));
+ }
+
+ @Test
+ public void variantIsNotSubtypeOfUnrelatedSumType() {
+ var typesInfo = TypesInfoInMemory.empty();
+
+ var variantType1 = new StructType(NamespaceName.of(), "Rectangle");
+ var variantType2 = new StructType(NamespaceName.of(), "Circle");
+ var sumType = new SumType(NamespaceName.of(), "Shape");
+ typesInfo.defineSumType(sumType, List.of(variantType1, variantType2), List.of());
+
+ var otherSumType = new SumType(NamespaceName.of(), "Shape3D");
+ var otherVariantType = new StructType(NamespaceName.of(), "Cube");
+ typesInfo.defineSumType(sumType, List.of(otherVariantType), List.of());
+
+ var isSubtype = isSubtype(variantType1, otherSumType, typesInfo);
assertThat(isSubtype, equalTo(false));
}