Skip to content

Commit 35f064d

Browse files
committed
Require identical type parameters to intersect call signatures.
1 parent 201c263 commit 35f064d

10 files changed

+730
-300
lines changed

src/compiler/checker.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7095,25 +7095,58 @@ namespace ts {
70957095
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
70967096
}
70977097

7098+
function callSignaturesAreIdenticalExceptReturnType(a: Signature, b: Signature): boolean {
7099+
const parametersA = a.parameters;
7100+
const parametersB = b.parameters;
7101+
7102+
if (parametersA.length !== parametersB.length) {
7103+
return false;
7104+
}
7105+
7106+
if (!every(parametersA, (parameter, i) => isTypeIdenticalTo(getTypeOfParameter(parameter), getTypeOfParameter(parametersB[i])))) {
7107+
return false;
7108+
}
7109+
7110+
// Parameter types are identical, now check type parameters.
7111+
const typesA = a.typeParameters;
7112+
const typesB = b.typeParameters;
7113+
7114+
if (typesA === undefined || typesB === undefined) {
7115+
return typesA === typesB;
7116+
}
7117+
7118+
if (typesA.length !== typesB.length) {
7119+
return false;
7120+
}
7121+
7122+
return every(typesA, (parameter, i) => {
7123+
const constraintA = getConstraintOfTypeParameter(parameter);
7124+
const constraintB = getConstraintOfTypeParameter(typesB[i]);
7125+
const sameConstraints = (constraintA === undefined && constraintB === undefined) ||
7126+
(constraintA !== undefined && constraintB !== undefined && isTypeIdenticalTo(constraintA, constraintB));
7127+
7128+
const defaultA = parameter.default;
7129+
const defaultB = typesB[i].default;
7130+
const sameDefaults = (defaultA === undefined && defaultB === undefined) ||
7131+
(defaultA !== undefined && defaultB !== undefined && isTypeIdenticalTo(defaultA, defaultB));
7132+
7133+
return sameConstraints && sameDefaults;
7134+
});
7135+
}
7136+
70987137
function intersectCallSignatures(callSignatures: ReadonlyArray<Signature>): ReadonlyArray<Signature> {
70997138
if (callSignatures.length === 0) {
71007139
return callSignatures;
71017140
}
71027141

7103-
// Overloads that differ only be return type make no sense. So:
7104-
// 1. Group all the call signatures by their parameter types. Each group is an overload.
7142+
// Overloads that differ only by return type are not usable, so:
7143+
// 1. Group all the call signatures by their type parameters + parameter types. Each group is an overload.
71057144
// 2. Create a new signature for each group where the return type is the intersection
71067145
// of the return types of the signatures in that group.
71077146
const groups: Signature[][] = [];
71087147

71097148
callSignatures.forEach((callSignature) => {
7110-
const matchingGroup = find(groups, (group) => {
7111-
const candidate = group[0];
7112-
const candidateParameters = candidate.parameters;
7113-
const thisCallParameters = callSignature.parameters;
7114-
return candidateParameters.length === thisCallParameters.length &&
7115-
every(thisCallParameters, (parameter, index) => isTypeIdenticalTo(getTypeOfParameter(parameter), getTypeOfParameter(candidateParameters[index])));
7116-
});
7149+
const matchingGroup = find(groups, (group) => callSignaturesAreIdenticalExceptReturnType(callSignature, group[0]));
71177150
if (matchingGroup === undefined) {
71187151
groups.push([callSignature]);
71197152
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
tests/cases/compiler/intersectionOfCallsWithSameParameters.ts(38,7): error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
2+
tests/cases/compiler/intersectionOfCallsWithSameParameters.ts(39,7): error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
3+
tests/cases/compiler/intersectionOfCallsWithSameParameters.ts(40,7): error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
4+
tests/cases/compiler/intersectionOfCallsWithSameParameters.ts(41,7): error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
5+
6+
7+
==== tests/cases/compiler/intersectionOfCallsWithSameParameters.ts (4 errors) ====
8+
interface One {
9+
differentParameterType(id: string): { one: number };
10+
differentNumberOfParameters(id: string): { one: number };
11+
differentTypeParameterDefault<T = number>(id: string): { one: number };
12+
differentTypeParameterConstraint<T extends { one: number }>(id: string): { one: number };
13+
14+
same1(id: string): { one: number };
15+
same2<T>(id: string): { one: number };
16+
same3<T extends { one: number }>(id: string): { one: number };
17+
same4<T = number>(id: string): { one: number };
18+
same5<T1 extends { one: number }, T2 = number>(id: string): { one: number };
19+
}
20+
21+
interface Two {
22+
differentParameterType(id: number): { two: number };
23+
differentNumberOfParameters(id: string, second: string): { two: number };
24+
differentTypeParameterDefault<T = string>(id: string): { two: number };
25+
differentTypeParameterConstraint<T extends { two: number }>(id: string): { two: number };
26+
27+
same1(id: string): { two: number };
28+
same2<T>(id: string): { two: number };
29+
same3<T extends { one: number }>(id: string): { two: number };
30+
same4<T = number>(id: string): { two: number };
31+
same5<T1 extends { one: number }, T2 = number>(id: string): { two: number };
32+
}
33+
34+
const i: One & Two = <any>{};
35+
36+
// These lines should type check; the return type should be intersected.
37+
const same1: { one: number, two: number } = i.same1('test');
38+
const same2: { one: number, two: number } = i.same2<number>('test');
39+
const same3: { one: number, two: number } = i.same3<{ one:number }>('test');
40+
const same4: { one: number, two: number } = i.same4('test');
41+
const same5: { one: number, two: number } = i.same5<{ one:number }, string>('test');
42+
43+
// These lines should not, because the functions should become overloads rather
44+
// than the return types intersected.
45+
const differentParameterType: { one: number, two: number } = i.differentParameterType('test');
46+
~~~~~~~~~~~~~~~~~~~~~~
47+
!!! error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
48+
!!! related TS2728 tests/cases/compiler/intersectionOfCallsWithSameParameters.ts:38:46: 'two' is declared here.
49+
const differentNumberOfParameters: { one: number, two: number } = i.differentNumberOfParameters('test');
50+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
51+
!!! error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
52+
!!! related TS2728 tests/cases/compiler/intersectionOfCallsWithSameParameters.ts:39:51: 'two' is declared here.
53+
const differentTypeParameterDefault: { one: number, two: number } = i.differentTypeParameterDefault('test');
54+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55+
!!! error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
56+
!!! related TS2728 tests/cases/compiler/intersectionOfCallsWithSameParameters.ts:40:53: 'two' is declared here.
57+
const differentTypeParameterConstraint: { one: number, two: number } = i.differentTypeParameterConstraint<{ one: number }>('test');
58+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59+
!!! error TS2741: Property 'two' is missing in type '{ one: number; }' but required in type '{ one: number; two: number; }'.
60+
!!! related TS2728 tests/cases/compiler/intersectionOfCallsWithSameParameters.ts:41:56: 'two' is declared here.
61+
Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,59 @@
11
//// [intersectionOfCallsWithSameParameters.ts]
22
interface One {
3-
overload(id: string): { one: number };
4-
intersect(id: string): { one: number };
3+
differentParameterType(id: string): { one: number };
4+
differentNumberOfParameters(id: string): { one: number };
5+
differentTypeParameterDefault<T = number>(id: string): { one: number };
6+
differentTypeParameterConstraint<T extends { one: number }>(id: string): { one: number };
7+
8+
same1(id: string): { one: number };
9+
same2<T>(id: string): { one: number };
10+
same3<T extends { one: number }>(id: string): { one: number };
11+
same4<T = number>(id: string): { one: number };
12+
same5<T1 extends { one: number }, T2 = number>(id: string): { one: number };
513
}
614

715
interface Two {
8-
overload(id: number): { two: number };
9-
intersect(id: string): { two: number };
16+
differentParameterType(id: number): { two: number };
17+
differentNumberOfParameters(id: string, second: string): { two: number };
18+
differentTypeParameterDefault<T = string>(id: string): { two: number };
19+
differentTypeParameterConstraint<T extends { two: number }>(id: string): { two: number };
20+
21+
same1(id: string): { two: number };
22+
same2<T>(id: string): { two: number };
23+
same3<T extends { one: number }>(id: string): { two: number };
24+
same4<T = number>(id: string): { two: number };
25+
same5<T1 extends { one: number }, T2 = number>(id: string): { two: number };
1026
}
1127

12-
class Both implements One, Two {
13-
overload(id: number): { two: number };
14-
overload(id: string): { one: number };
15-
overload(id: string | number): { one: number, two: number } {
16-
return {
17-
one: 1,
18-
two: 2
19-
};
20-
}
28+
const i: One & Two = <any>{};
2129

22-
intersect(id: string): { one: number, two: number } {
23-
return {
24-
one: 1,
25-
two: 2
26-
};
27-
}
28-
}
30+
// These lines should type check; the return type should be intersected.
31+
const same1: { one: number, two: number } = i.same1('test');
32+
const same2: { one: number, two: number } = i.same2<number>('test');
33+
const same3: { one: number, two: number } = i.same3<{ one:number }>('test');
34+
const same4: { one: number, two: number } = i.same4('test');
35+
const same5: { one: number, two: number } = i.same5<{ one:number }, string>('test');
2936

30-
const b = new Both();
31-
const intersect: { one: number, two: number } = b.intersect('test');
32-
const overloadA: { one: number } = b.overload('test');
33-
const overloadB: { two: number } = b.overload(4);
34-
const bAs: One & Two = b;
35-
const asIntersect: { one: number, two: number } = bAs.intersect('test');
36-
const asOverloadA: { one: number } = bAs.overload('test');
37-
const asOverloadB: { two: number } = bAs.overload(4);
37+
// These lines should not, because the functions should become overloads rather
38+
// than the return types intersected.
39+
const differentParameterType: { one: number, two: number } = i.differentParameterType('test');
40+
const differentNumberOfParameters: { one: number, two: number } = i.differentNumberOfParameters('test');
41+
const differentTypeParameterDefault: { one: number, two: number } = i.differentTypeParameterDefault('test');
42+
const differentTypeParameterConstraint: { one: number, two: number } = i.differentTypeParameterConstraint<{ one: number }>('test');
3843

3944

4045
//// [intersectionOfCallsWithSameParameters.js]
4146
"use strict";
42-
var Both = /** @class */ (function () {
43-
function Both() {
44-
}
45-
Both.prototype.overload = function (id) {
46-
return {
47-
one: 1,
48-
two: 2
49-
};
50-
};
51-
Both.prototype.intersect = function (id) {
52-
return {
53-
one: 1,
54-
two: 2
55-
};
56-
};
57-
return Both;
58-
}());
59-
var b = new Both();
60-
var intersect = b.intersect('test');
61-
var overloadA = b.overload('test');
62-
var overloadB = b.overload(4);
63-
var bAs = b;
64-
var asIntersect = bAs.intersect('test');
65-
var asOverloadA = bAs.overload('test');
66-
var asOverloadB = bAs.overload(4);
47+
var i = {};
48+
// These lines should type check; the return type should be intersected.
49+
var same1 = i.same1('test');
50+
var same2 = i.same2('test');
51+
var same3 = i.same3('test');
52+
var same4 = i.same4('test');
53+
var same5 = i.same5('test');
54+
// These lines should not, because the functions should become overloads rather
55+
// than the return types intersected.
56+
var differentParameterType = i.differentParameterType('test');
57+
var differentNumberOfParameters = i.differentNumberOfParameters('test');
58+
var differentTypeParameterDefault = i.differentTypeParameterDefault('test');
59+
var differentTypeParameterConstraint = i.differentTypeParameterConstraint('test');

0 commit comments

Comments
 (0)