From e87797aff31cd99a6a08e46cb52aff61e1c7d75d Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 9 Jul 2023 12:20:13 +0100 Subject: Handle block returns properly --- .../org/shedlang/compiler/ast/typed/Typed.java | 4 ++++ .../compiler/ast/typed/TypedNodeMatchers.java | 27 ++++++++++++++-------- .../org/shedlang/compiler/ast/untyped/Untyped.java | 6 ++++- .../compiler/ast/untyped/UntypedNodeMatchers.java | 9 ++++++++ .../parser/ParserFunctionDefinitionTests.java | 6 ++--- .../stackir/StackCompilerBlockReturnTests.java | 21 +++++++++++++++++ .../StackCompilerFunctionDefinitionTests.java | 24 +++++++++++++++++++ .../stackir/StackCompilerFunctionReturnTests.java | 22 ++++++++++++++++++ .../compiler/stackir/StackCompilerReturnTests.java | 22 ------------------ .../typechecker/TypeCheckerBlockReturnTests.java | 24 +++++++++++++++++++ .../TypeCheckerFunctionDefinitionTests.java | 16 ++++++++++++- .../TypeCheckerFunctionReturnTests.java | 12 +++++----- 12 files changed, 151 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/shedlang/compiler/stackir/StackCompilerBlockReturnTests.java create mode 100644 src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionReturnTests.java delete mode 100644 src/test/java/org/shedlang/compiler/stackir/StackCompilerReturnTests.java create mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerBlockReturnTests.java (limited to 'src/test/java') diff --git a/src/test/java/org/shedlang/compiler/ast/typed/Typed.java b/src/test/java/org/shedlang/compiler/ast/typed/Typed.java index ab575632..d52f67a5 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/Typed.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/Typed.java @@ -13,6 +13,10 @@ public class Typed { private Typed() { } + public static TypedBlockReturnNode blockReturn(TypedExpressionNode expression) { + return new TypedBlockReturnNode(expression, Sources.NULL); + } + public static TypedBoolLiteralNode boolLiteral(boolean value) { return new TypedBoolLiteralNode(value, Sources.NULL); } 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 befeb326..56f73a12 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java @@ -29,6 +29,15 @@ public class TypedNodeMatchers { ); } + public static Matcher isTypedBlockReturnNode( + Matcher expression + ) { + return instanceOf( + TypedBlockReturnNode.class, + has("expression", x -> x.expression(), expression) + ); + } + public static Matcher isTypedBoolLiteralNode(boolean value) { return instanceOf( TypedBoolLiteralNode.class, @@ -53,6 +62,15 @@ public class TypedNodeMatchers { return TypedFunctionDefinitionNodeMatcher.empty(); } + public static Matcher isTypedFunctionReturnNode( + Matcher expression + ) { + return instanceOf( + TypedFunctionReturnNode.class, + has("expression", x -> x.expression(), expression) + ); + } + public static Matcher isTypedIntAddNode( Matcher left, Matcher right @@ -144,15 +162,6 @@ public class TypedNodeMatchers { ); } - public static Matcher isTypedReturnNode( - Matcher expression - ) { - return instanceOf( - TypedFunctionReturnNode.class, - has("expression", x -> x.expression(), expression) - ); - } - public static Matcher isTypedStringAddNode( Matcher left, Matcher right 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 db7037ce..24ca7f87 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java @@ -22,6 +22,10 @@ public class Untyped { ); } + public static UntypedBlockReturnNode blockReturn(UntypedExpressionNode expression) { + return new UntypedBlockReturnNode(expression, Sources.NULL); + } + public static UntypedBoolLiteralNode boolLiteral(boolean value) { return new UntypedBoolLiteralNode(value, Sources.NULL); } @@ -62,7 +66,7 @@ public class Untyped { return new UntypedExpressionStatementNode(expression, Sources.NULL); } - public static UntypedFunctionReturnNode functionReturn_(UntypedExpressionNode expression) { + public static UntypedFunctionReturnNode functionReturn(UntypedExpressionNode expression) { return new UntypedFunctionReturnNode(expression, Sources.NULL); } 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 54edfc9d..dce29e55 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java @@ -34,6 +34,15 @@ public class UntypedNodeMatchers { ); } + public static Matcher isUntypedBlockReturnNode( + Matcher expression + ) { + return instanceOf( + UntypedBlockReturnNode.class, + has("expression", x -> x.expression(), expression) + ); + } + public static UntypedCallNodeMatcher isUntypedCallNode() { return UntypedCallNodeMatcher.empty(); } diff --git a/src/test/java/org/shedlang/compiler/parser/ParserFunctionDefinitionTests.java b/src/test/java/org/shedlang/compiler/parser/ParserFunctionDefinitionTests.java index 88bde61d..279309f6 100644 --- a/src/test/java/org/shedlang/compiler/parser/ParserFunctionDefinitionTests.java +++ b/src/test/java/org/shedlang/compiler/parser/ParserFunctionDefinitionTests.java @@ -205,7 +205,7 @@ public class ParserFunctionDefinitionTests { } @Test - public void canParseFunctionWithBodyWithImplicitReturn() { + public void canParseFunctionWithBodyWithBlockReturn() { var source = """ fun f() -> Unit { a; @@ -221,13 +221,13 @@ public class ParserFunctionDefinitionTests { .withBody(isSequence( isUntypedExpressionStatementNode(isUntypedReferenceNode("a")), isUntypedExpressionStatementNode(isUntypedReferenceNode("b")), - isUntypedFunctionReturnNode(isUntypedReferenceNode("c")) + isUntypedBlockReturnNode(isUntypedReferenceNode("c")) )) ); } @Test - public void implicitReturnEndsFunction() { + public void blockReturnEndsFunction() { var source = """ fun f() -> Unit { a; diff --git a/src/test/java/org/shedlang/compiler/stackir/StackCompilerBlockReturnTests.java b/src/test/java/org/shedlang/compiler/stackir/StackCompilerBlockReturnTests.java new file mode 100644 index 00000000..7ff57a10 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/stackir/StackCompilerBlockReturnTests.java @@ -0,0 +1,21 @@ +package org.shedlang.compiler.stackir; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.typed.Typed; + +import static org.shedlang.compiler.stackir.instructions.InstructionMatchers.isPushStringInstruction; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.isSequence; + +public class StackCompilerBlockReturnTests { + @Test + public void expressionIsEvaluated() { + var node = Typed.blockReturn(Typed.stringLiteral("hello")); + + var instructions = StackCompiler.compileFunctionStatement(node); + + assertThat(instructions, isSequence( + isPushStringInstruction("hello") + )); + } +} diff --git a/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionDefinitionTests.java b/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionDefinitionTests.java index 28974ea9..051b0fb6 100644 --- a/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionDefinitionTests.java +++ b/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionDefinitionTests.java @@ -46,4 +46,28 @@ public class StackCompilerFunctionDefinitionTests { isLocalStoreInstruction("f") )); } + + @Test + public void whenLastStatementIsBlockReturnThenThatValueIsReturned() { + var node = TypedFunctionDefinitionNode.builder() + .withName("f") + .addPositionalParam("x", Typed.typeLevelReference("String", Types.STRING)) + .addNamedParam("y", Typed.typeLevelReference("Int", Types.INT)) + .addFreeVariable("z") + .addStatement(Typed.blockReturn(Typed.stringLiteral("hello"))) + .build(); + + var instructions = StackCompiler.compileNamespaceStatement(node); + + assertThat(instructions, isSequence( + instanceOf( + DefineFunctionInstruction.class, + has("body", x -> x.body(), isSequence( + isPushStringInstruction("hello"), + isReturnInstruction() + )) + ), + isLocalStoreInstruction("f") + )); + } } diff --git a/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionReturnTests.java b/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionReturnTests.java new file mode 100644 index 00000000..0baf9acb --- /dev/null +++ b/src/test/java/org/shedlang/compiler/stackir/StackCompilerFunctionReturnTests.java @@ -0,0 +1,22 @@ +package org.shedlang.compiler.stackir; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.typed.Typed; + +import static org.shedlang.compiler.stackir.instructions.InstructionMatchers.*; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.isSequence; + +public class StackCompilerFunctionReturnTests { + @Test + public void expressionIsEvaluatedAndTheResultDiscarded() { + var node = Typed.functionReturn(Typed.stringLiteral("hello")); + + var instructions = StackCompiler.compileFunctionStatement(node); + + assertThat(instructions, isSequence( + isPushStringInstruction("hello"), + isReturnInstruction() + )); + } +} diff --git a/src/test/java/org/shedlang/compiler/stackir/StackCompilerReturnTests.java b/src/test/java/org/shedlang/compiler/stackir/StackCompilerReturnTests.java deleted file mode 100644 index dcb1af47..00000000 --- a/src/test/java/org/shedlang/compiler/stackir/StackCompilerReturnTests.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.shedlang.compiler.stackir; - -import org.junit.jupiter.api.Test; -import org.shedlang.compiler.ast.typed.Typed; - -import static org.shedlang.compiler.stackir.instructions.InstructionMatchers.*; -import static org.zwobble.precisely.AssertThat.assertThat; -import static org.zwobble.precisely.Matchers.isSequence; - -public class StackCompilerReturnTests { - @Test - public void expressionIsEvaluatedAndTheResultDiscarded() { - var node = Typed.functionReturn(Typed.stringLiteral("hello")); - - var instructions = StackCompiler.compileFunctionStatement(node); - - assertThat(instructions, isSequence( - isPushStringInstruction("hello"), - isReturnInstruction() - )); - } -} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerBlockReturnTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerBlockReturnTests.java new file mode 100644 index 00000000..bf4a948f --- /dev/null +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerBlockReturnTests.java @@ -0,0 +1,24 @@ +package org.shedlang.compiler.typechecker; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.untyped.Untyped; +import org.shedlang.compiler.types.Effects; +import org.shedlang.compiler.types.Types; +import org.shedlang.compiler.util.P; + +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedBlockReturnNode; +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedStringLiteralNode; +import static org.zwobble.precisely.AssertThat.assertThat; + +public class TypeCheckerBlockReturnTests { + @Test + public void expressionIsTypeChecked() { + var untyped = Untyped.blockReturn(Untyped.stringLiteral("hello")); + var context = TypeCheckerContext.EMPTY + .enterFunction(P.map(), Effects.NONE, Types.STRING); + + var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); + + assertThat(typed.value(), isTypedBlockReturnNode(isTypedStringLiteralNode("hello"))); + } +} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java index 2ff53f74..4c640f30 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java @@ -205,7 +205,7 @@ public class TypeCheckerFunctionDefinitionTests { public void bodyHasReturnTypeAsDefinedInSignature() { var untyped = UntypedFunctionDefinitionNode.builder() .withReturnType(Untyped.typeLevelReference("String")) - .addStatement(Untyped.functionReturn_(Untyped.intLiteral(42))) + .addStatement(Untyped.functionReturn(Untyped.intLiteral(42))) .build(); var context = TypeCheckerContext.EMPTY .enterNamespaceWithDefinedVariables(P.map( @@ -253,6 +253,20 @@ public class TypeCheckerFunctionDefinitionTests { assertThat(error.actual(), equalTo(Types.UNIT)); } + @Test + public void givenReturnTypeIsNotUnitWhenBodyHasBlockReturnOfCorrectTypeThenFunctionTypeChecks() { + var untyped = UntypedFunctionDefinitionNode.builder() + .withReturnType(Untyped.typeLevelReference("String")) + .addStatement(Untyped.blockReturn(Untyped.stringLiteral("hello"))) + .build(); + var context = TypeCheckerContext.EMPTY + .enterNamespaceWithDefinedVariables(P.map( + Map.entry("String", Types.metaType(Types.STRING)) + )); + + TypeChecker.typeCheckNamespaceStatement(untyped, context); + } + @Test public void freeVariablesIncludeVariablesReferencedInFunctionButDefinedOutsideOfFunction() { var untyped = UntypedFunctionDefinitionNode.builder() diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java index bea2d4d9..2e4b4206 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionReturnTests.java @@ -17,18 +17,18 @@ import static org.zwobble.precisely.Matchers.equalTo; public class TypeCheckerFunctionReturnTests { @Test public void expressionIsTypeChecked() { - var untyped = Untyped.functionReturn_(Untyped.stringLiteral("hello")); + var untyped = Untyped.functionReturn(Untyped.stringLiteral("hello")); var context = TypeCheckerContext.EMPTY .enterFunction(P.map(), Effects.NONE, Types.STRING); var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); - assertThat(typed.value(), isTypedReturnNode(isTypedStringLiteralNode("hello"))); + assertThat(typed.value(), isTypedFunctionReturnNode(isTypedStringLiteralNode("hello"))); } @Test public void whenNotInFunctionThenReturnIsForbidden() { - var untyped = Untyped.functionReturn_(Untyped.stringLiteral("hello")); + var untyped = Untyped.functionReturn(Untyped.stringLiteral("hello")); var context = TypeCheckerContext.EMPTY .enterNamespace(P.map()); @@ -40,18 +40,18 @@ public class TypeCheckerFunctionReturnTests { @Test public void canReturnSubtypeOfReturnType() { - var untyped = Untyped.functionReturn_(Untyped.stringLiteral("hello")); + var untyped = Untyped.functionReturn(Untyped.stringLiteral("hello")); var context = TypeCheckerContext.EMPTY .enterFunction(P.map(), Effects.NONE, Types.TOP); var typed = TypeChecker.typeCheckFunctionStatement(untyped, context); - assertThat(typed.value(), isTypedReturnNode(isTypedStringLiteralNode("hello"))); + assertThat(typed.value(), isTypedFunctionReturnNode(isTypedStringLiteralNode("hello"))); } @Test public void cannotReturnNonSubtypeOfReturnType() { - var untyped = Untyped.functionReturn_(Untyped.stringLiteral("hello")); + var untyped = Untyped.functionReturn(Untyped.stringLiteral("hello")); var context = TypeCheckerContext.EMPTY .enterFunction(P.map(), Effects.NONE, Types.INT); -- cgit v1.2.3