From c4e632746edcc5a8742b243d4b336fe4d2871c6c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Nov 2014 14:49:15 -0800 Subject: [PATCH] Fix the logic in getting the contextual signature of union type as per spec Fixes #1054 --- src/compiler/checker.ts | 35 +++++-- ...ntextualTypeWithUnionTypeCallSignatures.js | 86 ++++++++++++++++ ...xtualTypeWithUnionTypeCallSignatures.types | 99 +++++++++++++++++++ .../contextualTypeWithUnionTypeMembers.types | 98 +++++++++--------- ...lTypeWithUnionTypeObjectLiteral.errors.txt | 12 +-- ...ntextualTypeWithUnionTypeCallSignatures.ts | 36 +++++++ 6 files changed, 305 insertions(+), 61 deletions(-) create mode 100644 tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js create mode 100644 tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.types create mode 100644 tests/cases/conformance/types/union/contextualTypeWithUnionTypeCallSignatures.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf00022c6f0..8fdbe13806b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4879,8 +4879,9 @@ module ts { // Return the contextual signature for a given expression node. A contextual type provides a // contextual signature if it has a single call signature and if that call signature is non-generic. - // If the contextual type is a union type and each constituent type that has a contextual signature - // provides the same contextual signature, then the union type provides that contextual signature. + // If the contextual type is a union type, get the signature from each type possible and if they are + // all identical ignoring their return type, the result is same signature but with return type as + // union type of return types from these signatures function getContextualSignature(node: Expression): Signature { var type = getContextualType(node); if (!type) { @@ -4889,19 +4890,41 @@ module ts { if (!(type.flags & TypeFlags.Union)) { return getNonGenericSignature(type); } - var result: Signature; + var signatureList: Signature[]; var types = (type).types; for (var i = 0; i < types.length; i++) { + // The signature set of all constituent type with call signatures should match + // So number of signatures allowed is either 0 or 1 + if (signatureList && + getSignaturesOfObjectOrUnionType(types[i], SignatureKind.Call).length > 1) { + return undefined; + } + var signature = getNonGenericSignature(types[i]); if (signature) { - if (!result) { - result = signature; + if (!signatureList) { + // This signature will contribute to contextual union signature + signatureList = [signature]; } - else if (!compareSignatures(result, signature, /*compareReturnTypes*/ true, compareTypes)) { + else if (!compareSignatures(signatureList[0], signature, /*compareReturnTypes*/ false, compareTypes)) { + // Signatures arent identical, do not use return undefined; } + else { + // Use this signature for contextual union signature + signatureList.push(signature); + } } } + + // Result is union of signatures collected (return type is union of return types of this signature set) + var result: Signature; + if (signatureList) { + result = cloneSignature(signatureList[0]); + // Clear resolved return type we possibly got from cloneSignature + result.resolvedReturnType = undefined; + result.unionSignatures = signatureList; + } return result; } diff --git a/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js new file mode 100644 index 00000000000..2e21235ea0a --- /dev/null +++ b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js @@ -0,0 +1,86 @@ +//// [contextualTypeWithUnionTypeCallSignatures.ts] +//When used as a contextual type, a union type U has those members that are present in any of +// its constituent types, with types that are unions of the respective members in the constituent types. + +// Let S be the set of types in U that have call signatures. +// If S is not empty and the sets of call signatures of the types in S are identical ignoring return types, +// U has the same set of call signatures, but with return types that are unions of the return types of the respective call signatures from each type in S. + +interface IWithNoCallSignatures { + foo: string; +} +interface IWithCallSignatures { + (a: number): string; +} +interface IWithCallSignatures2 { + (a: number): number; +} +interface IWithCallSignatures3 { + (b: string): number; +} +interface IWithCallSignatures4 { + (a: number): string; + (a: string, b: number): number; +} + +// With no call signature | callSignatures +var x: IWithNoCallSignatures | IWithCallSignatures = a => a.toString(); + +// With call signatures with different return type +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a.toString(); // Like iWithCallSignatures +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a; // Like iWithCallSignatures2 + +// With call signatures of mismatching parameter type +var x3: IWithCallSignatures | IWithCallSignatures3 = a => /*here a should be any*/ a.toString(); + +// With call signature count mismatch +var x4: IWithCallSignatures | IWithCallSignatures4 = a => /*here a should be any*/ a.toString(); + +//// [contextualTypeWithUnionTypeCallSignatures.js] +//When used as a contextual type, a union type U has those members that are present in any of +// its constituent types, with types that are unions of the respective members in the constituent types. +// With no call signature | callSignatures +var x = function (a) { return a.toString(); }; +// With call signatures with different return type +var x2 = function (a) { return a.toString(); }; // Like iWithCallSignatures +var x2 = function (a) { return a; }; // Like iWithCallSignatures2 +// With call signatures of mismatching parameter type +var x3 = function (a /*here a should be any*/) { return a.toString(); }; +// With call signature count mismatch +var x4 = function (a //When used as a contextual type, a union type U has those members that are present in any of +// its constituent types, with types that are unions of the respective members in the constituent types. + +// Let S be the set of types in U that have call signatures. +// If S is not empty and the sets of call signatures of the types in S are identical ignoring return types, +// U has the same set of call signatures, but with return types that are unions of the return types of the respective call signatures from each type in S. + +interface IWithNoCallSignatures { + foo: string; +} +interface IWithCallSignatures { + (a: number): string; +} +interface IWithCallSignatures2 { + (a: number): number; +} +interface IWithCallSignatures3 { + (b: string): number; +} +interface IWithCallSignatures4 { + (a: number): string; + (a: string, b: number): number; +} + +// With no call signature | callSignatures +var x: IWithNoCallSignatures | IWithCallSignatures = a => a.toString(); + +// With call signatures with different return type +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a.toString(); // Like iWithCallSignatures +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a; // Like iWithCallSignatures2 + +// With call signatures of mismatching parameter type +var x3: IWithCallSignatures | IWithCallSignatures3 = a => /*here a should be any*/ a.toString(); + +// With call signature count mismatch +var x4: IWithCallSignatures | IWithCallSignatures4 = a => + ) { return a.toString(); }; diff --git a/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.types b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.types new file mode 100644 index 00000000000..02dfecf5c08 --- /dev/null +++ b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.types @@ -0,0 +1,99 @@ +=== tests/cases/conformance/types/union/contextualTypeWithUnionTypeCallSignatures.ts === +//When used as a contextual type, a union type U has those members that are present in any of +// its constituent types, with types that are unions of the respective members in the constituent types. + +// Let S be the set of types in U that have call signatures. +// If S is not empty and the sets of call signatures of the types in S are identical ignoring return types, +// U has the same set of call signatures, but with return types that are unions of the return types of the respective call signatures from each type in S. + +interface IWithNoCallSignatures { +>IWithNoCallSignatures : IWithNoCallSignatures + + foo: string; +>foo : string +} +interface IWithCallSignatures { +>IWithCallSignatures : IWithCallSignatures + + (a: number): string; +>a : number +} +interface IWithCallSignatures2 { +>IWithCallSignatures2 : IWithCallSignatures2 + + (a: number): number; +>a : number +} +interface IWithCallSignatures3 { +>IWithCallSignatures3 : IWithCallSignatures3 + + (b: string): number; +>b : string +} +interface IWithCallSignatures4 { +>IWithCallSignatures4 : IWithCallSignatures4 + + (a: number): string; +>a : number + + (a: string, b: number): number; +>a : string +>b : number +} + +// With no call signature | callSignatures +var x: IWithNoCallSignatures | IWithCallSignatures = a => a.toString(); +>x : IWithNoCallSignatures | IWithCallSignatures +>IWithNoCallSignatures : IWithNoCallSignatures +>IWithCallSignatures : IWithCallSignatures +>a => a.toString() : (a: number) => string +>a : number +>a.toString() : string +>a.toString : (radix?: number) => string +>a : number +>toString : (radix?: number) => string + +// With call signatures with different return type +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a.toString(); // Like iWithCallSignatures +>x2 : IWithCallSignatures | IWithCallSignatures2 +>IWithCallSignatures : IWithCallSignatures +>IWithCallSignatures2 : IWithCallSignatures2 +>a => a.toString() : (a: number) => string +>a : number +>a.toString() : string +>a.toString : (radix?: number) => string +>a : number +>toString : (radix?: number) => string + +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a; // Like iWithCallSignatures2 +>x2 : IWithCallSignatures | IWithCallSignatures2 +>IWithCallSignatures : IWithCallSignatures +>IWithCallSignatures2 : IWithCallSignatures2 +>a => a : (a: number) => number +>a : number +>a : number + +// With call signatures of mismatching parameter type +var x3: IWithCallSignatures | IWithCallSignatures3 = a => /*here a should be any*/ a.toString(); +>x3 : IWithCallSignatures | IWithCallSignatures3 +>IWithCallSignatures : IWithCallSignatures +>IWithCallSignatures3 : IWithCallSignatures3 +>a => /*here a should be any*/ a.toString() : (a: any) => any +>a : any +>a.toString() : any +>a.toString : any +>a : any +>toString : any + +// With call signature count mismatch +var x4: IWithCallSignatures | IWithCallSignatures4 = a => /*here a should be any*/ a.toString(); +>x4 : IWithCallSignatures | IWithCallSignatures4 +>IWithCallSignatures : IWithCallSignatures +>IWithCallSignatures4 : IWithCallSignatures4 +>a => /*here a should be any*/ a.toString() : (a: any) => any +>a : any +>a.toString() : any +>a.toString : any +>a : any +>toString : any + diff --git a/tests/baselines/reference/contextualTypeWithUnionTypeMembers.types b/tests/baselines/reference/contextualTypeWithUnionTypeMembers.types index 93e01910870..522d4a4a555 100644 --- a/tests/baselines/reference/contextualTypeWithUnionTypeMembers.types +++ b/tests/baselines/reference/contextualTypeWithUnionTypeMembers.types @@ -321,25 +321,25 @@ var i11Ori21: I11 | I21 = { >i11Ori21 : I11 | I21 >I11 : I11 >I21 : I21 ->{ // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", } : { commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: string; } +>{ // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", } : { commonMethodDifferentReturnType: (a: string, b: number) => string; commonPropertyDifferentType: string; } // Like i1 commonMethodDifferentReturnType: (a, b) => { ->commonMethodDifferentReturnType : (a: any, b: any) => any ->(a, b) => { var z = a.charAt(b); return z; } : (a: any, b: any) => any ->a : any ->b : any +>commonMethodDifferentReturnType : (a: string, b: number) => string +>(a, b) => { var z = a.charAt(b); return z; } : (a: string, b: number) => string +>a : string +>b : number var z = a.charAt(b); ->z : any ->a.charAt(b) : any ->a.charAt : any ->a : any ->charAt : any ->b : any +>z : string +>a.charAt(b) : string +>a.charAt : (pos: number) => string +>a : string +>charAt : (pos: number) => string +>b : number return z; ->z : any +>z : string }, commonPropertyDifferentType: "hello", @@ -350,25 +350,25 @@ var i11Ori21: I11 | I21 = { >i11Ori21 : I11 | I21 >I11 : I11 >I21 : I21 ->{ // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10,} : { commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: number; } +>{ // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10,} : { commonMethodDifferentReturnType: (a: string, b: number) => number; commonPropertyDifferentType: number; } // Like i2 commonMethodDifferentReturnType: (a, b) => { ->commonMethodDifferentReturnType : (a: any, b: any) => any ->(a, b) => { var z = a.charCodeAt(b); return z; } : (a: any, b: any) => any ->a : any ->b : any +>commonMethodDifferentReturnType : (a: string, b: number) => number +>(a, b) => { var z = a.charCodeAt(b); return z; } : (a: string, b: number) => number +>a : string +>b : number var z = a.charCodeAt(b); ->z : any ->a.charCodeAt(b) : any ->a.charCodeAt : any ->a : any ->charCodeAt : any ->b : any +>z : number +>a.charCodeAt(b) : number +>a.charCodeAt : (index: number) => number +>a : string +>charCodeAt : (index: number) => number +>b : number return z; ->z : any +>z : number }, commonPropertyDifferentType: 10, @@ -380,56 +380,56 @@ var arrayOrI11OrI21: Array = [i11, i21, i11 || i21, { >Array : T[] >I11 : I11 >I21 : I21 ->[i11, i21, i11 || i21, { // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", }, { // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10, }] : ({ commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: string; } | { commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: number; })[] +>[i11, i21, i11 || i21, { // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", }, { // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10, }] : (I11 | I21)[] >i11 : I11 >i21 : I21 >i11 || i21 : I11 | I21 >i11 : I11 >i21 : I21 ->{ // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", } : { commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: string; } +>{ // Like i1 commonMethodDifferentReturnType: (a, b) => { var z = a.charAt(b); return z; }, commonPropertyDifferentType: "hello", } : { commonMethodDifferentReturnType: (a: string, b: number) => string; commonPropertyDifferentType: string; } // Like i1 commonMethodDifferentReturnType: (a, b) => { ->commonMethodDifferentReturnType : (a: any, b: any) => any ->(a, b) => { var z = a.charAt(b); return z; } : (a: any, b: any) => any ->a : any ->b : any +>commonMethodDifferentReturnType : (a: string, b: number) => string +>(a, b) => { var z = a.charAt(b); return z; } : (a: string, b: number) => string +>a : string +>b : number var z = a.charAt(b); ->z : any ->a.charAt(b) : any ->a.charAt : any ->a : any ->charAt : any ->b : any +>z : string +>a.charAt(b) : string +>a.charAt : (pos: number) => string +>a : string +>charAt : (pos: number) => string +>b : number return z; ->z : any +>z : string }, commonPropertyDifferentType: "hello", >commonPropertyDifferentType : string }, { ->{ // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10, } : { commonMethodDifferentReturnType: (a: any, b: any) => any; commonPropertyDifferentType: number; } +>{ // Like i2 commonMethodDifferentReturnType: (a, b) => { var z = a.charCodeAt(b); return z; }, commonPropertyDifferentType: 10, } : { commonMethodDifferentReturnType: (a: string, b: number) => number; commonPropertyDifferentType: number; } // Like i2 commonMethodDifferentReturnType: (a, b) => { ->commonMethodDifferentReturnType : (a: any, b: any) => any ->(a, b) => { var z = a.charCodeAt(b); return z; } : (a: any, b: any) => any ->a : any ->b : any +>commonMethodDifferentReturnType : (a: string, b: number) => number +>(a, b) => { var z = a.charCodeAt(b); return z; } : (a: string, b: number) => number +>a : string +>b : number var z = a.charCodeAt(b); ->z : any ->a.charCodeAt(b) : any ->a.charCodeAt : any ->a : any ->charCodeAt : any ->b : any +>z : number +>a.charCodeAt(b) : number +>a.charCodeAt : (index: number) => number +>a : string +>charCodeAt : (index: number) => number +>b : number return z; ->z : any +>z : number }, commonPropertyDifferentType: 10, diff --git a/tests/baselines/reference/contextualTypeWithUnionTypeObjectLiteral.errors.txt b/tests/baselines/reference/contextualTypeWithUnionTypeObjectLiteral.errors.txt index 9ef73eaf337..6ca334ef311 100644 --- a/tests/baselines/reference/contextualTypeWithUnionTypeObjectLiteral.errors.txt +++ b/tests/baselines/reference/contextualTypeWithUnionTypeObjectLiteral.errors.txt @@ -23,10 +23,10 @@ tests/cases/conformance/types/union/contextualTypeWithUnionTypeObjectLiteral.ts( Types of property 'prop' are incompatible. Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. -tests/cases/conformance/types/union/contextualTypeWithUnionTypeObjectLiteral.ts(57,5): error TS2323: Type '{ commonMethodDifferentReturnType: (a: any, b: any) => string | number; }' is not assignable to type 'I11 | I21'. - Type '{ commonMethodDifferentReturnType: (a: any, b: any) => string | number; }' is not assignable to type 'I21'. +tests/cases/conformance/types/union/contextualTypeWithUnionTypeObjectLiteral.ts(57,5): error TS2323: Type '{ commonMethodDifferentReturnType: (a: string, b: number) => string | number; }' is not assignable to type 'I11 | I21'. + Type '{ commonMethodDifferentReturnType: (a: string, b: number) => string | number; }' is not assignable to type 'I21'. Types of property 'commonMethodDifferentReturnType' are incompatible. - Type '(a: any, b: any) => string | number' is not assignable to type '(a: string, b: number) => number'. + Type '(a: string, b: number) => string | number' is not assignable to type '(a: string, b: number) => number'. Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. @@ -120,10 +120,10 @@ tests/cases/conformance/types/union/contextualTypeWithUnionTypeObjectLiteral.ts( var strOrNumber: string | number; var i11Ori21: I11 | I21 = { // Like i1 and i2 both ~~~~~~~~ -!!! error TS2323: Type '{ commonMethodDifferentReturnType: (a: any, b: any) => string | number; }' is not assignable to type 'I11 | I21'. -!!! error TS2323: Type '{ commonMethodDifferentReturnType: (a: any, b: any) => string | number; }' is not assignable to type 'I21'. +!!! error TS2323: Type '{ commonMethodDifferentReturnType: (a: string, b: number) => string | number; }' is not assignable to type 'I11 | I21'. +!!! error TS2323: Type '{ commonMethodDifferentReturnType: (a: string, b: number) => string | number; }' is not assignable to type 'I21'. !!! error TS2323: Types of property 'commonMethodDifferentReturnType' are incompatible. -!!! error TS2323: Type '(a: any, b: any) => string | number' is not assignable to type '(a: string, b: number) => number'. +!!! error TS2323: Type '(a: string, b: number) => string | number' is not assignable to type '(a: string, b: number) => number'. !!! error TS2323: Type 'string | number' is not assignable to type 'number'. !!! error TS2323: Type 'string' is not assignable to type 'number'. commonMethodDifferentReturnType: (a, b) => strOrNumber, diff --git a/tests/cases/conformance/types/union/contextualTypeWithUnionTypeCallSignatures.ts b/tests/cases/conformance/types/union/contextualTypeWithUnionTypeCallSignatures.ts new file mode 100644 index 00000000000..fccf5381017 --- /dev/null +++ b/tests/cases/conformance/types/union/contextualTypeWithUnionTypeCallSignatures.ts @@ -0,0 +1,36 @@ +//When used as a contextual type, a union type U has those members that are present in any of +// its constituent types, with types that are unions of the respective members in the constituent types. + +// Let S be the set of types in U that have call signatures. +// If S is not empty and the sets of call signatures of the types in S are identical ignoring return types, +// U has the same set of call signatures, but with return types that are unions of the return types of the respective call signatures from each type in S. + +interface IWithNoCallSignatures { + foo: string; +} +interface IWithCallSignatures { + (a: number): string; +} +interface IWithCallSignatures2 { + (a: number): number; +} +interface IWithCallSignatures3 { + (b: string): number; +} +interface IWithCallSignatures4 { + (a: number): string; + (a: string, b: number): number; +} + +// With no call signature | callSignatures +var x: IWithNoCallSignatures | IWithCallSignatures = a => a.toString(); + +// With call signatures with different return type +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a.toString(); // Like iWithCallSignatures +var x2: IWithCallSignatures | IWithCallSignatures2 = a => a; // Like iWithCallSignatures2 + +// With call signatures of mismatching parameter type +var x3: IWithCallSignatures | IWithCallSignatures3 = a => /*here a should be any*/ a.toString(); + +// With call signature count mismatch +var x4: IWithCallSignatures | IWithCallSignatures4 = a => /*here a should be any*/ a.toString(); \ No newline at end of file