Type predicates in TypeScript
Asked Answered
C

1

6

Is this a lack of the feature in TypeScript or something well-considered that TS compiler can not infer (and then narrow down) the type of an argument if the condition logic is wrapped into a separate function? What I have in mind is the error TS compiler gives me in this case:

function isString(s) {
  return typeof s === 'string';
}

function toUpperCase(x: unknown) {
  if(isString(x)) {
    x.toUpperCase(); // ⚡️ x is still of type unknown
  }
}

However if I get rid of the isString function and inline its logic in if statement then TS compiler knows the type well.

I know that I can cast the type using as string or make use of the type predicates and use is string type annotation in isString function. But I just started to think - why TS can not narrow the type of the x.

Congest answered 26/9, 2019 at 23:18 Comment(0)
J
6

One way for this to work would be if the compiler inlined some function calls when performing control flow analysis, meaning that this:

function toUpperCase(x: unknown) {
  if (isString(x)) {
     x.toUpperCase(); 
  }
}

would need to be analyzed as if the body of isString() were expanded out like this:

function toUpperCase(x: unknown) {
  if (typeof x === "string") {
    x.toUpperCase();
  }
}

The canonical issue in GitHub talking about this is microsoft/TypeScript#9998. The question asked in that issue is "When a function is invoked, what should we assume its side effects are?" And there don't seem to be perfect answers. It's said that the full inlining solution for all function calls "wouldn't be even remotely practical". Could shallow inlining be done that behaves as you want for isString() without completely bogging down the compiler? Probably, but is it worth it? Not sure. It doesn't look like there's much progress being made there; if you feel strongly enough about it and have some compelling ideas you can contribute to the GitHub issue.


Another way for this to work would be if the compiler automatically inferred type predicate return types for the right sorts of boolean-returning functions, meaning that this:

function isString(s: unknown) {
  return typeof s === "string";
}

would need to be inferred as if it were this:

function isString(s: unknown): s is string {
  return typeof s === "string";
}

The canonical issue in GitHub talking about this is microsoft/TypeScript#16069. A previous version of this was declined as being too complex because again, it is not practical for the compiler to be able to analyze every boolean-returning function and figure out if there are any type-predicate-like implications for its return type. #16069 is still open, though, presumably with the intent of picking the low-hanging fruit of "simple" functions like x => typeof x === "string". And again, it's not clear if there is any progress being made there, so if you feel strongly enough about it and have some compelling ideas you can contribute to the GitHub issue.


But in neither of those cases would I hold my breath if I were you. The idiomatic solution for TS3.6 would be to just annotate isString() as a user-defined type guard returning s is string and move on to other things.

Okay, hope that helps you. Good luck!

Jeremiah answered 27/9, 2019 at 1:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.