From 7f0b4097352f429c31526c31b281c78b7ba4d8c9 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 2 Aug 2026 11:54:31 +0100 Subject: Generate proper errors when arbitrary value cannot be found --- .../compiler/analysis/ArbitraryValueAnalysis.java | 15 +++- .../analysis/errors/SumTypeHasNoVariantsError.java | 18 +++++ .../analysis/errors/TypeIsInfiniteError.java | 18 +++++ .../analysis/ArbitraryValueAnalysisTests.java | 83 ++++++++++++++++++++++ 4 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/SumTypeHasNoVariantsError.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/TypeIsInfiniteError.java create mode 100644 src/test/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysisTests.java diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysis.java b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysis.java index c1d7cc3..ecfdd76 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysis.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysis.java @@ -1,5 +1,8 @@ package org.zwobble.hobgoblin.compiler.analysis; +import org.zwobble.hobgoblin.compiler.analysis.errors.SumTypeHasNoVariantsError; +import org.zwobble.hobgoblin.compiler.analysis.errors.TypeIsInfiniteError; +import org.zwobble.hobgoblin.compiler.sources.NullSource; import org.zwobble.hobgoblin.compiler.typechecker.TypesInfo; import org.zwobble.hobgoblin.compiler.types.*; @@ -9,13 +12,19 @@ import java.util.Set; public class ArbitraryValueAnalysis { public static Type selectArbitraryVariant(SumType sumType, TypesInfo typesInfo) { + var sumVariants = typesInfo.sumVariants(sumType); + if (sumVariants.isEmpty()) { + // TODO: proper source + throw new SumTypeHasNoVariantsError(sumType, NullSource.INSTANCE); + } + var seenTypes = new HashSet(); seenTypes.add(sumType); - // TODO: better error - return typesInfo.sumVariants(sumType).stream() + return sumVariants.stream() .filter(variant -> isNonRecursivelyConstructable(variant.valueType(), seenTypes, typesInfo)) .findFirst() - .orElseThrow() + // TODO: proper source + .orElseThrow(() -> new TypeIsInfiniteError(sumType, NullSource.INSTANCE)) .containerType(); } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/SumTypeHasNoVariantsError.java b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/SumTypeHasNoVariantsError.java new file mode 100644 index 0000000..7624767 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/SumTypeHasNoVariantsError.java @@ -0,0 +1,18 @@ +package org.zwobble.hobgoblin.compiler.analysis.errors; + +import org.zwobble.hobgoblin.compiler.errors.SourceError; +import org.zwobble.hobgoblin.compiler.sources.Source; +import org.zwobble.hobgoblin.compiler.types.SumType; + +public class SumTypeHasNoVariantsError extends SourceError { + private final SumType type; + + public SumTypeHasNoVariantsError(SumType type, Source source) { + super(type + " has no variants", source); + this.type = type; + } + + public SumType type() { + return type; + } +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/TypeIsInfiniteError.java b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/TypeIsInfiniteError.java new file mode 100644 index 0000000..87e96cc --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/analysis/errors/TypeIsInfiniteError.java @@ -0,0 +1,18 @@ +package org.zwobble.hobgoblin.compiler.analysis.errors; + +import org.zwobble.hobgoblin.compiler.errors.SourceError; +import org.zwobble.hobgoblin.compiler.sources.Source; +import org.zwobble.hobgoblin.compiler.types.Type; + +public class TypeIsInfiniteError extends SourceError { + private final Type type; + + public TypeIsInfiniteError(Type type, Source source) { + super(type.describe() + " is infinite", source); + this.type = type; + } + + public Type type() { + return type; + } +} diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysisTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysisTests.java new file mode 100644 index 0000000..30dbc7b --- /dev/null +++ b/src/test/java/org/zwobble/hobgoblin/compiler/analysis/ArbitraryValueAnalysisTests.java @@ -0,0 +1,83 @@ +package org.zwobble.hobgoblin.compiler.analysis; + +import org.junit.jupiter.api.Test; +import org.zwobble.hobgoblin.compiler.analysis.errors.SumTypeHasNoVariantsError; +import org.zwobble.hobgoblin.compiler.analysis.errors.TypeIsInfiniteError; +import org.zwobble.hobgoblin.compiler.builtins.NativeTypes; +import org.zwobble.hobgoblin.compiler.sources.NullSource; +import org.zwobble.hobgoblin.compiler.typechecker.TypesInfoInMemory; +import org.zwobble.hobgoblin.compiler.types.*; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.zwobble.hobgoblin.compiler.analysis.ArbitraryValueAnalysis.selectArbitraryVariant; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.equalTo; + +public class ArbitraryValueAnalysisTests { + @Test + public void whenTypeHasNoVariantsThenErrorIsThrown() { + var sumType = new SumType(NamespaceName.of(), "X"); + var typesInfo = TypesInfoInMemory.empty(); + typesInfo.defineSumType(sumType, List.of(), List.of()); + + var error = assertThrows( + SumTypeHasNoVariantsError.class, + () -> selectArbitraryVariant(sumType, typesInfo) + ); + + assertThat(error.type(), equalTo(sumType)); + } + + @Test + public void whenTypeIsInfiniteThenErrorIsThrown() { + var sumType = new SumType(NamespaceName.of(), "X"); + var variantType = new StructType(NamespaceName.of(), "Y"); + var typesInfo = TypesInfoInMemory.empty(); + typesInfo.defineSumType( + sumType, + List.of( + new SumVariant(0, variantType, variantType) + ), + List.of() + ); + typesInfo.defineStructType(variantType, Optional.of(List.of( + new Field("x", sumType, NullSource.INSTANCE) + ))); + + var error = assertThrows( + TypeIsInfiniteError.class, + () -> selectArbitraryVariant(sumType, typesInfo) + ); + + assertThat(error.type(), equalTo(sumType)); + } + + @Test + public void firstNonRecursiveVariantIsChosen() { + var sumType = new SumType(NamespaceName.of(), "X"); + var recursiveVariantType = new StructType(NamespaceName.of(), "Y"); + var nonRecursiveVariantType = new StructType(NamespaceName.of(), "Z"); + var typesInfo = TypesInfoInMemory.empty(); + typesInfo.defineSumType( + sumType, + List.of( + new SumVariant(0, recursiveVariantType, recursiveVariantType), + new SumVariant(1, nonRecursiveVariantType, nonRecursiveVariantType) + ), + List.of() + ); + typesInfo.defineStructType(recursiveVariantType, Optional.of(List.of( + new Field("x", sumType, NullSource.INSTANCE) + ))); + typesInfo.defineStructType(nonRecursiveVariantType, Optional.of(List.of( + new Field("x", NativeTypes.INT_64, NullSource.INSTANCE) + ))); + + var variant = selectArbitraryVariant(sumType, typesInfo); + + assertThat(variant, equalTo(nonRecursiveVariantType)); + } +} -- cgit v1.2.3