From bd1515f9b219cef789cc31b2f8efbb217bfc2c3c Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Fri, 7 Feb 2025 20:53:02 +0000 Subject: Add basic type checking for native type declarations --- .../ast/typed/TypedNamespaceStatementNode.java | 2 +- .../ast/typed/TypedNativeTypeDeclarationNode.java | 50 +++++++++ .../org/shedlang/compiler/shid/ShidCodeGen.java | 6 + .../shedlang/compiler/typechecker/TypeChecker.java | 44 +++++++- ...notDefineNativeTypeOutsideOfNamespaceError.java | 10 ++ .../java/org/shedlang/compiler/types/Scope.java | 2 +- .../org/shedlang/compiler/types/TypeParam.java | 2 +- .../TypedNativeTypeDeclarationNodeMatcher.java | 62 +++++++++++ .../compiler/ast/typed/TypedNodeMatchers.java | 4 + .../TypeCheckerNativeTypeDeclarationTests.java | 122 +++++++++++++++++++++ .../org/shedlang/compiler/types/TypeMatchers.java | 23 ++++ 11 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNode.java create mode 100644 src/main/java/org/shedlang/compiler/typechecker/errors/CannotDefineNativeTypeOutsideOfNamespaceError.java create mode 100644 src/test/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNodeMatcher.java create mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNativeTypeDeclarationTests.java (limited to 'src') diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedNamespaceStatementNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedNamespaceStatementNode.java index d38cff5e..c63b58d7 100644 --- a/src/main/java/org/shedlang/compiler/ast/typed/TypedNamespaceStatementNode.java +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedNamespaceStatementNode.java @@ -1,4 +1,4 @@ package org.shedlang.compiler.ast.typed; -public sealed interface TypedNamespaceStatementNode extends TypedNode permits TypedAssignNode, TypedEffectDefinitionNode, TypedFunctionDefinitionNode, TypedNativeFunctionDeclarationNode, TypedStructDefinitionNode, TypedSumTypeDefinitionNode { +public sealed interface TypedNamespaceStatementNode extends TypedNode permits TypedAssignNode, TypedEffectDefinitionNode, TypedFunctionDefinitionNode, TypedNativeFunctionDeclarationNode, TypedNativeTypeDeclarationNode, TypedStructDefinitionNode, TypedSumTypeDefinitionNode { } diff --git a/src/main/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNode.java b/src/main/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNode.java new file mode 100644 index 00000000..2c438577 --- /dev/null +++ b/src/main/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNode.java @@ -0,0 +1,50 @@ +package org.shedlang.compiler.ast.typed; + +import org.pcollections.PVector; +import org.shedlang.compiler.sources.Source; +import org.shedlang.compiler.sources.Sources; +import org.shedlang.compiler.types.*; +import org.shedlang.compiler.util.P; + +import java.util.List; + +public record TypedNativeTypeDeclarationNode( + TypeOrConstructor typeOrConstructor, + List methods, + Source source +) implements TypedNamespaceStatementNode { + public static Builder builder() { + var namespaceName = NamespaceName.of("Default", "Test", "Namespace"); + return new Builder( + TypeOrConstructor.type(Types.nativeType(namespaceName, "X")), + P.vector() + ); + } + + public record Builder( + TypeOrConstructor type, + PVector methods + ) { + public TypedNativeTypeDeclarationNode build() { + return new TypedNativeTypeDeclarationNode( + this.type, + this.methods, + Sources.NULL + ); + } + + public Builder withType(SimpleNativeType type) { + return new Builder( + TypeOrConstructor.type(type), + this.methods + ); + } + + public Builder addMethod(TypedNativeFunctionDeclarationNode method) { + return new Builder( + this.type, + this.methods.plus(method) + ); + } + } +} diff --git a/src/main/java/org/shedlang/compiler/shid/ShidCodeGen.java b/src/main/java/org/shedlang/compiler/shid/ShidCodeGen.java index bf6d2072..11827861 100644 --- a/src/main/java/org/shedlang/compiler/shid/ShidCodeGen.java +++ b/src/main/java/org/shedlang/compiler/shid/ShidCodeGen.java @@ -593,6 +593,9 @@ public class ShidCodeGen { case TypedNativeFunctionDeclarationNode nativeFunctionDeclaration -> 1; + case TypedNativeTypeDeclarationNode nativeTypeDeclaration -> + 0; + case TypedStructDefinitionNode structDefinition -> 0; @@ -642,6 +645,9 @@ public class ShidCodeGen { case TypedNativeFunctionDeclarationNode nativeFunctionDeclaration -> compileNativeFunctionDeclaration(nativeFunctionDeclaration, context); + case TypedNativeTypeDeclarationNode nativeTypeDeclarationNode -> + throw new UnsupportedOperationException("TODO"); + case TypedStructDefinitionNode structDefinition -> compileStructDefinition(structDefinition, context); diff --git a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java index 98fe892e..a9d98ff2 100644 --- a/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/shedlang/compiler/typechecker/TypeChecker.java @@ -1609,7 +1609,7 @@ public class TypeChecker { typeCheckStepsNativeFunctionDeclaration(nativeFunctionDeclaration); case UntypedNativeTypeDeclarationNode nativeTypeDeclaration -> - throw new UnsupportedOperationException("TODO"); + typeCheckStepsNativeTypeDeclaration(nativeTypeDeclaration); case UntypedStructDefinitionNode structDefinition -> typeCheckStepsStructDefinition(structDefinition); @@ -1658,6 +1658,48 @@ public class TypeChecker { }; } + private static TypeCheckSteps typeCheckStepsNativeTypeDeclaration( + UntypedNativeTypeDeclarationNode untyped + ) { + return new TypeCheckSteps<>() { + @Override + public TypeCheckerContext declareTypes(TypeCheckerContext context) { + var namespaceName = context.namespaceName() + .orElseThrow(() -> new CannotDefineNativeTypeOutsideOfNamespaceError(untyped.source())); + + var type = new SimpleNativeType(namespaceName, untyped.name()); + + var typeOrConstructor = createTypeOrConstructor( + untyped.typeLevelParams(), + type, + new Scope.NativeType(type) + ); + + context = context.addLexicalState(untyped.name(), typeOrConstructor); + + return context.declareVariableType( + untyped.name(), + typeOrConstructor.toMetaType(), + untyped.source() + ); + } + + @Override + public TypeCheckerResult finish(TypeCheckerContext context) { + var typeOrConstructor = (TypeOrConstructor) context.getLexicalState( + TypeOrConstructor.class, + untyped.name() + ); + + return context.toResult(new TypedNativeTypeDeclarationNode( + typeOrConstructor, + List.of(), + untyped.source() + )); + } + }; + } + private static TypedParamNode typeCheckParam( UntypedParamNode untyped, TypeCheckerContext context diff --git a/src/main/java/org/shedlang/compiler/typechecker/errors/CannotDefineNativeTypeOutsideOfNamespaceError.java b/src/main/java/org/shedlang/compiler/typechecker/errors/CannotDefineNativeTypeOutsideOfNamespaceError.java new file mode 100644 index 00000000..01e9dead --- /dev/null +++ b/src/main/java/org/shedlang/compiler/typechecker/errors/CannotDefineNativeTypeOutsideOfNamespaceError.java @@ -0,0 +1,10 @@ +package org.shedlang.compiler.typechecker.errors; + +import org.shedlang.compiler.errors.InternalCompilerError; +import org.shedlang.compiler.sources.Source; + +public class CannotDefineNativeTypeOutsideOfNamespaceError extends InternalCompilerError { + public CannotDefineNativeTypeOutsideOfNamespaceError(Source source) { + super("Cannot define native type outside of namespace", source); + } +} diff --git a/src/main/java/org/shedlang/compiler/types/Scope.java b/src/main/java/org/shedlang/compiler/types/Scope.java index b0141916..9a61e51e 100644 --- a/src/main/java/org/shedlang/compiler/types/Scope.java +++ b/src/main/java/org/shedlang/compiler/types/Scope.java @@ -2,7 +2,7 @@ package org.shedlang.compiler.types; public sealed interface Scope { record Function(NamespaceName namespaceName, String name) implements Scope {} - record Native(NativeType type) implements Scope {} + record NativeType(org.shedlang.compiler.types.NativeType type) implements Scope {} record Struct(SimpleStructType type) implements Scope {} record Sum(SimpleSumType type) implements Scope {} } diff --git a/src/main/java/org/shedlang/compiler/types/TypeParam.java b/src/main/java/org/shedlang/compiler/types/TypeParam.java index 6d311940..c996dd97 100644 --- a/src/main/java/org/shedlang/compiler/types/TypeParam.java +++ b/src/main/java/org/shedlang/compiler/types/TypeParam.java @@ -10,7 +10,7 @@ public record TypeParam(Scope scope, String name) implements Type, TypeLevelPara } public static TypeParam forNativeType(SimpleNativeType nativeType, String name) { - return new TypeParam(new Scope.Native(nativeType), name); + return new TypeParam(new Scope.NativeType(nativeType), name); } public static TypeParam forStructType(SimpleStructType structType, String name) { diff --git a/src/test/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNodeMatcher.java b/src/test/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNodeMatcher.java new file mode 100644 index 00000000..16d3f36c --- /dev/null +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNativeTypeDeclarationNodeMatcher.java @@ -0,0 +1,62 @@ +package org.shedlang.compiler.ast.typed; + +import org.pcollections.PVector; +import org.shedlang.compiler.testing.MatcherBuilder; +import org.shedlang.compiler.types.SimpleNativeType; +import org.shedlang.compiler.types.TypeConstructor; +import org.shedlang.compiler.util.P; +import org.zwobble.precisely.Matcher; + +import static org.shedlang.compiler.types.TypeMatchers.isConstructorNotType; +import static org.shedlang.compiler.types.TypeMatchers.isTypeNotConstructor; +import static org.zwobble.precisely.Matchers.has; + +public class TypedNativeTypeDeclarationNodeMatcher extends MatcherBuilder< + TypedNamespaceStatementNode, + TypedNativeTypeDeclarationNode, + TypedNativeTypeDeclarationNodeMatcher +> { + public static TypedNativeTypeDeclarationNodeMatcher empty() { + return new TypedNativeTypeDeclarationNodeMatcher(P.vector()); + } + + private TypedNativeTypeDeclarationNodeMatcher( + PVector> submatchers + ) { + super(submatchers); + } + + @Override + protected Class expectedType() { + return TypedNativeTypeDeclarationNode.class; + } + + @Override + protected TypedNativeTypeDeclarationNodeMatcher create( + PVector> submatchers + ) { + return new TypedNativeTypeDeclarationNodeMatcher(submatchers); + } + + public TypedNativeTypeDeclarationNodeMatcher withType(Matcher type) { + return with(has( + "typeOrConstructor", + x -> x.typeOrConstructor(), + isTypeNotConstructor(type) + )); + } + + public TypedNativeTypeDeclarationNodeMatcher withTypeConstructor(Matcher> type) { + return with(has( + "typeOrConstructor", + x -> x.typeOrConstructor(), + isConstructorNotType(type) + )); + } + + public TypedNativeTypeDeclarationNodeMatcher withMethods( + Matcher> methods + ) { + return with(has("methods", x -> x.methods(), methods)); + } +} 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 7c0c4396..5d81d0f3 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java @@ -283,6 +283,10 @@ public class TypedNodeMatchers { return TypedNativeFunctionDeclarationNodeMatcher.empty(); } + public static TypedNativeTypeDeclarationNodeMatcher isTypedNativeTypeDeclarationNode() { + return TypedNativeTypeDeclarationNodeMatcher.empty(); + } + public static Matcher isTypedParamNode( String name, Matcher>> type diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNativeTypeDeclarationTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNativeTypeDeclarationTests.java new file mode 100644 index 00000000..c9dca725 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNativeTypeDeclarationTests.java @@ -0,0 +1,122 @@ +package org.shedlang.compiler.typechecker; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.untyped.Untyped; +import org.shedlang.compiler.ast.untyped.UntypedNativeTypeDeclarationNode; +import org.shedlang.compiler.typechecker.errors.CannotDefineNativeTypeOutsideOfNamespaceError; +import org.shedlang.compiler.types.NamespaceName; +import org.shedlang.compiler.types.Types; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedNativeTypeDeclarationNode; +import static org.shedlang.compiler.typechecker.TypeCheckerTesting.typeCheckNamespaceStatement; +import static org.shedlang.compiler.types.TypeMatchers.*; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.*; + +public class TypeCheckerNativeTypeDeclarationTests { + private static final NamespaceName NAMESPACE_NAME = NamespaceName.of("Example"); + + @Test + public void whenOutsideOfNamespaceThenErrorIsThrown() { + var untyped = UntypedNativeTypeDeclarationNode.builder() + .withName("ExampleType") + .build(); + var context = TypeCheckerContext.EMPTY; + + assertThrows( + CannotDefineNativeTypeOutsideOfNamespaceError.class, + () -> TypeChecker.typeCheckStepsNamespaceStatement(untyped).typeCheckAllPhases(context) + ); + } + + @Test + public void whenDeclarationHasNoTypeLevelParamsThenTypedNodeHasSimpleNativeType() { + var untyped = UntypedNativeTypeDeclarationNode.builder() + .withName("ExampleType") + .build(); + var context = TypeCheckerContext.EMPTY; + + var typed = typeCheckNamespaceStatement(untyped, context); + + assertThat( + typed.value(), + isTypedNativeTypeDeclarationNode() + .withType(isNativeType(NAMESPACE_NAME, "ExampleType")) + ); + } + + @Test + public void whenDeclarationHasTypeLevelParamsThenTypedNodeHasTypeConstructor() { + var untyped = UntypedNativeTypeDeclarationNode.builder() + .withName("ExampleType") + .withTypeLevelParams(List.of( + Untyped.typeParam("T1"), + Untyped.typeParam("T2") + )) + .build(); + var context = TypeCheckerContext.EMPTY; + + var typed = typeCheckNamespaceStatement(untyped, context); + + assertThat( + typed.value(), + isTypedNativeTypeDeclarationNode() + .withTypeConstructor(isTypeConstructor( + isSequence( + isNativeTypeTypeLevelParam(NAMESPACE_NAME, "ExampleType", "T1"), + isNativeTypeTypeLevelParam(NAMESPACE_NAME, "ExampleType", "T2") + ), + isNativeType(NAMESPACE_NAME, "ExampleType") + )) + ); + } + + @Test + public void whenDeclarationHasNoTypeParamsThenTypeIsAddedToEnvironment() { + var untyped = UntypedNativeTypeDeclarationNode.builder() + .withName("ExampleType") + .build(); + var context = TypeCheckerContext.EMPTY; + + var typed = typeCheckNamespaceStatement(untyped, context); + + assertThat( + typed.context().compileTimeLookup("ExampleType"), + isOptionalOf(equalTo(Variable.defined( + Types.metaType(Types.nativeType(NAMESPACE_NAME, "ExampleType")), + VariableType.CONSTANT + ))) + ); + } + + @Test + public void whenDeclarationHasTypeParamsThenTypeConstructorIsAddedToEnvironment() { + var untyped = UntypedNativeTypeDeclarationNode.builder() + .withName("ExampleType") + .withTypeLevelParams(List.of( + Untyped.typeParam("T1"), + Untyped.typeParam("T2") + )) + .build(); + var context = TypeCheckerContext.EMPTY; + + var typed = typeCheckNamespaceStatement(untyped, context); + + assertThat( + typed.context().compileTimeLookup("ExampleType"), + isOptionalOf(allOf( + has("type", x -> x.type(), isOptionalOf(isTypeLevelValueType(isTypeConstructor( + isSequence( + isNativeTypeTypeLevelParam(NAMESPACE_NAME, "ExampleType", "T1"), + isNativeTypeTypeLevelParam(NAMESPACE_NAME, "ExampleType", "T2") + ), + isNativeType(NAMESPACE_NAME, "ExampleType") + )))), + has("variableType", x -> x.variableType(), equalTo(VariableType.CONSTANT)) + )) + ); + } +} diff --git a/src/test/java/org/shedlang/compiler/types/TypeMatchers.java b/src/test/java/org/shedlang/compiler/types/TypeMatchers.java index 2c777c76..c6480342 100644 --- a/src/test/java/org/shedlang/compiler/types/TypeMatchers.java +++ b/src/test/java/org/shedlang/compiler/types/TypeMatchers.java @@ -10,6 +10,14 @@ public class TypeMatchers { private TypeMatchers() { } + public static Matcher isNativeType(NamespaceName namespaceName, String name) { + return instanceOf( + SimpleNativeType.class, + has("namespaceName", x -> x.namespaceName(), equalTo(namespaceName)), + has("name", x -> x.name(), equalTo(name)) + ); + } + public static Matcher isStructType(NamespaceName namespaceName, String name) { return instanceOf( SimpleStructType.class, @@ -55,6 +63,21 @@ public class TypeMatchers { ); } + public static Matcher isNativeTypeTypeLevelParam( + NamespaceName namespaceName, + String typeName, + String paramName + ) { + return instanceOf( + TypeParam.class, + has("scope", x -> x.scope(), instanceOf( + Scope.NativeType.class, + has("type", x -> x.type(), isNativeType(namespaceName, typeName)) + )), + has("name", x -> x.name(), equalTo(paramName)) + ); + } + public static Matcher isStructTypeLevelParam( NamespaceName namespaceName, String structName, -- cgit v1.2.3