From 56a9871ff3faa79866170ace835b2f5509b594e7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 9 Sep 2020 18:13:33 -0700 Subject: [PATCH 1/2] Add inference based for 'Promise' based on call to 'resolve' --- src/compiler/checker.ts | 114 +++++++++++--- src/compiler/types.ts | 7 +- src/compiler/utilities.ts | 73 +++++++++ src/services/findAllReferences.ts | 35 ----- .../reference/api/tsserverlibrary.d.ts | 7 +- tests/baselines/reference/api/typescript.d.ts | 7 +- .../defaultExportInAwaitExpression01.types | 20 +-- .../defaultExportInAwaitExpression02.types | 20 +-- .../baselines/reference/inferenceLimit.types | 22 +-- .../revealingConstructorInference.symbols | 110 +++++++++++++ .../revealingConstructorInference.types | 147 ++++++++++++++++++ .../revealingConstructorInference.ts | 40 +++++ 12 files changed, 507 insertions(+), 95 deletions(-) create mode 100644 tests/baselines/reference/revealingConstructorInference.symbols create mode 100644 tests/baselines/reference/revealingConstructorInference.types create mode 100644 tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6615cc3f015ef..388255d87ee4d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19458,7 +19458,7 @@ namespace ts { source = getUnionType(sources); } else if (target.flags & TypeFlags.Intersection && some((target).types, - t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { + t => !!getInferenceInfoForType(inferences, t) || (isGenericMappedType(t) && !!getInferenceInfoForType(inferences, getHomomorphicTypeVariable(t) || neverType)))) { // We reduce intersection types only when they contain naked type parameters. For example, when // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the @@ -19490,7 +19490,7 @@ namespace ts { (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { return; } - const inference = getInferenceInfoForType(target); + const inference = getInferenceInfoForType(inferences, target); if (inference) { if (!inference.isFixed) { if (inference.priority === undefined || priority < inference.priority) { @@ -19687,21 +19687,10 @@ namespace ts { } } - function getInferenceInfoForType(type: Type) { - if (type.flags & TypeFlags.TypeVariable) { - for (const inference of inferences) { - if (type === inference.typeParameter) { - return inference; - } - } - } - return undefined; - } - function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { let typeVariable: Type | undefined; for (const type of types) { - const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); + const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(inferences, t)); if (!t || typeVariable && t !== typeVariable) { return undefined; } @@ -19722,7 +19711,7 @@ namespace ts { // equal priority (i.e. of equal quality) to what we would infer for a naked type // parameter. for (const t of targets) { - if (getInferenceInfoForType(t)) { + if (getInferenceInfoForType(inferences, t)) { nakedTypeVariable = t; typeVariableCount++; } @@ -19764,7 +19753,7 @@ namespace ts { // make from nested naked type variables and given slightly higher priority by virtue // of being first in the candidates array. for (const t of targets) { - if (getInferenceInfoForType(t)) { + if (getInferenceInfoForType(inferences, t)) { typeVariableCount++; } else { @@ -19778,7 +19767,7 @@ namespace ts { // we only infer to single naked type variables. if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { for (const t of targets) { - if (getInferenceInfoForType(t)) { + if (getInferenceInfoForType(inferences, t)) { inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } } @@ -19798,7 +19787,7 @@ namespace ts { // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source // type and then make a secondary inference from that type to T. We make a secondary inference // such that direct inferences to T get priority over inferences to Partial, for example. - const inference = getInferenceInfoForType((constraintType).type); + const inference = getInferenceInfoForType(inferences, (constraintType).type); if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { @@ -19909,7 +19898,7 @@ namespace ts { const middleLength = targetArity - startLength - endLength; if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { // Middle of target is [...T, ...U] and source is tuple type - const targetInfo = getInferenceInfoForType(elementTypes[startLength]); + const targetInfo = getInferenceInfoForType(inferences, elementTypes[startLength]); if (targetInfo && targetInfo.impliedArity !== undefined) { // Infer slices from source based on implied arity of T. inferFromTypes(sliceTupleType(source, startLength, sourceEndLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); @@ -20004,6 +19993,22 @@ namespace ts { } } + function getInferenceInfoForType(inferences: InferenceInfo[], type: Type) { + if (type.flags & TypeFlags.TypeVariable) { + for (const inference of inferences) { + if (type === inference.typeParameter) { + return inference; + } + } + } + return undefined; + } + + function hasHigherPriorityInference(inferences: InferenceInfo[], type: Type, priority: InferencePriority) { + const inference = getInferenceInfoForType(inferences, type); + return !!inference && (inference.isFixed || inference.priority !== undefined && inference.priority < priority); + } + function isTypeOrBaseIdenticalTo(s: Type, t: Type) { return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral); } @@ -20661,7 +20666,7 @@ namespace ts { } function isTypeSubsetOf(source: Type, target: Type) { - return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target); + return source === target || !!(target.flags & TypeFlags.Union) && isTypeSubsetOfUnion(source, target); } function isTypeSubsetOfUnion(source: Type, target: UnionType) { @@ -26020,6 +26025,75 @@ namespace ts { inferTypes(context.inferences, spreadType, restType); } + // Attempt to solve for `T` in `new Promise(resolve => resolve(t))` (also known as the "revealing constructor" pattern). + // To avoid too much complexity, we use a very restrictive heuristic: + // - Restrict to NewExpression to reduce overhead. + // - `signature` has a single parameter (`callbackType`) + // - `callbackType` has a single call signature (`callbackSignature`) (i.e., `executor: (resolve: (value: T | PromiseLike) => void) => void`) + // - `callbackSignature` has at least one parameter (`innerCallbackType`) + // - `innerCallbackType` has a single call signature (`innerCallbackSignature`) (i.e., `resolve: (value: T | PromiseLike) => void`) + // - `innerCallbackSignature` has a single parameter (`innerCallbackValueType`) + // - `innerCallbackValueType` contains type variable for which we are gathering inferences (i.e. `value: T | PromiseLike`) + // - The function (`callbackFunc`) passed as the argument to the parameter `callbackType` must be inline (i.e., an arrow function or function expression) + // - `callbackFunc` must have one parameter (`innerCallbackParam`) that is untyped (and thus would be contextually typed by `innerCallbackType`) + // If the above conditions are met then: + // - Determine the name in function `callbackFunc` given to the parameter `innerCallbackParam` + // - Find all references to that name in the body of the function `callbackFunc` + // - If `innerCallbackParam` is called directly, collect inferences for the type of the argument passed to the parameter (`innerCallbackValueType`) each call to `innerCallbackParam` + // - If `innerCallbackParam` is passed as the argument to another function, we can attempt to use the contextual type of that parameter for inference. + if (isNewExpression(node) && argCount === 1) { + const callbackType = getTypeAtPosition(signature, 0); // executor: ... + const callbackSignature = getSingleCallSignature(callbackType); // (resolve: (...) => ...) => ... + const callbackFunc = skipParentheses(args[0]); + if (callbackSignature && isFunctionExpressionOrArrowFunction(callbackFunc)) { + const sourceFile = getSourceFileOfNode(callbackFunc); + for (let callbackParamIndex = 0; callbackParamIndex < callbackFunc.parameters.length; callbackParamIndex++) { + const innerCallbackType = tryGetTypeAtPosition(callbackSignature, callbackParamIndex); // resolve: ... + const innerCallbackSignature = innerCallbackType && getSingleCallSignature(innerCallbackType); // (value: T | PromiseLike) => ... + const innerCallbackParam = callbackFunc.parameters[callbackParamIndex]; + if (innerCallbackSignature && getParameterCount(innerCallbackSignature) === 1 && isIdentifier(innerCallbackParam.name) && !getEffectiveTypeAnnotationNode(innerCallbackParam)) { + const innerCallbackValueType = getTypeAtPosition(innerCallbackSignature, 0); // value: ... + // Don't do the work if we already have a higher-priority inference. + if (some(signature.typeParameters, typeParam => isTypeSubsetOf(typeParam, innerCallbackValueType) && !hasHigherPriorityInference(context.inferences, typeParam, InferencePriority.RevealingConstructor))) { + const innerCallbackSymbol = getSymbolOfNode(innerCallbackParam); + const positions = getPossibleSymbolReferencePositions(sourceFile, idText(innerCallbackParam.name), callbackFunc); + if (positions.length) { + const candidateReferences = findNodesAtPositions(callbackFunc, positions, sourceFile); + if (candidateReferences.length) { + // The callback will not have a type associated with it, so we temporarily assign it `anyFunctionType` so that + // we do not trigger implicit `any` errors and so that we do not create inferences from it. + const links = getSymbolLinks(innerCallbackSymbol); + const savedType = links.type; + links.type = anyFunctionType; + // collect types for inferences to ppB + for (const candidateReference of candidateReferences) { + if (!isIdentifier(candidateReference) || candidateReference === innerCallbackParam.name) continue; + const candidateReferenceSymbol = resolveName(candidateReference, candidateReference.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (candidateReferenceSymbol !== innerCallbackSymbol) continue; + if (isCallExpression(candidateReference.parent) && candidateReference === candidateReference.parent.expression) { + const argType = + candidateReference.parent.arguments.length >= 1 ? checkExpression(candidateReference.parent.arguments[0]) : + voidType; + inferTypes(context.inferences, argType, innerCallbackValueType, InferencePriority.RevealingConstructor); + } + else if (isCallOrNewExpression(candidateReference.parent) && contains(candidateReference.parent.arguments, candidateReference)) { + const callbackType = getContextualType(candidateReference); + const callbackSignature = callbackType && getSingleCallSignature(callbackType); + const callbackParamType = callbackSignature && tryGetTypeAtPosition(callbackSignature, 0); + if (callbackParamType) { + inferTypes(context.inferences, callbackParamType, innerCallbackValueType, InferencePriority.RevealingConstructor); + } + } + } + links.type = savedType; + } + } + } + } + } + } + } + return getInferredTypes(context); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d17f444f165d2..158017f4eaa86 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5437,10 +5437,11 @@ namespace ts { ReturnType = 1 << 6, // Inference made from return type of generic function LiteralKeyof = 1 << 7, // Inference made from a string literal to a keyof T NoConstraints = 1 << 8, // Don't infer from constraints of instantiable types - AlwaysStrict = 1 << 9, // Always use strict rules for contravariant inferences - MaxValue = 1 << 10, // Seed for inference priority tracking + RevealingConstructor = 1 << 9, // Inference made to a callback in a "revealing constructor" (i.e., `new Promise(resolve => resolve(1))`) + AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences + MaxValue = 1 << 11, // Seed for inference priority tracking - PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates + PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof | RevealingConstructor, // These priorities imply that the resulting type should be a combination of all candidates Circularity = -1, // Inference circularity (value less than all other priorities) } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4e1874f653eba..4ad2230bb4b8f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6903,4 +6903,77 @@ namespace ts { return bindParentToChildIgnoringJSDoc(child, parent) || bindJSDoc(child); } } + + export function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile) { + const positions: number[] = []; + + /// TODO: Cache symbol existence for files to save text search + // Also, need to make this work for unicode escapes. + + // Be resilient in the face of a symbol with no name or zero length name + if (!symbolName || !symbolName.length) { + return positions as readonly number[] as SortedReadonlyArray; + } + + const text = sourceFile.text; + const sourceLength = text.length; + const symbolNameLength = symbolName.length; + + let position = text.indexOf(symbolName, container.pos); + while (position >= 0) { + // If we are past the end, stop looking + if (position > container.end) break; + + // We found a match. Make sure it's not part of a larger word (i.e. the char + // before and after it have to be a non-identifier char). + const endPosition = position + symbolNameLength; + + if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && + (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { + // Found a real match. Keep searching. + positions.push(position); + } + position = text.indexOf(symbolName, position + symbolNameLength + 1); + } + + return positions as readonly number[] as SortedReadonlyArray; + } + + export function findNodesAtPositions(container: Node, positions: SortedReadonlyArray, sourceFile = getSourceFileOfNode(container)) { + let i = 0; + const results: Node[] = []; + visit(container); + return results; + function visit(node: Node) { + const startPos = skipTrivia(sourceFile.text, node.pos); + while (i < positions.length) { + const pos = positions[i]; + const startOffset = i; + if (pos >= node.pos && pos < node.end) { + if (pos < startPos) { + // The position exists in the node's trivia, so we should skip it and + // move on to the next position + i++; + } + else { + const length = results.length; + forEachChild(node, visit); + if (length === results.length) { + // no children were added, so add this node + results.push(node); + // advance to the next position + i++; + } + } + } + else { + // If we've advanced past the end of our parent we should break out of + // the containing `forEachChild`. Otherwise, the position is not contained + // within this node so we should skip to the next node + return !!node.parent && pos > node.parent.end; + } + Debug.assert(i !== startOffset, "Position did not advance"); + } + } + } } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 158b16d00c7f1..3f639b41a630a 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1261,41 +1261,6 @@ namespace ts.FindAllReferences { return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); } - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { - const positions: number[] = []; - - /// TODO: Cache symbol existence for files to save text search - // Also, need to make this work for unicode escapes. - - // Be resilient in the face of a symbol with no name or zero length name - if (!symbolName || !symbolName.length) { - return positions; - } - - const text = sourceFile.text; - const sourceLength = text.length; - const symbolNameLength = symbolName.length; - - let position = text.indexOf(symbolName, container.pos); - while (position >= 0) { - // If we are past the end, stop looking - if (position > container.end) break; - - // We found a match. Make sure it's not part of a larger word (i.e. the char - // before and after it have to be a non-identifier char). - const endPosition = position + symbolNameLength; - - if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && - (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { - // Found a real match. Keep searching. - positions.push(position); - } - position = text.indexOf(symbolName, position + symbolNameLength + 1); - } - - return positions; - } - function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 389e66c9a336a..02ec899779b6a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2651,9 +2651,10 @@ declare namespace ts { ReturnType = 64, LiteralKeyof = 128, NoConstraints = 256, - AlwaysStrict = 512, - MaxValue = 1024, - PriorityImpliesCombination = 208, + RevealingConstructor = 512, + AlwaysStrict = 1024, + MaxValue = 2048, + PriorityImpliesCombination = 720, Circularity = -1 } /** @deprecated Use FileExtensionInfo instead. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7b1a38a102b7c..58df44ef888b3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2651,9 +2651,10 @@ declare namespace ts { ReturnType = 64, LiteralKeyof = 128, NoConstraints = 256, - AlwaysStrict = 512, - MaxValue = 1024, - PriorityImpliesCombination = 208, + RevealingConstructor = 512, + AlwaysStrict = 1024, + MaxValue = 2048, + PriorityImpliesCombination = 720, Circularity = -1 } /** @deprecated Use FileExtensionInfo instead. */ diff --git a/tests/baselines/reference/defaultExportInAwaitExpression01.types b/tests/baselines/reference/defaultExportInAwaitExpression01.types index 81f8d4bdc5931..a68e9b88fa269 100644 --- a/tests/baselines/reference/defaultExportInAwaitExpression01.types +++ b/tests/baselines/reference/defaultExportInAwaitExpression01.types @@ -1,21 +1,21 @@ === tests/cases/conformance/es6/modules/a.ts === const x = new Promise( ( resolve, reject ) => { resolve( {} ); } ); ->x : Promise ->new Promise( ( resolve, reject ) => { resolve( {} ); } ) : Promise +>x : Promise<{}> +>new Promise( ( resolve, reject ) => { resolve( {} ); } ) : Promise<{}> >Promise : PromiseConstructor ->( resolve, reject ) => { resolve( {} ); } : (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void ->resolve : (value: unknown) => void +>( resolve, reject ) => { resolve( {} ); } : (resolve: (value: {} | PromiseLike<{}>) => void, reject: (reason?: any) => void) => void +>resolve : (value: {} | PromiseLike<{}>) => void >reject : (reason?: any) => void >resolve( {} ) : void ->resolve : (value: unknown) => void +>resolve : (value: {} | PromiseLike<{}>) => void >{} : {} export default x; ->x : Promise +>x : Promise<{}> === tests/cases/conformance/es6/modules/b.ts === import x from './a'; ->x : Promise +>x : Promise<{}> ( async function() { >( async function() { const value = await x;}() ) : Promise @@ -23,9 +23,9 @@ import x from './a'; >async function() { const value = await x;} : () => Promise const value = await x; ->value : unknown ->await x : unknown ->x : Promise +>value : {} +>await x : {} +>x : Promise<{}> }() ); diff --git a/tests/baselines/reference/defaultExportInAwaitExpression02.types b/tests/baselines/reference/defaultExportInAwaitExpression02.types index 81f8d4bdc5931..a68e9b88fa269 100644 --- a/tests/baselines/reference/defaultExportInAwaitExpression02.types +++ b/tests/baselines/reference/defaultExportInAwaitExpression02.types @@ -1,21 +1,21 @@ === tests/cases/conformance/es6/modules/a.ts === const x = new Promise( ( resolve, reject ) => { resolve( {} ); } ); ->x : Promise ->new Promise( ( resolve, reject ) => { resolve( {} ); } ) : Promise +>x : Promise<{}> +>new Promise( ( resolve, reject ) => { resolve( {} ); } ) : Promise<{}> >Promise : PromiseConstructor ->( resolve, reject ) => { resolve( {} ); } : (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void ->resolve : (value: unknown) => void +>( resolve, reject ) => { resolve( {} ); } : (resolve: (value: {} | PromiseLike<{}>) => void, reject: (reason?: any) => void) => void +>resolve : (value: {} | PromiseLike<{}>) => void >reject : (reason?: any) => void >resolve( {} ) : void ->resolve : (value: unknown) => void +>resolve : (value: {} | PromiseLike<{}>) => void >{} : {} export default x; ->x : Promise +>x : Promise<{}> === tests/cases/conformance/es6/modules/b.ts === import x from './a'; ->x : Promise +>x : Promise<{}> ( async function() { >( async function() { const value = await x;}() ) : Promise @@ -23,9 +23,9 @@ import x from './a'; >async function() { const value = await x;} : () => Promise const value = await x; ->value : unknown ->await x : unknown ->x : Promise +>value : {} +>await x : {} +>x : Promise<{}> }() ); diff --git a/tests/baselines/reference/inferenceLimit.types b/tests/baselines/reference/inferenceLimit.types index 48aa9aeec0236..f7f44ec72488a 100644 --- a/tests/baselines/reference/inferenceLimit.types +++ b/tests/baselines/reference/inferenceLimit.types @@ -29,15 +29,15 @@ export class BrokenClass { >[] : undefined[] let populateItems = (order) => { ->populateItems : (order: any) => Promise ->(order) => { return new Promise((resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); }); } : (order: any) => Promise +>populateItems : (order: any) => Promise +>(order) => { return new Promise((resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); }); } : (order: any) => Promise >order : any return new Promise((resolve, reject) => { ->new Promise((resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); }) : Promise +>new Promise((resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); }) : Promise >Promise : PromiseConstructor ->(resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); } : (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void ->resolve : (value: unknown) => void +>(resolve, reject) => { this.doStuff(order.id) .then((items) => { order.items = items; resolve(order); }); } : (resolve: (value: any) => void, reject: (reason?: any) => void) => void +>resolve : (value: any) => void >reject : (reason?: any) => void this.doStuff(order.id) @@ -65,7 +65,7 @@ export class BrokenClass { resolve(order); >resolve(order) : void ->resolve : (value: unknown) => void +>resolve : (value: any) => void >order : any }); @@ -74,19 +74,19 @@ export class BrokenClass { return Promise.all(result.map(populateItems)) >Promise.all(result.map(populateItems)) .then((orders: Array) => { resolve(orders); }) : Promise ->Promise.all(result.map(populateItems)) .then : (onfulfilled?: (value: unknown[]) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise ->Promise.all(result.map(populateItems)) : Promise +>Promise.all(result.map(populateItems)) .then : (onfulfilled?: (value: any[]) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>Promise.all(result.map(populateItems)) : Promise >Promise.all : { (values: Iterable>): Promise; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike]): Promise<[T1, T2, T3, T4, T5]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike]): Promise<[T1, T2, T3, T4]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise<[T1, T2, T3]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>; (values: readonly (T | PromiseLike)[]): Promise; } >Promise : PromiseConstructor >all : { (values: Iterable>): Promise; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike]): Promise<[T1, T2, T3, T4, T5]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike]): Promise<[T1, T2, T3, T4]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise<[T1, T2, T3]>; (values: readonly [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>; (values: readonly (T | PromiseLike)[]): Promise; } ->result.map(populateItems) : Promise[] +>result.map(populateItems) : Promise[] >result.map : (callbackfn: (value: MyModule.MyModel, index: number, array: MyModule.MyModel[]) => U, thisArg?: any) => U[] >result : MyModule.MyModel[] >map : (callbackfn: (value: MyModule.MyModel, index: number, array: MyModule.MyModel[]) => U, thisArg?: any) => U[] ->populateItems : (order: any) => Promise +>populateItems : (order: any) => Promise .then((orders: Array) => { ->then : (onfulfilled?: (value: unknown[]) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>then : (onfulfilled?: (value: any[]) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise >(orders: Array) => { resolve(orders); } : (orders: Array) => void >orders : MyModule.MyModel[] >MyModule : any diff --git a/tests/baselines/reference/revealingConstructorInference.symbols b/tests/baselines/reference/revealingConstructorInference.symbols new file mode 100644 index 0000000000000..b14ab54ed8e2f --- /dev/null +++ b/tests/baselines/reference/revealingConstructorInference.symbols @@ -0,0 +1,110 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts === +// uncalled +const p0 = new Promise(resolve => {}); +>p0 : Symbol(p0, Decl(revealingConstructorInference.ts, 1, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 1, 23)) + +// called with no argument +const p1 = new Promise(resolve => resolve()); +>p1 : Symbol(p1, Decl(revealingConstructorInference.ts, 4, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 4, 23)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 4, 23)) + +// called with argument +const p2 = new Promise(resolve => resolve(1)); +>p2 : Symbol(p2, Decl(revealingConstructorInference.ts, 7, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 7, 23)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 7, 23)) + +// called with promise-like argument +const p3 = new Promise(resolve => resolve(Promise.resolve(1))); +>p3 : Symbol(p3, Decl(revealingConstructorInference.ts, 10, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 10, 23)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 10, 23)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) + +// called with multiple arguments +const p4 = new Promise(resolve => { +>p4 : Symbol(p4, Decl(revealingConstructorInference.ts, 13, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 13, 23)) + + resolve(1); +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 13, 23)) + + resolve("a"); +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 13, 23)) + +}); + +// called with multiple arguments (mix of non-promise and PromiseLike) +const p5 = new Promise(resolve => { +>p5 : Symbol(p5, Decl(revealingConstructorInference.ts, 19, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 19, 23)) + + resolve(1); +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 19, 23)) + + resolve(Promise.resolve("a")); +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 19, 23)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) + +}); + +// called with argument in nested callback +declare function soon(f: () => void): void; +>soon : Symbol(soon, Decl(revealingConstructorInference.ts, 22, 3)) +>f : Symbol(f, Decl(revealingConstructorInference.ts, 25, 22)) + +const p6 = new Promise(resolve => { +>p6 : Symbol(p6, Decl(revealingConstructorInference.ts, 26, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 26, 23)) + + soon(() => resolve(1)); +>soon : Symbol(soon, Decl(revealingConstructorInference.ts, 22, 3)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 26, 23)) + +}); + +// callback passed to another function +declare function resolveWith(f: (value: T) => void, value: T): void; +>resolveWith : Symbol(resolveWith, Decl(revealingConstructorInference.ts, 28, 3)) +>T : Symbol(T, Decl(revealingConstructorInference.ts, 31, 29)) +>f : Symbol(f, Decl(revealingConstructorInference.ts, 31, 32)) +>value : Symbol(value, Decl(revealingConstructorInference.ts, 31, 36)) +>T : Symbol(T, Decl(revealingConstructorInference.ts, 31, 29)) +>value : Symbol(value, Decl(revealingConstructorInference.ts, 31, 54)) +>T : Symbol(T, Decl(revealingConstructorInference.ts, 31, 29)) + +const p7 = new Promise(resolve => resolveWith(resolve, 1)); +>p7 : Symbol(p7, Decl(revealingConstructorInference.ts, 32, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 32, 23)) +>resolveWith : Symbol(resolveWith, Decl(revealingConstructorInference.ts, 28, 3)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 32, 23)) + +// lower priority inference +const enum E { zero = 0 } +>E : Symbol(E, Decl(revealingConstructorInference.ts, 32, 59)) +>zero : Symbol(E.zero, Decl(revealingConstructorInference.ts, 35, 14)) + +const p8: Promise = new Promise(resolve => resolve(E.zero)); +>p8 : Symbol(p8, Decl(revealingConstructorInference.ts, 36, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 36, 40)) +>resolve : Symbol(resolve, Decl(revealingConstructorInference.ts, 36, 40)) +>E.zero : Symbol(E.zero, Decl(revealingConstructorInference.ts, 35, 14)) +>E : Symbol(E, Decl(revealingConstructorInference.ts, 32, 59)) +>zero : Symbol(E.zero, Decl(revealingConstructorInference.ts, 35, 14)) + diff --git a/tests/baselines/reference/revealingConstructorInference.types b/tests/baselines/reference/revealingConstructorInference.types new file mode 100644 index 0000000000000..fe48c897321d0 --- /dev/null +++ b/tests/baselines/reference/revealingConstructorInference.types @@ -0,0 +1,147 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts === +// uncalled +const p0 = new Promise(resolve => {}); +>p0 : Promise +>new Promise(resolve => {}) : Promise +>Promise : PromiseConstructor +>resolve => {} : (resolve: (value: unknown) => void) => void +>resolve : (value: unknown) => void + +// called with no argument +const p1 = new Promise(resolve => resolve()); +>p1 : Promise +>new Promise(resolve => resolve()) : Promise +>Promise : PromiseConstructor +>resolve => resolve() : (resolve: (value: void | PromiseLike) => void) => void +>resolve : (value: void | PromiseLike) => void +>resolve() : void +>resolve : (value: void | PromiseLike) => void + +// called with argument +const p2 = new Promise(resolve => resolve(1)); +>p2 : Promise +>new Promise(resolve => resolve(1)) : Promise +>Promise : PromiseConstructor +>resolve => resolve(1) : (resolve: (value: number | PromiseLike) => void) => void +>resolve : (value: number | PromiseLike) => void +>resolve(1) : void +>resolve : (value: number | PromiseLike) => void +>1 : 1 + +// called with promise-like argument +const p3 = new Promise(resolve => resolve(Promise.resolve(1))); +>p3 : Promise +>new Promise(resolve => resolve(Promise.resolve(1))) : Promise +>Promise : PromiseConstructor +>resolve => resolve(Promise.resolve(1)) : (resolve: (value: number | PromiseLike) => void) => any +>resolve : (value: number | PromiseLike) => void +>resolve(Promise.resolve(1)) : void +>resolve : (value: number | PromiseLike) => void +>Promise.resolve(1) : Promise +>Promise.resolve : { (): Promise; (value: T | PromiseLike): Promise; } +>Promise : PromiseConstructor +>resolve : { (): Promise; (value: T | PromiseLike): Promise; } +>1 : 1 + +// called with multiple arguments +const p4 = new Promise(resolve => { +>p4 : Promise +>new Promise(resolve => { resolve(1); resolve("a");}) : Promise +>Promise : PromiseConstructor +>resolve => { resolve(1); resolve("a");} : (resolve: (value: string | number | PromiseLike) => void) => void +>resolve : (value: string | number | PromiseLike) => void + + resolve(1); +>resolve(1) : void +>resolve : (value: string | number | PromiseLike) => void +>1 : 1 + + resolve("a"); +>resolve("a") : void +>resolve : (value: string | number | PromiseLike) => void +>"a" : "a" + +}); + +// called with multiple arguments (mix of non-promise and PromiseLike) +const p5 = new Promise(resolve => { +>p5 : Promise +>new Promise(resolve => { resolve(1); resolve(Promise.resolve("a"));}) : Promise +>Promise : PromiseConstructor +>resolve => { resolve(1); resolve(Promise.resolve("a"));} : (resolve: (value: string | number | PromiseLike) => void) => void +>resolve : (value: string | number | PromiseLike) => void + + resolve(1); +>resolve(1) : void +>resolve : (value: string | number | PromiseLike) => void +>1 : 1 + + resolve(Promise.resolve("a")); +>resolve(Promise.resolve("a")) : void +>resolve : (value: string | number | PromiseLike) => void +>Promise.resolve("a") : Promise +>Promise.resolve : { (): Promise; (value: T | PromiseLike): Promise; } +>Promise : PromiseConstructor +>resolve : { (): Promise; (value: T | PromiseLike): Promise; } +>"a" : "a" + +}); + +// called with argument in nested callback +declare function soon(f: () => void): void; +>soon : (f: () => void) => void +>f : () => void + +const p6 = new Promise(resolve => { +>p6 : Promise +>new Promise(resolve => { soon(() => resolve(1));}) : Promise +>Promise : PromiseConstructor +>resolve => { soon(() => resolve(1));} : (resolve: (value: number | PromiseLike) => void) => void +>resolve : (value: number | PromiseLike) => void + + soon(() => resolve(1)); +>soon(() => resolve(1)) : void +>soon : (f: () => void) => void +>() => resolve(1) : () => void +>resolve(1) : void +>resolve : (value: number | PromiseLike) => void +>1 : 1 + +}); + +// callback passed to another function +declare function resolveWith(f: (value: T) => void, value: T): void; +>resolveWith : (f: (value: T) => void, value: T) => void +>f : (value: T) => void +>value : T +>value : T + +const p7 = new Promise(resolve => resolveWith(resolve, 1)); +>p7 : Promise +>new Promise(resolve => resolveWith(resolve, 1)) : Promise +>Promise : PromiseConstructor +>resolve => resolveWith(resolve, 1) : (resolve: (value: number | PromiseLike) => void) => void +>resolve : (value: number | PromiseLike) => void +>resolveWith(resolve, 1) : void +>resolveWith : (f: (value: T) => void, value: T) => void +>resolve : (value: number | PromiseLike) => void +>1 : 1 + +// lower priority inference +const enum E { zero = 0 } +>E : E +>zero : E.zero +>0 : 0 + +const p8: Promise = new Promise(resolve => resolve(E.zero)); +>p8 : Promise +>new Promise(resolve => resolve(E.zero)) : Promise +>Promise : PromiseConstructor +>resolve => resolve(E.zero) : (resolve: (value: number | PromiseLike) => void) => void +>resolve : (value: number | PromiseLike) => void +>resolve(E.zero) : void +>resolve : (value: number | PromiseLike) => void +>E.zero : E +>E : typeof E +>zero : E + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts new file mode 100644 index 0000000000000..192a68ae7baa9 --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/revealingConstructorInference.ts @@ -0,0 +1,40 @@ +// @target: esnext +// @noEmit: true + +// uncalled +const p0 = new Promise(resolve => {}); + +// called with no argument +const p1 = new Promise(resolve => resolve()); + +// called with argument +const p2 = new Promise(resolve => resolve(1)); + +// called with promise-like argument +const p3 = new Promise(resolve => resolve(Promise.resolve(1))); + +// called with multiple arguments +const p4 = new Promise(resolve => { + resolve(1); + resolve("a"); +}); + +// called with multiple arguments (mix of non-promise and PromiseLike) +const p5 = new Promise(resolve => { + resolve(1); + resolve(Promise.resolve("a")); +}); + +// called with argument in nested callback +declare function soon(f: () => void): void; +const p6 = new Promise(resolve => { + soon(() => resolve(1)); +}); + +// callback passed to another function +declare function resolveWith(f: (value: T) => void, value: T): void; +const p7 = new Promise(resolve => resolveWith(resolve, 1)); + +// lower priority inference +const enum E { zero = 0 } +const p8: Promise = new Promise(resolve => resolve(E.zero)); \ No newline at end of file From f70a37958f23b3039fc72716489e04d983bcb842 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 9 Sep 2020 18:56:21 -0700 Subject: [PATCH 2/2] Minor comment cleanup --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 388255d87ee4d..ca3c62fc0a02e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26039,7 +26039,7 @@ namespace ts { // If the above conditions are met then: // - Determine the name in function `callbackFunc` given to the parameter `innerCallbackParam` // - Find all references to that name in the body of the function `callbackFunc` - // - If `innerCallbackParam` is called directly, collect inferences for the type of the argument passed to the parameter (`innerCallbackValueType`) each call to `innerCallbackParam` + // - If `innerCallbackParam` is called directly, collect inferences for the type of the argument passed to the parameter (`innerCallbackValueType`) // - If `innerCallbackParam` is passed as the argument to another function, we can attempt to use the contextual type of that parameter for inference. if (isNewExpression(node) && argCount === 1) { const callbackType = getTypeAtPosition(signature, 0); // executor: ...