diff options
| author | Michael Williamson <mike@zwobble.org> | 2023-11-05 20:54:40 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2023-11-05 20:54:40 +0000 |
| commit | f5dc163e766e389ddec4f5bbe4620c583391cfec (patch) | |
| tree | bb9a38f08b8d6ebcb716abdc33d0d7e725f4a989 /src/test/java | |
| parent | f98ea69c9d401217af2a6a5d7ab42dae0ae65b6d (diff) | |
Type-check effect handler fields
Diffstat (limited to 'src/test/java')
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())) + )) + ); + } } |
