From abf50f46fc99affe0f3b97e3bb2016648362ebc4 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Tue, 7 Nov 2023 17:17:57 +0000 Subject: Type check resume statements properly --- .../org/shedlang/compiler/ast/untyped/Untyped.java | 4 + .../TypeCheckerFunctionResumeTests.java | 116 +++++++++++++++++++++ .../TypeCheckerFunctionReturnTests.java | 2 +- .../compiler/typechecker/TypeCheckerTryTests.java | 2 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionResumeTests.java (limited to 'src/test/java/org') 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 adae966a..51b6c2b6 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java @@ -157,6 +157,10 @@ public class Untyped { return new UntypedResumeNode(Optional.empty(), Sources.NULL); } + public static UntypedResumeNode resume(UntypedExpressionNode expression) { + return new UntypedResumeNode(Optional.of(expression), Sources.NULL); + } + public static UntypedStringLiteralNode stringLiteral(String value) { return new UntypedStringLiteralNode(value, Sources.NULL); } diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionResumeTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionResumeTests.java new file mode 100644 index 00000000..c3326020 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionResumeTests.java @@ -0,0 +1,116 @@ +package org.shedlang.compiler.typechecker; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.untyped.Untyped; +import org.shedlang.compiler.ast.untyped.UntypedFunctionExpressionNode; +import org.shedlang.compiler.typechecker.errors.CannotResumeOutsideOfEffectHandlerFieldError; +import org.shedlang.compiler.typechecker.errors.UnexpectedTypeError; +import org.shedlang.compiler.typechecker.typesets.TypeSets; +import org.shedlang.compiler.types.Effects; +import org.shedlang.compiler.types.FunctionType; +import org.shedlang.compiler.types.NamespaceName; +import org.shedlang.compiler.types.Types; +import org.shedlang.compiler.util.P; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.*; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.equalTo; + +public class TypeCheckerFunctionResumeTests { + @Test + public void expressionIsTypeChecked() { + var untyped = Untyped.resume(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.STRING)); + + var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); + + assertThat(typed.value(), isTypedResumeNode(isTypedStringLiteralNode("hello"))); + } + + @Test + public void whenExpressionIsAbsentThenUnitIsReturned() { + var untyped = Untyped.resume(); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.UNIT)); + + var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); + + assertThat(typed.value(), isTypedResumeNode(isTypedUnitLiteralNode())); + } + + @Test + public void whenNotInFunctionThenResumeIsForbidden() { + var untyped = Untyped.resume(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterNamespace(NamespaceName.of("Example"), P.map()); + + assertThrows( + CannotResumeOutsideOfEffectHandlerFieldError.class, + () -> TypeChecker.typeCheckFunctionStatement(untyped, context) + ); + } + + @Test + public void whenInFunctionThenResumeIsForbidden() { + var untyped = Untyped.resume(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.explicit(Types.STRING)); + + assertThrows( + CannotResumeOutsideOfEffectHandlerFieldError.class, + () -> TypeChecker.typeCheckFunctionStatement(untyped, context) + ); + } + + @Test + public void canResumeWIthSubtypeOfResumeType() { + var untyped = Untyped.resume(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.TOP)); + + var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); + + assertThat(typed.value(), isTypedResumeNode(isTypedStringLiteralNode("hello"))); + } + + @Test + public void cannotResumeWithNonSubtypeOfResumeType() { + var untyped = Untyped.resume(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.INT)); + + var error = assertThrows( + UnexpectedTypeError.class, + () -> TypeChecker.typeCheckFunctionStatement(untyped, context) + ); + + assertThat(error.expected(), equalTo(TypeSets.type(Types.INT))); + assertThat(error.actual(), equalTo(Types.STRING)); + } + + @Test + public void resumeTypeIsUsedAsTypeHintForExpression() { + var untyped = Untyped.resume( + UntypedFunctionExpressionNode.builder() + .addPositionalParam("x", Optional.empty()) + .withoutReturnType() + .build() + ); + var resumeType = FunctionType.builder() + .addPositionalParam(Types.STRING) + .withReturnType(Types.UNIT) + .build(); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(resumeType)); + + var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); + + assertThat(typed.value(), isTypedResumeNode( + isTypedFunctionExpressionNode().withType(resumeType) + )); + } +} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java index f0c2daa7..8fe03d32 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java @@ -59,7 +59,7 @@ public class TypeCheckerFunctionReturnTests { public void whenInEffectHandlerThenReturnIsForbidden() { var untyped = Untyped.functionReturn(Untyped.stringLiteral("hello")); var context = TypeCheckerContext.EMPTY - .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.UNIT)); + .enterFunction(P.map(), Effects.NONE, ReturnTypeState.effectHandler(Types.STRING)); assertThrows( CannotReturnFromEffectHandlerFieldError.class, diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java index cffae60a..169816dc 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java @@ -366,7 +366,7 @@ public class TypeCheckerTryTests { .addHandlerField( UntypedFunctionDefinitionNode.builder() .withName("effectField") - .addStatement(Untyped.expressionStatement( + .addStatement(Untyped.resume( UntypedCallNode.builder() .withReceiver(Untyped.reference("f")) .withHasEffects(true) -- cgit v1.2.3