Skip to content

Commit f89165d

Browse files
authored
Merge pull request microsoft#32049 from microsoft/noDuplicateIntersectionSignatures
Remove duplicate signatures in intersections
2 parents 410b717 + 10c9fbc commit f89165d

12 files changed

+230
-17
lines changed

src/compiler/checker.ts

+29-16
Original file line numberDiff line numberDiff line change
@@ -7234,8 +7234,8 @@ namespace ts {
72347234
function resolveIntersectionTypeMembers(type: IntersectionType) {
72357235
// The members and properties collections are empty for intersection types. To get all properties of an
72367236
// intersection type use getPropertiesOfType (only the language service uses this).
7237-
let callSignatures: ReadonlyArray<Signature> = emptyArray;
7238-
let constructSignatures: ReadonlyArray<Signature> = emptyArray;
7237+
let callSignatures: Signature[] | undefined;
7238+
let constructSignatures: Signature[] | undefined;
72397239
let stringIndexInfo: IndexInfo | undefined;
72407240
let numberIndexInfo: IndexInfo | undefined;
72417241
const types = type.types;
@@ -7257,13 +7257,22 @@ namespace ts {
72577257
return clone;
72587258
});
72597259
}
7260-
constructSignatures = concatenate(constructSignatures, signatures);
7260+
constructSignatures = appendSignatures(constructSignatures, signatures);
72617261
}
7262-
callSignatures = concatenate(callSignatures, getSignaturesOfType(t, SignatureKind.Call));
7262+
callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call));
72637263
stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String));
72647264
numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number));
72657265
}
7266-
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
7266+
setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo);
7267+
}
7268+
7269+
function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) {
7270+
for (const sig of newSignatures) {
7271+
if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) {
7272+
signatures = append(signatures, sig);
7273+
}
7274+
}
7275+
return signatures;
72677276
}
72687277

