aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2023-11-05 20:54:40 +0000
committerMichael Williamson <mike@zwobble.org>2023-11-05 20:54:40 +0000
commitf5dc163e766e389ddec4f5bbe4620c583391cfec (patch)
treebb9a38f08b8d6ebcb716abdc33d0d7e725f4a989 /src/test/java
parentf98ea69c9d401217af2a6a5d7ab42dae0ae65b6d (diff)
Type-check effect handler fields
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java6
-rw-r--r--src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java4
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java95
3 files changed, 102 insertions, 3 deletions
diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java
index b6a124b9..19ac0d2a 100644
--- a/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java
+++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java
@@ -37,4 +37,10 @@ public class TypedTryNodeMatcher extends MatcherBuilder<
) {
return with(has("body", x -> x.body(), body));
}
+
+ public TypedTryNodeMatcher withHandlers(
+ Matcher<? super Iterable<TypedFunctionDefinitionNode>> handlers
+ ) {
+ return with(has("handlers", x -> x.handlers(), handlers));
+ }
}
diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java
index 07f3021d..adae966a 100644
--- a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java
+++ b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java
@@ -153,6 +153,10 @@ public class Untyped {
return new UntypedReferenceNode(name, Sources.NULL);
}
+ public static UntypedResumeNode resume() {
+ return new UntypedResumeNode(Optional.empty(), Sources.NULL);
+ }
+
public static UntypedStringLiteralNode stringLiteral(String value) {
return new UntypedStringLiteralNode(value, Sources.NULL);
}
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java
index c7a98211..f509331c 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java
@@ -5,10 +5,9 @@ import org.shedlang.compiler.ast.untyped.Untyped;
import org.shedlang.compiler.ast.untyped.UntypedCallNode;
import org.shedlang.compiler.ast.untyped.UntypedFunctionDefinitionNode;
import org.shedlang.compiler.ast.untyped.UntypedTryNode;
-import org.shedlang.compiler.typechecker.errors.MissingEffectHandlerFieldError;
+import org.shedlang.compiler.typechecker.errors.*;
import org.shedlang.compiler.typechecker.errors.NoSuchFieldError;
-import org.shedlang.compiler.typechecker.errors.NotUserDefinedEffectError;
-import org.shedlang.compiler.typechecker.errors.UnexpectedEffectError;
+import org.shedlang.compiler.typechecker.typesets.TypeSets;
import org.shedlang.compiler.types.*;
import org.shedlang.compiler.util.P;
@@ -224,4 +223,94 @@ public class TypeCheckerTryTests {
assertThat(error.fieldName(), equalTo("mark"));
}
+
+ @Test
+ public void whenHandlerFieldTypeIsNotSubtypeOfEffectFieldTypeThenErrorIsThrown() {
+ var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect");
+ var untyped = UntypedTryNode.builder()
+ .withHandledEffect(Untyped.typeLevelReference(handledEffect.name()))
+ .addHandlerField(
+ UntypedFunctionDefinitionNode.builder()
+ .addPositionalParam("x", Untyped.typeLevelReference("String"))
+ // TODO: support type-checking resume
+ //.addStatement(Untyped.resume())
+ .withName("mark")
+ .build()
+ )
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .addUserDefinedEffectInfo(
+ handledEffect,
+ UserDefinedEffectInfo.builder()
+ .addField("mark", FunctionType.builder().withReturnType(Types.UNIT).build())
+ .build()
+ )
+ .enterFunction(
+ P.map(
+ Map.entry("String", Variable.defined(Types.metaType(Types.STRING), VariableType.CONSTANT)),
+ Map.entry(handledEffect.name(), Variable.defined(Types.effectType(handledEffect), VariableType.CONSTANT))
+ ),
+ Effects.NONE,
+ Optional.empty()
+ );
+
+ var error = assertThrows(
+ UnexpectedTypeError.class,
+ () -> typeCheckExpression(untyped, context)
+ );
+
+ assertThat(error.expected(), equalTo(TypeSets.type(
+ FunctionType.builder()
+ .withReturnType(Types.UNIT)
+ .build()
+ )));
+ assertThat(error.actual(), equalTo(
+ FunctionType.builder()
+ .addPositionalParam(Types.STRING)
+ .withReturnType(Types.UNIT)
+ .build()
+ ));
+ }
+
+ @Test
+ public void whenHandlerFieldTypeIsSubtypeOfEffectFieldTypeThenHandlerFieldTypeChecks() {
+ var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect");
+ var untyped = UntypedTryNode.builder()
+ .withHandledEffect(Untyped.typeLevelReference(handledEffect.name()))
+ .addHandlerField(
+ UntypedFunctionDefinitionNode.builder()
+ // TODO: support type-checking resume
+ //.addStatement(Untyped.resume())
+ .withName("mark")
+ .build()
+ )
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .addUserDefinedEffectInfo(
+ handledEffect,
+ UserDefinedEffectInfo.builder()
+ .addField("mark", FunctionType.builder().withReturnType(Types.UNIT).build())
+ .build()
+ )
+ .enterFunction(
+ P.map(
+ Map.entry("String", Variable.defined(Types.metaType(Types.STRING), VariableType.CONSTANT)),
+ Map.entry(handledEffect.name(), Variable.defined(Types.effectType(handledEffect), VariableType.CONSTANT))
+ ),
+ Effects.NONE,
+ Optional.empty()
+ );
+
+ var result = typeCheckExpression(untyped, context);
+
+ assertThat(
+ result.value(),
+ isTypedTryNode()
+ .withHandlers(isSequence(
+ isTypedFunctionDefinitionNode()
+ .withName("mark")
+ .withBodyBlock(isTypedBlockNode(isSequence()))
+ ))
+ );
+ }
}