From 01c5b6649a3fbc03e04975eb4022315cd467a371 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 6 Mar 2026 15:35:09 +0000 Subject: [PATCH] implements PR029 --- ...tin-slot-inference-and-layout-semantics.md | 72 ----- .../pbs/specs/3. Core Syntax Specification.md | 1 + .../4. Static Semantics Specification.md | 12 +- ...rinsics and Builtin Types Specification.md | 38 +-- .../compiler/pbs/PbsFrontendCompiler.java | 6 +- .../p/studio/compiler/pbs/PbsLoadErrors.java | 6 - .../PbsReservedMetadataExtractor.java | 75 ++--- .../studio/compiler/pbs/parser/PbsParser.java | 4 + .../semantics/PbsBuiltinLayoutResolver.java | 277 ++++++++++++++++++ .../PbsDeclarationSemanticsValidator.java | 32 +- .../pbs/semantics/PbsSemanticsErrors.java | 3 + .../resources/stdlib/1/core/color/main.pbs | 8 +- .../pbs/PbsDiagnosticsContractTest.java | 12 +- .../compiler/pbs/PbsFrontendCompilerTest.java | 59 ++-- .../PbsGateUSdkInterfaceConformanceTest.java | 22 +- .../pbs/linking/PbsModuleVisibilityTest.java | 6 +- .../compiler/pbs/parser/PbsParserTest.java | 29 +- .../PbsInterfaceModuleSemanticsTest.java | 69 ++++- .../services/PBSFrontendPhaseServiceTest.java | 22 +- 19 files changed, 497 insertions(+), 256 deletions(-) delete mode 100644 docs/pbs/pull-requests/PR-029-pbs-builtin-slot-inference-and-layout-semantics.md delete 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/semantics/PbsBuiltinLayoutResolver.java diff --git a/docs/pbs/pull-requests/PR-029-pbs-builtin-slot-inference-and-layout-semantics.md b/docs/pbs/pull-requests/PR-029-pbs-builtin-slot-inference-and-layout-semantics.md deleted file mode 100644 index 9271793f..00000000 --- a/docs/pbs/pull-requests/PR-029-pbs-builtin-slot-inference-and-layout-semantics.md +++ /dev/null @@ -1,72 +0,0 @@ -# PR-029 - PBS Builtin Slot Inference and Canonical Layout Semantics - -## Briefing - -Hoje o frontend aceita `Slot(index=...)` manual em `declare builtin type`, e parte da validacao de layout fica empurrada para `load-facing`. -Isso abre espaco para inconsistencias entre declaracao de campos, flattening e offsets. - -Esta PR move ownership de slot para o compilador: offsets passam a ser inferidos deterministicamente a partir do layout canonico flattenado, sem autoria manual de `Slot`. - -## Motivation - -- Reduzir superficie de erro humano em builtin layout. -- Alinhar semantica estatica com o contrato de layout flattenado. -- Evitar que erros de layout aparecam tarde em `load-facing` quando sao semanticos. - -## Target - -- Tornar `Slot(index=...)` proibido na superficie de fonte de builtin field. -- Banir `Slot(...)` da gramatica PBS (erro de sintaxe), sem fallback transitorio. -- Inferir `slotStart` automaticamente no frontend com base em: - - ordem canonica dos campos, - - largura flattenada dos tipos admissiveis, - - composicao builtin aninhada. -- Mover validacoes de layout para `static semantics`. -- Atualizar extracao de metadata reservada para usar slots inferidos (nao lidos de atributo). -- Atualizar fixtures do SDK minimo (`@core:color` etc.) removendo `Slot`. - -## Scope - -- `frontends/prometeu-frontend-pbs` (semantica + metadata extraction + testes). -- Atualizacao de specs PBS relacionadas a builtin layout e diagnosticos. - -## Method - -1. Introduzir um resolvedor de layout builtin na fase semantica. -2. Calcular largura flattenada por tipo builtin e mapear offsets por campo. -3. Banir `Slot(...)` no parser (erro de sintaxe deterministico) com remocao da forma da superficie da linguagem. -4. Produzir diagnosticos `E_SEM_*` deterministicos para: - - tipo de campo nao admissivel para layout builtin, - - composicao ciclica de builtin layout, - - impossibilidade de resolver largura flattenada. -5. Persistir offsets inferidos no metadata model de lowering (`IRReservedMetadata`). -6. Remover a exigencia de `Slot` no loader/extractor. - -## Acceptance Criteria - -- Builtin fields sem `Slot` compilam quando o layout e admissivel. -- Qualquer `Slot(...)` manual e rejeitado como erro de sintaxe (breaking imediato). -- `Pixel(x:int, y:int, color:Color)` recebe offsets inferidos consistentes com flattening. -- Erros de layout builtin deixam de depender de `E_LOAD_*` para serem detectados. -- Suite Gate U cobre positivo e negativo para inferencia de slot. - -## Tests - -- Positivo: inferencia de offsets em builtin simples e composto. -- Negativo: `Slot` manual presente (falha de parser). -- Negativo: campo com tipo nao admissivel. -- Negativo: ciclo de composicao builtin. -- Asserts por `code`, `phase`, `templateId`, `span`. - -## Non-Goals - -- Alterar o contrato de runtime/verifier para alem da inferencia de layout no frontend. -- Definir encoding final de artifact/PBX. - -## Affected Documents - -- `docs/pbs/specs/4. Static Semantics Specification.md` -- `docs/pbs/specs/3. Core Syntax Specification.md` -- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md` -- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md` -- `docs/pbs/specs/12. Diagnostics Specification.md` diff --git a/docs/pbs/specs/3. Core Syntax Specification.md b/docs/pbs/specs/3. Core Syntax Specification.md index afca1159..2f568893 100644 --- a/docs/pbs/specs/3. Core Syntax Specification.md +++ b/docs/pbs/specs/3. Core Syntax Specification.md @@ -248,6 +248,7 @@ Rules: - Reserved stdlib/toolchain interface modules MAY use attributes where explicitly allowed by syntax and semantics. - Attribute interpretation is compile-time only unless another specification explicitly lowers its effect into runtime-facing metadata. - Reserved attribute names used by builtin-type and intrinsic shells do not become ordinary value-level syntax. +- `Slot(...)` is not part of the v1 attribute surface; source that uses `Slot` must be rejected in syntax phase. ### 6.2 Top-level declarations diff --git a/docs/pbs/specs/4. Static Semantics Specification.md b/docs/pbs/specs/4. Static Semantics Specification.md index ae1ea01f..0c51aa65 100644 --- a/docs/pbs/specs/4. Static Semantics Specification.md +++ b/docs/pbs/specs/4. Static Semantics Specification.md @@ -62,7 +62,7 @@ Rules: - Attributes are not first-class values and are not reflectable in v1 core. - Attributes do not automatically survive into runtime or bytecode artifacts. - An attribute affects runtime artifacts only when another specification defines an explicit lowering for its semantic effect. -- In v1 core, the normative reserved attributes are `Host`, `BuiltinType`, `BuiltinConst`, `IntrinsicCall`, and `Slot`. +- In v1 core, the normative reserved attributes are `Host`, `BuiltinType`, `BuiltinConst`, and `IntrinsicCall`. - `Host` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body. - `Host` is invalid on ordinary user-authored modules, top-level `fn`, struct methods, service methods, callbacks, contracts, and constants. - `Host` metadata is consumed by the compiler during host-binding lowering. @@ -70,7 +70,6 @@ Rules: - `BuiltinType` is valid only on a reserved top-level `declare builtin type` declaration. - `BuiltinConst` is valid only on a reserved top-level `declare const` declaration that omits an initializer. - `IntrinsicCall` is valid only on a method signature declared directly inside a reserved `declare builtin type` body. -- `Slot` is valid only on a field declaration in the header of a reserved `declare builtin type`. - Builtin metadata is consumed by the compiler during VM-owned builtin and intrinsic lowering rather than by host-binding lowering. ### 2.4 Tuple shapes @@ -184,7 +183,9 @@ Rules: - `BuiltinType.version` MUST be a positive integer literal. - Two builtin type declarations in the same resolved stdlib environment MUST NOT lower to the same canonical `(name, version)` unless they denote the same builtin declaration after project/module resolution. - A builtin field type must be builtin-layout-admissible as defined by the builtin/intrinsics specification. -- A builtin field `Slot` attribute, when present, MUST declare a non-negative integer literal and must match the declaration's canonical flattened layout. +- Builtin field slot offsets are inferred by the compiler from canonical field order and flattened builtin layout width. +- Builtin layout composition must be acyclic. +- A builtin field that depends on a builtin layout with unresolved flattened width is statically invalid. - `IntrinsicCall` MUST declare exactly the named argument `name` and MAY additionally declare `version`. - `IntrinsicCall.name` MUST be a non-empty string literal. - `IntrinsicCall.version`, when present, MUST be a positive integer literal. @@ -657,7 +658,6 @@ At minimum, deterministic static diagnostics are required for: - invalid `BuiltinType` attribute target, - invalid `BuiltinConst` attribute target, - invalid `IntrinsicCall` attribute target, -- invalid `Slot` attribute target, - duplicate `Host` attribute on one host method, - missing required `Host` attribute on a reserved host method, - malformed `Host(module=..., name=..., version=...)` argument set, @@ -675,9 +675,9 @@ At minimum, deterministic static diagnostics are required for: - malformed `BuiltinConst(target=..., name=..., version=...)` argument set, - invalid empty `BuiltinConst.target` or `BuiltinConst.name`, - invalid non-positive `BuiltinConst.version`, -- invalid builtin field `Slot(...)`, -- overlapping builtin field slot range, - builtin field type not builtin-layout-admissible, +- cyclic builtin layout composition, +- unresolved flattened builtin layout width, - duplicate canonical builtin type identity, - duplicate canonical builtin intrinsic identity, - duplicate canonical builtin constant identity, diff --git a/docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md b/docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md index 7325aab3..7749465a 100644 --- a/docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md +++ b/docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md @@ -117,8 +117,8 @@ Illustrative form: ```pbs [BuiltinType(name="vec2", version=1)] declare builtin type Vec2( - [Slot(0)] pub x: float, - [Slot(1)] pub y: float, + pub x: float, + pub y: float, ) { [IntrinsicCall(name="dot")] fn dot(other: Vec2) -> float; @@ -196,22 +196,16 @@ Consequences: - a builtin field declaration does not imply that builtin values are structs, - and a builtin field declaration does not allow user code to provide storage-level implementation bodies. -## 10. `Slot` Metadata +## 10. Builtin Slot Inference -`Slot(n)` identifies the starting flattened slot offset of a builtin field within -the enclosing builtin layout. +Builtin field slot offsets are inferred by the compiler, not authored in source. Rules: -- `n` is zero-based, -- `Slot(n)` refers to the starting slot offset after flattening nested builtin-layout-admissible fields, -- slot offsets must be unique, -- slot offsets must match the declaration's canonical flattened layout, -- and overlapping slot ranges are invalid. - -`Slot` exists to make canonical layout explicit. -Future grammar may allow offset inference by declaration order, but explicit slot -metadata is valid and authoritative in this temporary contract. +- slot offsets are zero-based and deterministic, +- offsets are computed from declaration order plus flattened widths of field types, +- nested builtin fields contribute their full flattened width to subsequent offsets, +- and source-level `Slot(...)` metadata is forbidden in v1. ## 11. Builtin Layout Admissibility @@ -256,14 +250,14 @@ Example: ```pbs [BuiltinType(name="color", version=1)] declare builtin type Color( - [Slot(0)] pub raw: int, + pub raw: int, ); [BuiltinType(name="pixel", version=1)] declare builtin type Pixel( - [Slot(0)] pub x: int, - [Slot(1)] pub y: int, - [Slot(2)] pub color: Color, + pub x: int, + pub y: int, + pub color: Color, ); ``` @@ -332,8 +326,8 @@ Example: ```pbs [BuiltinType(name="vec2", version=1)] declare builtin type Vec2( - [Slot(0)] pub x: float, - [Slot(1)] pub y: float, + pub x: float, + pub y: float, ) { [IntrinsicCall(name="dot")] fn dot(other: Vec2) -> float; @@ -530,13 +524,13 @@ At minimum, a conforming implementation must reject: 1. `declare builtin type` in an ordinary user-authored module, 2. missing required `BuiltinType` metadata on a builtin declaration, 3. duplicate builtin field names within one declaration, -4. duplicate or overlapping slot assignments, +4. source-level `Slot(...)` usage in builtin declarations, 5. builtin field types that are not builtin-layout-admissible, 6. `IntrinsicCall` used outside a builtin declaration body, 7. `BuiltinConst` used outside a reserved top-level `declare const` shell, 8. duplicate canonical builtin type identities in the same compilation environment, 9. duplicate intrinsic canonical identities within the same builtin owner and version line, -10. mismatched declared field slot metadata versus flattened layout, +10. cyclic or unresolved flattened builtin layout composition, 11. executable member bodies inside builtin declarations, 12. any lowering that routes VM-owned builtin operations through the host-binding pipeline. 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 3f78c0ed..b462bedd 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 @@ -64,11 +64,7 @@ public final class PbsFrontendCompiler { return IRBackendFile.empty(fileId); } - final var admissionErrorBaseline = diagnostics.errorCount(); - final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind, diagnostics); - if (diagnostics.errorCount() > admissionErrorBaseline) { - return IRBackendFile.empty(fileId); - } + final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind); final ReadOnlyList functions = sourceKind == SourceKind.SDK_INTERFACE ? ReadOnlyList.empty() 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 deleted file mode 100644 index 4fb6f094..00000000 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsLoadErrors.java +++ /dev/null @@ -1,6 +0,0 @@ -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 index c413f228..ffda815b 100644 --- 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 @@ -2,31 +2,26 @@ 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.compiler.pbs.semantics.PbsBuiltinLayoutResolver; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalLong; +import java.util.stream.Collectors; 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) { + final SourceKind sourceKind) { if (sourceKind != SourceKind.SDK_INTERFACE) { return IRReservedMetadata.empty(); } @@ -34,6 +29,10 @@ public final class PbsReservedMetadataExtractor { final var hostMethodBindings = new ArrayList(); final var builtinTypeSurfaces = new ArrayList(); final var builtinConstSurfaces = new ArrayList(); + final var inferredLayoutsByName = PbsBuiltinLayoutResolver.resolve(ast).layouts().stream() + .collect(Collectors.toMap( + PbsBuiltinLayoutResolver.ResolvedBuiltinLayout::sourceTypeName, + layout -> layout)); for (final var topDecl : ast.topDecls()) { if (topDecl instanceof PbsAst.HostDecl hostDecl) { @@ -41,7 +40,7 @@ public final class PbsReservedMetadataExtractor { continue; } if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) { - extractBuiltinTypeSurface(builtinTypeDecl, builtinTypeSurfaces, diagnostics); + extractBuiltinTypeSurface(builtinTypeDecl, inferredLayoutsByName, builtinTypeSurfaces); continue; } if (topDecl instanceof PbsAst.ConstDecl constDecl) { @@ -79,8 +78,8 @@ public final class PbsReservedMetadataExtractor { private void extractBuiltinTypeSurface( final PbsAst.BuiltinTypeDecl builtinTypeDecl, - final List builtinTypeSurfaces, - final DiagnosticSink diagnostics) { + final Map inferredLayoutsByName, + final List builtinTypeSurfaces) { final var builtinTypeAttribute = firstAttributeNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE); if (builtinTypeAttribute.isEmpty()) { return; @@ -89,45 +88,18 @@ public final class PbsReservedMetadataExtractor { final var builtinTypeMetadata = builtinTypeAttribute.get(); final var canonicalTypeName = stringArgument(builtinTypeMetadata, "name").orElse(builtinTypeDecl.name()); final var canonicalVersion = longArgument(builtinTypeMetadata, "version").orElse(0L); + final var inferredLayout = inferredLayoutsByName.get(builtinTypeDecl.name()); 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; - } - + for (int i = 0; i < builtinTypeDecl.fields().size(); i++) { + final var field = builtinTypeDecl.fields().get(i); + final var slotStart = inferredLayout != null && i < inferredLayout.fields().size() + ? inferredLayout.fields().get(i).slotStart() + : i; fields.add(new IRReservedMetadata.BuiltinFieldSurface( field.name(), typeSurfaceKey(field.typeRef()), - slotStart.getAsLong(), + slotStart, field.span())); } @@ -171,17 +143,6 @@ public final class PbsReservedMetadataExtractor { 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) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java index b74bfed6..edecf881 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java @@ -472,6 +472,10 @@ public final class PbsParser { if (cursor.match(PbsTokenKind.LEFT_PAREN)) { parseAttributeArguments(arguments); } + if ("Slot".equals(name.lexeme())) { + report(name, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Attribute 'Slot' is not part of PBS syntax; builtin slots are inferred by the compiler"); + } long end = Math.max(leftBracket.end(), name.end()); if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsBuiltinLayoutResolver.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsBuiltinLayoutResolver.java new file mode 100644 index 00000000..731c8177 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsBuiltinLayoutResolver.java @@ -0,0 +1,277 @@ +package p.studio.compiler.pbs.semantics; + +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.source.Span; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public final class PbsBuiltinLayoutResolver { + private static final Set BUILTIN_SCALAR_TYPES = Set.of("int", "float", "bool", "str"); + + private enum ResolveState { + UNVISITED, + RESOLVING, + RESOLVED, + FAILED + } + + private enum WidthStatus { + OK, + CYCLE, + FAILED + } + + private final Map builtinDeclByName = new LinkedHashMap<>(); + private final Map stateByBuiltinName = new HashMap<>(); + private final Map resolvedLayouts = new HashMap<>(); + private final Set reportedCycles = new HashSet<>(); + private final DiagnosticSink diagnostics; + + private PbsBuiltinLayoutResolver( + final PbsAst.File ast, + final DiagnosticSink diagnostics) { + this.diagnostics = diagnostics; + for (final var topDecl : ast.topDecls()) { + if (!(topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl)) { + continue; + } + builtinDeclByName.putIfAbsent(builtinTypeDecl.name(), builtinTypeDecl); + } + for (final var builtinTypeName : builtinDeclByName.keySet()) { + stateByBuiltinName.put(builtinTypeName, ResolveState.UNVISITED); + } + } + + public static ResolvedBuiltinLayoutTable resolve(final PbsAst.File ast) { + return new PbsBuiltinLayoutResolver(ast, null).resolveAll(); + } + + public static ResolvedBuiltinLayoutTable resolve( + final PbsAst.File ast, + final DiagnosticSink diagnostics) { + return new PbsBuiltinLayoutResolver(ast, diagnostics).resolveAll(); + } + + private ResolvedBuiltinLayoutTable resolveAll() { + for (final var builtinTypeName : builtinDeclByName.keySet()) { + resolveBuiltinLayout(builtinTypeName); + } + return new ResolvedBuiltinLayoutTable(ReadOnlyList.wrap(new ArrayList<>(resolvedLayouts.values()))); + } + + private WidthResult resolveBuiltinLayout(final String builtinTypeName) { + final var state = stateByBuiltinName.getOrDefault(builtinTypeName, ResolveState.UNVISITED); + if (state == ResolveState.RESOLVED) { + return WidthResult.ok(resolvedLayouts.get(builtinTypeName).totalWidth()); + } + if (state == ResolveState.FAILED) { + return WidthResult.failed(); + } + if (state == ResolveState.RESOLVING) { + return WidthResult.cycle(); + } + + final var declaration = builtinDeclByName.get(builtinTypeName); + if (declaration == null) { + return WidthResult.failed(); + } + + stateByBuiltinName.put(builtinTypeName, ResolveState.RESOLVING); + + long slotCursor = 0L; + var failed = false; + final var fieldLayouts = new ArrayList(declaration.fields().size()); + for (final var field : declaration.fields()) { + final var width = resolveFieldWidth(builtinTypeName, field); + if (width.status() != WidthStatus.OK) { + failed = true; + continue; + } + fieldLayouts.add(new ResolvedBuiltinFieldLayout( + field.name(), + slotCursor, + width.width(), + field.span())); + slotCursor += width.width(); + } + + if (failed) { + stateByBuiltinName.put(builtinTypeName, ResolveState.FAILED); + return WidthResult.failed(); + } + + final var layout = new ResolvedBuiltinLayout( + declaration.name(), + slotCursor, + ReadOnlyList.wrap(fieldLayouts)); + resolvedLayouts.put(builtinTypeName, layout); + stateByBuiltinName.put(builtinTypeName, ResolveState.RESOLVED); + return WidthResult.ok(slotCursor); + } + + private WidthResult resolveFieldWidth( + final String ownerBuiltinName, + final PbsAst.BuiltinFieldDecl field) { + final var typeRef = normalizeFieldType(field.typeRef()); + if (typeRef == null || typeRef.kind() != PbsAst.TypeRefKind.SIMPLE) { + reportTypeNotAdmissible(ownerBuiltinName, field, field.typeRef()); + return WidthResult.failed(); + } + + if (BUILTIN_SCALAR_TYPES.contains(typeRef.name())) { + return WidthResult.ok(1L); + } + + final var nestedBuiltin = builtinDeclByName.get(typeRef.name()); + if (nestedBuiltin == null) { + reportTypeNotAdmissible(ownerBuiltinName, field, field.typeRef()); + return WidthResult.failed(); + } + + final var nestedWidth = resolveBuiltinLayout(typeRef.name()); + if (nestedWidth.status() == WidthStatus.OK) { + return nestedWidth; + } + if (nestedWidth.status() == WidthStatus.CYCLE) { + reportCycleIfNeeded(typeRef.name(), ownerBuiltinName, field.name(), field.span()); + return WidthResult.cycle(); + } + + reportUnresolvableWidth(ownerBuiltinName, field, typeRef.name()); + return WidthResult.failed(); + } + + private PbsAst.TypeRef normalizeFieldType(final PbsAst.TypeRef typeRef) { + var current = typeRef; + while (current != null && current.kind() == PbsAst.TypeRefKind.GROUP) { + current = current.inner(); + } + return current; + } + + private void reportTypeNotAdmissible( + final String ownerBuiltinName, + final PbsAst.BuiltinFieldDecl field, + final PbsAst.TypeRef typeRef) { + if (diagnostics == null) { + return; + } + p.studio.compiler.source.diagnostics.Diagnostics.error( + diagnostics, + PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name(), + "Builtin field '%s.%s' uses non-admissible layout type '%s'" + .formatted(ownerBuiltinName, field.name(), renderTypeSurface(typeRef)), + field.span()); + } + + private void reportCycleIfNeeded( + final String targetBuiltinName, + final String ownerBuiltinName, + final String ownerFieldName, + final Span span) { + if (diagnostics == null) { + return; + } + final String cycleKey = ownerBuiltinName == null + ? "cycle:" + targetBuiltinName + : "cycle:" + ownerBuiltinName + "." + ownerFieldName + "->" + targetBuiltinName; + if (!reportedCycles.add(cycleKey)) { + return; + } + final var primarySpan = span != null + ? span + : Optional.ofNullable(builtinDeclByName.get(targetBuiltinName)) + .map(PbsAst.BuiltinTypeDecl::span) + .orElse(Span.none()); + p.studio.compiler.source.diagnostics.Diagnostics.error( + diagnostics, + PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_CYCLIC_COMPOSITION.name(), + ownerBuiltinName == null + ? "Builtin layout cycle detected for '%s'".formatted(targetBuiltinName) + : "Builtin field '%s.%s' introduces cyclic builtin layout dependency on '%s'" + .formatted(ownerBuiltinName, ownerFieldName, targetBuiltinName), + primarySpan); + } + + private void reportUnresolvableWidth( + final String ownerBuiltinName, + final PbsAst.BuiltinFieldDecl field, + final String nestedBuiltinName) { + if (diagnostics == null) { + return; + } + p.studio.compiler.source.diagnostics.Diagnostics.error( + diagnostics, + PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_UNRESOLVABLE_WIDTH.name(), + "Builtin field '%s.%s' cannot resolve flattened layout width for '%s'" + .formatted(ownerBuiltinName, field.name(), nestedBuiltinName), + field.span()); + } + + private String renderTypeSurface(final PbsAst.TypeRef typeRef) { + if (typeRef == null) { + return "void"; + } + return switch (typeRef.kind()) { + case SIMPLE -> typeRef.name(); + case SELF -> "Self"; + case OPTIONAL -> "optional %s".formatted(renderTypeSurface(typeRef.inner())); + case UNIT -> "void"; + case GROUP -> "(%s)".formatted(renderTypeSurface(typeRef.inner())); + case NAMED_TUPLE -> "(%s)".formatted(typeRef.fields().stream() + .map(field -> "%s: %s".formatted(field.label(), renderTypeSurface(field.typeRef()))) + .reduce((left, right) -> left + ", " + right) + .orElse("")); + case ERROR -> ""; + }; + } + + private record WidthResult( + WidthStatus status, + long width) { + static WidthResult ok(final long width) { + return new WidthResult(WidthStatus.OK, width); + } + + static WidthResult cycle() { + return new WidthResult(WidthStatus.CYCLE, 0L); + } + + static WidthResult failed() { + return new WidthResult(WidthStatus.FAILED, 0L); + } + } + + public record ResolvedBuiltinLayoutTable( + ReadOnlyList layouts) { + public Optional findByName(final String builtinTypeName) { + for (final var layout : layouts) { + if (layout.sourceTypeName().equals(builtinTypeName)) { + return Optional.of(layout); + } + } + return Optional.empty(); + } + } + + public record ResolvedBuiltinLayout( + String sourceTypeName, + long totalWidth, + ReadOnlyList fields) { + } + + public record ResolvedBuiltinFieldLayout( + String fieldName, + long slotStart, + long slotWidth, + Span span) { + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java index aa76029e..9e301640 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java @@ -19,14 +19,12 @@ public final class PbsDeclarationSemanticsValidator { private static final String ATTR_BUILTIN_TYPE = "BuiltinType"; private static final String ATTR_BUILTIN_CONST = "BuiltinConst"; private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall"; - private static final String ATTR_SLOT = "Slot"; private static final Set RESERVED_ATTRIBUTES = Set.of( ATTR_HOST, ATTR_BUILTIN_TYPE, ATTR_BUILTIN_CONST, - ATTR_INTRINSIC_CALL, - ATTR_SLOT); + ATTR_INTRINSIC_CALL); private final NameTable nameTable = new NameTable(); private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator(); @@ -150,6 +148,9 @@ public final class PbsDeclarationSemanticsValidator { } constSemanticsValidator.validate(ast, diagnostics); + if (interfaceModule) { + PbsBuiltinLayoutResolver.resolve(ast, diagnostics); + } } private void validateStructDeclaration( @@ -448,29 +449,15 @@ public final class PbsDeclarationSemanticsValidator { private void validateBuiltinFieldAttributes( final PbsAst.BuiltinFieldDecl field, final DiagnosticSink diagnostics) { - final var slotAttributes = attributesNamed(field.attributes(), ATTR_SLOT); for (final var attribute : field.attributes()) { if (!isReservedAttribute(attribute.name())) { continue; } - if (ATTR_SLOT.equals(attribute.name())) { - continue; - } reportInvalidReservedAttributeTarget( attribute, "Attribute '%s' is not valid on builtin fields".formatted(attribute.name()), diagnostics); } - - if (slotAttributes.size() > 1) { - for (int i = 1; i < slotAttributes.size(); i++) { - reportDuplicateReservedAttribute(slotAttributes.get(i), ATTR_SLOT, diagnostics); - } - } - - if (!slotAttributes.isEmpty()) { - validateSlotAttributeShape(slotAttributes.getFirst(), diagnostics); - } } private void validateIntrinsicCallAttributes( @@ -553,17 +540,6 @@ public final class PbsDeclarationSemanticsValidator { } } - private void validateSlotAttributeShape( - final PbsAst.Attribute attribute, - final DiagnosticSink diagnostics) { - final var args = validateNamedArguments(attribute, Set.of("index"), Set.of(), diagnostics); - if (args == null) { - return; - } - - validateRequiredIntArgument(attribute, args, "index", false, diagnostics); - } - private Map validateNamedArguments( final PbsAst.Attribute attribute, final Set requiredNames, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java index a0845b11..78ed36a4 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java @@ -21,6 +21,9 @@ public enum PbsSemanticsErrors { E_SEM_CONST_INITIALIZER_TYPE_MISMATCH, E_SEM_CONST_CYCLIC_DEPENDENCY, E_SEM_CONST_UNRESOLVED_REFERENCE, + E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE, + E_SEM_BUILTIN_LAYOUT_CYCLIC_COMPOSITION, + E_SEM_BUILTIN_LAYOUT_UNRESOLVABLE_WIDTH, E_SEM_INVALID_RETURN_INSIDE_CTOR, E_SEM_APPLY_NON_CALLABLE_TARGET, E_SEM_APPLY_UNRESOLVED_OVERLOAD, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs index 68f7862b..9bc1c8ac 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs @@ -1,9 +1,9 @@ [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 + pub r: int, + pub g: int, + pub b: int, + pub a: int ) { [IntrinsicCall(name = "core.color.pack", version = 1)] fn pack() -> int; 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 1a3eb472..ea7ab3c9 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 @@ -82,22 +82,24 @@ class PbsDiagnosticsContractTest { } @Test - void shouldTagLoadFacingDiagnosticsWithStableTemplateAndAttribution() { + void shouldTagBuiltinLayoutDiagnosticsWithStaticSemanticsPhase() { final var source = """ + declare struct S(value: int); [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int - ); + pub raw: S + ) { + } """; 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())) + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name())) .findFirst() .orElseThrow(); - assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, diagnostic.getPhase()); + assertEquals(DiagnosticPhase.STATIC_SEMANTICS, 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 807ef603..c307cb2e 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 @@ -4,7 +4,6 @@ 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; @@ -107,9 +106,9 @@ class PbsFrontendCompilerTest { 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 + pub r: int, + pub g: int, + pub b: int ) { [IntrinsicCall(name = "core.color.pack")] fn pack() -> int; @@ -136,49 +135,61 @@ class PbsFrontendCompilerTest { } @Test - void shouldRejectUnsupportedBuiltinSlotInferenceAtLoadAdmissionBoundary() { + void shouldInferBuiltinFieldSlotsFromDeclarationOrder() { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( pub r: int, - [Slot(index = 1)] pub g: int - ); + 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()); + assertTrue(diagnostics.isEmpty()); + final var fields = fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields(); + assertEquals(2, fields.size()); + assertEquals(0L, fields.get(0).slotStart()); + assertEquals(1L, fields.get(1).slotStart()); assertEquals(0, fileBackend.functions().size()); } @Test - void shouldRejectDuplicateBuiltinSlotIndexesAtLoadAdmissionBoundary() { + void shouldInferBuiltinFieldSlotsUsingFlattenedNestedWidth() { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - [Slot(index = 0)] pub r: int, - [Slot(index = 0)] pub g: int - ); + pub r: int, + pub g: int, + pub b: int, + pub a: int + ) { + } + + [BuiltinType(name = "Pixel", version = 1)] + declare builtin type Pixel( + pub x: int, + pub y: int, + pub color: Color + ) { + } """; final var diagnostics = DiagnosticSink.empty(); final var compiler = new PbsFrontendCompiler(); - compiler.compileFile(new FileId(5), source, diagnostics, SourceKind.SDK_INTERFACE); + final var fileBackend = 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())) + assertTrue(diagnostics.isEmpty()); + final var pixel = fileBackend.reservedMetadata().builtinTypeSurfaces().stream() + .filter(t -> t.sourceTypeName().equals("Pixel")) .findFirst() .orElseThrow(); - assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase()); - assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId()); - assertEquals(1, rejectionDiagnostic.getRelated().size()); + assertEquals(3, pixel.fields().size()); + assertEquals(0L, pixel.fields().get(0).slotStart()); + assertEquals(1L, pixel.fields().get(1).slotStart()); + assertEquals(2L, pixel.fields().get(2).slotStart()); } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java index 26be806e..44deb1b2 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java @@ -53,7 +53,7 @@ class PbsGateUSdkInterfaceConformanceTest { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - [Slot(index = 0)] pub r: int + pub r: int ) { } @@ -159,7 +159,7 @@ class PbsGateUSdkInterfaceConformanceTest { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - [Slot(index = 0)] pub r: int + pub r: int ) { } @@ -216,12 +216,12 @@ class PbsGateUSdkInterfaceConformanceTest { } @Test - void gateU_shouldExposeReservedMetadataAndRejectUnsupportedLoadAdmission() { + void gateU_shouldExposeReservedMetadataAndRejectInvalidBuiltinLayoutAtSemantics() { final var validSource = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - [Slot(index = 0)] pub r: int, - [Slot(index = 1)] pub g: int + pub r: int, + pub g: int ) { [IntrinsicCall(name = "core.color.pack", version = 1)] fn pack() -> int; @@ -247,9 +247,10 @@ class PbsGateUSdkInterfaceConformanceTest { assertEquals(1, validBackend.reservedMetadata().builtinConstSurfaces().size()); final var invalidSource = """ + declare struct RGBA(raw: int); [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int + pub raw: RGBA ) { } """; @@ -259,9 +260,12 @@ class PbsGateUSdkInterfaceConformanceTest { invalidSource, invalidDiagnostics, SourceKind.SDK_INTERFACE); - final var unsupportedLoad = firstDiagnostic(invalidDiagnostics, - d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name())); - assertStableDiagnosticIdentity(unsupportedLoad, PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name(), DiagnosticPhase.LOAD_FACING_REJECTION); + final var invalidLayout = firstDiagnostic(invalidDiagnostics, + d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name())); + assertStableDiagnosticIdentity( + invalidLayout, + PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name(), + DiagnosticPhase.STATIC_SEMANTICS); } private WorkspaceCompileResult compileWorkspaceModule( diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java index c0c4ac54..9cc80662 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityTest.java @@ -256,9 +256,9 @@ class PbsModuleVisibilityTest { """ [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 + pub r: int, + pub g: int, + pub b: int ) { [IntrinsicCall(name = "core.color.pack", version = 1)] fn pack() -> int; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java index b600ec93..10647f48 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/parser/PbsParserTest.java @@ -221,9 +221,9 @@ class PbsParserTest { 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, + pub r: int, + pub g: int, + pub b: int, ) { [IntrinsicCall(name = "color.pack", version = 1)] fn pack() -> int; @@ -251,7 +251,7 @@ class PbsParserTest { assertEquals(1, builtinType.attributes().size()); assertEquals(3, builtinType.fields().size()); assertEquals(1, builtinType.signatures().size()); - assertEquals(1, builtinType.fields().getFirst().attributes().size()); + assertEquals(0, builtinType.fields().getFirst().attributes().size()); assertEquals(1, builtinType.signatures().getFirst().attributes().size()); final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1)); @@ -303,6 +303,27 @@ class PbsParserTest { assertTrue(diagnostics.hasErrors(), "Function-level else fallback syntax is not valid PBS core syntax"); } + @Test + void shouldRejectSlotAttributeInInterfaceModuleMode() { + final var source = """ + [BuiltinType(name = "Color", version = 1)] + declare builtin type Color( + [Slot(index = 0)] pub raw: int + ) { + } + """; + final var diagnostics = DiagnosticSink.empty(); + final var fileId = new FileId(0); + + PbsParser.parse( + PbsLexer.lex(source, fileId, diagnostics), + fileId, + diagnostics, + PbsParser.ParseMode.INTERFACE_MODULE); + + assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_INVALID_DECL_SHAPE.name()))); + } + @Test void shouldReportEnumMixedAndDuplicateCases() { final var source = """ diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java index 034f9b05..cc44a32f 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsInterfaceModuleSemanticsTest.java @@ -16,7 +16,7 @@ class PbsInterfaceModuleSemanticsTest { final var source = """ [BuiltinType(name = "color", version = 1)] declare builtin type Color( - [Slot(index = 0)] pub raw: int + pub raw: int ) { [IntrinsicCall(name = "pack", version = 1)] fn pack() -> int; @@ -98,4 +98,71 @@ class PbsInterfaceModuleSemanticsTest { .count(); assertTrue(nonDeclarativeCount >= 3); } + + @Test + void shouldRejectBuiltinFieldWithNonAdmissibleLayoutType() { + final var source = """ + declare struct RGBA(raw: int); + + [BuiltinType(name = "Color", version = 1)] + declare builtin type Color( + pub raw: RGBA + ) { + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name()))); + } + + @Test + void shouldRejectCyclicBuiltinLayoutComposition() { + final var source = """ + [BuiltinType(name = "A", version = 1)] + declare builtin type A( + pub b: B + ) { + } + + [BuiltinType(name = "B", version = 1)] + declare builtin type B( + pub a: A + ) { + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(1), source, diagnostics, SourceKind.SDK_INTERFACE); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_CYCLIC_COMPOSITION.name()))); + } + + @Test + void shouldRejectBuiltinFieldWhenNestedBuiltinWidthIsUnresolvable() { + final var source = """ + declare struct RGBA(raw: int); + + [BuiltinType(name = "Bad", version = 1)] + declare builtin type Bad( + pub raw: RGBA + ) { + } + + [BuiltinType(name = "Pixel", version = 1)] + declare builtin type Pixel( + pub bad: Bad + ) { + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(2), source, diagnostics, SourceKind.SDK_INTERFACE); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_UNRESOLVABLE_WIDTH.name()))); + } } 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 bb8a5379..9f7c7e63 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 @@ -237,9 +237,9 @@ class PBSFrontendPhaseServiceTest { Files.writeString(sourceFile, """ [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 + pub r: int, + pub g: int, + pub b: int ) { [IntrinsicCall(name = "color.pack", version = 1)] fn pack() -> int; @@ -557,7 +557,7 @@ class PBSFrontendPhaseServiceTest { } @Test - void shouldEmitLoadFacingRejectionForUnsupportedStdlibReservedMetadataShape() throws IOException { + void shouldEmitStaticSemanticsErrorForInvalidStdlibBuiltinLayoutShape() throws IOException { final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata"); final var sourceRoot = projectRoot.resolve("src"); final var modulePath = sourceRoot.resolve("app"); @@ -589,10 +589,12 @@ class PBSFrontendPhaseServiceTest { ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile( "main.pbs", """ + declare struct RGBA(raw: int); [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int - ); + pub raw: RGBA + ) { + } """))), "pub struct Color;"); final var frontendService = new PBSFrontendPhaseService( @@ -611,12 +613,12 @@ class PBSFrontendPhaseServiceTest { 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())) + final var semanticsDiagnostic = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name())) .findFirst() .orElseThrow(); - assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase()); - assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId()); + assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.STATIC_SEMANTICS, semanticsDiagnostic.getPhase()); + assertEquals(semanticsDiagnostic.getCode(), semanticsDiagnostic.getTemplateId()); assertEquals(1, irBackend.getFunctions().size()); assertEquals("run", irBackend.getFunctions().getFirst().name()); }