implements PR006.1

This commit is contained in:
bQUARKz 2026-03-05 11:42:39 +00:00
parent f60346e174
commit 0cf2e3e099
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 152 additions and 2 deletions

View File

@ -2,6 +2,7 @@ package p.studio.compiler.pbs.linking;
public enum PbsLinkErrors { public enum PbsLinkErrors {
E_LINK_MISSING_BARREL, E_LINK_MISSING_BARREL,
E_LINK_INVALID_BARREL_FILENAME,
E_LINK_DUPLICATE_BARREL_FILE, E_LINK_DUPLICATE_BARREL_FILE,
E_LINK_DUPLICATE_BARREL_ENTRY, E_LINK_DUPLICATE_BARREL_ENTRY,
E_LINK_UNRESOLVED_BARREL_ENTRY, E_LINK_UNRESOLVED_BARREL_ENTRY,

View File

@ -215,6 +215,7 @@ public final class PbsModuleVisibilityValidator {
continue; continue;
} }
// TODO(pbs-interface-modules): include top-level host declarations when interface-module mode is added.
if (topDecl instanceof PbsAst.StructDecl structDecl) { if (topDecl instanceof PbsAst.StructDecl structDecl) {
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable); registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) { } else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {

View File

@ -9,14 +9,17 @@ import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.pbs.PbsFrontendCompiler; import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer; 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.linking.PbsModuleVisibilityValidator;
import p.studio.compiler.pbs.parser.PbsBarrelParser; import p.studio.compiler.pbs.parser.PbsBarrelParser;
import p.studio.compiler.pbs.parser.PbsParser; 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.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import p.studio.utilities.logs.LogAggregator; import p.studio.utilities.logs.LogAggregator;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -59,6 +62,11 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
if ("mod.barrel".equals(sourceHandle.getFilename())) { if ("mod.barrel".equals(sourceHandle.getFilename())) {
final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics); final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics);
moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst)); 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 -> { default -> {

View File

@ -26,7 +26,7 @@ class PbsModuleVisibilityTest {
""" """
fn sum(a: int, b: int) -> int { return a + b; } 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); new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
@ -57,6 +57,31 @@ class PbsModuleVisibilityTest {
assertEquals(2, duplicateCount); 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 @Test
void shouldReportUnresolvedBarrelEntries() { void shouldReportUnresolvedBarrelEntries() {
final var diagnostics = DiagnosticSink.empty(); final var diagnostics = DiagnosticSink.empty();
@ -111,6 +136,24 @@ class PbsModuleVisibilityTest {
assertTrue(importVisibilityDiagnostics.getFirst().getMessage().contains("secret")); 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<Err> (left: int, right: int) { return input; }
"""
), """
pub fn run(v: int) -> result<Err> (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( private PbsModuleVisibilityValidator.ModuleUnit module(
final String project, final String project,
final String modulePath, final String modulePath,
@ -118,6 +161,16 @@ class PbsModuleVisibilityTest {
final String barrelContent, final String barrelContent,
final AtomicInteger nextFileId, final AtomicInteger nextFileId,
final DiagnosticSink diagnostics) { 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<String> sourceContents,
final List<String> barrelContents,
final AtomicInteger nextFileId,
final DiagnosticSink diagnostics) {
final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>(); final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>();
for (final var sourceContent : sourceContents) { for (final var sourceContent : sourceContents) {
final var fileId = new FileId(nextFileId.getAndIncrement()); final var fileId = new FileId(nextFileId.getAndIncrement());
@ -126,7 +179,7 @@ class PbsModuleVisibilityTest {
} }
final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>(); final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>();
if (barrelContent != null) { for (final var barrelContent : barrelContents) {
final var fileId = new FileId(nextFileId.getAndIncrement()); final var fileId = new FileId(nextFileId.getAndIncrement());
final var barrelAst = parseBarrel(barrelContent, fileId, diagnostics); final var barrelAst = parseBarrel(barrelContent, fileId, diagnostics);
barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fileId, barrelAst)); barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fileId, barrelAst));

View File

@ -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()));
}
}