implements PR027

This commit is contained in:
bQUARKz 2026-03-06 14:11:02 +00:00
parent 9fb8622ddb
commit bb5cc05c0b
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
12 changed files with 611 additions and 120 deletions

View File

@ -1,55 +0,0 @@
# PR-025 - PBS Interface-Module Semantics and Linking Rules
## Briefing
Depois de parsear forms reservadas, o frontend precisa validar regras semanticas especificas de interface modules e integrar host/builtin shells ao linking.
## Motivation
Sem essa etapa:
- declarations reservadas entram sem contrato semantico,
- host owners nao entram corretamente no namespace/linking,
- e o barrel contract para `host` nao fecha.
## Target
- Validadores semanticos (`declaration`, `flow` quando aplicavel).
- `PbsNamespaceBinder` e `PbsModuleVisibilityValidator`.
## Scope
- Validar restricoes declarativas de interface module (nao executavel, assinatura-only onde requerido).
- Validar posicionamento/shape de `Host`, `BuiltinType`, `BuiltinConst`, `IntrinsicCall`, `Slot`.
- Incluir host owners em namespace e linking (incluindo barrel host entry resolution).
## Method
- Adicionar caminho semantico para `SDK_INTERFACE` com regras de admissao dedicadas.
- Reusar estrutura de diagnosticos com codigos estaveis.
- Garantir ownership de fase para erros de linking vs static semantics.
## Acceptance Criteria
- `host` barrel entries resolvem quando `declare host` existe no modulo reservado.
- Violacoes de posicionamento/shape de attributes reservadas sao rejeitadas deterministicamente.
- Modulos ordinarios continuam com as regras atuais e sem regressao.
## Tests
- Fixtures positivos de interface module valido com host/builtin declarations.
- Fixtures negativos de attributes mal posicionadas e host/builtin invalido.
- Fixtures de linking para barrel host item resolvido e nao resolvido.
## Non-Goals
- Materializacao final de metadata no artifact/lowering.
- Politica completa de runtime capabilities.
## Affected Documents
- `docs/pbs/specs/4. Static Semantics Specification.md`
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
- `docs/pbs/specs/6.2. Host ABI Binding and Loader Resolution Specification.md`
- `docs/pbs/specs/12. Diagnostics Specification.md`

View File

