From 74b2aa135e2bf1af1ef65476aa03406eaaddd2ab Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 10 Jun 2023 11:00:22 +0100 Subject: Add incomplete type checking for calls --- .../shedlang/compiler/ast/typed/TypedArgsNode.java | 12 ++++ .../shedlang/compiler/ast/typed/TypedCallNode.java | 13 +++++ .../compiler/ast/typed/TypedExpressionNode.java | 5 +- .../compiler/ast/typed/TypedNamedArgNode.java | 10 ++++ .../org/shedlang/compiler/ast/typed/TypedNode.java | 7 +++ .../shedlang/compiler/stackir/StackCompiler.java | 3 + .../shedlang/compiler/typechecker/TypeChecker.java | 29 ++++++++- .../compiler/ast/typed/TypedArgsNodeMatcher.java | 36 ++++++++++++ .../compiler/ast/typed/TypedCallNodeMatcher.java | 46 +++++++++++++++ .../compiler/ast/typed/TypedNodeMatchers.java | 18 ++++++ .../org/shedlang/compiler/ast/untyped/Untyped.java | 25 ++++++++ .../ast/untyped/UntypedArgsNodeMatcher.java | 7 ++- .../ast/untyped/UntypedCallNodeMatcher.java | 7 ++- .../UntypedFunctionDefinitionNodeMatcher.java | 7 ++- .../compiler/ast/untyped/UntypedNodeMatchers.java | 6 +- .../shedlang/compiler/testing/MatcherBuilder.java | 5 -- .../compiler/typechecker/TypeCheckerCallTests.java | 68 ++++++++++++++++++++++ 17 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/shedlang/compiler/ast/typed/TypedArgsNode.java create mode 100644 src/main/java/org/shedlang/compiler/ast/typed/TypedCallNode.java create mode 100644 src/main/java/org/shedlang/compiler/ast/typed/TypedNamedArgNode.java create mode 100644 src/main/java/org/shedlang/compiler/ast/typed/TypedNode.java create mode 100644 src/test/java/org/shedlang/compiler/ast/typed/TypedArgsNodeMatcher.java create mode 100644 src/test/java/org/shedlang/compiler/ast/typed/TypedCallNodeMatcher.java create mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java (limited to 'src') diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedArgsNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedArgsNode.java new file mode 100644 index 00000000..473fa197 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedArgsNode.java @@ -0,0 +1,12 @@ +package org.shedlang.compiler.ast.typed; + +import org.shedlang.compiler.sources.Source; + +import java.util.List; + +public record TypedArgsNode( + List positional, + List named, + Source source +) implements TypedNode { +} diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedCallNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedCallNode.java new file mode 100644 index 00000000..188436f4 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedCallNode.java @@ -0,0 +1,13 @@ +package org.shedlang.compiler.ast.typed; + +import org.shedlang.compiler.sources.Source; +import org.shedlang.compiler.types.Type; + +public record TypedCallNode( + TypedExpressionNode receiver, + TypedArgsNode args, + boolean hasEffects, + Type type, + Source source +) implements TypedExpressionNode { +} diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedExpressionNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedExpressionNode.java index 5ec6216b..0c61b3b2 100644 --- a/src/main/java/org/shedlang/compiler/ast/typed/TypedExpressionNode.java +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedExpressionNode.java @@ -1,9 +1,8 @@ package org.shedlang.compiler.ast.typed; -import org.shedlang.compiler.sources.Source; import org.shedlang.compiler.types.Type; -public sealed interface TypedExpressionNode permits TypedBoolLiteralNode, TypedIntLiteralNode, TypedReferenceNode, TypedStringLiteralNode { - Source source(); +public sealed interface TypedExpressionNode + permits TypedBoolLiteralNode, TypedCallNode, TypedIntLiteralNode, TypedReferenceNode, TypedStringLiteralNode { Type type(); } diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedNamedArgNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedNamedArgNode.java new file mode 100644 index 00000000..c29f3c04 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedNamedArgNode.java @@ -0,0 +1,10 @@ +package org.shedlang.compiler.ast.typed; + +import org.shedlang.compiler.sources.Source; + +public record TypedNamedArgNode( + String name, + TypedExpressionNode expression, + Source source +) implements TypedNode { +} diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedNode.java new file mode 100644 index 00000000..d38c6017 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedNode.java @@ -0,0 +1,7 @@ +package org.shedlang.compiler.ast.typed; + +import org.shedlang.compiler.sources.Source; + +public interface TypedNode { + Source source(); +} diff --git a/src/main/java/org/shedlang/compiler/stackir/StackCompiler.java b/src/main/java/org/shedlang/compiler/stackir/StackCompiler.java index 8a268c4c..1d6b0a88 100644 --- a/src/main/java/org/shedlang/compiler/stackir/StackCompiler.java +++ b/src/main/java/org/shedlang/compiler/stackir/StackCompiler.java @@ -18,6 +18,9 @@ public class StackCompiler { case TypedBoolLiteralNode typedBoolLiteralNode -> compileBoolLiteral(typedBoolLiteralNode); + case TypedCallNode typedCallNode -> + throw new UnsupportedOperationException("TODO"); + case TypedIntLiteralNode typedIntLiteralNode -> compileIntLiteral(typedIntLiteralNode); diff --git a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java index 73d3b9b6..850929c3 100644 --- a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java @@ -2,11 +2,22 @@ package org.shedlang.compiler.typechecker; import org.shedlang.compiler.ast.typed.*; import org.shedlang.compiler.ast.untyped.*; +import org.shedlang.compiler.types.FunctionType; + +import java.util.List; public class TypeChecker { private TypeChecker() { } + private static TypedArgsNode typeCheckArgs(UntypedArgsNode args, TypeCheckerContext context) { + var typedPositional = args.positional().stream() + .map(arg -> typeCheckExpression(arg, context)) + .toList(); + + return new TypedArgsNode(typedPositional, List.of(), args.source()); + } + public static TypedExpressionNode typeCheckBoolLiteral( UntypedBoolLiteralNode untyped ) { @@ -16,6 +27,22 @@ public class TypeChecker { ); } + private static TypedExpressionNode typeCheckCall( + UntypedCallNode untyped, + TypeCheckerContext context + ) { + var typedReceiver = typeCheckExpression(untyped.receiver(), context); + var receiverType = (FunctionType) typedReceiver.type(); + + return new TypedCallNode( + typedReceiver, + typeCheckArgs(untyped.args(), context), + false, + receiverType.returnType(), + untyped.source() + ); + } + public static TypedExpressionNode typeCheckExpression( UntypedExpressionNode untyped, TypeCheckerContext context @@ -25,7 +52,7 @@ public class TypeChecker { typeCheckBoolLiteral(untypedBoolLiteral); case UntypedCallNode untypedCall -> - throw new UnsupportedOperationException("TODO"); + typeCheckCall(untypedCall, context); case UntypedIntLiteralNode untypedIntLiteral -> typeCheckIntLiteral(untypedIntLiteral); diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedArgsNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedArgsNodeMatcher.java new file mode 100644 index 00000000..05037526 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedArgsNodeMatcher.java @@ -0,0 +1,36 @@ +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 TypedArgsNodeMatcher extends MatcherBuilder { + public static TypedArgsNodeMatcher empty() { + return new TypedArgsNodeMatcher(Empty.vector()); + } + + public TypedArgsNodeMatcher(PVector> submatchers) { + super(submatchers); + } + + @Override + protected Class expectedType() { + return TypedArgsNode.class; + } + + @Override + protected TypedArgsNodeMatcher create(PVector> submatchers) { + return new TypedArgsNodeMatcher(submatchers); + } + + public TypedArgsNodeMatcher withPositional(Matcher> positional) { + return with(has("positional", x -> x.positional(), positional)); + } + + public TypedArgsNodeMatcher withNamed(Matcher> named) { + return with(has("named", x -> x.named(), named)); + } +} diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedCallNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedCallNodeMatcher.java new file mode 100644 index 00000000..5512d9c8 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedCallNodeMatcher.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.shedlang.compiler.types.Type; +import org.zwobble.precisely.Matcher; + +import static org.zwobble.precisely.Matchers.equalTo; +import static org.zwobble.precisely.Matchers.has; + +public class TypedCallNodeMatcher extends MatcherBuilder { + public static TypedCallNodeMatcher empty() { + return new TypedCallNodeMatcher(Empty.vector()); + } + + public TypedCallNodeMatcher(PVector> submatchers) { + super(submatchers); + } + + @Override + protected Class expectedType() { + return TypedCallNode.class; + } + + @Override + protected TypedCallNodeMatcher create(PVector> submatchers) { + return new TypedCallNodeMatcher(submatchers); + } + + public TypedCallNodeMatcher withArgs(Matcher args) { + return with(has("args", x -> x.args(), args)); + } + + public TypedCallNodeMatcher withHasEffects(boolean hasEffects) { + return with(has("hasEffects", x -> x.hasEffects(), equalTo(hasEffects))); + } + + public TypedCallNodeMatcher withReceiver(Matcher receiver) { + return with(has("receiver", x -> x.receiver(), receiver)); + } + + public TypedCallNodeMatcher withType(Type type) { + return with(has("type", x -> x.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 7716b76a..acf61900 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java @@ -11,6 +11,10 @@ public class TypedNodeMatchers { private TypedNodeMatchers() { } + public static TypedArgsNodeMatcher isTypedArgsNode() { + return TypedArgsNodeMatcher.empty(); + } + public static Matcher isTypedBoolLiteralNode(boolean value) { return instanceOf( TypedBoolLiteralNode.class, @@ -18,6 +22,10 @@ public class TypedNodeMatchers { ); } + public static TypedCallNodeMatcher isTypedCallNode() { + return TypedCallNodeMatcher.empty(); + } + public static Matcher isTypedIntLiteralNode(int value) { return isTypedIntLiteralNode(BigInteger.valueOf(value)); } @@ -29,6 +37,16 @@ public class TypedNodeMatchers { ); } + public static Matcher isTypedNamedArgNode( + String name, + Matcher expression + ) { + return allOf( + has("name", x -> x.name(), equalTo(name)), + has("expression", x -> x.expression(), expression) + ); + } + public static Matcher isTypedReferenceNode(String name, Type type) { return instanceOf( TypedReferenceNode.class, 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 37778968..11e7b38f 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java @@ -3,6 +3,7 @@ package org.shedlang.compiler.ast.untyped; import org.shedlang.compiler.sources.Sources; import java.math.BigInteger; +import java.util.List; public class Untyped { private Untyped() { @@ -20,6 +21,10 @@ public class Untyped { return new UntypedIntLiteralNode(value, Sources.NULL); } + public static UntypedNamedArgNode namedArg(String name, UntypedExpressionNode expression) { + return new UntypedNamedArgNode(name, expression, Sources.NULL); + } + public static UntypedReferenceNode reference(String name) { return new UntypedReferenceNode(name, Sources.NULL); } @@ -27,4 +32,24 @@ public class Untyped { public static UntypedStringLiteralNode stringLiteral(String value) { return new UntypedStringLiteralNode(value, Sources.NULL); } + + public static UntypedCallNode call( + UntypedExpressionNode receiver, + List positionalArgs + ) { + return call(receiver, positionalArgs, List.of()); + } + + public static UntypedCallNode call( + UntypedExpressionNode receiver, + List positionalArgs, + List namedArgs + ) { + return new UntypedCallNode( + receiver, + new UntypedArgsNode(positionalArgs, namedArgs, Sources.NULL), + false, + Sources.NULL + ); + } } diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedArgsNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedArgsNodeMatcher.java index ce782c0d..da40138d 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedArgsNodeMatcher.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedArgsNodeMatcher.java @@ -1,5 +1,6 @@ package org.shedlang.compiler.ast.untyped; +import org.pcollections.Empty; import org.pcollections.PVector; import org.shedlang.compiler.testing.MatcherBuilder; import org.zwobble.precisely.Matcher; @@ -7,8 +8,8 @@ import org.zwobble.precisely.Matcher; import static org.zwobble.precisely.Matchers.has; public class UntypedArgsNodeMatcher extends MatcherBuilder { - public UntypedArgsNodeMatcher() { - super(); + public static UntypedArgsNodeMatcher empty() { + return new UntypedArgsNodeMatcher(Empty.vector()); } public UntypedArgsNodeMatcher(PVector> submatchers) { @@ -22,7 +23,7 @@ public class UntypedArgsNodeMatcher extends MatcherBuilder> submatchers) { - return new UntypedArgsNodeMatcher(); + return new UntypedArgsNodeMatcher(submatchers); } public UntypedArgsNodeMatcher withPositional(Matcher> positional) { diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedCallNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedCallNodeMatcher.java index b9411fec..aeb020a3 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedCallNodeMatcher.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedCallNodeMatcher.java @@ -1,5 +1,6 @@ package org.shedlang.compiler.ast.untyped; +import org.pcollections.Empty; import org.pcollections.PVector; import org.shedlang.compiler.testing.MatcherBuilder; import org.zwobble.precisely.Matcher; @@ -8,8 +9,8 @@ import static org.zwobble.precisely.Matchers.equalTo; import static org.zwobble.precisely.Matchers.has; public class UntypedCallNodeMatcher extends MatcherBuilder { - public UntypedCallNodeMatcher() { - super(); + public static UntypedCallNodeMatcher empty() { + return new UntypedCallNodeMatcher(Empty.vector()); } public UntypedCallNodeMatcher(PVector> submatchers) { @@ -23,7 +24,7 @@ public class UntypedCallNodeMatcher extends MatcherBuilder> submatchers) { - return new UntypedCallNodeMatcher(); + return new UntypedCallNodeMatcher(submatchers); } public UntypedCallNodeMatcher withArgs(Matcher args) { diff --git a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionDefinitionNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionDefinitionNodeMatcher.java index e8a227c3..c88111df 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionDefinitionNodeMatcher.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedFunctionDefinitionNodeMatcher.java @@ -1,5 +1,6 @@ package org.shedlang.compiler.ast.untyped; +import org.pcollections.Empty; import org.pcollections.PVector; import org.shedlang.compiler.testing.MatcherBuilder; import org.zwobble.precisely.Matcher; @@ -10,8 +11,8 @@ import static org.zwobble.precisely.Matchers.equalTo; import static org.zwobble.precisely.Matchers.has; public class UntypedFunctionDefinitionNodeMatcher extends MatcherBuilder { - public UntypedFunctionDefinitionNodeMatcher() { - super(); + public static UntypedFunctionDefinitionNodeMatcher empty() { + return new UntypedFunctionDefinitionNodeMatcher(Empty.vector()); } public UntypedFunctionDefinitionNodeMatcher(PVector> submatchers) { @@ -25,7 +26,7 @@ public class UntypedFunctionDefinitionNodeMatcher extends MatcherBuilder> submatchers) { - return new UntypedFunctionDefinitionNodeMatcher(); + return new UntypedFunctionDefinitionNodeMatcher(submatchers); } public UntypedFunctionDefinitionNodeMatcher withBody(Matcher> body) { 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 04c56443..6c06e52a 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java @@ -9,11 +9,11 @@ public class UntypedNodeMatchers { } public static UntypedArgsNodeMatcher isUntypedArgsNode() { - return new UntypedArgsNodeMatcher(); + return UntypedArgsNodeMatcher.empty(); } public static UntypedCallNodeMatcher isUntypedCallNode() { - return new UntypedCallNodeMatcher(); + return UntypedCallNodeMatcher.empty(); } public static Matcher isUntypedExpressionStatementNode( @@ -26,7 +26,7 @@ public class UntypedNodeMatchers { } public static UntypedFunctionDefinitionNodeMatcher isUntypedFunctionDefinitionNode() { - return new UntypedFunctionDefinitionNodeMatcher(); + return UntypedFunctionDefinitionNodeMatcher.empty(); } public static Matcher isUntypedNamedArgNode( diff --git a/src/test/java/org/shedlang/compiler/testing/MatcherBuilder.java b/src/test/java/org/shedlang/compiler/testing/MatcherBuilder.java index 6736659f..984ba3da 100644 --- a/src/test/java/org/shedlang/compiler/testing/MatcherBuilder.java +++ b/src/test/java/org/shedlang/compiler/testing/MatcherBuilder.java @@ -1,6 +1,5 @@ package org.shedlang.compiler.testing; -import org.pcollections.Empty; import org.pcollections.PVector; import org.zwobble.precisely.MatchResult; import org.zwobble.precisely.Matcher; @@ -11,10 +10,6 @@ import static org.zwobble.precisely.Matchers.instanceOf; public abstract class MatcherBuilder implements Matcher { private final PVector> submatchers; - public MatcherBuilder() { - this(Empty.vector()); - } - public MatcherBuilder(PVector> submatchers) { this.submatchers = submatchers; } diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java new file mode 100644 index 00000000..379368a1 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerCallTests.java @@ -0,0 +1,68 @@ +package org.shedlang.compiler.typechecker; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.untyped.Untyped; +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.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.isSequence; + +public class TypeCheckerCallTests { + @Test + public void canCallFunctionWithNoArgs() { + var untyped = Untyped.call( + Untyped.reference("f"), + List.of() + ); + var functionType = FunctionType.builder() + .withReturnType(Types.STRING) + .build(); + var context = TypeCheckerContext.EMPTY + .enter(P.map(Map.entry("f", functionType))); + + var typed = TypeChecker.typeCheckExpression(untyped, context); + + assertThat( + typed, + isTypedCallNode() + .withReceiver(isTypedReferenceNode("f", functionType)) + .withArgs(isTypedArgsNode().withPositional(isSequence()).withNamed(isSequence())) + .withHasEffects(false) + .withType(Types.STRING) + ); + } + + @Test + public void positionalArgsAreTypedChecked() { + var untyped = Untyped.call( + Untyped.reference("f"), + List.of( + Untyped.stringLiteral("hello"), + Untyped.intLiteral(42) + ) + ); + var functionType = FunctionType.builder() + .addPositionalParam(Types.STRING) + .addPositionalParam(Types.INT) + .build(); + var context = TypeCheckerContext.EMPTY + .enter(P.map(Map.entry("f", functionType))); + + var typed = TypeChecker.typeCheckExpression(untyped, context); + + assertThat( + typed, + isTypedCallNode() + .withArgs(isTypedArgsNode().withPositional(isSequence( + isTypedStringLiteralNode("hello"), + isTypedIntLiteralNode(42) + ))) + ); + } +} -- cgit v1.2.3