Skip to content
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

typeof x now has a string literal union type #13791

Merged
merged 6 commits into from
Feb 7, 2017

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Jan 31, 2017

typeof x now has the string literal type 'string' | 'number' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function'. This will prevent typos and misunderstandings of typeof (eg typeof a === "array" or typeof o === "null"). And once #13789 is fixed, it will also provide completions.

For implementations that disobey the advice of the standard, it is easy to work around the type, although narrowing no longer works:

if (typeof x === "ActiveXObject" as string) {
  // no narrowing here
} else {
  // or here
}

Fixes #9506
Fixes #13771 (by making the incorrect narrowing an error)

Previously, it was just a string
Test that `typeof x === 'random' as string`:

1. Does not issue an error.
2. Does not narrow.
@@ -14609,7 +14609,7 @@ namespace ts {

function checkTypeOfExpression(node: TypeOfExpression): Type {
checkExpression(node.expression);
return stringType;
return getUnionType(arrayFromMap(typeofEQFacts.keys(), s => getLiteralTypeForText(TypeFlags.StringLiteral, s)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this union type be precomputed and cached to avoid doing the same work multiple times?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I wasn't sure where to put it though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the new commit; I added a new typeofType that is created when the checker is.

Note that it changes the print order of string literal unions slightly. I think this is because 'string', 'symbol', etc are added to a union very early in the creation of the checker.

This changes the print order of string literal unions slightly. I think
this is because 'string', 'symbol', etc are added to a union very early
on in the creation of the checker.
@@ -895,6 +895,14 @@ namespace ts {
return result;
}

export function arrayFromMap<T, U>(iterator: Iterator<T>, f: (value: T) => U) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this is iteratorToArray to arrayFromIterator since there is nothing map specific in the signature

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant map in the sense of the map function not the Map type. Maybe mapIterator? mapIteratorToArray? Something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe convertToArray?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that one.

// - when true, removes the primitive types string, number, and boolean from the type of x, or
// - when false, has no effect on the type of x.

if (typeof strOrC === "Object") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is intent here to make C object in the true branch and string - in false one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe so. Javascript doesn't work that way, of course, so that's why this PR makes it an error.

Note that since 2.0 it has worked this way. Previously, the false branch had no effect on the type of x.

@sandersn sandersn merged commit 363d914 into master Feb 7, 2017
@sandersn sandersn deleted the typeof-string-literal-union-type branch February 7, 2017 21:19
@mhegazy mhegazy added the Breaking Change Would introduce errors in existing code label Feb 8, 2017
@mhegazy mhegazy added this to the TypeScript 2.2.1 milestone Feb 8, 2017
@treshugart
Copy link

@mhegazy Hey, got linked to this from the interwebs. Was going to adopt TS in SkateJS but it looks like you're making a breaking change as part of a patch milestone?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 14, 2017

Can you elaborate on how SkateJS would be broken by this change?

@sandersn
Copy link
Member Author

2.2.0 is 2.2 RC; 2.2.1 is the release version of 2.2.

@treshugart
Copy link

Can you elaborate on how SkateJS would be broken by this change?

We're not using it yet, but just breaking Skate doesn't concern me. Possibly breaking every consumer on a minor version does. I could be wrong, but this appears to be a change between the 2.1.x stable, minor range to the 2.2.x stable, minor range, correct? I could have easily missed something here.

2.2.0 is 2.2 RC; 2.2.1 is the release version of 2.2.

I apologise for my ignorance, I just couldn't infer that 2.2.1 was the stable after the 2.2 rc. Thanks for clarifying :)

@mhegazy
Copy link
Contributor

mhegazy commented Feb 15, 2017

I see, thanks for the explanation.

We make all changes that could result in an error, if previously was not, as "breaking" changes. We promise no breaking changes in revisions, e.g. 2.2.1 -> 2.2.2. but not in minor version upgrades e.g. 2.1 -> 2.2. We recommend that you fix your package.json to major.moinor of TypeScript. Please see https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes for the full list of breaking changes in the the past.

This change makes the type of typeof expressions to be 'string' | 'number' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function', which should cover all values you would get from one of the popular JS engines. The reason it is marked as breaking, is per the ES spec, an engine could generate a different value, so this is a "breaking change" if you happen to be running against an engine (that is not v8, chackra, spidermoneky, or Core).

@BenSayers
Copy link

BenSayers commented Apr 6, 2017

What the reasoning behind not following semver? Npm docs recommend that modules follow server and will automatically allow minor bumps in a module by default. I'm concerned that not following this convention will create unnecessary friction for TypeScript consumers who will by default be opted in to having their builds broken whenever TypeScript releases a minor version.

@sandersn
Copy link
Member Author

sandersn commented Apr 6, 2017

@BenSayers @treshugart the official semver issue is at #14116. You can read some of our reasoning and continue the discussion there.

@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
Breaking Change Would introduce errors in existing code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

typeof foo === "array" causes type narrowing Typeof comparison expression should check right hand side value
6 participants