Skip to content

Commit ea8ccc2

Browse files
authored
In JS, constructor functions infer from call+construct (#28353)
* constructor functions infer from call+construct Also fix an incorrect combining of inferences for rest parameters: the inferred types will be arrays in the body of the function and the arguments from outside the function will be the element type. * All functions infer from call+construct contexts
1 parent 1089424 commit ea8ccc2

File tree

6 files changed

+67
-6
lines changed

6 files changed

+67
-6
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ namespace ts {
283283
getNumberType: () => numberType,
284284
createPromiseType,
285285
createArrayType,
286+
getElementTypeOfArrayType,
286287
getBooleanType: () => booleanType,
287288
getFalseType: (fresh?) => fresh ? falseType : regularFalseType,
288289
getTrueType: (fresh?) => fresh ? trueType : regularTrueType,
@@ -13222,6 +13223,10 @@ namespace ts {
1322213223
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
1322313224
}
1322413225

13226+
function getElementTypeOfArrayType(type: Type): Type | undefined {
13227+
return isArrayType(type) && (type as TypeReference).typeArguments ? (type as TypeReference).typeArguments![0] : undefined;
13228+
}
13229+
1322513230
function isArrayLikeType(type: Type): boolean {
1322613231
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
1322713232
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3141,6 +3141,7 @@ namespace ts {
31413141
/* @internal */ getNeverType(): Type;
31423142
/* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): Type;
31433143
/* @internal */ createArrayType(elementType: Type): Type;
3144+
/* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined;
31443145
/* @internal */ createPromiseType(type: Type): Type;
31453146

31463147
/* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type;

src/services/codefixes/inferFromUsage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,9 @@ namespace ts.codefix {
413413
cancellationToken.throwIfCancellationRequested();
414414
inferTypeFromContext(reference, checker, usageContext);
415415
}
416-
const isConstructor = declaration.kind === SyntaxKind.Constructor;
417-
const callContexts = isConstructor ? usageContext.constructContexts : usageContext.callContexts;
418-
return callContexts && declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
419-
const types: Type[] = [];
416+
const callContexts = [...usageContext.constructContexts || [], ...usageContext.callContexts || []];
417+
return declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
418+
const types = [];
420419
const isRest = isRestParameter(parameter);
421420
let isOptional = false;
422421
for (const callContext of callContexts) {
@@ -434,7 +433,8 @@ namespace ts.codefix {
434433
}
435434
}
436435
if (isIdentifier(parameter.name)) {
437-
types.push(...inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken));
436+
const inferred = inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken);
437+
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
438438
}
439439
const type = unifyFromContext(types, checker);
440440
return {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='fourslash.ts' />
2+
// @strictNullChecks: true
3+
4+
////class TokenType {
5+
//// label;
6+
//// token;
7+
//// constructor([|label, token? |]) {
8+
//// this.label = label;
9+
//// this.token = token || "N/A";
10+
//// }
11+
////}
12+
////new TokenType("HI");
13+
verify.codeFix({
14+
description: "Infer parameter types from usage",
15+
index: 2,
16+
newFileContent:
17+
18+
`class TokenType {
19+
label;
20+
token;
21+
constructor(label: string, token?: string | undefined ) {
22+
this.label = label;
23+
this.token = token || "N/A";
24+
}
25+
}
26+
new TokenType("HI");`,
27+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path='fourslash.ts' />
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @noImplicitAny: true
5+
// @Filename: test.js
6+
////function TokenType([|label, token |]) {
7+
//// this.label = label;
8+
//// this.token = token || "N/A";
9+
////};
10+
////new TokenType("HI")
11+
12+
verify.codeFix({
13+
description: "Infer parameter types from usage",
14+
index: 0,
15+
newFileContent:
16+
17+
`/**
18+
* @param {string} label
19+
* @param {string} [token]
20+
*/
21+
function TokenType(label, token ) {
22+
this.label = label;
23+
this.token = token || "N/A";
24+
};
25+
new TokenType("HI")`,
26+
});
27+
28+

tests/cases/fourslash/codeFixInferFromUsageRestParam2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
////f(3, false, "s2");
1010
////f(4, "s1", "s2", false, "s4");
1111

12-
verify.rangeAfterCodeFix("...rest: (string | boolean)[]");
12+
verify.rangeAfterCodeFix("...rest: (string | boolean)[]");

0 commit comments

Comments
 (0)