implements PR030

This commit is contained in:
bQUARKz 2026-03-06 15:55:17 +00:00
parent 01c5b6649a
commit 495104db6d
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
23 changed files with 461 additions and 11 deletions

View File

@ -179,6 +179,8 @@ At minimum, the PBS diagnostics baseline must cover:
5. malformed, unauthorized, or capability-rejected host usage required by `6.2. Host ABI Binding and Loader Resolution Specification.md` and `7. Cartridge Manifest and Runtime Capabilities Specification.md`, 5. malformed, unauthorized, or capability-rejected host usage required by `6.2. Host ABI Binding and Loader Resolution Specification.md` and `7. Cartridge Manifest and Runtime Capabilities Specification.md`,
6. source-attributable backend-originated failures that remain user-actionable under normative lowering or load-facing rules. 6. source-attributable backend-originated failures that remain user-actionable under normative lowering or load-facing rules.
At minimum, host-admission diagnostics must cover missing or malformed host capability metadata and unknown or undeclared capability names.
Only backend-originated failures that remain source-attributable and user-actionable belong to the PBS-facing diagnostics contract. Only backend-originated failures that remain source-attributable and user-actionable belong to the PBS-facing diagnostics contract.
This means: This means:

View File

@ -75,6 +75,7 @@ For each admitted source unit and callable in the current lowering slice, `IRBac
3. declared return surface information, 3. declared return surface information,
4. source attribution anchor (`file + span`) for diagnostics and traceability, 4. source attribution anchor (`file + span`) for diagnostics and traceability,
5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape). 5. source-observable parse intent for statement/expression structure (including precedence/associativity outcome already fixed by AST shape).
6. deterministic `requiredCapabilities` derived from admitted host-binding metadata for packer/runtime-manifest assistance.
Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions. Lowering must not collapse source categories in a way that erases required declaration/callable identity needed by downstream diagnostics or conformance assertions.

View File

@ -62,8 +62,9 @@ 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`, and `IntrinsicCall`. - In v1 core, the normative reserved attributes are `Host`, `Capability`, `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.
- `Capability` 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.
- The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification. - The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
@ -168,9 +169,12 @@ Rules:
- Any payload-less `optional` type surface is statically invalid. - Any payload-less `optional` type surface is statically invalid.
- Any `optional void` type surface is statically invalid. - Any `optional void` type surface is statically invalid.
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute. - A host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` attribute.
- A host method signature in a reserved stdlib/interface module MUST carry exactly one `Capability` attribute.
- A `Host` attribute MUST declare exactly the named arguments `module`, `name`, and `version`. - A `Host` attribute MUST declare exactly the named arguments `module`, `name`, and `version`.
- `Host.module` and `Host.name` MUST be non-empty string literals. - `Host.module` and `Host.name` MUST be non-empty string literals.
- `Host.version` MUST be a positive integer literal. - `Host.version` MUST be a positive integer literal.
- A `Capability` attribute MUST declare exactly the named argument `name`.
- `Capability.name` MUST be a non-empty lowercase capability identifier.
- Two host methods in the same resolved stdlib environment MUST NOT lower to the same canonical `(module, name, version)` unless they denote the same host method declaration after project/module resolution. - Two host methods in the same resolved stdlib environment MUST NOT lower to the same canonical `(module, name, version)` unless they denote the same host method declaration after project/module resolution.
- A `declare const` declaration must include an explicit type annotation. - A `declare const` declaration must include an explicit type annotation.
- A `declare const` declaration without `BuiltinConst` metadata must include an initializer. - A `declare const` declaration without `BuiltinConst` metadata must include an initializer.
@ -655,6 +659,7 @@ At minimum, deterministic static diagnostics are required for:
- invalid host method declaration shape, - invalid host method declaration shape,
- invalid attribute surface, - invalid attribute surface,
- invalid `Host` attribute target, - invalid `Host` attribute target,
- invalid `Capability` attribute target,
- invalid `BuiltinType` attribute target, - invalid `BuiltinType` attribute target,
- invalid `BuiltinConst` attribute target, - invalid `BuiltinConst` attribute target,
- invalid `IntrinsicCall` attribute target, - invalid `IntrinsicCall` attribute target,
@ -663,6 +668,8 @@ At minimum, deterministic static diagnostics are required for:
- malformed `Host(module=..., name=..., version=...)` argument set, - malformed `Host(module=..., name=..., version=...)` argument set,
- invalid empty `Host.module` or `Host.name`, - invalid empty `Host.module` or `Host.name`,
- invalid non-positive `Host.version`, - invalid non-positive `Host.version`,
- malformed `Capability(name=...)` argument set,
- invalid empty or non-canonical `Capability.name`,
- invalid builtin declaration shape, - invalid builtin declaration shape,
- invalid builtin method declaration shape, - invalid builtin method declaration shape,
- missing required `BuiltinType` attribute on a reserved builtin declaration, - missing required `BuiltinType` attribute on a reserved builtin declaration,

View File

@ -118,6 +118,7 @@ The source-level call may originate from an SDK declaration such as:
```pbs ```pbs
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, c: color); fn draw_pixel(x: int, y: int, c: color);
} }
``` ```
@ -287,6 +288,7 @@ Capability gating is mandatory during load.
Rules: Rules:
- every resolved host binding declares required capabilities, - every resolved host binding declares required capabilities,
- frontend/build host-admission computes deterministic `requiredCapabilities` from host binding metadata for packer assistance,
- the cartridge manifest declares requested capabilities using a nominal capability list, - the cartridge manifest declares requested capabilities using a nominal capability list,
- the loader derives or receives the granted capability set from the cartridge/platform policy environment, - the loader derives or receives the granted capability set from the cartridge/platform policy environment,
- if a required capability is not granted, load fails, - if a required capability is not granted, load fails,

View File

@ -181,6 +181,7 @@ Compiler:
- compiles source to PBX, - compiles source to PBX,
- emits `SYSC`, - emits `SYSC`,
- emits `HOSTCALL <sysc_index>`, - emits `HOSTCALL <sysc_index>`,
- computes deterministic `requiredCapabilities` from admitted host bindings for packer tooling,
- does not grant authority. - does not grant authority.
Packer: Packer:

View File

@ -3,6 +3,8 @@ package p.studio.compiler.pbs;
import p.studio.compiler.models.IRFunction; import p.studio.compiler.models.IRFunction;
import p.studio.compiler.models.SourceKind; import p.studio.compiler.models.SourceKind;
import p.studio.compiler.models.IRBackendFile; import p.studio.compiler.models.IRBackendFile;
import p.studio.compiler.messages.HostAdmissionContext;
import p.studio.compiler.models.IRReservedMetadata;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer; import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor; import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor;
@ -19,6 +21,7 @@ public final class PbsFrontendCompiler {
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator(); private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator(); private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator();
private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor(); private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor();
private final PbsHostAdmissionValidator hostAdmissionValidator = new PbsHostAdmissionValidator();
public IRBackendFile compileFile( public IRBackendFile compileFile(
final FileId fileId, final FileId fileId,
@ -32,13 +35,22 @@ public final class PbsFrontendCompiler {
final String source, final String source,
final DiagnosticSink diagnostics, final DiagnosticSink diagnostics,
final SourceKind sourceKind) { final SourceKind sourceKind) {
return compileFile(fileId, source, diagnostics, sourceKind, HostAdmissionContext.permissiveDefault());
}
public IRBackendFile compileFile(
final FileId fileId,
final String source,
final DiagnosticSink diagnostics,
final SourceKind sourceKind,
final HostAdmissionContext hostAdmissionContext) {
final var admissionBaseline = diagnostics.errorCount(); final var admissionBaseline = diagnostics.errorCount();
final var tokens = PbsLexer.lex(source, fileId, diagnostics); final var tokens = PbsLexer.lex(source, fileId, diagnostics);
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
? PbsParser.ParseMode.INTERFACE_MODULE ? PbsParser.ParseMode.INTERFACE_MODULE
: PbsParser.ParseMode.ORDINARY; : PbsParser.ParseMode.ORDINARY;
final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode); final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode);
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind); final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind, hostAdmissionContext);
if (diagnostics.errorCount() > admissionBaseline) { if (diagnostics.errorCount() > admissionBaseline) {
return IRBackendFile.empty(fileId); return IRBackendFile.empty(fileId);
} }
@ -57,6 +69,15 @@ public final class PbsFrontendCompiler {
final PbsAst.File ast, final PbsAst.File ast,
final DiagnosticSink diagnostics, final DiagnosticSink diagnostics,
final SourceKind sourceKind) { final SourceKind sourceKind) {
return compileParsedFile(fileId, ast, diagnostics, sourceKind, HostAdmissionContext.permissiveDefault());
}
public IRBackendFile compileParsedFile(
final FileId fileId,
final PbsAst.File ast,
final DiagnosticSink diagnostics,
final SourceKind sourceKind,
final HostAdmissionContext hostAdmissionContext) {
final var semanticsErrorBaseline = diagnostics.errorCount(); final var semanticsErrorBaseline = diagnostics.errorCount();
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics); declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
flowSemanticsValidator.validate(ast, diagnostics); flowSemanticsValidator.validate(ast, diagnostics);
@ -64,7 +85,20 @@ public final class PbsFrontendCompiler {
return IRBackendFile.empty(fileId); return IRBackendFile.empty(fileId);
} }
final var reservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind); final var extractedReservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind);
final var hostAdmissionErrorBaseline = diagnostics.errorCount();
final var requiredCapabilities = hostAdmissionValidator.validate(
extractedReservedMetadata,
hostAdmissionContext,
diagnostics);
if (diagnostics.errorCount() > hostAdmissionErrorBaseline) {
return IRBackendFile.empty(fileId);
}
final var reservedMetadata = new IRReservedMetadata(
extractedReservedMetadata.hostMethodBindings(),
extractedReservedMetadata.builtinTypeSurfaces(),
extractedReservedMetadata.builtinConstSurfaces(),
requiredCapabilities);
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty() ? ReadOnlyList.empty()

