From b3dd18a4630ac9e4ccb24b92bbca40e1b940f3ff Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Aug 2015 15:20:17 -0700 Subject: [PATCH] Allow partial matches in union type signatures --- src/compiler/checker.ts | 105 +++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 45 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e1b7ba42f6..c5c51f88b0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3131,52 +3131,66 @@ namespace ts { setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType); } - function findMatchingSignature(signature: Signature, signatureList: Signature[]): Signature { - for (let s of signatureList) { - // Only signatures with no type parameters may differ in return types - if (compareSignatures(signature, s, /*compareReturnTypes*/ !!signature.typeParameters, compareTypes)) { + function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature { + for (let s of signatureList) { + if (compareSignatures(s, signature, partialMatch, ignoreReturnTypes, compareTypes)) { return s; } } } - function findMatchingSignatures(signature: Signature, signatureLists: Signature[][]): Signature[] { + function findMatchingSignatures(signatureLists: Signature[][], signature: Signature, listIndex: number): Signature[] { + if (signature.typeParameters) { + // We require an exact match for generic signatures, so we only return signatures from the first + // signature list and only if they have exact matches in the other signature lists. + if (listIndex > 0) { + return undefined; + } + for (let i = 1; i < signatureLists.length; i++) { + if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ false)) { + return undefined; + } + } + return [signature]; + } let result: Signature[] = undefined; - for (let i = 1; i < signatureLists.length; i++) { - let match = findMatchingSignature(signature, signatureLists[i]); + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters and different return types + let match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreReturnTypes*/ true); if (!match) { return undefined; } - if (!result) { - result = [signature]; - } - if (match !== signature) { - result.push(match); + if (!contains(result, match)) { + (result || (result = [])).push(match); } } return result; } - // The signatures of a union type are those signatures that are present and identical in each of the - // constituent types, except that non-generic signatures may differ in return types. When signatures - // differ in return types, the resulting return type is the union of the constituent return types. + // The signatures of a union type are those signatures that are present in each of the constituent types. + // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional + // parameters and may differ in return types. When signatures differ in return types, the resulting return + // type is the union of the constituent return types. function getUnionSignatures(types: Type[], kind: SignatureKind): Signature[] { let signatureLists = map(types, t => getSignaturesOfType(t, kind)); let result: Signature[] = undefined; - for (let source of signatureLists[0]) { - let unionSignatures = findMatchingSignatures(source, signatureLists); - if (unionSignatures) { - let signature: Signature = undefined; - if (unionSignatures.length === 1 || source.typeParameters) { - signature = source; + for (let i = 0; i < signatureLists.length; i++) { + for (let signature of signatureLists[i]) { + // Only process signatures with parameter lists that aren't already in the result list + if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true)) { + let unionSignatures = findMatchingSignatures(signatureLists, signature, i); + if (unionSignatures) { + let s = signature; + // Union the result types when more than one signature matches + if (unionSignatures.length > 1) { + s = cloneSignature(signature); + // Clear resolved return type we possibly got from cloneSignature + s.resolvedReturnType = undefined; + s.unionSignatures = unionSignatures; + } + (result || (result = [])).push(s); + } } - else { - signature = cloneSignature(source); - // Clear resolved return type we possibly got from cloneSignature - signature.resolvedReturnType = undefined; - signature.unionSignatures = unionSignatures; - } - (result || (result = [])).push(signature); } } return result || emptyArray; @@ -5242,7 +5256,7 @@ namespace ts { } let result = Ternary.True; for (let i = 0, len = sourceSignatures.length; i < len; ++i) { - let related = compareSignatures(sourceSignatures[i], targetSignatures[i], /*compareReturnTypes*/ true, isRelatedTo); + let related = compareSignatures(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); if (!related) { return Ternary.False; } @@ -5372,15 +5386,21 @@ namespace ts { return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } - function compareSignatures(source: Signature, target: Signature, compareReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + function compareSignatures(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { if (source === target) { return Ternary.True; } - if (source.parameters.length !== target.parameters.length || - source.minArgumentCount !== target.minArgumentCount || - source.hasRestParameter !== target.hasRestParameter) { + if (source.minArgumentCount !== target.minArgumentCount) { return Ternary.False; } + if (source.parameters.length !== target.parameters.length || + source.hasRestParameter !== target.hasRestParameter) { + if (!partialMatch || + source.parameters.length < target.parameters.length || + target.hasRestParameter) { + return Ternary.False; + } + } let result = Ternary.True; if (source.typeParameters && target.typeParameters) { if (source.typeParameters.length !== target.typeParameters.length) { @@ -5401,16 +5421,18 @@ namespace ts { // 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); - for (let i = 0, len = source.parameters.length; i < len; i++) { - let s = source.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); - let t = target.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); + let sourceLen = source.parameters.length; + let targetLen = target.parameters.length; + for (let i = 0; i < targetLen; i++) { + let s = source.hasRestParameter && i === sourceLen - 1 ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); + let t = target.hasRestParameter && i === targetLen - 1 ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); let related = compareTypes(s, t); if (!related) { return Ternary.False; } result &= related; } - if (compareReturnTypes) { + if (!ignoreReturnTypes) { result &= compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } return result; @@ -6924,20 +6946,13 @@ namespace ts { let signatureList: Signature[]; let types = (type).types; for (let current of types) { - // The signature set of all constituent type with call signatures should match - // So number of signatures allowed is either 0 or 1 - if (signatureList && - getSignaturesOfStructuredType(current, SignatureKind.Call).length > 1) { - return undefined; - } - let signature = getNonGenericSignature(current); if (signature) { if (!signatureList) { // This signature will contribute to contextual union signature signatureList = [signature]; } - else if (!compareSignatures(signatureList[0], signature, /*compareReturnTypes*/ false, compareTypes)) { + else if (!compareSignatures(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypes)) { // Signatures aren't identical, do not use return undefined; }