Upgrade to Pro — share decks privately, control downloads, hide ads and more …

配列にまつわる型検査をしたら思ったより大変だった話

 配列にまつわる型検査をしたら思ったより大変だった話

Kenta TSUNEMI

February 05, 2025
Tweet

More Decks by Kenta TSUNEMI

Other Decks in Technology

Transcript

  1. const ATTRS = ['id', 'type', 'class'] as const; という値があったときに、ある配列がこれらの要素を 過不足なく

    順不同で 含んでいるということを担保する型を作りたい。 3 実現したかったこと
  2. const ATTRS = ['id', 'class', 'type'] as const; type SugoiKata

    = ??? const ok1: SugoiKata = ['id', 'class', 'type'] // const ok2: SugoiKata = ['type', 'id', 'class'] // 順不同は許容 const ng1: SugoiKata = ['id', 'class'] // type が不足 const ng2: SugoiKata = ['id', 'class', 'type', 'for'] // for が余分 4 実現したかったこと
  3. 値の不足を検知できない const ATTRS = ['id', 'class', 'type'] as const; type

    Sugokunai = (typeof ATTRS)[number][]; // ^? type Sugokunai = ("id" | "class" | "type")[] const ok1: Sugokunai = ['id', 'class', 'type'] // const ok2: Sugokunai = ['type', 'id', 'class'] // const ng1: Sugokunai = ['id', 'class'] // 不足が検知できない const ng2: Sugokunai = ['id', 'class', 'type', 'for'] // : 期待通り : 期待通りでない 5 配列の型を作ってみる
  4. 順番の違いや重複でエラーになってしまう const ATTRS = ['id', 'class', 'type'] as const; type

    Sugokunai = typeof ATTRS; // ^? type Sugokunai = readonly ["id", "type", "class"] const ok1: Sugokunai = ['id', 'class', 'type'] // const ok2: Sugokunai = ['type', 'id', 'class'] // 順番が固定される const ng1: Sugokunai = ['id', 'class'] // const ng2: Sugokunai = ['id', 'class', 'type', 'for'] // : 期待通り : 期待通りでない 6 タプルにしてみる
  5. const ATTRS = ['id', 'class', 'type'] as const; type Diff<

    T extends readonly string[], U extends readonly string[] > = | Exclude<T[number], U[number]> | Exclude<U[number], T[number]> type HasSameElements< T extends readonly string[], U extends readonly string[] > = Diff<T, U> extends never ? true : false const ok1 = ['id', 'class', 'type'] as const; true satisfies HasSameElements<typeof ok1, typeof ATTRS> 7 どうにか頑張ってみる
  6. 過不足がなければ Diff<T> は never になる const ATTRS = ['id', 'class',

    'type'] as const; type Diff< T extends readonly string[], U extends readonly string[] > = | Exclude<T[number], U[number]> | Exclude<U[number], T[number]> type A = Diff<['id', 'class', 'type'], ATTRS> // ^? type A = never type B = Diff<['id', 'type'], ATTRS> // ^? type B = "class" type C = Diff<['id', 'type', 'class', 'for'], ATTRS> // ^? type C = "for" 8 Diff は何をしているか?
  7. Diff<T> の結果を boolean に変換した上で検査する // Diff<T> の結果を boolean に変換する型 type

    HasSameElements< T extends readonly string[], U extends readonly string[] > = Diff<T, U> extends never ? true : false // HasSameElements が false = 過不足がある場合は型エラー true satisfies HasSameElements< ['id', 'class', 'type'], typeof ATTRS > // OK true satisfies HasSameElements< ['id', 'class'], typeof ATTRS > // 型エラー 9 satisfies での検査
  8. const attrs = ['id', 'class', 'type'] as const; true satisfies

    HasSameElements<typeof attrs, typeof ATTRS> なんか微妙 type SugoiKata = ... const attrs: SugoiKata = ['type', 'id', 'class']; こういう形にしたかった... もっとシンプルにできないものか アイデアがあれば教えてください! 11 できたけど...