View File

@ -0,0 +1,9 @@
package p.studio.compiler.pbs;
public enum PbsHostAdmissionErrors {
E_HOST_MISSING_REQUIRED_CAPABILITY,
E_HOST_MALFORMED_CAPABILITY_METADATA,
E_HOST_UNKNOWN_CAPABILITY,
E_HOST_CAPABILITY_NOT_DECLARED,
E_HOST_INCONSISTENT_BINDING_CAPABILITY,
}

View File

@ -0,0 +1,129 @@
package p.studio.compiler.pbs;
import p.studio.compiler.messages.HostAdmissionContext;
import p.studio.compiler.models.IRReservedMetadata;
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.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
public final class PbsHostAdmissionValidator {
private static final Pattern CAPABILITY_NAME = Pattern.compile("^[a-z][a-z0-9_]*$");
public ReadOnlyList<String> validate(
final IRReservedMetadata metadata,
final HostAdmissionContext context,
final DiagnosticSink diagnostics) {
final var knownCapabilities = normalizedSet(context.knownCapabilities());
final var declaredCapabilities = normalizedSet(context.declaredCapabilities());
final var requiredCapabilities = new LinkedHashSet<String>();
final var firstCapabilityByHostBinding = new HashMap<String, FirstBindingCapability>();
for (final var hostBinding : metadata.hostMethodBindings()) {
final var normalizedCapability = normalize(hostBinding.requiredCapability());
if (!hostBinding.capabilityDeclared()) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
"Host binding '%s.%s' requires Capability(name=...) metadata"
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
hostBinding.span());
continue;
}
if (normalizedCapability.isBlank()) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
"Host binding '%s.%s' has malformed Capability(name=...) metadata"
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
hostBinding.span());
continue;
}
if (!CAPABILITY_NAME.matcher(normalizedCapability).matches()) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
"Host binding '%s.%s' has malformed capability name '%s'"
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
hostBinding.span());
continue;
}
if (!knownCapabilities.contains(normalizedCapability)) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name(),
"Host binding '%s.%s' references unknown capability '%s'"
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
hostBinding.span());
continue;
}
final var bindingKey = "%s|%s|%d".formatted(
hostBinding.abiModule(),
hostBinding.abiMethod(),
hostBinding.abiVersion());
final var firstBindingCapability = firstCapabilityByHostBinding.putIfAbsent(
bindingKey,
new FirstBindingCapability(normalizedCapability, hostBinding.span()));
if (firstBindingCapability != null && !firstBindingCapability.capability().equals(normalizedCapability)) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_INCONSISTENT_BINDING_CAPABILITY.name(),
"Canonical host binding (%s, %s, %d) has inconsistent capability metadata ('%s' vs '%s')"
.formatted(
hostBinding.abiModule(),
hostBinding.abiMethod(),
hostBinding.abiVersion(),
firstBindingCapability.capability(),
normalizedCapability),
hostBinding.span(),
List.of(new RelatedSpan("First capability declaration for this binding is here", firstBindingCapability.span())));
continue;
}
if (context.enforceDeclaredCapabilities() && !declaredCapabilities.contains(normalizedCapability)) {
Diagnostics.error(
diagnostics,
PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name(),
"Capability '%s' required by host binding '%s.%s' is not declared in host admission context"
.formatted(normalizedCapability, hostBinding.ownerName(), hostBinding.sourceMethodName()),
hostBinding.span());
continue;
}
requiredCapabilities.add(normalizedCapability);
}
return ReadOnlyList.wrap(requiredCapabilities.stream().toList());
}
private Set<String> normalizedSet(final ReadOnlyList<String> values) {
final var normalized = new HashSet<String>();
for (final var value : values) {
final var capability = normalize(value);
if (!capability.isBlank()) {
normalized.add(capability);
}
}
return normalized;
}
private String normalize(final String capability) {
return capability == null ? "" : capability.trim().toLowerCase(Locale.ROOT);
}
private record FirstBindingCapability(
String capability,
p.studio.compiler.source.Span span) {
}
}

