This is not a glitch in the system. TypeScript is operating as expected; for further information, refer to microsoft/TypeScript#29980. The compiler lacks the capability to inspect the function body in the desired manner, hence why the type predicate feature was implemented.
Commonly written code in TypeScript is automatically regarded as type guards, which help refine the apparent type of variables or properties. For instance, when x
is of type unknown
, writing
if (typeof x === "string") { console.log(x.toUpperCase()); }
prompts the compiler to treat
(typeof x === "string")
as a
typeof
type guard, narrowing
x
down to
string</code within that block of code.</p>
<p>Regrettably, TypeScript cannot always recognize every user attempt to narrow types in this way. As an example, if you write <code>if (["string"].includes(typeof x)) { console.log(x.toUpperCase()); }
, the compiler raises concerns about
x.toUpperCase
because it does not interpret the
["string"].includes(typeof x)
structure as a type guard.
Despite this, users still desire to create their own type checks. That's precisely why TypeScript introduced user-defined type guard functions. These functions enable users to refactor custom type checking routines into boolean-returning functions with explicitly annotated return types using a type predicate. Hence, even though the compiler may struggle to understand ["string"].includes(typeof x)
, by moving it into a function:
function isString(x: unknown): x is string {
return ["string"].includes(typeof x);
}
Then calling that function like
if (isString(x)) { console.log(x.toUpperCase()); }
delivers the intended narrowing effect.
By annotating the return type as x is string
, you essentially declare that the function's body executes the correct narrowing process. User-defined type guard functions serve the purpose of informing the compiler about things it can't deduce independently. This necessity also gave rise to type assertions.
If the compiler began questioning the function's code internally as requested, it could often undermine the function's very essence. Had the compiler been adept at recognizing incomplete narrowing processes inside the function, the function might not have been necessary at all... or perhaps the type predicative return wouldn't be required.
The issue request at microsoft/TypeScript#29980 urging the compiler to scrutinize user-defined type functions' bodies was turned down due to complexity reasons. As highlighted by the TS dev team lead in this thread:
We typically don't anticipate being able to verify [user-defined type guard functions] thoroughly, since that's the exact reason they were crafted in the first place. Simple type guards we could confirm may as well be inline, and more error-prone guards are likely unverifiable anyway.
View the code on Playground