implements PR027
This commit is contained in:
parent
9fb8622ddb
commit
bb5cc05c0b
@ -1,55 +0,0 @@
|
||||
# PR-025 - PBS Interface-Module Semantics and Linking Rules
|
||||
|
||||
## Briefing
|
||||
|
||||
Depois de parsear forms reservadas, o frontend precisa validar regras semanticas especificas de interface modules e integrar host/builtin shells ao linking.
|
||||
|
||||
## Motivation
|
||||
|
||||
Sem essa etapa:
|
||||
|
||||
- declarations reservadas entram sem contrato semantico,
|
||||
- host owners nao entram corretamente no namespace/linking,
|
||||
- e o barrel contract para `host` nao fecha.
|
||||
|
||||
## Target
|
||||
|
||||
- Validadores semanticos (`declaration`, `flow` quando aplicavel).
|
||||
- `PbsNamespaceBinder` e `PbsModuleVisibilityValidator`.
|
||||
|
||||
## Scope
|
||||
|
||||
- Validar restricoes declarativas de interface module (nao executavel, assinatura-only onde requerido).
|
||||
- Validar posicionamento/shape de `Host`, `BuiltinType`, `BuiltinConst`, `IntrinsicCall`, `Slot`.
|
||||
- Incluir host owners em namespace e linking (incluindo barrel host entry resolution).
|
||||
|
||||
## Method
|
||||
|
||||
- Adicionar caminho semantico para `SDK_INTERFACE` com regras de admissao dedicadas.
|
||||
- Reusar estrutura de diagnosticos com codigos estaveis.
|
||||
- Garantir ownership de fase para erros de linking vs static semantics.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `host` barrel entries resolvem quando `declare host` existe no modulo reservado.
|
||||
- Violacoes de posicionamento/shape de attributes reservadas sao rejeitadas deterministicamente.
|
||||
- Modulos ordinarios continuam com as regras atuais e sem regressao.
|
||||
|
||||
## Tests
|
||||
|
||||
- Fixtures positivos de interface module valido com host/builtin declarations.
|
||||
- Fixtures negativos de attributes mal posicionadas e host/builtin invalido.
|
||||
- Fixtures de linking para barrel host item resolvido e nao resolvido.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Materializacao final de metadata no artifact/lowering.
|
||||
- Politica completa de runtime capabilities.
|
||||
|
||||
## Affected Documents
|
||||
|
||||
- `docs/pbs/specs/4. Static Semantics Specification.md`
|
||||
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
|
||||
- `docs/pbs/specs/6.2. Host ABI Binding and Loader Resolution Specification.md`
|
||||
- `docs/pbs/specs/12. Diagnostics Specification.md`
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
# PR-026 - PBS Minimal SDK Bootstrap with Core Color and Gfx Host Surface
|
||||
|
||||
## Briefing
|
||||
|
||||
Precisamos de um SDK minimo funcional para iniciar implementacao de builtins e validar o fluxo interface-module end-to-end.
|
||||
|
||||
Esta PR introduz um pacote minimo de modulos reservados com exemplo `Color` e `Gfx`.
|
||||
|
||||
## Motivation
|
||||
|
||||
Sem SDK minimo, o suporte a interface mode fica sem fixture real de uso.
|
||||
Com ele, conseguimos exercitar import reservado, metadata, linking e base para lowering.
|
||||
|
||||
## Target
|
||||
|
||||
- Conteudo inicial do stdlib environment para linha v1.
|
||||
- Modulos reservados `@core:*` e `@sdk:*` com superfícies declarativas minimas.
|
||||
|
||||
## Scope
|
||||
|
||||
- Criar modulo `@core:color` com shell builtin `Color` (e opcional `Pixel`) como exemplo.
|
||||
- Criar modulo `@sdk:gfx` com `declare host Gfx` e assinatura anotada com `Host(...)`.
|
||||
- Fornecer `mod.barrel` coerente para os modulos criados.
|
||||
|
||||
## Method
|
||||
|
||||
- Publicar fontes `.pbs` de interface module no armazenamento escolhido para stdlib bootstrap (ex.: resources).
|
||||
- Manter tudo declarativo e nao executavel.
|
||||
- Garantir nomes/cases/assinaturas pequenas, estaveis e didaticas para suite inicial.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Import de `Color` via `@core:color` resolve e participa do type namespace.
|
||||
- Import de `Gfx` via `@sdk:gfx` resolve no host-owner namespace.
|
||||
- Modulos do SDK minimo passam parser+semantica+linking de interface-module.
|
||||
|
||||
## Tests
|
||||
|
||||
- Fixture de compilacao que importa `Color` e usa em assinatura/tipo.
|
||||
- Fixture de compilacao que importa `Gfx` e valida superficie host.
|
||||
- Fixtures negativas de barrel inconsistente no SDK minimo.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Cobertura completa de builtins do stdlib.
|
||||
- Qualquer implementacao de comportamento runtime da API grafica.
|
||||
|
||||
## Affected Documents
|
||||
|
||||
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
|
||||
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
|
||||
- `docs/pbs/specs/6.2. Host ABI Binding and Loader Resolution Specification.md`
|
||||
- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md`
|
||||
|
||||
@ -5,6 +5,7 @@ import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.models.IRBackendFile;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
|
||||
import p.studio.compiler.pbs.parser.PbsParser;
|
||||
import p.studio.compiler.pbs.semantics.PbsDeclarationSemanticsValidator;
|
||||
import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator;
|
||||
@ -17,6 +18,7 @@ import java.util.ArrayList;
|
||||
public final class PbsFrontendCompiler {
|
||||
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
|
||||
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
|
||||
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
|
||||
|
||||
public IRBackendFile compileFile(
|
||||
final FileId fileId,
|
||||
@ -61,7 +63,17 @@ public final class PbsFrontendCompiler {
|
||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
return new IRBackendFile(fileId, lowerFunctions(fileId, ast));
|
||||
|
||||
final var admissionErrorBaseline = diagnostics.errorCount();
|
||||
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind, diagnostics);
|
||||
if (diagnostics.errorCount() > admissionErrorBaseline) {
|
||||
return IRBackendFile.empty(fileId);
|
||||
}
|
||||
|
||||
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
|
||||
? ReadOnlyList.empty()
|
||||
: lowerFunctions(fileId, ast);
|
||||
return new IRBackendFile(fileId, functions, reservedMetadata);
|
||||
}
|
||||
|
||||
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
public enum PbsLoadErrors {
|
||||
E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE,
|
||||
E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX,
|
||||
}
|
||||
@ -0,0 +1,260 @@
|
||||
package p.studio.compiler.pbs.metadata;
|
||||
|
||||
import p.studio.compiler.models.IRReservedMetadata;
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.PbsLoadErrors;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.diagnostics.Diagnostics;
|
||||
import p.studio.compiler.source.diagnostics.RelatedSpan;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
public final class PbsReservedMetadataExtractor {
|
||||
private static final String ATTR_HOST = "Host";
|
||||
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||
private static final String ATTR_SLOT = "Slot";
|
||||
|
||||
public IRReservedMetadata extract(
|
||||
final PbsAst.File ast,
|
||||
final SourceKind sourceKind,
|
||||
final DiagnosticSink diagnostics) {
|
||||
if (sourceKind != SourceKind.SDK_INTERFACE) {
|
||||
return IRReservedMetadata.empty();
|
||||
}
|
||||
|
||||
final var hostMethodBindings = new ArrayList<IRReservedMetadata.HostMethodBinding>();
|
||||
final var builtinTypeSurfaces = new ArrayList<IRReservedMetadata.BuiltinTypeSurface>();
|
||||
final var builtinConstSurfaces = new ArrayList<IRReservedMetadata.BuiltinConstSurface>();
|
||||
|
||||
for (final var topDecl : ast.topDecls()) {
|
||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||
extractHostBindings(hostDecl, hostMethodBindings);
|
||||
continue;
|
||||
}
|
||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||
extractBuiltinTypeSurface(builtinTypeDecl, builtinTypeSurfaces, diagnostics);
|
||||
continue;
|
||||
}
|
||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||
extractBuiltinConstSurface(constDecl, builtinConstSurfaces);
|
||||
}
|
||||
}
|
||||
|
||||
return new IRReservedMetadata(
|
||||
ReadOnlyList.wrap(hostMethodBindings),
|
||||
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||
ReadOnlyList.wrap(builtinConstSurfaces));
|
||||
}
|
||||
|
||||
private void extractHostBindings(
|
||||
final PbsAst.HostDecl hostDecl,
|
||||
final List<IRReservedMetadata.HostMethodBinding> hostMethodBindings) {
|
||||
for (final var signature : hostDecl.signatures()) {
|
||||
final var hostAttribute = firstAttributeNamed(signature.attributes(), ATTR_HOST);
|
||||
if (hostAttribute.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final var hostMetadata = hostAttribute.get();
|
||||
final var abiModule = stringArgument(hostMetadata, "module").orElse("");
|
||||
final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name());
|
||||
final var abiVersion = longArgument(hostMetadata, "version").orElse(0L);
|
||||
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
|
||||
hostDecl.name(),
|
||||
signature.name(),
|
||||
abiModule,
|
||||
abiMethod,
|
||||
abiVersion,
|
||||
signature.span()));
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBuiltinTypeSurface(
|
||||
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||
final List<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var builtinTypeAttribute = firstAttributeNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
||||
if (builtinTypeAttribute.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var builtinTypeMetadata = builtinTypeAttribute.get();
|
||||
final var canonicalTypeName = stringArgument(builtinTypeMetadata, "name").orElse(builtinTypeDecl.name());
|
||||
final var canonicalVersion = longArgument(builtinTypeMetadata, "version").orElse(0L);
|
||||
|
||||
final var fields = new ArrayList<IRReservedMetadata.BuiltinFieldSurface>(builtinTypeDecl.fields().size());
|
||||
final var firstFieldBySlot = new HashMap<Long, Span>();
|
||||
for (final var field : builtinTypeDecl.fields()) {
|
||||
final var slotAttribute = firstAttributeNamed(field.attributes(), ATTR_SLOT);
|
||||
if (slotAttribute.isEmpty()) {
|
||||
reportUnsupportedSurface(
|
||||
diagnostics,
|
||||
field.span(),
|
||||
"Builtin field '%s' in '%s' requires Slot(index=...) for lowering admission"
|
||||
.formatted(field.name(), builtinTypeDecl.name()));
|
||||
continue;
|
||||
}
|
||||
|
||||
final var slotStart = longArgument(slotAttribute.get(), "index");
|
||||
if (slotStart.isEmpty()) {
|
||||
reportUnsupportedSurface(
|
||||
diagnostics,
|
||||
slotAttribute.get().span(),
|
||||
"Builtin field '%s' in '%s' requires numeric Slot(index=...) for lowering admission"
|
||||
.formatted(field.name(), builtinTypeDecl.name()));
|
||||
continue;
|
||||
}
|
||||
|
||||
final var firstField = firstFieldBySlot.putIfAbsent(slotStart.getAsLong(), field.span());
|
||||
if (firstField != null) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name(),
|
||||
"Duplicate builtin slot index %d in '%s'".formatted(slotStart.getAsLong(), builtinTypeDecl.name()),
|
||||
field.span(),
|
||||
List.of(new RelatedSpan("First builtin field using this slot is here", firstField)));
|
||||
continue;
|
||||
}
|
||||
|
||||
fields.add(new IRReservedMetadata.BuiltinFieldSurface(
|
||||
field.name(),
|
||||
typeSurfaceKey(field.typeRef()),
|
||||
slotStart.getAsLong(),
|
||||
field.span()));
|
||||
}
|
||||
|
||||
final var intrinsics = new ArrayList<IRReservedMetadata.IntrinsicSurface>(builtinTypeDecl.signatures().size());
|
||||
for (final var signature : builtinTypeDecl.signatures()) {
|
||||
final var intrinsicAttribute = firstAttributeNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
|
||||
if (intrinsicAttribute.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final var intrinsicMetadata = intrinsicAttribute.get();
|
||||
intrinsics.add(new IRReservedMetadata.IntrinsicSurface(
|
||||
signature.name(),
|
||||
stringArgument(intrinsicMetadata, "name").orElse(signature.name()),
|
||||
longArgument(intrinsicMetadata, "version").orElse(canonicalVersion),
|
||||
signature.span()));
|
||||
}
|
||||
|
||||
builtinTypeSurfaces.add(new IRReservedMetadata.BuiltinTypeSurface(
|
||||
builtinTypeDecl.name(),
|
||||
canonicalTypeName,
|
||||
canonicalVersion,
|
||||
ReadOnlyList.wrap(fields),
|
||||
ReadOnlyList.wrap(intrinsics),
|
||||
builtinTypeDecl.span()));
|
||||
}
|
||||
|
||||
private void extractBuiltinConstSurface(
|
||||
final PbsAst.ConstDecl constDecl,
|
||||
final List<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces) {
|
||||
final var builtinConstAttribute = firstAttributeNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
|
||||
if (builtinConstAttribute.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var builtinConstMetadata = builtinConstAttribute.get();
|
||||
builtinConstSurfaces.add(new IRReservedMetadata.BuiltinConstSurface(
|
||||
constDecl.name(),
|
||||
stringArgument(builtinConstMetadata, "target").orElse(""),
|
||||
stringArgument(builtinConstMetadata, "name").orElse(constDecl.name()),
|
||||
longArgument(builtinConstMetadata, "version").orElse(0L),
|
||||
constDecl.span()));
|
||||
}
|
||||
|
||||
private void reportUnsupportedSurface(
|
||||
final DiagnosticSink diagnostics,
|
||||
final Span span,
|
||||
final String message) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name(),
|
||||
message,
|
||||
span);
|
||||
}
|
||||
|
||||
private Optional<PbsAst.Attribute> firstAttributeNamed(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||
final String attributeName) {
|
||||
for (final var attribute : attributes) {
|
||||
if (attributeName.equals(attribute.name())) {
|
||||
return Optional.of(attribute);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<String> stringArgument(
|
||||
final PbsAst.Attribute attribute,
|
||||
final String argumentName) {
|
||||
final var argument = findArgument(attribute, argumentName);
|
||||
if (argument.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (argument.get().value() instanceof PbsAst.AttributeStringValue value) {
|
||||
return Optional.of(normalizeStringLiteralValue(value.value()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private OptionalLong longArgument(
|
||||
final PbsAst.Attribute attribute,
|
||||
final String argumentName) {
|
||||
final var argument = findArgument(attribute, argumentName);
|
||||
if (argument.isEmpty()) {
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
if (argument.get().value() instanceof PbsAst.AttributeIntValue value) {
|
||||
return OptionalLong.of(value.value());
|
||||
}
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
|
||||
private Optional<PbsAst.AttributeArgument> findArgument(
|
||||
final PbsAst.Attribute attribute,
|
||||
final String argumentName) {
|
||||
for (final var argument : attribute.arguments()) {
|
||||
if (argumentName.equals(argument.name())) {
|
||||
return Optional.of(argument);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String normalizeStringLiteralValue(final String rawValue) {
|
||||
if (rawValue == null) {
|
||||
return "";
|
||||
}
|
||||
if (rawValue.length() >= 2 && rawValue.startsWith("\"") && rawValue.endsWith("\"")) {
|
||||
return rawValue.substring(1, rawValue.length() - 1);
|
||||
}
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
|
||||
if (typeRef == null) {
|
||||
return "unit";
|
||||
}
|
||||
return switch (typeRef.kind()) {
|
||||
case SIMPLE -> "simple:" + typeRef.name();
|
||||
case SELF -> "self";
|
||||
case OPTIONAL -> "optional(" + typeSurfaceKey(typeRef.inner()) + ")";
|
||||
case UNIT -> "unit";
|
||||
case GROUP -> "group(" + typeSurfaceKey(typeRef.inner()) + ")";
|
||||
case NAMED_TUPLE -> "tuple(" + typeRef.fields().stream()
|
||||
.map(field -> typeSurfaceKey(field.typeRef()))
|
||||
.reduce((left, right) -> left + "," + right)
|
||||
.orElse("") + ")";
|
||||
case ERROR -> "error";
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -116,7 +116,12 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
}
|
||||
}
|
||||
|
||||
loadReservedStdlibModules(modulesByCoordinates, diagnostics, ctx.stdlibVersion());
|
||||
loadReservedStdlibModules(
|
||||
modulesByCoordinates,
|
||||
parsedSourceFiles,
|
||||
moduleKeyByFile,
|
||||
diagnostics,
|
||||
ctx.stdlibVersion());
|
||||
|
||||
final var modules = new ArrayList<PbsModuleVisibilityValidator.ModuleUnit>(modulesByCoordinates.size());
|
||||
for (final var entry : modulesByCoordinates.entrySet()) {
|
||||
@ -130,19 +135,28 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), diagnostics);
|
||||
markModulesWithLinkingErrors(diagnostics, moduleKeyByFile, failedModuleKeys);
|
||||
|
||||
final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size());
|
||||
for (final var parsedSource : parsedSourceFiles) {
|
||||
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
|
||||
continue;
|
||||
}
|
||||
final var compileErrorBaseline = diagnostics.errorCount();
|
||||
final var irBackendFile = frontendCompiler.compileParsedFile(
|
||||
parsedSource.fileId(),
|
||||
parsedSource.ast(),
|
||||
diagnostics,
|
||||
parsedSource.sourceKind());
|
||||
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
||||
if (diagnostics.errorCount() > compileErrorBaseline) {
|
||||
failedModuleKeys.add(parsedSource.moduleKey());
|
||||
}
|
||||
compiledSourceFiles.add(new CompiledSourceFile(parsedSource.moduleKey(), irBackendFile));
|
||||
}
|
||||
|
||||
for (final var compiledSource : compiledSourceFiles) {
|
||||
if (failedModuleKeys.contains(compiledSource.moduleKey())) {
|
||||
continue;
|
||||
}
|
||||
irBackendAggregator.merge(irBackendFile);
|
||||
irBackendAggregator.merge(compiledSource.irBackendFile());
|
||||
}
|
||||
|
||||
final var irBackend = irBackendAggregator.emit();
|
||||
@ -152,6 +166,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
|
||||
private void loadReservedStdlibModules(
|
||||
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates,
|
||||
final ArrayList<ParsedSourceFile> parsedSourceFiles,
|
||||
final Map<FileId, String> moduleKeyByFile,
|
||||
final DiagnosticSink diagnostics,
|
||||
final int stdlibVersion) {
|
||||
final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion);
|
||||
@ -179,6 +195,17 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
moduleData.sources.addAll(loadedModule.sourceFiles().asList());
|
||||
moduleData.barrels.addAll(loadedModule.barrelFiles().asList());
|
||||
modulesByCoordinates.put(loadedModule.coordinates(), moduleData);
|
||||
for (final var sourceFile : loadedModule.sourceFiles()) {
|
||||
moduleKeyByFile.put(sourceFile.fileId(), targetKey);
|
||||
parsedSourceFiles.add(new ParsedSourceFile(
|
||||
sourceFile.fileId(),
|
||||
sourceFile.ast(),
|
||||
targetKey,
|
||||
SourceKind.SDK_INTERFACE));
|
||||
}
|
||||
for (final var barrelFile : loadedModule.barrelFiles()) {
|
||||
moduleKeyByFile.put(barrelFile.fileId(), targetKey);
|
||||
}
|
||||
enqueueReservedImportsFromSourceFiles(loadedModule.sourceFiles().asList(), pending);
|
||||
}
|
||||
}
|
||||
@ -293,4 +320,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||
String moduleKey,
|
||||
SourceKind sourceKind) {
|
||||
}
|
||||
|
||||
private record CompiledSourceFile(
|
||||
String moduleKey,
|
||||
p.studio.compiler.models.IRBackendFile irBackendFile) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,4 +80,25 @@ class PbsDiagnosticsContractTest {
|
||||
assertEquals(DiagnosticPhase.LINKING, diagnostic.getPhase());
|
||||
assertEquals(1, diagnostic.getRelated().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTagLoadFacingDiagnosticsWithStableTemplateAndAttribution() {
|
||||
final var source = """
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
declare builtin type Color(
|
||||
pub r: int
|
||||
);
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(9), source, diagnostics, p.studio.compiler.models.SourceKind.SDK_INTERFACE);
|
||||
|
||||
final var diagnostic = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, diagnostic.getPhase());
|
||||
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
|
||||
assertEquals(9, diagnostic.getSpan().getFileId().getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.lexer.LexErrors;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
|
||||
@ -99,4 +101,84 @@ class PbsFrontendCompilerTest {
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())));
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractReservedMetadataForInterfaceModuleSurfaces() {
|
||||
final var source = """
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
declare builtin type Color(
|
||||
[Slot(index = 0)] pub r: int,
|
||||
[Slot(index = 1)] pub g: int,
|
||||
[Slot(index = 2)] pub b: int
|
||||
) {
|
||||
[IntrinsicCall(name = "core.color.pack")]
|
||||
fn pack() -> int;
|
||||
}
|
||||
declare host Gfx {
|
||||
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||
fn draw_pixel(x: int, y: int, color: Color) -> void;
|
||||
}
|
||||
[BuiltinConst(target = "Color", name = "white", version = 1)]
|
||||
declare const WHITE: Color;
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||
|
||||
assertTrue(diagnostics.isEmpty());
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size());
|
||||
assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size());
|
||||
assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size());
|
||||
assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName());
|
||||
assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUnsupportedBuiltinSlotInferenceAtLoadAdmissionBoundary() {
|
||||
final var source = """
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
declare builtin type Color(
|
||||
pub r: int,
|
||||
[Slot(index = 1)] pub g: int
|
||||
);
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
final var fileBackend = compiler.compileFile(new FileId(3), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||
|
||||
final var rejectionDiagnostic = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
||||
assertEquals(3, rejectionDiagnostic.getSpan().getFileId().getId());
|
||||
assertEquals(0, fileBackend.functions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectDuplicateBuiltinSlotIndexesAtLoadAdmissionBoundary() {
|
||||
final var source = """
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
declare builtin type Color(
|
||||
[Slot(index = 0)] pub r: int,
|
||||
[Slot(index = 0)] pub g: int
|
||||
);
|
||||
""";
|
||||
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var compiler = new PbsFrontendCompiler();
|
||||
compiler.compileFile(new FileId(5), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||
|
||||
final var rejectionDiagnostic = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsLoadErrors.E_LOAD_DUPLICATE_BUILTIN_SLOT_INDEX.name()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals(DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
||||
assertEquals(1, rejectionDiagnostic.getRelated().size());
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,6 +437,8 @@ class PBSFrontendPhaseServiceTest {
|
||||
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
|
||||
assertEquals(0, irBackend.getFunctions().size());
|
||||
assertTrue(irBackend.getReservedMetadata().builtinTypeSurfaces().stream()
|
||||
.anyMatch(t -> t.sourceTypeName().equals("Color") && t.fields().size() == 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -488,6 +490,8 @@ class PBSFrontendPhaseServiceTest {
|
||||
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
|
||||
assertEquals(0, irBackend.getFunctions().size());
|
||||
assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
|
||||
.anyMatch(h -> h.ownerName().equals("Gfx") && h.sourceMethodName().equals("draw_pixel")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -552,6 +556,71 @@ class PBSFrontendPhaseServiceTest {
|
||||
assertEquals(0, irBackend.getFunctions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEmitLoadFacingRejectionForUnsupportedStdlibReservedMetadataShape() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
final var modulePath = sourceRoot.resolve("app");
|
||||
Files.createDirectories(modulePath);
|
||||
|
||||
final var sourceFile = modulePath.resolve("source.pbs");
|
||||
final var modBarrel = modulePath.resolve("mod.barrel");
|
||||
Files.writeString(sourceFile, """
|
||||
import { Color } from @core:color;
|
||||
fn run() -> int { return 1; }
|
||||
""");
|
||||
Files.writeString(modBarrel, "pub fn run() -> int;");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("app")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, sourceFile, fileTable);
|
||||
registerFile(projectId, projectRoot, modBarrel, fileTable);
|
||||
|
||||
final var invalidColorModule = new StdlibModuleSource(
|
||||
"core",
|
||||
ReadOnlyList.wrap(List.of("color")),
|
||||
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
|
||||
"main.pbs",
|
||||
"""
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
declare builtin type Color(
|
||||
pub r: int
|
||||
);
|
||||
"""))),
|
||||
"pub struct Color;");
|
||||
final var frontendService = new PBSFrontendPhaseService(
|
||||
resolverForMajor(1, invalidColorModule),
|
||||
new InterfaceModuleLoader(2_700_000));
|
||||
final var ctx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||
1);
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
final var irBackend = frontendService.compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
final var rejectionDiagnostic = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(p.studio.compiler.pbs.PbsLoadErrors.E_LOAD_UNSUPPORTED_RESERVED_METADATA_SURFACE.name()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.LOAD_FACING_REJECTION, rejectionDiagnostic.getPhase());
|
||||
assertEquals(rejectionDiagnostic.getCode(), rejectionDiagnostic.getTemplateId());
|
||||
assertEquals(1, irBackend.getFunctions().size());
|
||||
assertEquals("run", irBackend.getFunctions().getFirst().name());
|
||||
}
|
||||
|
||||
private void registerFile(
|
||||
final ProjectId projectId,
|
||||
final Path projectRoot,
|
||||
|
||||
@ -2,33 +2,47 @@ package p.studio.compiler.models;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import p.studio.utilities.structures.MutableList;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
public class IRBackend {
|
||||
@Builder.Default
|
||||
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
|
||||
@Builder.Default
|
||||
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
|
||||
|
||||
public static IRBackendAggregator aggregator() {
|
||||
return new IRBackendAggregator();
|
||||
}
|
||||
|
||||
public static final class IRBackendAggregator {
|
||||
private final MutableList<IRFunction> functions = MutableList.create();
|
||||
private final ArrayList<IRFunction> functions = new ArrayList<>();
|
||||
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
|
||||
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
|
||||
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
|
||||
|
||||
public void merge(final IRBackendFile backendFile) {
|
||||
if (backendFile == null) {
|
||||
return;
|
||||
}
|
||||
functions.addAll(backendFile.functions());
|
||||
functions.addAll(backendFile.functions().asList());
|
||||
final var metadata = backendFile.reservedMetadata();
|
||||
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
|
||||
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
|
||||
builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList());
|
||||
}
|
||||
|
||||
public IRBackend emit() {
|
||||
return IRBackend
|
||||
.builder()
|
||||
.functions(functions.toReadOnlyList())
|
||||
.functions(ReadOnlyList.wrap(functions))
|
||||
.reservedMetadata(new IRReservedMetadata(
|
||||
ReadOnlyList.wrap(hostMethodBindings),
|
||||
ReadOnlyList.wrap(builtinTypeSurfaces),
|
||||
ReadOnlyList.wrap(builtinConstSurfaces)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -36,7 +50,11 @@ public class IRBackend {
|
||||
@Override
|
||||
public String toString() {
|
||||
final var sb = new StringBuilder();
|
||||
sb.append("IRBackend{functions=").append(functions.size()).append('}');
|
||||
sb.append("IRBackend{functions=").append(functions.size())
|
||||
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
|
||||
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
|
||||
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
|
||||
.append('}');
|
||||
|
||||
if (functions.isEmpty()) {
|
||||
return sb.toString();
|
||||
|
||||
@ -7,13 +7,21 @@ import java.util.Objects;
|
||||
|
||||
public record IRBackendFile(
|
||||
FileId fileId,
|
||||
ReadOnlyList<IRFunction> functions) {
|
||||
ReadOnlyList<IRFunction> functions,
|
||||
IRReservedMetadata reservedMetadata) {
|
||||
public IRBackendFile {
|
||||
fileId = Objects.requireNonNull(fileId, "fileId");
|
||||
functions = functions == null ? ReadOnlyList.empty() : functions;
|
||||
reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata;
|
||||
}
|
||||
|
||||
public IRBackendFile(
|
||||
final FileId fileId,
|
||||
final ReadOnlyList<IRFunction> functions) {
|
||||
this(fileId, functions, IRReservedMetadata.empty());
|
||||
}
|
||||
|
||||
public static IRBackendFile empty(final FileId fileId) {
|
||||
return new IRBackendFile(fileId, ReadOnlyList.empty());
|
||||
return new IRBackendFile(fileId, ReadOnlyList.empty(), IRReservedMetadata.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
package p.studio.compiler.models;
|
||||
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record IRReservedMetadata(
|
||||
ReadOnlyList<HostMethodBinding> hostMethodBindings,
|
||||
ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
|
||||
ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces) {
|
||||
|
||||
public IRReservedMetadata {
|
||||
hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
|
||||
builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
|
||||
builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
|
||||
}
|
||||
|
||||
public static IRReservedMetadata empty() {
|
||||
return new IRReservedMetadata(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty());
|
||||
}
|
||||
|
||||
public record HostMethodBinding(
|
||||
String ownerName,
|
||||
String sourceMethodName,
|
||||
String abiModule,
|
||||
String abiMethod,
|
||||
long abiVersion,
|
||||
Span span) {
|
||||
public HostMethodBinding {
|
||||
ownerName = Objects.requireNonNull(ownerName, "ownerName");
|
||||
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
|
||||
abiModule = Objects.requireNonNull(abiModule, "abiModule");
|
||||
abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
|
||||
span = Objects.requireNonNull(span, "span");
|
||||
}
|
||||
}
|
||||
|
||||
public record BuiltinTypeSurface(
|
||||
String sourceTypeName,
|
||||
String canonicalTypeName,
|
||||
long canonicalVersion,
|
||||
ReadOnlyList<BuiltinFieldSurface> fields,
|
||||
ReadOnlyList<IntrinsicSurface> intrinsics,
|
||||
Span span) {
|
||||
public BuiltinTypeSurface {
|
||||
sourceTypeName = Objects.requireNonNull(sourceTypeName, "sourceTypeName");
|
||||
canonicalTypeName = Objects.requireNonNull(canonicalTypeName, "canonicalTypeName");
|
||||
fields = fields == null ? ReadOnlyList.empty() : fields;
|
||||
intrinsics = intrinsics == null ? ReadOnlyList.empty() : intrinsics;
|
||||
span = Objects.requireNonNull(span, "span");
|
||||
}
|
||||
}
|
||||
|
||||
public record BuiltinFieldSurface(
|
||||
String fieldName,
|
||||
String declaredTypeSurface,
|
||||
long slotStart,
|
||||
Span span) {
|
||||
public BuiltinFieldSurface {
|
||||
fieldName = Objects.requireNonNull(fieldName, "fieldName");
|
||||
declaredTypeSurface = Objects.requireNonNull(declaredTypeSurface, "declaredTypeSurface");
|
||||
span = Objects.requireNonNull(span, "span");
|
||||
}
|
||||
}
|
||||
|
||||
public record IntrinsicSurface(
|
||||
String sourceMethodName,
|
||||
String canonicalName,
|
||||
long canonicalVersion,
|
||||
Span span) {
|
||||
public IntrinsicSurface {
|
||||
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
|
||||
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
|
||||
span = Objects.requireNonNull(span, "span");
|
||||
}
|
||||
}
|
||||
|
||||
public record BuiltinConstSurface(
|
||||
String sourceConstName,
|
||||
String canonicalTarget,
|
||||
String canonicalName,
|
||||
long canonicalVersion,
|
||||
Span span) {
|
||||
public BuiltinConstSurface {
|
||||
sourceConstName = Objects.requireNonNull(sourceConstName, "sourceConstName");
|
||||
canonicalTarget = Objects.requireNonNull(canonicalTarget, "canonicalTarget");
|
||||
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
|
||||
span = Objects.requireNonNull(span, "span");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user