72697278
/**
@@ -14325,20 +14334,25 @@ namespace ts {
1432514334
if (!(isMatchingSignature(source, target, partialMatch))) {
1432614335
return Ternary.False;
1432714336
}
14328-
// Check that the two signatures have the same number of type parameters. We might consider
14329-
// also checking that any type parameter constraints match, but that would require instantiating
14330-
// the constraints with a common set of type arguments to get relatable entities in places where
14331-
// type parameters occur in the constraints. The complexity of doing that doesn't seem worthwhile,
14332-
// particularly as we're comparing erased versions of the signatures below.
14337+
// Check that the two signatures have the same number of type parameters.
1433314338
if (length(source.typeParameters) !== length(target.typeParameters)) {
1433414339
return Ternary.False;
1433514340
}
14336-
// Spec 1.0 Section 3.8.3 & 3.8.4:
14337-
// M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N
14338-
source = getErasedSignature(source);
14339-
target = getErasedSignature(target);
14341+
// Check that type parameter constraints and defaults match. If they do, instantiate the source
14342+
// signature with the type parameters of the target signature and continue the comparison.
14343+
if (target.typeParameters) {
14344+
const mapper = createTypeMapper(source.typeParameters!, target.typeParameters);
14345+
for (let i = 0; i < target.typeParameters.length; i++) {
14346+
const s = source.typeParameters![i];
14347+
const t = target.typeParameters[i];
14348+
if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) &&
14349+
compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) {
14350+
return Ternary.False;
14351+
}
14352+
}
14353+
source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true);
14354+
}
1434014355
let result = Ternary.True;
14341-
1434214356
if (!ignoreThisTypes) {
1434314357
const sourceThisType = getThisTypeOfSignature(source);
1434414358
if (sourceThisType) {
@@ -14352,7 +14366,6 @@ namespace ts {
1435214366
}
1435314367
}
1435414368
}
14355-
1435614369
const targetLen = getParameterCount(target);
1435714370
for (let i = 0; i < targetLen; i++) {
1435814371
const s = getTypeAtPosition(source, i);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
tests/cases/compiler/genericSignatureIdentity.ts(10,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T extends number>(x: T) => T'.
2+
tests/cases/compiler/genericSignatureIdentity.ts(14,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T>(x: T) => T'.
3+
tests/cases/compiler/genericSignatureIdentity.ts(18,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T>(x: any) => any'.
4+
5+
6+
==== tests/cases/compiler/genericSignatureIdentity.ts (3 errors) ====
7+
// This test is here to remind us of our current limits of type identity checking.
8+
// Ideally all of the below declarations would be considered different (and thus errors)
9+
// but they aren't because we erase type parameters to type any and don't check that
10+
// constraints are identical.
11+
12+
var x: {
13+
<T extends Date>(x: T): T;
14+
};
15+
16+
var x: {
17+
~
18+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T extends number>(x: T) => T'.
19+
!!! related TS6203 tests/cases/compiler/genericSignatureIdentity.ts:6:5: 'x' was also declared here.
20+
<T extends number>(x: T): T;
21+
};
22+
23+
var x: {
24+
~
25+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T>(x: T) => T'.
26+
!!! related TS6203 tests/cases/compiler/genericSignatureIdentity.ts:6:5: 'x' was also declared here.
27+
<T>(x: T): T;
28+
};
29+
30+
var x: {
31+
~
32+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '<T extends Date>(x: T) => T', but here has type '<T>(x: any) => any'.
33+
!!! related TS6203 tests/cases/compiler/genericSignatureIdentity.ts:6:5: 'x' was also declared here.
34+
<T>(x: any): any;
35+
};
36+

tests/baselines/reference/identityForSignaturesWithTypeParametersAndAny.errors.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts(2,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'f' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: any, y: any) => any'.
12
tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts(5,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'g' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T>(x: any, y: any) => any'.
23
tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts(8,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'h' must be of type '<T, U>(x: T, y: U) => T', but here has type '(x: any, y: any) => any'.
34
tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts(11,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'i' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: any, y: string) => any'.
45
tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts(14,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'j' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: any, y: any) => string'.
56

67

7-
==== tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts (4 errors) ====
8+
==== tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts (5 errors) ====
89
var f: <T, U>(x: T, y: U) => T;
910
var f: <T, U>(x: any, y: any) => any;
11+
~
12+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'f' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: any, y: any) => any'.
13+
!!! related TS6203 tests/cases/compiler/identityForSignaturesWithTypeParametersAndAny.ts:1:5: 'f' was also declared here.
1014

1115
var g: <T, U>(x: T, y: U) => T;
1216
var g: <T>(x: any, y: any) => any;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests/cases/compiler/identityForSignaturesWithTypeParametersSwitched.ts(2,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'f' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: U, y: T) => U'.
2+
3+
4+
==== tests/cases/compiler/identityForSignaturesWithTypeParametersSwitched.ts (1 errors) ====
5+
var f: <T, U>(x: T, y: U) => T;
6+
var f: <T, U>(x: U, y: T) => U;
7+
~
8+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'f' must be of type '<T, U>(x: T, y: U) => T', but here has type '<T, U>(x: U, y: T) => U'.
9+
!!! related TS6203 tests/cases/compiler/identityForSignaturesWithTypeParametersSwitched.ts:1:5: 'f' was also declared here.

tests/baselines/reference/keyofAndIndexedAccess2.errors.txt

+9
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,13 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(108,5): error TS23
237237
type Baz<T, Q extends Foo<T>> = { [K in keyof Q]: T[Q[K]] };
238238

239239
type Qux<T, Q extends Bar<T>> = { [K in keyof Q]: T[Q[K]["0"]] };
240+
241+
// Repro from #32038
242+
243+
const actions = ['resizeTo', 'resizeBy'] as const;
244+
for (const action of actions) {
245+
window[action] = (x, y) => {
246+
window[action](x, y);
247+
}
248+
}
240249

tests/baselines/reference/keyofAndIndexedAccess2.js

+16
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,15 @@ type Bar<T> = { [key: string]: { [K in keyof T]: [K] }[keyof T] };
156156
type Baz<T, Q extends Foo<T>> = { [K in keyof Q]: T[Q[K]] };
157157

158158
type Qux<T, Q extends Bar<T>> = { [K in keyof Q]: T[Q[K]["0"]] };
159+
160+
// Repro from #32038
161+
162+
const actions = ['resizeTo', 'resizeBy'] as const;
163+
for (const action of actions) {
164+
window[action] = (x, y) => {
165+
window[action](x, y);
166+
}
167+
}
159168

160169

161170
//// [keyofAndIndexedAccess2.js]
@@ -253,3 +262,10 @@ export class c {
253262
this["a"] = "b";
254263
}
255264
}
265+
// Repro from #32038
266+
const actions = ['resizeTo', 'resizeBy'];
267+
for (const action of actions) {
268+
window[action] = (x, y) => {
269+
window[action](x, y);
270+
};
271+
}

tests/baselines/reference/keyofAndIndexedAccess2.symbols

+23
Original file line numberDiff line numberDiff line change
@@ -564,3 +564,26 @@ type Qux<T, Q extends Bar<T>> = { [K in keyof Q]: T[Q[K]["0"]] };
564564
>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 156, 11))
565565
>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 156, 35))
566566

567+
// Repro from #32038
568+
569+
const actions = ['resizeTo', 'resizeBy'] as const;
570+
>actions : Symbol(actions, Decl(keyofAndIndexedAccess2.ts, 160, 5))
571+
572+
for (const action of actions) {
573+
>action : Symbol(action, Decl(keyofAndIndexedAccess2.ts, 161, 10))
574+
>actions : Symbol(actions, Decl(keyofAndIndexedAccess2.ts, 160, 5))
575+
576+
window[action] = (x, y) => {
577+
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
578+
>action : Symbol(action, Decl(keyofAndIndexedAccess2.ts, 161, 10))
579+
>x : Symbol(x, Decl(keyofAndIndexedAccess2.ts, 162, 19))
580+
>y : Symbol(y, Decl(keyofAndIndexedAccess2.ts, 162, 21))
581+
582+
window[action](x, y);
583+
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
584+
>action : Symbol(action, Decl(keyofAndIndexedAccess2.ts, 161, 10))
585+
>x : Symbol(x, Decl(keyofAndIndexedAccess2.ts, 162, 19))
586+
>y : Symbol(y, Decl(keyofAndIndexedAccess2.ts, 162, 21))
587+
}
588+
}
589+

tests/baselines/reference/keyofAndIndexedAccess2.types

+32
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,35 @@ type Baz<T, Q extends Foo<T>> = { [K in keyof Q]: T[Q[K]] };
540540
type Qux<T, Q extends Bar<T>> = { [K in keyof Q]: T[Q[K]["0"]] };
541541
>Qux : Qux<T, Q>
542542

543+
// Repro from #32038
544+
545+
const actions = ['resizeTo', 'resizeBy'] as const;
546+
>actions : readonly ["resizeTo", "resizeBy"]
547+
>['resizeTo', 'resizeBy'] as const : readonly ["resizeTo", "resizeBy"]
548+
>['resizeTo', 'resizeBy'] : readonly ["resizeTo", "resizeBy"]
549+
>'resizeTo' : "resizeTo"
550+
>'resizeBy' : "resizeBy"
551+
552+
for (const action of actions) {
553+
>action : "resizeTo" | "resizeBy"
554+
>actions : readonly ["resizeTo", "resizeBy"]
555+
556+
window[action] = (x, y) => {
557+
>window[action] = (x, y) => { window[action](x, y); } : (x: number, y: number) => void
558+
>window[action] : ((x: number, y: number) => void) & ((x: number, y: number) => void)
559+
>window : Window
560+
>action : "resizeTo" | "resizeBy"
561+
>(x, y) => { window[action](x, y); } : (x: number, y: number) => void
562+
>x : number
563+
>y : number
564+
565+
window[action](x, y);
566+
>window[action](x, y) : void
567+
>window[action] : ((x: number, y: number) => void) | ((x: number, y: number) => void)
568+
>window : Window
569+
>action : "resizeTo" | "resizeBy"
570+
>x : number
571+
>y : number
572+
}
573+
}
574+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
tests/cases/compiler/promiseIdentity.ts(21,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'y' must be of type 'IPromise2<string, number>', but here has type 'Promise2<any, string>'.
2+
3+
4+
==== tests/cases/compiler/promiseIdentity.ts (1 errors) ====
5+
export interface IPromise<T> {
6+
then<U>(callback: (x: T) => IPromise<U>): IPromise<U>;
7+
}
8+
interface Promise<T> {
9+
then<U>(callback: (x: T) => Promise<U>): Promise<U>;
10+
}
11+
var x: IPromise<string>;
12+
var x: Promise<string>;
13+
14+
15+
interface IPromise2<T, V> {
16+
then<U, W>(callback: (x: T) => IPromise2<U, W>): IPromise2<W, U>;
17+
}
18+
interface Promise2<T, V> {
19+
then<U, W>(callback: (x: V) => Promise2<U, T>): Promise2<T, W>; // Uses V instead of T in callback's parameter
20+
}
21+
22+
// Ok because T in this particular Promise2 is any, as are all the U and W references.
23+
// Also, the V of Promise2 happens to coincide with the T of IPromise2 (they are both string).
24+
var y: IPromise2<string, number>;
25+
var y: Promise2<any, string>;
26+
~
27+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'y' must be of type 'IPromise2<string, number>', but here has type 'Promise2<any, string>'.
28+
!!! related TS6203 tests/cases/compiler/promiseIdentity.ts:20:5: 'y' was also declared here.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/compiler/promiseIdentityWithAny.ts(10,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'IPromise<string, number>', but here has type 'Promise<string, boolean>'.
2+
3+
4+
==== tests/cases/compiler/promiseIdentityWithAny.ts (1 errors) ====
5+
export interface IPromise<T, V> {
6+
then<U, W>(callback: (x: T) => IPromise<U, W>): IPromise<U, W>;
7+
}
8+
export interface Promise<T, V> {
9+
then<U, W>(callback: (x: T) => Promise<any, any>): Promise<any, any>;
10+
}
11+
12+
// Should be ok because signature type parameters get erased to any
13+
var x: IPromise<string, number>;
14+
var x: Promise<string, boolean>;
15+
~
16+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'IPromise<string, number>', but here has type 'Promise<string, boolean>'.
17+
!!! related TS6203 tests/cases/compiler/promiseIdentityWithAny.ts:9:5: 'x' was also declared here.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/compiler/promiseIdentityWithConstraints.ts(10,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'IPromise<string, number>', but here has type 'Promise<string, boolean>'.
2+
3+
4+
==== tests/cases/compiler/promiseIdentityWithConstraints.ts (1 errors) ====
5+
export interface IPromise<T, V> {
6+
then<U extends T, W extends V>(callback: (x: T) => IPromise<U, W>): IPromise<U, W>;
7+
}
8+
export interface Promise<T, V> {
9+
then<U extends T, W extends V>(callback: (x: T) => Promise<U, W>): Promise<U, W>;
10+
}
11+
12+
// Error because constraint V doesn't match
13+
var x: IPromise<string, number>;
14+
var x: Promise<string, boolean>;
15+
~
16+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'IPromise<string, number>', but here has type 'Promise<string, boolean>'.
17+
!!! related TS6203 tests/cases/compiler/promiseIdentityWithConstraints.ts:9:5: 'x' was also declared here.

tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts

+9
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,12 @@ type Bar<T> = { [key: string]: { [K in keyof T]: [K] }[keyof T] };
158158
type Baz<T, Q extends Foo<T>> = { [K in keyof Q]: T[Q[K]] };
159159

160160
type Qux<T, Q extends Bar<T>> = { [K in keyof Q]: T[Q[K]["0"]] };
161+
162+
// Repro from #32038
163+
164+
const actions = ['resizeTo', 'resizeBy'] as const;
165+
for (const action of actions) {
166+
window[action] = (x, y) => {
167+
window[action](x, y);
168+
}
169+
}

0 commit comments

Comments
 (0)