0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ジェネリクス関数で引数の制約はできるが、関数内で引数を合成して得られる値は動的解析するしかない

Last updated at Posted at 2024-11-27

ことの発端

思えば当たり前なのだが、久々に小一時間ハマった。(実際は3時間)
最近歳のせいか、鈍った自分へ喝をいれるため投稿。

具体例

function test<T, K extends keyof T>(
  data: T,
  key: T[K] extends string ? K : never,
) {
  const x: string = data[key]; // error: 残念(泣)
}

interface X {
  s: string;
  n: number;
}

const x: X = {s: 'string', n: 1};

test(x, 's');
test(x, 'n'); // error: 期待通り

説明

  • test() は第1引数にオブジェクト T、第2引数にオブジェクトのプロパティ K を渡すことを想定
  • オブジェクトのプロパティ T[K]【 値 】string のみにしたいことを表明するため key: T[K] extends string ? K : never として制約
  • test(x, 'n') がエラーとなりハッピー
  • ところが、test() 内で data[key]string として認識しない
Type 'T[T[K] extends string ? K : never]' is not assignable to type 'string'.
  Type 'T[K]' is not assignable to type 'string'.
    Type 'T[keyof T]' is not assignable to type 'string'.
      Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
        Type 'T[string]' is not assignable to type 'string'.ts(2322)

つまり

呼び出し時は test() の第2引数を制約することはできるけど、制約された datakey を使って得られる値には制約が及ばない。
ここで as を使いたくないのであれば、実行コストがかかるが typeof を使わないといけない。

【対応1】asに負ける
function test<T, K extends keyof T>(
  data: T,
  key: T[K] extends string ? K : never,
) {
  const x: string = data[key] as string;
}
【対応2】asに屈しない
function test<T, K extends keyof T>(
  data: T,
  key: T[K] extends string ? K : never,
) {
  const x = data[key];
  if(typeof x !== 'string') {
    throw new Error('Invalid type');
  }
  // x は string 型
}

しっかり型管理されているのであれば 【対応1】 で良いと思う。しかしながら、型管理されているということは極論 as なんか使わずに型を伝搬し続けることを意味するので、コード上に as が登場するのは異常事態1であるので、相当なコメントを書くなどして、この as の正当性をしっかりと説明する必要がある2
また、データが外部から与えられた動的なものの可能性がある場合、パフォーマンスを考えるより堅牢性を考え 【対応2】になる。

  1. WebAPIとかで得られるJSONのように型が無い世界からデータをお迎えする場合は異常事態ではないが、かなり気を遣う場面であることには違いない。

  2. とは言え、そんなコメントを書いても as を平然と書く輩がいると、そう遠くない未来にプロジェクトは炎上か破綻する。まぁ、ESLinthusky で抑止はできるけど、本質的な問題を理解していないと誰も幸せになりません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?