Skip to content

Commit 3cf1e39

Browse files
committed
adjust DisposableResult impl and union
1 parent e1cb8b6 commit 3cf1e39

File tree

4 files changed

+104
-35
lines changed

4 files changed

+104
-35
lines changed

packages/quickjs-emscripten-core/src/QuickJSIterator.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import type { QuickJSContext } from "./context"
2-
import { Lifetime, UsingDisposable } from "./lifetime"
1+
import type { ContextResult, QuickJSContext } from "./context"
2+
import { DisposableResult, Lifetime, UsingDisposable } from "./lifetime"
33
import type { QuickJSRuntime } from "./runtime"
44
import type { QuickJSHandle } from "./types"
5-
import type { VmCallResult } from "./vm-interface"
65

76
/**
87
* Proxies the iteration protocol from the host to a guest iterator.
@@ -29,7 +28,7 @@ import type { VmCallResult } from "./vm-interface"
2928
*/
3029
export class QuickJSIterator
3130
extends UsingDisposable
32-
implements Disposable, Iterator<VmCallResult<QuickJSHandle>>
31+
implements Disposable, Iterator<ContextResult<QuickJSHandle>>
3332
{
3433
public owner: QuickJSRuntime
3534
private _next: QuickJSHandle | undefined
@@ -43,7 +42,7 @@ export class QuickJSIterator
4342
this.owner = context.runtime
4443
}
4544

