diff --git a/packages/vat-data/src/exo-utils.js b/packages/vat-data/src/exo-utils.js index bde60c8f56a..b720624909f 100644 --- a/packages/vat-data/src/exo-utils.js +++ b/packages/vat-data/src/exo-utils.js @@ -270,13 +270,13 @@ export const makeExoUtils = VatData => { harden(prepareExoClassKit); /** - * @template {Record} M methods + * @template {InterfaceGuard} I * @param {Baggage} baggage * @param {string} kindName - * @param {InterfaceGuard | undefined} interfaceGuard - * @param {M} methods - * @param {DefineKindOptions<{ self: M }>} [options] - * @returns {M & RemotableBrand<{}, M>} + * @param {I | undefined} interfaceGuard + * @param {import('./types.js').GuardedMethods} methods + * @param {DefineKindOptions<{ self: import('./types.js').GuardedMethods }>} [options] + * @returns {import('./types.js').GuardedMethods & RemotableBrand<{}, import('./types.js').GuardedMethods>} */ const prepareExo = ( baggage, diff --git a/packages/vat-data/src/index.test-d.ts b/packages/vat-data/src/index.test-d.ts index c8fd74547d9..99287632270 100644 --- a/packages/vat-data/src/index.test-d.ts +++ b/packages/vat-data/src/index.test-d.ts @@ -1,18 +1,22 @@ /* eslint-disable no-use-before-define */ -import { expectType } from 'tsd'; +import { expectNotType, expectType } from 'tsd'; import type { KindFacets, DurableKindHandle, KindFacet, FunctionsPlusContext, } from '@agoric/swingset-liveslots'; +import { TypedMatcher } from '@agoric/internal/src/types.js'; import { defineKind, defineKindMulti, makeKindHandle, defineDurableKind, partialAssign, + prepareExo, + M, } from '.'; +import { GuardedMethod, TypedMethodGuard } from './types.js'; /* export const makePaymentMaker = (allegedName: string, brand: unknown) => { @@ -158,3 +162,38 @@ const someFacet: KindFacet = null as any; // @ts-expect-error someFacet.gt(); expectType(someFacet.gt(1)); + +const Mnumber = M.number() as TypedMatcher; + +{ + const numIdentityGuard = M.call(Mnumber).returns(Mnumber) as TypedMethodGuard< + (n: number) => number + >; + const numIdentity: GuardedMethod = x => x; + expectType(numIdentity(3)); +} + +{ + const baggage = null as any; + const UpCounterI = M.interface('UpCounter', { + // TODO infer the TypedMethodGuard signature from the fluent builder + adjustBy: M.call(Mnumber).returns(Mnumber) as TypedMethodGuard< + (y: number) => number + >, + }); + const exo = prepareExo(baggage, 'upCounter', UpCounterI, { + adjustBy(y) { + expectType(y); + expectNotType(y); + return y; + }, + }); + expectType<(y: number) => number>(exo.adjustBy); + + prepareExo(baggage, 'upCounter', UpCounterI, { + // @ts-expect-error invalid return type + adjustBy(y) { + return 'hi'; + }, + }); +} diff --git a/packages/vat-data/src/types.d.ts b/packages/vat-data/src/types.d.ts new file mode 100644 index 00000000000..020ef1aae46 --- /dev/null +++ b/packages/vat-data/src/types.d.ts @@ -0,0 +1,25 @@ +import { MatcherType } from '@agoric/internal/src/types.js'; +import type { InterfaceGuard } from '@endo/patterns'; + +type MethodGuardPayload = { + callKind: 'sync' | 'async'; + argGuards: ArgGuard[]; + optionalArgGuards?: ArgGuard[]; + restArgGuard?: unknown; + returnGuard: unknown; +}; + +declare const methodSignature: unique symbol; +export type TypedMethodGuard = { + klass: 'methodGuard'; +} & MethodGuardPayload & { methodSignature: F }; + +export type GuardedMethod = G extends TypedMethodGuard< + infer F +> + ? F + : unknown; + +export type GuardedMethods = { + readonly [P in keyof I['methodGuards']]: GuardedMethod; +};