From e9ad5daddcb9a6a57b6a595ee21e02d14fe1a233 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 14 Sep 2016 10:29:51 -0700 Subject: [PATCH 1/5] Match number and string literal types to number and string in inference --- src/compiler/checker.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e3422abe219..001e300bbc7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7608,11 +7608,18 @@ namespace ts { // itself. When inferring from a type to itself we effectively find all type parameter // occurrences within that type and infer themselves as their type arguments. let matchingTypes: Type[]; - for (const t of (target).types) { - if (typeIdenticalToSomeType(t, (source).types)) { + for (const t of (source).types) { + if (typeIdenticalToSomeType(t, (target).types)) { (matchingTypes || (matchingTypes = [])).push(t); inferFromTypes(t, t); } + else if (t.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral)) { + const b = getBaseTypeOfLiteralType(t); + if (typeIdenticalToSomeType(b, (target).types)) { + (matchingTypes || (matchingTypes = [])).push(t); + matchingTypes.push(b); + } + } } // Next, to improve the quality of inferences, reduce the source and target types by // removing the identically matched constituents. For example, when inferring from From efc15aef5b08a50e852dba29e7c8907be7e0170f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 14 Sep 2016 11:16:20 -0700 Subject: [PATCH 2/5] Accept new baselines --- tests/baselines/reference/unionTypeInference.errors.txt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index a906c3a8854..dfda0d57c2b 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -1,9 +1,7 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(9,15): error TS2345: Argument of type '2' is not assignable to parameter of type 'string | 1'. -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,15): error TS2345: Argument of type 'number | "hello"' is not assignable to parameter of type 'string | 1'. - Type 'number' is not assignable to type 'string | 1'. -==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (2 errors) ==== +==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (1 errors) ==== // Verify that inferences made *to* a type parameter in a union type are secondary // to inferences made directly to that type parameter @@ -19,9 +17,6 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference var a2 = f(1, "hello"); var a3: number; var a3 = f(1, a1 || "hello"); - ~~~~~~~~~~~~~ -!!! error TS2345: Argument of type 'number | "hello"' is not assignable to parameter of type 'string | 1'. -!!! error TS2345: Type 'number' is not assignable to type 'string | 1'. var a4: any; var a4 = f(undefined, "abc"); From 032cd32a812633a1c1d2c415118e108bd6e066d9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 14 Sep 2016 11:16:37 -0700 Subject: [PATCH 3/5] Add regression test --- .../reference/typeInferenceLiteralUnion.js | 59 ++++++++++++ .../typeInferenceLiteralUnion.symbols | 79 ++++++++++++++++ .../reference/typeInferenceLiteralUnion.types | 89 +++++++++++++++++++ .../compiler/typeInferenceLiteralUnion.ts | 35 ++++++++ 4 files changed, 262 insertions(+) create mode 100644 tests/baselines/reference/typeInferenceLiteralUnion.js create mode 100644 tests/baselines/reference/typeInferenceLiteralUnion.symbols create mode 100644 tests/baselines/reference/typeInferenceLiteralUnion.types create mode 100644 tests/cases/compiler/typeInferenceLiteralUnion.ts diff --git a/tests/baselines/reference/typeInferenceLiteralUnion.js b/tests/baselines/reference/typeInferenceLiteralUnion.js new file mode 100644 index 00000000000..07776d26061 --- /dev/null +++ b/tests/baselines/reference/typeInferenceLiteralUnion.js @@ -0,0 +1,59 @@ +//// [typeInferenceLiteralUnion.ts] +// Repro from #10901 +/** + * Administrivia: JavaScript primitive types and Date + */ +export type Primitive = number | string | boolean | Date; + +/** + * Administrivia: anything with a valueOf(): number method is comparable, so we allow it in numeric operations + */ +interface Numeric { + valueOf(): number; +} + +// Not very useful, but meets Numeric +class NumCoercible { + public a: number; + + constructor(a: number) { + this.a = a; + } + public valueOf() { + return this.a; + } +} + +/** + * Return the min and max simultaneously. + */ +export function extent(array: Array): [T | Primitive, T | Primitive] | [undefined, undefined] { + return [undefined, undefined]; +} + + +let extentMixed: [Primitive | NumCoercible, Primitive | NumCoercible] | [undefined, undefined]; +extentMixed = extent([new NumCoercible(10), 13, '12', true]); + + +//// [typeInferenceLiteralUnion.js] +"use strict"; +// Not very useful, but meets Numeric +var NumCoercible = (function () { + function NumCoercible(a) { + this.a = a; + } + NumCoercible.prototype.valueOf = function () { + return this.a; + }; + return NumCoercible; +}()); +/** + * Return the min and max simultaneously. + */ +function extent(array) { + return [undefined, undefined]; +} +exports.extent = extent; +var extentMixed; +extentMixed = extent([new NumCoercible(10), 13, '12', true]); diff --git a/tests/baselines/reference/typeInferenceLiteralUnion.symbols b/tests/baselines/reference/typeInferenceLiteralUnion.symbols new file mode 100644 index 00000000000..51fc4c828ec --- /dev/null +++ b/tests/baselines/reference/typeInferenceLiteralUnion.symbols @@ -0,0 +1,79 @@ +=== tests/cases/compiler/typeInferenceLiteralUnion.ts === +// Repro from #10901 +/** + * Administrivia: JavaScript primitive types and Date + */ +export type Primitive = number | string | boolean | Date; +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) +>Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +/** + * Administrivia: anything with a valueOf(): number method is comparable, so we allow it in numeric operations + */ +interface Numeric { +>Numeric : Symbol(Numeric, Decl(typeInferenceLiteralUnion.ts, 4, 57)) + + valueOf(): number; +>valueOf : Symbol(Numeric.valueOf, Decl(typeInferenceLiteralUnion.ts, 9, 19)) +} + +// Not very useful, but meets Numeric +class NumCoercible { +>NumCoercible : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) + + public a: number; +>a : Symbol(NumCoercible.a, Decl(typeInferenceLiteralUnion.ts, 14, 20)) + + constructor(a: number) { +>a : Symbol(a, Decl(typeInferenceLiteralUnion.ts, 17, 16)) + + this.a = a; +>this.a : Symbol(NumCoercible.a, Decl(typeInferenceLiteralUnion.ts, 14, 20)) +>this : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) +>a : Symbol(NumCoercible.a, Decl(typeInferenceLiteralUnion.ts, 14, 20)) +>a : Symbol(a, Decl(typeInferenceLiteralUnion.ts, 17, 16)) + } + public valueOf() { +>valueOf : Symbol(NumCoercible.valueOf, Decl(typeInferenceLiteralUnion.ts, 19, 5)) + + return this.a; +>this.a : Symbol(NumCoercible.a, Decl(typeInferenceLiteralUnion.ts, 14, 20)) +>this : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) +>a : Symbol(NumCoercible.a, Decl(typeInferenceLiteralUnion.ts, 14, 20)) + } +} + +/** + * Return the min and max simultaneously. + */ +export function extent(array: Array): [T | Primitive, T | Primitive] | [undefined, undefined] { +>extent : Symbol(extent, Decl(typeInferenceLiteralUnion.ts, 23, 1)) +>T : Symbol(T, Decl(typeInferenceLiteralUnion.ts, 28, 23)) +>Numeric : Symbol(Numeric, Decl(typeInferenceLiteralUnion.ts, 4, 57)) +>array : Symbol(array, Decl(typeInferenceLiteralUnion.ts, 28, 42)) +>Array : Symbol(Array, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(typeInferenceLiteralUnion.ts, 28, 23)) +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) +>T : Symbol(T, Decl(typeInferenceLiteralUnion.ts, 28, 23)) +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) +>T : Symbol(T, Decl(typeInferenceLiteralUnion.ts, 28, 23)) +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) + + return [undefined, undefined]; +>undefined : Symbol(undefined) +>undefined : Symbol(undefined) +} + + +let extentMixed: [Primitive | NumCoercible, Primitive | NumCoercible] | [undefined, undefined]; +>extentMixed : Symbol(extentMixed, Decl(typeInferenceLiteralUnion.ts, 33, 3)) +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) +>NumCoercible : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) +>Primitive : Symbol(Primitive, Decl(typeInferenceLiteralUnion.ts, 0, 0)) +>NumCoercible : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) + +extentMixed = extent([new NumCoercible(10), 13, '12', true]); +>extentMixed : Symbol(extentMixed, Decl(typeInferenceLiteralUnion.ts, 33, 3)) +>extent : Symbol(extent, Decl(typeInferenceLiteralUnion.ts, 23, 1)) +>NumCoercible : Symbol(NumCoercible, Decl(typeInferenceLiteralUnion.ts, 11, 1)) + diff --git a/tests/baselines/reference/typeInferenceLiteralUnion.types b/tests/baselines/reference/typeInferenceLiteralUnion.types new file mode 100644 index 00000000000..0416820604c --- /dev/null +++ b/tests/baselines/reference/typeInferenceLiteralUnion.types @@ -0,0 +1,89 @@ +=== tests/cases/compiler/typeInferenceLiteralUnion.ts === +// Repro from #10901 +/** + * Administrivia: JavaScript primitive types and Date + */ +export type Primitive = number | string | boolean | Date; +>Primitive : Primitive +>Date : Date + +/** + * Administrivia: anything with a valueOf(): number method is comparable, so we allow it in numeric operations + */ +interface Numeric { +>Numeric : Numeric + + valueOf(): number; +>valueOf : () => number +} + +// Not very useful, but meets Numeric +class NumCoercible { +>NumCoercible : NumCoercible + + public a: number; +>a : number + + constructor(a: number) { +>a : number + + this.a = a; +>this.a = a : number +>this.a : number +>this : this +>a : number +>a : number + } + public valueOf() { +>valueOf : () => number + + return this.a; +>this.a : number +>this : this +>a : number + } +} + +/** + * Return the min and max simultaneously. + */ +export function extent(array: Array): [T | Primitive, T | Primitive] | [undefined, undefined] { +>extent : (array: (string | number | boolean | Date | T)[]) => [string | number | boolean | Date | T, string | number | boolean | Date | T] | [undefined, undefined] +>T : T +>Numeric : Numeric +>array : (string | number | boolean | Date | T)[] +>Array : T[] +>T : T +>Primitive : Primitive +>T : T +>Primitive : Primitive +>T : T +>Primitive : Primitive + + return [undefined, undefined]; +>[undefined, undefined] : [undefined, undefined] +>undefined : undefined +>undefined : undefined +} + + +let extentMixed: [Primitive | NumCoercible, Primitive | NumCoercible] | [undefined, undefined]; +>extentMixed : [undefined, undefined] | [string | number | boolean | Date | NumCoercible, string | number | boolean | Date | NumCoercible] +>Primitive : Primitive +>NumCoercible : NumCoercible +>Primitive : Primitive +>NumCoercible : NumCoercible + +extentMixed = extent([new NumCoercible(10), 13, '12', true]); +>extentMixed = extent([new NumCoercible(10), 13, '12', true]) : [undefined, undefined] | [string | number | boolean | Date | NumCoercible, string | number | boolean | Date | NumCoercible] +>extentMixed : [undefined, undefined] | [string | number | boolean | Date | NumCoercible, string | number | boolean | Date | NumCoercible] +>extent([new NumCoercible(10), 13, '12', true]) : [undefined, undefined] | [string | number | boolean | Date | NumCoercible, string | number | boolean | Date | NumCoercible] +>extent : (array: (string | number | boolean | Date | T)[]) => [string | number | boolean | Date | T, string | number | boolean | Date | T] | [undefined, undefined] +>[new NumCoercible(10), 13, '12', true] : (true | NumCoercible | 13 | "12")[] +>new NumCoercible(10) : NumCoercible +>NumCoercible : typeof NumCoercible +>10 : 10 +>13 : 13 +>'12' : "12" +>true : true + diff --git a/tests/cases/compiler/typeInferenceLiteralUnion.ts b/tests/cases/compiler/typeInferenceLiteralUnion.ts new file mode 100644 index 00000000000..735954e96bb --- /dev/null +++ b/tests/cases/compiler/typeInferenceLiteralUnion.ts @@ -0,0 +1,35 @@ +// Repro from #10901 +/** + * Administrivia: JavaScript primitive types and Date + */ +export type Primitive = number | string | boolean | Date; + +/** + * Administrivia: anything with a valueOf(): number method is comparable, so we allow it in numeric operations + */ +interface Numeric { + valueOf(): number; +} + +// Not very useful, but meets Numeric +class NumCoercible { + public a: number; + + constructor(a: number) { + this.a = a; + } + public valueOf() { + return this.a; + } +} + +/** + * Return the min and max simultaneously. + */ +export function extent(array: Array): [T | Primitive, T | Primitive] | [undefined, undefined] { + return [undefined, undefined]; +} + + +let extentMixed: [Primitive | NumCoercible, Primitive | NumCoercible] | [undefined, undefined]; +extentMixed = extent([new NumCoercible(10), 13, '12', true]); From e50105bd9e35565f12f14c6d1cc39947dcd1382d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 14 Sep 2016 13:33:06 -0700 Subject: [PATCH 4/5] Fix super in down-level async method --- src/compiler/transformers/es6.ts | 17 ++++++++++++---- .../reference/asyncMethodWithSuper_es5.js | 20 +++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 44deac04279..51a6a088fb7 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -10,6 +10,7 @@ namespace ts { /** Enables substitutions for block-scoped bindings. */ BlockScopedBindings = 1 << 1, } + /** * If loop contains block scoped binding captured in some function then loop body is converted to a function. * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, @@ -166,6 +167,9 @@ namespace ts { let enclosingBlockScopeContainerParent: Node; let containingNonArrowFunction: FunctionLikeDeclaration | ClassElement; + /** Tracks the container that determines whether `super.x` is a static. */ + let superScopeContainer: FunctionLikeDeclaration | ClassElement; + /** * Used to track if we are emitting body of the converted loop */ @@ -203,6 +207,7 @@ namespace ts { function saveStateAndInvoke(node: Node, f: (node: Node) => T): T { const savedContainingNonArrowFunction = containingNonArrowFunction; + const savedSuperScopeContainer = superScopeContainer; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer; @@ -219,6 +224,7 @@ namespace ts { convertedLoopState = savedConvertedLoopState; containingNonArrowFunction = savedContainingNonArrowFunction; + superScopeContainer = savedSuperScopeContainer; currentParent = savedCurrentParent; currentNode = savedCurrentNode; enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer; @@ -414,13 +420,16 @@ namespace ts { } switch (currentParent.kind) { + case SyntaxKind.FunctionExpression: case SyntaxKind.Constructor: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: containingNonArrowFunction = currentParent; + if (!(containingNonArrowFunction.emitFlags & NodeEmitFlags.AsyncFunctionBody)) { + superScopeContainer = containingNonArrowFunction; + } break; } } @@ -2820,9 +2829,9 @@ namespace ts { * Visits the `super` keyword */ function visitSuperKeyword(node: PrimaryExpression): LeftHandSideExpression { - return containingNonArrowFunction - && isClassElement(containingNonArrowFunction) - && !hasModifier(containingNonArrowFunction, ModifierFlags.Static) + return superScopeContainer + && isClassElement(superScopeContainer) + && !hasModifier(superScopeContainer, ModifierFlags.Static) && currentParent.kind !== SyntaxKind.CallExpression ? createPropertyAccess(createIdentifier("_super"), "prototype") : createIdentifier("_super"); diff --git a/tests/baselines/reference/asyncMethodWithSuper_es5.js b/tests/baselines/reference/asyncMethodWithSuper_es5.js index 87debbdc55c..21683eed637 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es5.js +++ b/tests/baselines/reference/asyncMethodWithSuper_es5.js @@ -69,11 +69,11 @@ var B = (function (_super) { var a, b; return __generator(this, function (_a) { // call with property access - _super.x.call(this); + _super.prototype.x.call(this); // call with element access - _super["x"].call(this); - a = _super.x; - b = _super["x"]; + _super.prototype["x"].call(this); + a = _super.prototype.x; + b = _super.prototype["x"]; return [2 /*return*/]; }); }); @@ -85,15 +85,15 @@ var B = (function (_super) { return __generator(this, function (_c) { f = function () { }; // call with property access - _super.x.call(this); + _super.prototype.x.call(this); // call with element access - _super["x"].call(this); - a = _super.x; - b = _super["x"]; + _super.prototype["x"].call(this); + a = _super.prototype.x; + b = _super.prototype["x"]; // property access (assign) - _super.x = f; + _super.prototype.x = f; // element access (assign) - _super["x"] = f; + _super.prototype["x"] = f; // destructuring assign with property access (_a = { f: f }, super.x = _a.f, _a); // destructuring assign with element access From 832295d8b03f63b3879eb7329195f4dd09d22906 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 14 Sep 2016 14:58:39 -0700 Subject: [PATCH 5/5] Address CR feedback --- src/compiler/checker.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 001e300bbc7..080637363ed 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7603,10 +7603,12 @@ namespace ts { } return; } - // Find each target constituent type that has an identically matching source - // constituent type, and for each such target constituent type infer from the type to - // itself. When inferring from a type to itself we effectively find all type parameter - // occurrences within that type and infer themselves as their type arguments. + // Find each source constituent type that has an identically matching target constituent + // type, and for each such type infer from the type to itself. When inferring from a + // type to itself we effectively find all type parameter occurrences within that type + // and infer themselves as their type arguments. We have special handling for numeric + // and string literals because the number and string types are not represented as unions + // of all their possible values. let matchingTypes: Type[]; for (const t of (source).types) { if (typeIdenticalToSomeType(t, (target).types)) { @@ -7616,8 +7618,7 @@ namespace ts { else if (t.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral)) { const b = getBaseTypeOfLiteralType(t); if (typeIdenticalToSomeType(b, (target).types)) { - (matchingTypes || (matchingTypes = [])).push(t); - matchingTypes.push(b); + (matchingTypes || (matchingTypes = [])).push(t, b); } } }