Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
750 views
in Technique[技术] by (71.8m points)

typescript - Implementing a generic function with a conditional return type

I'm trying a very basic (contrived) conditional type function and getting unexpected errors:

function test<
  T
>(
  maybeNumber: T
): T extends number ? number : string {
  if (typeof maybeNumber === 'number') {
    return maybeNumber // Type 'T & number' is not assignable to type 'T extends number ? number : string'.
  }

  return 'Not a number' // Type '"Not a number"' is not assignable to type 'T extends number ? number : string'.
}

I thought that this was a pretty straightforward usage of a conditional type so not sure what's going on. Any ideas?

To clarify, I’m not really trying to implement this specific function. I’m just experimenting with conditional types and want to better understand why this doesn’t actually work.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The underlying issue is that TypeScript's compiler does not narrow the type of a generic type variable via control flow analysis. When you check (typeof maybeNumber === "number"), the compiler can narrow the value maybeNumber to number, but it does not narrow the type parameter T to number. And therefore it cannot verify that it's safe to assign a number value to the return type T extends number ? number : string. The compiler would have to perform some analysis it currently does not do, such as "okay, if typeof maybeNumber === "number", and we inferred T from the type of maybeNumber alone, then inside this block we can narrow T to number, and therefore we should return a value of type number extends number ? number : string, a.k.a., number". But this doesn't happen.

This is quite a pain point with generic functions with conditional return types. The canonical open GitHub issue about this is probably microsoft/TypeScript#33912, but there are a bunch of other GitHub issues out there where this is the main problem.

So that's the answer to "why doesn't this work"?


If you're not interested in refactoring to get this to work, you can ignore the rest, but it might still be instructive to know what to do in this situation instead of waiting for the language to change.

The most straightforward workaround here that maintains your call signature is to make your function a single signature overload where the implementation signature is not generic. This essentially loosens the type safety guarantees inside the implementation:

type MyConditional<T> = T extends number ? number : string;
type Unknown = string | number | boolean | {} | null | undefined;

function test<T>(maybeNumber: T): MyConditional<T>;
function test(maybeNumber: Unknown): MyConditional<Unknown> {
  if (typeof maybeNumber === 'number') {
    const ret: MyConditional<typeof maybeNumber> = maybeNumber;
    return ret;
  }
  const ret: MyConditional<typeof maybeNumber> = "Not a number";
  return ret;
}

Here I've gone about as far as I can go to try to guarantee type safety, by using a temporary ret variable annotated as MyConditional<typeof maybeNumber> which uses the control-flow-analysis-narrowed type of maybeNumber. This will at least complain if you switch around the check (turn === into !== to verify). But usually I just do something simpler like this and let the chips fall where they may:

function test2<T>(maybeNumber: T): MyConditional<T>;
function test2(maybeNumber: any): string | number {
  if (typeof maybeNumber === 'number') {
    return maybeNumber;
  }
  return "Not a number";
}

Okay, hope that helps; good luck!

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...