View File

@ -7,6 +7,7 @@ import p.studio.compiler.pbs.semantics.PbsBuiltinLayoutResolver;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -15,6 +16,7 @@ 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_CAPABILITY = "Capability";
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";
@ -51,7 +53,8 @@ public final class PbsReservedMetadataExtractor {
return new IRReservedMetadata( return new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings), ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces), ReadOnlyList.wrap(builtinTypeSurfaces),
ReadOnlyList.wrap(builtinConstSurfaces)); ReadOnlyList.wrap(builtinConstSurfaces),
requiredCapabilities(hostMethodBindings));
} }
private void extractHostBindings( private void extractHostBindings(
@ -66,12 +69,18 @@ public final class PbsReservedMetadataExtractor {
final var abiModule = stringArgument(hostMetadata, "module").orElse(""); final var abiModule = stringArgument(hostMetadata, "module").orElse("");
final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name()); final var abiMethod = stringArgument(hostMetadata, "name").orElse(signature.name());
final var abiVersion = longArgument(hostMetadata, "version").orElse(0L); final var abiVersion = longArgument(hostMetadata, "version").orElse(0L);
final var capabilityAttribute = firstAttributeNamed(signature.attributes(), ATTR_CAPABILITY);
final var capability = capabilityAttribute
.flatMap(attr -> stringArgument(attr, "name"))
.orElse("");
hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding( hostMethodBindings.add(new IRReservedMetadata.HostMethodBinding(
hostDecl.name(), hostDecl.name(),
signature.name(), signature.name(),
abiModule, abiModule,
abiMethod, abiMethod,
abiVersion, abiVersion,
capabilityAttribute.isPresent(),
capability,
signature.span())); signature.span()));
} }
} }
@ -218,4 +227,16 @@ public final class PbsReservedMetadataExtractor {
case ERROR -> "error"; case ERROR -> "error";
}; };
} }
private ReadOnlyList<String> requiredCapabilities(
final List<IRReservedMetadata.HostMethodBinding> hostMethodBindings) {
final var required = new LinkedHashSet<String>();
for (final var hostBinding : hostMethodBindings) {
if (hostBinding.requiredCapability() == null || hostBinding.requiredCapability().isBlank()) {
continue;
}
required.add(hostBinding.requiredCapability().trim().toLowerCase());
}
return ReadOnlyList.wrap(required.stream().toList());
}
} }

