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 {
E_LINK_MISSING_BARREL,
E_LINK_INVALID_BARREL_FILENAME,
E_LINK_DUPLICATE_BARREL_FILE,
E_LINK_DUPLICATE_BARREL_ENTRY,
E_LINK_UNRESOLVED_BARREL_ENTRY,

View File

@ -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) {

View File

@ -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 -> {

View File

@ -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<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(
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<String> sourceContents,
final List<String> barrelContents,
final AtomicInteger nextFileId,
final DiagnosticSink diagnostics) {
final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>();
for (final var sourceContent : sourceContents) {
final var fileId = new FileId(nextFileId.getAndIncrement());
@ -126,7 +179,7 @@ class PbsModuleVisibilityTest {
}
final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>();
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));

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