@tomatommy

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

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

Q&A

Closed

質問

下記の [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

Comments

  1. @tomatommy

    Questioner

    @yp_s10

    雑な書き換えですが、下記のように記述した際に機能しないのと同じ理屈、ということになりますか?

    type CustomUnion = { class: {a:1} ; val: 'number' } | { class: {b:2}; val: 'string' };
    declare const customUnion: CustomUnion
    
    if (customUnion.class === {a:1}) {
      
      const wrong: {b:1} = customUnion.class // <- これは型エラーとでる. 想定通り
      const correct: {a:1} = customUnion.class // <- これは型エラーでない. 想定通り
    
      const why = customUnion.val // <- [why] なぜか 'number' | 'string' と推論. 'number' じゃないの?
    }
    
  2. はい、その認識であっています。
    customUnion.class === {a:1}
    この比較がcustomUnion.valを絞り込むにあたっての根拠として採用していません。

  3. @tomatommy

    Questioner

    ご回答ありがとうございます!

Your answer might help someone💌