View File

@ -16,12 +16,14 @@ import java.util.Set;
public final class PbsDeclarationSemanticsValidator { public final class PbsDeclarationSemanticsValidator {
private static final String ATTR_HOST = "Host"; private static final String ATTR_HOST = "Host";
private static final String ATTR_CAPABILITY = "Capability";
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 Set<String> RESERVED_ATTRIBUTES = Set.of( private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
ATTR_HOST, ATTR_HOST,
ATTR_CAPABILITY,
ATTR_BUILTIN_TYPE, ATTR_BUILTIN_TYPE,
ATTR_BUILTIN_CONST, ATTR_BUILTIN_CONST,
ATTR_INTRINSIC_CALL); ATTR_INTRINSIC_CALL);
@ -386,7 +388,7 @@ public final class PbsDeclarationSemanticsValidator {
if (!isReservedAttribute(attribute.name())) { if (!isReservedAttribute(attribute.name())) {
continue; continue;
} }
if (ATTR_HOST.equals(attribute.name())) { if (ATTR_HOST.equals(attribute.name()) || ATTR_CAPABILITY.equals(attribute.name())) {
continue; continue;
} }
reportInvalidReservedAttributeTarget( reportInvalidReservedAttributeTarget(

View File

@ -145,7 +145,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
parsedSource.fileId(), parsedSource.fileId(),
parsedSource.ast(), parsedSource.ast(),
diagnostics, diagnostics,
parsedSource.sourceKind()); parsedSource.sourceKind(),
ctx.hostAdmissionContext());
if (diagnostics.errorCount() > compileErrorBaseline) { if (diagnostics.errorCount() > compileErrorBaseline) {
failedModuleKeys.add(parsedSource.moduleKey()); failedModuleKeys.add(parsedSource.moduleKey());
} }

View File

@ -2,5 +2,6 @@ import { Color } from @core:color;
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, color: Color) -> void; fn draw_pixel(x: int, y: int, color: Color) -> void;
} }

View File

@ -103,4 +103,29 @@ class PbsDiagnosticsContractTest {
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId()); assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
assertEquals(9, diagnostic.getSpan().getFileId().getId()); assertEquals(9, diagnostic.getSpan().getFileId().getId());
} }
@Test
void shouldTagHostAdmissionDiagnosticsWithHostAdmissionPhase() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(
new FileId(10),
source,
diagnostics,
p.studio.compiler.models.SourceKind.SDK_INTERFACE);
final var diagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.HOST_ADMISSION, diagnostic.getPhase());
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
assertEquals(10, diagnostic.getSpan().getFileId().getId());
}
} }

View File

