TS 5.7 から ArrayBufferView(各 TypedArray と DataView)の型がジェネリクスになりました
変更情報
【2024/11/29】
- 正式版がリリースされたため、一部記述を修正しました
【2024/10/07】
- 注釈を修正
- 型の記述をわかりやすくした
【2024/10/02】
- タイトルに TS 5.7 を追加
【2024/09/30】
- DefinitelyTyped について追記
結論
次のバージョンである TypeScript 5.7 で ArrayBufferView
(各 TypedArray
と DataView
)の型がジェネリクスになり、ArrayBuffer
と SharedArrayBuffer
のどっちを保持しているのかを型レベルで判定できるようになります。
Uint8Array
の型について一部抜き出してみると以下のようになります。
type ArrayBufferLike = ArrayBuffer | SharedArrayBuffer;
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike> {
readonly buffer: TArrayBuffer;
// ...
slice(start?: number, end?: number): Uint8Array<ArrayBuffer>;
subarray(begin?: number, end?: number): Uint8Array<TArrayBuffer>;
// ...
}
interface Uint8ArrayConstructor {
readonly prototype: Uint8Array<ArrayBufferLike>;
// ...
new (): Uint8Array<ArrayBuffer>;
new (length: number): Uint8Array<ArrayBuffer>;
new (array: ArrayLike<number>): Uint8Array<ArrayBuffer>;
new (elements: Iterable<number>): Uint8Array<ArrayBuffer>;
new <TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(
buffer: TArrayBuffer,
byteOffset?: number,
length?: number,
): Uint8Array<TArrayBuffer>;
// ...
}
ライブラリなどで型情報をパブリッシュしている場合は対応が必要そうです。なお DefinitelyTyped は一括で対応されました(package.json の typesVersions で対応されているため、実質別の方ファイルであると思わなくもない)。
背景
ArrayBuffer
と SharedArrayBuffer
は同じ構造
ES2023 まで TypeScript は型システムに構造的型付けを採用しています。名前が異なっていても構造が一緒の場合同一の型として扱います。
ところで ES2023 まで ArrayBuffer
と SharedArrayBuffer
は構造が一緒です。実際 TypeScript 5.6 で ES2023 (ESNext) をターゲットにした場合、ArrayBuffer
型が期待されている変数に SharedArrayBuffer
を代入できます[1]。
const buffer: ArrayBuffer = new SharedArrayBuffer(256);
これによって ArrayBufferView
の buffer
プロパティが ArrayBufferLike
(ArrayBuffer
と SharedArrayBuffer
のどっちを保持しているかわからない)であるにも関わらず、チェックが緩くなり型エラーが出ていない状況になっていました。
const bytes = new Uint8Array(256); // 型で ArrayBuffer を保持している情報が抜け落ちている
const hash = crypto.subtle.digest(
"SHA-512",
bytes.buffer, // bytes.buffer は ArrayBufferLike だが型エラーにならない
);
ES2024 の型導入にあたって問題が発覚
さて ES2024 として仕様に入った Resizable and growable ArrayBuffers と ArrayBuffer Transfer によって ArrayBuffer
と SharedArrayBuffer
がそれぞれ独立してプロパティやメソッドを持つようになり、構造が一致しなくなりました。
ES2024 の型を TypeScript に組み込むにあたって npm の top 400 のライブラリでチェックした際に広く型エラーを起こしてしまうことが発覚し、先程の問題が顕在化しました。
これは ES2023 まで ArrayBuffer
と SharedArrayBuffer
が同じ構造なのも要因の一つではありますが、主な原因は ArrayBufferView
の buffer
プロパティが ArrayBufferLike
になっていることです。そこで ArrayBufferView
をジェネリクスにし、ArrayBuffer
と SharedArrayBuffer
のどっちを保持しているのか型レベルでわかるようにし解決されることになりました。
const bytes = new Uint8Array(256); // Uint8Array<ArrayBuffer>
const hash = crypto.subtle.digest(
"SHA-512",
bytes.buffer, // bytes.buffer が ArrayBuffer として扱われ、型エラーにならない
);
余談
以前から TypedArray
インターフェースを導入しプロパティやメソッドを共通化することが要望されています。この変更については今回見送られました。
-
互換性の理由から
ArrayBuffer
のSymbol.species
プロパティが文字列リテラル型ではなくstring
として定義されており、逆は成り立たないみたいです。これについてはArrayBuffer
とSharedArrayBuffer
が混ざらないように自分が別の PR を出しています。 ↩︎
Discussion