From d8db60a9a2be9c4ca82170bc6bb457b70b20f7b5 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 15 Dec 2015 17:52:26 -0800 Subject: [PATCH] Use a comparison function instead of creating a new type for each signature. --- src/compiler/checker.ts | 125 ++++++++++++++++++++++++++++++---------- src/compiler/types.ts | 2 - 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fac21177aae..e1b400310b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3501,7 +3501,7 @@ namespace ts { function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature { for (const s of signatureList) { - if (compareSignatures(s, signature, partialMatch, ignoreReturnTypes, compareTypes)) { + if (compareSignaturesIdentical(s, signature, partialMatch, ignoreReturnTypes, compareTypesIdentical)) { return s; } } @@ -4115,16 +4115,6 @@ namespace ts { return signature.erasedSignatureCache; } - function getAnyReturningErasedSignature(signature: Signature): Signature { - if (!signature.anyReturningErasedSignatureCache) { - const erasedSignature = getErasedSignature(signature); - const anyReturningErasedSignature = cloneSignature(erasedSignature); - anyReturningErasedSignature.resolvedReturnType = anyType; - signature.anyReturningErasedSignatureCache = anyReturningErasedSignature; - } - return signature.anyReturningErasedSignatureCache; - } - function getOrCreateTypeFromSignature(signature: Signature): 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 @@ -4965,7 +4955,7 @@ namespace ts { return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined); } - function compareTypes(source: Type, target: Type): Ternary { + function compareTypesIdentical(source: Type, target: Type): Ternary { return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined) ? Ternary.True : Ternary.False; } @@ -4985,10 +4975,53 @@ namespace ts { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } - function isSignatureAssignableTo(source: Signature, target: Signature): boolean { - const sourceType = getOrCreateTypeFromSignature(source); - const targetType = getOrCreateTypeFromSignature(target); - return checkTypeRelatedTo(sourceType, targetType, assignableRelation, /*errorNode*/ undefined); + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return true; + } + if (!target.hasRestParameter && source.minArgumentCount > target.parameters.length) { + return 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); + + const sourceMax = getNumNonRestParameters(source); + const targetMax = getNumNonRestParameters(target); + const checkCount = getNumParametersToCheckForSignatureRelatability(source, sourceMax, target, targetMax); + for (let i = 0; i < checkCount; i++) { + const s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target); + const related = isTypeAssignableTo(t, s) || isTypeAssignableTo(s, t); + if (!related) { + return false; + } + } + + if (!ignoreReturnTypes) { + const targetReturnType = getReturnTypeOfSignature(target); + if (targetReturnType === voidType) { + return true; + } + const sourceReturnType = getReturnTypeOfSignature(source); + + // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions + if (targetReturnType.flags & TypeFlags.PredicateType && (targetReturnType as PredicateType).predicate.kind === TypePredicateKind.Identifier) { + if (!(sourceReturnType.flags & TypeFlags.PredicateType)) { + return false; + } + } + + return isTypeAssignableTo(sourceReturnType, targetReturnType); + } + + return true; } function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { @@ -5002,15 +5035,35 @@ namespace ts { || checkTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation, /*errorNode*/ undefined) || checkTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation, /*errorNode*/ undefined)) { - // The return types are compatible, so create versions of the signature with 'any' as the return type. - // We need to do this so that we can check assignability while disregarding the return type. - const anyReturningSource = getAnyReturningErasedSignature(implementation); - const anyReturningTarget = getAnyReturningErasedSignature(overload); + return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); + } - // Create object types to actually perform relation checks. - const anyReturningSourceType = getOrCreateTypeFromSignature(anyReturningSource); - const anyReturningTargetType = getOrCreateTypeFromSignature(anyReturningTarget); - return checkTypeRelatedTo(anyReturningSourceType, anyReturningTargetType, assignableRelation, /*errorNode*/ undefined); + return false; + } + + function getNumNonRestParameters(signature: Signature) { + const numParams = signature.parameters.length; + return signature.hasRestParameter ? + numParams - 1 : + numParams; + } + + function getNumParametersToCheckForSignatureRelatability(source: Signature, sourceNonRestParamCount: number, target: Signature, targetNonRestParamCount: number) { + if (source.hasRestParameter === target.hasRestParameter) { + if (source.hasRestParameter) { + // If both have rest parameters, get the max and add 1 to + // compensate for the rest parameter. + return Math.max(sourceNonRestParamCount, targetNonRestParamCount) + 1; + } + else { + return Math.min(sourceNonRestParamCount, targetNonRestParamCount); + } + } + else { + // Return the count for whichever signature doesn't have rest parameters. + return source.hasRestParameter ? + targetNonRestParamCount : + sourceNonRestParamCount; } } @@ -5645,7 +5698,11 @@ namespace ts { } } + /** + * See signatureAssignableTo, signatureAssignableTo + */ function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary { + // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; } @@ -5697,10 +5754,12 @@ namespace ts { } const targetReturnType = getReturnTypeOfSignature(target); - if (targetReturnType === voidType) return result; + if (targetReturnType === voidType) { + return result; + } const sourceReturnType = getReturnTypeOfSignature(source); - // The follow block preserves old behavior forbidding boolean returning functions from being assignable to type guard returning functions + // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions if (targetReturnType.flags & TypeFlags.PredicateType && (targetReturnType as PredicateType).predicate.kind === TypePredicateKind.Identifier) { if (!(sourceReturnType.flags & TypeFlags.PredicateType)) { if (reportErrors) { @@ -5721,7 +5780,7 @@ namespace ts { } let result = Ternary.True; for (let i = 0, len = sourceSignatures.length; i < len; ++i) { - const related = compareSignatures(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); + const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); if (!related) { return Ternary.False; } @@ -5833,7 +5892,7 @@ namespace ts { } function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { - return compareProperties(sourceProp, targetProp, compareTypes) !== Ternary.False; + return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; } function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { @@ -5880,7 +5939,11 @@ namespace ts { return false; } - function compareSignatures(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; } @@ -7571,7 +7634,7 @@ namespace ts { // This signature will contribute to contextual union signature signatureList = [signature]; } - else if (!compareSignatures(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypes)) { + else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { // Signatures aren't identical, do not use return undefined; } @@ -11564,7 +11627,7 @@ namespace ts { } for (const otherSignature of signaturesToCheck) { - if (!otherSignature.hasStringLiterals && isSignatureAssignableTo(signature, otherSignature)) { + if (!otherSignature.hasStringLiterals && isSignatureAssignableTo(signature, otherSignature, /*ignoreReturnTypes*/ false)) { return; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c6c78709300..ff8ffa4ebd2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2280,8 +2280,6 @@ namespace ts { /* @internal */ erasedSignatureCache?: Signature; // Erased version of signature (deferred) /* @internal */ - anyReturningErasedSignatureCache?: Signature; // A version of the erased signature whose type returns 'any' - /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison }