implements PR029
This commit is contained in:
parent
fc734403b5
commit
01c5b6649a
@ -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`
|
|
||||||
@ -248,6 +248,7 @@ Rules:
|
|||||||
- Reserved stdlib/toolchain interface modules MAY use attributes where explicitly allowed by syntax and semantics.
|
- 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.
|
- 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.
|
- 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
|
### 6.2 Top-level declarations
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ Rules:
|
|||||||
- Attributes are not first-class values and are not reflectable in v1 core.
|
- Attributes are not first-class values and are not reflectable in v1 core.
|
||||||
- Attributes do not automatically survive into runtime or bytecode artifacts.
|
- 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.
|
- 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 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` 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.
|
- `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.
|
- `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.
|
- `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.
|
- `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.
|
- Builtin metadata is consumed by the compiler during VM-owned builtin and intrinsic lowering rather than by host-binding lowering.
|
||||||
|
|
||||||
### 2.4 Tuple shapes
|
### 2.4 Tuple shapes
|
||||||
@ -184,7 +183,9 @@ Rules:
|
|||||||
- `BuiltinType.version` MUST be a positive integer literal.
|
- `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.
|
- 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 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` MUST declare exactly the named argument `name` and MAY additionally declare `version`.
|
||||||
- `IntrinsicCall.name` MUST be a non-empty string literal.
|
- `IntrinsicCall.name` MUST be a non-empty string literal.
|
||||||
- `IntrinsicCall.version`, when present, MUST be a positive integer 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 `BuiltinType` attribute target,
|
||||||
- invalid `BuiltinConst` attribute target,
|
- invalid `BuiltinConst` attribute target,
|
||||||
- invalid `IntrinsicCall` attribute target,
|
- invalid `IntrinsicCall` attribute target,
|
||||||
- invalid `Slot` attribute target,
|
|
||||||
- duplicate `Host` attribute on one host method,
|
- duplicate `Host` attribute on one host method,
|
||||||
- missing required `Host` attribute on a reserved host method,
|
- missing required `Host` attribute on a reserved host method,
|
||||||
- malformed `Host(module=..., name=..., version=...)` argument set,
|
- 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,
|
- malformed `BuiltinConst(target=..., name=..., version=...)` argument set,
|
||||||
- invalid empty `BuiltinConst.target` or `BuiltinConst.name`,
|
- invalid empty `BuiltinConst.target` or `BuiltinConst.name`,
|
||||||
- invalid non-positive `BuiltinConst.version`,
|
- invalid non-positive `BuiltinConst.version`,
|
||||||
- invalid builtin field `Slot(...)`,
|
|
||||||
- overlapping builtin field slot range,
|
|
||||||
- builtin field type not builtin-layout-admissible,
|
- builtin field type not builtin-layout-admissible,
|
||||||
|
- cyclic builtin layout composition,
|
||||||
|
- unresolved flattened builtin layout width,
|
||||||
- duplicate canonical builtin type identity,
|
- duplicate canonical builtin type identity,
|
||||||
- duplicate canonical builtin intrinsic identity,
|
- duplicate canonical builtin intrinsic identity,
|
||||||
- duplicate canonical builtin constant identity,
|
- duplicate canonical builtin constant identity,
|
||||||
|
|||||||
@ -117,8 +117,8 @@ Illustrative form:
|
|||||||
```pbs
|
```pbs
|
||||||
[BuiltinType(name="vec2", version=1)]
|
[BuiltinType(name="vec2", version=1)]
|
||||||
declare builtin type Vec2(
|
declare builtin type Vec2(
|
||||||
[Slot(0)] pub x: float,
|
pub x: float,
|
||||||
[Slot(1)] pub y: float,
|
pub y: float,
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name="dot")]
|
[IntrinsicCall(name="dot")]
|
||||||
fn dot(other: Vec2) -> float;
|
fn dot(other: Vec2) -> float;
|
||||||
@ -196,22 +196,16 @@ Consequences:
|
|||||||
- a builtin field declaration does not imply that builtin values are structs,
|
- 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.
|
- 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
|
Builtin field slot offsets are inferred by the compiler, not authored in source.
|
||||||
the enclosing builtin layout.
|
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- `n` is zero-based,
|
- slot offsets are zero-based and deterministic,
|
||||||
- `Slot(n)` refers to the starting slot offset after flattening nested builtin-layout-admissible fields,
|
- offsets are computed from declaration order plus flattened widths of field types,
|
||||||
- slot offsets must be unique,
|
- nested builtin fields contribute their full flattened width to subsequent offsets,
|
||||||
- slot offsets must match the declaration's canonical flattened layout,
|
- and source-level `Slot(...)` metadata is forbidden in v1.
|
||||||
- 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.
|
|
||||||
|
|
||||||
## 11. Builtin Layout Admissibility
|
## 11. Builtin Layout Admissibility
|
||||||
|
|
||||||
@ -256,14 +250,14 @@ Example:
|
|||||||
```pbs
|
```pbs
|
||||||
[BuiltinType(name="color", version=1)]
|
[BuiltinType(name="color", version=1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(0)] pub raw: int,
|
pub raw: int,
|
||||||
);
|
);
|
||||||
|
|
||||||
[BuiltinType(name="pixel", version=1)]
|
[BuiltinType(name="pixel", version=1)]
|
||||||
declare builtin type Pixel(
|
declare builtin type Pixel(
|
||||||
[Slot(0)] pub x: int,
|
pub x: int,
|
||||||
[Slot(1)] pub y: int,
|
pub y: int,
|
||||||
[Slot(2)] pub color: Color,
|
pub color: Color,
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -332,8 +326,8 @@ Example:
|
|||||||
```pbs
|
```pbs
|
||||||
[BuiltinType(name="vec2", version=1)]
|
[BuiltinType(name="vec2", version=1)]
|
||||||
declare builtin type Vec2(
|
declare builtin type Vec2(
|
||||||
[Slot(0)] pub x: float,
|
pub x: float,
|
||||||
[Slot(1)] pub y: float,
|
pub y: float,
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name="dot")]
|
[IntrinsicCall(name="dot")]
|
||||||
fn dot(other: Vec2) -> float;
|
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,
|
1. `declare builtin type` in an ordinary user-authored module,
|
||||||
2. missing required `BuiltinType` metadata on a builtin declaration,
|
2. missing required `BuiltinType` metadata on a builtin declaration,
|
||||||
3. duplicate builtin field names within one 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,
|
5. builtin field types that are not builtin-layout-admissible,
|
||||||
6. `IntrinsicCall` used outside a builtin declaration body,
|
6. `IntrinsicCall` used outside a builtin declaration body,
|
||||||
7. `BuiltinConst` used outside a reserved top-level `declare const` shell,
|
7. `BuiltinConst` used outside a reserved top-level `declare const` shell,
|
||||||
8. duplicate canonical builtin type identities in the same compilation environment,
|
8. duplicate canonical builtin type identities in the same compilation environment,
|
||||||
9. duplicate intrinsic canonical identities within the same builtin owner and version line,
|
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,
|
11. executable member bodies inside builtin declarations,
|
||||||
12. any lowering that routes VM-owned builtin operations through the host-binding pipeline.
|
12. any lowering that routes VM-owned builtin operations through the host-binding pipeline.
|
||||||
|
|
||||||
|
|||||||
@ -64,11 +64,7 @@ public final class PbsFrontendCompiler {
|
|||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var admissionErrorBaseline = diagnostics.errorCount();
|
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind);
|
||||||
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind, diagnostics);
|
|
||||||
if (diagnostics.errorCount() > admissionErrorBaseline) {
|
|
||||||
return IRBackendFile.empty(fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
||||||
? ReadOnlyList.empty()
|
? ReadOnlyList.empty()
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
package p.studio.compiler.pbs;
|
|
||||||
|
|
||||||
public enum PbsLoadErrors {
|
|
||||||
E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE,
|
|
||||||
E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX,
|
|
||||||
}
|
|
||||||
@ -2,31 +2,26 @@ package p.studio.compiler.pbs.metadata;
|
|||||||
|
|
||||||
import p.studio.compiler.models.IRReservedMetadata;
|
import p.studio.compiler.models.IRReservedMetadata;
|
||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.PbsLoadErrors;
|
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.source.Span;
|
import p.studio.compiler.pbs.semantics.PbsBuiltinLayoutResolver;
|
||||||
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 p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class PbsReservedMetadataExtractor {
|
public final class PbsReservedMetadataExtractor {
|
||||||
private static final String ATTR_HOST = "Host";
|
private static final String ATTR_HOST = "Host";
|
||||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||||
private static final String ATTR_SLOT = "Slot";
|
|
||||||
|
|
||||||
public IRReservedMetadata extract(
|
public IRReservedMetadata extract(
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
final SourceKind sourceKind,
|
final SourceKind sourceKind) {
|
||||||
final DiagnosticSink diagnostics) {
|
|
||||||
if (sourceKind != SourceKind.SDK_INTERFACE) {
|
if (sourceKind != SourceKind.SDK_INTERFACE) {
|
||||||
return IRReservedMetadata.empty();
|
return IRReservedMetadata.empty();
|
||||||
}
|
}
|
||||||
@ -34,6 +29,10 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
final var hostMethodBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
|
final var hostMethodBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
|
||||||
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
||||||
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
|
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
|
||||||
|
final var inferredLayoutsByName = PbsBuiltinLayoutResolver.resolve(ast).layouts().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
PbsBuiltinLayoutResolver.ResolvedBuiltinLayout::sourceTypeName,
|
||||||
|
layout -> layout));
|
||||||
|
|
||||||
for (final var topDecl : ast.topDecls()) {
|
for (final var topDecl : ast.topDecls()) {
|
||||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
@ -41,7 +40,7 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
extractBuiltinTypeSurface(builtinTypeDecl, builtinTypeSurfaces, diagnostics);
|
extractBuiltinTypeSurface(builtinTypeDecl, inferredLayoutsByName, builtinTypeSurfaces);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
@ -79,8 +78,8 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
|
|
||||||
private void extractBuiltinTypeSurface(
|
private void extractBuiltinTypeSurface(
|
||||||
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||||
final List<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces,
|
final Map<String, PbsBuiltinLayoutResolver.ResolvedBuiltinLayout> inferredLayoutsByName,
|
||||||
final DiagnosticSink diagnostics) {
|
final List<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces) {
|
||||||
final var builtinTypeAttribute = firstAttributeNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
final var builtinTypeAttribute = firstAttributeNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
||||||
if (builtinTypeAttribute.isEmpty()) {
|
if (builtinTypeAttribute.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@ -89,45 +88,18 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
final var builtinTypeMetadata = builtinTypeAttribute.get();
|
final var builtinTypeMetadata = builtinTypeAttribute.get();
|
||||||
final var canonicalTypeName = stringArgument(builtinTypeMetadata, "name").orElse(builtinTypeDecl.name());
|
final var canonicalTypeName = stringArgument(builtinTypeMetadata, "name").orElse(builtinTypeDecl.name());
|
||||||
final var canonicalVersion = longArgument(builtinTypeMetadata, "version").orElse(0L);
|
final var canonicalVersion = longArgument(builtinTypeMetadata, "version").orElse(0L);
|
||||||
|
final var inferredLayout = inferredLayoutsByName.get(builtinTypeDecl.name());
|
||||||
|
|
||||||
final var fields = new ArrayList<IRReservedMetadata.BuiltinFieldSurface>(builtinTypeDecl.fields().size());
|
final var fields = new ArrayList<IRReservedMetadata.BuiltinFieldSurface>(builtinTypeDecl.fields().size());
|
||||||
final var firstFieldBySlot = new HashMap<Long, Span>();
|
for (int i = 0; i < builtinTypeDecl.fields().size(); i++) {
|
||||||
for (final var field : builtinTypeDecl.fields()) {
|
final var field = builtinTypeDecl.fields().get(i);
|
||||||
final var slotAttribute = firstAttributeNamed(field.attributes(), ATTR_SLOT);
|
final var slotStart = inferredLayout != null && i < inferredLayout.fields().size()
|
||||||
if (slotAttribute.isEmpty()) {
|
? inferredLayout.fields().get(i).slotStart()
|
||||||
reportUnsupportedSurface(
|
: i;
|
||||||
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(
|
fields.add(new IRReservedMetadata.BuiltinFieldSurface(
|
||||||
field.name(),
|
field.name(),
|
||||||
typeSurfaceKey(field.typeRef()),
|
typeSurfaceKey(field.typeRef()),
|
||||||
slotStart.getAsLong(),
|
slotStart,
|
||||||
field.span()));
|
field.span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,17 +143,6 @@ public final class PbsReservedMetadataExtractor {
|
|||||||
constDecl.span()));
|
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(
|
private Optional<PbsAst.Attribute> firstAttributeNamed(
|
||||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
final String attributeName) {
|
final String attributeName) {
|
||||||
|
|||||||
@ -472,6 +472,10 @@ public final class PbsParser {
|
|||||||
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
|
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
|
||||||
parseAttributeArguments(arguments);
|
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());
|
long end = Math.max(leftBracket.end(), name.end());
|
||||||
if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) {
|
if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) {
|
||||||
|
|||||||
@ -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<String> 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<String, PbsAst.BuiltinTypeDecl> builtinDeclByName = new LinkedHashMap<>();
|
||||||
|
private final Map<String, ResolveState> stateByBuiltinName = new HashMap<>();
|
||||||
|
private final Map<String, ResolvedBuiltinLayout> resolvedLayouts = new HashMap<>();
|
||||||
|
private final Set<String> 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<ResolvedBuiltinFieldLayout>(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 -> "<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<ResolvedBuiltinLayout> layouts) {
|
||||||
|
public Optional<ResolvedBuiltinLayout> 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<ResolvedBuiltinFieldLayout> fields) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ResolvedBuiltinFieldLayout(
|
||||||
|
String fieldName,
|
||||||
|
long slotStart,
|
||||||
|
long slotWidth,
|
||||||
|
Span span) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,14 +19,12 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
private static final String ATTR_SLOT = "Slot";
|
|
||||||
|
|
||||||
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||||
ATTR_HOST,
|
ATTR_HOST,
|
||||||
ATTR_BUILTIN_TYPE,
|
ATTR_BUILTIN_TYPE,
|
||||||
ATTR_BUILTIN_CONST,
|
ATTR_BUILTIN_CONST,
|
||||||
ATTR_INTRINSIC_CALL,
|
ATTR_INTRINSIC_CALL);
|
||||||
ATTR_SLOT);
|
|
||||||
|
|
||||||
private final NameTable nameTable = new NameTable();
|
private final NameTable nameTable = new NameTable();
|
||||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||||
@ -150,6 +148,9 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constSemanticsValidator.validate(ast, diagnostics);
|
constSemanticsValidator.validate(ast, diagnostics);
|
||||||
|
if (interfaceModule) {
|
||||||
|
PbsBuiltinLayoutResolver.resolve(ast, diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateStructDeclaration(
|
private void validateStructDeclaration(
|
||||||
@ -448,29 +449,15 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
private void validateBuiltinFieldAttributes(
|
private void validateBuiltinFieldAttributes(
|
||||||
final PbsAst.BuiltinFieldDecl field,
|
final PbsAst.BuiltinFieldDecl field,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
final var slotAttributes = attributesNamed(field.attributes(), ATTR_SLOT);
|
|
||||||
for (final var attribute : field.attributes()) {
|
for (final var attribute : field.attributes()) {
|
||||||
if (!isReservedAttribute(attribute.name())) {
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ATTR_SLOT.equals(attribute.name())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
reportInvalidReservedAttributeTarget(
|
reportInvalidReservedAttributeTarget(
|
||||||
attribute,
|
attribute,
|
||||||
"Attribute '%s' is not valid on builtin fields".formatted(attribute.name()),
|
"Attribute '%s' is not valid on builtin fields".formatted(attribute.name()),
|
||||||
diagnostics);
|
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(
|
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<String, PbsAst.AttributeValue> validateNamedArguments(
|
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||||
final PbsAst.Attribute attribute,
|
final PbsAst.Attribute attribute,
|
||||||
final Set<String> requiredNames,
|
final Set<String> requiredNames,
|
||||||
|
|||||||
@ -21,6 +21,9 @@ public enum PbsSemanticsErrors {
|
|||||||
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
E_SEM_CONST_INITIALIZER_TYPE_MISMATCH,
|
||||||
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
E_SEM_CONST_CYCLIC_DEPENDENCY,
|
||||||
E_SEM_CONST_UNRESOLVED_REFERENCE,
|
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_INVALID_RETURN_INSIDE_CTOR,
|
||||||
E_SEM_APPLY_NON_CALLABLE_TARGET,
|
E_SEM_APPLY_NON_CALLABLE_TARGET,
|
||||||
E_SEM_APPLY_UNRESOLVED_OVERLOAD,
|
E_SEM_APPLY_UNRESOLVED_OVERLOAD,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int,
|
pub g: int,
|
||||||
[Slot(index = 2)] pub b: int,
|
pub b: int,
|
||||||
[Slot(index = 3)] pub a: int
|
pub a: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
|
|||||||
@ -82,22 +82,24 @@ class PbsDiagnosticsContractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldTagLoadFacingDiagnosticsWithStableTemplateAndAttribution() {
|
void shouldTagBuiltinLayoutDiagnosticsWithStaticSemanticsPhase() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
declare struct S(value: int);
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub r: int
|
pub raw: S
|
||||||
);
|
) {
|
||||||
|
}
|
||||||
""";
|
""";
|
||||||
final var diagnostics = DiagnosticSink.empty();
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
new PbsFrontendCompiler().compileFile(new FileId(9), source, diagnostics, p.studio.compiler.models.SourceKind.SDK_INTERFACE);
|
new PbsFrontendCompiler().compileFile(new FileId(9), source, diagnostics, p.studio.compiler.models.SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
final var diagnostic = diagnostics.stream()
|
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()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, diagnostic.getPhase());
|
assertEquals(DiagnosticPhase.STATIC_SEMANTICS, diagnostic.getPhase());
|
||||||
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
||||||
assertEquals(9, diagnostic.getSpan().getFileId().getId());
|
assertEquals(9, diagnostic.getSpan().getFileId().getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
|
|||||||
import p.studio.compiler.models.SourceKind;
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.lexer.LexErrors;
|
import p.studio.compiler.pbs.lexer.LexErrors;
|
||||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
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.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
@ -107,9 +106,9 @@ class PbsFrontendCompilerTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int,
|
pub g: int,
|
||||||
[Slot(index = 2)] pub b: int
|
pub b: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "core.color.pack")]
|
[IntrinsicCall(name = "core.color.pack")]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
@ -136,49 +135,61 @@ class PbsFrontendCompilerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRejectUnsupportedBuiltinSlotInferenceAtLoadAdmissionBoundary() {
|
void shouldInferBuiltinFieldSlotsFromDeclarationOrder() {
|
||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int
|
pub g: int
|
||||||
);
|
) {
|
||||||
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
final var diagnostics = DiagnosticSink.empty();
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
final var compiler = new PbsFrontendCompiler();
|
final var compiler = new PbsFrontendCompiler();
|
||||||
final var fileBackend = compiler.compileFile(new FileId(3), source, diagnostics, SourceKind.SDK_INTERFACE);
|
final var fileBackend = compiler.compileFile(new FileId(3), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
final var rejectionDiagnostic = diagnostics.stream()
|
assertTrue(diagnostics.isEmpty());
|
||||||
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
|
final var fields = fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields();
|
||||||
.findFirst()
|
assertEquals(2, fields.size());
|
||||||
.orElseThrow();
|
assertEquals(0L, fields.get(0).slotStart());
|
||||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
assertEquals(1L, fields.get(1).slotStart());
|
||||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
|
||||||
assertEquals(3, rejectionDiagnostic.getSpan().getFileId().getId());
|
|
||||||
assertEquals(0, fileBackend.functions().size());
|
assertEquals(0, fileBackend.functions().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRejectDuplicateBuiltinSlotIndexesAtLoadAdmissionBoundary() {
|
void shouldInferBuiltinFieldSlotsUsingFlattenedNestedWidth() {
|
||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 0)] pub g: 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 diagnostics = DiagnosticSink.empty();
|
||||||
final var compiler = new PbsFrontendCompiler();
|
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()
|
assertTrue(diagnostics.isEmpty());
|
||||||
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name()))
|
final var pixel = fileBackend.reservedMetadata().builtinTypeSurfaces().stream()
|
||||||
|
.filter(t -> t.sourceTypeName().equals("Pixel"))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
assertEquals(3, pixel.fields().size());
|
||||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
assertEquals(0L, pixel.fields().get(0).slotStart());
|
||||||
assertEquals(1, rejectionDiagnostic.getRelated().size());
|
assertEquals(1L, pixel.fields().get(1).slotStart());
|
||||||
|
assertEquals(2L, pixel.fields().get(2).slotStart());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int
|
pub r: int
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int
|
pub r: int
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,12 +216,12 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void gateU_shouldExposeReservedMetadataAndRejectUnsupportedLoadAdmission() {
|
void gateU_shouldExposeReservedMetadataAndRejectInvalidBuiltinLayoutAtSemantics() {
|
||||||
final var validSource = """
|
final var validSource = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int
|
pub g: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
@ -247,9 +247,10 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
assertEquals(1, validBackend.reservedMetadata().builtinConstSurfaces().size());
|
assertEquals(1, validBackend.reservedMetadata().builtinConstSurfaces().size());
|
||||||
|
|
||||||
final var invalidSource = """
|
final var invalidSource = """
|
||||||
|
declare struct RGBA(raw: int);
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub r: int
|
pub raw: RGBA
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
@ -259,9 +260,12 @@ class PbsGateUSdkInterfaceConformanceTest {
|
|||||||
invalidSource,
|
invalidSource,
|
||||||
invalidDiagnostics,
|
invalidDiagnostics,
|
||||||
SourceKind.SDK_INTERFACE);
|
SourceKind.SDK_INTERFACE);
|
||||||
final var unsupportedLoad = firstDiagnostic(invalidDiagnostics,
|
final var invalidLayout = firstDiagnostic(invalidDiagnostics,
|
||||||
d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()));
|
d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name()));
|
||||||
assertStableDiagnosticIdentity(unsupportedLoad, PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name(), DiagnosticPhase.LOAD_FACING_REJECTION);
|
assertStableDiagnosticIdentity(
|
||||||
|
invalidLayout,
|
||||||
|
PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name(),
|
||||||
|
DiagnosticPhase.STATIC_SEMANTICS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorkspaceCompileResult compileWorkspaceModule(
|
private WorkspaceCompileResult compileWorkspaceModule(
|
||||||
|
|||||||
@ -256,9 +256,9 @@ class PbsModuleVisibilityTest {
|
|||||||
"""
|
"""
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int,
|
pub g: int,
|
||||||
[Slot(index = 2)] pub b: int
|
pub b: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
[IntrinsicCall(name = "core.color.pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
|
|||||||
@ -221,9 +221,9 @@ class PbsParserTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int,
|
pub g: int,
|
||||||
[Slot(index = 2)] pub b: int,
|
pub b: int,
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "color.pack", version = 1)]
|
[IntrinsicCall(name = "color.pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
@ -251,7 +251,7 @@ class PbsParserTest {
|
|||||||
assertEquals(1, builtinType.attributes().size());
|
assertEquals(1, builtinType.attributes().size());
|
||||||
assertEquals(3, builtinType.fields().size());
|
assertEquals(3, builtinType.fields().size());
|
||||||
assertEquals(1, builtinType.signatures().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());
|
assertEquals(1, builtinType.signatures().getFirst().attributes().size());
|
||||||
|
|
||||||
final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1));
|
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");
|
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
|
@Test
|
||||||
void shouldReportEnumMixedAndDuplicateCases() {
|
void shouldReportEnumMixedAndDuplicateCases() {
|
||||||
final var source = """
|
final var source = """
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
[BuiltinType(name = "color", version = 1)]
|
[BuiltinType(name = "color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub raw: int
|
pub raw: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "pack", version = 1)]
|
[IntrinsicCall(name = "pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
@ -98,4 +98,71 @@ class PbsInterfaceModuleSemanticsTest {
|
|||||||
.count();
|
.count();
|
||||||
assertTrue(nonDeclarativeCount >= 3);
|
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())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -237,9 +237,9 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
Files.writeString(sourceFile, """
|
Files.writeString(sourceFile, """
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
[Slot(index = 0)] pub r: int,
|
pub r: int,
|
||||||
[Slot(index = 1)] pub g: int,
|
pub g: int,
|
||||||
[Slot(index = 2)] pub b: int
|
pub b: int
|
||||||
) {
|
) {
|
||||||
[IntrinsicCall(name = "color.pack", version = 1)]
|
[IntrinsicCall(name = "color.pack", version = 1)]
|
||||||
fn pack() -> int;
|
fn pack() -> int;
|
||||||
@ -557,7 +557,7 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldEmitLoadFacingRejectionForUnsupportedStdlibReservedMetadataShape() throws IOException {
|
void shouldEmitStaticSemanticsErrorForInvalidStdlibBuiltinLayoutShape() throws IOException {
|
||||||
final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata");
|
final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata");
|
||||||
final var sourceRoot = projectRoot.resolve("src");
|
final var sourceRoot = projectRoot.resolve("src");
|
||||||
final var modulePath = sourceRoot.resolve("app");
|
final var modulePath = sourceRoot.resolve("app");
|
||||||
@ -589,10 +589,12 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
|
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
|
||||||
"main.pbs",
|
"main.pbs",
|
||||||
"""
|
"""
|
||||||
|
declare struct RGBA(raw: int);
|
||||||
[BuiltinType(name = "Color", version = 1)]
|
[BuiltinType(name = "Color", version = 1)]
|
||||||
declare builtin type Color(
|
declare builtin type Color(
|
||||||
pub r: int
|
pub raw: RGBA
|
||||||
);
|
) {
|
||||||
|
}
|
||||||
"""))),
|
"""))),
|
||||||
"pub struct Color;");
|
"pub struct Color;");
|
||||||
final var frontendService = new PBSFrontendPhaseService(
|
final var frontendService = new PBSFrontendPhaseService(
|
||||||
@ -611,12 +613,12 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
LogAggregator.empty(),
|
LogAggregator.empty(),
|
||||||
BuildingIssueSink.empty());
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
final var rejectionDiagnostic = diagnostics.stream()
|
final var semanticsDiagnostic = diagnostics.stream()
|
||||||
.filter(d -> d.getCode().equals(p.studio.compiler.pbs.PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.STATIC_SEMANTICS, semanticsDiagnostic.getPhase());
|
||||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
assertEquals(semanticsDiagnostic.getCode(), semanticsDiagnostic.getTemplateId());
|
||||||
assertEquals(1, irBackend.getFunctions().size());
|
assertEquals(1, irBackend.getFunctions().size());
|
||||||
assertEquals("run", irBackend.getFunctions().getFirst().name());
|
assertEquals("run", irBackend.getFunctions().getFirst().name());
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user