Skip to content

Commit 3d09010

Browse files
authored
Intersect 'this' types in union signatures (#32538)
* Intersect this types in union signatures * Actually update baselines
1 parent 2a4930f commit 3d09010

13 files changed

+626
-13
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7181,8 +7181,9 @@ namespace ts {
71817181
}
71827182
let result: Signature[] | undefined;
71837183
for (let i = 0; i < signatureLists.length; i++) {
7184-
// Allow matching non-generic signatures to have excess parameters and different return types
7185-
const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true);
7184+
// Allow matching non-generic signatures to have excess parameters and different return types.
7185+
// Prefer matching this types if possible.
7186+
const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true);
71867187
if (!match) {
71877188
return undefined;
71887189
}
@@ -7205,7 +7206,7 @@ namespace ts {
72057206
}
72067207
for (const signature of signatureLists[i]) {
72077208
// Only process signatures with parameter lists that aren't already in the result list
7208-
if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) {
7209+
if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) {
72097210
const unionSignatures = findMatchingSignatures(signatureLists, signature, i);
72107211
if (unionSignatures) {
72117212
let s = signature;
@@ -7214,7 +7215,7 @@ namespace ts {
72147215
let thisParameter = signature.thisParameter;
72157216
const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter);
72167217
if (firstThisParameterOfUnionSignatures) {
7217-
const thisType = getUnionType(map(unionSignatures, sig => sig.thisParameter ? getTypeOfSymbol(sig.thisParameter) : anyType), UnionReduction.Subtype);
7218+
const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter)));
72187219
thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType);
72197220
}
72207221
s = createUnionSignature(signature, unionSignatures);
@@ -7253,8 +7254,8 @@ namespace ts {
72537254
}
72547255
// A signature `this` type might be a read or a write position... It's very possible that it should be invariant
72557256
// and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
7256-
// permissive when calling, for now, we'll union the `this` types just like the overlapping-union-signature check does
7257-
const thisType = getUnionType([getTypeOfSymbol(left), getTypeOfSymbol(right)], UnionReduction.Subtype);
7257+
// permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures.
7258+
const thisType = getIntersectionType([getTypeOfSymbol(left), getTypeOfSymbol(right)]);
72587259
return createSymbolWithType(left, thisType);
72597260
}
72607261

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts(10,5): error TS2684: The 'this' context of type 'Real | Fake' is not assignable to method's 'this' of type 'Real & Fake'.
2+
Type 'Real' is not assignable to type 'Real & Fake'.
3+
Type 'Real' is not assignable to type 'Fake'.
4+
Types of property 'method' are incompatible.
5+
Type '(this: Real, n: number) => void' is not assignable to type '(this: Fake, n: number) => void'.
6+
The 'this' types of each signature are incompatible.
7+
Type 'Fake' is not assignable to type 'Real'.
8+
Types of property 'data' are incompatible.
9+
Type 'number' is not assignable to type 'string'.
10+
11+
12+
==== tests/cases/conformance/types/thisType/unionThisTypeInFunctions.ts (1 errors) ====
13+
interface Real {
14+
method(this: this, n: number): void;
15+
data: string;
16+
}
17+
interface Fake {
18+
method(this: this, n: number): void;
19+
data: number;
20+
}
21+
function test(r: Real | Fake) {
22+
r.method(12); // error
23+
~
24+
!!! error TS2684: The 'this' context of type 'Real | Fake' is not assignable to method's 'this' of type 'Real & Fake'.
25+
!!! error TS2684: Type 'Real' is not assignable to type 'Real & Fake'.
26+
!!! error TS2684: Type 'Real' is not assignable to type 'Fake'.
27+
!!! error TS2684: Types of property 'method' are incompatible.
28+
!!! error TS2684: Type '(this: Real, n: number) => void' is not assignable to type '(this: Fake, n: number) => void'.
29+
!!! error TS2684: The 'this' types of each signature are incompatible.
30+
!!! error TS2684: Type 'Fake' is not assignable to type 'Real'.
31+
!!! error TS2684: Types of property 'data' are incompatible.
32+
!!! error TS2684: Type 'number' is not assignable to type 'string'.
33+
}
34+

