-
Notifications
You must be signed in to change notification settings - Fork 12
structuredCloneについて #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
脳筋な実装をしたのは |
提案ありがとうございます。structuredCloneの型をより安全にするのはよいアイデアだと思いますが、 |
TypeScript本体の型定義では、次の手法が紹介されていました。 type Cloneable<T> = T extends Function | Symbol
? never
: T extends Record<any, any>
? {-readonly [k in keyof T]: Cloneable<T[k]>}
: T
declare function structuredClone<T>(value: Cloneable<T>, options?: StructuredSerializeOptions | undefined): Cloneable<T> これはメンテナンス性が高い一方、本来削ぎ落とさなければならないプロパティが存在したままになってしまいます: class MyError extends Error {
public hi: string | undefined = "";
}
// TS2339。プロパティが存在しない。
const y = structuredClone(new Error()).hi;
// ^?
// TSではエラーにならない。しかし実行時にstructuredCloneはErrorを返すので実行時エラー。
const test = structuredClone(new MyError()).hi;
// ^? 妥当な案としては |
type Unit = [];
type Weaken = [
RangeError, ReferenceError, TypeError, SyntaxError, URIError, Error, Boolean, String, Date, RegExp, Blob, File, FileList,
Int8Array, Int16Array, Int32Array, BigInt64Array, Uint8Array, Uint16Array, Uint32Array, BigUint64Array, Uint8ClampedArray,
DataView, ImageBitmap, ImageData
];
type WeakenN<PI extends readonly [...Unit[]]> = Weaken[PI["length"]];
type StructuredCloneOutput<T> = RecurseHelper<T, Unit>
type RecurseHelper<T, PI extends readonly [...Unit[]]> =
T extends Function | Symbol
? never
: T extends Map<infer K, infer V> // weaken
? Map<K, V>
: T extends Set<infer E> // weaken
? Set<E>
: T extends Record<any, any>
? T extends WeakenN<PI>
? WeakenN<PI>
: PI["length"] extends Weaken["length"]
? {-readonly [k in keyof T]: StructuredCloneOutput<T[k]>}
: RecurseHelper<T, [...PI, Unit]>
: T
declare function structuredClone<const T>(value: T, options?: StructuredSerializeOptions | undefined): StructuredCloneOutput<T> ということであれやこれややって、インデントの二乗に比例しないようなコードを得ることができたと思います。 |
TS自体の再帰制限がそれほど変わらなかったので、構文上で再帰を行わない実装にしました。 コードtype Basics = [RangeError, ReferenceError, TypeError, SyntaxError, URIError, Error, Boolean, String, Date, RegExp]
type DOMSpecifics = [
DOMException,
DOMMatrix,
DOMMatrixReadOnly,
DOMPoint,
DOMPointReadOnly,
DOMQuad,
DOMRect,
DOMRectReadOnly,
]
type FileSystemTypeFamily = [
FileSystemDirectoryHandle,
FileSystemFileHandle,
FileSystemHandle,
]
type WebGPURelatedTypeFamily = [
// GPUCompilationInfo,
// GPUCompilationMessage,
]
type TypedArrayFamily = [
Int8Array, Int16Array, Int32Array, BigInt64Array, Uint8Array, Uint16Array, Uint32Array, BigUint64Array, Uint8ClampedArray,
]
type Weaken = [
...Basics,
// AudioData,
Blob,
// CropTarget,
// CryptoTarget,
...DOMSpecifics,
...FileSystemTypeFamily,
...WebGPURelatedTypeFamily,
File, FileList,
...TypedArrayFamily,
DataView, ImageBitmap, ImageData,
RTCCertificate,
VideoFrame,
];
type MapSubtype<R> = {[k in keyof Weaken]: R extends Weaken[k] ? true : false};
type SelectNumericLiteral<H> = number extends H ? never : H;
type FilterByNumericLiteralKey<R extends Record<string | number, any>> = {[
k in keyof R as `${R[k] extends true ? Exclude<SelectNumericLiteral<k>, symbol> : never}`
]: []};
type HitWeakenEntry<E> = keyof FilterByNumericLiteralKey<MapSubtype<E>>;
type StructuredCloneOutput<T> =
T extends Function | Symbol
? never
: T extends Map<infer K, infer V>
? Map<StructuredCloneOutput<K>, StructuredCloneOutput<V>>
: T extends Set<infer E>
? Set<StructuredCloneOutput<E>>
: T extends Record<any, any>
? HitWeakenEntry<T> extends never
? {-readonly [k in keyof T]: StructuredCloneOutput<T[k]>}
// hit
: Weaken[HitWeakenEntry<T>]
: T
declare function structuredClone<const T>(
value: T, options?: StructuredSerializeOptions | undefined
): StructuredCloneOutput<T>
class Weirdo extends Int16Array {
public weirdo: undefined = undefined;
}
class Weirdo2 extends Int32Array {
public weirdo2: undefined = undefined;
}
const a: 1 = structuredClone(1);
const b: Int16Array = structuredClone(new Int16Array());
// @ts-expect-error property do not exist
const c: undefined = structuredClone(new Weirdo()).weirdo;
const f = [new Weirdo()] as const;
const g: [Int16Array] = structuredClone(f);
const h = new Map([[new Weirdo(), new Weirdo2()]]);
const i: Map<Int16Array, Int32Array> = structuredClone(h);
// @ts-expect-error weaken types
const i_: Map<Weirdo, Weirdo2> = structuredClone(h);
const j = new Set([new Weirdo()]);
const k: Set<Int16Array> = structuredClone(j);
// @ts-expect-error weaken type
const k_: Set<Weirdo> = structuredClone(j);
// not cloneable
const m: never = structuredClone(class {});
// not cloneable
const n: never = structuredClone(Symbol.iterator);
// not cloneable
const p: never = structuredClone(() => 1);
const r = structuredClone({ a: 1, b: 2, c: 3 }); |
ありがとうございます。cloneできないときに返り値がneverになるのは型安全性の面でまずいと考えており、structuredCloneの呼び出し自体に型エラーが発生するような定義が望ましいと考えています。(いわゆるinvalid typeが欲しくなる……)
|
型について再帰的に言及するために、 |
// 承前
type AvoidCyclicConstraint<T> = [T] extends [infer R] ? R : never;
declare function x<const T extends StructuredCloneOutput<AvoidCyclicConstraint<T>>>(a: T): StructuredCloneOutput<T>;
x(() => 1); できたにはできました。以下は動作原理の説明です。
ただ、これを |
関数として呼び出せるシグネチャのプロパティが残存する問題と、配列及びタプルのreadonly修飾子が消えない問題を修正しました: playground。 |
こちらお待たせしました。この定義を取り入れる方向で試していますが、次のケースで想定通りの結果が出ないようです。自分も見ていますが、修正に協力いただけると助かります 😇 type B = StructuredCloneOutput<{a: Weirdo}>; |
|
↑これは若干不正確で、正確には以下のような作用です。
|
structuredClone
についてこの型定義ライブラリでオーバーライドされていると便利かなと思うのですがいかがでしょうか?標準ライブラリのそれは今の所<T = any>(val: T, options?: StructuredSerializeOptions): T
というシグネチャになっており、うっかりすると実行時エラーを引き起こしそうです (例えば殆どのクラスはコンストラクタの情報を暗に持つためシリアライズできず、例外が起きます)。提案するだけでは忍びないので実際にたたき台を作ってみました。継承が保存されずに親クラスに弱化されることに注意すると読みやすいと思います。
The text was updated successfully, but these errors were encountered: