@tomatommy

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[TypeScript] ここで意図通りに推論をさせる方法は

質問

下記の [why] のとこの理由がわからないです. 解決求む

type CustomUnion = { class: typeof Number ; val: 'number' } | { class: typeof String; val: 'string' };
declare const customUnion: CustomUnion

if (customUnion.class === Number) {
  
  const wrong: typeof String = customUnion.class // <- これは型エラーとでる. 想定通り
  const correct: typeof Number = customUnion.class // <- これは型エラーでない. 想定通り

  const why = customUnion.val // <- [why] なぜか 'number' | 'string' と推論. 'number' じゃないの?
}

playground

暫定解決策

primitive 型で絞ったら可能です.

type CustomUnion = { class: "number" ; val: 'number' } | { class: "string"; val: 'string' };
declare const customUnion: CustomUnion

if (customUnion.class === "number") {
  
  const wrong: "string" = customUnion.class // <- これは型エラーとでる. 想定通り
  const correct: "number" = customUnion.class // <- これは型エラーでない. 想定通り

  const noProblem = customUnion.val // <- "number"
}

class のまま実現できたら嬉しいこと

class の値を generics の値として使える
たとえば、 customUnion の class に応じて、動的な型を生成可能

type GetToString<T> = T extends {"toString": (...args: unknown[]) => unknown } ? ReturnType<T["toString"]> : never
type GenericToString = GetToString<CustomUnion["class"]>
0 likes

1Answer

classプロパティで絞り込んだ場合

この場合、classNumberの場合でも、val'number' | 'string'のままになるのは、型定義で以下の両方が可能だからです。

  • { class: typeof Number; val: 'number' }
  • { class: typeof Number; val: 'string' }

Numberは関数オブジェクト(コンストラクタ)で、参照の比較になります。
関数オブジェクトやオブジェクトリテラルの比較は、TypeScriptが安全と判断できないので、type宣言があっても、関数オブジェクトの比較では型絞り込みされません。

0Like

Your answer might help someone💌