From 482acccadaa754c96e09059dff68c50619e5f8aa Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 8 Mar 2016 13:05:00 -0800 Subject: [PATCH] Union this-types of unioned call signatures And and tests and baselines --- src/compiler/checker.ts | 26 +++++++++----- .../reference/unionThisTypeInFunctions.js | 18 ++++++++++ .../unionThisTypeInFunctions.symbols | 33 +++++++++++++++++ .../reference/unionThisTypeInFunctions.types | 35 +++++++++++++++++++ .../thisType/unionThisTypeInFunctions.ts | 12 +++++++ 5 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/unionThisTypeInFunctions.js create mode 100644 tests/baselines/reference/unionThisTypeInFunctions.symbols create mode 100644 tests/baselines/reference/unionThisTypeInFunctions.types create mode 100644 tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d1b6dc5435e..b5337082c0f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3610,9 +3610,9 @@ namespace ts { setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexInfo, arrayType.numberIndexInfo); } - function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature { + function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature { for (const s of signatureList) { - if (compareSignaturesIdentical(s, signature, partialMatch, ignoreReturnTypes, compareTypesIdentical)) { + if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, compareTypesIdentical)) { return s; } } @@ -3626,7 +3626,7 @@ namespace ts { return undefined; } for (let i = 1; i < signatureLists.length; i++) { - if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ false)) { + if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { return undefined; } } @@ -3635,7 +3635,7 @@ namespace ts { let result: Signature[] = undefined; for (let i = 0; i < signatureLists.length; i++) { // Allow matching non-generic signatures to have excess parameters and different return types - const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreReturnTypes*/ true); + const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true); if (!match) { return undefined; } @@ -3656,13 +3656,16 @@ namespace ts { for (let i = 0; i < signatureLists.length; i++) { for (const 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)) { + if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) { const 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); + if (forEach(unionSignatures, sig => sig.thisType)) { + s.thisType = getUnionType(map(unionSignatures, sig => sig.thisType || anyType)); + } // Clear resolved return type we possibly got from cloneSignature s.resolvedReturnType = undefined; s.unionSignatures = unionSignatures; @@ -6002,7 +6005,7 @@ namespace ts { } let result = Ternary.True; for (let i = 0, len = sourceSignatures.length; i < len; i++) { - const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); + const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); if (!related) { return Ternary.False; } @@ -6203,7 +6206,7 @@ namespace ts { /** * See signatureRelatedTo, compareSignaturesIdentical */ - function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; @@ -6224,6 +6227,13 @@ namespace ts { source = getErasedSignature(source); target = getErasedSignature(target); let result = Ternary.True; + if (!ignoreThisTypes && source.thisType && target.thisType) { + const related = compareTypes(source.thisType, target.thisType); + if (!related) { + return Ternary.False; + } + result &= related; + } const targetLen = target.parameters.length; for (let i = 0; i < targetLen; i++) { const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); @@ -8137,7 +8147,7 @@ namespace ts { // This signature will contribute to contextual union signature signatureList = [signature]; } - else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { // Signatures aren't identical, do not use return undefined; } diff --git a/tests/baselines/reference/unionThisTypeInFunctions.js b/tests/baselines/reference/unionThisTypeInFunctions.js new file mode 100644 index 00000000000..31ff266087b --- /dev/null +++ b/tests/baselines/reference/unionThisTypeInFunctions.js @@ -0,0 +1,18 @@ +//// [unionThisTypeInFunctions.ts] +interface Real { + method(n: number): void; + data: string; +} +interface Fake { + method(n: number): void; + data: number; +} +function test(r: Real | Fake) { + r.method(12); +} + + +//// [unionThisTypeInFunctions.js] +function test(r) { + r.method(12); +} diff --git a/tests/baselines/reference/unionThisTypeInFunctions.symbols b/tests/baselines/reference/unionThisTypeInFunctions.symbols new file mode 100644 index 00000000000..8b5f2af01e0 --- /dev/null +++ b/tests/baselines/reference/unionThisTypeInFunctions.symbols @@ -0,0 +1,33 @@ +=== tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts === +interface Real { +>Real : Symbol(Real, Decl(unionThisTypeInFunctions.ts, 0, 0)) + + method(n: number): void; +>method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 0, 16)) +>n : Symbol(n, Decl(unionThisTypeInFunctions.ts, 1, 11)) + + data: string; +>data : Symbol(data, Decl(unionThisTypeInFunctions.ts, 1, 28)) +} +interface Fake { +>Fake : Symbol(Fake, Decl(unionThisTypeInFunctions.ts, 3, 1)) + + method(n: number): void; +>method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 4, 16)) +>n : Symbol(n, Decl(unionThisTypeInFunctions.ts, 5, 11)) + + data: number; +>data : Symbol(data, Decl(unionThisTypeInFunctions.ts, 5, 28)) +} +function test(r: Real | Fake) { +>test : Symbol(test, Decl(unionThisTypeInFunctions.ts, 7, 1)) +>r : Symbol(r, Decl(unionThisTypeInFunctions.ts, 8, 14)) +>Real : Symbol(Real, Decl(unionThisTypeInFunctions.ts, 0, 0)) +>Fake : Symbol(Fake, Decl(unionThisTypeInFunctions.ts, 3, 1)) + + r.method(12); +>r.method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 0, 16), Decl(unionThisTypeInFunctions.ts, 4, 16)) +>r : Symbol(r, Decl(unionThisTypeInFunctions.ts, 8, 14)) +>method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 0, 16), Decl(unionThisTypeInFunctions.ts, 4, 16)) +} + diff --git a/tests/baselines/reference/unionThisTypeInFunctions.types b/tests/baselines/reference/unionThisTypeInFunctions.types new file mode 100644 index 00000000000..f2c4f7ee4ec --- /dev/null +++ b/tests/baselines/reference/unionThisTypeInFunctions.types @@ -0,0 +1,35 @@ +=== tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts === +interface Real { +>Real : Real + + method(n: number): void; +>method : (this: this, n: number) => void +>n : number + + data: string; +>data : string +} +interface Fake { +>Fake : Fake + + method(n: number): void; +>method : (this: this, n: number) => void +>n : number + + data: number; +>data : number +} +function test(r: Real | Fake) { +>test : (this: void, r: Real | Fake) => void +>r : Real | Fake +>Real : Real +>Fake : Fake + + r.method(12); +>r.method(12) : void +>r.method : ((this: Real, n: number) => void) | ((this: Fake, n: number) => void) +>r : Real | Fake +>method : ((this: Real, n: number) => void) | ((this: Fake, n: number) => void) +>12 : number +} + diff --git a/tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts b/tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts new file mode 100644 index 00000000000..a140c3fba95 --- /dev/null +++ b/tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts @@ -0,0 +1,12 @@ +// @strictThis: true +interface Real { + method(n: number): void; + data: string; +} +interface Fake { + method(n: number): void; + data: number; +} +function test(r: Real | Fake) { + r.method(12); +}