From ea8ccc2ce4b0298a67ffa65b092b252e8bad312c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Nov 2018 09:51:07 -0800 Subject: [PATCH] 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 --- src/compiler/checker.ts | 5 ++++ src/compiler/types.ts | 1 + src/services/codefixes/inferFromUsage.ts | 10 +++---- .../codeFixInferFromUsageConstructor.ts | 27 ++++++++++++++++++ ...eFixInferFromUsageConstructorFunctionJS.ts | 28 +++++++++++++++++++ .../codeFixInferFromUsageRestParam2.ts | 2 +- 6 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInferFromUsageConstructor.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageConstructorFunctionJS.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1ad721ad5b4..848758d5be2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -283,6 +283,7 @@ namespace ts { getNumberType: () => numberType, createPromiseType, createArrayType, + getElementTypeOfArrayType, getBooleanType: () => booleanType, getFalseType: (fresh?) => fresh ? falseType : regularFalseType, getTrueType: (fresh?) => fresh ? trueType : regularTrueType, @@ -13222,6 +13223,10 @@ namespace ts { return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type).target === globalReadonlyArrayType; } + function getElementTypeOfArrayType(type: Type): Type | undefined { + return isArrayType(type) && (type as TypeReference).typeArguments ? (type as TypeReference).typeArguments![0] : undefined; + } + function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 897bfb00879..b4c405c3174 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3141,6 +3141,7 @@ namespace ts { /* @internal */ getNeverType(): Type; /* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): Type; /* @internal */ createArrayType(elementType: Type): Type; + /* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined; /* @internal */ createPromiseType(type: Type): Type; /* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type; diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index a814e31962a..9cf5133d982 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -413,10 +413,9 @@ namespace ts.codefix { cancellationToken.throwIfCancellationRequested(); inferTypeFromContext(reference, checker, usageContext); } - const isConstructor = declaration.kind === SyntaxKind.Constructor; - const callContexts = isConstructor ? usageContext.constructContexts : usageContext.callContexts; - return callContexts && declaration.parameters.map((parameter, parameterIndex): ParameterInference => { - const types: Type[] = []; + const callContexts = [...usageContext.constructContexts || [], ...usageContext.callContexts || []]; + return declaration.parameters.map((parameter, parameterIndex): ParameterInference => { + const types = []; const isRest = isRestParameter(parameter); let isOptional = false; for (const callContext of callContexts) { @@ -434,7 +433,8 @@ namespace ts.codefix { } } if (isIdentifier(parameter.name)) { - types.push(...inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken)); + const inferred = inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken); + types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); } const type = unifyFromContext(types, checker); return { diff --git a/tests/cases/fourslash/codeFixInferFromUsageConstructor.ts b/tests/cases/fourslash/codeFixInferFromUsageConstructor.ts new file mode 100644 index 00000000000..aa74fb2f6ff --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageConstructor.ts @@ -0,0 +1,27 @@ +/// +// @strictNullChecks: true + +////class TokenType { +//// label; +//// token; +//// constructor([|label, token? |]) { +//// this.label = label; +//// this.token = token || "N/A"; +//// } +////} +////new TokenType("HI"); +verify.codeFix({ + description: "Infer parameter types from usage", + index: 2, + newFileContent: + +`class TokenType { + label; + token; + constructor(label: string, token?: string | undefined ) { + this.label = label; + this.token = token || "N/A"; + } +} +new TokenType("HI");`, +}); diff --git a/tests/cases/fourslash/codeFixInferFromUsageConstructorFunctionJS.ts b/tests/cases/fourslash/codeFixInferFromUsageConstructorFunctionJS.ts new file mode 100644 index 00000000000..2177e5b8ea0 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageConstructorFunctionJS.ts @@ -0,0 +1,28 @@ +/// +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: test.js +////function TokenType([|label, token |]) { +//// this.label = label; +//// this.token = token || "N/A"; +////}; +////new TokenType("HI") + +verify.codeFix({ + description: "Infer parameter types from usage", + index: 0, + newFileContent: + +`/** + * @param {string} label + * @param {string} [token] + */ +function TokenType(label, token ) { + this.label = label; + this.token = token || "N/A"; +}; +new TokenType("HI")`, +}); + + diff --git a/tests/cases/fourslash/codeFixInferFromUsageRestParam2.ts b/tests/cases/fourslash/codeFixInferFromUsageRestParam2.ts index 1e0c5f7a195..0b0d0e1dfbf 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageRestParam2.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageRestParam2.ts @@ -9,4 +9,4 @@ ////f(3, false, "s2"); ////f(4, "s1", "s2", false, "s4"); -verify.rangeAfterCodeFix("...rest: (string | boolean)[]"); \ No newline at end of file +verify.rangeAfterCodeFix("...rest: (string | boolean)[]");