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 98751e53..b3a69ba1 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 @@ -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) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/ResourceStdlibEnvironmentResolver.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/ResourceStdlibEnvironmentResolver.java new file mode 100644 index 00000000..541a28cb --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/stdlib/ResourceStdlibEnvironmentResolver.java @@ -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 resolveModule( + final String project, + final ReadOnlyList 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 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 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(); + } + } + } +} 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 98f3597f..9268aef1 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 @@ -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( diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs new file mode 100644 index 00000000..68f7862b --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs @@ -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; +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/mod.barrel b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/mod.barrel new file mode 100644 index 00000000..c7bfb43f --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/mod.barrel @@ -0,0 +1 @@ +pub struct Color; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs new file mode 100644 index 00000000..cd0c713f --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs @@ -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; +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel new file mode 100644 index 00000000..b08ba680 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel @@ -0,0 +1 @@ +pub host Gfx; 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 86f800dd..c0c4ac54 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 @@ -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(); 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 index 9939d0d2..598df5cf 100644 --- 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 @@ -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,