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