In typescript:
let str: string = 'abc';
let val: unknown = 'test';
if (typeof val === 'string') {
str = val;
}
// this code does not report any error, everything works fine.
but, if I change the code a little bit:
if ((typeof val) === 'string') {
str = val;
}
// add the () to hold typeof val;
// error report in typescript in this line: str = val !
TS Playground link
This is really confusing me, who can help to explain what happened here.
TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared is called narrowing.
What does ?: mean in TypeScript? Using a question mark followed by a colon ( ?: ) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined .
The "Property does not exist on type Object" error occurs when we try to access a property that is not contained in the object's type. To solve the error, type the object properties explicitly or use a type with variable key names.
TypeScript's typeof
type guards walk a fine line. typeof val
is a string, and you can do arbitrary string operations to it, but typeof val === "string"
is a special construction that narrows the type of val
when the expression is true
. Consequently, TypeScript is explicitly programmed to match typeof ${reference} ${op} ${literal}
and ${literal} ${op} typeof ${reference}
(for op = ==
, !=
, ===
, and !==
), but typeof ${reference}
has no tolerance built in for parentheses (which is a SyntaxKind.ParenthesizedExpression
and not a SyntaxKind.TypeOfExpression
), string manipulation, or anything else.
TypeScript lead Ryan Cavanaugh describes this in microsoft/TypeScript#42203, "typeof type narrowing acts differently with equivalent parentheses grouping", with gratitude to jcalz for the link:
Narrowings only occur on predefined syntactic patterns, and this isn't one of them. I could see wanting to add parens here for clarity, though -- we should detect this one too.
It sounds like this is a candidate for a future fix, though even if the pattern were added, you would still be somewhat limited in the complexity of typeof
expressions that work as type guards.
From compiler source microsoft/TypeScript main/src/compiler/checker.ts
, comments mine:
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
// ...
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
const operator = expr.operatorToken.kind;
const left = getReferenceCandidate(expr.left);
const right = getReferenceCandidate(expr.right);
// Check that the left is typeof and the right is a string literal...
if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue);
}
// ...or the opposite...
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
}
// ...or skip it and move on. Don't bother trying to remove parentheses
// or doing anything else clever to try to make arbitrary expressions work.
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
// ...
}
return type;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With