From bb5cc05c0b0d62529e125e0daaed5ca2c41bf8a1 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 6 Mar 2026 14:11:02 +0000 Subject: [PATCH] implements PR027 --- ...-interface-module-semantics-and-linking.md | 55 ---- ...-026-pbs-sdk-minimal-core-color-and-gfx.md | 54 ---- .../compiler/pbs/PbsFrontendCompiler.java | 14 +- .../p/studio/compiler/pbs/PbsLoadErrors.java | 6 + .../PbsReservedMetadataExtractor.java | 260 ++++++++++++++++++ .../services/PBSFrontendPhaseService.java | 38 ++- .../pbs/PbsDiagnosticsContractTest.java | 21 ++ .../compiler/pbs/PbsFrontendCompilerTest.java | 82 ++++++ .../services/PBSFrontendPhaseServiceTest.java | 69 +++++ .../p/studio/compiler/models/IRBackend.java | 28 +- .../studio/compiler/models/IRBackendFile.java | 12 +- .../compiler/models/IRReservedMetadata.java | 92 +++++++ 12 files changed, 611 insertions(+), 120 deletions(-) delete mode 100644 docs/pbs/pull-requests/PR-025-pbs-interface-module-semantics-and-linking.md delete mode 100644 docs/pbs/pull-requests/PR-026-pbs-sdk-minimal-core-color-and-gfx.md create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsLoadErrors.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java create mode 100644 prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java diff --git a/docs/pbs/pull-requests/PR-025-pbs-interface-module-semantics-and-linking.md b/docs/pbs/pull-requests/PR-025-pbs-interface-module-semantics-and-linking.md deleted file mode 100644 index 4a0a1ff3..00000000 --- a/docs/pbs/pull-requests/PR-025-pbs-interface-module-semantics-and-linking.md +++ /dev/null @@ -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` - diff --git a/docs/pbs/pull-requests/PR-026-pbs-sdk-minimal-core-color-and-gfx.md b/docs/pbs/pull-requests/PR-026-pbs-sdk-minimal-core-color-and-gfx.md deleted file mode 100644 index 43862276..00000000 --- a/docs/pbs/pull-requests/PR-026-pbs-sdk-minimal-core-color-and-gfx.md +++ /dev/null @@ -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` - diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index c70c6364..3f78c0ed 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -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 functions = sourceKind == SourceKind.SDK_INTERFACE + ? ReadOnlyList.empty() + : lowerFunctions(fileId, ast); + return new IRBackendFile(fileId, functions, reservedMetadata); } private ReadOnlyList lowerFunctions(final FileId fileId, final PbsAst.File ast) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsLoadErrors.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsLoadErrors.java new file mode 100644 index 00000000..4fb6f094 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsLoadErrors.java @@ -0,0 +1,6 @@ +package p.studio.compiler.pbs; + +public enum PbsLoadErrors { + E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE, + E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX, +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java new file mode 100644 index 00000000..c413f228 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java @@ -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(); + final var builtinTypeSurfaces = new ArrayList(); + final var builtinConstSurfaces = new ArrayList(); + + 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 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 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(builtinTypeDecl.fields().size()); + final var firstFieldBySlot = new HashMap(); + 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(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 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 firstAttributeNamed( + final ReadOnlyList attributes, + final String attributeName) { + for (final var attribute : attributes) { + if (attributeName.equals(attribute.name())) { + return Optional.of(attribute); + } + } + return Optional.empty(); + } + + private Optional 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 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"; + }; + } +} 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 9268aef1..724d38ee 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 @@ -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(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(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 modulesByCoordinates, + final ArrayList parsedSourceFiles, + final Map 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) { + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java index 0f76bf22..1a3eb472 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java @@ -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()); + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java index 04d86742..807ef603 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java @@ -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()); + } } 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 598df5cf..bb8a5379 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 @@ -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, diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java index 3567c858..af91d7f4 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java @@ -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 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 functions = MutableList.create(); + private final ArrayList functions = new ArrayList<>(); + private final ArrayList hostMethodBindings = new ArrayList<>(); + private final ArrayList builtinTypeSurfaces = new ArrayList<>(); + private final ArrayList 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(); diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java index 59c1f8c5..6e99f20b 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java @@ -7,13 +7,21 @@ import java.util.Objects; public record IRBackendFile( FileId fileId, - ReadOnlyList functions) { + ReadOnlyList 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 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()); } } diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java new file mode 100644 index 00000000..6bb251ae --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java @@ -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 hostMethodBindings, + ReadOnlyList builtinTypeSurfaces, + ReadOnlyList 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 fields, + ReadOnlyList 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"); + } + } +}