From 90792bb81602e61436988ccdf7345a04d54c2164 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Wed, 8 Nov 2023 20:38:02 +0000 Subject: Clarify typing around handler methods --- .../compiler/ast/typed/TypedNodeMatchers.java | 4 ++ .../typed/TypedTryHandlerMethodNodeMatcher.java | 58 +++++++++++++++++ .../compiler/ast/typed/TypedTryNodeMatcher.java | 2 +- .../compiler/ast/untyped/UntypedNodeMatchers.java | 4 ++ .../UntypedTryHandlerMethodNodeMatcher.java | 51 +++++++++++++++ .../ast/untyped/UntypedTryNodeMatcher.java | 2 +- .../shedlang/compiler/parser/ParserTryTests.java | 22 +++++-- .../compiler/typechecker/TypeCheckerTryTests.java | 73 ++++++++++++++++------ 8 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 src/test/java/org/shedlang/compiler/ast/typed/TypedTryHandlerMethodNodeMatcher.java create mode 100644 src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryHandlerMethodNodeMatcher.java (limited to 'src/test/java') diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java index 1c0cad87..faf6f31c 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java @@ -328,6 +328,10 @@ public class TypedNodeMatchers { return TypedTryNodeMatcher.empty(); } + public static TypedTryHandlerMethodNodeMatcher isTypedTryHandlerMethodNode() { + return TypedTryHandlerMethodNodeMatcher.empty(); + } + public static Matcher isTypedTupleNode( Matcher> elements ) { diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedTryHandlerMethodNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedTryHandlerMethodNodeMatcher.java new file mode 100644 index 00000000..bd19a7d6 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedTryHandlerMethodNodeMatcher.java @@ -0,0 +1,58 @@ +package org.shedlang.compiler.ast.typed; + +import org.pcollections.Empty; +import org.pcollections.PVector; +import org.shedlang.compiler.testing.MatcherBuilder; +import org.shedlang.compiler.types.FunctionType; +import org.zwobble.precisely.Matcher; + +import static org.zwobble.precisely.Matchers.equalTo; +import static org.zwobble.precisely.Matchers.has; + +public class TypedTryHandlerMethodNodeMatcher extends MatcherBuilder< + TypedTryHandlerMethodNode, + TypedTryHandlerMethodNode, + TypedTryHandlerMethodNodeMatcher +> { + public static TypedTryHandlerMethodNodeMatcher empty() { + return new TypedTryHandlerMethodNodeMatcher(Empty.vector()); + } + + public TypedTryHandlerMethodNodeMatcher( + PVector> submatchers + ) { + super(submatchers); + } + + @Override + protected Class expectedType() { + return TypedTryHandlerMethodNode.class; + } + + @Override + protected TypedTryHandlerMethodNodeMatcher create( + PVector> submatchers + ) { + return new TypedTryHandlerMethodNodeMatcher(submatchers); + } + + public TypedTryHandlerMethodNodeMatcher withBodyBlock(Matcher body) { + return with(has("body", x -> x.body().block(), body)); + } + + public TypedTryHandlerMethodNodeMatcher withFreeVariables(Matcher> freeVariables) { + return with(has("freeVariables", x -> x.body().freeVariables(), freeVariables)); + } + + public TypedTryHandlerMethodNodeMatcher withName(String name) { + return with(has("name", x -> x.name(), equalTo(name))); + } + + public TypedTryHandlerMethodNodeMatcher withParams(Matcher params) { + return with(has("params", x -> x.params(), params)); + } + + public TypedTryHandlerMethodNodeMatcher withType(FunctionType type) { + return with(has("type", x -> x.type(), equalTo(type))); + } +} 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 6998f85a..9bc549d3 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedTryNodeMatcher.java @@ -39,7 +39,7 @@ public class TypedTryNodeMatcher extends MatcherBuilder< } public TypedTryNodeMatcher withHandlerMethods( - Matcher> handlerMethods + Matcher> handlerMethods ) { return with(has("handlerMethods", x -> x.handlerMethods(), handlerMethods)); } diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java index d2df2a3c..41c51713 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java @@ -324,6 +324,10 @@ public class UntypedNodeMatchers { return UntypedTryNodeMatcher.empty(); } + public static UntypedTryHandlerMethodNodeMatcher isUntypedTryHandlerMethodNode() { + return UntypedTryHandlerMethodNodeMatcher.empty(); + } + public static Matcher isUntypedTupleNode( Matcher> elements ) { diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryHandlerMethodNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryHandlerMethodNodeMatcher.java new file mode 100644 index 00000000..8e08587d --- /dev/null +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryHandlerMethodNodeMatcher.java @@ -0,0 +1,51 @@ +package org.shedlang.compiler.ast.untyped; + +import org.pcollections.Empty; +import org.pcollections.PVector; +import org.shedlang.compiler.testing.MatcherBuilder; +import org.zwobble.precisely.Matcher; + +import static org.zwobble.precisely.Matchers.equalTo; +import static org.zwobble.precisely.Matchers.has; + +public class UntypedTryHandlerMethodNodeMatcher extends MatcherBuilder< + UntypedTryHandlerMethodNode, + UntypedTryHandlerMethodNode, + UntypedTryHandlerMethodNodeMatcher +> { + public static UntypedTryHandlerMethodNodeMatcher empty() { + return new UntypedTryHandlerMethodNodeMatcher(Empty.vector()); + } + + public UntypedTryHandlerMethodNodeMatcher( + PVector> submatchers + ) { + super(submatchers); + } + + @Override + protected Class expectedType() { + return UntypedTryHandlerMethodNode.class; + } + + @Override + protected UntypedTryHandlerMethodNodeMatcher create( + PVector> submatchers + ) { + return new UntypedTryHandlerMethodNodeMatcher(submatchers); + } + + public UntypedTryHandlerMethodNodeMatcher withParams( + Matcher params + ) { + return with(has("params", x -> x.params(), params)); + } + + public UntypedTryHandlerMethodNodeMatcher withBody(Matcher body) { + return with(has("body", x -> x.body(), body)); + } + + public UntypedTryHandlerMethodNodeMatcher withName(String name) { + return with(has("name", x -> x.name(), equalTo(name))); + } +} diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryNodeMatcher.java index eac572ab..b5df5392 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryNodeMatcher.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedTryNodeMatcher.java @@ -43,7 +43,7 @@ public class UntypedTryNodeMatcher extends MatcherBuilder< } public UntypedTryNodeMatcher withHandlerMethods( - Matcher> handlerMethods + Matcher> handlerMethods ) { return with(has("handlerMethods", x -> x.handlerMethods(), handlerMethods)); } diff --git a/src/test/java/org/shedlang/compiler/parser/ParserTryTests.java b/src/test/java/org/shedlang/compiler/parser/ParserTryTests.java index 0782cdd4..3a1a2ac2 100644 --- a/src/test/java/org/shedlang/compiler/parser/ParserTryTests.java +++ b/src/test/java/org/shedlang/compiler/parser/ParserTryTests.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.*; import static org.shedlang.compiler.parser.ParserTesting.parseString; import static org.zwobble.precisely.AssertThat.assertThat; -import static org.zwobble.precisely.Matchers.isSequence; +import static org.zwobble.precisely.Matchers.*; public class ParserTryTests { @Test @@ -15,9 +15,10 @@ public class ParserTryTests { 1; } handle State { fun read() { + resume 42; } - fun write() { + fun write(value) { } } """; @@ -32,10 +33,21 @@ public class ParserTryTests { ))) .withHandledEffect(isUntypedTypeLevelReferenceNode("State")) .withHandlerMethods(isSequence( - isUntypedFunctionDefinitionNode() - .withName("read"), - isUntypedFunctionDefinitionNode() + isUntypedTryHandlerMethodNode() + .withName("read") + .withParams(isUntypedParamsNode(isSequence(), isSequence())) + .withBody(isUntypedBlockNode(isSequence( + isUntypedResumeNode(isOptionalOf(isUntypedIntLiteralNode(42))) + ))), + isUntypedTryHandlerMethodNode() .withName("write") + .withParams(isUntypedParamsNode( + isSequence( + isUntypedParamNode("value", isOptionalEmpty()) + ), + isSequence() + )) + .withBody(isUntypedBlockNode(isSequence())) )) ); } diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java index f62780d4..c23511be 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTryTests.java @@ -1,10 +1,7 @@ package org.shedlang.compiler.typechecker; import org.junit.jupiter.api.Test; -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.ast.untyped.*; import org.shedlang.compiler.typechecker.errors.*; import org.shedlang.compiler.typechecker.errors.NoSuchFieldError; import org.shedlang.compiler.typechecker.typesets.TypeSets; @@ -197,7 +194,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .withName("mark") .build() ) @@ -230,7 +227,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .addPositionalParam("x", Untyped.typeLevelReference("String")) .addStatement(Untyped.resume()) .withName("mark") @@ -277,7 +274,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .addStatement(Untyped.resume()) .withName("mark") .build() @@ -304,7 +301,7 @@ public class TypeCheckerTryTests { result.value(), isTypedTryNode() .withHandlerMethods(isSequence( - isTypedFunctionDefinitionNode() + isTypedTryHandlerMethodNode() .withName("mark") .withBodyBlock(isTypedBlockNode(isSequence( isTypedResumeNode(isTypedUnitLiteralNode()) @@ -314,14 +311,14 @@ public class TypeCheckerTryTests { } @Test - public void whenHandlerMethodTypeHasWrongReturnTypeThenErrorIsThrown() { + public void whenHandlerMethodTypeHasWrongResumeTypeThenErrorIsThrown() { var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect"); var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .addStatement(Untyped.resume()) - .withReturnType(Untyped.typeLevelReference("String")) + .withResumeType(Untyped.typeLevelReference("String")) .withName("mark") .build() ) @@ -352,14 +349,14 @@ public class TypeCheckerTryTests { } @Test - public void whenHandlerMethodTypeHasNarrowerReturnTypeThanEffectMethodThenNarrowerTypeIsUsedToTypeCheckBody() { + public void whenHandlerMethodTypeHasNarrowerResumeTypeThanEffectMethodThenNarrowerTypeIsUsedToTypeCheckBody() { var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect"); var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .addStatement(Untyped.resume(Untyped.intLiteral(0))) - .withReturnType(Untyped.typeLevelReference("String")) + .withResumeType(Untyped.typeLevelReference("String")) .withName("mark") .build() ) @@ -390,12 +387,12 @@ public class TypeCheckerTryTests { } @Test - public void whenHandlerMethodTypeHasEffectThenEffectOfExpressionScopeIsIgnored() { + public void whenHandlerMethodTypeHasNarrowerEffectThanExpressionScopeThenEffectOfExpressionScopeIsIgnored() { var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect"); var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .withName("effectField") .withEffect(Untyped.typeLevelReference("Pure")) .addStatement(Untyped.resume( @@ -433,6 +430,44 @@ public class TypeCheckerTryTests { assertThat(error.actual(), equalTo(Effects.IO)); } + @Test + public void whenHandlerMethodTypeHasNonSubEffectOfExpressionScopeThenErrorIsThrown() { + var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect"); + var untyped = UntypedTryNode.builder() + .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) + .addHandlerMethod( + UntypedTryHandlerMethodNode.builder() + .withName("effectField") + .withEffect(Untyped.typeLevelReference("Io")) + .addStatement(Untyped.resume()) + .build() + ) + .build(); + var context = TypeCheckerContext.EMPTY + .enterFunction( + P.map( + Map.entry(handledEffect.name(), Variable.defined(Types.effectType(handledEffect), VariableType.CONSTANT)), + Map.entry("Io", Variable.defined(Types.effectType(Effects.IO), VariableType.CONSTANT)) + ), + Effects.NONE, + Optional.empty() + ) + .addUserDefinedEffectInfo( + handledEffect, + UserDefinedEffectInfo.builder() + .addField("effectField", FunctionType.builder().withReturnType(Types.UNIT).build()) + .build() + ); + + var error = assertThrows( + UnexpectedEffectError.class, + () -> typeCheckExpression(untyped, context) + ); + + assertThat(error.allowed(), equalTo(Effects.NONE)); + assertThat(error.actual(), equalTo(Effects.IO)); + } + @Test public void whenEffectIsNotAllowedInExpressionScopeAndIsNotHandledEffectThenEffectIsNotAllowedInHandlerScope() { var handledEffect = Effects.userDefined(NAMESPACE_NAME, "HandledEffect"); @@ -440,7 +475,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .withName("effectField") .addStatement(Untyped.expressionStatement( UntypedCallNode.builder() @@ -484,7 +519,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .withName("effectField") .addStatement(Untyped.resume( UntypedCallNode.builder() @@ -523,7 +558,7 @@ public class TypeCheckerTryTests { var untyped = UntypedTryNode.builder() .withHandledEffect(Untyped.typeLevelReference(handledEffect.name())) .addHandlerMethod( - UntypedFunctionDefinitionNode.builder() + UntypedTryHandlerMethodNode.builder() .withName("effectField") .addStatement(Untyped.expressionStatement( UntypedCallNode.builder() -- cgit v1.2.3