Skip to content

Commit a108989

Browse files
jameskeanesandersn
authored andcommitted
Fixes microsoft#26128 - signature comp for jsdoc @Class. (microsoft#26160)
* Fixes microsoft#26128 - signature comp for jsdoc @Class. Another issue caused by js functions tagged with jsdoc `@constructor` not having construct signatures. A jsdoc function type that constructs a type (`function(new: Ex)`), has a construct signature and return value inferred as the constructed type where as a jsdoc `@constructor` has no construct signatures, and it's call signature has a void return type (or undefined). i.e: ```javascript /** @constructor **/ function E() {}; // typeof E -> call signature: () => void /** @param {function(new: E)} d */ function c(d) {} // typeof d -> construct: () => E ``` -- This commit fixes this (in an inelegant way) by considering `@class` function signatures as construct signatures and synthesizing it's return value _only for signature comparison_. There might be a slight performance hit, since the synthesized return value is not cached; but changing the `@class` function's return type in `getReturnTypeOfSignature` causes other issues. * Update jsdoc function test to fix mistake.
1 parent cea4838 commit a108989

File tree

5 files changed

+279
-4
lines changed

5 files changed

+279
-4
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10744,11 +10744,13 @@ namespace ts {
1074410744
}
1074510745

1074610746
if (!ignoreReturnTypes) {
10747-
const targetReturnType = getReturnTypeOfSignature(target);
10747+
const targetReturnType = (target.declaration && isJavaScriptConstructor(target.declaration)) ?
10748+
getJavaScriptClassType(target.declaration.symbol)! : getReturnTypeOfSignature(target);
1074810749
if (targetReturnType === voidType) {
1074910750
return result;
1075010751
}
10751-
const sourceReturnType = getReturnTypeOfSignature(source);
10752+
const sourceReturnType = (source.declaration && isJavaScriptConstructor(source.declaration)) ?
10753+
getJavaScriptClassType(source.declaration.symbol)! : getReturnTypeOfSignature(source);
1075210754

1075310755
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
1075410756
const targetTypePredicate = getTypePredicateOfSignature(target);
@@ -12015,8 +12017,14 @@ namespace ts {
1201512017
return Ternary.True;
1201612018
}
1201712019

12018-
const sourceSignatures = getSignaturesOfType(source, kind);
12019-
const targetSignatures = getSignaturesOfType(target, kind);
12020+
const sourceIsJSConstructor = source.symbol && isJavaScriptConstructor(source.symbol.valueDeclaration);
12021+
const targetIsJSConstructor = target.symbol && isJavaScriptConstructor(target.symbol.valueDeclaration);
12022+
12023+
const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ?
12024+
SignatureKind.Call : kind);
12025+
const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ?
12026+
SignatureKind.Call : kind);
12027+
1202012028
if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) {
1202112029
if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) {
1202212030
// An abstract constructor type is not assignable to a non-abstract constructor type
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
tests/cases/conformance/jsdoc/functions.js(65,14): error TS2345: Argument of type 'typeof E' is not assignable to parameter of type 'new (arg1: number) => { length: number; }'.
2+
Type 'E' is not assignable to type '{ length: number; }'.
3+
Property 'length' is missing in type 'E'.
4+
5+
6+
==== tests/cases/conformance/jsdoc/functions.js (1 errors) ====
7+
/**
8+
* @param {function(this: string, number): number} c is just passing on through
9+
* @return {function(this: string, number): number}
10+
*/
11+
function id1(c) {
12+
return c
13+
}
14+
15+
var x = id1(function (n) { return this.length + n });
16+
17+
/**
18+
* @param {function(new: { length: number }, number): number} c is just passing on through
19+
* @return {function(new: { length: number }, number): number}
20+
*/
21+
function id2(c) {
22+
return c
23+
}
24+
25+
class C {
26+
/** @param {number} n */
27+
constructor(n) {
28+
this.length = n;
29+
}
30+
}
31+
32+
var y = id2(C);
33+
var z = new y(12);
34+
z.length;
35+
36+
/** @type {function ("a" | "b", 1 | 2): 3 | 4} */
37+
var f = function (ab, onetwo) { return ab === "a" ? 3 : 4; }
38+
39+
40+
/**
41+
* @constructor
42+
* @param {number} n
43+
*/
44+
function D(n) {
45+
this.length = n;
46+
}
47+
48+
var y2 = id2(D);
49+
var z2 = new y2(33);
50+
z2.length;
51+
52+
53+
/**
54+
* @param {function(new: D, number)} dref
55+
* @return {D}
56+
*/
57+
var construct = function(dref) { return new dref(33); }
58+
var z3 = construct(D);
59+
z3.length;
60+
61+
62+
/**
63+
* @constructor
64+
* @param {number} n
65+
*/
66+
var E = function(n) {
67+
this.not_length_on_purpose = n;
68+
};
69+
70+
71+
var y3 = id2(E);
72+
~
73+
!!! error TS2345: Argument of type 'typeof E' is not assignable to parameter of type 'new (arg1: number) => { length: number; }'.
74+
!!! error TS2345: Type 'E' is not assignable to type '{ length: number; }'.
75+
!!! error TS2345: Property 'length' is missing in type 'E'.
76+

tests/baselines/reference/jsdocFunctionType.symbols

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,76 @@ var f = function (ab, onetwo) { return ab === "a" ? 3 : 4; }
6868
>onetwo : Symbol(onetwo, Decl(functions.js, 30, 21))
6969
>ab : Symbol(ab, Decl(functions.js, 30, 18))
7070

71+
72+
/**
73+
* @constructor
74+
* @param {number} n
75+
*/
76+
function D(n) {
77+
>D : Symbol(D, Decl(functions.js, 30, 61))
78+
>n : Symbol(n, Decl(functions.js, 37, 11))
79+
80+
this.length = n;
81+
>this.length : Symbol(D.length, Decl(functions.js, 37, 15))
82+
>this : Symbol(D, Decl(functions.js, 30, 61))
83+
>length : Symbol(D.length, Decl(functions.js, 37, 15))
84+
>n : Symbol(n, Decl(functions.js, 37, 11))
85+
}
86+
87+
var y2 = id2(D);
88+
>y2 : Symbol(y2, Decl(functions.js, 41, 3))
89+
>id2 : Symbol(id2, Decl(functions.js, 8, 53))
90+
>D : Symbol(D, Decl(functions.js, 30, 61))
91+
92+
var z2 = new y2(33);
93+
>z2 : Symbol(z2, Decl(functions.js, 42, 3))
94+
>y2 : Symbol(y2, Decl(functions.js, 41, 3))
95+
96+
z2.length;
97+
>z2.length : Symbol(length, Decl(functions.js, 12, 27))
98+
>z2 : Symbol(z2, Decl(functions.js, 42, 3))
99+
>length : Symbol(length, Decl(functions.js, 12, 27))
100+
101+
102+
/**
103+
* @param {function(new: D, number)} dref
104+
* @return {D}
105+
*/
106+
var construct = function(dref) { return new dref(33); }
107+
>construct : Symbol(construct, Decl(functions.js, 50, 3))
108+
>dref : Symbol(dref, Decl(functions.js, 50, 25))
109+
>dref : Symbol(dref, Decl(functions.js, 50, 25))
110+
111+
var z3 = construct(D);
112+
>z3 : Symbol(z3, Decl(functions.js, 51, 3))
113+
>construct : Symbol(construct, Decl(functions.js, 50, 3))
114+
>D : Symbol(D, Decl(functions.js, 30, 61))
115+
116+
z3.length;
117+
>z3.length : Symbol(D.length, Decl(functions.js, 37, 15))
118+
>z3 : Symbol(z3, Decl(functions.js, 51, 3))
119+
>length : Symbol(D.length, Decl(functions.js, 37, 15))
120+
121+
122+
/**
123+
* @constructor
124+
* @param {number} n
125+
*/
126+
var E = function(n) {
127+
>E : Symbol(E, Decl(functions.js, 59, 3))
128+
>n : Symbol(n, Decl(functions.js, 59, 17))
129+
130+
this.not_length_on_purpose = n;
131+
>this.not_length_on_purpose : Symbol(E.not_length_on_purpose, Decl(functions.js, 59, 21))
132+
>this : Symbol(E, Decl(functions.js, 59, 7))
133+
>not_length_on_purpose : Symbol(E.not_length_on_purpose, Decl(functions.js, 59, 21))
134+
>n : Symbol(n, Decl(functions.js, 59, 17))
135+
136+
};
137+
138+
139+
var y3 = id2(E);
140+
>y3 : Symbol(y3, Decl(functions.js, 64, 3))
141+
>id2 : Symbol(id2, Decl(functions.js, 8, 53))
142+
>E : Symbol(E, Decl(functions.js, 59, 3))
143+

tests/baselines/reference/jsdocFunctionType.types

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,87 @@ var f = function (ab, onetwo) { return ab === "a" ? 3 : 4; }
8181
>3 : 3
8282
>4 : 4
8383

84+
85+
/**
86+
* @constructor
87+
* @param {number} n
88+
*/
89+
function D(n) {
90+
>D : typeof D
91+
>n : number
92+
93+
this.length = n;
94+
>this.length = n : number
95+
>this.length : number
96+
>this : D
97+
>length : number
98+
>n : number
99+
}
100+
101+
var y2 = id2(D);
102+
>y2 : new (arg1: number) => { length: number; }
103+
>id2(D) : new (arg1: number) => { length: number; }
104+
>id2 : (c: new (arg1: number) => { length: number; }) => new (arg1: number) => { length: number; }
105+
>D : typeof D
106+
107+
var z2 = new y2(33);
108+
>z2 : { length: number; }
109+
>new y2(33) : { length: number; }
110+
>y2 : new (arg1: number) => { length: number; }
111+
>33 : 33
112+
113+
z2.length;
114+
>z2.length : number
115+
>z2 : { length: number; }
116+
>length : number
117+
118+
119+
/**
120+
* @param {function(new: D, number)} dref
121+
* @return {D}
122+
*/
123+
var construct = function(dref) { return new dref(33); }
124+
>construct : (dref: new (arg1: number) => D) => D
125+
>function(dref) { return new dref(33); } : (dref: new (arg1: number) => D) => D
126+
>dref : new (arg1: number) => D
127+
>new dref(33) : D
128+
>dref : new (arg1: number) => D
129+
>33 : 33
130+
131+
var z3 = construct(D);
132+
>z3 : D
133+
>construct(D) : D
134+
>construct : (dref: new (arg1: number) => D) => D
135+
>D : typeof D
136+
137+
z3.length;
138+
>z3.length : number
139+
>z3 : D
140+
>length : number
141+
142+
143+
/**
144+
* @constructor
145+
* @param {number} n
146+
*/
147+
var E = function(n) {
148+
>E : typeof E
149+
>function(n) { this.not_length_on_purpose = n;} : typeof E
150+
>n : number
151+
152+
this.not_length_on_purpose = n;
153+
>this.not_length_on_purpose = n : number
154+
>this.not_length_on_purpose : number
155+
>this : E
156+
>not_length_on_purpose : number
157+
>n : number
158+
159+
};
160+
161+
162+
var y3 = id2(E);
163+
>y3 : new (arg1: number) => { length: number; }
164+
>id2(E) : new (arg1: number) => { length: number; }
165+
>id2 : (c: new (arg1: number) => { length: number; }) => new (arg1: number) => { length: number; }
166+
>E : typeof E
167+

tests/cases/conformance/jsdoc/jsdocFunctionType.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,37 @@ z.length;
3636

3737
/** @type {function ("a" | "b", 1 | 2): 3 | 4} */
3838
var f = function (ab, onetwo) { return ab === "a" ? 3 : 4; }
39+
40+
41+
/**
42+
* @constructor
43+
* @param {number} n
44+
*/
45+
function D(n) {
46+
this.length = n;
47+
}
48+
49+
var y2 = id2(D);
50+
var z2 = new y2(33);
51+
z2.length;
52+
53+
54+
/**
55+
* @param {function(new: D, number)} dref
56+
* @return {D}
57+
*/
58+
var construct = function(dref) { return new dref(33); }
59+
var z3 = construct(D);
60+
z3.length;
61+
62+
63+
/**
64+
* @constructor
65+
* @param {number} n
66+
*/
67+
var E = function(n) {
68+
this.not_length_on_purpose = n;
69+
};
70+
71+
72+
var y3 = id2(E);

0 commit comments

Comments
 (0)