diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..0a4be9a95c333 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -965,6 +965,7 @@ import { SignatureFlags, SignatureKind, singleElementArray, + SingleSignatureType, skipOuterExpressions, skipParentheses, skipTrivia, @@ -7088,7 +7089,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); if (some(abstractSignatures)) { - const types = map(abstractSignatures, getOrCreateTypeFromSignature); + const types = map(abstractSignatures, s => getOrCreateTypeFromSignature(s)); // count the number of type elements excluding abstract constructors const typeElementCount = resolved.callSignatures.length + (resolved.constructSignatures.length - abstractSignatures.length) + @@ -15567,7 +15568,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } - function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { + function getSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); if (inferredTypeParameters) { const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); @@ -15631,6 +15632,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } + function getImplementationSignature(signature: Signature) { + return signature.typeParameters ? + signature.implementationSignatureCache ||= createImplementationSignature(signature) : + signature; + } + + function createImplementationSignature(signature: Signature) { + return signature.typeParameters ? instantiateSignature(signature, createTypeMapper([], [])) : signature; + } + function getBaseSignature(signature: Signature) { const typeParameters = signature.typeParameters; if (typeParameters) { @@ -15652,7 +15663,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return signature; } - function getOrCreateTypeFromSignature(signature: Signature): ObjectType { + function getOrCreateTypeFromSignature(signature: Signature, outerTypeParameters?: TypeParameter[]): ObjectType { // There are two ways to declare a construct signature, one is by declaring a class constructor // using the constructor keyword, and the other is declaring a bare construct signature in an // object type literal or interface (using the new keyword). Each way of declaring a constructor @@ -15663,7 +15674,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If declaration is undefined, it is likely to be the signature of the default constructor. const isConstructor = kind === undefined || kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; - const type = createObjectType(ObjectFlags.Anonymous); + // The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing + // type variables by `couldContainTypeVariables` + const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType; + if (signature.declaration && !nodeIsSynthesized(signature.declaration)) { // skip synthetic declarations - keeping those around could be bad, since they lack a parent pointer + type.symbol.declarations = [signature.declaration]; + type.symbol.valueDeclaration = signature.declaration; + } + outerTypeParameters ||= signature.declaration && getOuterTypeParameters(signature.declaration, /*includeThisTypes*/ true); + type.outerTypeParameters = outerTypeParameters; + type.members = emptySymbols; type.properties = emptyArray; type.callSignatures = !isConstructor ? [signature] : emptyArray; @@ -19621,7 +19641,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const links = getNodeLinks(declaration); const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; - let typeParameters = links.outerTypeParameters; + let typeParameters = type.objectFlags & ObjectFlags.SingleSignatureType ? (type as SingleSignatureType).outerTypeParameters : links.outerTypeParameters; if (!typeParameters) { // The first time an anonymous type is instantiated we compute and store a list of the type // parameters that are in scope (and therefore potentially referenced). For type literals that @@ -19851,6 +19871,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (type.objectFlags & ObjectFlags.InstantiationExpressionType) { (result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node; } + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + (result as SingleSignatureType).outerTypeParameters = (type as SingleSignatureType).outerTypeParameters; + } result.target = type; result.mapper = mapper; result.aliasSymbol = aliasSymbol || type.aliasSymbol; @@ -25117,6 +25140,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.SingleSignatureType && !!length((type as SingleSignatureType).outerTypeParameters) || objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType) ) || @@ -25474,6 +25498,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + /** + * @returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter` + */ + function isTupleOfSelf(typeParameter: TypeParameter, type: Type) { + return isTupleType(type) && getTupleElementType(type, 0) === getIndexedAccessType(typeParameter, getNumberLiteralType(0)) && !getTypeOfPropertyOfType(type, "1" as __String); + } + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) { let bivariant = false; let propagationType: Type; @@ -25602,6 +25633,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { inference.priority = priority; } if (priority === inference.priority) { + // Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences + // By discarding it early, we can allow more fruitful results to be used instead. + if (isTupleOfSelf(inference.typeParameter, candidate)) { + return; + } // We make contravariant inferences only if we are in a pure contravariant position, // i.e. only if we have not descended into a bivariant position. if (contravariant && !bivariant) { @@ -34222,6 +34258,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkMode: CheckMode, reportErrors: boolean, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + inferenceContext: InferenceContext | undefined, ): readonly Diagnostic[] | undefined { const errorOutputContainer: { errors?: Diagnostic[]; skipLogging?: boolean; } = { errors: undefined, skipLogging: true }; if (isJsxOpeningLikeElement(node)) { @@ -34256,7 +34293,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), // we obtain the regular type of any object literal arguments because we may not have inferred complete // parameter types yet and therefore excess property checks may yield false positives (see #17041). - const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + const regularArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + // If this was inferred under a given inference context, we may need to instantiate the expression type to finish resolving + // the type variables in the expression. + const checkArgType = inferenceContext ? instantiateType(regularArgType, inferenceContext.nonFixingMapper) : regularArgType; const effectiveCheckArgumentNode = getEffectiveCheckNode(arg); if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, headMessage, containingMessageChain, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); @@ -34720,7 +34760,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (headMessage) { chain = chainDiagnosticMessages(chain, headMessage); } - const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain, /*inferenceContext*/ undefined); if (diags) { for (const d of diags) { if (last.declaration && candidatesForArgumentError.length > 3) { @@ -34742,7 +34782,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let i = 0; for (const c of candidatesForArgumentError) { const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); - const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); + const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain, /*inferenceContext*/ undefined); if (diags) { if (diags.length <= min) { min = diags.length; @@ -34831,7 +34871,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { return undefined; } - if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined, /*inferenceContext*/ undefined)) { candidatesForArgumentError = [candidate]; return undefined; } @@ -34839,7 +34879,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { - const candidate = candidates[candidateIndex]; + let candidate = candidates[candidateIndex]; if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { continue; } @@ -34848,7 +34888,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let inferenceContext: InferenceContext | undefined; if (candidate.typeParameters) { - let typeArgumentTypes: Type[] | undefined; + // If we are *inside the body of candidate*, we need to create a clone of `candidate` with differing type parameter identities, + // so our inference results for this call doesn't pollute expression types referencing the outer type parameter! + if (candidate.declaration && findAncestor(node, a => a === candidate.declaration)) { + candidate = getImplementationSignature(candidate); + } + let typeArgumentTypes: readonly Type[] | undefined; if (some(typeArguments)) { typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); if (!typeArgumentTypes) { @@ -34857,8 +34902,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); + inferenceContext = createInferenceContext(candidate.typeParameters!, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + // The resulting type arguments are instantiated with the inference context mapper, as the inferred types may still contain references to the inference context's + // type variables via contextual projection. These are kept generic until all inferences are locked in, so the dependencies expressed can pass constraint checks. + typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext), inferenceContext.nonFixingMapper); argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; } checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); @@ -34872,7 +34919,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else { checkCandidate = candidate; } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined, inferenceContext)) { // Give preference to error candidates that have no rest parameters (as they are more specific) (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); continue; @@ -34883,7 +34930,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // round of type inference and applicability checking for this particular candidate. argCheckMode = CheckMode.Normal; if (inferenceContext) { - const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); + const typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext), inferenceContext.mapper); checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters); // If the original signature has a generic rest type, instantiation may produce a // signature with different arity and we need to perform another arity check. @@ -34892,7 +34939,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined, inferenceContext)) { // Give preference to error candidates that have no rest parameters (as they are more specific) (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); continue; @@ -39287,7 +39334,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); + // TODO: The signature may reference any outer inference contexts, but we map pop off and then apply new inference contexts, and thus get different inferred types. + // That this is cached on the *first* such attempt is not currently an issue, since expression types *also* get cached on the first pass. If we ever properly speculate, though, + // the cached "isolatedSignatureType" signature field absolutely needs to be included in the list of speculative caches. + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context), flatMap(inferenceContexts, c => c && map(c.inferences, i => i.typeParameter)).slice()); } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28061c88e94bb..2c09a3e455b33 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6317,6 +6317,7 @@ export const enum ObjectFlags { ContainsSpread = 1 << 21, // Object literal contains spread operation ObjectRestType = 1 << 22, // Originates in object rest declaration InstantiationExpressionType = 1 << 23, // Originates in instantiation expression + SingleSignatureType = 1 << 27, // A single signature type extracted from a potentially broader type /** @internal */ IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type // Flags that require TypeFlags.Object and ObjectFlags.Reference @@ -6527,6 +6528,12 @@ export interface AnonymousType extends ObjectType { instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) } +/** @internal */ +// A SingleSignatureType may have bespoke outer type parameters to handle free type variable inferences +export interface SingleSignatureType extends AnonymousType { + outerTypeParameters?: TypeParameter[]; +} + /** @internal */ export interface InstantiationExpressionType extends AnonymousType { node: NodeWithTypeArguments; @@ -6812,6 +6819,8 @@ export interface Signature { isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /** @internal */ instantiations?: Map; // Generic signature instantiation cache + /** @internal */ + implementationSignatureCache?: Signature; // Copy of the signature with fresh type parameters to use in checking the body of a potentially self-referential generic function (deferred) } export const enum IndexKind { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bf2b10738827f..2c9642e47eb20 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -7242,6 +7242,7 @@ declare namespace ts { ContainsSpread = 2097152, ObjectRestType = 4194304, InstantiationExpressionType = 8388608, + SingleSignatureType = 134217728, } interface ObjectType extends Type { objectFlags: ObjectFlags; diff --git a/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.js b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.js new file mode 100644 index 0000000000000..9c1884e53c471 --- /dev/null +++ b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts] //// + +//// [genericCallWithinOwnBodyCastTypeParameterIdentity.ts] +interface Thenable { + then( + onFulfilled: (value: Value) => V | Thenable, + ): Thenable; +} + +const toThenable = (fn: (input: Input) => Result | Thenable) => + (input: Input): Thenable => { + const result = fn(input) + return { + then(onFulfilled: (value: Result) => V | Thenable) { + return toThenable(onFulfilled)(result as Result) + } + }; + } + +const toThenableInferred = (fn: (input: Input) => Result | Thenable) => + (input: Input): Thenable => { + const result = fn(input) + return { + then(onFulfilled) { + return toThenableInferred(onFulfilled)(result as Result) + } + }; + } + + +//// [genericCallWithinOwnBodyCastTypeParameterIdentity.js] +"use strict"; +var toThenable = function (fn) { + return function (input) { + var result = fn(input); + return { + then: function (onFulfilled) { + return toThenable(onFulfilled)(result); + } + }; + }; +}; +var toThenableInferred = function (fn) { + return function (input) { + var result = fn(input); + return { + then: function (onFulfilled) { + return toThenableInferred(onFulfilled)(result); + } + }; + }; +}; diff --git a/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.symbols b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.symbols new file mode 100644 index 0000000000000..7cbd117b09ffb --- /dev/null +++ b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.symbols @@ -0,0 +1,104 @@ +//// [tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts] //// + +=== genericCallWithinOwnBodyCastTypeParameterIdentity.ts === +interface Thenable { +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>Value : Symbol(Value, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 19)) + + then( +>then : Symbol(Thenable.then, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 27)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 1, 9)) + + onFulfilled: (value: Value) => V | Thenable, +>onFulfilled : Symbol(onFulfilled, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 1, 12)) +>value : Symbol(value, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 2, 22)) +>Value : Symbol(Value, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 19)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 1, 9)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 1, 9)) + + ): Thenable; +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 1, 9)) +} + +const toThenable = (fn: (input: Input) => Result | Thenable) => +>toThenable : Symbol(toThenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 5)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 27)) +>fn : Symbol(fn, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 35)) +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 40)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 27)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) + + (input: Input): Thenable => { +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 7, 5)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 27)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) + + const result = fn(input) +>result : Symbol(result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 8, 13)) +>fn : Symbol(fn, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 35)) +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 7, 5)) + + return { + then(onFulfilled: (value: Result) => V | Thenable) { +>then : Symbol(then, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 9, 16)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 17)) +>onFulfilled : Symbol(onFulfilled, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 20)) +>value : Symbol(value, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 34)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 17)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 17)) + + return toThenable(onFulfilled)(result as Result) +>toThenable : Symbol(toThenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 5)) +>V : Symbol(V, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 17)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) +>onFulfilled : Symbol(onFulfilled, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 10, 20)) +>result : Symbol(result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 8, 13)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 6, 20)) + } + }; + } + +const toThenableInferred = (fn: (input: Input) => Result | Thenable) => +>toThenableInferred : Symbol(toThenableInferred, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 5)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 28)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 35)) +>fn : Symbol(fn, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 43)) +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 48)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 35)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 28)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 28)) + + (input: Input): Thenable => { +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 17, 5)) +>Input : Symbol(Input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 35)) +>Thenable : Symbol(Thenable, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 0, 0)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 28)) + + const result = fn(input) +>result : Symbol(result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 18, 13)) +>fn : Symbol(fn, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 43)) +>input : Symbol(input, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 17, 5)) + + return { + then(onFulfilled) { +>then : Symbol(then, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 19, 16)) +>onFulfilled : Symbol(onFulfilled, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 20, 17)) + + return toThenableInferred(onFulfilled)(result as Result) +>toThenableInferred : Symbol(toThenableInferred, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 5)) +>onFulfilled : Symbol(onFulfilled, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 20, 17)) +>result : Symbol(result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 18, 13)) +>Result : Symbol(Result, Decl(genericCallWithinOwnBodyCastTypeParameterIdentity.ts, 16, 28)) + } + }; + } + diff --git a/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.types b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.types new file mode 100644 index 0000000000000..390177662069d --- /dev/null +++ b/tests/baselines/reference/genericCallWithinOwnBodyCastTypeParameterIdentity.types @@ -0,0 +1,83 @@ +//// [tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts] //// + +=== genericCallWithinOwnBodyCastTypeParameterIdentity.ts === +interface Thenable { + then( +>then : (onFulfilled: (value: Value) => V | Thenable) => Thenable + + onFulfilled: (value: Value) => V | Thenable, +>onFulfilled : (value: Value) => V | Thenable +>value : Value + + ): Thenable; +} + +const toThenable = (fn: (input: Input) => Result | Thenable) => +>toThenable : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>(fn: (input: Input) => Result | Thenable) => (input: Input): Thenable => { const result = fn(input) return { then(onFulfilled: (value: Result) => V | Thenable) { return toThenable(onFulfilled)(result as Result) } }; } : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>fn : (input: Input) => Result | Thenable +>input : Input + + (input: Input): Thenable => { +>(input: Input): Thenable => { const result = fn(input) return { then(onFulfilled: (value: Result) => V | Thenable) { return toThenable(onFulfilled)(result as Result) } }; } : (input: Input) => Thenable +>input : Input + + const result = fn(input) +>result : Result | Thenable +>fn(input) : Result | Thenable +>fn : (input: Input) => Result | Thenable +>input : Input + + return { +>{ then(onFulfilled: (value: Result) => V | Thenable) { return toThenable(onFulfilled)(result as Result) } } : { then(onFulfilled: (value: Result) => V | Thenable): Thenable; } + + then(onFulfilled: (value: Result) => V | Thenable) { +>then : (onFulfilled: (value: Result) => V | Thenable) => Thenable +>onFulfilled : (value: Result) => V | Thenable +>value : Result + + return toThenable(onFulfilled)(result as Result) +>toThenable(onFulfilled)(result as Result) : Thenable +>toThenable(onFulfilled) : (input: Result) => Thenable +>toThenable : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>onFulfilled : (value: Result) => V | Thenable +>result as Result : Result +>result : Result | Thenable + } + }; + } + +const toThenableInferred = (fn: (input: Input) => Result | Thenable) => +>toThenableInferred : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>(fn: (input: Input) => Result | Thenable) => (input: Input): Thenable => { const result = fn(input) return { then(onFulfilled) { return toThenableInferred(onFulfilled)(result as Result) } }; } : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>fn : (input: Input) => Result | Thenable +>input : Input + + (input: Input): Thenable => { +>(input: Input): Thenable => { const result = fn(input) return { then(onFulfilled) { return toThenableInferred(onFulfilled)(result as Result) } }; } : (input: Input) => Thenable +>input : Input + + const result = fn(input) +>result : Result | Thenable +>fn(input) : Result | Thenable +>fn : (input: Input) => Result | Thenable +>input : Input + + return { +>{ then(onFulfilled) { return toThenableInferred(onFulfilled)(result as Result) } } : { then(onFulfilled: (value: Result) => V | Thenable): Thenable; } + + then(onFulfilled) { +>then : (onFulfilled: (value: Result) => V | Thenable) => Thenable +>onFulfilled : (value: Result) => V | Thenable + + return toThenableInferred(onFulfilled)(result as Result) +>toThenableInferred(onFulfilled)(result as Result) : Thenable +>toThenableInferred(onFulfilled) : (input: Result) => Thenable +>toThenableInferred : (fn: (input: Input) => Result | Thenable) => (input: Input) => Thenable +>onFulfilled : (value: Result) => V | Thenable +>result as Result : Result +>result : Result | Thenable + } + }; + } + diff --git a/tests/baselines/reference/nestedGenericSpreadInference.js b/tests/baselines/reference/nestedGenericSpreadInference.js new file mode 100644 index 0000000000000..00bb4a558c2d4 --- /dev/null +++ b/tests/baselines/reference/nestedGenericSpreadInference.js @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/nestedGenericSpreadInference.ts] //// + +//// [nestedGenericSpreadInference.ts] +declare function wrap(x: X): { x: X }; +declare function call(x: { x: (...args: A) => T }, ...args: A): T; + +// This should be of type `number` - ideally, it also would not error. +const leak = call(wrap((x: T) => x), 1); + + +//// [nestedGenericSpreadInference.js] +"use strict"; +// This should be of type `number` - ideally, it also would not error. +var leak = call(wrap(function (x) { return x; }), 1); diff --git a/tests/baselines/reference/nestedGenericSpreadInference.symbols b/tests/baselines/reference/nestedGenericSpreadInference.symbols new file mode 100644 index 0000000000000..bef06547b70ec --- /dev/null +++ b/tests/baselines/reference/nestedGenericSpreadInference.symbols @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/nestedGenericSpreadInference.ts] //// + +=== nestedGenericSpreadInference.ts === +declare function wrap(x: X): { x: X }; +>wrap : Symbol(wrap, Decl(nestedGenericSpreadInference.ts, 0, 0)) +>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 0, 25)) +>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 0, 33)) +>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22)) + +declare function call(x: { x: (...args: A) => T }, ...args: A): T; +>call : Symbol(call, Decl(nestedGenericSpreadInference.ts, 0, 41)) +>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22)) +>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 1, 46)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 1, 50)) +>args : Symbol(args, Decl(nestedGenericSpreadInference.ts, 1, 55)) +>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22)) +>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42)) +>args : Symbol(args, Decl(nestedGenericSpreadInference.ts, 1, 74)) +>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22)) +>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42)) + +// This should be of type `number` - ideally, it also would not error. +const leak = call(wrap((x: T) => x), 1); +>leak : Symbol(leak, Decl(nestedGenericSpreadInference.ts, 4, 5)) +>call : Symbol(call, Decl(nestedGenericSpreadInference.ts, 0, 41)) +>wrap : Symbol(wrap, Decl(nestedGenericSpreadInference.ts, 0, 0)) +>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 4, 24)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 4, 27)) +>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 4, 24)) +>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 4, 27)) + diff --git a/tests/baselines/reference/nestedGenericSpreadInference.types b/tests/baselines/reference/nestedGenericSpreadInference.types new file mode 100644 index 0000000000000..7d62f13b35347 --- /dev/null +++ b/tests/baselines/reference/nestedGenericSpreadInference.types @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/nestedGenericSpreadInference.ts] //// + +=== nestedGenericSpreadInference.ts === +declare function wrap(x: X): { x: X }; +>wrap : (x: X) => { x: X;} +>x : X +>x : X + +declare function call(x: { x: (...args: A) => T }, ...args: A): T; +>call : (x: { x: (...args: A) => T; }, ...args: A) => T +>x : { x: (...args: A) => T; } +>x : (...args: A) => T +>args : A +>args : A + +// This should be of type `number` - ideally, it also would not error. +const leak = call(wrap((x: T) => x), 1); +>leak : number +>call(wrap((x: T) => x), 1) : number +>call : (x: { x: (...args: A) => T; }, ...args: A) => T +>wrap((x: T) => x) : { x: (x: A[0]) => A[0]; } +>wrap : (x: X) => { x: X; } +>(x: T) => x : (x: T) => T +>x : T +>x : T +>1 : 1 + diff --git a/tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts b/tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts new file mode 100644 index 0000000000000..97058662ae75d --- /dev/null +++ b/tests/cases/compiler/genericCallWithinOwnBodyCastTypeParameterIdentity.ts @@ -0,0 +1,26 @@ +// @strict: true +interface Thenable { + then( + onFulfilled: (value: Value) => V | Thenable, + ): Thenable; +} + +const toThenable = (fn: (input: Input) => Result | Thenable) => + (input: Input): Thenable => { + const result = fn(input) + return { + then(onFulfilled: (value: Result) => V | Thenable) { + return toThenable(onFulfilled)(result as Result) + } + }; + } + +const toThenableInferred = (fn: (input: Input) => Result | Thenable) => + (input: Input): Thenable => { + const result = fn(input) + return { + then(onFulfilled) { + return toThenableInferred(onFulfilled)(result as Result) + } + }; + } diff --git a/tests/cases/compiler/nestedGenericSpreadInference.ts b/tests/cases/compiler/nestedGenericSpreadInference.ts new file mode 100644 index 0000000000000..14e606f5f5cbf --- /dev/null +++ b/tests/cases/compiler/nestedGenericSpreadInference.ts @@ -0,0 +1,6 @@ +// @strict: true +declare function wrap(x: X): { x: X }; +declare function call(x: { x: (...args: A) => T }, ...args: A): T; + +// This should be of type `number` - ideally, it also would not error. +const leak = call(wrap((x: T) => x), 1);