Skip to content

Commit

Permalink
feat: typed method guards for prepareExo
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Apr 24, 2024
1 parent 4e15865 commit 85e633f
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 19 deletions.
27 changes: 18 additions & 9 deletions packages/vat-data/src/exo-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { initEmpty } from '@agoric/store';
import { provide, VatData as globalVatData } from './vat-data-bindings.js';

/**
* @import {Guarded} from '@endo/exo'
* @import {InterfaceGuard} from '@endo/patterns'
* @import {Baggage, DefineKindOptions, DurableKindHandle, InterfaceGuardKit} from '@agoric/swingset-liveslots'
* @import {MethodGuard} from '@endo/patterns'
* @import {GuardedMethods} from './types.js'
* @import {GuardedMethods, TypedInterfaceGuard} from './types.js'
* @import {RemotableBrand} from '@endo/eventual-send'
* @import {ERef} from '@endo/far'
* @import {KindFacet} from '@agoric/swingset-liveslots'
Expand Down Expand Up @@ -273,13 +274,22 @@ export const makeExoUtils = VatData => {
harden(prepareExoClassKit);

/**
* @template {InterfaceGuard} I
* @param {Baggage} baggage
* @param {string} kindName
* @param {I | undefined} interfaceGuard
* @param {GuardedMethods<I>} methods
* @param {DefineKindOptions<{ self: GuardedMethods<I> }>} [options]
* @returns {GuardedMethods<I> & RemotableBrand<{}, GuardedMethods<I>>}
* @type {{
* <I extends TypedInterfaceGuard>(
* baggage: Baggage,
* kindName: string,
* interfaceGuard: I,
* methods: GuardedMethods<I>,
* options?: DefineKindOptions<{ self: GuardedMethods<I> }>)
* : GuardedMethods<I> & RemotableBrand<{}, GuardedMethods<I>>;
* <M extends Record<PropertyKey, CallableFunction>>(
* baggage: Baggage,
* kindName: string,
* interfaceGuard: InterfaceGuard | undefined,
* methods: M,
* options?: DefineKindOptions<{ self: M }>)
* : Guarded<M>;
* }};
*/
const prepareExo = (
baggage,
Expand Down Expand Up @@ -311,7 +321,6 @@ export const makeExoUtils = VatData => {
* @returns {import('@endo/exo').Guarded<M>}
*/
const prepareSingleton = (baggage, kindName, methods, options = undefined) =>
// @ts-expect-error types puzzle, ignore b/c deprecated
prepareExo(baggage, kindName, undefined, methods, options);
harden(prepareSingleton);

Expand Down
58 changes: 49 additions & 9 deletions packages/vat-data/src/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
/* eslint-disable no-use-before-define */
import { expectNotType, expectType } from 'tsd';
import { TypedMatcher } from '@agoric/internal/src/types.js';
import type {
KindFacets,
DurableKindHandle,
KindFacet,
FunctionsPlusContext,
KindFacet,
KindFacets,
} from '@agoric/swingset-liveslots';
import { VirtualObjectManager } from '@agoric/swingset-liveslots/src/virtualObjectManager.js';
import { TypedMatcher } from '@agoric/internal/src/types.js';
import { InterfaceGuard } from '@endo/patterns';
import { expectNotType, expectType } from 'tsd';
import {
defineDurableKind,
defineKind,
defineKindMulti,
M,
makeKindHandle,
defineDurableKind,
partialAssign,
watchPromise,
prepareExo,
M,
watchPromise,
} from '.';

Check warning on line 21 in packages/vat-data/src/index.test-d.ts

View workflow job for this annotation

GitHub Actions / lint-rest

Missing file extension "js" for "."
import { GuardedMethod, GuardedMethods, TypedMethodGuard } from './types.js';
import {
GuardedMethod,
TypedInterfaceGuard,
TypedMethodGuard,
} from './types.js';

// for use in assignments below
const anyVal = null as any;
Expand Down Expand Up @@ -200,16 +205,24 @@ const Mnumber = M.number() as TypedMatcher<number>;
>;
const numIdentity: GuardedMethod<typeof numIdentityGuard> = x => x;
expectType<number>(numIdentity(3));

const untypedGuard = M.call(Mnumber).returns(Mnumber);
// @ts-expect-error cannot assign to never
const untypedIdentity: GuardedMethod<typeof untypedGuard> = x => x;
expectType<never>(untypedIdentity);
}

{
// TypedMethodGuard
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
>,
});
expectType<InterfaceGuard>(UpCounterI);
expectType<TypedInterfaceGuard>(UpCounterI);
const exo = prepareExo(baggage, 'upCounter', UpCounterI, {
adjustBy(y) {
expectType<number>(y);
Expand All @@ -218,11 +231,38 @@ const Mnumber = M.number() as TypedMatcher<number>;
},
});
expectType<(y: number) => number>(exo.adjustBy);
// @ts-expect-error invalid argument
exo.adjustBy('foo');
// @ts-expect-error cannot add number to bigint
exo.adjustBy(1) + 1n;

prepareExo(baggage, 'upCounter', UpCounterI, {
// @ts-expect-error invalid return type
// TODO error on the faulty return type
adjustBy(y) {
expectType<number>(y);
return 'hi';
},
});
}

{
// MethodGuard with type on impl
const baggage = null as any;
const UpCounterI = M.interface('UpCounter', {
adjustBy: M.call(Mnumber).returns(Mnumber),
});
expectType<InterfaceGuard>(UpCounterI);
expectNotType<TypedInterfaceGuard>(UpCounterI);
const exo = prepareExo(baggage, 'upCounter', UpCounterI, {
/** @param {number} y */
adjustBy(y) {
return y;
},
});
// @ts-expect-error must not be any
exo.adjustBy + 1;
// TODO propagate return type instead of `any`
exo.adjustBy(1) + 1n;

expectType<(y: number) => number>(exo.adjustBy);
}
7 changes: 6 additions & 1 deletion packages/vat-data/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ export type GuardedMethod<G extends MethodGuard> = G extends TypedMethodGuard<
infer F
>
? F

Check warning on line 12 in packages/vat-data/src/types.d.ts

View workflow job for this annotation

GitHub Actions / lint-rest

Insert `··`
: CallableFunction;
: // Could be `unknown` but this helps detect errors

Check warning on line 13 in packages/vat-data/src/types.d.ts

View workflow job for this annotation

GitHub Actions / lint-rest

Insert `··`
never;

Check warning on line 14 in packages/vat-data/src/types.d.ts

View workflow job for this annotation

GitHub Actions / lint-rest

Insert `··`

export type GuardedMethods<I extends InterfaceGuard> = {
readonly [P in keyof I['payload']['methodGuards']]: GuardedMethod<
I['payload']['methodGuards'][P]
>;
};

export type TypedInterfaceGuard = InterfaceGuard & {
payload: { methodGuards: Record<PropertyKey, TypedMethodGuard> };
};
4 changes: 4 additions & 0 deletions packages/vat-data/test/test-prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ test('test prepareExo', t => {
return x;
},
});
// @ts-expect-error must be a function
upCounter.incr + 1;
// @ts-expect-error arity
upCounter.incr(1, 2);
t.is(upCounter.incr(5), 8);
t.is(upCounter.incr(1), 9);
t.throws(() => upCounter.incr(-3), {
Expand Down

0 comments on commit 85e633f

Please sign in to comment.