Skip to content

Commit f2f9df0

Browse files
committed
Move where instantiation happens out a call, clean up edge cases around signature self-references
1 parent 1ea52a4 commit f2f9df0

File tree

3 files changed

+33
-16
lines changed

3 files changed

+33
-16
lines changed

src/compiler/checker.ts

+30-16
Original file line numberDiff line numberDiff line change
@@ -15564,7 +15564,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1556415564
return undefined;
1556515565
}
1556615566

15567-
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature {
15567+
function getSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature {
1556815568
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
1556915569
if (inferredTypeParameters) {
1557015570
const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature));
@@ -15628,6 +15628,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1562815628
);
1562915629
}
1563015630

15631+
function getImplementationSignature(signature: Signature) {
15632+
return signature.typeParameters ?
15633+
signature.implementationSignatureCache ||= createImplementationSignature(signature) :
15634+
signature;
15635+
}
15636+
15637+
function createImplementationSignature(signature: Signature) {
15638+
return signature.typeParameters ? instantiateSignature(signature, createTypeMapper([], [])) : signature;
15639+
}
15640+
1563115641
function getBaseSignature(signature: Signature) {
1563215642
const typeParameters = signature.typeParameters;
1563315643
if (typeParameters) {
@@ -15663,7 +15673,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1566315673
// The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing
1566415674
// type variables by `couldContainTypeVariables`
1566515675
const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType;
15666-
if (signature.declaration) {
15676+
if (signature.declaration && !nodeIsSynthesized(signature.declaration)) { // skip synthetic declarations - keeping those around could be bad, since they lack a parent pointer
1566715677
type.symbol.declarations = [signature.declaration];
1566815678
type.symbol.valueDeclaration = signature.declaration;
1566915679
}
@@ -25139,6 +25149,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2513925149
const result = !!(type.flags & TypeFlags.Instantiable ||
2514025150
type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && (
2514125151
objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) ||
25152+
objectFlags & ObjectFlags.SingleSignatureType && !!length((type as SingleSignatureType).outerTypeParameters) ||
2514225153
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
2514325154
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)
2514425155
) ||
@@ -26372,25 +26383,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2637226383
// inference error only occurs when there are *conflicting* candidates, i.e.
2637326384
// candidates with no common supertype.
2637426385
const defaultType = getDefaultFromTypeParameter(inference.typeParameter);
26375-
inferredType = defaultType;
26386+
// Instantiate the default type. Any forward reference to a type
26387+
// parameter should be instantiated to the empty object type.
26388+
inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
2637626389
}
2637726390
}
2637826391
else {
2637926392
inferredType = getTypeFromInference(inference);
2638026393
}
2638126394

26382-
const isDefault = !inferredType;
26383-
inferredType ||= getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
26384-
inference.inferredType = inferredType;
26385-
// Instantiate the inferred type. Any forward reference to a type
26386-
// parameter should be instantiated to the empty object type.
26387-
inferredType = instantiateType(inferredType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
26388-
inference.inferredType = inferredType;
26395+
inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
2638926396

2639026397
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
2639126398
if (constraint) {
2639226399
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
26393-
if (isDefault || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
26400+
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
2639426401
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
2639526402
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2639626403
}
@@ -34875,7 +34882,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3487534882
}
3487634883

3487734884
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
34878-
const candidate = candidates[candidateIndex];
34885+
let candidate = candidates[candidateIndex];
3487934886
if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
3488034887
continue;
3488134888
}
@@ -34884,7 +34891,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3488434891
let inferenceContext: InferenceContext | undefined;
3488534892

3488634893
if (candidate.typeParameters) {
34887-
let typeArgumentTypes: Type[] | undefined;
34894+
// If we are *inside the body of candidate*, we need to create a clone of `candidate` with differing type parameter identities,
34895+
// so our inference results for this call doesn't pollute expression types referencing the outer type parameter!
34896+
if (candidate.declaration && findAncestor(node, a => a === candidate.declaration)) {
34897+
candidate = getImplementationSignature(candidate);
34898+
}
34899+
let typeArgumentTypes: readonly Type[] | undefined;
3488834900
if (some(typeArguments)) {
3488934901
typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false);
3489034902
if (!typeArgumentTypes) {
@@ -34893,8 +34905,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3489334905
}
3489434906
}
3489534907
else {
34896-
inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
34897-
typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
34908+
inferenceContext = createInferenceContext(candidate.typeParameters!, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
34909+
// The resulting type arguments are instantiated with the inference context mapper, as the inferred types may still contain references to the inference context's
34910+
// type variables via contextual projection. These are kept generic until all inferences are locked in, so the dependencies expressed can pass constraint checks.
34911+
typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext), inferenceContext.nonFixingMapper);
3489834912
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
3489934913
}
3490034914
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
@@ -34919,7 +34933,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3491934933
// round of type inference and applicability checking for this particular candidate.
3492034934
argCheckMode = CheckMode.Normal;
3492134935
if (inferenceContext) {
34922-
const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext);
34936+
const typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext), inferenceContext.mapper);
3492334937
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters);
3492434938
// If the original signature has a generic rest type, instantiation may produce a
3492534939
// signature with different arity and we need to perform another arity check.

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6819,6 +6819,8 @@ export interface Signature {
68196819
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
68206820
/** @internal */
68216821
instantiations?: Map<string, Signature>; // Generic signature instantiation cache
6822+
/** @internal */
6823+
implementationSignatureCache?: Signature; // Copy of the signature with fresh type parameters to use in checking the body of a potentially self-referential generic function (deferred)
68226824
}
68236825

68246826
export const enum IndexKind {

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7241,6 +7241,7 @@ declare namespace ts {
72417241
ContainsSpread = 2097152,
72427242
ObjectRestType = 4194304,
72437243
InstantiationExpressionType = 8388608,
7244+
SingleSignatureType = 134217728,
72447245
}
72457246
interface ObjectType extends Type {
72467247
objectFlags: ObjectFlags;

0 commit comments

Comments
 (0)