Skip to content

Commit 35d6e98

Browse files
committed
type narrowing by constructor signiture of interface
1 parent 64f3798 commit 35d6e98

File tree

4 files changed

+368
-9
lines changed

4 files changed

+368
-9
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5351,17 +5351,39 @@ module ts {
53515351
}
53525352
// Target type is type of prototype property
53535353
let prototypeProperty = getPropertyOfType(rightType, "prototype");
5354-
if (!prototypeProperty) {
5355-
return type;
5354+
if (prototypeProperty) {
5355+
let targetType = getTypeOfSymbol(prototypeProperty);
5356+
if (targetType !== anyType) {
5357+
// Narrow to target type if it is a subtype of current type
5358+
if (isTypeSubtypeOf(targetType, type)) {
5359+
return targetType;
5360+
}
5361+
// If current type is a union type, remove all constituents that aren't subtypes of target type
5362+
if (type.flags & TypeFlags.Union) {
5363+
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, targetType)));
5364+
}
5365+
}
53565366
}
5357-
let targetType = getTypeOfSymbol(prototypeProperty);
5358-
// Narrow to target type if it is a subtype of current type
5359-
if (isTypeSubtypeOf(targetType, type)) {
5360-
return targetType;
5367+
// Target type is type of constructor signiture
5368+
let constructorSignitures: Signature[];
5369+
if (rightType.flags & TypeFlags.Interface) {
5370+
constructorSignitures = (<InterfaceTypeWithDeclaredMembers>rightType).declaredConstructSignatures;
53615371
}
5362-
// If current type is a union type, remove all constituents that aren't subtypes of target type
5363-
if (type.flags & TypeFlags.Union) {
5364-
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, targetType)));
5372+
if (rightType.flags & TypeFlags.Anonymous) {
5373+
constructorSignitures = (<ResolvedType>rightType).constructSignatures;
5374+
}
5375+
if (constructorSignitures) {
5376+
let constructorType = getUnionType(map(constructorSignitures, constructorSignature => {
5377+
if (constructorSignature.typeParameters && constructorSignature.typeParameters.length !== 0) {
5378+
// TODO convert type parameters to any or empty object types. e.g. Sample<T> -> Sample<any> or Sample<{}>.
5379+
}
5380+
return constructorSignature.resolvedReturnType;
5381+
}));
5382+
// Pickup type from union types
5383+
if (type.flags & TypeFlags.Union) {
5384+
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, constructorType)));
5385+
}
5386+
return constructorType;
53655387
}
53665388
return type;
53675389
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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(32,10): error TS2339: Property 'foo' does not exist on type '{}'.
3+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,10): error TS2339: Property 'foo' does not exist on type '{}'.
4+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type '{}'.
5+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(63,10): error TS2339: Property 'bar2' does not exist on type 'C1'.
6+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(82,10): error TS2339: Property 'bar' does not exist on type 'D'.
7+
8+
9+
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (6 errors) ====
10+
interface AConstructor {
11+
new (): A;
12+
}
13+
interface A {
14+
foo: string;
15+
}
16+
declare var A: AConstructor;
17+
18+
var obj1: A | string;
19+
if (obj1 instanceof A) { // narrowed to A.
20+
obj1.foo;
21+
obj1.bar;
22+
~~~
23+
!!! error TS2339: Property 'bar' does not exist on type 'A'.
24+
}
25+
26+
var obj2: any;
27+
if (obj2 instanceof A) { // can't type narrowing from any.
28+
obj2.foo;
29+
obj2.bar;
30+
}
31+
32+
// with generics
33+
interface BConstructor {
34+
new <T>(): B<T>;
35+
}
36+
interface B<T> {
37+
foo: T;
38+
}
39+
declare var B: BConstructor;
40+
41+
var obj3: B<string> | A;
42+
if (obj3 instanceof B) { // narrowed to B<string>.
43+
obj3.foo = "str";
44+
~~~
45+
!!! error TS2339: Property 'foo' does not exist on type '{}'.
46+
obj3.foo = 1;
47+
~~~
48+
!!! error TS2339: Property 'foo' does not exist on type '{}'.
49+
obj3.bar = "str";
50+
~~~
51+
!!! error TS2339: Property 'bar' does not exist on type '{}'.
52+
}
53+
54+
var obj4: any;
55+
if (obj4 instanceof B) { // can't type narrowing from any.
56+
obj4.foo = "str";
57+
obj4.foo = 1;
58+
obj4.bar = "str";
59+
}
60+
61+
// has multiple constructor signature
62+
interface CConstructor {
63+
new (value: string): C1;
64+
new (value: number): C2;
65+
}
66+
interface C1 {
67+
foo: string;
68+
bar1: number;
69+
}
70+
interface C2 {
71+
foo: string;
72+
bar2: number;
73+
}
74+
declare var C: CConstructor;
75+
76+
var obj5: C1 | A;
77+
if (obj5 instanceof C) { // narrowed to C1.
78+
obj5.foo;
79+
obj5.bar1;
80+
obj5.bar2;
81+
~~~~
82+
!!! error TS2339: Property 'bar2' does not exist on type 'C1'.
83+
}
84+
85+
var obj6: any;
86+
if (obj6 instanceof C) { // can't type narrowing from any.
87+
obj6.foo;
88+
obj6.bar1;
89+
obj6.bar2;
90+
}
91+
92+
// with object type literal
93+
interface D {
94+
foo: string;
95+
}
96+
declare var D: { new (): D; };
97+
98+
var obj7: D | string;
99+
if (obj7 instanceof D) { // narrowed to D.
100+
obj7.foo;
101+
obj7.bar;
102+
~~~
103+
!!! error TS2339: Property 'bar' does not exist on type 'D'.
104+
}
105+
106+
var obj8: any;
107+
if (obj8 instanceof D) { // can't type narrowing from any.
108+
obj8.foo;
109+
obj8.bar;
110+
}
111+
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//// [typeGuardsWithInstanceOfByConstructorSignature.ts]
2+
interface AConstructor {
3+
new (): A;
4+
}
5+
interface A {
6+
foo: string;
7+
}
8+
declare var A: AConstructor;
9+
10+
var obj1: A | string;
11+
if (obj1 instanceof A) { // narrowed to A.
12+
obj1.foo;
13+
obj1.bar;
14+
}
15+
16+
var obj2: any;
17+
if (obj2 instanceof A) { // can't type narrowing from any.
18+
obj2.foo;
19+
obj2.bar;
20+
}
21+
22+
// with generics
23+
interface BConstructor {
24+
new <T>(): B<T>;
25+
}
26+
interface B<T> {
27+
foo: T;
28+
}
29+
declare var B: BConstructor;
30+
31+
var obj3: B<string> | A;
32+
if (obj3 instanceof B) { // narrowed to B<string>.
33+
obj3.foo = "str";
34+
obj3.foo = 1;
35+
obj3.bar = "str";
36+
}
37+
38+
var obj4: any;
39+
if (obj4 instanceof B) { // can't type narrowing from any.
40+
obj4.foo = "str";
41+
obj4.foo = 1;
42+
obj4.bar = "str";
43+
}
44+
45+
// has multiple constructor signature
46+
interface CConstructor {
47+
new (value: string): C1;
48+
new (value: number): C2;
49+
}
50+
interface C1 {
51+
foo: string;
52+
bar1: number;
53+
}
54+
interface C2 {
55+
foo: string;
56+
bar2: number;
57+
}
58+
declare var C: CConstructor;
59+
60+
var obj5: C1 | A;
61+
if (obj5 instanceof C) { // narrowed to C1.
62+
obj5.foo;
63+
obj5.bar1;
64+
obj5.bar2;
65+
}
66+
67+
var obj6: any;
68+
if (obj6 instanceof C) { // can't type narrowing from any.
69+
obj6.foo;
70+
obj6.bar1;
71+
obj6.bar2;
72+
}
73+
74+
// with object type literal
75+
interface D {
76+
foo: string;
77+
}
78+
declare var D: { new (): D; };
79+
80+
var obj7: D | string;
81+
if (obj7 instanceof D) { // narrowed to D.
82+
obj7.foo;
83+
obj7.bar;
84+
}
85+
86+
var obj8: any;
87+
if (obj8 instanceof D) { // can't type narrowing from any.
88+
obj8.foo;
89+
obj8.bar;
90+
}
91+
92+
93+
//// [typeGuardsWithInstanceOfByConstructorSignature.js]
94+
var obj1;
95+
if (obj1 instanceof A) {
96+
obj1.foo;
97+
obj1.bar;
98+
}
99+
var obj2;
100+
if (obj2 instanceof A) {
101+
obj2.foo;
102+
obj2.bar;
103+
}
104+
var obj3;
105+
if (obj3 instanceof B) {
106+
obj3.foo = "str";
107+
obj3.foo = 1;
108+
obj3.bar = "str";
109+
}
110+
var obj4;
111+
if (obj4 instanceof B) {
112+
obj4.foo = "str";
113+
obj4.foo = 1;
114+
obj4.bar = "str";
115+
}
116+
var obj5;
117+
if (obj5 instanceof C) {
118+
obj5.foo;
119+
obj5.bar1;
120+
obj5.bar2;
121+
}
122+
var obj6;
123+
if (obj6 instanceof C) {
124+
obj6.foo;
125+
obj6.bar1;
126+
obj6.bar2;
127+
}
128+
var obj7;
129+
if (obj7 instanceof D) {
130+
obj7.foo;
131+
obj7.bar;
132+
}
133+
var obj8;
134+
if (obj8 instanceof D) {
135+
obj8.foo;
136+
obj8.bar;
137+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
interface AConstructor {
2+
new (): A;
3+
}
4+
interface A {
5+
foo: string;
6+
}
7+
declare var A: AConstructor;
8+
9+
var obj1: A | string;
10+
if (obj1 instanceof A) { // narrowed to A.
11+
obj1.foo;
12+
obj1.bar;
13+
}
14+
15+
var obj2: any;
16+
if (obj2 instanceof A) { // can't type narrowing from any.
17+
obj2.foo;
18+
obj2.bar;
19+
}
20+
21+
// with generics
22+
interface BConstructor {
23+
new <T>(): B<T>;
24+
}
25+
interface B<T> {
26+
foo: T;
27+
}
28+
declare var B: BConstructor;
29+
30+
var obj3: B<string> | A;
31+
if (obj3 instanceof B) { // narrowed to B<string>.
32+
obj3.foo = "str";
33+
obj3.foo = 1;
34+
obj3.bar = "str";
35+
}
36+
37+
var obj4: any;
38+
if (obj4 instanceof B) { // can't type narrowing from any.
39+
obj4.foo = "str";
40+
obj4.foo = 1;
41+
obj4.bar = "str";
42+
}
43+
44+
// has multiple constructor signature
45+
interface CConstructor {
46+
new (value: string): C1;
47+
new (value: number): C2;
48+
}
49+
interface C1 {
50+
foo: string;
51+
bar1: number;
52+
}
53+
interface C2 {
54+
foo: string;
55+
bar2: number;
56+
}
57+
declare var C: CConstructor;
58+
59+
var obj5: C1 | A;
60+
if (obj5 instanceof C) { // narrowed to C1.
61+
obj5.foo;
62+
obj5.bar1;
63+
obj5.bar2;
64+
}
65+
66+
var obj6: any;
67+
if (obj6 instanceof C) { // can't type narrowing from any.
68+
obj6.foo;
69+
obj6.bar1;
70+
obj6.bar2;
71+
}
72+
73+
// with object type literal
74+
interface D {
75+
foo: string;
76+
}
77+
declare var D: { new (): D; };
78+
79+
var obj7: D | string;
80+
if (obj7 instanceof D) { // narrowed to D.
81+
obj7.foo;
82+
obj7.bar;
83+
}
84+
85+
var obj8: any;
86+
if (obj8 instanceof D) { // can't type narrowing from any.
87+
obj8.foo;
88+
obj8.bar;
89+
}

0 commit comments

Comments
 (0)