summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-07-31 12:34:40 +0100
committerMichael Williamson <mike@zwobble.org>2026-07-31 12:41:53 +0100
commit5148cdd9343e28e945c3e4c206d5d29c5790c001 (patch)
tree191b5f9b494dfa046f61286185c09d6d4ef5ed81 /src/main
parent4bf0bacb0f369962144f51ad4491ac58875532a0 (diff)
Support circular imports
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java56
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java97
2 files changed, 61 insertions, 92 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
index e4622a0..6f4c65d 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java
@@ -1,13 +1,11 @@
package org.zwobble.hobgoblin.compiler;
-import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode;
import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedNamespaceNode;
import org.zwobble.hobgoblin.compiler.builtins.NativeTypes;
import org.zwobble.hobgoblin.compiler.config.ProjectConfig;
import org.zwobble.hobgoblin.compiler.parser.Parser;
import org.zwobble.hobgoblin.compiler.typechecker.TypeChecker;
import org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerGlobalContext;
-import org.zwobble.hobgoblin.compiler.typechecker.errors.UnknownNamespaceError;
import org.zwobble.hobgoblin.compiler.types.NamespaceName;
import org.zwobble.sourcetext.SourceText;
@@ -18,10 +16,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
-import java.util.*;
-import java.util.stream.Collectors;
-
-import static org.zwobble.hobgoblin.compiler.typechecker.NamespaceNameResolution.resolveNamespaceName;
+import java.util.ArrayList;
+import java.util.List;
public class HobgoblinCompiler {
public static final String FILE_NAME_EXTENSION = ".hob";
@@ -46,8 +42,6 @@ public class HobgoblinCompiler {
untypedNamespaceNodes.add(untypedNamespaceNode);
}
- var typedNamespaceNodes = new ArrayList<TypedNamespaceNode>();
-
var typeCheckerContext = TypeCheckerGlobalContext.initial();
typeCheckerContext.addNativeType(NativeTypes.BOOL);
typeCheckerContext.addNativeType(NativeTypes.INT_8);
@@ -58,10 +52,7 @@ public class HobgoblinCompiler {
typeCheckerContext.addNativeTypeConstructor(NativeTypes.OPTION);
typeCheckerContext.addNativeTypeConstructor(NativeTypes.SHARED);
- for (var untypedNamespaceNode : sortUntypedNamespaceNodes(untypedNamespaceNodes)) {
- var typedNamespaceNode = TypeChecker.typeCheckNamespace(untypedNamespaceNode, typeCheckerContext);
- typedNamespaceNodes.add(typedNamespaceNode);
- }
+ var typedNamespaceNodes = TypeChecker.typeCheckNamespaces(untypedNamespaceNodes, typeCheckerContext);
var typesInfo = typeCheckerContext.toTypesInfo();
@@ -70,47 +61,6 @@ public class HobgoblinCompiler {
}
}
- private static List<UntypedNamespaceNode> sortUntypedNamespaceNodes(List<UntypedNamespaceNode> namespaceNodes) {
- var namespaceNodesByName = namespaceNodes.stream()
- .collect(Collectors.toMap(node -> node.namespaceName(), node -> node));
-
- var sortedNamespaceNodes = new ArrayList<UntypedNamespaceNode>();
- var handledNamespaceNames = new HashSet<NamespaceName>();
- var pendingNamespaceNodes = new ArrayDeque<>(namespaceNodes);
-
- while (!pendingNamespaceNodes.isEmpty()) {
- var namespaceNode = pendingNamespaceNodes.removeLast();
- // TODO: handle cycles
- if (!handledNamespaceNames.contains(namespaceNode.namespaceName())) {
- var unhandledDependencies = new ArrayList<UntypedNamespaceNode>();
- for (var importNode : namespaceNode.imports()) {
- var importedNamespaceName = resolveNamespaceName(
- namespaceNode.namespaceName(),
- importNode.ancestorDepth(),
- importNode.namespaceName(),
- importNode.source()
- );
- if (!handledNamespaceNames.contains(importedNamespaceName)) {
- unhandledDependencies.add(
- Optional.ofNullable(namespaceNodesByName.get(importedNamespaceName))
- .orElseThrow(() -> new UnknownNamespaceError(importedNamespaceName, importNode.source()))
- );
- }
- }
-
- if (unhandledDependencies.isEmpty()) {
- handledNamespaceNames.add(namespaceNode.namespaceName());
- sortedNamespaceNodes.add(namespaceNode);
- } else {
- pendingNamespaceNodes.add(namespaceNode);
- pendingNamespaceNodes.addAll(unhandledDependencies);
- }
- }
- }
-
- return sortedNamespaceNodes;
- }
-
static NamespaceName sourceFileNamespaceName(Path sourceDirectory, Path sourceFilePath) {
var relativePath = sourceDirectory.relativize(sourceFilePath);
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 e8fc633..f448a56 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
@@ -9,6 +9,7 @@ import org.zwobble.hobgoblin.compiler.types.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
import static org.zwobble.hobgoblin.compiler.typechecker.NamespaceNameResolution.resolveNamespaceName;
import static org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerSubtyping.isSubtype;
@@ -18,53 +19,69 @@ public class TypeChecker {
private TypeChecker() {
}
- public static TypedNamespaceNode typeCheckNamespace(
- UntypedNamespaceNode untyped,
+ public static List<TypedNamespaceNode> typeCheckNamespaces(
+ List<UntypedNamespaceNode> untypedNamespaces,
TypeCheckerGlobalContext context
) {
- var namespaceContext = context.enterNamespace(untyped.namespaceName());
-
- for (var untypedImport : untyped.imports()) {
- var importedNamespace = resolveNamespaceName(
- untyped.namespaceName(),
- untypedImport.ancestorDepth(),
- untypedImport.namespaceName(),
- untypedImport.source()
- );
-
- var importedNamespaceFields = context.namespaceFields(importedNamespace)
- .orElseThrow(() -> new UnknownNamespaceError(importedNamespace, untypedImport.source()));
-
- for (var importedName : untypedImport.importedNames()) {
- var fieldType = importedNamespaceFields.fieldType(importedName)
- .orElseThrow(() -> new UnknownNamespaceFieldError(
- importedName,
- importedNamespace,
- untypedImport.source()
- ));
- namespaceContext.declare(importedName, fieldType, untypedImport.source());
+ var namespaceContexts = untypedNamespaces.stream()
+ .collect(Collectors.toMap(
+ untypedNamespace -> untypedNamespace.namespaceName(),
+ untypedNamespace -> context.enterNamespace(untypedNamespace.namespaceName())
+ ));
+
+ for (var untypedNamespace : untypedNamespaces) {
+ var namespaceContext = namespaceContexts.get(untypedNamespace.namespaceName());
+
+ var fields = new ArrayList<Field>();
+ for (var untypedStatement : untypedNamespace.body()) {
+ var type = declareNamespaceStatement(untypedStatement, namespaceContext);
+ var field = new Field(untypedStatement.name(), type, untypedStatement.source());
+ fields.add(field);
}
+ context.defineNamespace(untypedNamespace.namespaceName(), fields);
}
- var fields = new ArrayList<Field>();
- for (var untypedStatement : untyped.body()) {
- var type = declareNamespaceStatement(untypedStatement, namespaceContext);
- var field = new Field(untypedStatement.name(), type, untypedStatement.source());
- fields.add(field);
- }
- context.defineNamespace(untyped.namespaceName(), fields);
+ var typedNamespaceNodes = new ArrayList<TypedNamespaceNode>();
+
+ for (var untypedNamespace : untypedNamespaces) {
+ var namespaceContext = namespaceContexts.get(untypedNamespace.namespaceName());
+
+ for (var untypedImport : untypedNamespace.imports()) {
+ var importedNamespace = resolveNamespaceName(
+ untypedNamespace.namespaceName(),
+ untypedImport.ancestorDepth(),
+ untypedImport.namespaceName(),
+ untypedImport.source()
+ );
- var typedBody = new ArrayList<TypedNamespaceStatementNode>();
- for (var untypedStatement : untyped.body()) {
- var typedStatement = defineNamespaceStatement(untypedStatement, namespaceContext);
- typedBody.add(typedStatement);
+ var importedNamespaceFields = context.namespaceFields(importedNamespace)
+ .orElseThrow(() -> new UnknownNamespaceError(importedNamespace, untypedImport.source()));
+
+ for (var importedName : untypedImport.importedNames()) {
+ var fieldType = importedNamespaceFields.fieldType(importedName)
+ .orElseThrow(() -> new UnknownNamespaceFieldError(
+ importedName,
+ importedNamespace,
+ untypedImport.source()
+ ));
+ namespaceContext.declare(importedName, fieldType, untypedImport.source());
+ }
+ }
+
+ var typedBody = new ArrayList<TypedNamespaceStatementNode>();
+ for (var untypedStatement : untypedNamespace.body()) {
+ var typedStatement = defineNamespaceStatement(untypedStatement, namespaceContext);
+ typedBody.add(typedStatement);
+ }
+
+ typedNamespaceNodes.add(new TypedNamespaceNode(
+ untypedNamespace.namespaceName(),
+ typedBody,
+ untypedNamespace.source()
+ ));
}
- return new TypedNamespaceNode(
- untyped.namespaceName(),
- typedBody,
- untyped.source()
- );
+ return typedNamespaceNodes;
}
static Type declareNamespaceStatement(
@@ -311,6 +328,8 @@ public class TypeChecker {
Optional<List<Field>> variantTypeFields,
TypesInfo typesInfo
) {
+ // TODO: check that variant and sum are in the same namespace.
+
for (var sumTypeField : sumTypeFields) {
var variantTypeField = variantTypeFields.flatMap(
fields -> fields.stream()