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.
|
||||
- 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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
||||
? 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.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<IRReservedMetadata.HostMethodBinding>();
|
||||
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
||||
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()) {
|
||||
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<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final Map<String, PbsBuiltinLayoutResolver.ResolvedBuiltinLayout> inferredLayoutsByName,
|
||||
final List<IRReservedMetadata.BuiltinTypeSurface> 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<IRReservedMetadata.BuiltinFieldSurface>(builtinTypeDecl.fields().size());
|
||||
final var firstFieldBySlot = new HashMap<Long, Span>();
|
||||
for (final var field : builtinTypeDecl.fields()) {
|
||||
final var slotAttribute = firstAttributeNamed(field.attributes(), ATTR_SLOT);
|
||||
if (slotAttribute.isEmpty()) {
|
||||
reportUnsupportedSurface(
|
||||
diagnostics,
|
||||
field.span(),
|
||||
"Builtin field '%s' in '%s' requires Slot(index=...) for lowering admission"
|
||||
.formatted(field.name(), builtinTypeDecl.name()));
|
||||
continue;
|
||||
}
|
||||
|
||||
final var slotStart = longArgument(slotAttribute.get(), "index");
|
||||
if (slotStart.isEmpty()) {
|
||||
reportUnsupportedSurface(
|
||||
diagnostics,
|
||||
slotAttribute.get().span(),
|
||||
"Builtin field '%s' in '%s' requires numeric Slot(index=...) for lowering admission"
|
||||
.formatted(field.name(), builtinTypeDecl.name()));
|
||||
continue;
|
||||
}
|
||||
|
||||
final var firstField = firstFieldBySlot.putIfAbsent(slotStart.getAsLong(), field.span());
|
||||
if (firstField != null) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name(),
|
||||
"Duplicate builtin slot index %d in '%s'".formatted(slotStart.getAsLong(), builtinTypeDecl.name()),
|
||||
field.span(),
|
||||
List.of(new RelatedSpan("First builtin field using this slot is here", firstField)));
|
||||
continue;
|
||||
}
|
||||
|
||||
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<PbsAst.Attribute> firstAttributeNamed(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||
final String attributeName) {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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_CONST = "BuiltinConst";
|
||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||
private static final String ATTR_SLOT = "Slot";
|
||||
|
||||
private static final Set<String> 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<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||
final PbsAst.Attribute attribute,
|
||||
final Set<String> requiredNames,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 = """
|
||||
|
||||
@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user