46-
next(value?: QuickJSHandle): IteratorResult<VmCallResult<QuickJSHandle>, any> {
45+
next(value?: QuickJSHandle): IteratorResult<ContextResult<QuickJSHandle>, any> {
4746
if (!this.alive || this._isDone) {
4847
return {
4948
done: true,
@@ -55,7 +54,7 @@ export class QuickJSIterator
5554
return this.callIteratorMethod(nextMethod, value)
5655
}
5756

58-
return(value?: QuickJSHandle): IteratorResult<VmCallResult<QuickJSHandle>, any> {
57+
return(value?: QuickJSHandle): IteratorResult<ContextResult<QuickJSHandle>, any> {
5958
if (!this.alive) {
6059
return {
6160
done: true,
@@ -81,7 +80,7 @@ export class QuickJSIterator
8180
return result
8281
}
8382

84-
throw(e?: any): IteratorResult<VmCallResult<QuickJSHandle>, any> {
83+
throw(e?: any): IteratorResult<ContextResult<QuickJSHandle>, any> {
8584
if (!this.alive) {
8685
return {
8786
done: true,
@@ -113,7 +112,7 @@ export class QuickJSIterator
113112
private callIteratorMethod(
114113
method: QuickJSHandle,
115114
input?: QuickJSHandle,
116-
): IteratorResult<VmCallResult<QuickJSHandle>, any> {
115+
): IteratorResult<ContextResult<QuickJSHandle>, any> {
117116
const callResult = input
118117
? this.context.callFunction(method, this.handle, input)
119118
: this.context.callFunction(method, this.handle)
@@ -137,8 +136,8 @@ export class QuickJSIterator
137136
const value = this.context.getProp(callResult.value, "value")
138137
callResult.value.dispose()
139138
return {
140-
value: { value },
141-
done,
139+
value: DisposableResult.success(value),
140+
done: done as false,
142141
}
143142
}
144143
}

packages/quickjs-emscripten-core/src/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ export class QuickJSContext
925925
const methodHandle = scope.manage(this.getProp(handle, SymbolIterator))
926926
const iteratorCallResult = this.callFunction(methodHandle, handle)
927927
if (iteratorCallResult.error) {
928-
return iteratorCallResult.cast()
928+
return iteratorCallResult
929929
}
930930
return this.success(new QuickJSIterator(iteratorCallResult.value, this))
931931
})

packages/quickjs-emscripten-core/src/lifetime.test.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from "assert"
22
import { describe, it } from "vitest"
3-
import { Lifetime, Scope } from "./lifetime"
3+
import { DisposableResult, Lifetime, Scope } from "./lifetime"
44

55
describe("Lifetime", () => {
66
describe(".consume", () => {
@@ -57,3 +57,77 @@ describe("Scope", () => {
5757
})
5858
})
5959
})
60+
61+
describe("DisposableResult", () => {
62+
function itBehavesAsDisposable(factory: <T>(v: T) => DisposableResult<T, T>) {
63+
it("is alive when the value is not disposable", () => {
64+
const result = factory(false)
65+
assert.strictEqual(result.alive, true)
66+
})
67+
68+
it("is alive when the value is disposable and alive", () => {
69+
const lifetime = new Lifetime(1)
70+
const result = factory(lifetime)
71+
assert.strictEqual(result.alive, true)
72+
lifetime.dispose()
73+
assert.strictEqual(result.alive, false)
74+
})
75+
76+
it("disposes the value when dispose is called", () => {
77+
const lifetime = new Lifetime(1)
78+
const result = factory(lifetime)
79+
result.dispose()
80+
assert.strictEqual(lifetime.alive, false)
81+
})
82+
83+
it("can call unwrapOr", () => {
84+
const result = factory("hi")
85+
result.unwrapOr(0)
86+
})
87+
}
88+
89+
describe("success", () => {
90+
itBehavesAsDisposable((v) => DisposableResult.success(v))
91+
92+
it("unwrap returns the value", () => {
93+
const val = {}
94+
const result = DisposableResult.success(val)
95+
assert.strictEqual(result.unwrap(), val)
96+
})
97+
98+
it("unwrapOr returns the value", () => {
99+
const val = {}
100+
const result = DisposableResult.success(val)
101+
assert.strictEqual(result.unwrapOr(0), val)
102+
})
103+
})
104+
105+
describe("fail", () => {
106+
itBehavesAsDisposable((v) => DisposableResult.fail(v, () => {}))
107+
108+
it("unwrap calls the onUnwrap", () => {
109+
let calledWith: unknown = undefined
110+
const expectedError = new Error("will throw")
111+
const result = DisposableResult.fail("error", (v) => {
112+
calledWith = v
113+
throw expectedError
114+
})
115+
assert.throws(
116+
() => result.unwrap(),
117+
(e) => e === expectedError,
118+
)
119+
assert.strictEqual(calledWith, result)
120+
})
121+
122+
it("throws even if unwrap doesnt", () => {
123+
const result = DisposableResult.fail("error", () => {})
124+
assert.throws(() => result.unwrap())
125+
})
126+
127+
it("unwrapOr returns the fallback", () => {
128+
const fallback = {}
129+
const result = DisposableResult.fail("error", () => {})
130+
assert.strictEqual(result.unwrapOr(fallback), fallback)
131+
})
132+
})
133+
})

packages/quickjs-emscripten-core/src/lifetime.ts

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SuccessOrFail } from "./vm-interface"
1+
import type { SuccessOrFail } from "./vm-interface"
22
import type { MaybeAsyncBlock } from "./asyncify-helpers"
33
import { maybeAsync } from "./asyncify-helpers"
44
import { QTS_DEBUG } from "./debug"
@@ -376,16 +376,16 @@ function isDisposable(value: unknown): value is { alive: boolean; dispose(): unk
376376
)
377377
}
378378

379-
abstract class AbstractDisposableResult<S, F> extends UsingDisposable {
380-
static success<S, F>(value: S): DisposableSuccess<S, F> {
379+
abstract class AbstractDisposableResult extends UsingDisposable implements Disposable {
380+
static success<S, F>(value: S): DisposableSuccess<S> {
381381
return new DisposableSuccess(value) satisfies SuccessOrFail<S, F>
382382
}
383383

384-
static fail<S, F>(
385-
error: F,
386-
onUnwrap: (status: SuccessOrFail<S, F>) => void,
387-
): DisposableFail<S, F> {
388-
return new DisposableFail(error, onUnwrap) satisfies SuccessOrFail<S, F>
384+
static fail<S, F>(error: F, onUnwrap: (status: SuccessOrFail<S, F>) => void): DisposableFail<F> {
385+
return new DisposableFail(
386+
error,
387+
onUnwrap as (status: SuccessOrFail<never, F>) => void,
388+
) satisfies SuccessOrFail<S, F>
389389
}
390390

391391
static is<S, F>(result: SuccessOrFail<S, F>): result is DisposableResult<S, F> {
@@ -394,11 +394,9 @@ abstract class AbstractDisposableResult<S, F> extends UsingDisposable {
394394

395395
abstract get alive(): boolean
396396
abstract dispose(): void
397-
abstract unwrap(): S
398-
abstract unwrapOr<T>(fallback: T): S | T
399397
}
400398

401-
export class DisposableSuccess<S, F> extends AbstractDisposableResult<S, F> {
399+
export class DisposableSuccess<S> extends AbstractDisposableResult {
402400
declare error?: undefined
403401

404402
constructor(readonly value: S) {
@@ -415,19 +413,19 @@ export class DisposableSuccess<S, F> extends AbstractDisposableResult<S, F> {
415413
}
416414
}
417415

418-
override unwrap(): S {
416+
unwrap(): S {
419417
return this.value
420418
}
421419

422-
override unwrapOr<T>(_fallback: T): S | T {
420+
unwrapOr<T>(_fallback: T): S | T {
423421
return this.value
424422
}
425423
}
426424

427-
export class DisposableFail<S, F> extends AbstractDisposableResult<S, F> {
425+
export class DisposableFail<F> extends AbstractDisposableResult {
428426
constructor(
429427
readonly error: F,
430-
private readonly onUnwrap: (status: SuccessOrFail<S, F>) => void,
428+
private readonly onUnwrap: (status: SuccessOrFail<never, F>) => void,
431429
) {
432430
super()
433431
}
@@ -437,22 +435,20 @@ export class DisposableFail<S, F> extends AbstractDisposableResult<S, F> {
437435
}
438436

439437
override dispose(): void {
440-
throw new Error("Method not implemented.")
438+
if (isDisposable(this.error)) {
439+
this.error.dispose()
440+
}
441441
}
442442

443-
override unwrap(): S {
443+
unwrap(): never {
444444
this.onUnwrap(this)
445445
throw this.error
446446
}
447447

448-
override unwrapOr<T>(fallback: T): S | T {
448+
unwrapOr<T>(fallback: T): T {
449449
return fallback
450450
}
451-
452-
cast<S2 = never>(): DisposableFail<S2, F> {
453-
return this as unknown as DisposableFail<S2, F>
454-
}
455451
}
456452

457-
export type DisposableResult<S, F> = DisposableSuccess<S, F> | DisposableFail<S, F>
453+
export type DisposableResult<S, F> = DisposableSuccess<S> | DisposableFail<F>
458454
export const DisposableResult = AbstractDisposableResult

0 commit comments

Comments
 (0)