Implement typed module contracts and conformance bindings
This commit is contained in:
@@ -642,7 +642,19 @@ const lowerInterface = (
|
||||
source: SourceRevision,
|
||||
): InterfaceRevision => {
|
||||
const alias = identifier(context.identifier());
|
||||
const members = context.interfaceMember().flatMap((entry) => {
|
||||
const members = context.interfaceMember().flatMap<InterfaceMember>((entry) => {
|
||||
const module = entry.moduleMember();
|
||||
if (module)
|
||||
return [
|
||||
{
|
||||
kind: "module" as const,
|
||||
id: capabilityId.member(stringValue(module.stringLiteral(0))),
|
||||
displayName: identifier(module.identifier()),
|
||||
contract: stringValue(module.stringLiteral(1)),
|
||||
...(module.targetConstraint() ? { subject: state.types.target(module.targetConstraint()!) } : {}),
|
||||
operations: [],
|
||||
},
|
||||
];
|
||||
const value = entry.valueMember();
|
||||
if (value) {
|
||||
return [lowerValueMember(state, value)];
|
||||
@@ -745,6 +757,16 @@ const lowerInterfaceTemplate = (
|
||||
const members: InterfaceMember<ValueTypeExpression, ObjectTypeExpression>[] = context
|
||||
.interfaceMember()
|
||||
.map((entry) => {
|
||||
const module = entry.moduleMember();
|
||||
if (module)
|
||||
return {
|
||||
kind: "module" as const,
|
||||
id: capabilityId.member(stringValue(module.stringLiteral(0))),
|
||||
displayName: identifier(module.identifier()),
|
||||
contract: stringValue(module.stringLiteral(1)),
|
||||
...(module.targetConstraint() ? { subject: types.targetExpression(module.targetConstraint()!) } : {}),
|
||||
operations: [],
|
||||
};
|
||||
const value = entry.valueMember();
|
||||
if (value) {
|
||||
const type = types.expression(value.valueType());
|
||||
@@ -1333,23 +1355,26 @@ const lowerPackage = (
|
||||
}),
|
||||
);
|
||||
const genericExports: GenericPackageExport[] = [];
|
||||
const components: NonNullable<PackageRevision["components"]> = [];
|
||||
const modules: NonNullable<PackageRevision["modules"]> = [];
|
||||
const queries: QueryDeclaration[] = [];
|
||||
const queryTemplates: QueryTemplate[] = [];
|
||||
const exports = context.packageExport().flatMap((exportContext): PackageExport[] => {
|
||||
const component = exportContext.componentDecl();
|
||||
if (component) {
|
||||
const runtime = identifier(component.identifier(1));
|
||||
if (runtime !== "react")
|
||||
loweringIssue(state, component, "unsupported-component-runtime", `Unsupported component runtime ${runtime}`);
|
||||
components.push({
|
||||
id: stringValue(component.stringLiteral()),
|
||||
displayName: identifier(component.identifier(0)),
|
||||
subject: new TypeSubstitution(state.types.environment()).application(
|
||||
state.types.interface(component.interfaceType()),
|
||||
),
|
||||
runtime: "react",
|
||||
});
|
||||
const module = exportContext.moduleExport();
|
||||
if (module) {
|
||||
const interfaceRevisionId = new TypeSubstitution(state.types.environment()).application(
|
||||
state.types.interface(module.interfaceType()),
|
||||
);
|
||||
const contract = state.types.definitions.get(interfaceRevisionId);
|
||||
const member = contract?.members.find((m) => m.displayName === identifier(module.identifier(1)));
|
||||
if (member?.kind !== "module")
|
||||
loweringIssue(state, module, "invalid-module-export", "Module export must implement an imported module member");
|
||||
else
|
||||
modules.push({
|
||||
id: stringValue(module.stringLiteral()),
|
||||
displayName: identifier(module.identifier(0)),
|
||||
interfaceRevisionId,
|
||||
memberId: member.id,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
if (exportContext.packageQuerySpecialization()) return [];
|
||||
@@ -1611,7 +1636,7 @@ const lowerPackage = (
|
||||
displayName: alias,
|
||||
source,
|
||||
exports,
|
||||
...(components.length ? { components } : {}),
|
||||
...(modules.length ? { modules } : {}),
|
||||
...(queries.length ? { queries } : {}),
|
||||
...(queryTemplates.length ? { queryTemplates } : {}),
|
||||
...(queries.some((query) => query.argumentRequirements?.length)
|
||||
@@ -1931,6 +1956,25 @@ const lowerConformance = (
|
||||
state.types.self = previousSelf;
|
||||
}
|
||||
}
|
||||
const moduleBindings: NonNullable<WorkspaceRevision["conformances"][number]["moduleBindings"]> = [];
|
||||
for (const item of context.conformanceItem()) {
|
||||
const binding = item.moduleBindingDecl();
|
||||
if (!binding) continue;
|
||||
const memberName = identifier(binding.identifier(0));
|
||||
const member = interfaceSymbol.definition?.members.find((m) => m.displayName === memberName);
|
||||
const pkg = requireSymbol(state, state.packages, identifier(binding.identifier(1)), binding, "package");
|
||||
const module = pkg?.definition?.modules?.find((m) => m.displayName === identifier(binding.identifier(2)));
|
||||
if (member?.kind !== "module" || !module || !pkg) {
|
||||
loweringIssue(
|
||||
state,
|
||||
binding,
|
||||
"invalid-module-binding",
|
||||
"Expected a module member bound to an imported module export",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
moduleBindings.push({ memberId: member.id, packageRevisionId: pkg.revisionId, moduleId: module.id });
|
||||
}
|
||||
const operationBindings = context
|
||||
.conformanceItem()
|
||||
.flatMap<WorkspaceRevision["conformances"][number]["operationBindings"][number]>((item) => {
|
||||
@@ -2182,6 +2226,7 @@ const lowerConformance = (
|
||||
...(context.INTEGER() ? { semanticMajor: Number(context.INTEGER()!.getText()) } : {}),
|
||||
privateAttachments,
|
||||
operationBindings,
|
||||
...(moduleBindings.length ? { moduleBindings } : {}),
|
||||
relationshipMaterializations,
|
||||
};
|
||||
};
|
||||
@@ -2420,7 +2465,7 @@ const lowerWorkspace = (
|
||||
const packageName = identifier(presentation.identifier(1));
|
||||
const componentName = identifier(presentation.identifier(2));
|
||||
const pkg = requireSymbol(state, state.packages, packageName, presentation, "package");
|
||||
const component = pkg?.definition?.components?.find((entry) => entry.displayName === componentName);
|
||||
const component = pkg?.definition?.modules?.find((entry) => entry.displayName === componentName);
|
||||
if (!component) {
|
||||
loweringIssue(state, presentation, "unknown-component", `Unknown component ${packageName}.${componentName}`);
|
||||
return [];
|
||||
@@ -2692,7 +2737,7 @@ const compileCapabilityResourceSourceInternal = (
|
||||
externalInterfaces,
|
||||
revision: lowerPackage(state, context, options.source),
|
||||
};
|
||||
if (resource.revision.components?.length) {
|
||||
if (resource.revision.modules?.length) {
|
||||
resource.revision.clientInterfaces = Object.fromEntries(
|
||||
imports.flatMap((entry) => {
|
||||
if (entry.kind !== "interface") return [];
|
||||
|
||||
Reference in New Issue
Block a user