diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c82e0ece074..8b1b70b0fa1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7603,16 +7603,24 @@ 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 (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, 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 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 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/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"); 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]);