From 19f293d8ea6c5401efc1b57cf45dfce12c14d8a2 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 06:36:21 +0000 Subject: [PATCH] implements PR-05.0.4 --- .../compiler/pbs/PbsFrontendCompiler.java | 70 ++++++++++++----- .../linking/PbsModuleVisibilityValidator.java | 76 +++++++++++-------- .../source/identifiers/CallableShapeId.java | 7 ++ .../source/identifiers/TypeSurfaceId.java | 7 ++ .../source/tables/CallableShapeRef.java | 15 ++++ .../source/tables/CallableShapeTable.java | 18 +++++ .../source/tables/TypeSurfaceRef.java | 13 ++++ .../source/tables/TypeSurfaceTable.java | 14 ++++ .../source/tables/CallableShapeTableTest.java | 25 ++++++ .../source/tables/TypeSurfaceTableTest.java | 27 +++++++ 10 files changed, 221 insertions(+), 51 deletions(-) create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableShapeId.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeSurfaceId.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeRef.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeTable.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceRef.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceTable.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableShapeTableTest.java create mode 100644 prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/TypeSurfaceTableTest.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index 2b04c822..64efd468 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -15,14 +15,19 @@ import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator; import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticPhase; +import p.studio.compiler.source.identifiers.CallableShapeId; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.NameId; +import p.studio.compiler.source.identifiers.TypeSurfaceId; +import p.studio.compiler.source.tables.CallableShapeTable; import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.CallableTable; import p.studio.compiler.source.tables.IntrinsicReference; import p.studio.compiler.source.tables.IntrinsicTable; import p.studio.compiler.source.tables.NameTable; +import p.studio.compiler.source.tables.TypeSurfaceTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; @@ -144,7 +149,7 @@ public final class PbsFrontendCompiler { final var loweringErrorBaseline = diagnostics.errorCount(); final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE ? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty()) - : lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics); + : lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, effectiveNameTable, diagnostics); if (diagnostics.errorCount() > loweringErrorBaseline) { return IRBackendFile.empty(fileId); } @@ -175,6 +180,7 @@ public final class PbsFrontendCompiler { final PbsAst.File ast, final String moduleKey, final IRReservedMetadata reservedMetadata, + final NameTable nameTable, final DiagnosticSink diagnostics) { final var normalizedModuleKey = moduleKey == null ? "" : moduleKey; final var hostByMethodName = new HashMap(); @@ -188,19 +194,24 @@ public final class PbsFrontendCompiler { } } final var callableIdTable = new CallableTable(); - final var callableIdsByNameAndArity = new HashMap>(); + final var callableIdsByNameAndArity = new HashMap>(); final var callableIdByDeclaration = new HashMap(); final var returnSlotsByCallableId = new HashMap(); final var intrinsicIdTable = new IntrinsicTable(); + final var typeSurfaceTable = new TypeSurfaceTable(); + final var callableShapeTable = new CallableShapeTable(); for (final var declaredFn : ast.functions()) { + final var callableShapeId = callableShapeId(declaredFn, typeSurfaceTable, callableShapeTable); final var callableId = callableIdTable.register( normalizedModuleKey, declaredFn.name(), declaredFn.parameters().size(), - callableShapeKey(declaredFn)); + callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable)); callableIdByDeclaration.put(declaredFn, callableId); callableIdsByNameAndArity - .computeIfAbsent(callableArityKey(declaredFn.name(), declaredFn.parameters().size()), ignored -> new ArrayList<>()) + .computeIfAbsent( + new CallableResolutionKey(nameTable.register(declaredFn.name()), declaredFn.parameters().size()), + ignored -> new ArrayList<>()) .add(callableId); final var retSlots = returnSlotsFor(declaredFn); returnSlotsByCallableId.put(callableId, retSlots); @@ -261,7 +272,8 @@ public final class PbsFrontendCompiler { continue; } - final var candidateCallableIds = callableIdsByNameAndArity.get(callableArityKey(calleeName, callExpr.arguments().size())); + final var candidateCallableIds = callableIdsByNameAndArity.get( + new CallableResolutionKey(nameTable.register(calleeName), callExpr.arguments().size())); if (candidateCallableIds == null || candidateCallableIds.isEmpty()) { diagnostics.error( DiagnosticPhase.STATIC_SEMANTICS, @@ -354,38 +366,51 @@ public final class PbsFrontendCompiler { }; } - private String callableArityKey( - final String callableName, - final int arity) { - return callableName + "#" + arity; + private CallableShapeId callableShapeId( + final PbsAst.FunctionDecl functionDecl, + final TypeSurfaceTable typeSurfaceTable, + final CallableShapeTable callableShapeTable) { + final var parameterSurfaceIds = new ArrayList(functionDecl.parameters().size()); + for (final var parameter : functionDecl.parameters()) { + parameterSurfaceIds.add(typeSurfaceTable.register(typeSurfaceKey(parameter.typeRef()))); + } + final var outputSurfaceId = typeSurfaceTable.register(outputShapeSurface( + functionDecl.returnKind(), + functionDecl.returnType(), + functionDecl.resultErrorType())); + return callableShapeTable.register(ReadOnlyList.wrap(parameterSurfaceIds), outputSurfaceId); } - private String callableShapeKey(final PbsAst.FunctionDecl functionDecl) { + private String callableShapeSurface( + final CallableShapeId callableShapeId, + final TypeSurfaceTable typeSurfaceTable, + final CallableShapeTable callableShapeTable) { + final var shape = callableShapeTable.get(callableShapeId); final var builder = new StringBuilder(); builder.append('('); - for (var i = 0; i < functionDecl.parameters().size(); i++) { + for (var i = 0; i < shape.parameterTypeSurfaces().size(); i++) { if (i > 0) { builder.append(','); } - builder.append(typeKey(functionDecl.parameters().get(i).typeRef())); + builder.append(typeSurfaceTable.get(shape.parameterTypeSurfaces().get(i)).surface()); } builder.append(")->"); - builder.append(outputShapeKey(functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType())); + builder.append(typeSurfaceTable.get(shape.outputTypeSurface()).surface()); return builder.toString(); } - private String outputShapeKey( + private String outputShapeSurface( final PbsAst.ReturnKind returnKind, final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType) { return switch (returnKind) { case INFERRED_UNIT, EXPLICIT_UNIT -> "unit"; - case PLAIN -> typeKey(returnType); - case RESULT -> "result<" + typeKey(resultErrorType) + ">" + typeKey(returnType); + case PLAIN -> typeSurfaceKey(returnType); + case RESULT -> "result<" + typeSurfaceKey(resultErrorType) + ">" + typeSurfaceKey(returnType); }; } - private String typeKey(final PbsAst.TypeRef typeRef) { + private String typeSurfaceKey(final PbsAst.TypeRef typeRef) { final var unwrapped = unwrapGroup(typeRef); if (unwrapped == null) { return "unit"; @@ -393,11 +418,11 @@ public final class PbsFrontendCompiler { return switch (unwrapped.kind()) { case SIMPLE -> "simple:" + unwrapped.name(); case SELF -> "self"; - case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")"; + case OPTIONAL -> "optional(" + typeSurfaceKey(unwrapped.inner()) + ")"; case UNIT -> "unit"; - case GROUP -> typeKey(unwrapped.inner()); + case GROUP -> typeSurfaceKey(unwrapped.inner()); case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream() - .map(field -> typeKey(field.typeRef())) + .map(field -> typeSurfaceKey(field.typeRef())) .reduce((left, right) -> left + "," + right) .orElse("") + ")"; case ERROR -> "error"; @@ -681,6 +706,11 @@ public final class PbsFrontendCompiler { ReadOnlyList intrinsicPool) { } + private record CallableResolutionKey( + NameId callableNameId, + int arity) { + } + private static final class ExecutableLoweringAnalysisException extends RuntimeException { private ExecutableLoweringAnalysisException(final String message) { super(message); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java index 82b0d866..14fe9bd1 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java @@ -6,8 +6,12 @@ import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.RelatedSpan; import p.studio.compiler.source.diagnostics.Severity; import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.identifiers.CallableShapeId; import p.studio.compiler.source.identifiers.NameId; +import p.studio.compiler.source.identifiers.TypeSurfaceId; +import p.studio.compiler.source.tables.CallableShapeTable; import p.studio.compiler.source.tables.NameTable; +import p.studio.compiler.source.tables.TypeSurfaceTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; @@ -29,10 +33,11 @@ public final class PbsModuleVisibilityValidator { final ReadOnlyList modules, final NameTable nameTable, final DiagnosticSink diagnostics) { + final var signatureInterning = new SignatureInterning(new TypeSurfaceTable(), new CallableShapeTable()); final var exportsByModule = new HashMap(); for (final var module : modules) { - final var exports = validateModule(module, nameTable, diagnostics); + final var exports = validateModule(module, nameTable, signatureInterning, diagnostics); exportsByModule.put(toModuleRef(module.coordinates()), exports); } @@ -44,6 +49,7 @@ public final class PbsModuleVisibilityValidator { private ModuleExports validateModule( final ModuleUnit module, final NameTable nameTable, + final SignatureInterning signatureInterning, final DiagnosticSink diagnostics) { final var exports = new ModuleExports(); @@ -79,7 +85,7 @@ public final class PbsModuleVisibilityValidator { } final var barrel = module.barrelFiles().getFirst(); - final var declarations = collectDeclarations(module, nameTable); + final var declarations = collectDeclarations(module, nameTable, signatureInterning); exports.declaredNameIds.addAll(declarations.declaredNameIds); final Map seenNonFunctionEntries = new HashMap<>(); final Map seenFunctionEntries = new HashMap<>(); @@ -92,7 +98,8 @@ public final class PbsModuleVisibilityValidator { functionItem.returnKind(), functionItem.returnType(), functionItem.resultErrorType(), - nameTable); + nameTable, + signatureInterning); final var firstFunctionEntry = seenFunctionEntries.putIfAbsent(functionKey, functionItem.span()); if (firstFunctionEntry != null) { p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, @@ -231,7 +238,8 @@ public final class PbsModuleVisibilityValidator { private ModuleDeclarations collectDeclarations( final ModuleUnit module, - final NameTable nameTable) { + final NameTable nameTable, + final SignatureInterning signatureInterning) { final var declarations = new ModuleDeclarations(); for (final var sourceFile : module.sourceFiles()) { @@ -243,7 +251,8 @@ public final class PbsModuleVisibilityValidator { functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType(), - nameTable); + nameTable, + signatureInterning); declarations.functionsBySignature .computeIfAbsent(key, ignored -> new ArrayList<>()) .add(functionDecl.span()); @@ -295,37 +304,37 @@ public final class PbsModuleVisibilityValidator { final PbsAst.ReturnKind returnKind, final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType, - final NameTable nameTable) { - final var signatureSurface = signatureSurfaceKey(parameterTypes, returnKind, returnType, resultErrorType); - return new FunctionSymbolKey(nameTable.register(name), signatureSurface); + final NameTable nameTable, + final SignatureInterning signatureInterning) { + return new FunctionSymbolKey( + nameTable.register(name), + callableShapeId(parameterTypes, returnKind, returnType, resultErrorType, signatureInterning)); } - private String signatureSurfaceKey( + private CallableShapeId callableShapeId( final List parameterTypes, + final PbsAst.ReturnKind returnKind, + final PbsAst.TypeRef returnType, + final PbsAst.TypeRef resultErrorType, + final SignatureInterning signatureInterning) { + final var parameterSurfaceIds = new ArrayList(parameterTypes.size()); + for (final var parameterType : parameterTypes) { + parameterSurfaceIds.add(signatureInterning.typeSurfaces().register(typeSurfaceKey(parameterType))); + } + final var outputSurfaceId = signatureInterning.typeSurfaces().register(outputSurfaceKey(returnKind, returnType, resultErrorType)); + return signatureInterning.callableShapes().register(ReadOnlyList.wrap(parameterSurfaceIds), outputSurfaceId); + } + + private String outputSurfaceKey( final PbsAst.ReturnKind returnKind, final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType) { - final var builder = new StringBuilder(); - builder.append('('); - for (int i = 0; i < parameterTypes.size(); i++) { - if (i > 0) { - builder.append(','); - } - builder.append(typeSurfaceKey(parameterTypes.get(i))); - } - builder.append(")->"); - - switch (returnKind) { - case INFERRED_UNIT -> builder.append("infer_unit"); - case EXPLICIT_UNIT -> builder.append("unit"); - case PLAIN -> builder.append(typeSurfaceKey(returnType)); - case RESULT -> builder.append("result<") - .append(typeSurfaceKey(resultErrorType)) - .append('>') - .append(typeSurfaceKey(returnType)); - } - - return builder.toString(); + return switch (returnKind) { + case INFERRED_UNIT -> "infer_unit"; + case EXPLICIT_UNIT -> "unit"; + case PLAIN -> typeSurfaceKey(returnType); + case RESULT -> "result<" + typeSurfaceKey(resultErrorType) + ">" + typeSurfaceKey(returnType); + }; } private String typeSurfaceKey(final PbsAst.TypeRef typeRef) { @@ -489,7 +498,12 @@ public final class PbsModuleVisibilityValidator { private record FunctionSymbolKey( NameId nameId, - String signatureSurface) { + CallableShapeId callableShapeId) { + } + + private record SignatureInterning( + TypeSurfaceTable typeSurfaces, + CallableShapeTable callableShapes) { } private static final class ModuleDeclarations { diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableShapeId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableShapeId.java new file mode 100644 index 00000000..d4e451bc --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableShapeId.java @@ -0,0 +1,7 @@ +package p.studio.compiler.source.identifiers; + +public class CallableShapeId extends SourceIdentifier { + public CallableShapeId(final int id) { + super(id); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeSurfaceId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeSurfaceId.java new file mode 100644 index 00000000..eb6a21bd --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeSurfaceId.java @@ -0,0 +1,7 @@ +package p.studio.compiler.source.identifiers; + +public class TypeSurfaceId extends SourceIdentifier { + public TypeSurfaceId(final int id) { + super(id); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeRef.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeRef.java new file mode 100644 index 00000000..948903d3 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeRef.java @@ -0,0 +1,15 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.TypeSurfaceId; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.Objects; + +public record CallableShapeRef( + ReadOnlyList parameterTypeSurfaces, + TypeSurfaceId outputTypeSurface) { + public CallableShapeRef { + parameterTypeSurfaces = parameterTypeSurfaces == null ? ReadOnlyList.empty() : parameterTypeSurfaces; + outputTypeSurface = Objects.requireNonNull(outputTypeSurface, "outputTypeSurface"); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeTable.java new file mode 100644 index 00000000..f33634b4 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableShapeTable.java @@ -0,0 +1,18 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.CallableShapeId; +import p.studio.compiler.source.identifiers.TypeSurfaceId; +import p.studio.utilities.structures.ReadOnlyList; + +public class CallableShapeTable extends InternTable { + + public CallableShapeTable() { + super(CallableShapeId::new); + } + + public CallableShapeId register( + final ReadOnlyList parameterTypeSurfaces, + final TypeSurfaceId outputTypeSurface) { + return register(new CallableShapeRef(parameterTypeSurfaces, outputTypeSurface)); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceRef.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceRef.java new file mode 100644 index 00000000..fae14a31 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceRef.java @@ -0,0 +1,13 @@ +package p.studio.compiler.source.tables; + +import java.util.Objects; + +public record TypeSurfaceRef( + String surface) { + public TypeSurfaceRef { + surface = Objects.requireNonNull(surface, "surface"); + if (surface.isBlank()) { + throw new IllegalArgumentException("surface must not be blank"); + } + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceTable.java new file mode 100644 index 00000000..123dd980 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/TypeSurfaceTable.java @@ -0,0 +1,14 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.TypeSurfaceId; + +public class TypeSurfaceTable extends InternTable { + + public TypeSurfaceTable() { + super(TypeSurfaceId::new); + } + + public TypeSurfaceId register(final String surface) { + return register(new TypeSurfaceRef(surface)); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableShapeTableTest.java b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableShapeTableTest.java new file mode 100644 index 00000000..98cf2fbb --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableShapeTableTest.java @@ -0,0 +1,25 @@ +package p.studio.compiler.source.tables; + +import org.junit.jupiter.api.Test; +import p.studio.utilities.structures.ReadOnlyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CallableShapeTableTest { + + @Test + void shouldInternEqualShapesToSameIdentifier() { + final var surfaces = new TypeSurfaceTable(); + final var shapes = new CallableShapeTable(); + + final var intType = surfaces.register("simple:int"); + final var unitType = surfaces.register("unit"); + final var first = shapes.register(ReadOnlyList.from(intType, intType), unitType); + final var second = shapes.register(ReadOnlyList.from(intType, intType), unitType); + final var third = shapes.register(ReadOnlyList.from(intType), unitType); + + assertEquals(first, second); + assertEquals(2, shapes.size()); + assertEquals(1, shapes.get(third).parameterTypeSurfaces().size()); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/TypeSurfaceTableTest.java b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/TypeSurfaceTableTest.java new file mode 100644 index 00000000..25e160e9 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/TypeSurfaceTableTest.java @@ -0,0 +1,27 @@ +package p.studio.compiler.source.tables; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class TypeSurfaceTableTest { + + @Test + void shouldInternEqualSurfacesToSameIdentifier() { + final var table = new TypeSurfaceTable(); + + final var first = table.register("simple:int"); + final var second = table.register("simple:int"); + final var third = table.register("resultsimple:int"); + + assertEquals(first, second); + assertEquals(2, table.size()); + assertEquals("resultsimple:int", table.get(third).surface()); + } + + @Test + void shouldRejectInvalidSurfaceContract() { + assertThrows(IllegalArgumentException.class, () -> new TypeSurfaceRef("")); + } +}