From 1f610064076cbf7fc419e2307ab497bdea34aae1 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 5 Jan 2024 20:43:48 -0600 Subject: [PATCH] Lift a simple function into an operation safely. There is the potential for a function, if it is a continuation, to cause the scope in which it is called to be destroyed. In this case, nothing after that function should ever be called. However, because of the way `lift()` is currently implemented, this will not be the case. This unpacks the `lift()` function to be a single instruction that immediately returns value of invoking the function. However, because there is a `yield` point in the computation, it is an opportunity to not continue which will happen if corresponding frame is closed. At the same time, it removes the named types since they don't really add much clarity when you look at them from your language server. --- lib/lift.ts | 20 +++++++++----------- test/lift.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 test/lift.test.ts diff --git a/lib/lift.ts b/lib/lift.ts index 76a48818..f93d8c9c 100644 --- a/lib/lift.ts +++ b/lib/lift.ts @@ -1,3 +1,4 @@ +import { shift } from "./deps.ts"; import { type Operation } from "./types.ts"; /** @@ -16,20 +17,17 @@ import { type Operation } from "./types.ts"; * @returns a function returning an operation that invokes `fn` when evaluated */ export function lift( - fn: Fn, -): LiftedFn { + fn: (...args: TArgs) => TReturn, +): (...args: TArgs) => Operation { return (...args: TArgs) => { return ({ - [Symbol.iterator]() { - let value = fn(...args); - return { next: () => ({ done: true, value }) }; + *[Symbol.iterator]() { + return yield () => { + return shift(function* (k) { + k({ ok: true, value: fn(...args) }); + }); + }; }, }); }; } - -type Fn = (...args: TArgs) => TReturn; - -type LiftedFn = ( - ...args: TArgs -) => Operation; diff --git a/test/lift.test.ts b/test/lift.test.ts new file mode 100644 index 00000000..d2240296 --- /dev/null +++ b/test/lift.test.ts @@ -0,0 +1,23 @@ +import { createSignal, each, lift, run, sleep, spawn } from "../mod.ts"; +import { describe, expect, it } from "./suite.ts"; + +describe("lift", () => { + it("safely does not continue if the call stops the operation", async () => { + let reached = false; + + await run(function* () { + let signal = createSignal(); + + yield* spawn(function* () { + yield* sleep(0); + yield* lift(signal.close)(); + + reached = true; + }); + + for (let _ of yield* each(signal)); + }); + + expect(reached).toBe(false); + }); +});