1/* @internal */ 2namespace ts.codefix { 3 const fixId = "inferFromUsage"; 4 const errorCodes = [ 5 // Variable declarations 6 Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, 7 8 // Variable uses 9 Diagnostics.Variable_0_implicitly_has_an_1_type.code, 10 11 // Parameter declarations 12 Diagnostics.Parameter_0_implicitly_has_an_1_type.code, 13 Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code, 14 15 // Get Accessor declarations 16 Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code, 17 Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code, 18 19 // Set Accessor declarations 20 Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code, 21 22 // Property declarations 23 Diagnostics.Member_0_implicitly_has_an_1_type.code, 24 25 //// Suggestions 26 // Variable declarations 27 Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code, 28 29 // Variable uses 30 Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code, 31 32 // Parameter declarations 33 Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code, 34 Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code, 35 36 // Get Accessor declarations 37 Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code, 38 Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code, 39 40 // Set Accessor declarations 41 Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code, 42 43 // Property declarations 44 Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code, 45 46 // Function expressions and declarations 47 Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code, 48 ]; 49 registerCodeFix({ 50 errorCodes, 51 getCodeActions(context) { 52 const { sourceFile, program, span: { start }, errorCode, cancellationToken, host, preferences } = context; 53 54 const token = getTokenAtPosition(sourceFile, start); 55 let declaration: Declaration | undefined; 56 const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host, preferences); }); 57 const name = declaration && getNameOfDeclaration(declaration); 58 return !name || changes.length === 0 ? undefined 59 : [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)]; 60 }, 61 fixIds: [fixId], 62 getAllCodeActions(context) { 63 const { sourceFile, program, cancellationToken, host, preferences } = context; 64 const markSeen = nodeSeenTracker(); 65 return codeFixAll(context, errorCodes, (changes, err) => { 66 doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host, preferences); 67 }); 68 }, 69 }); 70 71 function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage { 72 switch (errorCode) { 73 case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: 74 case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code: 75 return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217 76 case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: 77 case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code: 78 return Diagnostics.Infer_parameter_types_from_usage; 79 case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code: 80 return Diagnostics.Infer_this_type_of_0_from_usage; 81 default: 82 return Diagnostics.Infer_type_of_0_from_usage; 83 } 84 } 85 86 /** Map suggestion code to error code */ 87 function mapSuggestionDiagnostic(errorCode: number) { 88 switch (errorCode) { 89 case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code: 90 return Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code; 91 case Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code: 92 return Diagnostics.Variable_0_implicitly_has_an_1_type.code; 93 case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code: 94 return Diagnostics.Parameter_0_implicitly_has_an_1_type.code; 95 case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code: 96 return Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code; 97 case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code: 98 return Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code; 99 case Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code: 100 return Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code; 101 case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code: 102 return Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code; 103 case Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code: 104 return Diagnostics.Member_0_implicitly_has_an_1_type.code; 105 } 106 return errorCode; 107 } 108 109 function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost, preferences: UserPreferences): Declaration | undefined { 110 if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) { 111 return undefined; 112 } 113 114 const { parent } = token; 115 const importAdder = createImportAdder(sourceFile, program, preferences, host); 116 errorCode = mapSuggestionDiagnostic(errorCode); 117 switch (errorCode) { 118 // Variable and Property declarations 119 case Diagnostics.Member_0_implicitly_has_an_1_type.code: 120 case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: 121 if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location 122 annotateVariableDeclaration(changes, importAdder, sourceFile, parent, program, host, cancellationToken); 123 importAdder.writeFixes(changes); 124 return parent; 125 } 126 if (isPropertyAccessExpression(parent)) { 127 const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken); 128 const typeNode = getTypeNodeIfAccessible(type, parent, program, host); 129 if (typeNode) { 130 // Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags 131 const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode), /*comment*/ ""); 132 addJSDocTags(changes, sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]); 133 } 134 importAdder.writeFixes(changes); 135 return parent; 136 } 137 return undefined; 138 139 case Diagnostics.Variable_0_implicitly_has_an_1_type.code: { 140 const symbol = program.getTypeChecker().getSymbolAtLocation(token); 141 if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) { 142 annotateVariableDeclaration(changes, importAdder, sourceFile, symbol.valueDeclaration, program, host, cancellationToken); 143 importAdder.writeFixes(changes); 144 return symbol.valueDeclaration; 145 } 146 return undefined; 147 } 148 } 149 150 const containingFunction = getContainingFunction(token); 151 if (containingFunction === undefined) { 152 return undefined; 153 } 154 155 let declaration: Declaration | undefined; 156 switch (errorCode) { 157 // Parameter declarations 158 case Diagnostics.Parameter_0_implicitly_has_an_1_type.code: 159 if (isSetAccessorDeclaration(containingFunction)) { 160 annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken); 161 declaration = containingFunction; 162 break; 163 } 164 // falls through 165 case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: 166 if (markSeen(containingFunction)) { 167 const param = cast(parent, isParameter); 168 annotateParameters(changes, importAdder, sourceFile, param, containingFunction, program, host, cancellationToken); 169 declaration = param; 170 } 171 break; 172 173 // Get Accessor declarations 174 case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code: 175 case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code: 176 if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) { 177 annotate(changes, importAdder, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host); 178 declaration = containingFunction; 179 } 180 break; 181 182 // Set Accessor declarations 183 case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: 184 if (isSetAccessorDeclaration(containingFunction)) { 185 annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken); 186 declaration = containingFunction; 187 } 188 break; 189 190 // Function 'this' 191 case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code: 192 if (textChanges.isThisTypeAnnotatable(containingFunction) && markSeen(containingFunction)) { 193 annotateThis(changes, sourceFile, containingFunction, program, host, cancellationToken); 194 declaration = containingFunction; 195 } 196 break; 197 198 default: 199 return Debug.fail(String(errorCode)); 200 } 201 202 importAdder.writeFixes(changes); 203 return declaration; 204 } 205 206 function annotateVariableDeclaration( 207 changes: textChanges.ChangeTracker, 208 importAdder: ImportAdder, 209 sourceFile: SourceFile, 210 declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, 211 program: Program, 212 host: LanguageServiceHost, 213 cancellationToken: CancellationToken, 214 ): void { 215 if (isIdentifier(declaration.name)) { 216 annotate(changes, importAdder, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host); 217 } 218 } 219 220 function annotateParameters( 221 changes: textChanges.ChangeTracker, 222 importAdder: ImportAdder, 223 sourceFile: SourceFile, 224 parameterDeclaration: ParameterDeclaration, 225 containingFunction: SignatureDeclaration, 226 program: Program, 227 host: LanguageServiceHost, 228 cancellationToken: CancellationToken, 229 ): void { 230 if (!isIdentifier(parameterDeclaration.name)) { 231 return; 232 } 233 234 const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken); 235 Debug.assert(containingFunction.parameters.length === parameterInferences.length, "Parameter count and inference count should match"); 236 237 if (isInJSFile(containingFunction)) { 238 annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host); 239 } 240 else { 241 const needParens = isArrowFunction(containingFunction) && !findChildOfKind(containingFunction, SyntaxKind.OpenParenToken, sourceFile); 242 if (needParens) changes.insertNodeBefore(sourceFile, first(containingFunction.parameters), factory.createToken(SyntaxKind.OpenParenToken)); 243 for (const { declaration, type } of parameterInferences) { 244 if (declaration && !declaration.type && !declaration.initializer) { 245 annotate(changes, importAdder, sourceFile, declaration, type, program, host); 246 } 247 } 248 if (needParens) changes.insertNodeAfter(sourceFile, last(containingFunction.parameters), factory.createToken(SyntaxKind.CloseParenToken)); 249 } 250 } 251 252 function annotateThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: textChanges.ThisTypeAnnotatable, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken) { 253 const references = getFunctionReferences(containingFunction, sourceFile, program, cancellationToken); 254 if (!references || !references.length) { 255 return; 256 } 257 const thisInference = inferTypeFromReferences(program, references, cancellationToken).thisParameter(); 258 const typeNode = getTypeNodeIfAccessible(thisInference, containingFunction, program, host); 259 if (!typeNode) { 260 return; 261 } 262 263 if (isInJSFile(containingFunction)) { 264 annotateJSDocThis(changes, sourceFile, containingFunction, typeNode); 265 } 266 else { 267 changes.tryInsertThisTypeAnnotation(sourceFile, containingFunction, typeNode); 268 } 269 } 270 271 function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: SignatureDeclaration, typeNode: TypeNode) { 272 addJSDocTags(changes, sourceFile, containingFunction, [ 273 factory.createJSDocThisTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode)), 274 ]); 275 } 276 277 function annotateSetAccessor( 278 changes: textChanges.ChangeTracker, 279 importAdder: ImportAdder, 280 sourceFile: SourceFile, 281 setAccessorDeclaration: SetAccessorDeclaration, 282 program: Program, 283 host: LanguageServiceHost, 284 cancellationToken: CancellationToken, 285 ): void { 286 const param = firstOrUndefined(setAccessorDeclaration.parameters); 287 if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) { 288 let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken); 289 if (type === program.getTypeChecker().getAnyType()) { 290 type = inferTypeForVariableFromUsage(param.name, program, cancellationToken); 291 } 292 if (isInJSFile(setAccessorDeclaration)) { 293 annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host); 294 } 295 else { 296 annotate(changes, importAdder, sourceFile, param, type, program, host); 297 } 298 } 299 } 300 301 function annotate(changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void { 302 const typeNode = getTypeNodeIfAccessible(type, declaration, program, host); 303 if (typeNode) { 304 if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) { 305 const parent = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : declaration; 306 if (!parent) { 307 return; 308 } 309 const typeExpression = factory.createJSDocTypeExpression(typeNode); 310 const typeTag = isGetAccessorDeclaration(declaration) ? factory.createJSDocReturnTag(/*tagName*/ undefined, typeExpression, "") : factory.createJSDocTypeTag(/*tagName*/ undefined, typeExpression, ""); 311 addJSDocTags(changes, sourceFile, parent, [typeTag]); 312 } 313 else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) { 314 changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode); 315 } 316 } 317 } 318 319 function tryReplaceImportTypeNodeWithAutoImport( 320 typeNode: TypeNode, 321 declaration: textChanges.TypeAnnotatable, 322 sourceFile: SourceFile, 323 changes: textChanges.ChangeTracker, 324 importAdder: ImportAdder, 325 scriptTarget: ScriptTarget 326 ): boolean { 327 const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); 328 if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) { 329 forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true)); 330 return true; 331 } 332 return false; 333 } 334 335 function annotateJSDocParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterInferences: readonly ParameterInference[], program: Program, host: LanguageServiceHost): void { 336 const signature = parameterInferences.length && parameterInferences[0].declaration.parent; 337 if (!signature) { 338 return; 339 } 340 341 const inferences = mapDefined(parameterInferences, inference => { 342 const param = inference.declaration; 343 // only infer parameters that have (1) no type and (2) an accessible inferred type 344 if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) { 345 return; 346 } 347 const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host); 348 if (typeNode) { 349 const name = factory.cloneNode(param.name); 350 setEmitFlags(name, EmitFlags.NoComments | EmitFlags.NoNestedComments); 351 return { name: factory.cloneNode(param.name), param, isOptional: !!inference.isOptional, typeNode }; 352 } 353 }); 354 355 if (!inferences.length) { 356 return; 357 } 358 359 if (isArrowFunction(signature) || isFunctionExpression(signature)) { 360 const needParens = isArrowFunction(signature) && !findChildOfKind(signature, SyntaxKind.OpenParenToken, sourceFile); 361 if (needParens) { 362 changes.insertNodeBefore(sourceFile, first(signature.parameters), factory.createToken(SyntaxKind.OpenParenToken)); 363 } 364 365 forEach(inferences, ({ typeNode, param }) => { 366 const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode)); 367 const jsDoc = factory.createJSDocComment(/*comment*/ undefined, [typeTag]); 368 changes.insertNodeAt(sourceFile, param.getStart(sourceFile), jsDoc, { suffix: " " }); 369 }); 370 371 if (needParens) { 372 changes.insertNodeAfter(sourceFile, last(signature.parameters), factory.createToken(SyntaxKind.CloseParenToken)); 373 } 374 } 375 else { 376 const paramTags = map(inferences, ({ name, typeNode, isOptional }) => 377 factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, "")); 378 addJSDocTags(changes, sourceFile, signature, paramTags); 379 } 380 } 381 382 export function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { 383 const comments = mapDefined(parent.jsDoc, j => j.comment); 384 const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); 385 const unmergedNewTags = newTags.filter(newTag => !oldTags || !oldTags.some((tag, i) => { 386 const merged = tryMergeJsdocTags(tag, newTag); 387 if (merged) oldTags[i] = merged; 388 return !!merged; 389 })); 390 const tag = factory.createJSDocComment(comments.join("\n"), factory.createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags])); 391 const jsDocNode = parent.kind === SyntaxKind.ArrowFunction ? getJsDocNodeForArrowFunction(parent) : parent; 392 jsDocNode.jsDoc = parent.jsDoc; 393 jsDocNode.jsDocCache = parent.jsDocCache; 394 changes.insertJsdocCommentBefore(sourceFile, jsDocNode, tag); 395 } 396 397 function getJsDocNodeForArrowFunction(signature: ArrowFunction): HasJSDoc { 398 if (signature.parent.kind === SyntaxKind.PropertyDeclaration) { 399 return <HasJSDoc>signature.parent; 400 } 401 return <HasJSDoc>signature.parent.parent; 402 } 403 404 function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined { 405 if (oldTag.kind !== newTag.kind) { 406 return undefined; 407 } 408 switch (oldTag.kind) { 409 case SyntaxKind.JSDocParameterTag: { 410 const oldParam = oldTag as JSDocParameterTag; 411 const newParam = newTag as JSDocParameterTag; 412 return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText 413 ? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment) 414 : undefined; 415 } 416 case SyntaxKind.JSDocReturnTag: 417 return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment); 418 } 419 } 420 421 function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): readonly Identifier[] { 422 // Position shouldn't matter since token is not a SourceFile. 423 return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry => 424 entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined); 425 } 426 427 function inferTypeForVariableFromUsage(token: Identifier | PrivateIdentifier, program: Program, cancellationToken: CancellationToken): Type { 428 const references = getReferences(token, program, cancellationToken); 429 return inferTypeFromReferences(program, references, cancellationToken).single(); 430 } 431 432 function inferTypeForParametersFromUsage(func: SignatureDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken) { 433 const references = getFunctionReferences(func, sourceFile, program, cancellationToken); 434 return references && inferTypeFromReferences(program, references, cancellationToken).parameters(func) || 435 func.parameters.map<ParameterInference>(p => ({ 436 declaration: p, 437 type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType() 438 })); 439 } 440 441 function getFunctionReferences(containingFunction: SignatureDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): readonly Identifier[] | undefined { 442 let searchToken; 443 switch (containingFunction.kind) { 444 case SyntaxKind.Constructor: 445 searchToken = findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile); 446 break; 447 case SyntaxKind.ArrowFunction: 448 case SyntaxKind.FunctionExpression: 449 const parent = containingFunction.parent; 450 searchToken = (isVariableDeclaration(parent) || isPropertyDeclaration(parent)) && isIdentifier(parent.name) ? 451 parent.name : 452 containingFunction.name; 453 break; 454 case SyntaxKind.FunctionDeclaration: 455 case SyntaxKind.MethodDeclaration: 456 case SyntaxKind.MethodSignature: 457 searchToken = containingFunction.name; 458 break; 459 } 460 461 if (!searchToken) { 462 return undefined; 463 } 464 465 return getReferences(searchToken, program, cancellationToken); 466 } 467 468 interface ParameterInference { 469 readonly declaration: ParameterDeclaration; 470 readonly type: Type; 471 readonly isOptional?: boolean; 472 } 473 474 function inferTypeFromReferences(program: Program, references: readonly Identifier[], cancellationToken: CancellationToken) { 475 const checker = program.getTypeChecker(); 476 const builtinConstructors: { [s: string]: (t: Type) => Type } = { 477 string: () => checker.getStringType(), 478 number: () => checker.getNumberType(), 479 Array: t => checker.createArrayType(t), 480 Promise: t => checker.createPromiseType(t), 481 }; 482 const builtins = [ 483 checker.getStringType(), 484 checker.getNumberType(), 485 checker.createArrayType(checker.getAnyType()), 486 checker.createPromiseType(checker.getAnyType()), 487 ]; 488 489 return { 490 single, 491 parameters, 492 thisParameter, 493 }; 494 495 interface CallUsage { 496 argumentTypes: Type[]; 497 return_: Usage; 498 } 499 500 interface Usage { 501 isNumber: boolean | undefined; 502 isString: boolean | undefined; 503 /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */ 504 isNumberOrString: boolean | undefined; 505 506 candidateTypes: Type[] | undefined; 507 properties: UnderscoreEscapedMap<Usage> | undefined; 508 calls: CallUsage[] | undefined; 509 constructs: CallUsage[] | undefined; 510 numberIndex: Usage | undefined; 511 stringIndex: Usage | undefined; 512 candidateThisTypes: Type[] | undefined; 513 inferredTypes: Type[] | undefined; 514 } 515 516 function createEmptyUsage(): Usage { 517 return { 518 isNumber: undefined, 519 isString: undefined, 520 isNumberOrString: undefined, 521 candidateTypes: undefined, 522 properties: undefined, 523 calls: undefined, 524 constructs: undefined, 525 numberIndex: undefined, 526 stringIndex: undefined, 527 candidateThisTypes: undefined, 528 inferredTypes: undefined, 529 }; 530 } 531 532 function combineUsages(usages: Usage[]): Usage { 533 const combinedProperties = new Map<__String, Usage[]>(); 534 for (const u of usages) { 535 if (u.properties) { 536 u.properties.forEach((p, name) => { 537 if (!combinedProperties.has(name)) { 538 combinedProperties.set(name, []); 539 } 540 combinedProperties.get(name)!.push(p); 541 }); 542 } 543 } 544 const properties = new Map<__String, Usage>(); 545 combinedProperties.forEach((ps, name) => { 546 properties.set(name, combineUsages(ps)); 547 }); 548 return { 549 isNumber: usages.some(u => u.isNumber), 550 isString: usages.some(u => u.isString), 551 isNumberOrString: usages.some(u => u.isNumberOrString), 552 candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], 553 properties, 554 calls: flatMap(usages, u => u.calls) as CallUsage[], 555 constructs: flatMap(usages, u => u.constructs) as CallUsage[], 556 numberIndex: forEach(usages, u => u.numberIndex), 557 stringIndex: forEach(usages, u => u.stringIndex), 558 candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], 559 inferredTypes: undefined, // clear type cache 560 }; 561 } 562 563 function single(): Type { 564 return combineTypes(inferTypesFromReferencesSingle(references)); 565 } 566 567 function parameters(declaration: SignatureDeclaration): ParameterInference[] | undefined { 568 if (references.length === 0 || !declaration.parameters) { 569 return undefined; 570 } 571 572 const usage = createEmptyUsage(); 573 for (const reference of references) { 574 cancellationToken.throwIfCancellationRequested(); 575 calculateUsageOfNode(reference, usage); 576 } 577 const calls = [...usage.constructs || [], ...usage.calls || []]; 578 return declaration.parameters.map((parameter, parameterIndex): ParameterInference => { 579 const types = []; 580 const isRest = isRestParameter(parameter); 581 let isOptional = false; 582 for (const call of calls) { 583 if (call.argumentTypes.length <= parameterIndex) { 584 isOptional = isInJSFile(declaration); 585 types.push(checker.getUndefinedType()); 586 } 587 else if (isRest) { 588 for (let i = parameterIndex; i < call.argumentTypes.length; i++) { 589 types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[i])); 590 } 591 } 592 else { 593 types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); 594 } 595 } 596 if (isIdentifier(parameter.name)) { 597 const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken)); 598 types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); 599 } 600 const type = combineTypes(types); 601 return { 602 type: isRest ? checker.createArrayType(type) : type, 603 isOptional: isOptional && !isRest, 604 declaration: parameter 605 }; 606 }); 607 } 608 609 function thisParameter() { 610 const usage = createEmptyUsage(); 611 for (const reference of references) { 612 cancellationToken.throwIfCancellationRequested(); 613 calculateUsageOfNode(reference, usage); 614 } 615 616 return combineTypes(usage.candidateThisTypes || emptyArray); 617 } 618 619 function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] { 620 const usage: Usage = createEmptyUsage(); 621 for (const reference of references) { 622 cancellationToken.throwIfCancellationRequested(); 623 calculateUsageOfNode(reference, usage); 624 } 625 return inferTypes(usage); 626 } 627 628 function calculateUsageOfNode(node: Expression, usage: Usage): void { 629 while (isRightSideOfQualifiedNameOrPropertyAccess(node)) { 630 node = <Expression>node.parent; 631 } 632 633 switch (node.parent.kind) { 634 case SyntaxKind.ExpressionStatement: 635 inferTypeFromExpressionStatement(node, usage); 636 break; 637 case SyntaxKind.PostfixUnaryExpression: 638 usage.isNumber = true; 639 break; 640 case SyntaxKind.PrefixUnaryExpression: 641 inferTypeFromPrefixUnaryExpression(<PrefixUnaryExpression>node.parent, usage); 642 break; 643 case SyntaxKind.BinaryExpression: 644 inferTypeFromBinaryExpression(node, <BinaryExpression>node.parent, usage); 645 break; 646 case SyntaxKind.CaseClause: 647 case SyntaxKind.DefaultClause: 648 inferTypeFromSwitchStatementLabel(<CaseOrDefaultClause>node.parent, usage); 649 break; 650 case SyntaxKind.CallExpression: 651 case SyntaxKind.NewExpression: 652 if ((<CallExpression | NewExpression>node.parent).expression === node) { 653 inferTypeFromCallExpression(<CallExpression | NewExpression>node.parent, usage); 654 } 655 else { 656 inferTypeFromContextualType(node, usage); 657 } 658 break; 659 case SyntaxKind.PropertyAccessExpression: 660 inferTypeFromPropertyAccessExpression(<PropertyAccessExpression>node.parent, usage); 661 break; 662 case SyntaxKind.ElementAccessExpression: 663 inferTypeFromPropertyElementExpression(<ElementAccessExpression>node.parent, node, usage); 664 break; 665 case SyntaxKind.PropertyAssignment: 666 case SyntaxKind.ShorthandPropertyAssignment: 667 inferTypeFromPropertyAssignment(<PropertyAssignment | ShorthandPropertyAssignment>node.parent, usage); 668 break; 669 case SyntaxKind.PropertyDeclaration: 670 inferTypeFromPropertyDeclaration(<PropertyDeclaration>node.parent, usage); 671 break; 672 case SyntaxKind.VariableDeclaration: { 673 const { name, initializer } = node.parent as VariableDeclaration; 674 if (node === name) { 675 if (initializer) { // This can happen for `let x = null;` which still has an implicit-any error. 676 addCandidateType(usage, checker.getTypeAtLocation(initializer)); 677 } 678 break; 679 } 680 } 681 // falls through 682 default: 683 return inferTypeFromContextualType(node, usage); 684 } 685 } 686 687 function inferTypeFromContextualType(node: Expression, usage: Usage): void { 688 if (isExpressionNode(node)) { 689 addCandidateType(usage, checker.getContextualType(node)); 690 } 691 } 692 693 function inferTypeFromExpressionStatement(node: Expression, usage: Usage): void { 694 addCandidateType(usage, isCallExpression(node) ? checker.getVoidType() : checker.getAnyType()); 695 } 696 697 function inferTypeFromPrefixUnaryExpression(node: PrefixUnaryExpression, usage: Usage): void { 698 switch (node.operator) { 699 case SyntaxKind.PlusPlusToken: 700 case SyntaxKind.MinusMinusToken: 701 case SyntaxKind.MinusToken: 702 case SyntaxKind.TildeToken: 703 usage.isNumber = true; 704 break; 705 706 case SyntaxKind.PlusToken: 707 usage.isNumberOrString = true; 708 break; 709 710 // case SyntaxKind.ExclamationToken: 711 // no inferences here; 712 } 713 } 714 715 function inferTypeFromBinaryExpression(node: Expression, parent: BinaryExpression, usage: Usage): void { 716 switch (parent.operatorToken.kind) { 717 // ExponentiationOperator 718 case SyntaxKind.AsteriskAsteriskToken: 719 720 // MultiplicativeOperator 721 // falls through 722 case SyntaxKind.AsteriskToken: 723 case SyntaxKind.SlashToken: 724 case SyntaxKind.PercentToken: 725 726 // ShiftOperator 727 // falls through 728 case SyntaxKind.LessThanLessThanToken: 729 case SyntaxKind.GreaterThanGreaterThanToken: 730 case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: 731 732 // BitwiseOperator 733 // falls through 734 case SyntaxKind.AmpersandToken: 735 case SyntaxKind.BarToken: 736 case SyntaxKind.CaretToken: 737 738 // CompoundAssignmentOperator 739 // falls through 740 case SyntaxKind.MinusEqualsToken: 741 case SyntaxKind.AsteriskAsteriskEqualsToken: 742 case SyntaxKind.AsteriskEqualsToken: 743 case SyntaxKind.SlashEqualsToken: 744 case SyntaxKind.PercentEqualsToken: 745 case SyntaxKind.AmpersandEqualsToken: 746 case SyntaxKind.BarEqualsToken: 747 case SyntaxKind.CaretEqualsToken: 748 case SyntaxKind.LessThanLessThanEqualsToken: 749 case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: 750 case SyntaxKind.GreaterThanGreaterThanEqualsToken: 751 752 // AdditiveOperator 753 // falls through 754 case SyntaxKind.MinusToken: 755 756 // RelationalOperator 757 // falls through 758 case SyntaxKind.LessThanToken: 759 case SyntaxKind.LessThanEqualsToken: 760 case SyntaxKind.GreaterThanToken: 761 case SyntaxKind.GreaterThanEqualsToken: 762 const operandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left); 763 if (operandType.flags & TypeFlags.EnumLike) { 764 addCandidateType(usage, operandType); 765 } 766 else { 767 usage.isNumber = true; 768 } 769 break; 770 771 case SyntaxKind.PlusEqualsToken: 772 case SyntaxKind.PlusToken: 773 const otherOperandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left); 774 if (otherOperandType.flags & TypeFlags.EnumLike) { 775 addCandidateType(usage, otherOperandType); 776 } 777 else if (otherOperandType.flags & TypeFlags.NumberLike) { 778 usage.isNumber = true; 779 } 780 else if (otherOperandType.flags & TypeFlags.StringLike) { 781 usage.isString = true; 782 } 783 else if (otherOperandType.flags & TypeFlags.Any) { 784 // do nothing, maybe we'll learn something elsewhere 785 } 786 else { 787 usage.isNumberOrString = true; 788 } 789 break; 790 791 // AssignmentOperators 792 case SyntaxKind.EqualsToken: 793 case SyntaxKind.EqualsEqualsToken: 794 case SyntaxKind.EqualsEqualsEqualsToken: 795 case SyntaxKind.ExclamationEqualsEqualsToken: 796 case SyntaxKind.ExclamationEqualsToken: 797 addCandidateType(usage, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left)); 798 break; 799 800 case SyntaxKind.InKeyword: 801 if (node === parent.left) { 802 usage.isString = true; 803 } 804 break; 805 806 // LogicalOperator Or NullishCoalescing 807 case SyntaxKind.BarBarToken: 808 case SyntaxKind.QuestionQuestionToken: 809 if (node === parent.left && 810 (node.parent.parent.kind === SyntaxKind.VariableDeclaration || isAssignmentExpression(node.parent.parent, /*excludeCompoundAssignment*/ true))) { 811 // var x = x || {}; 812 // TODO: use getFalsyflagsOfType 813 addCandidateType(usage, checker.getTypeAtLocation(parent.right)); 814 } 815 break; 816 817 case SyntaxKind.AmpersandAmpersandToken: 818 case SyntaxKind.CommaToken: 819 case SyntaxKind.InstanceOfKeyword: 820 // nothing to infer here 821 break; 822 } 823 } 824 825 function inferTypeFromSwitchStatementLabel(parent: CaseOrDefaultClause, usage: Usage): void { 826 addCandidateType(usage, checker.getTypeAtLocation(parent.parent.parent.expression)); 827 } 828 829 function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void { 830 const call: CallUsage = { 831 argumentTypes: [], 832 return_: createEmptyUsage() 833 }; 834 835 if (parent.arguments) { 836 for (const argument of parent.arguments) { 837 call.argumentTypes.push(checker.getTypeAtLocation(argument)); 838 } 839 } 840 841 calculateUsageOfNode(parent, call.return_); 842 if (parent.kind === SyntaxKind.CallExpression) { 843 (usage.calls || (usage.calls = [])).push(call); 844 } 845 else { 846 (usage.constructs || (usage.constructs = [])).push(call); 847 } 848 } 849 850 function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, usage: Usage): void { 851 const name = escapeLeadingUnderscores(parent.name.text); 852 if (!usage.properties) { 853 usage.properties = new Map(); 854 } 855 const propertyUsage = usage.properties.get(name) || createEmptyUsage(); 856 calculateUsageOfNode(parent, propertyUsage); 857 usage.properties.set(name, propertyUsage); 858 } 859 860 function inferTypeFromPropertyElementExpression(parent: ElementAccessExpression, node: Expression, usage: Usage): void { 861 if (node === parent.argumentExpression) { 862 usage.isNumberOrString = true; 863 return; 864 } 865 else { 866 const indexType = checker.getTypeAtLocation(parent.argumentExpression); 867 const indexUsage = createEmptyUsage(); 868 calculateUsageOfNode(parent, indexUsage); 869 if (indexType.flags & TypeFlags.NumberLike) { 870 usage.numberIndex = indexUsage; 871 } 872 else { 873 usage.stringIndex = indexUsage; 874 } 875 } 876 } 877 878 function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, usage: Usage) { 879 const nodeWithRealType = isVariableDeclaration(assignment.parent.parent) ? 880 assignment.parent.parent : 881 assignment.parent; 882 addCandidateThisType(usage, checker.getTypeAtLocation(nodeWithRealType)); 883 } 884 885 function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, usage: Usage) { 886 addCandidateThisType(usage, checker.getTypeAtLocation(declaration.parent)); 887 } 888 889 interface Priority { 890 high: (t: Type) => boolean; 891 low: (t: Type) => boolean; 892 } 893 894 function removeLowPriorityInferences(inferences: readonly Type[], priorities: Priority[]): Type[] { 895 const toRemove: ((t: Type) => boolean)[] = []; 896 for (const i of inferences) { 897 for (const { high, low } of priorities) { 898 if (high(i)) { 899 Debug.assert(!low(i), "Priority can't have both low and high"); 900 toRemove.push(low); 901 } 902 } 903 } 904 return inferences.filter(i => toRemove.every(f => !f(i))); 905 } 906 907 function combineFromUsage(usage: Usage) { 908 return combineTypes(inferTypes(usage)); 909 } 910 911 function combineTypes(inferences: readonly Type[]): Type { 912 if (!inferences.length) return checker.getAnyType(); 913 914 // 1. string or number individually override string | number 915 // 2. non-any, non-void overrides any or void 916 // 3. non-nullable, non-any, non-void, non-anonymous overrides anonymous types 917 const stringNumber = checker.getUnionType([checker.getStringType(), checker.getNumberType()]); 918 const priorities: Priority[] = [ 919 { 920 high: t => t === checker.getStringType() || t === checker.getNumberType(), 921 low: t => t === stringNumber 922 }, 923 { 924 high: t => !(t.flags & (TypeFlags.Any | TypeFlags.Void)), 925 low: t => !!(t.flags & (TypeFlags.Any | TypeFlags.Void)) 926 }, 927 { 928 high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous), 929 low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous) 930 }]; 931 let good = removeLowPriorityInferences(inferences, priorities); 932 const anons = good.filter(i => getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[]; 933 if (anons.length) { 934 good = good.filter(i => !(getObjectFlags(i) & ObjectFlags.Anonymous)); 935 good.push(combineAnonymousTypes(anons)); 936 } 937 return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype)); 938 } 939 940 function combineAnonymousTypes(anons: AnonymousType[]) { 941 if (anons.length === 1) { 942 return anons[0]; 943 } 944 const calls = []; 945 const constructs = []; 946 const stringIndices = []; 947 const numberIndices = []; 948 let stringIndexReadonly = false; 949 let numberIndexReadonly = false; 950 const props = createMultiMap<Type>(); 951 for (const anon of anons) { 952 for (const p of checker.getPropertiesOfType(anon)) { 953 props.add(p.name, checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration)); 954 } 955 calls.push(...checker.getSignaturesOfType(anon, SignatureKind.Call)); 956 constructs.push(...checker.getSignaturesOfType(anon, SignatureKind.Construct)); 957 if (anon.stringIndexInfo) { 958 stringIndices.push(anon.stringIndexInfo.type); 959 stringIndexReadonly = stringIndexReadonly || anon.stringIndexInfo.isReadonly; 960 } 961 if (anon.numberIndexInfo) { 962 numberIndices.push(anon.numberIndexInfo.type); 963 numberIndexReadonly = numberIndexReadonly || anon.numberIndexInfo.isReadonly; 964 } 965 } 966 const members = mapEntries(props, (name, types) => { 967 const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0; 968 const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String); 969 s.type = checker.getUnionType(types); 970 return [name, s]; 971 }); 972 return checker.createAnonymousType( 973 anons[0].symbol, 974 members as UnderscoreEscapedMap<TransientSymbol>, 975 calls, 976 constructs, 977 stringIndices.length ? checker.createIndexInfo(checker.getUnionType(stringIndices), stringIndexReadonly) : undefined, 978 numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined); 979 } 980 981 function inferTypes(usage: Usage): Type[] { 982 const types = []; 983 984 if (usage.isNumber) { 985 types.push(checker.getNumberType()); 986 } 987 if (usage.isString) { 988 types.push(checker.getStringType()); 989 } 990 if (usage.isNumberOrString) { 991 types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()])); 992 } 993 if (usage.numberIndex) { 994 types.push(checker.createArrayType(combineFromUsage(usage.numberIndex))); 995 } 996 if (usage.properties?.size || usage.calls?.length || usage.constructs?.length || usage.stringIndex) { 997 types.push(inferStructuralType(usage)); 998 } 999 1000 types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); 1001 types.push(...inferNamedTypesFromProperties(usage)); 1002 1003 return types; 1004 } 1005 1006 function inferStructuralType(usage: Usage) { 1007 const members = new Map<__String, Symbol>(); 1008 if (usage.properties) { 1009 usage.properties.forEach((u, name) => { 1010 const symbol = checker.createSymbol(SymbolFlags.Property, name); 1011 symbol.type = combineFromUsage(u); 1012 members.set(name, symbol); 1013 }); 1014 } 1015 const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : []; 1016 const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : []; 1017 const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(combineFromUsage(usage.stringIndex), /*isReadonly*/ false); 1018 return checker.createAnonymousType(/*symbol*/ undefined, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); 1019 } 1020 1021 function inferNamedTypesFromProperties(usage: Usage): Type[] { 1022 if (!usage.properties || !usage.properties.size) return []; 1023 const types = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage)); 1024 if (0 < types.length && types.length < 3) { 1025 return types.map(t => inferInstantiationFromUsage(t, usage)); 1026 } 1027 return []; 1028 } 1029 1030 function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) { 1031 if (!usage.properties) return false; 1032 return !forEachEntry(usage.properties, (propUsage, name) => { 1033 const source = checker.getTypeOfPropertyOfType(type, name as string); 1034 if (!source) { 1035 return true; 1036 } 1037 if (propUsage.calls) { 1038 const sigs = checker.getSignaturesOfType(source, SignatureKind.Call); 1039 return !sigs.length || !checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); 1040 } 1041 else { 1042 return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage)); 1043 } 1044 }); 1045 } 1046 1047 /** 1048 * inference is limited to 1049 * 1. generic types with a single parameter 1050 * 2. inference to/from calls with a single signature 1051 */ 1052 function inferInstantiationFromUsage(type: Type, usage: Usage) { 1053 if (!(getObjectFlags(type) & ObjectFlags.Reference) || !usage.properties) { 1054 return type; 1055 } 1056 const generic = (type as TypeReference).target; 1057 const singleTypeParameter = singleOrUndefined(generic.typeParameters); 1058 if (!singleTypeParameter) return type; 1059 1060 const types: Type[] = []; 1061 usage.properties.forEach((propUsage, name) => { 1062 const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string); 1063 Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference."); 1064 types.push(...inferTypeParameters(genericPropertyType, combineFromUsage(propUsage), singleTypeParameter)); 1065 }); 1066 return builtinConstructors[type.symbol.escapedName as string](combineTypes(types)); 1067 } 1068 1069 function inferTypeParameters(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] { 1070 if (genericType === typeParameter) { 1071 return [usageType]; 1072 } 1073 else if (genericType.flags & TypeFlags.UnionOrIntersection) { 1074 return flatMap((genericType as UnionOrIntersectionType).types, t => inferTypeParameters(t, usageType, typeParameter)); 1075 } 1076 else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) { 1077 // this is wrong because we need a reference to the targetType to, so we can check that it's also a reference 1078 const genericArgs = checker.getTypeArguments(genericType as TypeReference); 1079 const usageArgs = checker.getTypeArguments(usageType as TypeReference); 1080 const types = []; 1081 if (genericArgs && usageArgs) { 1082 for (let i = 0; i < genericArgs.length; i++) { 1083 if (usageArgs[i]) { 1084 types.push(...inferTypeParameters(genericArgs[i], usageArgs[i], typeParameter)); 1085 } 1086 } 1087 } 1088 return types; 1089 } 1090 const genericSigs = checker.getSignaturesOfType(genericType, SignatureKind.Call); 1091 const usageSigs = checker.getSignaturesOfType(usageType, SignatureKind.Call); 1092 if (genericSigs.length === 1 && usageSigs.length === 1) { 1093 return inferFromSignatures(genericSigs[0], usageSigs[0], typeParameter); 1094 } 1095 return []; 1096 } 1097 1098 function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) { 1099 const types = []; 1100 for (let i = 0; i < genericSig.parameters.length; i++) { 1101 const genericParam = genericSig.parameters[i]; 1102 const usageParam = usageSig.parameters[i]; 1103 const isRest = genericSig.declaration && isRestParameter(genericSig.declaration.parameters[i]); 1104 if (!usageParam) { 1105 break; 1106 } 1107 let genericParamType = checker.getTypeOfSymbolAtLocation(genericParam, genericParam.valueDeclaration); 1108 const elementType = isRest && checker.getElementTypeOfArrayType(genericParamType); 1109 if (elementType) { 1110 genericParamType = elementType; 1111 } 1112 const targetType = (usageParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration); 1113 types.push(...inferTypeParameters(genericParamType, targetType, typeParameter)); 1114 } 1115 const genericReturn = checker.getReturnTypeOfSignature(genericSig); 1116 const usageReturn = checker.getReturnTypeOfSignature(usageSig); 1117 types.push(...inferTypeParameters(genericReturn, usageReturn, typeParameter)); 1118 return types; 1119 } 1120 1121 function getFunctionFromCalls(calls: CallUsage[]) { 1122 return checker.createAnonymousType(/*symbol*/ undefined, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); 1123 } 1124 1125 function getSignatureFromCalls(calls: CallUsage[]): Signature { 1126 const parameters: Symbol[] = []; 1127 const length = Math.max(...calls.map(c => c.argumentTypes.length)); 1128 for (let i = 0; i < length; i++) { 1129 const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); 1130 symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); 1131 if (calls.some(call => call.argumentTypes[i] === undefined)) { 1132 symbol.flags |= SymbolFlags.Optional; 1133 } 1134 parameters.push(symbol); 1135 } 1136 const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_))); 1137 // TODO: GH#18217 1138 return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, SignatureFlags.None); 1139 } 1140 1141 function addCandidateType(usage: Usage, type: Type | undefined) { 1142 if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) { 1143 (usage.candidateTypes || (usage.candidateTypes = [])).push(type); 1144 } 1145 } 1146 1147 function addCandidateThisType(usage: Usage, type: Type | undefined) { 1148 if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) { 1149 (usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type); 1150 } 1151 } 1152 } 1153} 1154