From 71a8ca11b03331a92bb271cbd1fbcb877e587300 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 2 Jul 2023 12:19:46 +0100 Subject: Check call to function with effect is marked with effects --- .../java/org/shedlang/compiler/ShedCompiler.java | 2 ++ .../ast/typed/TypedFunctionDefinitionNode.java | 1 + .../shedlang/compiler/typechecker/TypeChecker.java | 4 ++++ .../errors/CallIsMissingEffectsError.java | 18 ++++++++++++++++++ .../java/org/shedlang/compiler/types/Effect.java | 2 +- .../java/org/shedlang/compiler/types/Effects.java | 2 ++ .../org/shedlang/compiler/types/FunctionType.java | 16 +++++++++++----- .../org/shedlang/compiler/types/NoneEffect.java | 10 ++++++++++ .../compiler/typechecker/TypeCheckerCallTests.java | 22 ++++++++++++++++++++++ .../TypeCheckerFunctionDefinitionTests.java | 3 ++- .../typechecker/TypeCheckerNamespaceTests.java | 7 ++----- 11 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/shedlang/compiler/typechecker/errors/CallIsMissingEffectsError.java create mode 100644 src/main/java/org/shedlang/compiler/types/NoneEffect.java (limited to 'src') diff --git a/src/main/java/org/shedlang/compiler/ShedCompiler.java b/src/main/java/org/shedlang/compiler/ShedCompiler.java index bf26436e..d727aea5 100644 --- a/src/main/java/org/shedlang/compiler/ShedCompiler.java +++ b/src/main/java/org/shedlang/compiler/ShedCompiler.java @@ -42,6 +42,7 @@ public class ShedCompiler { Map.entry("print", new FunctionType( List.of(Types.STRING), List.of(), + Effects.IO, Types.UNIT )) )) @@ -52,6 +53,7 @@ public class ShedCompiler { Map.entry("print", new FunctionType( List.of(Types.STRING), List.of(), + Effects.IO, Types.UNIT )) )); diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedFunctionDefinitionNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedFunctionDefinitionNode.java index 417b9ad6..42783224 100644 --- a/src/main/java/org/shedlang/compiler/ast/typed/TypedFunctionDefinitionNode.java +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedFunctionDefinitionNode.java @@ -42,6 +42,7 @@ public record TypedFunctionDefinitionNode( params.named().stream() .map(param -> new NamedParam(param.name(), param.type().value())) .toList(), + effect.map(e -> e.value()).orElse(Effects.NONE), returnType.value() ), source diff --git a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java index 770b7063..4d2b7326 100644 --- a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java @@ -142,6 +142,10 @@ public class TypeChecker { var typedArgs = argsResult.value(); context = argsResult.context(); + if (!receiverType.effect().equals(Effects.NONE) && !untyped.hasEffects()) { + throw new CallIsMissingEffectsError(receiverType.effect(), untyped.source()); + } + return context.toResult(new TypedCallNode( typedReceiver, typedArgs, diff --git a/src/main/java/org/shedlang/compiler/typechecker/errors/CallIsMissingEffectsError.java b/src/main/java/org/shedlang/compiler/typechecker/errors/CallIsMissingEffectsError.java new file mode 100644 index 00000000..083d9a03 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/typechecker/errors/CallIsMissingEffectsError.java @@ -0,0 +1,18 @@ +package org.shedlang.compiler.typechecker.errors; + +import org.shedlang.compiler.errors.SourceError; +import org.shedlang.compiler.sources.Source; +import org.shedlang.compiler.types.Effect; + +public class CallIsMissingEffectsError extends SourceError { + private final Effect effect; + + public CallIsMissingEffectsError(Effect effect, Source source) { + super("Function has effect, but call is missing effect: " + effect.describe(), source); + this.effect = effect; + } + + public Effect effect() { + return effect; + } +} diff --git a/src/main/java/org/shedlang/compiler/types/Effect.java b/src/main/java/org/shedlang/compiler/types/Effect.java index 257a3847..7e8bc203 100644 --- a/src/main/java/org/shedlang/compiler/types/Effect.java +++ b/src/main/java/org/shedlang/compiler/types/Effect.java @@ -1,4 +1,4 @@ package org.shedlang.compiler.types; -public sealed interface Effect extends TypeLevelValue permits IoEffect { +public sealed interface Effect extends TypeLevelValue permits IoEffect, NoneEffect { } diff --git a/src/main/java/org/shedlang/compiler/types/Effects.java b/src/main/java/org/shedlang/compiler/types/Effects.java index 820d31c5..c894a58b 100644 --- a/src/main/java/org/shedlang/compiler/types/Effects.java +++ b/src/main/java/org/shedlang/compiler/types/Effects.java @@ -4,5 +4,7 @@ public class Effects { private Effects() { } + public static final Effect NONE = NoneEffect.INSTANCE; + public static final Effect IO = IoEffect.INSTANCE; } diff --git a/src/main/java/org/shedlang/compiler/types/FunctionType.java b/src/main/java/org/shedlang/compiler/types/FunctionType.java index 22d3487b..e40439f7 100644 --- a/src/main/java/org/shedlang/compiler/types/FunctionType.java +++ b/src/main/java/org/shedlang/compiler/types/FunctionType.java @@ -8,10 +8,11 @@ import java.util.List; public record FunctionType( List positionalParams, List namedParams, + Effect effect, Type returnType ) implements Type { public static Builder builder() { - return new Builder(Empty.vector(), Empty.vector(), Types.UNIT); + return new Builder(Empty.vector(), Empty.vector(), Effects.NONE, Types.UNIT); } @Override @@ -23,23 +24,28 @@ public record FunctionType( public record Builder( PVector positionalParams, PVector namedParams, + Effect effect, Type returnType ) { public FunctionType build() { - return new FunctionType(positionalParams, namedParams, returnType); + return new FunctionType(positionalParams, namedParams, effect, returnType); } public Builder addNamedParam(String name, Type type) { var namedParam = new NamedParam(name, type); - return new Builder(positionalParams, namedParams.plus(namedParam), returnType); + return new Builder(positionalParams, namedParams.plus(namedParam), effect, returnType); } public Builder addPositionalParam(Type type) { - return new Builder(positionalParams.plus(type), namedParams, returnType); + return new Builder(positionalParams.plus(type), namedParams, effect, returnType); + } + + public Builder withEffect(Effect effect) { + return new Builder(positionalParams, namedParams, effect, returnType); } public Builder withReturnType(Type returnType) { - return new Builder(positionalParams, namedParams, returnType); + return new Builder(positionalParams, namedParams, effect, returnType); } } } diff --git a/src/main/java/org/shedlang/compiler/types/NoneEffect.java b/src/main/java/org/shedlang/compiler/types/NoneEffect.java new file mode 100644 index 00000000..e6fe5f95 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/types/NoneEffect.java @@ -0,0 +1,10 @@ +package org.shedlang.compiler.types; + +public record NoneEffect() implements Effect { + static final NoneEffect INSTANCE = new NoneEffect(); + + @Override + public String describe() { + return "None"; + } +} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java index 24ded26f..ad1ab07e 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.shedlang.compiler.ast.untyped.Untyped; import org.shedlang.compiler.typechecker.errors.*; 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; @@ -252,4 +253,25 @@ public class TypeCheckerCallTests { assertThat(error.argName(), equalTo("x")); } + + @Test + public void givenFunctionHasEffectsWhenCallingFunctionWithoutExplicitEffectsThenErrorIsThrown() { + var untyped = Untyped.call( + Untyped.reference("f"), + List.of() + ); + var functionType = FunctionType.builder() + .withEffect(Effects.IO) + .withReturnType(Types.UNIT) + .build(); + var context = TypeCheckerContext.EMPTY + .enter(P.map(Map.entry("f", functionType))); + + var error = assertThrows( + CallIsMissingEffectsError.class, + () -> TypeChecker.typeCheckExpression(untyped, context) + ); + + assertThat(error.effect(), 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 26ea2693..b7b2d66e 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFunctionDefinitionTests.java @@ -36,7 +36,7 @@ public class TypeCheckerFunctionDefinitionTests { .withEffect(isOptionalEmpty()) .withReturnType(isTypedTypeLevelReferenceNode("Unit", Types.UNIT)) .withBody(isSequence()) - .withType(new FunctionType(List.of(), List.of(), Types.UNIT)) + .withType(new FunctionType(List.of(), List.of(), Effects.NONE, Types.UNIT)) ); } @@ -68,6 +68,7 @@ public class TypeCheckerFunctionDefinitionTests { .withType(new FunctionType( List.of(Types.STRING), List.of(new NamedParam("y", Types.INT)), + Effects.NONE, 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 b9b6679c..8d537515 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java @@ -3,10 +3,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.UntypedFunctionDefinitionNode; -import org.shedlang.compiler.types.FunctionType; -import org.shedlang.compiler.types.NamespaceName; -import org.shedlang.compiler.types.NamespaceTypeInfo; -import org.shedlang.compiler.types.Types; +import org.shedlang.compiler.types.*; import org.shedlang.compiler.util.P; import java.util.List; @@ -77,7 +74,7 @@ public class TypeCheckerNamespaceTests { assertThat( result.context().namespaceTypeInfo().get(namespaceName), equalTo(new NamespaceTypeInfo(P.orderedMap( - Map.entry("f", new FunctionType(List.of(), List.of(), Types.UNIT)) + Map.entry("f", new FunctionType(List.of(), List.of(), Effects.NONE, Types.UNIT)) ))) ); } -- cgit v1.2.3