From 550bdc5177fa72234a1359795615df9f80a98657 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 19 Dec 2024 18:21:30 +0000 Subject: Rename field access to member access --- .../org/shedlang/compiler/ast/typed/Typed.java | 16 +- .../compiler/ast/typed/TypedNodeMatchers.java | 8 +- .../org/shedlang/compiler/ast/untyped/Untyped.java | 14 +- .../compiler/ast/untyped/UntypedNodeMatchers.java | 22 +- .../compiler/parser/ParserFieldAccessTests.java | 22 -- .../compiler/parser/ParserMemberAccessTests.java | 22 ++ .../compiler/shid/ShidCodeGenFieldAccessTests.java | 109 ---------- .../shid/ShidCodeGenMemberAccessTests.java | 109 ++++++++++ .../typechecker/TypeCheckerFieldAccessTests.java | 239 --------------------- .../typechecker/TypeCheckerMemberAccessTests.java | 239 +++++++++++++++++++++ .../typechecker/TypeCheckerNamespaceTests.java | 2 +- .../compiler/typechecker/TypeCheckerWhenTests.java | 6 +- 12 files changed, 404 insertions(+), 404 deletions(-) delete mode 100644 src/test/java/org/shedlang/compiler/parser/ParserFieldAccessTests.java create mode 100644 src/test/java/org/shedlang/compiler/parser/ParserMemberAccessTests.java delete mode 100644 src/test/java/org/shedlang/compiler/shid/ShidCodeGenFieldAccessTests.java create mode 100644 src/test/java/org/shedlang/compiler/shid/ShidCodeGenMemberAccessTests.java delete mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFieldAccessTests.java create mode 100644 src/test/java/org/shedlang/compiler/typechecker/TypeCheckerMemberAccessTests.java (limited to 'src/test/java') diff --git a/src/test/java/org/shedlang/compiler/ast/typed/Typed.java b/src/test/java/org/shedlang/compiler/ast/typed/Typed.java index bfa2564b..f391a316 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/Typed.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/Typed.java @@ -78,14 +78,6 @@ public class Typed { return new TypedExpressionStatementNode(expression, Sources.NULL); } - public static TypedFieldAccessNode fieldAccess( - TypedExpressionNode receiver, - String fieldName, - Type type - ) { - return new TypedFieldAccessNode(receiver, fieldName, type, Sources.NULL); - } - public static TypedFunctionReturnNode functionReturn(TypedExpressionNode expression) { return new TypedFunctionReturnNode(expression, Sources.NULL); } @@ -159,6 +151,14 @@ public class Typed { ); } + public static TypedMemberAccessNode memberAccess( + TypedExpressionNode receiver, + String memberName, + Type type + ) { + return new TypedMemberAccessNode(receiver, memberName, type, Sources.NULL); + } + public static TypedNamedArgNode namedArg(String name, TypedExpressionNode expression) { return new TypedNamedArgNode(name, expression, Sources.NULL); } 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 2b63f11c..3c67685b 100644 --- a/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/typed/TypedNodeMatchers.java @@ -125,15 +125,15 @@ public class TypedNodeMatchers { ); } - public static Matcher isTypedFieldAccessNode( + public static Matcher isTypedMemberAccessNode( Matcher receiver, - String fieldName, + String memberName, Type type ) { return instanceOf( - TypedFieldAccessNode.class, + TypedMemberAccessNode.class, has("receiver", x -> x.receiver(), receiver), - has("fieldName", x -> x.fieldName(), equalTo(fieldName)), + has("memberName", x -> x.memberName(), equalTo(memberName)), has("type", x -> x.type(), equalTo(type)) ); } 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 2eacfade..58b061cb 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/Untyped.java @@ -106,13 +106,6 @@ public class Untyped { return new UntypedExpressionStatementNode(expression, Sources.NULL); } - public static UntypedExpressionNode fieldAccess( - UntypedExpressionNode receiver, - String fieldName - ) { - return new UntypedFieldAccessNode(receiver, fieldName, Sources.NULL); - } - public static UntypedFunctionReturnNode functionReturn(UntypedExpressionNode expression) { return functionReturn(Optional.of(expression)); } @@ -142,6 +135,13 @@ public class Untyped { return new UntypedIntLiteralNode(value, Sources.NULL); } + public static UntypedExpressionNode memberAccess( + UntypedExpressionNode receiver, + String memberName + ) { + return new UntypedMemberAccessNode(receiver, memberName, Sources.NULL); + } + public static UntypedNamedArgNode namedArg(String name, UntypedExpressionNode expression) { return new UntypedNamedArgNode(name, expression, Sources.NULL); } 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 e0597387..0e11231b 100644 --- a/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java +++ b/src/test/java/org/shedlang/compiler/ast/untyped/UntypedNodeMatchers.java @@ -141,17 +141,6 @@ public class UntypedNodeMatchers { ); } - public static Matcher isUntypedFieldAccessNode( - Matcher receiver, - String fieldName - ) { - return instanceOf( - UntypedFieldAccessNode.class, - has("receiver", x -> x.receiver(), receiver), - has("fieldName", x -> x.fieldName(), equalTo(fieldName)) - ); - } - public static UntypedFunctionDefinitionNodeMatcher isUntypedFunctionDefinitionNode() { return UntypedFunctionDefinitionNodeMatcher.empty(); } @@ -225,6 +214,17 @@ public class UntypedNodeMatchers { ); } + public static Matcher isUntypedMemberAccessNode( + Matcher receiver, + String memberName + ) { + return instanceOf( + UntypedMemberAccessNode.class, + has("receiver", x -> x.receiver(), receiver), + has("memberName", x -> x.memberName(), equalTo(memberName)) + ); + } + public static Matcher isUntypedNamedArgNode( String name, Matcher expression diff --git a/src/test/java/org/shedlang/compiler/parser/ParserFieldAccessTests.java b/src/test/java/org/shedlang/compiler/parser/ParserFieldAccessTests.java deleted file mode 100644 index a543d38e..00000000 --- a/src/test/java/org/shedlang/compiler/parser/ParserFieldAccessTests.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.shedlang.compiler.parser; - -import org.junit.jupiter.api.Test; - -import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.isUntypedFieldAccessNode; -import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.isUntypedReferenceNode; -import static org.shedlang.compiler.parser.ParserTesting.parseString; -import static org.zwobble.precisely.AssertThat.assertThat; - -public class ParserFieldAccessTests { - @Test - public void canParseFieldAccess() { - var source = "x.y"; - - var node = parseString(source, Parser::parseTopLevelExpression); - - assertThat( - node, - isUntypedFieldAccessNode(isUntypedReferenceNode("x"), "y") - ); - } -} diff --git a/src/test/java/org/shedlang/compiler/parser/ParserMemberAccessTests.java b/src/test/java/org/shedlang/compiler/parser/ParserMemberAccessTests.java new file mode 100644 index 00000000..ccb881b7 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/parser/ParserMemberAccessTests.java @@ -0,0 +1,22 @@ +package org.shedlang.compiler.parser; + +import org.junit.jupiter.api.Test; + +import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.isUntypedMemberAccessNode; +import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.isUntypedReferenceNode; +import static org.shedlang.compiler.parser.ParserTesting.parseString; +import static org.zwobble.precisely.AssertThat.assertThat; + +public class ParserMemberAccessTests { + @Test + public void canParseMemberAccess() { + var source = "x.y"; + + var node = parseString(source, Parser::parseTopLevelExpression); + + assertThat( + node, + isUntypedMemberAccessNode(isUntypedReferenceNode("x"), "y") + ); + } +} diff --git a/src/test/java/org/shedlang/compiler/shid/ShidCodeGenFieldAccessTests.java b/src/test/java/org/shedlang/compiler/shid/ShidCodeGenFieldAccessTests.java deleted file mode 100644 index e7560770..00000000 --- a/src/test/java/org/shedlang/compiler/shid/ShidCodeGenFieldAccessTests.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.shedlang.compiler.shid; - -import org.junit.jupiter.api.Test; -import org.shedlang.compiler.ast.typed.Typed; -import org.shedlang.compiler.shid.instructions.ShidFieldLoad; -import org.shedlang.compiler.shid.instructions.ShidMatchers; -import org.shedlang.compiler.types.Effects; -import org.shedlang.compiler.types.NamespaceName; -import org.shedlang.compiler.types.TypeParam; -import org.shedlang.compiler.types.Types; - -import java.util.List; - -import static org.shedlang.compiler.shid.instructions.ShidMatchers.isShidFieldLoad; -import static org.shedlang.compiler.shid.instructions.ShidMatchers.isShidLocalLoad; -import static org.zwobble.precisely.AssertThat.assertThat; -import static org.zwobble.precisely.Matchers.isSequence; - -public class ShidCodeGenFieldAccessTests { - @Test - public void namespaceFieldAccessIsCompiledToFieldLoad() { - var namespaceType = Types.namespace(NamespaceName.of("Example", "Project")); - var node = Typed.fieldAccess( - Typed.reference("Project", namespaceType), - "name", - Types.STRING - ); - var context = ShidCodeGen.Context.EMPTY - .addShedVal("Project"); - - var result = ShidCodeGen.compileExpression(node, context); - - assertThat(result.instructions(), isSequence( - isShidLocalLoad("Project__0"), - ShidMatchers.isShidFieldLoad( - new ShidFieldLoad.Receiver.Namespace(namespaceType.namespaceName()), - "name" - ) - )); - } - - @Test - public void structFieldAccessIsCompiledToFieldLoad() { - var structType = Types.struct(NamespaceName.of("Example", "Project"), "User"); - var node = Typed.fieldAccess( - Typed.reference("user", structType), - "name", - Types.STRING - ); - var context = ShidCodeGen.Context.EMPTY - .addShedVal("user"); - - var result = ShidCodeGen.compileExpression(node, context); - - assertThat(result.instructions(), isSequence( - isShidLocalLoad("user__0"), - isShidFieldLoad(structType, "name") - )); - } - - @Test - public void constructedTypeFieldAccessIsCompiledToFieldLoad() { - var structType = Types.struct(NamespaceName.of("Example", "Project"), "Box"); - var typeConstructor = Types.typeConstructor( - List.of(TypeParam.forStructType(structType, "T")), - structType - ); - var constructedType = Types.constructedStructType( - typeConstructor, - List.of(Types.STRING) - ); - var node = Typed.fieldAccess( - Typed.reference("box", constructedType), - "value", - Types.STRING - ); - var context = ShidCodeGen.Context.EMPTY - .addShedVal("box"); - - var result = ShidCodeGen.compileExpression(node, context); - - assertThat(result.instructions(), isSequence( - isShidLocalLoad("box__0"), - isShidFieldLoad(structType, "value") - )); - } - - @Test - public void userDefinedEffectFieldAccessIsCompiledToFieldLoad() { - var effect = Effects.userDefined(NamespaceName.of("Example", "Project"), "State"); - var node = Typed.fieldAccess( - Typed.reference("State", Types.effectType(effect)), - "read", - Types.STRING - ); - var context = ShidCodeGen.Context.EMPTY - .addShedVal("State"); - - var result = ShidCodeGen.compileExpression(node, context); - - assertThat(result.instructions(), isSequence( - isShidLocalLoad("State__0"), - ShidMatchers.isShidFieldLoad( - new ShidFieldLoad.Receiver.Effect(effect), - "read" - ) - )); - } -} diff --git a/src/test/java/org/shedlang/compiler/shid/ShidCodeGenMemberAccessTests.java b/src/test/java/org/shedlang/compiler/shid/ShidCodeGenMemberAccessTests.java new file mode 100644 index 00000000..9c82692d --- /dev/null +++ b/src/test/java/org/shedlang/compiler/shid/ShidCodeGenMemberAccessTests.java @@ -0,0 +1,109 @@ +package org.shedlang.compiler.shid; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.typed.Typed; +import org.shedlang.compiler.shid.instructions.ShidFieldLoad; +import org.shedlang.compiler.shid.instructions.ShidMatchers; +import org.shedlang.compiler.types.Effects; +import org.shedlang.compiler.types.NamespaceName; +import org.shedlang.compiler.types.TypeParam; +import org.shedlang.compiler.types.Types; + +import java.util.List; + +import static org.shedlang.compiler.shid.instructions.ShidMatchers.isShidFieldLoad; +import static org.shedlang.compiler.shid.instructions.ShidMatchers.isShidLocalLoad; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.isSequence; + +public class ShidCodeGenMemberAccessTests { + @Test + public void namespaceFieldAccessIsCompiledToFieldLoad() { + var namespaceType = Types.namespace(NamespaceName.of("Example", "Project")); + var node = Typed.memberAccess( + Typed.reference("Project", namespaceType), + "name", + Types.STRING + ); + var context = ShidCodeGen.Context.EMPTY + .addShedVal("Project"); + + var result = ShidCodeGen.compileExpression(node, context); + + assertThat(result.instructions(), isSequence( + isShidLocalLoad("Project__0"), + ShidMatchers.isShidFieldLoad( + new ShidFieldLoad.Receiver.Namespace(namespaceType.namespaceName()), + "name" + ) + )); + } + + @Test + public void structFieldAccessIsCompiledToFieldLoad() { + var structType = Types.struct(NamespaceName.of("Example", "Project"), "User"); + var node = Typed.memberAccess( + Typed.reference("user", structType), + "name", + Types.STRING + ); + var context = ShidCodeGen.Context.EMPTY + .addShedVal("user"); + + var result = ShidCodeGen.compileExpression(node, context); + + assertThat(result.instructions(), isSequence( + isShidLocalLoad("user__0"), + isShidFieldLoad(structType, "name") + )); + } + + @Test + public void constructedTypeFieldAccessIsCompiledToFieldLoad() { + var structType = Types.struct(NamespaceName.of("Example", "Project"), "Box"); + var typeConstructor = Types.typeConstructor( + List.of(TypeParam.forStructType(structType, "T")), + structType + ); + var constructedType = Types.constructedStructType( + typeConstructor, + List.of(Types.STRING) + ); + var node = Typed.memberAccess( + Typed.reference("box", constructedType), + "value", + Types.STRING + ); + var context = ShidCodeGen.Context.EMPTY + .addShedVal("box"); + + var result = ShidCodeGen.compileExpression(node, context); + + assertThat(result.instructions(), isSequence( + isShidLocalLoad("box__0"), + isShidFieldLoad(structType, "value") + )); + } + + @Test + public void userDefinedEffectFieldAccessIsCompiledToFieldLoad() { + var effect = Effects.userDefined(NamespaceName.of("Example", "Project"), "State"); + var node = Typed.memberAccess( + Typed.reference("State", Types.effectType(effect)), + "read", + Types.STRING + ); + var context = ShidCodeGen.Context.EMPTY + .addShedVal("State"); + + var result = ShidCodeGen.compileExpression(node, context); + + assertThat(result.instructions(), isSequence( + isShidLocalLoad("State__0"), + ShidMatchers.isShidFieldLoad( + new ShidFieldLoad.Receiver.Effect(effect), + "read" + ) + )); + } +} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFieldAccessTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFieldAccessTests.java deleted file mode 100644 index e555181b..00000000 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerFieldAccessTests.java +++ /dev/null @@ -1,239 +0,0 @@ -package org.shedlang.compiler.typechecker; - -import org.junit.jupiter.api.Test; -import org.shedlang.compiler.ast.untyped.Untyped; -import org.shedlang.compiler.typechecker.errors.MissingStructTypeInfoError; -import org.shedlang.compiler.typechecker.errors.NoSuchFieldError; -import org.shedlang.compiler.types.*; -import org.shedlang.compiler.util.P; - -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedFieldAccessNode; -import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedReferenceNode; -import static org.shedlang.compiler.typechecker.TypeCheckerTesting.typeCheckExpression; -import static org.zwobble.precisely.AssertThat.assertThat; -import static org.zwobble.precisely.Matchers.equalTo; - -public class TypeCheckerFieldAccessTests { - @Test - public void canAccessFieldOnStructType() { - var untyped = Untyped.fieldAccess( - Untyped.reference("user"), - "name" - ); - var structType = Types.struct(NamespaceName.of("Example"), "User"); - var structTypeInfo = StructTypeInfo.builder() - .addField("name", Types.STRING) - .build(); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("user", structType))) - .addStructTypeInfo(structType, structTypeInfo); - - var typed = typeCheckExpression(untyped, context); - - assertThat( - typed.value(), - isTypedFieldAccessNode( - isTypedReferenceNode("user", structType), - "name", - Types.STRING - ) - ); - } - - @Test - public void canAccessFieldWithFixedTypeOnConstructedStructType() { - var untyped = Untyped.fieldAccess( - Untyped.reference("x"), - "size" - ); - var genericType = Types.struct(NamespaceName.of("Example"), "List"); - var typeConstructor = Types.typeConstructor( - List.of(TypeParam.forStructType(genericType, "T")), - genericType - ); - var constructedType = Types.constructedType( - typeConstructor, - List.of(Types.STRING) - ); - var structTypeInfo = StructTypeInfo.builder() - .addField("size", Types.INT) - .build(); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("x", constructedType))) - .addStructTypeInfo(genericType, structTypeInfo); - - var typed = typeCheckExpression(untyped, context); - - assertThat( - typed.value(), - isTypedFieldAccessNode( - isTypedReferenceNode("x", constructedType), - "size", - Types.INT - ) - ); - } - - @Test - public void canAccessFieldWithGenericTypeOnConstructedStructType() { - var untyped = Untyped.fieldAccess( - Untyped.reference("x"), - "value" - ); - var genericType = Types.struct(NamespaceName.of("Example"), "Box"); - var typeConstructor = Types.typeConstructor( - List.of( - TypeParam.forStructType(genericType, "T1"), - TypeParam.forStructType(genericType, "T2"), - TypeParam.forStructType(genericType, "T3") - ), - genericType - ); - var constructedType = Types.constructedType( - typeConstructor, - List.of(Types.INT, Types.STRING, Types.BOOL) - ); - var structTypeInfo = StructTypeInfo.builder() - .addField("value", TypeParam.forStructType(genericType, "T2")) - .build(); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("x", constructedType))) - .addStructTypeInfo(genericType, structTypeInfo); - - var typed = typeCheckExpression(untyped, context); - - assertThat( - typed.value(), - isTypedFieldAccessNode( - isTypedReferenceNode("x", constructedType), - "value", - Types.STRING - ) - ); - } - - @Test - public void canAccessFieldOnNamespaceType() { - var untyped = Untyped.fieldAccess( - Untyped.reference("Example"), - "name" - ); - var namespaceType = Types.namespace(NamespaceName.of("Example")); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("Example", namespaceType))) - .addNamespaceTypeInfo( - namespaceType.namespaceName(), - NamespaceTypeInfo.builder() - .addField("name", Types.STRING) - .build() - ); - - var typed = typeCheckExpression(untyped, context); - - assertThat( - typed.value(), - isTypedFieldAccessNode( - isTypedReferenceNode("Example", namespaceType), - "name", - Types.STRING - ) - ); - } - - @Test - public void canAccessFieldOnEffect() { - var untyped = Untyped.fieldAccess( - Untyped.reference("Effect"), - "mark" - ); - var effect = Effects.userDefined(NamespaceName.of("Example"), "Effect"); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("Effect", Types.effectType(effect)))) - .addUserDefinedEffectInfo( - effect, - UserDefinedEffectInfo.builder() - .addMethod( - "mark", - FunctionType.builder() - .addPositionalParam(Types.BOOL) - .addNamedParam("x", Types.INT) - .withReturnType(Types.STRING) - .build() - ) - .build() - ); - - var typed = typeCheckExpression(untyped, context); - - assertThat( - typed.value(), - isTypedFieldAccessNode( - isTypedReferenceNode("Effect", Types.effectType(effect)), - "mark", - FunctionType.builder() - .addPositionalParam(Types.BOOL) - .addNamedParam("x", Types.INT) - .withEffect(effect) - .withReturnType(Types.STRING) - .build() - ) - ); - } - - @Test - public void whenReceiverIsTypeWithoutFieldsThenErrorIsThrown() { - var untyped = Untyped.fieldAccess( - Untyped.reference("user"), - "name" - ); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("user", Types.STRING))); - - var error = assertThrows(NoSuchFieldError.class, () -> typeCheckExpression(untyped, context)); - - assertThat(error.type(), equalTo(Types.STRING)); - assertThat(error.fieldName(), equalTo("name")); - } - - @Test - public void whenReceiverHasNoSuchFieldThenErrorIsThrown() { - var untyped = Untyped.fieldAccess( - Untyped.reference("user"), - "name" - ); - var structType = Types.struct(NamespaceName.of("Example"), "User"); - var structTypeInfo = StructTypeInfo.builder() - .addField("username", Types.STRING) - .build(); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("user", structType))) - .addStructTypeInfo(structType, structTypeInfo); - - var error = assertThrows(NoSuchFieldError.class, () -> typeCheckExpression(untyped, context)); - - assertThat(error.type(), equalTo(structType)); - assertThat(error.fieldName(), equalTo("name")); - } - - @Test - public void whenStructTypeInfoIsMissingThenErrorIsThrown() { - var untyped = Untyped.fieldAccess( - Untyped.reference("user"), - "name" - ); - var structType = Types.struct(NamespaceName.of("Example"), "User"); - var context = TypeCheckerContext.EMPTY - .enterGlobal(P.map(Map.entry("user", structType))); - - var error = assertThrows( - MissingStructTypeInfoError.class, - () -> typeCheckExpression(untyped, context) - ); - - assertThat(error.type(), equalTo(structType)); - } -} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerMemberAccessTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerMemberAccessTests.java new file mode 100644 index 00000000..7c794243 --- /dev/null +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerMemberAccessTests.java @@ -0,0 +1,239 @@ +package org.shedlang.compiler.typechecker; + +import org.junit.jupiter.api.Test; +import org.shedlang.compiler.ast.untyped.Untyped; +import org.shedlang.compiler.typechecker.errors.MissingStructTypeInfoError; +import org.shedlang.compiler.typechecker.errors.NoSuchFieldError; +import org.shedlang.compiler.types.*; +import org.shedlang.compiler.util.P; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedMemberAccessNode; +import static org.shedlang.compiler.ast.typed.TypedNodeMatchers.isTypedReferenceNode; +import static org.shedlang.compiler.typechecker.TypeCheckerTesting.typeCheckExpression; +import static org.zwobble.precisely.AssertThat.assertThat; +import static org.zwobble.precisely.Matchers.equalTo; + +public class TypeCheckerMemberAccessTests { + @Test + public void canAccessFieldOnStructType() { + var untyped = Untyped.memberAccess( + Untyped.reference("user"), + "name" + ); + var structType = Types.struct(NamespaceName.of("Example"), "User"); + var structTypeInfo = StructTypeInfo.builder() + .addField("name", Types.STRING) + .build(); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("user", structType))) + .addStructTypeInfo(structType, structTypeInfo); + + var typed = typeCheckExpression(untyped, context); + + assertThat( + typed.value(), + isTypedMemberAccessNode( + isTypedReferenceNode("user", structType), + "name", + Types.STRING + ) + ); + } + + @Test + public void canAccessFieldWithFixedTypeOnConstructedStructType() { + var untyped = Untyped.memberAccess( + Untyped.reference("x"), + "size" + ); + var genericType = Types.struct(NamespaceName.of("Example"), "List"); + var typeConstructor = Types.typeConstructor( + List.of(TypeParam.forStructType(genericType, "T")), + genericType + ); + var constructedType = Types.constructedType( + typeConstructor, + List.of(Types.STRING) + ); + var structTypeInfo = StructTypeInfo.builder() + .addField("size", Types.INT) + .build(); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("x", constructedType))) + .addStructTypeInfo(genericType, structTypeInfo); + + var typed = typeCheckExpression(untyped, context); + + assertThat( + typed.value(), + isTypedMemberAccessNode( + isTypedReferenceNode("x", constructedType), + "size", + Types.INT + ) + ); + } + + @Test + public void canAccessFieldWithGenericTypeOnConstructedStructType() { + var untyped = Untyped.memberAccess( + Untyped.reference("x"), + "value" + ); + var genericType = Types.struct(NamespaceName.of("Example"), "Box"); + var typeConstructor = Types.typeConstructor( + List.of( + TypeParam.forStructType(genericType, "T1"), + TypeParam.forStructType(genericType, "T2"), + TypeParam.forStructType(genericType, "T3") + ), + genericType + ); + var constructedType = Types.constructedType( + typeConstructor, + List.of(Types.INT, Types.STRING, Types.BOOL) + ); + var structTypeInfo = StructTypeInfo.builder() + .addField("value", TypeParam.forStructType(genericType, "T2")) + .build(); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("x", constructedType))) + .addStructTypeInfo(genericType, structTypeInfo); + + var typed = typeCheckExpression(untyped, context); + + assertThat( + typed.value(), + isTypedMemberAccessNode( + isTypedReferenceNode("x", constructedType), + "value", + Types.STRING + ) + ); + } + + @Test + public void canAccessFieldOnNamespaceType() { + var untyped = Untyped.memberAccess( + Untyped.reference("Example"), + "name" + ); + var namespaceType = Types.namespace(NamespaceName.of("Example")); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("Example", namespaceType))) + .addNamespaceTypeInfo( + namespaceType.namespaceName(), + NamespaceTypeInfo.builder() + .addField("name", Types.STRING) + .build() + ); + + var typed = typeCheckExpression(untyped, context); + + assertThat( + typed.value(), + isTypedMemberAccessNode( + isTypedReferenceNode("Example", namespaceType), + "name", + Types.STRING + ) + ); + } + + @Test + public void canAccessFieldOnEffect() { + var untyped = Untyped.memberAccess( + Untyped.reference("Effect"), + "mark" + ); + var effect = Effects.userDefined(NamespaceName.of("Example"), "Effect"); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("Effect", Types.effectType(effect)))) + .addUserDefinedEffectInfo( + effect, + UserDefinedEffectInfo.builder() + .addMethod( + "mark", + FunctionType.builder() + .addPositionalParam(Types.BOOL) + .addNamedParam("x", Types.INT) + .withReturnType(Types.STRING) + .build() + ) + .build() + ); + + var typed = typeCheckExpression(untyped, context); + + assertThat( + typed.value(), + isTypedMemberAccessNode( + isTypedReferenceNode("Effect", Types.effectType(effect)), + "mark", + FunctionType.builder() + .addPositionalParam(Types.BOOL) + .addNamedParam("x", Types.INT) + .withEffect(effect) + .withReturnType(Types.STRING) + .build() + ) + ); + } + + @Test + public void whenReceiverIsTypeWithoutFieldsThenErrorIsThrown() { + var untyped = Untyped.memberAccess( + Untyped.reference("user"), + "name" + ); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("user", Types.STRING))); + + var error = assertThrows(NoSuchFieldError.class, () -> typeCheckExpression(untyped, context)); + + assertThat(error.type(), equalTo(Types.STRING)); + assertThat(error.fieldName(), equalTo("name")); + } + + @Test + public void whenReceiverHasNoSuchFieldThenErrorIsThrown() { + var untyped = Untyped.memberAccess( + Untyped.reference("user"), + "name" + ); + var structType = Types.struct(NamespaceName.of("Example"), "User"); + var structTypeInfo = StructTypeInfo.builder() + .addField("username", Types.STRING) + .build(); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("user", structType))) + .addStructTypeInfo(structType, structTypeInfo); + + var error = assertThrows(NoSuchFieldError.class, () -> typeCheckExpression(untyped, context)); + + assertThat(error.type(), equalTo(structType)); + assertThat(error.fieldName(), equalTo("name")); + } + + @Test + public void whenStructTypeInfoIsMissingThenErrorIsThrown() { + var untyped = Untyped.memberAccess( + Untyped.reference("user"), + "name" + ); + var structType = Types.struct(NamespaceName.of("Example"), "User"); + var context = TypeCheckerContext.EMPTY + .enterGlobal(P.map(Map.entry("user", structType))); + + var error = assertThrows( + MissingStructTypeInfoError.class, + () -> typeCheckExpression(untyped, context) + ); + + assertThat(error.type(), equalTo(structType)); + } +} diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java index 28b2421e..e7bf1044 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerNamespaceTests.java @@ -430,7 +430,7 @@ public class TypeCheckerNamespaceTests { .withName("printUser") .addPositionalParam("user", Untyped.typeLevelReference("User")) .withReturnType(Untyped.typeLevelReference("Unit")) - .addStatement(Untyped.expressionStatement(Untyped.fieldAccess(Untyped.reference("user"), "name"))) + .addStatement(Untyped.expressionStatement(Untyped.memberAccess(Untyped.reference("user"), "name"))) .build(), UntypedStructDefinitionNode.builder() .withName("User") diff --git a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerWhenTests.java b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerWhenTests.java index c6fd0480..02f40efa 100644 --- a/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerWhenTests.java +++ b/src/test/java/org/shedlang/compiler/typechecker/TypeCheckerWhenTests.java @@ -281,7 +281,7 @@ public class TypeCheckerWhenTests { Untyped.typeLevelReference(VARIANT_TYPE_1.name()), Untyped.block(List.of( Untyped.blockReturn( - Untyped.fieldAccess( + Untyped.memberAccess( Untyped.reference("x"), "value" ) @@ -542,7 +542,7 @@ public class TypeCheckerWhenTests { Untyped.whenCase( Untyped.typeLevelReference(VARIANT_TYPE_1.name()), Untyped.block(List.of( - Untyped.blockReturn(Untyped.fieldAccess( + Untyped.blockReturn(Untyped.memberAccess( Untyped.reference("x"), "a" )) @@ -551,7 +551,7 @@ public class TypeCheckerWhenTests { Untyped.whenCase( Untyped.typeLevelReference(VARIANT_TYPE_2.name()), Untyped.block(List.of( - Untyped.blockReturn(Untyped.fieldAccess( + Untyped.blockReturn(Untyped.memberAccess( Untyped.reference("x"), "b" )) -- cgit v1.2.3