aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2023-07-02 13:13:58 +0100
committerMichael Williamson <mike@zwobble.org>2023-07-02 13:13:58 +0100
commit2e008d29e5d956f5463fa27c397f395a7e106d9e (patch)
tree2c2febea1d0544aed1ff1d5d5a7c34ac3c94c22e /src/test/java
parentff0b422cddead23865f0f5662bd9a4d2abef462d (diff)
Check call effect is sub-effect of function effect
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java73
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java54
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java6
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerReferenceTests.java4
-rw-r--r--src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java8
5 files changed, 116 insertions, 29 deletions
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java
index ffbc3c29..46c83cf5 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java
@@ -32,7 +32,7 @@ public class TypeCheckerCallTests {
.withReturnType(Types.STRING)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var typed = TypeChecker.typeCheckExpression(untyped, context);
@@ -53,7 +53,7 @@ public class TypeCheckerCallTests {
List.of()
);
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", Types.STRING)));
+ .enterBlock(P.map(Map.entry("f", Types.STRING)));
var error = assertThrows(
UnexpectedTypeError.class,
@@ -78,7 +78,7 @@ public class TypeCheckerCallTests {
.addPositionalParam(Types.INT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var typed = TypeChecker.typeCheckExpression(untyped, context);
@@ -107,7 +107,7 @@ public class TypeCheckerCallTests {
.addNamedParam("y", Types.INT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var typed = TypeChecker.typeCheckExpression(untyped, context);
@@ -133,7 +133,7 @@ public class TypeCheckerCallTests {
.addPositionalParam(Types.INT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
UnexpectedTypeError.class,
@@ -157,7 +157,7 @@ public class TypeCheckerCallTests {
.addNamedParam("x", Types.INT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
UnexpectedTypeError.class,
@@ -179,7 +179,7 @@ public class TypeCheckerCallTests {
var functionType = FunctionType.builder()
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
WrongNumberOfPositionalArgsError.class,
@@ -201,7 +201,7 @@ public class TypeCheckerCallTests {
.addNamedParam("x", Types.STRING)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
MissingNamedArgError.class,
@@ -223,7 +223,7 @@ public class TypeCheckerCallTests {
var functionType = FunctionType.builder()
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
UnexpectedNamedArgError.class,
@@ -247,7 +247,7 @@ public class TypeCheckerCallTests {
.addNamedParam("x", Types.STRING)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
CannotPassNamedArgTwiceError.class,
@@ -268,7 +268,7 @@ public class TypeCheckerCallTests {
.withReturnType(Types.UNIT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
var error = assertThrows(
CallIsMissingEffectsError.class,
@@ -291,11 +291,60 @@ public class TypeCheckerCallTests {
.withReturnType(Types.UNIT)
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("f", functionType)));
+ .enterBlock(P.map(Map.entry("f", functionType)));
assertThrows(
FunctionHasNoEffectsError.class,
() -> TypeChecker.typeCheckExpression(untyped, context)
);
}
+
+ @Test
+ public void whenReceiverEffectIsSubEffectOfContextEffectThenCallIsAllowed() {
+ var untyped = new UntypedCallNode(
+ Untyped.reference("f"),
+ new UntypedArgsNode(List.of(), List.of(), Sources.NULL),
+ true,
+ Sources.NULL
+ );
+ var functionType = FunctionType.builder()
+ .withEffect(Effects.IO)
+ .withReturnType(Types.UNIT)
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterFunction(P.map(Map.entry("f", functionType)), Effects.IO);
+
+ var typed = TypeChecker.typeCheckExpression(untyped, context);
+
+ assertThat(
+ typed.value(),
+ isTypedCallNode()
+ .withReceiver(isTypedReferenceNode("f", functionType))
+ .withHasEffects(true)
+ );
+ }
+
+ @Test
+ public void whenReceiverEffectIsNotSubEffectOfContextEffectThenCallIsNotAllowed() {
+ var untyped = new UntypedCallNode(
+ Untyped.reference("f"),
+ new UntypedArgsNode(List.of(), List.of(), Sources.NULL),
+ true,
+ Sources.NULL
+ );
+ var functionType = FunctionType.builder()
+ .withEffect(Effects.IO)
+ .withReturnType(Types.UNIT)
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterFunction(P.map(Map.entry("f", functionType)), Effects.NONE);
+
+ var error = assertThrows(
+ UnexpectedEffectError.class,
+ () -> TypeChecker.typeCheckExpression(untyped, context)
+ );
+
+ assertThat(error.allowed(), equalTo(Effects.NONE));
+ assertThat(error.actual(), equalTo(Effects.IO));
+ }
}
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
index b7b2d66e..a7d439dd 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java
@@ -2,7 +2,10 @@ package org.shedlang.compiler.typechecker;
import org.junit.jupiter.api.Test;
import org.shedlang.compiler.ast.untyped.Untyped;
+import org.shedlang.compiler.ast.untyped.UntypedArgsNode;
+import org.shedlang.compiler.ast.untyped.UntypedCallNode;
import org.shedlang.compiler.ast.untyped.UntypedFunctionDefinitionNode;
+import org.shedlang.compiler.sources.Sources;
import org.shedlang.compiler.types.Effects;
import org.shedlang.compiler.types.FunctionType;
import org.shedlang.compiler.types.NamedParam;
@@ -24,7 +27,7 @@ public class TypeCheckerFunctionDefinitionTests {
.withReturnType(Untyped.typeLevelReference("Unit"))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+ .enterBlock(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
var typed = TypeChecker.typeCheckNamespaceStatement(untyped, context);
@@ -48,7 +51,7 @@ public class TypeCheckerFunctionDefinitionTests {
.withReturnType(Untyped.typeLevelReference("Unit"))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("Int", Types.metaType(Types.INT)),
Map.entry("String", Types.metaType(Types.STRING)),
Map.entry("Unit", Types.metaType(Types.UNIT))
@@ -73,6 +76,7 @@ public class TypeCheckerFunctionDefinitionTests {
))
);
}
+
@Test
public void effectIsTypeChecked() {
var untyped = UntypedFunctionDefinitionNode.builder()
@@ -80,7 +84,7 @@ public class TypeCheckerFunctionDefinitionTests {
.withReturnType(Untyped.typeLevelReference("Unit"))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("Io", Types.effectType(Effects.IO)),
Map.entry("Unit", Types.metaType(Types.UNIT))
));
@@ -101,7 +105,7 @@ public class TypeCheckerFunctionDefinitionTests {
.addStatement(Untyped.expressionStatement(Untyped.stringLiteral("hello")))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+ .enterBlock(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
var typed = TypeChecker.typeCheckNamespaceStatement(untyped, context);
@@ -122,7 +126,7 @@ public class TypeCheckerFunctionDefinitionTests {
.addStatement(Untyped.expressionStatement(Untyped.reference("x")))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("String", Types.metaType(Types.STRING)),
Map.entry("Unit", Types.metaType(Types.UNIT))
));
@@ -146,7 +150,7 @@ public class TypeCheckerFunctionDefinitionTests {
.addStatement(Untyped.expressionStatement(Untyped.reference("x")))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("String", Types.metaType(Types.STRING)),
Map.entry("Unit", Types.metaType(Types.UNIT))
));
@@ -163,13 +167,47 @@ public class TypeCheckerFunctionDefinitionTests {
}
@Test
+ public void bodyHasEffectAsDefinedInSignature() {
+ var untyped = UntypedFunctionDefinitionNode.builder()
+ .withEffect(Untyped.typeLevelReference("Io"))
+ .withReturnType(Untyped.typeLevelReference("Unit"))
+ .addStatement(Untyped.expressionStatement(new UntypedCallNode(
+ Untyped.reference("f"),
+ new UntypedArgsNode(List.of(), List.of(), Sources.NULL),
+ true,
+ Sources.NULL
+ )))
+ .build();
+ var functionType = FunctionType.builder()
+ .withEffect(Effects.IO)
+ .withReturnType(Types.UNIT)
+ .build();
+ var context = TypeCheckerContext.EMPTY
+ .enterBlock(P.map(
+ Map.entry("f", functionType),
+ Map.entry("Io", Types.effectType(Effects.IO)),
+ Map.entry("Unit", Types.metaType(Types.UNIT))
+ ));
+
+ var typed = TypeChecker.typeCheckNamespaceStatement(untyped, context);
+
+ assertThat(
+ typed,
+ isTypedFunctionDefinitionNode()
+ .withBody(isSequence(
+ isTypedExpressionStatementNode(isTypedCallNode().withHasEffects(true))
+ ))
+ );
+ }
+
+ @Test
public void freeVariablesIncludeVariablesReferencedInFunctionButDefinedOutsideOfFunction() {
var untyped = UntypedFunctionDefinitionNode.builder()
.withReturnType(Untyped.typeLevelReference("Unit"))
.addStatement(Untyped.expressionStatement(Untyped.reference("y")))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("Unit", Types.metaType(Types.UNIT)),
Map.entry("x", Types.STRING),
Map.entry("y", Types.STRING)
@@ -194,7 +232,7 @@ public class TypeCheckerFunctionDefinitionTests {
.addStatement(Untyped.expressionStatement(Untyped.reference("x")))
.build();
var context = TypeCheckerContext.EMPTY
- .enter(P.map(
+ .enterBlock(P.map(
Map.entry("String", Types.metaType(Types.STRING)),
Map.entry("Unit", Types.metaType(Types.UNIT))
));
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java
index 8d537515..9ed2e837 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java
@@ -20,7 +20,7 @@ public class TypeCheckerNamespaceTests {
public void canTypeCheckEmptyNamespace() {
var untyped = Untyped.namespace(List.of());
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+ .enterBlock(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
var result = TypeChecker.typeCheckNamespace(untyped, context);
@@ -39,7 +39,7 @@ public class TypeCheckerNamespaceTests {
.build()
));
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+ .enterBlock(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
var result = TypeChecker.typeCheckNamespace(untyped, context);
@@ -67,7 +67,7 @@ public class TypeCheckerNamespaceTests {
)
);
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
+ .enterBlock(P.map(Map.entry("Unit", Types.metaType(Types.UNIT))));
var result = TypeChecker.typeCheckNamespace(untyped, context);
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerReferenceTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerReferenceTests.java
index 9d067cc1..317dbd02 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerReferenceTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerReferenceTests.java
@@ -18,7 +18,7 @@ public class TypeCheckerReferenceTests {
public void whenNameIsBoundInEnvironmentThenTypeIsUsedFromEnvironment() {
var untyped = Untyped.reference("x");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("x", Types.STRING)));
+ .enterBlock(P.map(Map.entry("x", Types.STRING)));
var typed = TypeChecker.typeCheckExpression(untyped, context);
@@ -29,7 +29,7 @@ public class TypeCheckerReferenceTests {
public void whenNameIsNotBoundInEnvironmentThenErrorIsThrown() {
var untyped = Untyped.reference("x");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("y", Types.STRING)));
+ .enterBlock(P.map(Map.entry("y", Types.STRING)));
var error = assertThrows(
UnknownVariableError.class,
diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
index c9f1272c..90337920 100644
--- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
+++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
@@ -21,7 +21,7 @@ public class TypeCheckerTypeLevelReferenceTests {
public void whenNameIsBoundInEnvironmentThenTypeIsUsedFromEnvironment() {
var untyped = Untyped.typeLevelReference("String");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("String", Types.metaType(Types.STRING))));
+ .enterBlock(P.map(Map.entry("String", Types.metaType(Types.STRING))));
var typed = TypeChecker.typeCheckTypeLevelExpression(TypeSets.META_TYPE, untyped, context);
@@ -32,7 +32,7 @@ public class TypeCheckerTypeLevelReferenceTests {
public void whenNameRefersToNonTypeLevelValueThenErrorIsThrown() {
var untyped = Untyped.typeLevelReference("String");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("String", Types.STRING)));
+ .enterBlock(P.map(Map.entry("String", Types.STRING)));
var error = assertThrows(
UnexpectedTypeError.class,
@@ -47,7 +47,7 @@ public class TypeCheckerTypeLevelReferenceTests {
public void whenNameRefersToValueOfWrongTypeThenErrorIsThrown() {
var untyped = Untyped.typeLevelReference("Io");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Io", Types.effectType(Effects.IO))));
+ .enterBlock(P.map(Map.entry("Io", Types.effectType(Effects.IO))));
var error = assertThrows(
UnexpectedTypeError.class,
@@ -62,7 +62,7 @@ public class TypeCheckerTypeLevelReferenceTests {
public void whenNameIsNotBoundInEnvironmentThenErrorIsThrown() {
var untyped = Untyped.typeLevelReference("String");
var context = TypeCheckerContext.EMPTY
- .enter(P.map(Map.entry("Int", Types.metaType(Types.INT))));
+ .enterBlock(P.map(Map.entry("Int", Types.metaType(Types.INT))));
var error = assertThrows(
UnknownVariableError.class,