@ -1,11 +1,14 @@
package p.studio.compiler.pbs; package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.HostAdmissionContext;
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;
import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -115,6 +118,7 @@ class PbsFrontendCompilerTest {
} }
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, color: Color) -> void; fn draw_pixel(x: int, y: int, color: Color) -> void;
} }
[BuiltinConst(target = "Color", name = "white", version = 1)] [BuiltinConst(target = "Color", name = "white", version = 1)]
@ -130,10 +134,102 @@ class PbsFrontendCompilerTest {
assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size()); assertEquals(1, fileBackend.reservedMetadata().hostMethodBindings().size());
assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size()); assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().size());
assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size()); assertEquals(1, fileBackend.reservedMetadata().builtinConstSurfaces().size());
assertEquals(1, fileBackend.reservedMetadata().requiredCapabilities().size());
assertEquals("gfx", fileBackend.reservedMetadata().requiredCapabilities().getFirst());
assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName()); assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName());
assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size()); assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size());
} }
@Test
void shouldRejectHostBindingWithoutCapabilityAtHostAdmission() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(7), source, diagnostics, SourceKind.SDK_INTERFACE);
final var hostDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
assertEquals(hostDiagnostic.getCode(), hostDiagnostic.getTemplateId());
assertEquals(0, fileBackend.functions().size());
}
@Test
void shouldRejectHostBindingCapabilityNotDeclaredInStrictContext() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
compiler.compileFile(
new FileId(8),
source,
diagnostics,
SourceKind.SDK_INTERFACE,
HostAdmissionContext.strictWithDeclaredCapabilities(ReadOnlyList.wrap(java.util.List.of("input"))));
final var hostDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
}
@Test
void shouldRejectUnknownHostCapabilityAtHostAdmission() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "video")]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
compiler.compileFile(new FileId(9), source, diagnostics, SourceKind.SDK_INTERFACE);
final var hostDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
}
@Test
void shouldRejectMalformedHostCapabilityMetadataAtHostAdmission() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(version = 1)]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
compiler.compileFile(new FileId(10), source, diagnostics, SourceKind.SDK_INTERFACE);
final var hostDiagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.HOST_ADMISSION, hostDiagnostic.getPhase());
}
@Test @Test
void shouldInferBuiltinFieldSlotsFromDeclarationOrder() { void shouldInferBuiltinFieldSlotsFromDeclarationOrder() {
final var source = """ final var source = """

View File

@ -59,6 +59,7 @@ class PbsGateUSdkInterfaceConformanceTest {
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, color: Color) -> void; fn draw_pixel(x: int, y: int, color: Color) -> void;
} }
"""; """;
@ -165,6 +166,7 @@ class PbsGateUSdkInterfaceConformanceTest {
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, color: Color) -> void; fn draw_pixel(x: int, y: int, color: Color) -> void;
} }
"""; """;
@ -229,6 +231,7 @@ class PbsGateUSdkInterfaceConformanceTest {
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, color: Color) -> void; fn draw_pixel(x: int, y: int, color: Color) -> void;
} }
@ -268,6 +271,30 @@ class PbsGateUSdkInterfaceConformanceTest {
DiagnosticPhase.STATIC_SEMANTICS); DiagnosticPhase.STATIC_SEMANTICS);
} }
@Test
void gateU_shouldEmitHostAdmissionDiagnosticForMissingCapabilityMetadata() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int) -> void;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(
new FileId(42),
source,
diagnostics,
SourceKind.SDK_INTERFACE);
final var hostAdmission = firstDiagnostic(diagnostics,
d -> d.getCode().equals(PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name()));
assertStableDiagnosticIdentity(
hostAdmission,
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
DiagnosticPhase.HOST_ADMISSION);
}
private WorkspaceCompileResult compileWorkspaceModule( private WorkspaceCompileResult compileWorkspaceModule(
final Path projectRoot, final Path projectRoot,
final String sourceContent, final String sourceContent,

View File

@ -224,6 +224,7 @@ class PbsModuleVisibilityTest {
""" """
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int) -> void; fn draw_pixel(x: int, y: int) -> void;
} }
""", """,

View File

@ -127,6 +127,7 @@ class PbsParserTest {
void shouldRejectAttributesInOrdinarySourceAndRecover() { void shouldRejectAttributesInOrdinarySourceAndRecover() {
final var source = """ final var source = """
[Host(module = "gfx", name = "draw", version = 1)] [Host(module = "gfx", name = "draw", version = 1)]
[Capability(name = "gfx")]
fn run() -> int { return 1; } fn run() -> int { return 1; }
declare struct Point(x: int,); declare struct Point(x: int,);
"""; """;
@ -230,6 +231,7 @@ class PbsParserTest {
} }
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw(x: int, y: int, color: Color) -> void; fn draw(x: int, y: int, color: Color) -> void;
} }
[BuiltinConst(target = "Color", name = "white", version = 1)] [BuiltinConst(target = "Color", name = "white", version = 1)]
@ -256,7 +258,7 @@ class PbsParserTest {
final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1)); final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1));
assertEquals(1, hostDecl.signatures().size()); assertEquals(1, hostDecl.signatures().size());
assertEquals(1, hostDecl.signatures().getFirst().attributes().size()); assertEquals(2, hostDecl.signatures().getFirst().attributes().size());
final var constDecl = assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(2)); final var constDecl = assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(2));
assertEquals(1, constDecl.attributes().size()); assertEquals(1, constDecl.attributes().size());
@ -267,6 +269,7 @@ class PbsParserTest {
final var source = """ final var source = """
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw(x: int, y: int) -> void; fn draw(x: int, y: int) -> void;
unexpected_token unexpected_token
} }

