The two comparisons below seem equivalent, but only one gets a type error (playground):
function test(arr: string[]) {
const el = arr[0];
if (el === 2) { } // Error here
if (arr[0] === 2) { } // No error here
}
What's the motivation behind this behavior?
Hard to say, really. But Flow actually allows most mismatched-type comparisons.
As of 0.218.1, Flow will warn about none of the following:
You may notice that Flow will warn about the examples above if the
===operator is replaced with==. From this you may surmise that in general Flow considers===an explicit check of both type and value: if a comparison is done between different types, so what? It’s just a comparison that returns false, it’s not like it throws aTypeErroror anything.So maybe you should not be asking why Flow allows identity comparisons between mismatching types when one comparand is an array indexing expression. That’s the default case. You should be asking why it disallows them exactly when the following conditions are true:
if,while, ordo-while), or the comparison is performed as part of aswitchstatementYou may notice that
switchstatements always result in a strict comparison, so if those didn’t warn, switching on an enumeration type (Flow’s native enumerations, or a union-of-literals type) would effectively be left unchecked unless you wrote them out as a stack ofifs. You wouldn’t be warned if you accidentally checked a value against an enumerator it can’t ever assume:You may also notice that unlike general Boolean expressions, comparisons controlling a branch statement subject the non-literal comparand to type refinement: the type of the variable is narrowed down in both the successful and unsuccessful branches to match the result of the test. By a similar token as with the
switch, if a type of a variable (which wasn’t the bottom type to begin with) is narrowed out of inhabitants, this may be an error in the code. Except that array accesses are just as much subject to refinement, yet they don’t trigger errors on mismatched-type comparisons:You may be tempted to think that Flow wants to sanction testing
arr[i]against undefined to check if an array contains thei-th element. But the type ofarr[i]above is not merely narrowed tovoid(the type of undefined); it’s narrowed toempty, the bottom type with no values. Having a value of the bottom type in scope is an impossibility, and is supposed to mean the code is unreachable, and therefore can be removed. So Flow seems to assume indexing arrays always results in a present element, for otherwise this would be a soundness hole.You may further ask ‘But wouldn’t a mismatched-type comparison be just as much indicative of an error in the general case of an arbitrary expression? What makes refinements of a variable so special?’ Well, yes, it would. Because of that, I have reason to suspect which mismatched-type comparisons trigger errors and which don’t was not deliberately chosen by some general principle, but is an emergent outcome of certain more specific, ad-hoc checks. And I can only speak of a ‘reason to suspect’ because none of this seems to be documented anywhere in Flow documentation (and I looked pretty thoroughly); every rationale above is my conjecture. For all I know, this wasn’t specifically decided, and array-element refinements may have just slipped through. Deal with it.