aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2023-10-28 23:50:09 +0100
committerMichael Williamson <mike@zwobble.org>2023-10-28 23:50:09 +0100
commitba87d647e68a52f92dc859008f0ed20fe72659a0 (patch)
tree16dd1bb1d5d51b0487e1225366f35a5622510fae /src/test/java
parent052177994ca489078cf6057064ea2a97b35fe5d1 (diff)
Type check function expressions
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionBodyNodeMatcher.java46
-rw-r--r--src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionExpressionNodeMatcher.java64
-rw-r--r--src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java8
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionBodyTests.java337
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java318
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionExpressionTests.java41
6 files changed, 498 insertions, 316 deletions
diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionBodyNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionBodyNodeMatcher.java
new file mode 100644
index 00000000..d392ec4d
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionBodyNodeMatcher.java
@@ -0,0 +1,46 @@
+package org.shedlang.compiler.ast.typed;
+
+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.has;
+
+public class TypedFunctionBodyNodeMatcher extends MatcherBuilder<
+ TypedFunctionBodyNode,
+ TypedFunctionBodyNode,
+ TypedFunctionBodyNodeMatcher
+> {
+ public static TypedFunctionBodyNodeMatcher empty() {
+ return new TypedFunctionBodyNodeMatcher(Empty.vector());
+ }
+
+ public TypedFunctionBodyNodeMatcher(
+ PVector<Matcher<? super TypedFunctionBodyNode>> submatchers
+ ) {
+ super(submatchers);
+ }
+
+ @Override
+ protected Class<TypedFunctionBodyNode> expectedType() {
+ return TypedFunctionBodyNode.class;
+ }
+
+ @Override
+ protected TypedFunctionBodyNodeMatcher create(
+ PVector<Matcher<? super TypedFunctionBodyNode>> submatchers
+ ) {
+ return new TypedFunctionBodyNodeMatcher(submatchers);
+ }
+
+ public TypedFunctionBodyNodeMatcher withBlock(Matcher<TypedBlockNode> block) {
+ return with(has("body", x -> x.block(), block));
+ }
+
+ public TypedFunctionBodyNodeMatcher withFreeVariables(
+ Matcher<Iterable<FreeVariable>> freeVariables
+ ) {
+ return with(has("freeVariables", x -> x.freeVariables(), freeVariables));
+ }
+}
diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionExpressionNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionExpressionNodeMatcher.java
new file mode 100644
index 00000000..a2c047ec
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedFunctionExpressionNodeMatcher.java
@@ -0,0 +1,64 @@
+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 java.util.Optional;
+
+import static org.zwobble.precisely.Matchers.equalTo;
+import static org.zwobble.precisely.Matchers.has;
+
+public class TypedFunctionExpressionNodeMatcher extends MatcherBuilder<
+ TypedExpressionNode,
+ TypedFunctionExpressionNode,
+ TypedFunctionExpressionNodeMatcher
+> {
+ public static TypedFunctionExpressionNodeMatcher empty() {
+ return new TypedFunctionExpressionNodeMatcher(Empty.vector());
+ }
+
+ public TypedFunctionExpressionNodeMatcher(
+ PVector<Matcher<? super TypedFunctionExpressionNode>> submatchers
+ ) {
+ super(submatchers);
+ }
+
+ @Override
+ protected Class<TypedFunctionExpressionNode> expectedType() {
+ return TypedFunctionExpressionNode.class;
+ }
+
+ @Override
+ protected TypedFunctionExpressionNodeMatcher create(
+ PVector<Matcher<? super TypedFunctionExpressionNode>> submatchers
+ ) {
+ return new TypedFunctionExpressionNodeMatcher(submatchers);
+ }
+
+ public TypedFunctionExpressionNodeMatcher withBodyBlock(Matcher<TypedBlockNode> body) {
+ return with(has("body", x -> x.body().block(), body));
+ }
+
+ public TypedFunctionExpressionNodeMatcher withEffect(Matcher<Optional<? extends TypedTypeLevelExpressionNode<?>>> effect) {
+ return with(has("effect", x -> x.signature().effect(), effect));
+ }
+
+ public TypedFunctionExpressionNodeMatcher withFreeVariables(Matcher<Iterable<FreeVariable>> freeVariables) {
+ return with(has("freeVariables", x -> x.body().freeVariables(), freeVariables));
+ }
+
+ public TypedFunctionExpressionNodeMatcher withParams(Matcher<TypedParamsNode> params) {
+ return with(has("params", x -> x.signature().params(), params));
+ }
+
+ public TypedFunctionExpressionNodeMatcher withReturnType(Matcher<TypedTypeLevelExpressionNode<?>> returnType) {
+ return with(has("returnType", x -> x.signature().returnType(), returnType));
+ }
+
+ public TypedFunctionExpressionNodeMatcher withType(FunctionType type) {
+ return with(has("type", x -> x.signature().type(), equalTo(type)));
+ }
+}
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 2723883e..8a438233 100644
--- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java
+++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java
@@ -112,10 +112,18 @@ public class TypedNodeMatchers {
);
}
+ public static TypedFunctionBodyNodeMatcher isTypedFunctionBodyNode() {
+ return TypedFunctionBodyNodeMatcher.empty();
+ }
+
public static TypedFunctionDefinitionNodeMatcher isTypedFunctionDefinitionNode() {
return TypedFunctionDefinitionNodeMatcher.empty();
}
+ public static TypedFunctionExpressionNodeMatcher isTypedFunctionExpressionNode() {
+ return TypedFunctionExpressionNodeMatcher.empty();
+ }
+
public static Matcher<TypedFunctionStatementNode> isTypedFunctionReturnNode(
Matcher<TypedExpressionNode> expression
) {
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionBodyTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionBodyTests.java
new file mode 100644
index 00000000..be83f50d
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionBodyTests.java
@@ -0,0 +1,337 @@
+package org.shedlang.compiler.typechecker;
+
+import org.junit.jupiter.api.Test;
+import org.shedlang.compiler.ast.typed.Typed;
+import org.shedlang.compiler.ast.typed.TypedFunctionSignatureNode;
+import org.shedlang.compiler.ast.untyped.Untyped;
+import org.shedlang.compiler.ast.untyped.UntypedCallNode;
+import org.shedlang.compiler.typechecker.errors.DuplicateVariableNameError;
+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.Types;
+import org.shedlang.compiler.util.P;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.*;
+import static org.shedlang.compiler.typechecker.TypeChecker.typeCheckFunctionBody;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.*;
+
+public class TypeCheckerFunctionBodyTests {
+ @Test
+ public void canTypeCheckEmptyBody() {
+ var untyped = Untyped.block(List.of());
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode().withBlock(isTypedBlockNode(isSequence()))
+ );
+ }
+
+ @Test
+ public void paramsMustHaveUniqueNames() {
+ var untyped = Untyped.block(List.of());
+ var signature = TypedFunctionSignatureNode.builder()
+ .addPositionalParam("x", Typed.typeLevelReference("String", Types.STRING))
+ .addPositionalParam("x", Typed.typeLevelReference("Int", Types.INT))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var error = assertThrows(
+ DuplicateVariableNameError.class,
+ () -> typeCheckFunctionBody(untyped, signature, context)
+ );
+
+ assertThat(error.variableName(), equalTo("x"));
+ }
+
+ @Test
+ public void blockIsTypeChecked() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.stringLiteral("hello"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withBlock(isTypedBlockNode(isSequence(
+ isTypedExpressionStatementNode(isTypedStringLiteralNode("hello"))
+ )))
+ );
+ }
+
+ @Test
+ public void canReferencePositionalArgInBody() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.reference("x"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .addPositionalParam("x", Typed.typeLevelReference("String", Types.STRING))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withBlock(isTypedBlockNode(isSequence(
+ isTypedExpressionStatementNode(isTypedReferenceNode("x", Types.STRING))
+ )))
+ );
+ }
+
+ @Test
+ public void canReferenceNamedArgInBody() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.reference("x"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .addNamedParam("x", Typed.typeLevelReference("String", Types.STRING))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withBlock(isTypedBlockNode(isSequence(
+ isTypedExpressionStatementNode(isTypedReferenceNode("x", Types.STRING))
+ )))
+ );
+ }
+
+ @Test
+ public void bodyHasEffectAsDefinedInSignature() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(
+ UntypedCallNode.builder()
+ .withReceiver(Untyped.reference("f"))
+ .withHasEffects(true)
+ .build()
+ )
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withEffect(Typed.typeLevelReference("Io", Effects.IO))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var functionType = FunctionType.builder()
+ .withEffect(Effects.IO)
+ .withReturnType(Types.UNIT)
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterGlobal(P.map(
+ Map.entry("f", functionType)
+ ));
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withBlock(isTypedBlockNode(isSequence(
+ isTypedExpressionStatementNode(isTypedCallNode().withHasEffects(true))
+ )))
+ );
+ }
+
+ @Test
+ public void bodyHasReturnTypeAsDefinedInSignature() {
+ var untyped = Untyped.block(List.of(
+ Untyped.functionReturn(Untyped.intLiteral(42))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("String", Types.STRING))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var error = assertThrows(
+ UnexpectedTypeError.class,
+ () -> typeCheckFunctionBody(untyped, signature, context)
+ );
+
+ assertThat(error.expected(), equalTo(TypeSets.type(Types.STRING)));
+ assertThat(error.actual(), equalTo(Types.INT));
+ }
+
+ @Test
+ public void givenReturnTypeIsUnitWhenBodyHasNoReturnThenFunctionTypeChecks() {
+ var untyped = Untyped.block(List.of());
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ typeCheckFunctionBody(untyped, signature, context);
+ }
+
+ @Test
+ public void givenReturnTypeIsNotUnitWhenBodyHasNoReturnThenFunctionFailsTypeCheck() {
+ var untyped = Untyped.block(List.of());
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("String", Types.STRING))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var error = assertThrows(
+ UnexpectedTypeError.class,
+ () -> typeCheckFunctionBody(untyped, signature, context)
+ );
+
+ assertThat(error.expected(), equalTo(TypeSets.type(Types.STRING)));
+ assertThat(error.actual(), equalTo(Types.UNIT));
+ }
+
+ @Test
+ public void givenReturnTypeIsNotUnitWhenBodyHasBlockReturnOfCorrectTypeThenFunctionTypeChecks() {
+ var untyped = Untyped.block(List.of(
+ Untyped.blockReturn(Untyped.stringLiteral("hello"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("String", Types.STRING))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ typeCheckFunctionBody(untyped, signature, context);
+ }
+
+ @Test
+ public void freeVariablesIncludeVariablesReferencedInFunctionButDefinedOutsideOfFunction() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.reference("y"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterGlobal(P.map(
+ Map.entry("x", Types.STRING),
+ Map.entry("y", Types.STRING)
+ ));
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withFreeVariables(containsExactly(
+ has("name", x -> x.name(), equalTo("y"))
+ ))
+ );
+ }
+
+ @Test
+ public void freeVariablesDoNotIncludeArgsReferencedInFunction() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.reference("x"))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .addPositionalParam("x", Typed.typeLevelReference("String", Types.STRING))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY;
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withFreeVariables(containsExactly())
+ );
+ }
+
+ @Test
+ public void freeVariablesIncludeVariablesReferencedInBlockInFunctionButDefinedOutsideOfFunction() {
+ var untyped = Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.if_(
+ List.of(
+ Untyped.conditionalBranch(
+ Untyped.boolLiteral(true),
+ Untyped.block(List.of())
+ )
+ ),
+ Untyped.block(List.of(
+ Untyped.expressionStatement(Untyped.reference("x"))
+ ))
+ ))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterGlobal(P.map(
+ Map.entry("x", Types.STRING)
+ ));
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withFreeVariables(containsExactly(
+ has("name", x -> x.name(), equalTo("x"))
+ ))
+ );
+ }
+
+ @Test
+ public void boundVariablesAreSet() {
+ var untyped = Untyped.block(List.of(
+ Untyped.val("local1", Untyped.intLiteral(1)),
+ Untyped.expressionStatement(Untyped.reference("y")),
+ Untyped.expressionStatement(Untyped.if_(
+ List.of(
+ Untyped.conditionalBranch(
+ Untyped.boolLiteral(true),
+ Untyped.block(List.of(
+ Untyped.val("local2", Untyped.intLiteral(2))
+ ))
+ )
+ ),
+ Untyped.block(List.of())
+ ))
+ ));
+ var signature = TypedFunctionSignatureNode.builder()
+ .addPositionalParam("argPositional", Typed.typeLevelReference("Unit", Types.UNIT))
+ .addNamedParam("argNamed", Typed.typeLevelReference("Unit", Types.UNIT))
+ .withReturnType(Typed.typeLevelReference("Unit", Types.UNIT))
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterGlobal(P.map(
+ Map.entry("y", Types.STRING)
+ ));
+
+ var typed = typeCheckFunctionBody(untyped, signature, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionBodyNode()
+ .withBlock(isTypedBlockNodeWithBoundVariableNames(containsExactly(
+ equalTo("argPositional"),
+ equalTo("argNamed"),
+ equalTo("local1")
+ )))
+ );
+ }
+}
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
index eaa7f007..b44b0f96 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
@@ -2,11 +2,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.typechecker.errors.DuplicateVariableNameError;
-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.Types;
@@ -15,11 +11,11 @@ import org.shedlang.compiler.util.P;
import java.util.List;
import java.util.Map;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.*;
import static org.shedlang.compiler.typechecker.TypeCheckerTesting.typeCheckNamespaceStatement;
import static org.zwobble.precisely.AssertThat.assertThat;
-import static org.zwobble.precisely.Matchers.*;
+import static org.zwobble.precisely.Matchers.isOptionalEmpty;
+import static org.zwobble.precisely.Matchers.isSequence;
public class TypeCheckerFunctionDefinitionTests {
@Test
@@ -44,314 +40,4 @@ public class TypeCheckerFunctionDefinitionTests {
.withType(new FunctionType(List.of(), List.of(), Effects.NONE, Types.UNIT))
);
}
-
- @Test
- public void paramsMustHaveUniqueNames() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withName("f")
- .addPositionalParam("x", Untyped.typeLevelReference("String"))
- .addPositionalParam("x", Untyped.typeLevelReference("Int"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("Int", Types.metaType(Types.INT)),
- Map.entry("String", Types.metaType(Types.STRING)),
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- var error = assertThrows(
- DuplicateVariableNameError.class,
- () -> typeCheckNamespaceStatement(untyped, context)
- );
-
- assertThat(error.variableName(), equalTo("x"));
- }
-
- @Test
- public void bodyIsTypeChecked() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.stringLiteral("hello")))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withBodyBlock(isTypedBlockNode(isSequence(
- isTypedExpressionStatementNode(isTypedStringLiteralNode("hello"))
- )))
- );
- }
-
- @Test
- public void canReferencePositionalArgInBody() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .addPositionalParam("x", Untyped.typeLevelReference("String"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.reference("x")))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING)),
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withBodyBlock(isTypedBlockNode(isSequence(
- isTypedExpressionStatementNode(isTypedReferenceNode("x", Types.STRING))
- )))
- );
- }
-
- @Test
- public void canReferenceNamedArgInBody() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .addNamedParam("x", Untyped.typeLevelReference("String"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.reference("x")))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING)),
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withBodyBlock(isTypedBlockNode(isSequence(
- isTypedExpressionStatementNode(isTypedReferenceNode("x", Types.STRING))
- )))
- );
- }
-
- @Test
- public void bodyHasEffectAsDefinedInSignature() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withName("g")
- .withEffect(Untyped.typeLevelReference("Io"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(
- UntypedCallNode.builder()
- .withReceiver(Untyped.reference("f"))
- .withHasEffects(true)
- .build()
- ))
- .build();
- var functionType = FunctionType.builder()
- .withEffect(Effects.IO)
- .withReturnType(Types.UNIT)
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("f", functionType),
- Map.entry("Io", Types.effectType(Effects.IO)),
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withBodyBlock(isTypedBlockNode(isSequence(
- isTypedExpressionStatementNode(isTypedCallNode().withHasEffects(true))
- )))
- );
- }
-
- @Test
- public void bodyHasReturnTypeAsDefinedInSignature() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("String"))
- .addStatement(Untyped.functionReturn(Untyped.intLiteral(42)))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING))
- ));
-
- var error = assertThrows(
- UnexpectedTypeError.class,
- () -> typeCheckNamespaceStatement(untyped, context)
- );
-
- assertThat(error.expected(), equalTo(TypeSets.type(Types.STRING)));
- assertThat(error.actual(), equalTo(Types.INT));
- }
-
- @Test
- public void givenReturnTypeIsUnitWhenBodyHasNoReturnThenFunctionTypeChecks() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- typeCheckNamespaceStatement(untyped, context);
- }
-
- @Test
- public void givenReturnTypeIsNotUnitWhenBodyHasNoReturnThenFunctionFailsTypeCheck() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("String"))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING))
- ));
-
- var error = assertThrows(
- UnexpectedTypeError.class,
- () -> typeCheckNamespaceStatement(untyped, context)
- );
-
- assertThat(error.expected(), equalTo(TypeSets.type(Types.STRING)));
- 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
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING))
- ));
-
- typeCheckNamespaceStatement(untyped, context);
- }
-
- @Test
- public void freeVariablesIncludeVariablesReferencedInFunctionButDefinedOutsideOfFunction() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.reference("y")))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("Unit", Types.metaType(Types.UNIT)),
- Map.entry("x", Types.STRING),
- Map.entry("y", Types.STRING)
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withFreeVariables(containsExactly(
- has("name", x -> x.name(), equalTo("y"))
- ))
- );
- }
-
- @Test
- public void freeVariablesDoNotIncludeArgsReferencedInFunction() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .addPositionalParam("x", Untyped.typeLevelReference("String"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.reference("x")))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("String", Types.metaType(Types.STRING)),
- Map.entry("Unit", Types.metaType(Types.UNIT))
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withFreeVariables(containsExactly())
- );
- }
-
- @Test
- public void freeVariablesIncludeVariablesReferencedInBlockInFunctionButDefinedOutsideOfFunction() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.expressionStatement(Untyped.if_(
- List.of(
- Untyped.conditionalBranch(
- Untyped.boolLiteral(true),
- Untyped.block(List.of())
- )
- ),
- Untyped.block(List.of(
- Untyped.expressionStatement(Untyped.reference("x"))
- ))
- )))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("Unit", Types.metaType(Types.UNIT)),
- Map.entry("x", Types.STRING)
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withFreeVariables(containsExactly(
- has("name", x -> x.name(), equalTo("x"))
- ))
- );
- }
-
- @Test
- public void boundVariablesAreSet() {
- var untyped = UntypedFunctionDefinitionNode.builder()
- .addPositionalParam("argPositional", Untyped.typeLevelReference("Unit"))
- .addNamedParam("argNamed", Untyped.typeLevelReference("Unit"))
- .withReturnType(Untyped.typeLevelReference("Unit"))
- .addStatement(Untyped.val("local1", Untyped.intLiteral(1)))
- .addStatement(Untyped.expressionStatement(Untyped.reference("y")))
- .addStatement(Untyped.expressionStatement(Untyped.if_(
- List.of(
- Untyped.conditionalBranch(
- Untyped.boolLiteral(true),
- Untyped.block(List.of(
- Untyped.val("local2", Untyped.intLiteral(2))
- ))
- )
- ),
- Untyped.block(List.of())
- )))
- .build();
- var context = TypeCheckerContext.EMPTY
- .enterGlobal(P.map(
- Map.entry("Unit", Types.metaType(Types.UNIT)),
- Map.entry("y", Types.STRING)
- ));
-
- var typed = typeCheckNamespaceStatement(untyped, context);
-
- assertThat(
- typed.value(),
- isTypedFunctionDefinitionNode()
- .withBodyBlock(isTypedBlockNodeWithBoundVariableNames(containsExactly(
- equalTo("argPositional"),
- equalTo("argNamed"),
- equalTo("local1")
- )))
- );
- }
}
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionExpressionTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionExpressionTests.java
new file mode 100644
index 00000000..85f6b3b7
--- /dev/null
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionExpressionTests.java
@@ -0,0 +1,41 @@
+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.types.Effects;
+import org.shedlang.compiler.types.FunctionType;
+import org.shedlang.compiler.types.Types;
+import org.shedlang.compiler.util.P;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.*;
+import static org.shedlang.compiler.typechecker.TypeChecker.typeCheckExpression;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.isOptionalEmpty;
+import static org.zwobble.precisely.Matchers.isSequence;
+
+public class TypeCheckerFunctionExpressionTests {
+ @Test
+ public void canTypeCheckMinimalFunctionDefinition() {
+ var untyped = UntypedFunctionExpressionNode.builder()
+ .withReturnType(Untyped.typeLevelReference("Unit"))
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterGlobal(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+
+ var typed = typeCheckExpression(untyped, context);
+
+ assertThat(
+ typed.value(),
+ isTypedFunctionExpressionNode()
+ .withParams(isTypedParamsNode(isSequence(), isSequence()))
+ .withEffect(isOptionalEmpty())
+ .withReturnType(isTypedTypeLevelReferenceNode("Unit", Types.UNIT))
+ .withBodyBlock(isTypedBlockNode(isSequence()))
+ .withType(new FunctionType(List.of(), List.of(), Effects.NONE, Types.UNIT))
+ );
+ }
+}