Skip to content

Commit 5e94ce6

Browse files
committed
Merge branch 'typeguardsByConstructorSigniture' of https://github.com/vvakame/TypeScript into vvakame-typeguardsByConstructorSigniture
2 parents 61a404d + 776f390 commit 5e94ce6

File tree

4 files changed

+713
-9
lines changed

4 files changed

+713
-9
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3634,6 +3634,10 @@ module ts {
36343634
return type;
36353635
}
36363636

3637+
function getUnionTypeOfSubtypeConstituents(source: UnionType, target: Type): Type {
3638+
return getUnionType(filter(source.types, t => isTypeSubtypeOf(t, target)));
3639+
}
3640+
36373641
function getReducedTypeOfUnionType(type: UnionType): Type {
36383642
// If union type was created without subtype reduction, perform the deferred reduction now
36393643
if (!type.reducedType) {
@@ -5386,17 +5390,35 @@ module ts {
53865390
}
53875391
// Target type is type of prototype property
53885392
let prototypeProperty = getPropertyOfType(rightType, "prototype");
5389-
if (!prototypeProperty) {
5390-
return type;
5393+
if (prototypeProperty) {
5394+
let targetType = getTypeOfSymbol(prototypeProperty);
5395+
if (targetType !== anyType) {
5396+
// Narrow to the target type if it's a subtype of the current type
5397+
if (isTypeSubtypeOf(targetType, type)) {
5398+
return targetType;
5399+
}
5400+
// If the current type is a union type, remove all constituents that aren't subtypes of the target.
5401+
if (type.flags & TypeFlags.Union) {
5402+
return getUnionTypeOfSubtypeConstituents(<UnionType>type, targetType);
5403+
}
5404+
}
53915405
}
5392-
let targetType = getTypeOfSymbol(prototypeProperty);
5393-
// Narrow to target type if it is a subtype of current type
5394-
if (isTypeSubtypeOf(targetType, type)) {
5395-
return targetType;
5406+
// Target type is type of construct signature
5407+
let constructSignatures: Signature[];
5408+
if (rightType.flags & TypeFlags.Interface) {
5409+
constructSignatures = resolveDeclaredMembers(<InterfaceType>rightType).declaredConstructSignatures;
53965410
}
5397-
// If current type is a union type, remove all constituents that aren't subtypes of target type
5398-
if (type.flags & TypeFlags.Union) {
5399-
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, targetType)));
5411+
else if (rightType.flags & TypeFlags.Anonymous) {
5412+
constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
5413+
}
5414+
5415+
if (constructSignatures && constructSignatures.length !== 0) {
5416+
let instanceType = getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
5417+
// Pickup type from union types
5418+
if (type.flags & TypeFlags.Union) {
5419+
return getUnionTypeOfSubtypeConstituents(<UnionType>type, instanceType);
5420+
}
5421+
return instanceType;
54005422
}
54015423
return type;
54025424
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'.
2+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,5): error TS2322: Type 'string' is not assignable to type 'number'.
3+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type 'B<number>'.
4+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(63,10): error TS2339: Property 'bar2' does not exist on type 'C1'.
5+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(82,10): error TS2339: Property 'bar' does not exist on type 'D'.
6+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(109,10): error TS2339: Property 'bar2' does not exist on type 'E1'.
7+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(131,11): error TS2339: Property 'foo' does not exist on type 'string | F'.
8+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(132,11): error TS2339: Property 'bar' does not exist on type 'string | F'.
9+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(157,11): error TS2339: Property 'foo2' does not exist on type 'G1'.
10+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(179,11): error TS2339: Property 'bar' does not exist on type 'H'.
11+
12+
13+
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (10 errors) ====
14+
interface AConstructor {
15+
new (): A;
16+
}
17+
interface A {
18+
foo: string;
19+
}
20+
declare var A: AConstructor;
21+
22+
var obj1: A | string;
23+
if (obj1 instanceof A) { // narrowed to A.
24+
obj1.foo;
25+
obj1.bar;
26+
~~~
27+
!!! error TS2339: Property 'bar' does not exist on type 'A'.
28+
}
29+
30+
var obj2: any;
31+
if (obj2 instanceof A) { // can't narrow type from 'any'
32+
obj2.foo;
33+
obj2.bar;
34+
}
35+
36+
// a construct signature with generics
37+
interface BConstructor {
38+
new <T>(): B<T>;
39+
}
40+
interface B<T> {
41+
foo: T;
42+
}
43+
declare var B: BConstructor;
44+
45+
var obj3: B<number> | string;
46+
if (obj3 instanceof B) { // narrowed to B<number>.
47+
obj3.foo = 1;
48+
obj3.foo = "str";
49+
~~~~~~~~
50+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
51+
obj3.bar = "str";
52+
~~~
53+
!!! error TS2339: Property 'bar' does not exist on type 'B<number>'.
54+
}
55+
56+
var obj4: any;
57+
if (obj4 instanceof B) { // can't narrow type from 'any'
58+
obj4.foo = "str";
59+
obj4.foo = 1;
60+
obj4.bar = "str";
61+
}
62+
63+
// has multiple construct signature
64+
interface CConstructor {
65+
new (value: string): C1;
66+
new (value: number): C2;
67+
}
68+
interface C1 {
69+
foo: string;
70+
bar1: number;
71+
}
72+
interface C2 {
73+
foo: string;
74+
bar2: number;
75+
}
76+
declare var C: CConstructor;
77+
78+
var obj5: C1 | A;
79+
if (obj5 instanceof C) { // narrowed to C1.
80+
obj5.foo;
81+
obj5.bar1;
82+
obj5.bar2;
83+
~~~~
84+
!!! error TS2339: Property 'bar2' does not exist on type 'C1'.
85+
}
86+
87+
var obj6: any;
88+
if (obj6 instanceof C) { // can't narrow type from 'any'
89+
obj6.foo;
90+
obj6.bar1;
91+
obj6.bar2;
92+
}
93+
94+
// with object type literal
95+
interface D {
96+
foo: string;
97+
}
98+
declare var D: { new (): D; };
99+
100+
var obj7: D | string;
101+
if (obj7 instanceof D) { // narrowed to D.
102+
obj7.foo;
103+
obj7.bar;
104+
~~~
105+
!!! error TS2339: Property 'bar' does not exist on type 'D'.
106+
}
107+
108+
var obj8: any;
109+
if (obj8 instanceof D) { // can't narrow type from 'any'
110+
obj8.foo;
111+
obj8.bar;
112+
}
113+
114+
// a construct signature that returns a union type
115+
interface EConstructor {
116+
new (): E1 | E2;
117+
}
118+
interface E1 {
119+
foo: string;
120+
bar1: number;
121+
}
122+
interface E2 {
123+
foo: string;
124+
bar2: number;
125+
}
126+
declare var E: EConstructor;
127+
128+
var obj9: E1 | A;
129+
if (obj9 instanceof E) { // narrowed to E1.
130+
obj9.foo;
131+
obj9.bar1;
132+
obj9.bar2;
133+
~~~~
134+
!!! error TS2339: Property 'bar2' does not exist on type 'E1'.
135+
}
136+
137+
var obj10: any;
138+
if (obj10 instanceof E) { // can't narrow type from 'any'
139+
obj10.foo;
140+
obj10.bar1;
141+
obj10.bar2;
142+
}
143+
144+
// a construct signature that returns any
145+
interface FConstructor {
146+
new (): any;
147+
}
148+
interface F {
149+
foo: string;
150+
bar: number;
151+
}
152+
declare var F: FConstructor;
153+
154+
var obj11: F | string;
155+
if (obj11 instanceof F) { // can't type narrowing, construct signature returns any.
156+
obj11.foo;
157+
~~~
158+
!!! error TS2339: Property 'foo' does not exist on type 'string | F'.
159+
obj11.bar;
160+
~~~
161+
!!! error TS2339: Property 'bar' does not exist on type 'string | F'.
162+
}
163+
164+
var obj12: any;
165+
if (obj12 instanceof F) { // can't narrow type from 'any'
166+
obj12.foo;
167+
obj12.bar;
168+
}
169+
170+
// a type with a prototype, it overrides the construct signature
171+
interface GConstructor {
172+
prototype: G1; // high priority
173+
new (): G2; // low priority
174+
}
175+
interface G1 {
176+
foo1: number;
177+
}
178+
interface G2 {
179+
foo2: boolean;
180+
}
181+
declare var G: GConstructor;
182+
183+
var obj13: G1 | G2;
184+
if (obj13 instanceof G) { // narrowed to G1. G1 is return type of prototype property.
185+
obj13.foo1;
186+
obj13.foo2;
187+
~~~~
188+
!!! error TS2339: Property 'foo2' does not exist on type 'G1'.
189+
}
190+
191+
var obj14: any;
192+
if (obj14 instanceof G) { // can't narrow type from 'any'
193+
obj14.foo1;
194+
obj14.foo2;
195+
}
196+
197+
// a type with a prototype that has any type
198+
interface HConstructor {
199+
prototype: any; // high priority, but any type is ignored. interface has implicit `prototype: any`.
200+
new (): H; // low priority
201+
}
202+
interface H {
203+
foo: number;
204+
}
205+
declare var H: HConstructor;
206+
207+
var obj15: H | string;
208+
if (obj15 instanceof H) { // narrowed to H.
209+
obj15.foo;
210+
obj15.bar;
211+
~~~
212+
!!! error TS2339: Property 'bar' does not exist on type 'H'.
213+
}
214+
215+
var obj16: any;
216+
if (obj16 instanceof H) { // can't narrow type from 'any'
217+
obj16.foo1;
218+
obj16.foo2;
219+
}
220+

0 commit comments

Comments
 (0)