From 368e5af162c341dad6bb5db8d1c4ed6c843024e1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Jun 2017 09:49:50 -0700 Subject: [PATCH] Stricter type relationship checking of generic signatures --- src/compiler/checker.ts | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b3084f9cbd..b5b5af9d8d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8408,10 +8408,9 @@ namespace ts { return Ternary.False; } - // Spec 1.0 Section 3.8.3 & 3.8.4: - // M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N - source = getErasedSignature(source); - target = getErasedSignature(target); + if (source.typeParameters) { + source = instantiateSignatureInContextOf(source, target); + } let result = Ternary.True; @@ -9446,23 +9445,32 @@ namespace ts { const saveErrorInfo = errorInfo; if (getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol) { - // We instantiations of the same anonymous type (which typically will be the type of a method). - // Simply do a pairwise comparison of the signatures in the two signature lists instead of the - // much more expensive N * M comparison matrix we explore below. + // We have instantiations of the same anonymous type (which typically will be the type of a + // method). Simply do a pairwise comparison of the signatures in the two signature lists instead + // of the much more expensive N * M comparison matrix we explore below. We erase type parameters + // as they are known to always be the same. for (let i = 0; i < targetSignatures.length; i++) { - const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], reportErrors); + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors); if (!related) { return Ternary.False; } result &= related; } } + else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { + // For pure functions (functions with a single signature) we only erase type parameters for + // the comparable relation. Otherwise, if the source signature is generic, we instantiate it + // in the context of the target signature before checking the relationship. Ideally we'd do + // this regardless of the number of signatures, but the potential costs are prohibitive due + // to the quadratic nature of the logic below. + result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], /*erase*/ relation === comparableRelation, reportErrors); + } else { outer: for (const t of targetSignatures) { // Only elaborate errors from the first failure let shouldElaborateErrors = reportErrors; for (const s of sourceSignatures) { - const related = signatureRelatedTo(s, t, shouldElaborateErrors); + const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors); if (related) { result &= related; errorInfo = saveErrorInfo; @@ -9485,8 +9493,9 @@ namespace ts { /** * See signatureAssignableTo, compareSignaturesIdentical */ - function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary { - return compareSignaturesRelated(source, target, /*checkAsCallback*/ false, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo); + function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean): Ternary { + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, + /*checkAsCallback*/ false, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { @@ -14915,12 +14924,15 @@ namespace ts { } // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper: TypeMapper): Signature { + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper): Signature { const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes); forEachMatchingParameterType(contextualSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type - inferTypes(context.inferences, instantiateType(source, contextualMapper), target); + inferTypes(context.inferences, instantiateType(source, contextualMapper || identityMapper), target); }); + if (!contextualMapper) { + inferTypes(context.inferences, getReturnTypeOfSignature(contextualSignature), getReturnTypeOfSignature(signature), InferencePriority.ReturnType); + } return getSignatureInstantiation(signature, getInferredTypes(context)); }