Skip to content

Commit c3b4f72

Browse files
authored
Improve indexed access inferences (#27015)
1 parent bc709a8 commit c3b4f72

File tree

5 files changed

+207
-2
lines changed

5 files changed

+207
-2
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13373,7 +13373,7 @@ namespace ts {
1337313373
let propagationType: Type;
1337413374
inferFromTypes(originalSource, originalTarget);
1337513375

13376-
function inferFromTypes(source: Type, target: Type) {
13376+
function inferFromTypes(source: Type, target: Type): void {
1337713377
if (!couldContainTypeVariables(target)) {
1337813378
return;
1337913379
}
@@ -13541,7 +13541,14 @@ namespace ts {
1354113541
}
1354213542
else {
1354313543
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
13544-
source = getApparentType(source);
13544+
const apparentSource = getApparentType(source);
13545+
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
13546+
// If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes`
13547+
// with the simplified source.
13548+
if (apparentSource !== source && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
13549+
return inferFromTypes(apparentSource, target);
13550+
}
13551+
source = apparentSource;
1354513552
}
1354613553
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
1354713554
const key = source.id + "," + target.id;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [voidReturnIndexUnionInference.ts]
2+
// repro from https://github.com/Microsoft/TypeScript/issues/25274
3+
export function safeInvoke<A1, R>(
4+
func: ((arg1: A1) => R) | null | undefined,
5+
arg1: A1
6+
): R | undefined {
7+
if (func) {
8+
return func(arg1);
9+
} else {
10+
return undefined;
11+
}
12+
}
13+
14+
interface Props {
15+
onFoo?(value: string): boolean;
16+
onBar?(value: string): void;
17+
}
18+
19+
function bad<P extends Props>(props: Readonly<P>) {
20+
safeInvoke(props.onFoo, "blah");
21+
// ERROR HERE!!!
22+
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
23+
safeInvoke(props.onBar, "blah");
24+
}
25+
26+
27+
//// [voidReturnIndexUnionInference.js]
28+
"use strict";
29+
exports.__esModule = true;
30+
// repro from https://github.com/Microsoft/TypeScript/issues/25274
31+
function safeInvoke(func, arg1) {
32+
if (func) {
33+
return func(arg1);
34+
}
35+
else {
36+
return undefined;
37+
}
38+
}
39+
exports.safeInvoke = safeInvoke;
40+
function bad(props) {
41+
safeInvoke(props.onFoo, "blah");
42+
// ERROR HERE!!!
43+
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
44+
safeInvoke(props.onBar, "blah");
45+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=== tests/cases/compiler/voidReturnIndexUnionInference.ts ===
2+
// repro from https://github.com/Microsoft/TypeScript/issues/25274
3+
export function safeInvoke<A1, R>(
4+
>safeInvoke : Symbol(safeInvoke, Decl(voidReturnIndexUnionInference.ts, 0, 0))
5+
>A1 : Symbol(A1, Decl(voidReturnIndexUnionInference.ts, 1, 27))
6+
>R : Symbol(R, Decl(voidReturnIndexUnionInference.ts, 1, 30))
7+
8+
func: ((arg1: A1) => R) | null | undefined,
9+
>func : Symbol(func, Decl(voidReturnIndexUnionInference.ts, 1, 34))
10+
>arg1 : Symbol(arg1, Decl(voidReturnIndexUnionInference.ts, 2, 12))
11+
>A1 : Symbol(A1, Decl(voidReturnIndexUnionInference.ts, 1, 27))
12+
>R : Symbol(R, Decl(voidReturnIndexUnionInference.ts, 1, 30))
13+
14+
arg1: A1
15+
>arg1 : Symbol(arg1, Decl(voidReturnIndexUnionInference.ts, 2, 47))
16+
>A1 : Symbol(A1, Decl(voidReturnIndexUnionInference.ts, 1, 27))
17+
18+
): R | undefined {
19+
>R : Symbol(R, Decl(voidReturnIndexUnionInference.ts, 1, 30))
20+
21+
if (func) {
22+
>func : Symbol(func, Decl(voidReturnIndexUnionInference.ts, 1, 34))
23+
24+
return func(arg1);
25+
>func : Symbol(func, Decl(voidReturnIndexUnionInference.ts, 1, 34))
26+
>arg1 : Symbol(arg1, Decl(voidReturnIndexUnionInference.ts, 2, 47))
27+
28+
} else {
29+
return undefined;
30+
>undefined : Symbol(undefined)
31+
}
32+
}
33+
34+
interface Props {
35+
>Props : Symbol(Props, Decl(voidReturnIndexUnionInference.ts, 10, 1))
36+
37+
onFoo?(value: string): boolean;
38+
>onFoo : Symbol(Props.onFoo, Decl(voidReturnIndexUnionInference.ts, 12, 17))
39+
>value : Symbol(value, Decl(voidReturnIndexUnionInference.ts, 13, 11))
40+
41+
onBar?(value: string): void;
42+
>onBar : Symbol(Props.onBar, Decl(voidReturnIndexUnionInference.ts, 13, 35))
43+
>value : Symbol(value, Decl(voidReturnIndexUnionInference.ts, 14, 11))
44+
}
45+
46+
function bad<P extends Props>(props: Readonly<P>) {
47+
>bad : Symbol(bad, Decl(voidReturnIndexUnionInference.ts, 15, 1))
48+
>P : Symbol(P, Decl(voidReturnIndexUnionInference.ts, 17, 13))
49+
>Props : Symbol(Props, Decl(voidReturnIndexUnionInference.ts, 10, 1))
50+
>props : Symbol(props, Decl(voidReturnIndexUnionInference.ts, 17, 30))
51+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
52+
>P : Symbol(P, Decl(voidReturnIndexUnionInference.ts, 17, 13))
53+
54+
safeInvoke(props.onFoo, "blah");
55+
>safeInvoke : Symbol(safeInvoke, Decl(voidReturnIndexUnionInference.ts, 0, 0))
56+
>props.onFoo : Symbol(onFoo, Decl(voidReturnIndexUnionInference.ts, 12, 17))
57+
>props : Symbol(props, Decl(voidReturnIndexUnionInference.ts, 17, 30))
58+
>onFoo : Symbol(onFoo, Decl(voidReturnIndexUnionInference.ts, 12, 17))
59+
60+
// ERROR HERE!!!
61+
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
62+
safeInvoke(props.onBar, "blah");
63+
>safeInvoke : Symbol(safeInvoke, Decl(voidReturnIndexUnionInference.ts, 0, 0))
64+
>props.onBar : Symbol(onBar, Decl(voidReturnIndexUnionInference.ts, 13, 35))
65+
>props : Symbol(props, Decl(voidReturnIndexUnionInference.ts, 17, 30))
66+
>onBar : Symbol(onBar, Decl(voidReturnIndexUnionInference.ts, 13, 35))
67+
}
68+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/compiler/voidReturnIndexUnionInference.ts ===
2+
// repro from https://github.com/Microsoft/TypeScript/issues/25274
3+
export function safeInvoke<A1, R>(
4+
>safeInvoke : <A1, R>(func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined
5+
6+
func: ((arg1: A1) => R) | null | undefined,
7+
>func : ((arg1: A1) => R) | null | undefined
8+
>arg1 : A1
9+
>null : null
10+
11+
arg1: A1
12+
>arg1 : A1
13+
14+
): R | undefined {
15+
if (func) {
16+
>func : ((arg1: A1) => R) | null | undefined
17+
18+
return func(arg1);
19+
>func(arg1) : R
20+
>func : (arg1: A1) => R
21+
>arg1 : A1
22+
23+
} else {
24+
return undefined;
25+
>undefined : undefined
26+
}
27+
}
28+
29+
interface Props {
30+
onFoo?(value: string): boolean;
31+
>onFoo : ((value: string) => boolean) | undefined
32+
>value : string
33+
34+
onBar?(value: string): void;
35+
>onBar : ((value: string) => void) | undefined
36+
>value : string
37+
}
38+
39+
function bad<P extends Props>(props: Readonly<P>) {
40+
>bad : <P extends Props>(props: Readonly<P>) => void
41+
>props : Readonly<P>
42+
43+
safeInvoke(props.onFoo, "blah");
44+
>safeInvoke(props.onFoo, "blah") : boolean | undefined
45+
>safeInvoke : <A1, R>(func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined
46+
>props.onFoo : P["onFoo"]
47+
>props : Readonly<P>
48+
>onFoo : P["onFoo"]
49+
>"blah" : "blah"
50+
51+
// ERROR HERE!!!
52+
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
53+
safeInvoke(props.onBar, "blah");
54+
>safeInvoke(props.onBar, "blah") : void | undefined
55+
>safeInvoke : <A1, R>(func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined
56+
>props.onBar : P["onBar"]
57+
>props : Readonly<P>
58+
>onBar : P["onBar"]
59+
>"blah" : "blah"
60+
}
61+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @strict: true
2+
// repro from https://github.com/Microsoft/TypeScript/issues/25274
3+
export function safeInvoke<A1, R>(
4+
func: ((arg1: A1) => R) | null | undefined,
5+
arg1: A1
6+
): R | undefined {
7+
if (func) {
8+
return func(arg1);
9+
} else {
10+
return undefined;
11+
}
12+
}
13+
14+
interface Props {
15+
onFoo?(value: string): boolean;
16+
onBar?(value: string): void;
17+
}
18+
19+
function bad<P extends Props>(props: Readonly<P>) {
20+
safeInvoke(props.onFoo, "blah");
21+
// ERROR HERE!!!
22+
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
23+
safeInvoke(props.onBar, "blah");
24+
}

0 commit comments

Comments
 (0)