Skip to content

Commit daa1e2b

Browse files
authored
fix(immutable-arraybuffer): update to recent spec (#2688)
Closes: tc39/proposal-immutable-arraybuffer#26 Refs: https://github.com/tc39/proposal-immutable-arraybuffer tc39/proposal-immutable-arraybuffer#15 tc39/proposal-immutable-arraybuffer#9 tc39/proposal-immutable-arraybuffer#16 ## Description Since the last work on this immutable-arraybuffer shim, at the tc39 plenary we decided that - `transferToImmutable` should take an optional `newLength` parameter, to stay parallel to the other `transfer*` methods. - The `sliceToImmutable` method should be added. The spec at https://github.com/tc39/proposal-immutable-arraybuffer and the Moddable XS implementation both already reflect these changes. This PR brings this shim up to date with those changes, closing tc39/proposal-immutable-arraybuffer#26 ### Security Considerations none ### Scaling Considerations none ### Documentation Considerations The proposal's draft spec has already been updated. ### Testing Considerations New tests lightly test the new functionality. The code and tests may differ from the current draft spec on order of operations and errors thrown. But since these issues are purposely still open tc39/proposal-immutable-arraybuffer#16 , this divergence is not yet a big deal. ### Compatibility Considerations No more than the baseline shim already on master. ### Upgrade Considerations No production code yet depends on this shim. So, none.
1 parent e02b0f6 commit daa1e2b

File tree

4 files changed

+223
-13
lines changed

4 files changed

+223
-13
lines changed

packages/immutable-arraybuffer/index.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/* global globalThis */
22

3-
const { setPrototypeOf, getOwnPropertyDescriptor } = Object;
3+
const { setPrototypeOf, getOwnPropertyDescriptors } = Object;
44
const { apply } = Reflect;
55
const { prototype: arrayBufferPrototype } = ArrayBuffer;
66

77
const {
88
slice,
9-
// @ts-expect-error At the time of this writing, the `ArrayBuffer` type built
9+
// TODO used to be a-ts-expect-error, but my local IDE's TS server
10+
// seems to use a more recent definition of the `ArrayBuffer` type.
11+
// @ts-ignore At the time of this writing, the `ArrayBuffer` type built
1012
// into TypeScript does not know about the recent standard `transfer` method.
1113
// Indeed, the `transfer` method is absent from Node <= 20.
1214
transfer,
@@ -99,8 +101,13 @@ class ImmutableArrayBufferInternal {
99101
return true;
100102
}
101103

102-
slice(begin = undefined, end = undefined) {
103-
return arrayBufferSlice(this.#buffer, begin, end);
104+
slice(start = undefined, end = undefined) {
105+
return arrayBufferSlice(this.#buffer, start, end);
106+
}
107+
108+
sliceToImmutable(start = undefined, end = undefined) {
109+
// eslint-disable-next-line no-use-before-define
110+
return sliceBufferToImmutable(this.#buffer, start, end);
104111
}
105112

106113
resize(_newByteLength = undefined) {
@@ -129,17 +136,36 @@ const immutableArrayBufferPrototype = ImmutableArrayBufferInternal.prototype;
129136
delete immutableArrayBufferPrototype.constructor;
130137

131138
const {
132-
// @ts-expect-error We know it is there.
133-
get: isImmutableGetter,
134-
} = getOwnPropertyDescriptor(immutableArrayBufferPrototype, 'immutable');
139+
slice: { value: sliceOfImmutable },
140+
immutable: { get: isImmutableGetter },
141+
} = getOwnPropertyDescriptors(immutableArrayBufferPrototype);
135142

136143
setPrototypeOf(immutableArrayBufferPrototype, arrayBufferPrototype);
137144

138-
export const transferBufferToImmutable = buffer =>
139-
new ImmutableArrayBufferInternal(buffer);
145+
export const transferBufferToImmutable = (buffer, newLength = undefined) => {
146+
if (newLength !== undefined) {
147+
if (transfer) {
148+
buffer = apply(transfer, buffer, [newLength]);
149+
} else {
150+
buffer = arrayBufferTransfer(buffer);
151+
const oldLength = buffer.byteLength;
152+
// eslint-disable-next-line @endo/restrict-comparison-operands
153+
if (newLength <= oldLength) {
154+
buffer = arrayBufferSlice(buffer, 0, newLength);
155+
} else {
156+
const oldTA = new Uint8Array(buffer);
157+
const newTA = new Uint8Array(newLength);
158+
newTA.set(oldTA);
159+
buffer = newTA.buffer;
160+
}
161+
}
162+
}
163+
return new ImmutableArrayBufferInternal(buffer);
164+
};
140165

141166
export const isBufferImmutable = buffer => {
142167
try {
168+
// @ts-expect-error Getter should be typed as this-sensitive
143169
return apply(isImmutableGetter, buffer, []);
144170
} catch (err) {
145171
if (err instanceof TypeError) {
@@ -150,3 +176,21 @@ export const isBufferImmutable = buffer => {
150176
throw err;
151177
}
152178
};
179+
180+
const sliceBuffer = (buffer, start = undefined, end = undefined) => {
181+
try {
182+
// @ts-expect-error We know it is really there
183+
return apply(sliceOfImmutable, buffer, [start, end]);
184+
} catch (err) {
185+
if (err instanceof TypeError) {
186+
return arrayBufferSlice(buffer, start, end);
187+
}
188+
throw err;
189+
}
190+
};
191+
192+
export const sliceBufferToImmutable = (
193+
buffer,
194+
start = undefined,
195+
end = undefined,
196+
) => transferBufferToImmutable(sliceBuffer(buffer, start, end));

packages/immutable-arraybuffer/shim.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { transferBufferToImmutable, isBufferImmutable } from './index.js';
1+
import {
2+
transferBufferToImmutable,
3+
isBufferImmutable,
4+
sliceBufferToImmutable,
5+
} from './index.js';
26

37
const { getOwnPropertyDescriptors, defineProperties } = Object;
48
const { prototype: arrayBufferPrototype } = ArrayBuffer;
59

610
const arrayBufferMethods = {
7-
transferToImmutable() {
8-
return transferBufferToImmutable(this);
11+
transferToImmutable(newLength = undefined) {
12+
return transferBufferToImmutable(this, newLength);
13+
},
14+
sliceToImmutable(start = undefined, end = undefined) {
15+
return sliceBufferToImmutable(this, start, end);
916
},
1017
get immutable() {
1118
return isBufferImmutable(this);

packages/immutable-arraybuffer/test/index.test.js

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import test from 'ava';
22
import {
33
transferBufferToImmutable,
4-
// isBufferImmutable,
4+
isBufferImmutable,
5+
sliceBufferToImmutable,
56
} from '../index.js';
67

78
const { isFrozen, getPrototypeOf } = Object;
@@ -124,3 +125,82 @@ test('TypedArray on Immutable ArrayBuffer ponyfill limitations', t => {
124125
const ta3 = new Uint8Array(iab);
125126
t.is(ta3.byteLength, 0);
126127
});
128+
129+
const testTransfer = t => {
130+
const ta12 = new Uint8Array([3, 4, 5]);
131+
const ab12 = ta12.buffer;
132+
t.is(ab12.byteLength, 3);
133+
t.deepEqual([...ta12], [3, 4, 5]);
134+
135+
const ab2 = ab12.transfer(5);
136+
t.false(isBufferImmutable(ab2));
137+
t.is(ab2.byteLength, 5);
138+
t.is(ab12.byteLength, 0);
139+
const ta2 = new Uint8Array(ab2);
140+
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);
141+
142+
const ta13 = new Uint8Array([3, 4, 5]);
143+
const ab13 = ta13.buffer;
144+
145+
const ab3 = ab13.transfer(2);
146+
t.false(isBufferImmutable(ab3));
147+
t.is(ab3.byteLength, 2);
148+
t.is(ab13.byteLength, 0);
149+
const ta3 = new Uint8Array(ab3);
150+
t.deepEqual([...ta3], [3, 4]);
151+
};
152+
153+
{
154+
// `transfer` is absent in Node <= 20. Present in Node >= 22
155+
const maybeTest = 'transfer' in ArrayBuffer.prototype ? test : test.skip;
156+
maybeTest('Standard buf.transfer(newLength) behavior baseline', testTransfer);
157+
}
158+
159+
test('Analogous transferBufferToImmutable(buf, newLength) ponyfill', t => {
160+
const ta12 = new Uint8Array([3, 4, 5]);
161+
const ab12 = ta12.buffer;
162+
t.is(ab12.byteLength, 3);
163+
t.deepEqual([...ta12], [3, 4, 5]);
164+
165+
const ab2 = transferBufferToImmutable(ab12, 5);
166+
t.true(isBufferImmutable(ab2));
167+
t.is(ab2.byteLength, 5);
168+
t.is(ab12.byteLength, 0);
169+
// slice needed due to ponyfill limitations.
170+
const ta2 = new Uint8Array(ab2.slice());
171+
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);
172+
173+
const ta13 = new Uint8Array([3, 4, 5]);
174+
const ab13 = ta13.buffer;
175+
176+
const ab3 = transferBufferToImmutable(ab13, 2);
177+
t.true(isBufferImmutable(ab3));
178+
t.is(ab3.byteLength, 2);
179+
t.is(ab13.byteLength, 0);
180+
// slice needed due to ponyfill limitations.
181+
const ta3 = new Uint8Array(ab3.slice());
182+
t.deepEqual([...ta3], [3, 4]);
183+
});
184+
185+
test('sliceBufferToImmutable ponyfill', t => {
186+
const ta12 = new Uint8Array([3, 4, 5]);
187+
const ab12 = ta12.buffer;
188+
t.is(ab12.byteLength, 3);
189+
t.deepEqual([...ta12], [3, 4, 5]);
190+
191+
const ab2 = sliceBufferToImmutable(ab12, 1, 5);
192+
t.true(isBufferImmutable(ab2));
193+
t.is(ab2.byteLength, 2);
194+
t.is(ab12.byteLength, 3);
195+
// slice needed due to ponyfill limitations.
196+
const ta2 = new Uint8Array(ab2.slice());
197+
t.deepEqual([...ta2], [4, 5]);
198+
199+
const ab3 = sliceBufferToImmutable(ab2, 1, 2);
200+
t.true(isBufferImmutable(ab3));
201+
t.is(ab3.byteLength, 1);
202+
t.is(ab2.byteLength, 2);
203+
// slice needed due to ponyfill limitations.
204+
const ta3 = new Uint8Array(ab3.slice());
205+
t.deepEqual([...ta3], [5]);
206+
});

packages/immutable-arraybuffer/test/shim.test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,82 @@ test('TypedArray on Immutable ArrayBuffer shim limitations', t => {
121121
const ta3 = new Uint8Array(iab);
122122
t.is(ta3.byteLength, 0);
123123
});
124+
125+
const testTransfer = t => {
126+
const ta12 = new Uint8Array([3, 4, 5]);
127+
const ab12 = ta12.buffer;
128+
t.is(ab12.byteLength, 3);
129+
t.deepEqual([...ta12], [3, 4, 5]);
130+
131+
const ab2 = ab12.transfer(5);
132+
t.false(ab2.immutable);
133+
t.is(ab2.byteLength, 5);
134+
t.is(ab12.byteLength, 0);
135+
const ta2 = new Uint8Array(ab2);
136+
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);
137+
138+
const ta13 = new Uint8Array([3, 4, 5]);
139+
const ab13 = ta13.buffer;
140+
141+
const ab3 = ab13.transfer(2);
142+
t.false(ab3.immutable);
143+
t.is(ab3.byteLength, 2);
144+
t.is(ab13.byteLength, 0);
145+
const ta3 = new Uint8Array(ab3);
146+
t.deepEqual([...ta3], [3, 4]);
147+
};
148+
149+
{
150+
// `transfer` is absent in Node <= 20. Present in Node >= 22
151+
const maybeTest = 'transfer' in ArrayBuffer.prototype ? test : test.skip;
152+
maybeTest('Standard buf.transfer(newLength) behavior baseline', testTransfer);
153+
}
154+
155+
test('Analogous buf.transferToImmutable(newLength) shim', t => {
156+
const ta12 = new Uint8Array([3, 4, 5]);
157+
const ab12 = ta12.buffer;
158+
t.is(ab12.byteLength, 3);
159+
t.deepEqual([...ta12], [3, 4, 5]);
160+
161+
const ab2 = ab12.transferToImmutable(5);
162+
t.true(ab2.immutable);
163+
t.is(ab2.byteLength, 5);
164+
t.is(ab12.byteLength, 0);
165+
// slice needed due to ponyfill limitations.
166+
const ta2 = new Uint8Array(ab2.slice());
167+
t.deepEqual([...ta2], [3, 4, 5, 0, 0]);
168+
169+
const ta13 = new Uint8Array([3, 4, 5]);
170+
const ab13 = ta13.buffer;
171+
172+
const ab3 = ab13.transferToImmutable(2);
173+
t.true(ab3.immutable);
174+
t.is(ab3.byteLength, 2);
175+
t.is(ab13.byteLength, 0);
176+
// slice needed due to ponyfill limitations.
177+
const ta3 = new Uint8Array(ab3.slice());
178+
t.deepEqual([...ta3], [3, 4]);
179+
});
180+
181+
test('sliceToImmutable shim', t => {
182+
const ta12 = new Uint8Array([3, 4, 5]);
183+
const ab12 = ta12.buffer;
184+
t.is(ab12.byteLength, 3);
185+
t.deepEqual([...ta12], [3, 4, 5]);
186+
187+
const ab2 = ab12.sliceToImmutable(1, 5);
188+
t.true(ab2.immutable);
189+
t.is(ab2.byteLength, 2);
190+
t.is(ab12.byteLength, 3);
191+
// slice needed due to ponyfill limitations.
192+
const ta2 = new Uint8Array(ab2.slice());
193+
t.deepEqual([...ta2], [4, 5]);
194+
195+
const ab3 = ab2.sliceToImmutable(1, 2);
196+
t.true(ab3.immutable);
197+
t.is(ab3.byteLength, 1);
198+
t.is(ab2.byteLength, 2);
199+
// slice needed due to ponyfill limitations.
200+
const ta3 = new Uint8Array(ab3.slice());
201+
t.deepEqual([...ta3], [5]);
202+
});

0 commit comments

Comments
 (0)