implements PR026
This commit is contained in:
parent
52e8746bcc
commit
9fb8622ddb
@ -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) {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
pub struct Color;
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
pub host Gfx;
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user