View File

@ -24,6 +24,7 @@ class PbsInterfaceModuleSemanticsTest {
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int, c: Color) -> void; fn draw_pixel(x: int, y: int, c: Color) -> void;
} }
@ -51,6 +52,7 @@ class PbsInterfaceModuleSemanticsTest {
declare contract Api { declare contract Api {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn run() -> void; fn run() -> void;
} }

View File

@ -246,6 +246,7 @@ class PBSFrontendPhaseServiceTest {
} }
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw(x: int, y: int, color: Color) -> void; fn draw(x: int, y: int, color: Color) -> void;
} }
"""); """);
@ -529,6 +530,7 @@ class PBSFrontendPhaseServiceTest {
""" """
declare host Gfx { declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)] [Host(module = "gfx", name = "draw_pixel", version = 1)]
[Capability(name = "gfx")]
fn draw_pixel(x: int, y: int) -> void; fn draw_pixel(x: int, y: int) -> void;
} }
"""))), """))),

View File

@ -11,12 +11,13 @@ public class FrontendPhaseContext {
public final FileTableReader fileTable; public final FileTableReader fileTable;
public final BuildStack stack; public final BuildStack stack;
private final int stdlibVersion; private final int stdlibVersion;
private final HostAdmissionContext hostAdmissionContext;
public FrontendPhaseContext( public FrontendPhaseContext(
final ProjectTableReader projectTable, final ProjectTableReader projectTable,
final FileTableReader fileTable, final FileTableReader fileTable,
final BuildStack stack) { final BuildStack stack) {
this(projectTable, fileTable, stack, 1); this(projectTable, fileTable, stack, 1, HostAdmissionContext.permissiveDefault());
} }
public FrontendPhaseContext( public FrontendPhaseContext(
@ -24,10 +25,22 @@ public class FrontendPhaseContext {
final FileTableReader fileTable, final FileTableReader fileTable,
final BuildStack stack, final BuildStack stack,
final int stdlibVersion) { final int stdlibVersion) {
this(projectTable, fileTable, stack, stdlibVersion, HostAdmissionContext.permissiveDefault());
}
public FrontendPhaseContext(
final ProjectTableReader projectTable,
final FileTableReader fileTable,
final BuildStack stack,
final int stdlibVersion,
final HostAdmissionContext hostAdmissionContext) {
this.projectTable = projectTable; this.projectTable = projectTable;
this.fileTable = fileTable; this.fileTable = fileTable;
this.stack = stack; this.stack = stack;
this.stdlibVersion = stdlibVersion; this.stdlibVersion = stdlibVersion;
this.hostAdmissionContext = hostAdmissionContext == null
? HostAdmissionContext.permissiveDefault()
: hostAdmissionContext;
} }
public SourceKind sourceKind(final ProjectId projectId) { public SourceKind sourceKind(final ProjectId projectId) {
@ -37,4 +50,8 @@ public class FrontendPhaseContext {
public int stdlibVersion() { public int stdlibVersion() {
return stdlibVersion; return stdlibVersion;
} }
public HostAdmissionContext hostAdmissionContext() {
return hostAdmissionContext;
}
} }

View File

@ -0,0 +1,53 @@
package p.studio.compiler.messages;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public record HostAdmissionContext(
ReadOnlyList<String> knownCapabilities,
ReadOnlyList<String> declaredCapabilities,
boolean enforceDeclaredCapabilities) {
private static final ReadOnlyList<String> DEFAULT_KNOWN_CAPABILITIES = ReadOnlyList.wrap(List.of(
"system",
"gfx",
"input",
"audio",
"fs",
"log",
"asset",
"bank"));
public HostAdmissionContext {
knownCapabilities = knownCapabilities == null || knownCapabilities.isEmpty()
? DEFAULT_KNOWN_CAPABILITIES
: normalize(knownCapabilities);
declaredCapabilities = declaredCapabilities == null
? ReadOnlyList.empty()
: normalize(declaredCapabilities);
}
public static HostAdmissionContext permissiveDefault() {
return new HostAdmissionContext(DEFAULT_KNOWN_CAPABILITIES, ReadOnlyList.empty(), false);
}
public static HostAdmissionContext strictWithDeclaredCapabilities(
final ReadOnlyList<String> declaredCapabilities) {
return new HostAdmissionContext(DEFAULT_KNOWN_CAPABILITIES, declaredCapabilities, true);
}
private static ReadOnlyList<String> normalize(final ReadOnlyList<String> values) {
final Set<String> dedup = new LinkedHashSet<>();
for (final var value : values) {
if (value == null || value.isBlank()) {
continue;
}
dedup.add(value.trim().toLowerCase(Locale.ROOT));
}
return ReadOnlyList.wrap(dedup.stream().toList());
}
}

View File

@ -5,6 +5,7 @@ import lombok.Getter;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
@Builder @Builder
@Getter @Getter
@ -23,6 +24,7 @@ public class IRBackend {
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>(); private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>(); private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>(); private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
private final LinkedHashSet<String> requiredCapabilities = new LinkedHashSet<>();
public void merge(final IRBackendFile backendFile) { public void merge(final IRBackendFile backendFile) {
if (backendFile == null) { if (backendFile == null) {
@ -33,6 +35,7 @@ public class IRBackend {
hostMethodBindings.addAll(metadata.hostMethodBindings().asList()); hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList()); builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList()); builtinConstSurfaces.addAll(metadata.builtinConstSurfaces().asList());
requiredCapabilities.addAll(metadata.requiredCapabilities().asList());
} }
public IRBackend emit() { public IRBackend emit() {
@ -42,7 +45,8 @@ public class IRBackend {
.reservedMetadata(new IRReservedMetadata( .reservedMetadata(new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings), ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces), ReadOnlyList.wrap(builtinTypeSurfaces),
ReadOnlyList.wrap(builtinConstSurfaces))) ReadOnlyList.wrap(builtinConstSurfaces),
ReadOnlyList.wrap(requiredCapabilities.stream().toList())))
.build(); .build();
} }
} }
@ -54,6 +58,7 @@ public class IRBackend {
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size()) .append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size()) .append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size()) .append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())
.append(", requiredCapabilities=").append(reservedMetadata.requiredCapabilities().size())
.append('}'); .append('}');
if (functions.isEmpty()) { if (functions.isEmpty()) {

View File

@ -8,16 +8,22 @@ import java.util.Objects;
public record IRReservedMetadata( public record IRReservedMetadata(
ReadOnlyList<HostMethodBinding> hostMethodBindings, ReadOnlyList<HostMethodBinding> hostMethodBindings,
ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces, ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces) { ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces,
ReadOnlyList<String> requiredCapabilities) {
public IRReservedMetadata { public IRReservedMetadata {
hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings; hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces; builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces; builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
requiredCapabilities = requiredCapabilities == null ? ReadOnlyList.empty() : requiredCapabilities;
} }
public static IRReservedMetadata empty() { public static IRReservedMetadata empty() {
return new IRReservedMetadata(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty()); return new IRReservedMetadata(
ReadOnlyList.empty(),
ReadOnlyList.empty(),
ReadOnlyList.empty(),
ReadOnlyList.empty());
} }
public record HostMethodBinding( public record HostMethodBinding(
@ -26,12 +32,15 @@ public record IRReservedMetadata(
String abiModule, String abiModule,
String abiMethod, String abiMethod,
long abiVersion, long abiVersion,
boolean capabilityDeclared,
String requiredCapability,
Span span) { Span span) {
public HostMethodBinding { public HostMethodBinding {
ownerName = Objects.requireNonNull(ownerName, "ownerName"); ownerName = Objects.requireNonNull(ownerName, "ownerName");
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName"); sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
abiModule = Objects.requireNonNull(abiModule, "abiModule"); abiModule = Objects.requireNonNull(abiModule, "abiModule");
abiMethod = Objects.requireNonNull(abiMethod, "abiMethod"); abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
requiredCapability = requiredCapability == null ? "" : requiredCapability;
span = Objects.requireNonNull(span, "span"); span = Objects.requireNonNull(span, "span");
} }
} }