Merge pull request #10920 from Microsoft/fixLiteralUnionInference

Fix literal union type inference
This commit is contained in:
Anders Hejlsberg
2016-09-14 17:48:17 -07:00
committed by GitHub
6 changed files with 277 additions and 12 deletions

View File

@@ -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 (<UnionOrIntersectionType>target).types) {
if (typeIdenticalToSomeType(t, (<UnionOrIntersectionType>source).types)) {
for (const t of (<UnionOrIntersectionType>source).types) {
if (typeIdenticalToSomeType(t, (<UnionOrIntersectionType>target).types)) {
(matchingTypes || (matchingTypes = [])).push(t);
inferFromTypes(t, t);
}
else if (t.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral)) {
const b = getBaseTypeOfLiteralType(t);
if (typeIdenticalToSomeType(b, (<UnionOrIntersectionType>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

View File

@@ -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<T extends Numeric>(array: Array<T | Primitive>): [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]);

View File

@@ -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<T extends Numeric>(array: Array<T | Primitive>): [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))

View File

@@ -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<T extends Numeric>(array: Array<T | Primitive>): [T | Primitive, T | Primitive] | [undefined, undefined] {
>extent : <T extends Numeric>(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 : <T extends Numeric>(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

View File

@@ -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");

View File

@@ -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<T extends Numeric>(array: Array<T | Primitive>): [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]);