@ -1,54 +0,0 @@
# PR-026 - PBS Minimal SDK Bootstrap with Core Color and Gfx Host Surface
## Briefing
Precisamos de um SDK minimo funcional para iniciar implementacao de builtins e validar o fluxo interface-module end-to-end.
Esta PR introduz um pacote minimo de modulos reservados com exemplo `Color` e `Gfx`.
## Motivation
Sem SDK minimo, o suporte a interface mode fica sem fixture real de uso.
Com ele, conseguimos exercitar import reservado, metadata, linking e base para lowering.
## Target
- Conteudo inicial do stdlib environment para linha v1.
- Modulos reservados `@core:*` e `@sdk:*` com superfícies declarativas minimas.
## Scope
- Criar modulo `@core:color` com shell builtin `Color` (e opcional `Pixel`) como exemplo.
- Criar modulo `@sdk:gfx` com `declare host Gfx` e assinatura anotada com `Host(...)`.
- Fornecer `mod.barrel` coerente para os modulos criados.
## Method
- Publicar fontes `.pbs` de interface module no armazenamento escolhido para stdlib bootstrap (ex.: resources).
- Manter tudo declarativo e nao executavel.
- Garantir nomes/cases/assinaturas pequenas, estaveis e didaticas para suite inicial.
## Acceptance Criteria
- Import de `Color` via `@core:color` resolve e participa do type namespace.
- Import de `Gfx` via `@sdk:gfx` resolve no host-owner namespace.
- Modulos do SDK minimo passam parser+semantica+linking de interface-module.
## Tests
- Fixture de compilacao que importa `Color` e usa em assinatura/tipo.
- Fixture de compilacao que importa `Gfx` e valida superficie host.
- Fixtures negativas de barrel inconsistente no SDK minimo.
## Non-Goals
- Cobertura completa de builtins do stdlib.
- Qualquer implementacao de comportamento runtime da API grafica.
## Affected Documents
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
- `docs/pbs/specs/6.2. Host ABI Binding and Loader Resolution Specification.md`
- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md`

View File

@ -5,6 +5,7 @@ import p.studio.compiler.models.SourceKind;
import p.studio.compiler.models.IRBackendFile;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
import p.studio.compiler.pbs.parser.PbsParser;
import p.studio.compiler.pbs.semantics.PbsDeclarationSemanticsValidator;
import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator;
@ -17,6 +18,7 @@ import java.util.ArrayList;
public final class PbsFrontendCompiler {
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
public IRBackendFile compileFile(
final FileId fileId,
@ -61,7 +63,17 @@ public final class PbsFrontendCompiler {
if (diagnostics.errorCount() > semanticsErrorBaseline) {
return IRBackendFile.empty(fileId);
}
return new IRBackendFile(fileId, lowerFunctions(fileId, ast));
final var admissionErrorBaseline = diagnostics.errorCount();
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind, diagnostics);
if (diagnostics.errorCount() > admissionErrorBaseline) {
return IRBackendFile.empty(fileId);
}
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
: lowerFunctions(fileId, ast);
return new IRBackendFile(fileId, functions, reservedMetadata);
}
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {

View File

@ -0,0 +1,6 @@
package p.studio.compiler.pbs;
public enum PbsLoadErrors {
E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE,
E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX,
}

View File

@ -0,0 +1,260 @@
package p.studio.compiler.pbs.metadata;
import p.studio.compiler.models.IRReservedMetadata;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.PbsLoadErrors;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.Diagnostics;
import p.studio.compiler.source.diagnostics.RelatedSpan;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
public final class PbsReservedMetadataExtractor {
private static final String ATTR_HOST = "Host";
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
private static final String ATTR_SLOT = "Slot";
public IRReservedMetadata extract(
final PbsAst.File ast,
final SourceKind sourceKind,
final DiagnosticSink diagnostics) {
if (sourceKind != SourceKind.SDK_INTERFACE) {
return IRReservedMetadata.empty();
}
final var hostMethodBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
for (final var topDecl : ast.topDecls()) {
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
extractHostBindings(hostDecl, hostMethodBindings);
continue;
}
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
extractBuiltinTypeSurface(builtinTypeDecl, builtinTypeSurfaces, diagnostics);
continue;
}
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
extractBuiltinConstSurface(constDecl, builtinConstSurfaces);
}
}
return new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces),
ReadOnlyList.wrap(builtinConstSurfaces));
}
private void extractHostBindings(
final PbsAst.HostDecl hostDecl,
final List<IRReservedMetadata.HostMethodBinding> hostMethodBindings) {
for (final var signature : hostDecl.signatures()) {
final var hostAttribute = firstAttributeNamed(signature.attributes(), ATTR_HOST);
if (hostAttribute.isEmpty()) {
continue;
}
final var hostMetadata = hostAttribute.get();
final var abiModule = stringArgument(hostMetadata, "module").orElse("");
final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name());
final var abiVersion = longArgument(hostMetadata, "version").orElse(0L);
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
hostDecl.name(),
signature.name(),
abiModule,
abiMethod,
abiVersion,
signature.span()));
}
}
private void extractBuiltinTypeSurface(
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
final List<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces,
final DiagnosticSink diagnostics) {
final var builtinTypeAttribute = firstAttributeNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
if (builtinTypeAttribute.isEmpty()) {
return;
}
final var builtinTypeMetadata = builtinTypeAttribute.get();
final var canonicalTypeName = stringArgument(builtinTypeMetadata, "name").orElse(builtinTypeDecl.name());
final var canonicalVersion = longArgument(builtinTypeMetadata, "version").orElse(0L);
final var fields = new ArrayList<IRReservedMetadata.BuiltinFieldSurface>(builtinTypeDecl.fields().size());
final var firstFieldBySlot = new HashMap<Long, Span>();
for (final var field : builtinTypeDecl.fields()) {
final var slotAttribute = firstAttributeNamed(field.attributes(), ATTR_SLOT);
if (slotAttribute.isEmpty()) {
reportUnsupportedSurface(
diagnostics,
field.span(),
"Builtin field '%s' in '%s' requires Slot(index=...) for lowering admission"
.formatted(field.name(), builtinTypeDecl.name()));
continue;
}
final var slotStart = longArgument(slotAttribute.get(), "index");
if (slotStart.isEmpty()) {
reportUnsupportedSurface(
diagnostics,
slotAttribute.get().span(),
"Builtin field '%s' in '%s' requires numeric Slot(index=...) for lowering admission"
.formatted(field.name(), builtinTypeDecl.name()));
continue;
}
final var firstField = firstFieldBySlot.putIfAbsent(slotStart.getAsLong(), field.span());
if (firstField != null) {
Diagnostics.error(
diagnostics,
PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name(),
"Duplicate builtin slot index %d in '%s'".formatted(slotStart.getAsLong(), builtinTypeDecl.name()),
field.span(),
List.of(new RelatedSpan("First builtin field using this slot is here", firstField)));
continue;
}
fields.add(new IRReservedMetadata.BuiltinFieldSurface(
field.name(),
typeSurfaceKey(field.typeRef()),
slotStart.getAsLong(),
field.span()));
}
final var intrinsics = new ArrayList<IRReservedMetadata.IntrinsicSurface>(builtinTypeDecl.signatures().size());
for (final var signature : builtinTypeDecl.signatures()) {
final var intrinsicAttribute = firstAttributeNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
if (intrinsicAttribute.isEmpty()) {
continue;
}
final var intrinsicMetadata = intrinsicAttribute.get();
intrinsics.add(new IRReservedMetadata.IntrinsicSurface(
signature.name(),
stringArgument(intrinsicMetadata, "name").orElse(signature.name()),
longArgument(intrinsicMetadata, "version").orElse(canonicalVersion),
signature.span()));
}
builtinTypeSurfaces.add(new IRReservedMetadata.BuiltinTypeSurface(
builtinTypeDecl.name(),
canonicalTypeName,
canonicalVersion,
ReadOnlyList.wrap(fields),
ReadOnlyList.wrap(intrinsics),
builtinTypeDecl.span()));
}
private void extractBuiltinConstSurface(
final PbsAst.ConstDecl constDecl,
final List<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces) {
final var builtinConstAttribute = firstAttributeNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
if (builtinConstAttribute.isEmpty()) {
return;
}
final var builtinConstMetadata = builtinConstAttribute.get();
builtinConstSurfaces.add(new IRReservedMetadata.BuiltinConstSurface(
constDecl.name(),
stringArgument(builtinConstMetadata, "target").orElse(""),
stringArgument(builtinConstMetadata, "name").orElse(constDecl.name()),
longArgument(builtinConstMetadata, "version").orElse(0L),
constDecl.span()));
}
private void reportUnsupportedSurface(
final DiagnosticSink diagnostics,
final Span span,
final String message) {
Diagnostics.error(
diagnostics,
PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name(),
message,
span);
}
private Optional<PbsAst.Attribute> firstAttributeNamed(
final ReadOnlyList<PbsAst.Attribute> attributes,
final String attributeName) {
for (final var attribute : attributes) {
if (attributeName.equals(attribute.name())) {
return Optional.of(attribute);
}
}
return Optional.empty();
}
private Optional<String> stringArgument(
final PbsAst.Attribute attribute,
final String argumentName) {
final var argument = findArgument(attribute, argumentName);
if (argument.isEmpty()) {
return Optional.empty();
}
if (argument.get().value() instanceof PbsAst.AttributeStringValue value) {
return Optional.of(normalizeStringLiteralValue(value.value()));
}
return Optional.empty();
}
private OptionalLong longArgument(
final PbsAst.Attribute attribute,
final String argumentName) {
final var argument = findArgument(attribute, argumentName);
if (argument.isEmpty()) {
return OptionalLong.empty();
}
if (argument.get().value() instanceof PbsAst.AttributeIntValue value) {
return OptionalLong.of(value.value());
}
return OptionalLong.empty();
}
private Optional<PbsAst.AttributeArgument> findArgument(
final PbsAst.Attribute attribute,
final String argumentName) {
for (final var argument : attribute.arguments()) {
if (argumentName.equals(argument.name())) {
return Optional.of(argument);
}
}
return Optional.empty();
}
private String normalizeStringLiteralValue(final String rawValue) {
if (rawValue == null) {
return "";
}
if (rawValue.length() >= 2 && rawValue.startsWith("\"") && rawValue.endsWith("\"")) {
return rawValue.substring(1, rawValue.length() - 1);
}
return rawValue;
}
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return "unit";
}
return switch (typeRef.kind()) {
case SIMPLE -> "simple:" + typeRef.name();
case SELF -> "self";
case OPTIONAL -> "optional(" + typeSurfaceKey(typeRef.inner()) + ")";
case UNIT -> "unit";
case GROUP -> "group(" + typeSurfaceKey(typeRef.inner()) + ")";
case NAMED_TUPLE -> "tuple(" + typeRef.fields().stream()
.map(field -> typeSurfaceKey(field.typeRef()))
.reduce((left, right) -> left + "," + right)
.orElse("") + ")";
case ERROR -> "error";
};
}
}

View File

@ -116,7 +116,12 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
}
}
loadReservedStdlibModules(modulesByCoordinates, diagnostics, ctx.stdlibVersion());
loadReservedStdlibModules(
modulesByCoordinates,
parsedSourceFiles,
moduleKeyByFile,
diagnostics,
ctx.stdlibVersion());
final var modules = new ArrayList<PbsModuleVisibilityValidator.ModuleUnit>(modulesByCoordinates.size());
for (final var entry : modulesByCoordinates.entrySet()) {
@ -130,19 +135,28 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), diagnostics);
markModulesWithLinkingErrors(diagnostics, moduleKeyByFile, failedModuleKeys);
final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size());
for (final var parsedSource : parsedSourceFiles) {
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
continue;
}
final var compileErrorBaseline = diagnostics.errorCount();
final var irBackendFile = frontendCompiler.compileParsedFile(
parsedSource.fileId(),
parsedSource.ast(),
diagnostics,
parsedSource.sourceKind());
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
if (diagnostics.errorCount() > compileErrorBaseline) {
failedModuleKeys.add(parsedSource.moduleKey());
}
compiledSourceFiles.add(new CompiledSourceFile(parsedSource.moduleKey(), irBackendFile));
}
for (final var compiledSource : compiledSourceFiles) {
if (failedModuleKeys.contains(compiledSource.moduleKey())) {
continue;
}
irBackendAggregator.merge(irBackendFile);
irBackendAggregator.merge(compiledSource.irBackendFile());
}
final var irBackend = irBackendAggregator.emit();
@ -152,6 +166,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private void loadReservedStdlibModules(
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates,
final ArrayList<ParsedSourceFile> parsedSourceFiles,
final Map<FileId, String> moduleKeyByFile,
final DiagnosticSink diagnostics,
final int stdlibVersion) {
final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion);
@ -179,6 +195,17 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
moduleData.sources.addAll(loadedModule.sourceFiles().asList());
moduleData.barrels.addAll(loadedModule.barrelFiles().asList());
modulesByCoordinates.put(loadedModule.coordinates(), moduleData);
for (final var sourceFile : loadedModule.sourceFiles()) {
moduleKeyByFile.put(sourceFile.fileId(), targetKey);
parsedSourceFiles.add(new ParsedSourceFile(
sourceFile.fileId(),
sourceFile.ast(),
targetKey,
SourceKind.SDK_INTERFACE));
}
for (final var barrelFile : loadedModule.barrelFiles()) {
moduleKeyByFile.put(barrelFile.fileId(), targetKey);
}
enqueueReservedImportsFromSourceFiles(loadedModule.sourceFiles().asList(), pending);
}
}
@ -293,4 +320,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
String moduleKey,
SourceKind sourceKind) {
}
private record CompiledSourceFile(
String moduleKey,
p.studio.compiler.models.IRBackendFile irBackendFile) {
}
}

View File

@ -80,4 +80,25 @@ class PbsDiagnosticsContractTest {
assertEquals(DiagnosticPhase.LINKING, diagnostic.getPhase());
assertEquals(1, diagnostic.getRelated().size());
}
@Test
void shouldTagLoadFacingDiagnosticsWithStableTemplateAndAttribution() {
final var source = """
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
pub r: int
);
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(9), source, diagnostics, p.studio.compiler.models.SourceKind.SDK_INTERFACE);
final var diagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, diagnostic.getPhase());
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
assertEquals(9, diagnostic.getSpan().getFileId().getId());
}
}

View File

@ -1,8 +1,10 @@
package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.lexer.LexErrors;
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
@ -99,4 +101,84 @@ class PbsFrontendCompilerTest {
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())));
assertEquals(0, fileBackend.functions().size());
}
@Test
void shouldExtractReservedMetadataForInterfaceModuleSurfaces() {
final var source = """
[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")]
fn pack() -> int;
}
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int, color: Color) -> void;
}
[BuiltinConst(target = "Color", name = "white", version = 1)]
declare const WHITE: Color;
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
assertTrue(diagnostics.isEmpty());
assertEquals(0, fileBackend.functions().size());
assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size());
assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size());
assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size());
assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName());
assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size());
}
@Test
void shouldRejectUnsupportedBuiltinSlotInferenceAtLoadAdmissionBoundary() {
final var source = """
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
pub r: int,
[Slot(index = 1)] pub g: int
);
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(3), source, diagnostics, SourceKind.SDK_INTERFACE);
final var rejectionDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
assertEquals(3, rejectionDiagnostic.getSpan().getFileId().getId());
assertEquals(0, fileBackend.functions().size());
}
@Test
void shouldRejectDuplicateBuiltinSlotIndexesAtLoadAdmissionBoundary() {
final var source = """
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
[Slot(index = 0)] pub r: int,
[Slot(index = 0)] pub g: int
);
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
compiler.compileFile(new FileId(5), source, diagnostics, SourceKind.SDK_INTERFACE);
final var rejectionDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
assertEquals(1, rejectionDiagnostic.getRelated().size());
}
}

View File

@ -437,6 +437,8 @@ class PBSFrontendPhaseServiceTest {
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
assertEquals(0, irBackend.getFunctions().size());
assertTrue(irBackend.getReservedMetadata().builtinTypeSurfaces().stream()
.anyMatch(t -> t.sourceTypeName().equals("Color") && t.fields().size() == 4));
}
@Test
@ -488,6 +490,8 @@ class PBSFrontendPhaseServiceTest {
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
assertEquals(0, irBackend.getFunctions().size());
assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
.anyMatch(h -> h.ownerName().equals("Gfx") && h.sourceMethodName().equals("draw_pixel")));
}
@Test
@ -552,6 +556,71 @@ class PBSFrontendPhaseServiceTest {
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldEmitLoadFacingRejectionForUnsupportedStdlibReservedMetadataShape() throws IOException {
final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata");
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;
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 invalidColorModule = new StdlibModuleSource(
"core",
ReadOnlyList.wrap(List.of("color")),
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
"main.pbs",
"""
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
pub r: int
);
"""))),
"pub struct Color;");
final var frontendService = new PBSFrontendPhaseService(
resolverForMajor(1, invalidColorModule),
new InterfaceModuleLoader(2_700_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());
final var rejectionDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(p.studio.compiler.pbs.PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
.findFirst()
.orElseThrow();
assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
assertEquals(1, irBackend.getFunctions().size());
assertEquals("run", irBackend.getFunctions().getFirst().name());
}
private void registerFile(
final ProjectId projectId,
final Path projectRoot,

View File

@ -2,33 +2,47 @@ package p.studio.compiler.models;
import lombok.Builder;
import lombok.Getter;
import p.studio.utilities.structures.MutableList;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
@Builder
@Getter
public class IRBackend {
@Builder.Default
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
@Builder.Default
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
public static IRBackendAggregator aggregator() {
return new IRBackendAggregator();
}
public static final class IRBackendAggregator {
private final MutableList<IRFunction> functions = MutableList.create();
private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
public void merge(final IRBackendFile backendFile) {
if (backendFile == null) {
return;
}
functions.addAll(backendFile.functions());
functions.addAll(backendFile.functions().asList());
final var metadata = backendFile.reservedMetadata();
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList());
}
public IRBackend emit() {
return IRBackend
.builder()
.functions(functions.toReadOnlyList())
.functions(ReadOnlyList.wrap(functions))
.reservedMetadata(new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces),
ReadOnlyList.wrap(builtinConstSurfaces)))
.build();
}
}
@ -36,7 +50,11 @@ public class IRBackend {
@Override
public String toString() {
final var sb = new StringBuilder();
sb.append("IRBackend{functions=").append(functions.size()).append('}');
sb.append("IRBackend{functions=").append(functions.size())
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
.append('}');
if (functions.isEmpty()) {
return sb.toString();

View File

@ -7,13 +7,21 @@ import java.util.Objects;
public record IRBackendFile(
FileId fileId,
ReadOnlyList<IRFunction> functions) {
ReadOnlyList<IRFunction> functions,
IRReservedMetadata reservedMetadata) {
public IRBackendFile {
fileId = Objects.requireNonNull(fileId, "fileId");
functions = functions == null ? ReadOnlyList.empty() : functions;
reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata;
}
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions) {
this(fileId, functions, IRReservedMetadata.empty());
}
public static IRBackendFile empty(final FileId fileId) {
return new IRBackendFile(fileId, ReadOnlyList.empty());
return new IRBackendFile(fileId, ReadOnlyList.empty(), IRReservedMetadata.empty());
}
}

View File

@ -0,0 +1,92 @@
package p.studio.compiler.models;
import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Objects;
public record IRReservedMetadata(
ReadOnlyList<HostMethodBinding> hostMethodBindings,
ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces) {
public IRReservedMetadata {
hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
}
public static IRReservedMetadata empty() {
return new IRReservedMetadata(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty());
}
public record HostMethodBinding(
String ownerName,
String sourceMethodName,
String abiModule,
String abiMethod,
long abiVersion,
Span span) {
public HostMethodBinding {
ownerName = Objects.requireNonNull(ownerName, "ownerName");
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
abiModule = Objects.requireNonNull(abiModule, "abiModule");
abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
span = Objects.requireNonNull(span, "span");
}
}
public record BuiltinTypeSurface(
String sourceTypeName,
String canonicalTypeName,
long canonicalVersion,
ReadOnlyList<BuiltinFieldSurface> fields,
ReadOnlyList<IntrinsicSurface> intrinsics,
Span span) {
public BuiltinTypeSurface {
sourceTypeName = Objects.requireNonNull(sourceTypeName, "sourceTypeName");
canonicalTypeName = Objects.requireNonNull(canonicalTypeName, "canonicalTypeName");
fields = fields == null ? ReadOnlyList.empty() : fields;
intrinsics = intrinsics == null ? ReadOnlyList.empty() : intrinsics;
span = Objects.requireNonNull(span, "span");
}
}
public record BuiltinFieldSurface(
String fieldName,
String declaredTypeSurface,
long slotStart,
Span span) {
public BuiltinFieldSurface {
fieldName = Objects.requireNonNull(fieldName, "fieldName");
declaredTypeSurface = Objects.requireNonNull(declaredTypeSurface, "declaredTypeSurface");
span = Objects.requireNonNull(span, "span");
}
}
public record IntrinsicSurface(
String sourceMethodName,
String canonicalName,
long canonicalVersion,
Span span) {
public IntrinsicSurface {
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
span = Objects.requireNonNull(span, "span");
}
}
public record BuiltinConstSurface(
String sourceConstName,
String canonicalTarget,
String canonicalName,
long canonicalVersion,
Span span) {
public BuiltinConstSurface {
sourceConstName = Objects.requireNonNull(sourceConstName, "sourceConstName");
canonicalTarget = Objects.requireNonNull(canonicalTarget, "canonicalTarget");
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
span = Objects.requireNonNull(span, "span");
}
}
}