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) {
|
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.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, builtinTypeDecl.name(), builtinTypeDecl.span(), nameTable);
|
||||||
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
||||||
} else if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
} 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.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.pbs.stdlib.EmptyStdlibEnvironmentResolver;
|
|
||||||
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
|
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.pbs.stdlib.StdlibEnvironmentResolver;
|
||||||
import p.studio.compiler.source.Span;
|
import p.studio.compiler.source.Span;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
@ -42,7 +42,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
private final InterfaceModuleLoader interfaceModuleLoader;
|
private final InterfaceModuleLoader interfaceModuleLoader;
|
||||||
|
|
||||||
public PBSFrontendPhaseService() {
|
public PBSFrontendPhaseService() {
|
||||||
this(new EmptyStdlibEnvironmentResolver(), new InterfaceModuleLoader());
|
this(new ResourceStdlibEnvironmentResolver(), new InterfaceModuleLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
PBSFrontendPhaseService(
|
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())));
|
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
|
@Test
|
||||||
void shouldRejectHostBarrelEntryWhenHostDeclarationIsMissing() {
|
void shouldRejectHostBarrelEntryWhenHostDeclarationIsMissing() {
|
||||||
final var diagnostics = DiagnosticSink.empty();
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|||||||
@ -388,6 +388,170 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
assertEquals(0, irBackend.getFunctions().size());
|
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(
|
private void registerFile(
|
||||||
final ProjectId projectId,
|
final ProjectId projectId,
|
||||||
final Path projectRoot,
|
final Path projectRoot,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user