summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-04-25 10:44:02 +0100
committerMichael Williamson <mike@zwobble.org>2026-04-25 10:44:02 +0100
commit39ef07c887e33aff56c087388980a92e750b547c (patch)
treea41e907d69c54a5d38045d0bc73797df34422ed0 /src
parent032cdc6462ad575f62b349152f31e87b187e974d (diff)
Type check namespace bodies
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java4
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java25
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java16
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java (renamed from src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java)23
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java15
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceTests.java49
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java3
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java8
8 files changed, 112 insertions, 31 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
index 6daa31b..4f56fb1 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
@@ -2,7 +2,7 @@ package org.zwobble.hobgoblin.compiler;
import org.zwobble.hobgoblin.compiler.parser.Parser;
import org.zwobble.hobgoblin.compiler.typechecker.TypeChecker;
-import org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerContext;
+import org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerGlobalContext;
import org.zwobble.hobgoblin.compiler.types.NamespaceName;
import org.zwobble.sourcetext.SourceText;
@@ -28,7 +28,7 @@ public class HobgoblinCompiler {
var sourceText = SourceText.fromString(sourceFile.toPath().toString(), sourceContents);
var untypedNamespaceNode = Parser.parseNamespace(sourceText, namespaceName);
- var typeCheckerContext = TypeCheckerContext.initial();
+ var typeCheckerContext = TypeCheckerGlobalContext.initial();
var typedNamespaceNode = TypeChecker.typeCheckNamespace(untypedNamespaceNode, typeCheckerContext);
}
}
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 0a301c6..b8bdd7f 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
@@ -7,6 +7,7 @@ import org.zwobble.hobgoblin.compiler.types.TypeLevelValue;
import org.zwobble.hobgoblin.compiler.types.TypeLevelValueType;
import org.zwobble.hobgoblin.compiler.types.TypeSet;
+import java.util.ArrayList;
import java.util.List;
public class TypeChecker {
@@ -15,18 +16,26 @@ public class TypeChecker {
public static TypedNamespaceNode typeCheckNamespace(
UntypedNamespaceNode untyped,
- TypeCheckerContext context
+ TypeCheckerGlobalContext context
) {
+ var namespaceContext = context.enterNamespace(untyped.namespaceName());
+
+ var typedBody = new ArrayList<TypedNamespaceStatementNode>();
+ for (var untypedStatement : untyped.body()) {
+ var typedStatement = typeCheckNamespaceStatement(untypedStatement, namespaceContext);
+ typedBody.add(typedStatement);
+ }
+
return new TypedNamespaceNode(
untyped.namespaceName(),
- List.of(),
+ typedBody,
untyped.source()
);
}
static TypedNamespaceStatementNode typeCheckNamespaceStatement(
UntypedNamespaceStatementNode untyped,
- TypeCheckerContext context
+ TypeCheckerNamespaceContext context
) {
return switch (untyped) {
case UntypedStructDefinitionNode untypedStructDefinition ->
@@ -36,11 +45,9 @@ public class TypeChecker {
private static TypedStructDefinitionNode typeCheckStructDefinition(
UntypedStructDefinitionNode untyped,
- TypeCheckerContext context
+ TypeCheckerNamespaceContext context
) {
- // TODO: better error when not in namespace
- var namespaceName = context.namespaceName().orElseThrow();
- var structType = new StructType(namespaceName, untyped.name());
+ var structType = new StructType(context.namespaceName(), untyped.name());
return new TypedStructDefinitionNode(
structType,
@@ -51,7 +58,7 @@ public class TypeChecker {
static TypedTypeLevelExpressionNode<? extends TypeLevelValue> typeCheckTypeLevelExpression(
UntypedTypeLevelExpressionNode untyped,
- TypeCheckerContext context
+ TypeCheckerNamespaceContext context
) {
return switch (untyped) {
case UntypedTypeLevelReferenceNode untypedTypeLevelReference ->
@@ -61,7 +68,7 @@ public class TypeChecker {
private static TypedTypeLevelExpressionNode<? extends TypeLevelValue> typeCheckTypeLevelReference(
UntypedTypeLevelReferenceNode untyped,
- TypeCheckerContext context
+ TypeCheckerNamespaceContext context
) {
var variable = context.lookup(untyped.name())
.orElseThrow(() -> new UndeclaredVariableError(untyped.name(), untyped.source()));
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java
new file mode 100644
index 0000000..3915767
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java
@@ -0,0 +1,16 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.types.NamespaceName;
+
+public class TypeCheckerGlobalContext {
+ public static TypeCheckerGlobalContext initial() {
+ return new TypeCheckerGlobalContext();
+ }
+
+ private TypeCheckerGlobalContext() {
+ }
+
+ public TypeCheckerNamespaceContext enterNamespace(NamespaceName namespaceName) {
+ return TypeCheckerNamespaceContext.initial(namespaceName);
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
index e1109f9..c1a23fd 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
@@ -7,20 +7,23 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-public class TypeCheckerContext {
- private Optional<NamespaceName> namespaceName;
+public class TypeCheckerNamespaceContext {
+ public static TypeCheckerNamespaceContext initial(NamespaceName namespaceName) {
+ return new TypeCheckerNamespaceContext(namespaceName, new HashMap<>());
+ }
+
+ private final NamespaceName namespaceName;
private final Map<String, Variable> variables;
- private TypeCheckerContext(Optional<NamespaceName> namespaceName, Map<String, Variable> variables) {
+ private TypeCheckerNamespaceContext(
+ NamespaceName namespaceName,
+ Map<String, Variable> variables
+ ) {
this.namespaceName = namespaceName;
this.variables = variables;
}
- public static TypeCheckerContext initial() {
- return new TypeCheckerContext(Optional.empty(), new HashMap<>());
- }
-
- public Optional<NamespaceName> namespaceName() {
+ public NamespaceName namespaceName() {
return this.namespaceName;
}
@@ -37,8 +40,4 @@ public class TypeCheckerContext {
public Optional<Variable> lookup(String name) {
return Optional.ofNullable(this.variables.get(name));
}
-
- public void enterNamespace(NamespaceName namespaceName) {
- this.namespaceName = Optional.of(namespaceName);
- }
}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java
index b917ba4..4959aa0 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java
@@ -1,10 +1,21 @@
package org.zwobble.hobgoblin.compiler.typechecker;
+import org.zwobble.hobgoblin.compiler.types.NamespaceName;
+
public class TypeCheckerContextArb {
private TypeCheckerContextArb() {
}
- public static TypeCheckerContext context() {
- return TypeCheckerContext.initial();
+ public static TypeCheckerGlobalContext globalContext() {
+ return TypeCheckerGlobalContext.initial();
+ }
+
+ public static TypeCheckerNamespaceContext namespaceContext() {
+ return namespaceContext(NamespaceName.of("example"));
+ }
+
+ public static TypeCheckerNamespaceContext namespaceContext(NamespaceName namespaceName) {
+ return TypeCheckerGlobalContext.initial()
+ .enterNamespace(namespaceName);
}
}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceTests.java
new file mode 100644
index 0000000..737c7db
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceTests.java
@@ -0,0 +1,49 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.junit.jupiter.api.Test;
+import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode;
+import org.zwobble.hobgoblin.compiler.ast.typed.TypedStructDefinitionNode;
+import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedNamespaceNode;
+import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedStructDefinitionNode;
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
+import org.zwobble.hobgoblin.compiler.types.NamespaceName;
+import org.zwobble.hobgoblin.compiler.types.StructType;
+
+import java.util.List;
+
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.*;
+
+public class TypeCheckerNamespaceTests {
+ @Test
+ public void namespaceStatementsAreTypeChecked() {
+ var untyped = new UntypedNamespaceNode(
+ NamespaceName.of("a", "b"),
+ List.of(
+ new UntypedStructDefinitionNode(
+ "X",
+ List.of(),
+ NullSource.INSTANCE
+ )
+ ),
+ NullSource.INSTANCE
+ );
+ var context = TypeCheckerContextArb.globalContext();
+
+ var typed = TypeChecker.typeCheckNamespace(untyped, context);
+
+ assertThat(typed, instanceOf(
+ TypedNamespaceNode.class,
+ has("body", TypedNamespaceNode::body, isSequence(
+ instanceOf(
+ TypedStructDefinitionNode.class,
+ has(
+ "type",
+ TypedStructDefinitionNode::type,
+ equalTo(new StructType(NamespaceName.of("a", "b"), "X"))
+ )
+ )
+ ))
+ ));
+ }
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java
index 86f78ae..b6fddba 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerStructDefinitionTests.java
@@ -20,8 +20,7 @@ public class TypeCheckerStructDefinitionTests {
List.of(),
NullSource.INSTANCE
);
- var context = TypeCheckerContextArb.context();
- context.enterNamespace(NamespaceName.of("a", "b"));
+ var context = TypeCheckerContextArb.namespaceContext(NamespaceName.of("a", "b"));
var typed = TypeChecker.typeCheckNamespaceStatement(untyped, context);
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
index 82704fc..76572a4 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
@@ -15,7 +15,7 @@ public class TypeCheckerTypeLevelReferenceTests {
@Test
public void whenNameIsUnrecognisedThenErrorIsThrown() {
var untyped = UntypedArb.typeLevelReference("X");
- var context = TypeCheckerContextArb.context();
+ var context = TypeCheckerContextArb.namespaceContext();
var error = assertThrows(
UndeclaredVariableError.class,
@@ -28,7 +28,7 @@ public class TypeCheckerTypeLevelReferenceTests {
@Test
public void whenNameIsUndefinedThenErrorIsThrown() {
var untyped = UntypedArb.typeLevelReference("X");
- var context = TypeCheckerContextArb.context();
+ var context = TypeCheckerContextArb.namespaceContext();
context.declare("X");
var error = assertThrows(
@@ -42,7 +42,7 @@ public class TypeCheckerTypeLevelReferenceTests {
@Test
public void whenVariableIsNotTypeLevelValueThenErrorIsThrown() {
var untyped = UntypedArb.typeLevelReference("X");
- var context = TypeCheckerContextArb.context();
+ var context = TypeCheckerContextArb.namespaceContext();
context.declare("X");
var scalarType = new ScalarType("Int");
context.define("X", scalarType);
@@ -59,7 +59,7 @@ public class TypeCheckerTypeLevelReferenceTests {
@Test
public void whenNameIsMetaTypeThenValueIsType() {
var untyped = UntypedArb.typeLevelReference("X");
- var context = TypeCheckerContextArb.context();
+ var context = TypeCheckerContextArb.namespaceContext();
context.declare("X");
var scalarType = new ScalarType("Int");
context.define("X", new TypeLevelValueType(scalarType));