Skip to content

typeof foo === "array" causes type narrowing #13771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sciolizer opened this issue Jan 31, 2017 · 5 comments · Fixed by #13791
Closed

typeof foo === "array" causes type narrowing #13771

sciolizer opened this issue Jan 31, 2017 · 5 comments · Fixed by #13791
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@sciolizer
Copy link

TypeScript Version: 2.1.5 / nightly (2.2.0-dev.20170130)

Code

function mixedMessages(): string | Array<string> {
    return ["Our relationship is strong", "so we should see other people."];
}

const ourRelationship = mixedMessages();

if (typeof ourRelationship === "array") {
    throw new Error("There can be only one.");
}

console.log(ourRelationship.substring(0, 10));

Expected behavior:

Compilation should fail with a type error, because in the final line, ourRelationship has type string | Array<string>, and arrays do not have a substring method.

Actual behavior:

The if-throw is misinterpreted as a type narrowing from string | Array<string> to string. Compilation succeeds, and the generated javascript produces the runtime type error ourRelationship.substring is not a function.

I'm no pro at javascript, so maybe this is an allowance for some obscure browser/environment which handles typeof differently, but in all the environments I looked at, typeof [] === "object".

@mhegazy
Copy link
Contributor

mhegazy commented Jan 31, 2017

at runtime an array has typeof "object" so this exception would never throw (unless you are running on a non-standard engine).

image

use Array.isArray(ourRelationship) instead.

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 31, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Jan 31, 2017

:D. that was dump.. you are right, since the typeof is not one we know about, we excluding the primitives, but fail to exclude "object" as well.

@mhegazy mhegazy added Bug A bug in TypeScript and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Jan 31, 2017
@mhegazy mhegazy added this to the TypeScript 2.2 milestone Jan 31, 2017
@sandersn
Copy link
Member

From the spec, section 4.24: "A type guard of the form typeof x === s, where s is not 'string' | 'number' | 'boolean': when true, removes subtypes of string, number or boolean; when false, does nothing."

So typeof x === "array" should remove string in the true branch and leave the type alone in the false branch. The bug is that it's removing string[] in the false branch.

@sandersn
Copy link
Member

@mhegazy had an even cleverer idea: make typeof x return a string literal type 'string' | 'number' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function'. This will quickly tell you when you've done the comparison wrong.

We previously discussed this at #9506 and decided not to do it, but I think it's worth revisiting, especially if we provide completions for binary comparisons with string literal unions.

@Andy-MS points out that there is a ts-lint rule that checks this, so if we decide to take the PR, they will be able to get rid of that rule.

@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Feb 7, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants