From 0cf2e3e09925a50e9564e26e59e916e3fbc69df5 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 5 Mar 2026 11:42:39 +0000 Subject: [PATCH] implements PR006.1 --- .../compiler/pbs/linking/PbsLinkErrors.java | 1 + .../linking/PbsModuleVisibilityValidator.java | 1 + .../services/PBSFrontendPhaseService.java | 8 ++ .../pbs/linking/PbsModuleVisibilityTest.java | 57 +++++++++++- .../services/PBSFrontendPhaseServiceTest.java | 87 +++++++++++++++++++ 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsLinkErrors.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsLinkErrors.java index 6a84aea4..7d62d501 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsLinkErrors.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsLinkErrors.java @@ -2,6 +2,7 @@ package p.studio.compiler.pbs.linking; public enum PbsLinkErrors { E_LINK_MISSING_BARREL, + E_LINK_INVALID_BARREL_FILENAME, E_LINK_DUPLICATE_BARREL_FILE, E_LINK_DUPLICATE_BARREL_ENTRY, E_LINK_UNRESOLVED_BARREL_ENTRY, 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 abdfe707..39eb588a 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 @@ -215,6 +215,7 @@ public final class PbsModuleVisibilityValidator { continue; } + // TODO(pbs-interface-modules): include top-level host declarations when interface-module mode is added. if (topDecl instanceof PbsAst.StructDecl structDecl) { registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable); } else if (topDecl instanceof PbsAst.ContractDecl contractDecl) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java index 865ffea8..861f7268 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java @@ -9,14 +9,17 @@ import p.studio.compiler.models.SourceHandle; import p.studio.compiler.pbs.PbsFrontendCompiler; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.lexer.PbsLexer; +import p.studio.compiler.pbs.linking.PbsLinkErrors; import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator; import p.studio.compiler.pbs.parser.PbsBarrelParser; import p.studio.compiler.pbs.parser.PbsParser; +import p.studio.compiler.source.Span; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.FileId; import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.logs.LogAggregator; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -59,6 +62,11 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { if ("mod.barrel".equals(sourceHandle.getFilename())) { final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics); moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst)); + } else { + diagnostics.error( + PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name(), + "Only 'mod.barrel' is allowed as barrel filename", + new Span(fId, 0, utf8Content.getBytes(StandardCharsets.UTF_8).length)); } } default -> { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java index 6a00f16b..11c99151 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java @@ -26,7 +26,7 @@ class PbsModuleVisibilityTest { """ fn sum(a: int, b: int) -> int { return a + b; } """ - ), null, nextFileId, diagnostics); + ), (String) null, nextFileId, diagnostics); new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics); @@ -57,6 +57,31 @@ class PbsModuleVisibilityTest { assertEquals(2, duplicateCount); } + @Test + void shouldReportDuplicateBarrelFiles() { + final var diagnostics = DiagnosticSink.empty(); + final var nextFileId = new AtomicInteger(0); + final var module = module("core", "math", List.of( + """ + fn run() -> int { return 1; } + """ + ), List.of( + """ + pub fn run() -> int; + """, + """ + pub fn run() -> int; + """ + ), nextFileId, diagnostics); + + new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics); + + final long duplicateBarrelCount = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsLinkErrors.E_LINK_DUPLICATE_BARREL_FILE.name())) + .count(); + assertEquals(1, duplicateBarrelCount); + } + @Test void shouldReportUnresolvedBarrelEntries() { final var diagnostics = DiagnosticSink.empty(); @@ -111,6 +136,24 @@ class PbsModuleVisibilityTest { assertTrue(importVisibilityDiagnostics.getFirst().getMessage().contains("secret")); } + @Test + void shouldResolveBarrelFunctionWithResultReturnSurface() { + final var diagnostics = DiagnosticSink.empty(); + final var nextFileId = new AtomicInteger(0); + final var module = module("core", "math", List.of( + """ + fn run(input: int) -> result (left: int, right: int) { return input; } + """ + ), """ + pub fn run(v: int) -> result (a: int, b: int); + """, nextFileId, diagnostics); + + new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics); + + assertTrue(diagnostics.stream().noneMatch(d -> + d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name()))); + } + private PbsModuleVisibilityValidator.ModuleUnit module( final String project, final String modulePath, @@ -118,6 +161,16 @@ class PbsModuleVisibilityTest { final String barrelContent, final AtomicInteger nextFileId, final DiagnosticSink diagnostics) { + return module(project, modulePath, sourceContents, barrelContent == null ? List.of() : List.of(barrelContent), nextFileId, diagnostics); + } + + private PbsModuleVisibilityValidator.ModuleUnit module( + final String project, + final String modulePath, + final List sourceContents, + final List barrelContents, + final AtomicInteger nextFileId, + final DiagnosticSink diagnostics) { final var sources = new ArrayList(); for (final var sourceContent : sourceContents) { final var fileId = new FileId(nextFileId.getAndIncrement()); @@ -126,7 +179,7 @@ class PbsModuleVisibilityTest { } final var barrels = new ArrayList(); - if (barrelContent != null) { + for (final var barrelContent : barrelContents) { final var fileId = new FileId(nextFileId.getAndIncrement()); final var barrelAst = parseBarrel(barrelContent, fileId, diagnostics); barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fileId, barrelAst)); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java new file mode 100644 index 00000000..98618659 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -0,0 +1,87 @@ +package p.studio.compiler.services; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.messages.FrontendPhaseContext; +import p.studio.compiler.models.BuildStack; +import p.studio.compiler.models.ProjectDescriptor; +import p.studio.compiler.models.SourceHandle; +import p.studio.compiler.pbs.linking.PbsLinkErrors; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.tables.FileTable; +import p.studio.compiler.source.tables.ProjectTable; +import p.studio.compiler.utilities.SourceProviderFactory; +import p.studio.utilities.logs.LogAggregator; +import p.studio.utilities.structures.ReadOnlyList; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PBSFrontendPhaseServiceTest { + + @TempDir + Path tempDir; + + @Test + void shouldReportInvalidBarrelFilename() throws IOException { + final var projectRoot = tempDir.resolve("project"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("math"); + Files.createDirectories(modulePath); + + final var sourceFile = modulePath.resolve("source.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + final var invalidBarrel = modulePath.resolve("extra.barrel"); + Files.writeString(sourceFile, "fn run() -> int { return 1; }"); + Files.writeString(modBarrel, "pub fn run() -> int;"); + Files.writeString(invalidBarrel, "pub fn run() -> int;"); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("core") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, sourceFile, fileTable); + registerFile(projectId, projectRoot, modBarrel, fileTable); + registerFile(projectId, projectRoot, invalidBarrel, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId)))); + final var diagnostics = DiagnosticSink.empty(); + + new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name()))); + } + + private void registerFile( + final p.studio.compiler.source.identifiers.ProjectId projectId, + final Path projectRoot, + final Path file, + final FileTable fileTable) throws IOException { + final BasicFileAttributes attributes = Files.readAttributes(file, BasicFileAttributes.class); + fileTable.register(new SourceHandle( + projectId, + projectRoot.relativize(file), + file, + attributes.size(), + attributes.lastModifiedTime().toMillis(), + SourceProviderFactory.filesystem())); + } +}