From 39ef07c887e33aff56c087388980a92e750b547c Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 25 Apr 2026 10:44:02 +0100 Subject: Type check namespace bodies --- .../hobgoblin/compiler/HobgoblinCompiler.java | 4 +- .../compiler/typechecker/TypeChecker.java | 25 +++++++---- .../compiler/typechecker/TypeCheckerContext.java | 44 ------------------- .../typechecker/TypeCheckerGlobalContext.java | 16 +++++++ .../typechecker/TypeCheckerNamespaceContext.java | 43 +++++++++++++++++++ .../typechecker/TypeCheckerContextArb.java | 15 ++++++- .../typechecker/TypeCheckerNamespaceTests.java | 49 ++++++++++++++++++++++ .../TypeCheckerStructDefinitionTests.java | 3 +- .../TypeCheckerTypeLevelReferenceTests.java | 8 ++-- 9 files changed, 144 insertions(+), 63 deletions(-) delete mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerGlobalContext.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java create mode 100644 src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceTests.java (limited to 'src') 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(); + 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 typeCheckTypeLevelExpression( UntypedTypeLevelExpressionNode untyped, - TypeCheckerContext context + TypeCheckerNamespaceContext context ) { return switch (untyped) { case UntypedTypeLevelReferenceNode untypedTypeLevelReference -> @@ -61,7 +68,7 @@ public class TypeChecker { private static TypedTypeLevelExpressionNode 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/TypeCheckerContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java deleted file mode 100644 index e1109f9..0000000 --- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.zwobble.hobgoblin.compiler.typechecker; - -import org.zwobble.hobgoblin.compiler.types.NamespaceName; -import org.zwobble.hobgoblin.compiler.types.Type; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -public class TypeCheckerContext { - private Optional namespaceName; - private final Map variables; - - private TypeCheckerContext(Optional namespaceName, Map variables) { - this.namespaceName = namespaceName; - this.variables = variables; - } - - public static TypeCheckerContext initial() { - return new TypeCheckerContext(Optional.empty(), new HashMap<>()); - } - - public Optional namespaceName() { - return this.namespaceName; - } - - public void declare(String name) { - // TODO: Check variable is not already declared. - this.variables.put(name, new Variable.Declared()); - } - - public void define(String name, Type type) { - // TODO: Check variable is not already defined. - this.variables.put(name, new Variable.Defined(type)); - } - - public Optional lookup(String name) { - return Optional.ofNullable(this.variables.get(name)); - } - - public void enterNamespace(NamespaceName namespaceName) { - this.namespaceName = Optional.of(namespaceName); - } -} 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/TypeCheckerNamespaceContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java new file mode 100644 index 0000000..c1a23fd --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java @@ -0,0 +1,43 @@ +package org.zwobble.hobgoblin.compiler.typechecker; + +import org.zwobble.hobgoblin.compiler.types.NamespaceName; +import org.zwobble.hobgoblin.compiler.types.Type; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class TypeCheckerNamespaceContext { + public static TypeCheckerNamespaceContext initial(NamespaceName namespaceName) { + return new TypeCheckerNamespaceContext(namespaceName, new HashMap<>()); + } + + private final NamespaceName namespaceName; + private final Map variables; + + private TypeCheckerNamespaceContext( + NamespaceName namespaceName, + Map variables + ) { + this.namespaceName = namespaceName; + this.variables = variables; + } + + public NamespaceName namespaceName() { + return this.namespaceName; + } + + public void declare(String name) { + // TODO: Check variable is not already declared. + this.variables.put(name, new Variable.Declared()); + } + + public void define(String name, Type type) { + // TODO: Check variable is not already defined. + this.variables.put(name, new Variable.Defined(type)); + } + + public Optional lookup(String name) { + return Optional.ofNullable(this.variables.get(name)); + } +} 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)); -- cgit v1.2.3