Make local graph execution optional across composed packages

This commit is contained in:
Timothy J. Aveni
2026-09-28 21:05:42 -07:00
parent 1e72713ae2
commit e9c15fe40f
87 changed files with 3703 additions and 3400 deletions
+60 -16
View File
@@ -205,6 +205,7 @@ interface LoweringState {
fileName: string;
diagnostics: CapabilitySourceDiagnostic[];
atoms: Map<string, AtomId>;
symbols: Map<string, string>;
interfaces: Map<string, InterfaceSymbol>;
packages: Map<string, PackageSymbol>;
attachments: Map<string, AttachmentSymbol>;
@@ -387,14 +388,33 @@ const lowerSelectedOperations = (
return { operations };
};
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
const lowerExecution = (
state: LoweringState,
context: ExecutionClauseContext | null,
implementation = false,
): { execution?: ExecutionContract } => {
if (!context) return {};
if (!implementation && context.identifierList())
loweringIssue(
state,
context,
"interface-execution-targets",
"Interfaces declare effects; execution targets belong to package implementations",
);
const targets = context
.identifierList()
?.identifier()
.map((node) => {
const name = identifier(node);
if (name === "server") return name;
return requireSymbol(state, state.symbols, name, node, "nominal execution target") ?? `unresolved:${name}`;
}) ?? ["server"];
return {
execution: context.REMOTE()
? { kind: "remote" }
: {
kind: context.SNAPSHOT() ? "snapshot" : "transaction",
targets: context.identifierList()!.identifier().map(identifier),
targets: implementation ? targets : ["server"],
},
};
};
@@ -508,17 +528,17 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
if (operation.GET()) {
operations.push({
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else if (operation.EDIT()) {
operations.push({
...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else if (operation.SET()) {
operations.push({
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else {
operations.push(
@@ -611,7 +631,7 @@ const lowerRelationshipMember = (
);
}
if (operation.executionClause())
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
Object.assign(operations[operations.length - 1]!, lowerExecution(state, operation.executionClause()));
}
return {
kind: "relationship",
@@ -653,7 +673,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
outputType,
mode: "call",
...(context.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause()),
},
],
};
@@ -814,18 +834,24 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.GET())
return [
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
{
...signature("get", id, valueType.unit, type),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.EDIT())
return [
{
...signature("edit", id, { kind: "change", value: type }, valueType.unit),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.SET())
return [
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
{
...signature("set", id, type, valueType.unit),
...lowerExecution(state, operation.executionClause()),
},
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
@@ -859,7 +885,7 @@ const lowerInterfaceTemplate = (
outputType,
),
...(operation.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
],
};
@@ -907,7 +933,10 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.RESOLVE())
return [
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) },
{
...signature("resolve", id, valueType.unit, resolved),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.CONNECT() || operation.DISCONNECT())
return [
@@ -918,7 +947,7 @@ const lowerInterfaceTemplate = (
operation.CONNECT() ? keyedTarget : targetType,
valueType.unit,
),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
];
return [
@@ -1196,7 +1225,7 @@ const lowerPackageOperation = (
inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)),
mode: text(context.operationMode()) as InterfaceOperationMode,
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
dependencyPorts: ports,
@@ -1213,7 +1242,7 @@ const lowerPackageFunction = (
const alias = identifier(context.identifier());
const entry: PackageExport = {
kind: "function",
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType(0)),
@@ -1355,7 +1384,7 @@ const lowerGenericPackageExport = (
displayName: identifier(context.identifier()),
parameters,
kind: operation ? "operation" : "function",
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
inputType: state.types.expression(context.valueType(0)!),
outputType: state.types.expression(context.valueType(1)!),
dependencyPorts,
@@ -3092,6 +3121,7 @@ const newLoweringState = (fileName: string, diagnostics: CapabilitySourceDiagnos
atoms,
types: new GenericSourceTypes(atoms),
interfaces: new Map(),
symbols: new Map(),
packages: new Map(),
attachments: new Map(),
};
@@ -3240,6 +3270,20 @@ const compileCapabilityResourceSourceInternal = (
const externalAtoms = externalAtomsFrom(state, preamble.atoms);
const imports = registerImports(state, preamble.imports, environment);
const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces);
for (const declaration of (interfaceContext ?? packageContext)!
.resourcePreamble()
.flatMap((entry) => entry.symbolDecl() ?? [])) {
const name = identifier(declaration.identifier());
const identity = stringValue(declaration.stringLiteral());
if (name === "server" || identity === "server" || !identity.trim())
loweringIssue(
state,
declaration,
"invalid-symbol",
"Nominal symbols require a nonempty identity distinct from the server executor",
);
declareSymbol(state, state.symbols, name, identity, declaration, "symbol");
}
state.types.declareAliases(preamble.aliases);
let resource: CapabilityResource;