summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-07-24 20:14:19 +0100
committerMichael Williamson <mike@zwobble.org>2026-07-24 20:14:19 +0100
commitfeb676e39415b89578a7095775f8770f71972f59 (patch)
tree47b9130d2d55f1d27b1e21b5fe0ee6a3984f270e /src
parent2409a40985dad843fe25611c9cd14afdac6d67bf (diff)
Throw error on duplicate variable name
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/DuplicateVariableNameError.java17
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java10
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java8
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerConstructedTypeTests.java5
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerMetaTypeTests.java7
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContextTests.java26
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSumDefinitionTests.java12
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java5
8 files changed, 70 insertions, 20 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/DuplicateVariableNameError.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/DuplicateVariableNameError.java
new file mode 100644
index 0000000..1fc79bf
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/DuplicateVariableNameError.java
@@ -0,0 +1,17 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.errors.SourceError;
+import org.zwobble.hobgoblin.compiler.sources.Source;
+
+public class DuplicateVariableNameError extends SourceError {
+ private final String name;
+
+ public DuplicateVariableNameError(String name, Source source) {
+ super("A variable with the name " + name + " has already been declared", source);
+ this.name = name;
+ }
+
+ public String name() {
+ return name;
+ }
+}
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 f313333..793e1f6 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
@@ -40,7 +40,7 @@ public class TypeChecker {
importedNamespace,
untypedImport.source()
));
- namespaceContext.declare(importedName, fieldType);
+ namespaceContext.declare(importedName, fieldType, untypedImport.source());
}
}
@@ -109,7 +109,7 @@ public class TypeChecker {
) {
var nativeType = new EnumType(context.namespaceName(), untyped.name());
var metaType = new TypeLevelValueType(nativeType);
- context.declare(untyped.name(), metaType);
+ context.declare(untyped.name(), metaType, untyped.source());
return metaType;
}
@@ -146,7 +146,7 @@ public class TypeChecker {
) {
var nativeType = new SimpleNativeType(context.namespaceName(), untyped.name());
var metaType = new TypeLevelValueType(nativeType);
- context.declare(untyped.name(), metaType);
+ context.declare(untyped.name(), metaType, untyped.source());
return metaType;
}
@@ -169,7 +169,7 @@ public class TypeChecker {
) {
var structType = new StructType(context.namespaceName(), untyped.name());
var metaType = new TypeLevelValueType(structType);
- context.declare(untyped.name(), metaType);
+ context.declare(untyped.name(), metaType, untyped.source());
return metaType;
}
@@ -233,7 +233,7 @@ public class TypeChecker {
) {
var sumType = new SumType(context.namespaceName(), untyped.name());
var metaType = new TypeLevelValueType(sumType);
- context.declare(untyped.name(), metaType);
+ context.declare(untyped.name(), metaType, untyped.source());
return metaType;
}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
index 82c8273..51e11d6 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContext.java
@@ -1,5 +1,6 @@
package org.zwobble.hobgoblin.compiler.typechecker;
+import org.zwobble.hobgoblin.compiler.sources.Source;
import org.zwobble.hobgoblin.compiler.types.*;
import java.util.HashMap;
@@ -43,8 +44,11 @@ public class TypeCheckerNamespaceContext {
return this.namespaceName;
}
- public void declare(String name, Type type) {
- // TODO: Check variable is not already declared.
+ public void declare(String name, Type type, Source source) {
+ if (this.variables.containsKey(name)) {
+ throw new DuplicateVariableNameError(name, source);
+ }
+
this.variables.put(name, type);
}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerConstructedTypeTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerConstructedTypeTests.java
index ef55465..fa511ba 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerConstructedTypeTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerConstructedTypeTests.java
@@ -2,6 +2,7 @@ package org.zwobble.hobgoblin.compiler.typechecker;
import org.junit.jupiter.api.Test;
import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedArb;
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
import org.zwobble.hobgoblin.compiler.types.*;
import java.util.List;
@@ -20,9 +21,9 @@ public class TypeCheckerConstructedTypeTests {
var context = TypeCheckerContextArb.namespaceContext();
var listType = SimpleNativeType.builtin("List");
var listTypeConstructor = new TypeConstructor<>(List.of(new TypeParam("T")), listType);
- context.declare("List", new TypeLevelValueType(listTypeConstructor));
+ context.declare("List", new TypeLevelValueType(listTypeConstructor), NullSource.INSTANCE);
var int32Type = SimpleNativeType.builtin("Int32");
- context.declare("Int32", new TypeLevelValueType(int32Type));
+ context.declare("Int32", new TypeLevelValueType(int32Type), NullSource.INSTANCE);
var typed = TypeChecker.typeCheckTypeLevelExpression(untyped, context);
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerMetaTypeTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerMetaTypeTests.java
index 1f966b1..2c37d22 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerMetaTypeTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerMetaTypeTests.java
@@ -3,6 +3,7 @@ package org.zwobble.hobgoblin.compiler.typechecker;
import org.junit.jupiter.api.Test;
import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelReferenceNode;
import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedArb;
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
import org.zwobble.hobgoblin.compiler.types.*;
import java.util.List;
@@ -18,7 +19,7 @@ public class TypeCheckerMetaTypeTests {
var untyped = UntypedArb.typeLevelReference("X");
var context = TypeCheckerContextArb.namespaceContext();
var nativeType = SimpleNativeType.builtin("Int");
- context.declare("X", nativeType);
+ context.declare("X", nativeType, NullSource.INSTANCE);
var error = assertThrows(
UnexpectedTypeError.class,
@@ -34,7 +35,7 @@ public class TypeCheckerMetaTypeTests {
var untyped = UntypedArb.typeLevelReference("List");
var context = TypeCheckerContextArb.namespaceContext();
var typeConstructor = new TypeConstructor<>(List.of(new TypeParam("T")), SimpleNativeType.builtin("List"));
- context.declare("List", new TypeLevelValueType(typeConstructor));
+ context.declare("List", new TypeLevelValueType(typeConstructor), NullSource.INSTANCE);
var error = assertThrows(
UnexpectedTypeError.class,
@@ -50,7 +51,7 @@ public class TypeCheckerMetaTypeTests {
var untyped = UntypedArb.typeLevelReference("X");
var context = TypeCheckerContextArb.namespaceContext();
var nativeType = SimpleNativeType.builtin("Int");
- context.declare("X", new TypeLevelValueType(nativeType));
+ context.declare("X", new TypeLevelValueType(nativeType), NullSource.INSTANCE);
var typed = TypeChecker.typeCheckMetaType(untyped, context);
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContextTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContextTests.java
new file mode 100644
index 0000000..72c9702
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerNamespaceContextTests.java
@@ -0,0 +1,26 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.junit.jupiter.api.Test;
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
+import org.zwobble.hobgoblin.compiler.types.NamespaceName;
+import org.zwobble.hobgoblin.compiler.types.SumType;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.equalTo;
+
+public class TypeCheckerNamespaceContextTests {
+ @Test
+ public void declaringVariablesWithSameThrowsError() {
+ var context = TypeCheckerGlobalContext.initial()
+ .enterNamespace(NamespaceName.of());
+ context.declare("Shape", new SumType(NamespaceName.of(), "Shape"), NullSource.INSTANCE);
+
+ var error = assertThrows(
+ DuplicateVariableNameError.class,
+ () -> context.declare("Shape", new SumType(NamespaceName.of(), "Shape"), NullSource.INSTANCE)
+ );
+
+ assertThat(error.name(), equalTo("Shape"));
+ }
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSumDefinitionTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSumDefinitionTests.java
index 597fb42..718460f 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSumDefinitionTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerSumDefinitionTests.java
@@ -50,8 +50,8 @@ public class TypeCheckerSumDefinitionTests {
var globalContext = TypeCheckerGlobalContext.initial();
var namespaceContext = globalContext
.enterNamespace(NamespaceName.of("a", "b"));
- namespaceContext.declare(rectangleType.name(), new TypeLevelValueType(rectangleType));
- namespaceContext.declare(triangleType.name(), new TypeLevelValueType(triangleType));
+ namespaceContext.declare(rectangleType.name(), new TypeLevelValueType(rectangleType), NullSource.INSTANCE);
+ namespaceContext.declare(triangleType.name(), new TypeLevelValueType(triangleType), NullSource.INSTANCE);
var typed = typeCheckNamespaceStatement(untyped, namespaceContext);
@@ -156,7 +156,7 @@ public class TypeCheckerSumDefinitionTests {
var namespaceContext = globalContext
.enterNamespace(namespaceName);
var variantType = new StructType(namespaceName, "Square");
- namespaceContext.declare("Square", new TypeLevelValueType(variantType));
+ namespaceContext.declare("Square", new TypeLevelValueType(variantType), NullSource.INSTANCE);
namespaceContext.defineStructType(variantType, List.of());
var error = assertThrows(
@@ -193,7 +193,7 @@ public class TypeCheckerSumDefinitionTests {
var namespaceContext = globalContext
.enterNamespace(namespaceName);
var variantType = new StructType(namespaceName, "Square");
- namespaceContext.declare("Square", new TypeLevelValueType(variantType));
+ namespaceContext.declare("Square", new TypeLevelValueType(variantType), NullSource.INSTANCE);
namespaceContext.defineStructType(variantType, List.of(new Field("area", int64Type, NullSource.INSTANCE)));
// TODO: more specific error
@@ -229,7 +229,7 @@ public class TypeCheckerSumDefinitionTests {
var namespaceContext = globalContext
.enterNamespace(namespaceName);
var variantType = new StructType(namespaceName, "Square");
- namespaceContext.declare("Square", new TypeLevelValueType(variantType));
+ namespaceContext.declare("Square", new TypeLevelValueType(variantType), NullSource.INSTANCE);
namespaceContext.defineStructType(variantType, List.of());
assertThrows(
@@ -251,7 +251,7 @@ public class TypeCheckerSumDefinitionTests {
.enterNamespace(namespaceName);
var sumType = new SumType(namespaceName, "Shape");
var variantType = new StructType(namespaceName, "Square");
- namespaceContext.declare("Sum", new TypeLevelValueType(sumType));
+ namespaceContext.declare("Sum", new TypeLevelValueType(sumType), NullSource.INSTANCE);
namespaceContext.defineSumType(
sumType,
List.of(variantType),
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 0924eee..bb99952 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
@@ -3,6 +3,7 @@ package org.zwobble.hobgoblin.compiler.typechecker;
import org.junit.jupiter.api.Test;
import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelReferenceNode;
import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedArb;
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
import org.zwobble.hobgoblin.compiler.types.SimpleNativeType;
import org.zwobble.hobgoblin.compiler.types.TypeLevelValueType;
import org.zwobble.hobgoblin.compiler.types.TypeSet;
@@ -30,7 +31,7 @@ public class TypeCheckerTypeLevelReferenceTests {
var untyped = UntypedArb.typeLevelReference("X");
var context = TypeCheckerContextArb.namespaceContext();
var nativeType = SimpleNativeType.builtin("Int");
- context.declare("X", nativeType);
+ context.declare("X", nativeType, NullSource.INSTANCE);
var error = assertThrows(
UnexpectedTypeError.class,
@@ -46,7 +47,7 @@ public class TypeCheckerTypeLevelReferenceTests {
var untyped = UntypedArb.typeLevelReference("X");
var context = TypeCheckerContextArb.namespaceContext();
var nativeType = SimpleNativeType.builtin("Int");
- context.declare("X", new TypeLevelValueType(nativeType));
+ context.declare("X", new TypeLevelValueType(nativeType), NullSource.INSTANCE);
var typed = TypeChecker.typeCheckTypeLevelExpression(untyped, context);