Skip to content

Commit c184184

Browse files
authored
Add getEffectiveConstructSignatures (#27561)
* Add helpers that understand constructor functions * getEffectiveConstructSignatures gets construct signatures from type, and call signatures from constructor functions if there are no construct signatures. * getEffectiveConstructSignatureReturnType gets the "JS Class type" for constructor functions, and the return type of signatures for all other declarations. This is a first step toward making constructor functions have construct signatures instead of call signatures, which will also contribute to fixing instantiation of generic constructor functions, which is basically broken right now. Note that the baselines *improve* but, because of the previously mentioned generic problem, are still not correct. Construct signatures for constructor functions and generic constructor functions turns out to be an intertwined problem. * Correct correct originalBaseType And, for now, return anyType for generic constructor functions used as base types. Don't give an incorrect error based on the function's return type, which is usually void. * Add error examples to tests * Add construct signatures instead of getEffective* functions * Fix typo in baseline * Remove pesky newline I thought I got rid of it! * Test of constructor tag on object literal method It doesn't work, and shouldn't in the future, because it's a runtime error.
1 parent bb275b9 commit c184184

15 files changed

+141
-53
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5622,9 +5622,6 @@ namespace ts {
56225622
function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray<TypeNode> | undefined, location: Node): ReadonlyArray<Signature> {
56235623
const typeArgCount = length(typeArgumentNodes);
56245624
const isJavascript = isInJSFile(location);
5625-
if (isJSConstructorType(type) && !typeArgCount) {
5626-
return getSignaturesOfType(type, SignatureKind.Call);
5627-
}
56285625
return filter(getSignaturesOfType(type, SignatureKind.Construct),
56295626
sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters));
56305627
}
@@ -5706,7 +5703,9 @@ namespace ts {
57065703
const baseTypeNode = getBaseTypeNodeOfClass(type)!;
57075704
const typeArgs = typeArgumentsFromTypeReferenceNode(baseTypeNode);
57085705
let baseType: Type;
5709-
const originalBaseType = baseConstructorType && baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined;
5706+
const originalBaseType = isJSConstructorType(baseConstructorType) ? baseConstructorType :
5707+
baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) :
5708+
undefined;
57105709
if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class &&
57115710
areAllOuterTypeParametersApplied(originalBaseType!)) {
57125711
// When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the
@@ -5717,8 +5716,8 @@ namespace ts {
57175716
else if (baseConstructorType.flags & TypeFlags.Any) {
57185717
baseType = baseConstructorType;
57195718
}
5720-
else if (isJSConstructorType(baseConstructorType) && !baseTypeNode.typeArguments) {
5721-
baseType = getJSClassType(baseConstructorType.symbol) || anyType;
5719+
else if (isJSConstructorType(baseConstructorType)) {
5720+
baseType = !baseTypeNode.typeArguments && getJSClassType(baseConstructorType.symbol) || anyType;
57225721
}
57235722
else {
57245723
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
@@ -6730,6 +6729,7 @@ namespace ts {
67306729
// will never be observed because a qualified name can't reference signatures.
67316730
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) {
67326731
type.callSignatures = getSignaturesOfSymbol(symbol);
6732+
type.constructSignatures = filter(type.callSignatures, sig => isJSConstructor(sig.declaration));
67336733
}
67346734
// And likewise for construct signatures for classes
67356735
if (symbol.flags & SymbolFlags.Class) {
@@ -7866,6 +7866,7 @@ namespace ts {
78667866
let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper!) :
78677867
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
78687868
getReturnTypeFromAnnotation(signature.declaration!) ||
7869+
isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) ||
78697870
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
78707871
if (!popTypeResolution()) {
78717872
if (signature.declaration) {
@@ -15451,12 +15452,9 @@ namespace ts {
1545115452
}
1545215453

1545315454
if (!targetType) {
15454-
let constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
15455-
if (constructSignatures.length === 0) {
15456-
constructSignatures = filter(getSignaturesOfType(rightType, SignatureKind.Call), sig => isJSConstructor(sig.declaration));
15457-
}
15455+
const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
1545815456
targetType = constructSignatures.length ?
15459-
getUnionType(map(constructSignatures, signature => isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) || getReturnTypeOfSignature(getErasedSignature(signature)))) :
15457+
getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
1546015458
emptyObjectType;
1546115459
}
1546215460

@@ -20039,12 +20037,12 @@ namespace ts {
2003920037
// Function interface, since they have none by default. This is a bit of a leap of faith
2004020038
// that the user will not add any.
2004120039
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
20042-
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
20040+
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
2004320041

2004420042
// TS 1.0 Spec: 4.12
2004520043
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
2004620044
// types are provided for the argument expressions, and the result is always of type Any.
20047-
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) {
20045+
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
2004820046
// The unknownType indicates that an error already occurred (and was reported). No
2004920047
// need to report another error in this case.
2005020048
if (funcType !== errorType && node.typeArguments) {
@@ -20056,7 +20054,7 @@ namespace ts {
2005620054
// TypeScript employs overload resolution in typed function calls in order to support functions
2005720055
// with multiple call signatures.
2005820056
if (!callSignatures.length) {
20059-
if (constructSignatures.length) {
20057+
if (numConstructSignatures) {
2006020058
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
2006120059
}
2006220060
else {
@@ -20272,9 +20270,9 @@ namespace ts {
2027220270
}
2027320271

2027420272
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
20275-
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
20273+
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
2027620274

20277-
if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, constructSignatures.length)) {
20275+
if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) {
2027820276
return resolveUntypedCall(node);
2027920277
}
2028020278

@@ -20322,8 +20320,8 @@ namespace ts {
2032220320
}
2032320321

2032420322
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
20325-
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
20326-
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) {
20323+
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
20324+
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
2032720325
return resolveUntypedCall(node);
2032820326
}
2032920327

tests/baselines/reference/classCanExtendConstructorFunction.errors.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ tests/cases/conformance/salsa/first.js(23,9): error TS2554: Expected 1 arguments
22
tests/cases/conformance/salsa/first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'.
33
Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'.
44
tests/cases/conformance/salsa/first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
5-
tests/cases/conformance/salsa/generic.js(8,15): error TS2508: No base constructor has the specified number of type arguments.
6-
tests/cases/conformance/salsa/generic.js(11,21): error TS2339: Property 'flavour' does not exist on type 'Chowder'.
7-
tests/cases/conformance/salsa/generic.js(18,9): error TS2339: Property 'flavour' does not exist on type 'Chowder'.
5+
tests/cases/conformance/salsa/generic.js(19,19): error TS2554: Expected 1 arguments, but got 0.
6+
tests/cases/conformance/salsa/generic.js(20,32): error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.
87
tests/cases/conformance/salsa/second.ts(8,25): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type.
98
tests/cases/conformance/salsa/second.ts(14,7): error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'.
109
Types of property 'circle' are incompatible.
@@ -115,7 +114,7 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '
115114
c.drunkOO
116115
c.numberOxen
117116

118-
==== tests/cases/conformance/salsa/generic.js (3 errors) ====
117+
==== tests/cases/conformance/salsa/generic.js (2 errors) ====
119118
/**
120119
* @template T
121120
* @param {T} flavour
@@ -124,21 +123,22 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '
124123
this.flavour = flavour
125124
}
126125
/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */
127-
~~~~
128-
!!! error TS2508: No base constructor has the specified number of type arguments.
129126
class Chowder extends Soup {
130127
log() {
131128
return this.flavour
132-
~~~~~~~
133-
!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'.
134129
}
135130
}
136131

137132
var soup = new Soup(1);
138133
soup.flavour
139-
var chowder = new Chowder();
134+
var chowder = new Chowder({ claim: "ignorant" });
140135
chowder.flavour.claim
141-
~~~~~~~
142-
!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'.
136+
var errorNoArgs = new Chowder();
137+
~~~~~~~~~~~~~
138+
!!! error TS2554: Expected 1 arguments, but got 0.
139+
!!! related TS6210 tests/cases/conformance/salsa/generic.js:5:15: An argument for 'flavour' was not provided.
140+
var errorArgType = new Chowder(0);
141+
~
142+
!!! error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'.
143143

144144

tests/baselines/reference/classCanExtendConstructorFunction.symbols

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,20 @@ soup.flavour
218218
>soup : Symbol(soup, Decl(generic.js, 14, 3))
219219
>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
220220

221-
var chowder = new Chowder();
221+
var chowder = new Chowder({ claim: "ignorant" });
222222
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))
223223
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
224+
>claim : Symbol(claim, Decl(generic.js, 16, 27))
224225

225226
chowder.flavour.claim
226227
>chowder : Symbol(chowder, Decl(generic.js, 16, 3))
227228

229+
var errorNoArgs = new Chowder();
230+
>errorNoArgs : Symbol(errorNoArgs, Decl(generic.js, 18, 3))
231+
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
232+
233+
var errorArgType = new Chowder(0);
234+
>errorArgType : Symbol(errorArgType, Decl(generic.js, 19, 3))
235+
>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1))
236+
228237

tests/baselines/reference/classCanExtendConstructorFunction.types

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,16 +268,30 @@ soup.flavour
268268
>soup : typeof Soup
269269
>flavour : number
270270

271-
var chowder = new Chowder();
272-
>chowder : Chowder
273-
>new Chowder() : Chowder
271+
var chowder = new Chowder({ claim: "ignorant" });
272+
>chowder : any
273+
>new Chowder({ claim: "ignorant" }) : any
274274
>Chowder : typeof Chowder
275+
>{ claim: "ignorant" } : { claim: "ignorant"; }
276+
>claim : "ignorant"
277+
>"ignorant" : "ignorant"
275278

276279
chowder.flavour.claim
277280
>chowder.flavour.claim : any
278281
>chowder.flavour : any
279-
>chowder : Chowder
282+
>chowder : any
280283
>flavour : any
281284
>claim : any
282285

286+
var errorNoArgs = new Chowder();
287+
>errorNoArgs : any
288+
>new Chowder() : any
289+
>Chowder : typeof Chowder
290+
291+
var errorArgType = new Chowder(0);
292+
>errorArgType : any
293+
>new Chowder(0) : any
294+
>Chowder : typeof Chowder
295+
>0 : 0
296+
283297

tests/baselines/reference/constructorFunctions.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument
2525
if (!(this instanceof C3)) return new C3();
2626
};
2727

28-
const c3_v1 = C3();
28+
const c3_v1 = C3(); // error: @class tag requires 'new'
2929
~~~~
3030
!!! error TS2348: Value of type 'typeof C3' is not callable. Did you mean to include 'new'?
3131
const c3_v2 = new C3();
@@ -35,7 +35,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument
3535
if (!(this instanceof C4)) return new C4();
3636
};
3737

38-
const c4_v1 = C4();
38+
const c4_v1 = C4(); // error: @class tag requires 'new'
3939
~~~~
4040
!!! error TS2348: Value of type 'typeof C4' is not callable. Did you mean to include 'new'?
4141
const c4_v2 = new C4();

tests/baselines/reference/constructorFunctions.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function C3() {
4949

5050
};
5151

52-
const c3_v1 = C3();
52+
const c3_v1 = C3(); // error: @class tag requires 'new'
5353
>c3_v1 : Symbol(c3_v1, Decl(index.js, 21, 5))
5454
>C3 : Symbol(C3, Decl(index.js, 14, 23))
5555

@@ -68,7 +68,7 @@ var C4 = function () {
6868

6969
};
7070

71-
const c4_v1 = C4();
71+
const c4_v1 = C4(); // error: @class tag requires 'new'
7272
>c4_v1 : Symbol(c4_v1, Decl(index.js, 29, 5))
7373
>C4 : Symbol(C4, Decl(index.js, 25, 3))
7474

tests/baselines/reference/constructorFunctions.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ function C3() {
7676

7777
};
7878

79-
const c3_v1 = C3();
80-
>c3_v1 : C3
81-
>C3() : C3
79+
const c3_v1 = C3(); // error: @class tag requires 'new'
80+
>c3_v1 : any
81+
>C3() : any
8282
>C3 : typeof C3
8383

8484
const c3_v2 = new C3();
@@ -102,9 +102,9 @@ var C4 = function () {
102102

103103
};
104104

105-
const c4_v1 = C4();
106-
>c4_v1 : C4
107-
>C4() : C4
105+
const c4_v1 = C4(); // error: @class tag requires 'new'
106+
>c4_v1 : any
107+
>C4() : any
108108
>C4 : typeof C4
109109

110110
const c4_v2 = new C4();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/conformance/jsdoc/example.js(3,16): error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'.
2+
tests/cases/conformance/jsdoc/example.js(5,2): error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
3+
4+
5+
==== tests/cases/conformance/jsdoc/example.js (2 errors) ====
6+
const obj = {
7+
/** @constructor */
8+
Foo() { this.bar = "bar" }
9+
~~~
10+
!!! error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'.
11+
};
12+
(new obj.Foo()).bar
13+
~~~~~~~~~~~~~
14+
!!! error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
15+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/conformance/jsdoc/example.js ===
2+
const obj = {
3+
>obj : Symbol(obj, Decl(example.js, 0, 5))
4+
5+
/** @constructor */
6+
Foo() { this.bar = "bar" }
7+
>Foo : Symbol(Foo, Decl(example.js, 0, 13))
8+
>this : Symbol(obj, Decl(example.js, 0, 11))
9+
>bar : Symbol(bar, Decl(example.js, 2, 9))
10+
11+
};
12+
(new obj.Foo()).bar
13+
>obj.Foo : Symbol(Foo, Decl(example.js, 0, 13))
14+
>obj : Symbol(obj, Decl(example.js, 0, 5))
15+
>Foo : Symbol(Foo, Decl(example.js, 0, 13))
16+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/conformance/jsdoc/example.js ===
2+
const obj = {
3+
>obj : { Foo(): void; }
4+
>{ /** @constructor */ Foo() { this.bar = "bar" }} : { Foo(): void; }
5+
6+
/** @constructor */
7+
Foo() { this.bar = "bar" }
8+
>Foo : () => void
9+
>this.bar = "bar" : "bar"
10+
>this.bar : any
11+
>this : { Foo(): void; }
12+
>bar : any
13+
>"bar" : "bar"
14+
15+
};
16+
(new obj.Foo()).bar
17+
>(new obj.Foo()).bar : any
18+
>(new obj.Foo()) : any
19+
>new obj.Foo() : any
20+
>obj.Foo : () => void
21+
>obj : { Foo(): void; }
22+
>Foo : () => void
23+
>bar : any
24+

0 commit comments

Comments
 (0)