TypeScript Version: 3.7-beta
Search Terms:
||
or
default
Code
async function loadTasks() {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1') // 1)
let words: string[] = await response.json() // 2)
let s1: string[] = words ?? 100 // 3) s1 is assigned to 4 if words is null / undefined
let s2: string[] = words || 100 // 4) s2 is assigned to 4 if words is falsy
}
loadTasks()
Expected behavior:
Since words is initialized at runtime (from external data), there is a possible type error if the left hand side of the comparisons on lines 3 and 4 is null/undefined/falsy (see code). While that would occur at runtime, the issue can be caught at compile time.
Thus, TypeScript should throw an error / warning since the number 100 would not be a valid assignment to a variable of type string[]. This issue occurs using both the traditional || syntax as well as the new ?? syntax for assigning default values.
Actual behavior:
Typescript does not show any error/warning, and compiles cleanly.
Playground Link: https://www.typescriptlang.org/play/?ts=3.7-Beta#code/IYZwngdgxgBAZgV2gFwJYHsIwDbuAEwBVQBrEACgEoYBvAWACgZmcBTZGAJ1ZAAdMQrGAF4YwAO7BUHOOygALcgHJ5yZLxAAuAPTaAViEy9swKK3nps+VpwB0yML1RR012y4C225K-QhtAIxKlIwsbBzi6Jz4WjAgyJyoEADmANoAuiJiktJcPPwQgrYGmFShLNjscQGacQlJaZmikdEgMAD87TABAAw95cyVHCAATLXxiSkZWS0xMAA+8919jAC+jIy4BMQgZGUMQA
Related Issues: N/A
While the code is hella suspicious (I would never argue otherwise!), from TypeScript's point of view, string[] is always truthy so when it goes to infer a type for the whole expression, control flow analysis gives back string[] and the assignment is therefore allowed. A good way to see that this is in fact what's happening is to turn off strictNullChecks: in that case the analyzer knows words can be falsy and thus flags a type error on the assignment.
The type inference thing is probably a design limitation, but it would at least be nice to get a warning that words is always truthy (for ||) or always non-nullish (for ??) and therefore the expression is suspicious.
(To be clear, this issue only arises in cases where TS thinks the RHS can't possibly be reached based on the types involved. So long as the compiler believes the RHS is reachable, you get your type error as expected.)
a warning that
wordsis always truthy
Did you mean always non-null and non-undefined for ???
Yes; I thought that was implied but probably should have specified to be clear.
Yep, what @fatcerberus said. "I lied to TypeScript and it believed me" -> 🤷♂️
This is actually fairly desirable behavior that predates the never type, because you could have idiomatic code like this
function debugFail(message: string): void {
throw new Error(message);
}
function fn(y: object) {
const x = y || debugFail("oops");
}
where you wouldn't want the assignment to x to fail due to a possible void inhabitant.
@RyanCavanaugh Ah, that explains some things. I understood why the assignment was allowed (RHS is unreachable per control flow analysis) but couldn't figure out why there was no "this condition is always true" error. Your example explains that.
I do note that your "idiomatic" code will tend to fail if strictNullChecks is off, however, because then every type can be falsy.
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Most helpful comment
Yep, what @fatcerberus said. "I lied to TypeScript and it believed me" -> 🤷♂️
This is actually fairly desirable behavior that predates the
nevertype, because you could have idiomatic code like thiswhere you wouldn't want the assignment to
xto fail due to a possiblevoidinhabitant.