implements PR026

This commit is contained in:
bQUARKz 2026-03-06 13:44:24 +00:00
parent 52e8746bcc
commit 9fb8622ddb
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 297 additions and 2 deletions

View File

@ -247,6 +247,8 @@ public final class PbsModuleVisibilityValidator {
if (topDecl instanceof PbsAst.StructDecl structDecl) {
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
} else if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, builtinTypeDecl.name(), builtinTypeDecl.span(), nameTable);
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
} else if (topDecl instanceof PbsAst.HostDecl hostDecl) {

View File

@ -0,0 +1,74 @@
package p.studio.compiler.pbs.stdlib;
import p.studio.utilities.structures.ReadOnlyList;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public final class ResourceStdlibEnvironmentResolver implements StdlibEnvironmentResolver {
private static final String STDLIB_ROOT = "stdlib";
@Override
public StdlibEnvironment resolve(final int stdlibVersion) {
return new ResourceStdlibEnvironment(stdlibVersion);
}
private static final class ResourceStdlibEnvironment implements StdlibEnvironment {
private final int stdlibVersion;
private ResourceStdlibEnvironment(final int stdlibVersion) {
this.stdlibVersion = stdlibVersion;
}
@Override
public Optional<StdlibModuleSource> resolveModule(
final String project,
final ReadOnlyList<String> pathSegments) {
final var moduleBasePath = moduleBasePath(project, pathSegments);
final var mainSource = readResource(moduleBasePath + "/main.pbs");
final var barrelSource = readResource(moduleBasePath + "/mod.barrel");
if (mainSource.isEmpty() || barrelSource.isEmpty()) {
return Optional.empty();
}
return Optional.of(new StdlibModuleSource(
project,
pathSegments,
ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile("main.pbs", mainSource.get()))),
barrelSource.get()));
}
private String moduleBasePath(
final String project,
final ReadOnlyList<String> pathSegments) {
final var pathBuilder = new StringBuilder();
pathBuilder.append(STDLIB_ROOT)
.append('/')
.append(stdlibVersion)
.append('/')
.append(project);
for (final var pathSegment : pathSegments) {
pathBuilder.append('/').append(pathSegment);
}
return pathBuilder.toString();
}
private Optional<String> readResource(final String resourcePath) {
final var classLoader = ResourceStdlibEnvironmentResolver.class.getClassLoader();
if (classLoader == null) {
return Optional.empty();
}
try (InputStream stream = classLoader.getResourceAsStream(resourcePath)) {
if (stream == null) {
return Optional.empty();
}
return Optional.of(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
} catch (IOException ignored) {
return Optional.empty();
}
}
}
}

View File

@ -14,8 +14,8 @@ 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.pbs.stdlib.EmptyStdlibEnvironmentResolver;
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
import p.studio.compiler.pbs.stdlib.ResourceStdlibEnvironmentResolver;
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
@ -42,7 +42,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private final InterfaceModuleLoader interfaceModuleLoader;
public PBSFrontendPhaseService() {
this(new EmptyStdlibEnvironmentResolver(), new InterfaceModuleLoader());
this(new ResourceStdlibEnvironmentResolver(), new InterfaceModuleLoader());
}
PBSFrontendPhaseService(

View File

@ -0,0 +1,10 @@
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
[Slot(index = 0)] pub r: int,
[Slot(index = 1)] pub g: int,
[Slot(index = 2)] pub b: int,
[Slot(index = 3)] pub a: int
) {
[IntrinsicCall(name = "core.color.pack", version = 1)]
fn pack() -> int;
}

View File

@ -0,0 +1,6 @@
import { Color } from @core:color;
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int, color: Color) -> void;
}

View File

@ -247,6 +247,43 @@ class PbsModuleVisibilityTest {
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
}
@Test
void shouldResolveBuiltinTypeBarrelStructEntryWhenDeclarationExistsInInterfaceModule() {
final var diagnostics = DiagnosticSink.empty();
final var sourceFileId = new FileId(105);
final var barrelFileId = new FileId(106);
final var sourceAst = parseSource(
"""
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
[Slot(index = 0)] pub r: int,
[Slot(index = 1)] pub g: int,
[Slot(index = 2)] pub b: int
) {
[IntrinsicCall(name = "core.color.pack", version = 1)]
fn pack() -> int;
}
""",
sourceFileId,
diagnostics,
PbsParser.ParseMode.INTERFACE_MODULE);
final var barrelAst = parseBarrel(
"""
pub struct Color;
""",
barrelFileId,
diagnostics);
final var module = new PbsModuleVisibilityValidator.ModuleUnit(
new PbsModuleVisibilityValidator.ModuleCoordinates("core", ReadOnlyList.wrap(List.of("color"))),
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))),
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst))));
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
}
@Test
void shouldRejectHostBarrelEntryWhenHostDeclarationIsMissing() {
final var diagnostics = DiagnosticSink.empty();

View File

@ -388,6 +388,170 @@ class PBSFrontendPhaseServiceTest {
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldResolveCoreColorImportFromBootstrapStdlib() throws IOException {
final var projectRoot = tempDir.resolve("project-bootstrap-core-color");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("app");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { Color } from @core:color;
declare contract Palette {
fn pick(base: Color) -> Color;
}
""");
Files.writeString(modBarrel, "pub contract Palette;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("app")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
1);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = new PBSFrontendPhaseService().compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldResolveSdkGfxImportFromBootstrapStdlib() throws IOException {
final var projectRoot = tempDir.resolve("project-bootstrap-sdk-gfx");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("app");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { Gfx } from @sdk:gfx;
declare contract Renderer {
fn render(gfx: Gfx) -> void;
}
""");
Files.writeString(modBarrel, "pub contract Renderer;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("app")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
1);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = new PBSFrontendPhaseService().compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldReportInconsistentBarrelInsideSdkBootstrapFixture() throws IOException {
final var projectRoot = tempDir.resolve("project-stdlib-barrel-inconsistent");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("app");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { Gfx } from @sdk:gfx;
fn run() -> int { return 1; }
""");
Files.writeString(modBarrel, "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("app")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var inconsistentGfxModule = new StdlibModuleSource(
"sdk",
ReadOnlyList.wrap(List.of("gfx")),
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
"main.pbs",
"""
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int) -> void;
}
"""))),
"pub host Missing;");
final var frontendService = new PBSFrontendPhaseService(
resolverForMajor(1, inconsistentGfxModule),
new InterfaceModuleLoader(2_500_000));
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
1);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = frontendService.compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
assertEquals(0, irBackend.getFunctions().size());
}
private void registerFile(
final ProjectId projectId,
final Path projectRoot,