diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-11-06 15:51:57 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-11-06 15:51:57 +0000 |
| commit | 8906b59513edbb31df4fa62adad8105c45c2ae0c (patch) | |
| tree | 1f7ccc32144e0d1b5443a28dea077135ed321a81 /src | |
| parent | 050d2e54aafb4408ff3d7eb91eaa0ff5a873fa44 (diff) | |
Evaluate constant numbers
Diffstat (limited to 'src')
4 files changed, 69 insertions, 28 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java index 5ce2be4..0ebb9c7 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java @@ -717,8 +717,8 @@ public class TypeChecker { case UntypedMemberAccessNode memberAccess -> { return evalMemberAccess(memberAccess, context); } - case UntypedNumberLiteralNode _ -> { - throw new UnsupportedOperationException("TODO"); + case UntypedNumberLiteralNode numberLiteral -> { + return evalNumberLiteral(numberLiteral); } case UntypedReferenceNode reference -> { return lookupTypeLevelValue(reference.name(), reference.source(), context); @@ -740,7 +740,7 @@ public class TypeChecker { } } - case Table _, Type _, TypeConstructor<?> _ -> { + case ConstantNumber _, Table _, Type _, TypeConstructor<?> _ -> { } } @@ -750,4 +750,10 @@ public class TypeChecker { memberAccess.source() ); } + + private static TypeLevelValue evalNumberLiteral( + UntypedNumberLiteralNode numberLiteral + ) { + return new ConstantNumber(numberLiteral.value()); + } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstantNumber.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstantNumber.java new file mode 100644 index 0000000..bd16d81 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/ConstantNumber.java @@ -0,0 +1,12 @@ +package org.zwobble.hobgoblin.compiler.types; + +import java.math.BigDecimal; + +public record ConstantNumber( + BigDecimal value +) implements TypeLevelValue { + @Override + public String describe() { + return value.toString(); + } +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java index 8d12c2e..d2d8f6d 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java @@ -1,5 +1,5 @@ package org.zwobble.hobgoblin.compiler.types; -public sealed interface TypeLevelValue permits DatabaseSchema, Table, Type, TypeConstructor { +public sealed interface TypeLevelValue permits ConstantNumber, DatabaseSchema, Table, Type, TypeConstructor { String describe(); } diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerEvalExpressionTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerEvalExpressionTests.java index cf1e73c..a112c1d 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerEvalExpressionTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerEvalExpressionTests.java @@ -2,6 +2,7 @@ package org.zwobble.hobgoblin.compiler.typechecker; import org.junit.jupiter.api.Test; import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedMemberAccessNode; +import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedNumberLiteralNode; import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedReferenceNode; import org.zwobble.hobgoblin.compiler.builtins.NativeTypes; import org.zwobble.hobgoblin.compiler.sources.NullSource; @@ -10,6 +11,7 @@ import org.zwobble.hobgoblin.compiler.typechecker.errors.UnexpectedTypeError; import org.zwobble.hobgoblin.compiler.types.*; import org.zwobble.hobgoblin.compiler.util.SequencedMaps; +import java.math.BigDecimal; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -17,6 +19,51 @@ import static org.zwobble.precisely.AssertThat.assertThat; import static org.zwobble.precisely.Matchers.equalTo; public class TypeCheckerEvalExpressionTests { + // == Member access == + + @Test + public void canReadMemberField() { + var untyped = UntypedMemberAccessNode.arbitrary() + .withReceiver(UntypedReferenceNode.arbitrary().withName("blogApp")) + .withMemberName("authors") + .build(); + var globalContext = TypeCheckerContextArb.globalContext(); + var namespaceName = NamespaceName.of("App"); + var namespaceContext = globalContext.enterNamespace(namespaceName); + var databaseSchema = new DatabaseSchema(namespaceName, "blogApp"); + namespaceContext.declare("blogApp", Types.databaseSchema(databaseSchema), NullSource.INSTANCE); + var table = new Table(databaseSchema, "authors"); + var rowType = new SimpleStructType(namespaceName, "Author"); + var idField = new Field("id", NativeTypes.INT_32, NullSource.INSTANCE); + var databaseSchemaInfo = new DatabaseSchemaInfo(SequencedMaps.ofEntries( + Map.entry("authors", new TableInfo(table, rowType, idField)) + )); + namespaceContext.defineDatabaseSchema(databaseSchema, databaseSchemaInfo); + + var typed = TypeChecker.evalExpression(untyped, namespaceContext); + + assertThat(typed, equalTo(table)); + } + + // == Number literal == + + @Test + public void canEvaluateNumber() { + // TODO: infer more precise type for number + var untyped = UntypedNumberLiteralNode.arbitrary() + .withValue(BigDecimal.valueOf(42)) + .build(); + var globalContext = TypeCheckerContextArb.globalContext(); + var namespaceName = NamespaceName.of("App"); + var namespaceContext = globalContext.enterNamespace(namespaceName); + + var typed = TypeChecker.evalExpression(untyped, namespaceContext); + + assertThat(typed, equalTo(new ConstantNumber(BigDecimal.valueOf(42)))); + } + + // == References == + @Test public void whenVariableIsTypeLevelValueThenCanEvaluateReference() { var untyped = UntypedReferenceNode.arbitrary() @@ -68,28 +115,4 @@ public class TypeCheckerEvalExpressionTests { assertThat(error.variableName(), equalTo("count")); } - - @Test - public void canReadMemberField() { - var untyped = UntypedMemberAccessNode.arbitrary() - .withReceiver(UntypedReferenceNode.arbitrary().withName("blogApp")) - .withMemberName("authors") - .build(); - var globalContext = TypeCheckerContextArb.globalContext(); - var namespaceName = NamespaceName.of("App"); - var namespaceContext = globalContext.enterNamespace(namespaceName); - var databaseSchema = new DatabaseSchema(namespaceName, "blogApp"); - namespaceContext.declare("blogApp", Types.databaseSchema(databaseSchema), NullSource.INSTANCE); - var table = new Table(databaseSchema, "authors"); - var rowType = new SimpleStructType(namespaceName, "Author"); - var idField = new Field("id", NativeTypes.INT_32, NullSource.INSTANCE); - var databaseSchemaInfo = new DatabaseSchemaInfo(SequencedMaps.ofEntries( - Map.entry("authors", new TableInfo(table, rowType, idField)) - )); - namespaceContext.defineDatabaseSchema(databaseSchema, databaseSchemaInfo); - - var typed = TypeChecker.evalExpression(untyped, namespaceContext); - - assertThat(typed, equalTo(table)); - } } |
