Support explicitly reference-selected read-only interface dependencies
This commit is contained in:
@@ -183,10 +183,23 @@ function methods(
|
||||
`Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`,
|
||||
);
|
||||
if (execution.kind === "transaction" && !remote) writable();
|
||||
if (req.receiver === "input" && (remote || execution.kind !== "snapshot"))
|
||||
throw new PortableBindingError(path, "Reference-selected dependencies require portable snapshot calls");
|
||||
return method(
|
||||
`${member.displayName}.${op.displayName}`,
|
||||
{ kind: "interface", operationId: op.id },
|
||||
op.inputType,
|
||||
req.receiver === "input"
|
||||
? {
|
||||
kind: "record",
|
||||
fields: {
|
||||
receiver: {
|
||||
kind: "object-ref",
|
||||
expectation: { kind: "interface", interfaceRevisionId: req.interfaceRevisionId },
|
||||
},
|
||||
input: op.inputType,
|
||||
},
|
||||
}
|
||||
: op.inputType,
|
||||
op.outputType,
|
||||
execution.kind === "transaction",
|
||||
);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1020,6 +1020,7 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
|
||||
kind: "interface",
|
||||
interfaceRevisionId: target.revisionId,
|
||||
...lowerSelectedOperations(state, context, target),
|
||||
...(context.FROM() ? { receiver: "input" as const } : {}),
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
@@ -1241,6 +1242,7 @@ const lowerGenericPackageExport = (
|
||||
kind: "interface",
|
||||
application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()),
|
||||
...lowerSelectedOperations(state, port, state.interfaces.get(identifier(port.identifier(1)))),
|
||||
...(port.FROM() ? { receiver: "input" as const } : {}),
|
||||
},
|
||||
};
|
||||
const name = identifier(port.identifier(1)),
|
||||
@@ -1817,6 +1819,7 @@ const lowerBoundDependencies = (
|
||||
binding: {
|
||||
kind: "interface",
|
||||
interfaceRevisionId: interfaceSymbol.revisionId,
|
||||
...(entry.FROM() ? { receiver: "input" as const } : {}),
|
||||
...(via ? { via } : {}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -66,6 +66,23 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
|
||||
: requirement.application.definitionId,
|
||||
);
|
||||
const selected = requirement.operations;
|
||||
if (requirement.receiver === "input") {
|
||||
if (
|
||||
entry.execution?.kind !== "snapshot" ||
|
||||
entry.execution.targets.length !== 1 ||
|
||||
entry.execution.targets[0] !== "server" ||
|
||||
!selected?.length
|
||||
)
|
||||
fail(
|
||||
portPath,
|
||||
"Reference-selected reads currently require a server-only snapshot and an explicit selection; local acquisition of dynamic inputs is not implemented",
|
||||
);
|
||||
for (const id of selected ?? []) {
|
||||
const operation = contract && operations(id, contract);
|
||||
if (operation?.execution?.kind !== "snapshot" || operation.scope === "class")
|
||||
fail(portPath, "Reference-selected dependencies currently support object snapshot operations only");
|
||||
}
|
||||
}
|
||||
if (selected && (!selected.length || new Set(selected).size !== selected.length))
|
||||
fail(portPath, "Selected interface operations must be nonempty and distinct");
|
||||
for (const id of selected ?? [])
|
||||
|
||||
@@ -38,6 +38,7 @@ export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
|
||||
kind: "interface";
|
||||
application: InterfaceApplicationExpression;
|
||||
operations?: import("./types.js").OperationId[];
|
||||
receiver?: "input";
|
||||
}
|
||||
| { kind: "creation"; target: ObjectTypeExpression; inputType: ValueTypeExpression }
|
||||
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
|
||||
@@ -100,6 +101,7 @@ export const specializePackageExport = (
|
||||
kind: "interface",
|
||||
interfaceRevisionId: substitution.application(requirement.application),
|
||||
...(requirement.operations ? { operations: requirement.operations } : {}),
|
||||
...(requirement.receiver ? { receiver: requirement.receiver } : {}),
|
||||
},
|
||||
};
|
||||
case "creation":
|
||||
|
||||
@@ -246,6 +246,7 @@ export type DependencyPortRequirement =
|
||||
interfaceRevisionId: InterfaceRevisionId;
|
||||
/** Exact public operation identities, not implementation export names. */
|
||||
operations?: OperationId[];
|
||||
receiver?: "input";
|
||||
}
|
||||
| { kind: "creation"; atomId: AtomId; inputType: ValueType }
|
||||
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
|
||||
@@ -323,6 +324,7 @@ export type DependencyBinding =
|
||||
kind: "interface";
|
||||
interfaceRevisionId: InterfaceRevisionId;
|
||||
via?: EdgeTraversal;
|
||||
receiver?: "input";
|
||||
}
|
||||
| {
|
||||
kind: "creation";
|
||||
|
||||
@@ -1418,6 +1418,15 @@ const validateBoundDependencies = (
|
||||
}
|
||||
case "interface": {
|
||||
const interfaceBinding = binding as Extract<DependencyBinding, { kind: "interface" }>;
|
||||
if (interfaceBinding.receiver !== requirement.receiver || (interfaceBinding.receiver && interfaceBinding.via)) {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-dependency-binding",
|
||||
path,
|
||||
"Reference-selected interface ports require an explicit matching from input binding",
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (interfaceBinding.interfaceRevisionId !== requirement.interfaceRevisionId) {
|
||||
issue(
|
||||
issues,
|
||||
@@ -1427,6 +1436,9 @@ const validateBoundDependencies = (
|
||||
);
|
||||
break;
|
||||
}
|
||||
// The reference supplies its own checked witness at invocation time.
|
||||
// This is not a requirement that the current receiver implements the interface.
|
||||
if (interfaceBinding.receiver === "input") break;
|
||||
const traversal = interfaceBinding.via
|
||||
? validateTraversal(
|
||||
issues,
|
||||
|
||||
@@ -136,6 +136,7 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
||||
...common,
|
||||
kind: "interface",
|
||||
interface: bound.interfaceRevisionId,
|
||||
...(bound.receiver ? { receiver: bound.receiver } : {}),
|
||||
operations: requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,6 +162,15 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
const queryOperations = (query: LinkedQuery) =>
|
||||
query.fields.map((f) => publicOperationKey(f.atomId, f.interfaceRevisionId, f.getter));
|
||||
const targetAtoms = (binding: DependencyBinding, atom: AtomId): AtomId[] => {
|
||||
if (binding.kind === "interface" && binding.receiver === "input")
|
||||
return workspace.atoms
|
||||
.filter((candidate) =>
|
||||
workspace.conformances.some(
|
||||
(conformance) =>
|
||||
conformance.atomId === candidate.id && conformance.interfaceRevisionId === binding.interfaceRevisionId,
|
||||
),
|
||||
)
|
||||
.map((candidate) => candidate.id);
|
||||
if (!("via" in binding) || !binding.via) return [atom];
|
||||
const edge = attachments.find((a) => a.id === binding.via!.edgeTypeId);
|
||||
if (!edge || edge.kind !== "edge") throw new ExecutionPlanError("MISSING_EDGE", atom, "Missing traversal edge");
|
||||
|
||||
Reference in New Issue
Block a user