From 2e008d29e5d956f5463fa27c397f395a7e106d9e Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 2 Jul 2023 13:13:58 +0100 Subject: Check call effect is sub-effect of function effect --- .../compiler/typechecker/TypeCheckerCallTests.java | 73 ++++++++++++++++++---- .../TypeCheckerFunctionDefinitionTests.java | 54 +++++++++++++--- .../typechecker/TypeCheckerNamespaceTests.java | 6 +- .../typechecker/TypeCheckerReferenceTests.java | 4 +- .../TypeCheckerTypeLevelReferenceTests.java | 8 +-- 5 files changed, 116 insertions(+), 29 deletions(-) (limited to 'src/test/java') 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)) )); @@ -162,6 +166,40 @@ 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() @@ -169,7 +207,7 @@ public class TypeCheckerFunctionDefinitionTests { .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, -- cgit v1.2.3