tests/baselines/reference/unionThisTypeInFunctions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ interface Fake {
88
data: number;
99
}
1010
function test(r: Real | Fake) {
11-
r.method(12);
11+
r.method(12); // error
1212
}
1313

1414

1515
//// [unionThisTypeInFunctions.js]
1616
function test(r) {
17-
r.method(12);
17+
r.method(12); // error
1818
}

tests/baselines/reference/unionThisTypeInFunctions.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function test(r: Real | Fake) {
2727
>Real : Symbol(Real, Decl(unionThisTypeInFunctions.ts, 0, 0))
2828
>Fake : Symbol(Fake, Decl(unionThisTypeInFunctions.ts, 3, 1))
2929

30-
r.method(12);
30+
r.method(12); // error
3131
>r.method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 0, 16), Decl(unionThisTypeInFunctions.ts, 4, 16))
3232
>r : Symbol(r, Decl(unionThisTypeInFunctions.ts, 8, 14))
3333
>method : Symbol(method, Decl(unionThisTypeInFunctions.ts, 0, 16), Decl(unionThisTypeInFunctions.ts, 4, 16))

tests/baselines/reference/unionThisTypeInFunctions.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ function test(r: Real | Fake) {
2121
>test : (r: Real | Fake) => void
2222
>r : Real | Fake
2323

24-
r.method(12);
25-
>r.method(12) : void
24+
r.method(12); // error
25+
>r.method(12) : any
2626
>r.method : ((this: Real, n: number) => void) | ((this: Fake, n: number) => void)
2727
>r : Real | Fake
2828
>method : ((this: Real, n: number) => void) | ((this: Fake, n: number) => void)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/conformance/types/union/unionTypeCallSignatures5.ts(12,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'never'.
2+
3+
4+
==== tests/cases/conformance/types/union/unionTypeCallSignatures5.ts (1 errors) ====
5+
// #31485
6+
interface A {
7+
(this: void, b?: number): void;
8+
}
9+
interface B {
10+
(this: number, b?: number): void;
11+
}
12+
interface C {
13+
(i: number): void;
14+
}
15+
declare const fn: A | B | C;
16+
fn(0);
17+
~~~~~
18+
!!! error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'never'.
19+

tests/baselines/reference/unionTypeCallSignatures5.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ declare const fn: A | B | C;
1818
>fn : A | B | C
1919

2020
fn(0);
21-
>fn(0) : void
21+
>fn(0) : any
2222
>fn : A | B | C
2323
>0 : 0
2424

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(11,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
2+
Type 'void' is not assignable to type 'A'.
3+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(13,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A'.
4+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(38,4): error TS2349: This expression is not callable.
5+
Each member of the union type 'F3 | F4' has signatures, but none of those signatures are compatible with each other.
6+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(39,1): error TS2684: The 'this' context of type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' is not assignable to method's 'this' of type 'B'.
7+
Property 'b' is missing in type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' but required in type 'B'.
8+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(48,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
9+
Type 'void' is not assignable to type 'A'.
10+
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(55,1): error TS2769: No overload matches this call.
11+
Overload 1 of 2, '(this: A & B & C): void', gave the following error.
12+
The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B & C'.
13+
Type 'void' is not assignable to type 'A'.
14+
Overload 2 of 2, '(this: A & B): void', gave the following error.
15+
The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
16+
Type 'void' is not assignable to type 'A'.
17+
18+
19+
==== tests/cases/conformance/types/union/unionTypeCallSignatures6.ts (6 errors) ====
20+
type A = { a: string };
21+
type B = { b: number };
22+
type C = { c: string };
23+
type D = { d: number };
24+
type F0 = () => void;
25+
26+
// #31547
27+
type F1 = (this: A) => void;
28+
type F2 = (this: B) => void;
29+
declare var f1: F1 | F2;
30+
f1(); // error
31+
~~~~
32+
!!! error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
33+
!!! error TS2684: Type 'void' is not assignable to type 'A'.
34+
declare var f2: F0 | F1;
35+
f2(); // error
36+
~~~~
37+
!!! error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A'.
38+
39+
interface F3 {
40+
(this: A): void;
41+
(this: B): void;
42+
}
43+
interface F4 {
44+
(this: C): void;
45+
(this: D): void;
46+
}
47+
interface F5 {
48+
(this: C): void;
49+
(this: B): void;
50+
}
51+
52+
declare var x1: A & C & {
53+
f0: F0 | F3;
54+
f1: F1 | F3;
55+
f2: F1 | F4;
56+
f3: F3 | F4;
57+
f4: F3 | F5;
58+
}
59+
x1.f0();
60+
x1.f1();
61+
x1.f2();
62+
x1.f3(); // error
63+
~~
64+
!!! error TS2349: This expression is not callable.
65+
!!! error TS2349: Each member of the union type 'F3 | F4' has signatures, but none of those signatures are compatible with each other.
66+
x1.f4(); // error
67+
~~
68+
!!! error TS2684: The 'this' context of type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' is not assignable to method's 'this' of type 'B'.
69+
!!! error TS2684: Property 'b' is missing in type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' but required in type 'B'.
70+
!!! related TS2728 tests/cases/conformance/types/union/unionTypeCallSignatures6.ts:2:12: 'b' is declared here.
71+
72+
declare var x2: A & B & {
73+
f4: F3 | F5;
74+
}
75+
x2.f4();
76+
77+
type F6 = (this: A & B) => void;
78+
declare var f3: F1 | F6;
79+
f3(); // error
80+
~~~~
81+
!!! error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
82+
!!! error TS2684: Type 'void' is not assignable to type 'A'.
83+
84+
interface F7 {
85+
(this: A & B & C): void;
86+
(this: A & B): void;
87+
}
88+
declare var f4: F6 | F7;
89+
f4(); // error
90+
~~~~
91+
!!! error TS2769: No overload matches this call.
92+
!!! error TS2769: Overload 1 of 2, '(this: A & B & C): void', gave the following error.
93+
!!! error TS2769: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B & C'.
94+
!!! error TS2769: Type 'void' is not assignable to type 'A'.
95+
!!! error TS2769: Overload 2 of 2, '(this: A & B): void', gave the following error.
96+
!!! error TS2769: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
97+
!!! error TS2769: Type 'void' is not assignable to type 'A'.
98+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//// [unionTypeCallSignatures6.ts]
2+
type A = { a: string };
3+
type B = { b: number };
4+
type C = { c: string };
5+
type D = { d: number };
6+
type F0 = () => void;
7+
8+
// #31547
9+
type F1 = (this: A) => void;
10+
type F2 = (this: B) => void;
11+
declare var f1: F1 | F2;
12+
f1(); // error
13+
declare var f2: F0 | F1;
14+
f2(); // error
15+
16+
interface F3 {
17+
(this: A): void;
18+
(this: B): void;
19+
}
20+
interface F4 {
21+
(this: C): void;
22+
(this: D): void;
23+
}
24+
interface F5 {
25+
(this: C): void;
26+
(this: B): void;
27+
}
28+
29+
declare var x1: A & C & {
30+
f0: F0 | F3;
31+
f1: F1 | F3;
32+
f2: F1 | F4;
33+
f3: F3 | F4;
34+
f4: F3 | F5;
35+
}
36+
x1.f0();
37+
x1.f1();
38+
x1.f2();
39+
x1.f3(); // error
40+
x1.f4(); // error
41+
42+
declare var x2: A & B & {
43+
f4: F3 | F5;
44+
}
45+
x2.f4();
46+
47+
type F6 = (this: A & B) => void;
48+
declare var f3: F1 | F6;
49+
f3(); // error
50+
51+
interface F7 {
52+
(this: A & B & C): void;
53+
(this: A & B): void;
54+
}
55+
declare var f4: F6 | F7;
56+
f4(); // error
57+
58+
59+
//// [unionTypeCallSignatures6.js]
60+
f1(); // error
61+
f2(); // error
62+
x1.f0();
63+
x1.f1();
64+
x1.f2();
65+
x1.f3(); // error
66+
x1.f4(); // error
67+
x2.f4();
68+
f3(); // error
69+
f4(); // error

0 commit comments

Comments
 (0)