Use different relation for instanceof type guards

This commit is contained in:
Ryan Cavanaugh
2015-08-05 09:05:06 -07:00
parent 4c7b214a69
commit cc3a48fc7f
6 changed files with 419 additions and 17 deletions
+12 -6
View File
@@ -6207,14 +6207,20 @@ namespace ts {
}
function getNarrowedType(originalType: Type, narrowedTypeCandidate: Type) {
// Narrow to the target type if it's a subtype of the current type
if (isTypeSubtypeOf(narrowedTypeCandidate, originalType)) {
// If the current type is a union type, remove all constituents that aren't assignable to target. If that produces
// 0 candidates, fall back to the assignability check
if (originalType.flags & TypeFlags.Union) {
let assignableConsituents = filter((<UnionType>originalType).types, t => isTypeAssignableTo(t, narrowedTypeCandidate));
if (assignableConsituents.length) {
return getUnionType(assignableConsituents);
}
}
if (isTypeAssignableTo(narrowedTypeCandidate, originalType)) {
// Narrow to the target type if it's assignable to the current type
return narrowedTypeCandidate;
}
// If the current type is a union type, remove all constituents that aren't subtypes of the target.
if (originalType.flags & TypeFlags.Union) {
return getUnionType(filter((<UnionType>originalType).types, t => isTypeSubtypeOf(t, narrowedTypeCandidate)));
}
return originalType;
}
@@ -0,0 +1,105 @@
//// [instanceOfAssignability.ts]
interface Base {
foo: string|number;
optional?: number;
}
// Derived1 is assignable to, but not a subtype of, Base
class Derived1 implements Base {
foo: string;
}
// Derived2 is a subtype of Base that is not assignable to Derived1
class Derived2 implements Base {
foo: number;
optional: number;
}
function fn1(x: Array<number>|Array<string>|boolean) {
if(x instanceof Array) {
// 1.5: y: Array<number>|Array<string>
// Want: y: Array<number>|Array<string>
let y = x;
}
}
function fn2(x: Base) {
if(x instanceof Derived1) {
// 1.5: y: Base
// Want: y: Derived1
let y = x;
}
}
function fn3(x: Base|Derived1) {
if(x instanceof Derived2) {
// 1.5: y: Derived2
// Want: Derived2
let y = x;
}
}
function fn4(x: Base|Derived2) {
if(x instanceof Derived1) {
// 1.5: y: {}
// Want: Derived1
let y = x;
}
}
function fn5(x: Derived1) {
if(x instanceof Derived2) {
// 1.5: y: Derived1
// Want: ???
let y = x;
}
}
//// [instanceOfAssignability.js]
// Derived1 is assignable to, but not a subtype of, Base
var Derived1 = (function () {
function Derived1() {
}
return Derived1;
})();
// Derived2 is a subtype of Base that is not assignable to Derived1
var Derived2 = (function () {
function Derived2() {
}
return Derived2;
})();
function fn1(x) {
if (x instanceof Array) {
// 1.5: y: Array<number>|Array<string>
// Want: y: Array<number>|Array<string>
var y = x;
}
}
function fn2(x) {
if (x instanceof Derived1) {
// 1.5: y: Base
// Want: y: Derived1
var y = x;
}
}
function fn3(x) {
if (x instanceof Derived2) {
// 1.5: y: Derived2
// Want: Derived2
var y = x;
}
}
function fn4(x) {
if (x instanceof Derived1) {
// 1.5: y: {}
// Want: Derived1
var y = x;
}
}
function fn5(x) {
if (x instanceof Derived2) {
// 1.5: y: Derived1
// Want: ???
var y = x;
}
}
@@ -0,0 +1,119 @@
=== tests/cases/compiler/instanceOfAssignability.ts ===
interface Base {
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
foo: string|number;
>foo : Symbol(foo, Decl(instanceOfAssignability.ts, 0, 16))
optional?: number;
>optional : Symbol(optional, Decl(instanceOfAssignability.ts, 1, 20))
}
// Derived1 is assignable to, but not a subtype of, Base
class Derived1 implements Base {
>Derived1 : Symbol(Derived1, Decl(instanceOfAssignability.ts, 3, 1))
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
foo: string;
>foo : Symbol(foo, Decl(instanceOfAssignability.ts, 6, 32))
}
// Derived2 is a subtype of Base that is not assignable to Derived1
class Derived2 implements Base {
>Derived2 : Symbol(Derived2, Decl(instanceOfAssignability.ts, 8, 1))
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
foo: number;
>foo : Symbol(foo, Decl(instanceOfAssignability.ts, 10, 32))
optional: number;
>optional : Symbol(optional, Decl(instanceOfAssignability.ts, 11, 13))
}
function fn1(x: Array<number>|Array<string>|boolean) {
>fn1 : Symbol(fn1, Decl(instanceOfAssignability.ts, 13, 1))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 15, 13))
>Array : Symbol(Array, Decl(lib.d.ts, 1000, 23), Decl(lib.d.ts, 1171, 11))
>Array : Symbol(Array, Decl(lib.d.ts, 1000, 23), Decl(lib.d.ts, 1171, 11))
if(x instanceof Array) {
>x : Symbol(x, Decl(instanceOfAssignability.ts, 15, 13))
>Array : Symbol(Array, Decl(lib.d.ts, 1000, 23), Decl(lib.d.ts, 1171, 11))
// 1.5: y: Array<number>|Array<string>
// Want: y: Array<number>|Array<string>
let y = x;
>y : Symbol(y, Decl(instanceOfAssignability.ts, 19, 5))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 15, 13))
}
}
function fn2(x: Base) {
>fn2 : Symbol(fn2, Decl(instanceOfAssignability.ts, 21, 1))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 23, 13))
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
if(x instanceof Derived1) {
>x : Symbol(x, Decl(instanceOfAssignability.ts, 23, 13))
>Derived1 : Symbol(Derived1, Decl(instanceOfAssignability.ts, 3, 1))
// 1.5: y: Base
// Want: y: Derived1
let y = x;
>y : Symbol(y, Decl(instanceOfAssignability.ts, 27, 5))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 23, 13))
}
}
function fn3(x: Base|Derived1) {
>fn3 : Symbol(fn3, Decl(instanceOfAssignability.ts, 29, 1))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 31, 13))
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
>Derived1 : Symbol(Derived1, Decl(instanceOfAssignability.ts, 3, 1))
if(x instanceof Derived2) {
>x : Symbol(x, Decl(instanceOfAssignability.ts, 31, 13))
>Derived2 : Symbol(Derived2, Decl(instanceOfAssignability.ts, 8, 1))
// 1.5: y: Derived2
// Want: Derived2
let y = x;
>y : Symbol(y, Decl(instanceOfAssignability.ts, 35, 5))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 31, 13))
}
}
function fn4(x: Base|Derived2) {
>fn4 : Symbol(fn4, Decl(instanceOfAssignability.ts, 37, 1))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 39, 13))
>Base : Symbol(Base, Decl(instanceOfAssignability.ts, 0, 0))
>Derived2 : Symbol(Derived2, Decl(instanceOfAssignability.ts, 8, 1))
if(x instanceof Derived1) {
>x : Symbol(x, Decl(instanceOfAssignability.ts, 39, 13))
>Derived1 : Symbol(Derived1, Decl(instanceOfAssignability.ts, 3, 1))
// 1.5: y: {}
// Want: Derived1
let y = x;
>y : Symbol(y, Decl(instanceOfAssignability.ts, 43, 5))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 39, 13))
}
}
function fn5(x: Derived1) {
>fn5 : Symbol(fn5, Decl(instanceOfAssignability.ts, 45, 1))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 47, 13))
>Derived1 : Symbol(Derived1, Decl(instanceOfAssignability.ts, 3, 1))
if(x instanceof Derived2) {
>x : Symbol(x, Decl(instanceOfAssignability.ts, 47, 13))
>Derived2 : Symbol(Derived2, Decl(instanceOfAssignability.ts, 8, 1))
// 1.5: y: Derived1
// Want: ???
let y = x;
>y : Symbol(y, Decl(instanceOfAssignability.ts, 51, 5))
>x : Symbol(x, Decl(instanceOfAssignability.ts, 47, 13))
}
}
@@ -0,0 +1,124 @@
=== tests/cases/compiler/instanceOfAssignability.ts ===
interface Base {
>Base : Base
foo: string|number;
>foo : string | number
optional?: number;
>optional : number
}
// Derived1 is assignable to, but not a subtype of, Base
class Derived1 implements Base {
>Derived1 : Derived1
>Base : Base
foo: string;
>foo : string
}
// Derived2 is a subtype of Base that is not assignable to Derived1
class Derived2 implements Base {
>Derived2 : Derived2
>Base : Base
foo: number;
>foo : number
optional: number;
>optional : number
}
function fn1(x: Array<number>|Array<string>|boolean) {
>fn1 : (x: number[] | string[] | boolean) => void
>x : number[] | string[] | boolean
>Array : T[]
>Array : T[]
if(x instanceof Array) {
>x instanceof Array : boolean
>x : number[] | string[] | boolean
>Array : ArrayConstructor
// 1.5: y: Array<number>|Array<string>
// Want: y: Array<number>|Array<string>
let y = x;
>y : number[] | string[]
>x : number[] | string[]
}
}
function fn2(x: Base) {
>fn2 : (x: Base) => void
>x : Base
>Base : Base
if(x instanceof Derived1) {
>x instanceof Derived1 : boolean
>x : Base
>Derived1 : typeof Derived1
// 1.5: y: Base
// Want: y: Derived1
let y = x;
>y : Derived1
>x : Derived1
}
}
function fn3(x: Base|Derived1) {
>fn3 : (x: Base | Derived1) => void
>x : Base | Derived1
>Base : Base
>Derived1 : Derived1
if(x instanceof Derived2) {
>x instanceof Derived2 : boolean
>x : Base | Derived1
>Derived2 : typeof Derived2
// 1.5: y: Derived2
// Want: Derived2
let y = x;
>y : Derived2
>x : Derived2
}
}
function fn4(x: Base|Derived2) {
>fn4 : (x: Base | Derived2) => void
>x : Base | Derived2
>Base : Base
>Derived2 : Derived2
if(x instanceof Derived1) {
>x instanceof Derived1 : boolean
>x : Base | Derived2
>Derived1 : typeof Derived1
// 1.5: y: {}
// Want: Derived1
let y = x;
>y : Derived1
>x : Derived1
}
}
function fn5(x: Derived1) {
>fn5 : (x: Derived1) => void
>x : Derived1
>Derived1 : Derived1
if(x instanceof Derived2) {
>x instanceof Derived2 : boolean
>x : Derived1
>Derived2 : typeof Derived2
// 1.5: y: Derived1
// Want: ???
let y = x;
>y : Derived1
>x : Derived1
}
}
@@ -1,18 +1,16 @@
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,5): error TS2322: Type 'string' is not assignable to type 'number'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type 'B<number>'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(65,10): error TS2339: Property 'bar1' does not exist on type 'C1 | C2'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1 | C2'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(85,10): error TS2339: Property 'bar' does not exist on type 'D'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(111,10): error TS2339: Property 'bar1' does not exist on type 'E1 | E2'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1 | E2'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(134,11): error TS2339: Property 'foo' does not exist on type 'F | string'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(135,11): error TS2339: Property 'bar' does not exist on type 'F | string'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(160,11): error TS2339: Property 'foo2' does not exist on type 'G1'.
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(182,11): error TS2339: Property 'bar' does not exist on type 'H'.
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (12 errors) ====
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (10 errors) ====
interface AConstructor {
new (): A;
}
@@ -84,11 +82,9 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru
obj5.foo;
obj5.c;
obj5.bar1;
~~~~
!!! error TS2339: Property 'bar1' does not exist on type 'C1 | C2'.
obj5.bar2;
~~~~
!!! error TS2339: Property 'bar2' does not exist on type 'C1 | C2'.
!!! error TS2339: Property 'bar2' does not exist on type 'C1'.
}
var obj6: any;
@@ -136,11 +132,9 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru
if (obj9 instanceof E) { // narrowed to E1 | E2
obj9.foo;
obj9.bar1;
~~~~
!!! error TS2339: Property 'bar1' does not exist on type 'E1 | E2'.
obj9.bar2;
~~~~
!!! error TS2339: Property 'bar2' does not exist on type 'E1 | E2'.
!!! error TS2339: Property 'bar2' does not exist on type 'E1'.
}
var obj10: any;
@@ -0,0 +1,54 @@
interface Base {
foo: string|number;
optional?: number;
}
// Derived1 is assignable to, but not a subtype of, Base
class Derived1 implements Base {
foo: string;
}
// Derived2 is a subtype of Base that is not assignable to Derived1
class Derived2 implements Base {
foo: number;
optional: number;
}
function fn1(x: Array<number>|Array<string>|boolean) {
if(x instanceof Array) {
// 1.5: y: Array<number>|Array<string>
// Want: y: Array<number>|Array<string>
let y = x;
}
}
function fn2(x: Base) {
if(x instanceof Derived1) {
// 1.5: y: Base
// Want: y: Derived1
let y = x;
}
}
function fn3(x: Base|Derived1) {
if(x instanceof Derived2) {
// 1.5: y: Derived2
// Want: Derived2
let y = x;
}
}
function fn4(x: Base|Derived2) {
if(x instanceof Derived1) {
// 1.5: y: {}
// Want: Derived1
let y = x;
}
}
function fn5(x: Derived1) {
if(x instanceof Derived2) {
// 1.5: y: Derived1
// Want: ???
let y = x;
}
}