Skip to content

Commit 811a637

Browse files
authored
Fix issue with optional chaining and type inference in type guard (#55613)
1 parent 543d7ed commit 811a637

13 files changed

+565
-3
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26125,7 +26125,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2612526125
function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) {
2612626126
if (expression.arguments) {
2612726127
for (const argument of expression.arguments) {
26128-
if (isOrContainsMatchingReference(reference, argument)) {
26128+
if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) {
2612926129
return true;
2613026130
}
2613126131
}
@@ -28091,8 +28091,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2809128091
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
2809228092
}
2809328093
if (
28094-
strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
28095-
!(hasTypeFacts(predicate.type, TypeFacts.EQUndefined))
28094+
strictNullChecks && optionalChainContainsReference(predicateArgument, reference) &&
28095+
(
28096+
assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) ||
28097+
!assumeTrue && everyType(predicate.type, isNullableType)
28098+
)
2809628099
) {
2809728100
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2809828101
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining1.ts] ////
2+
3+
//// [typePredicatesOptionalChaining1.ts]
4+
type X = {
5+
y?: {
6+
z?: string;
7+
};
8+
};
9+
const x: X = {
10+
y: {},
11+
};
12+
// type guard
13+
function isNotNull<A>(x: A): x is NonNullable<A> {
14+
return x !== null && x !== undefined;
15+
}
16+
// function which I want to call in the result of the expression
17+
function title(str: string) {
18+
return str.length > 0 ? "Dear " + str : "Dear nobody";
19+
}
20+
21+
isNotNull(x?.y?.z) ? title(x.y.z) : null; // should not error
22+
23+
24+
//// [typePredicatesOptionalChaining1.js]
25+
"use strict";
26+
var _a;
27+
var x = {
28+
y: {},
29+
};
30+
// type guard
31+
function isNotNull(x) {
32+
return x !== null && x !== undefined;
33+
}
34+
// function which I want to call in the result of the expression
35+
function title(str) {
36+
return str.length > 0 ? "Dear " + str : "Dear nobody";
37+
}
38+
isNotNull((_a = x === null || x === void 0 ? void 0 : x.y) === null || _a === void 0 ? void 0 : _a.z) ? title(x.y.z) : null; // should not error
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining1.ts] ////
2+
3+
=== typePredicatesOptionalChaining1.ts ===
4+
type X = {
5+
>X : Symbol(X, Decl(typePredicatesOptionalChaining1.ts, 0, 0))
6+
7+
y?: {
8+
>y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 0, 10))
9+
10+
z?: string;
11+
>z : Symbol(z, Decl(typePredicatesOptionalChaining1.ts, 1, 7))
12+
13+
};
14+
};
15+
const x: X = {
16+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 5, 5))
17+
>X : Symbol(X, Decl(typePredicatesOptionalChaining1.ts, 0, 0))
18+
19+
y: {},
20+
>y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 5, 14))
21+
22+
};
23+
// type guard
24+
function isNotNull<A>(x: A): x is NonNullable<A> {
25+
>isNotNull : Symbol(isNotNull, Decl(typePredicatesOptionalChaining1.ts, 7, 2))
26+
>A : Symbol(A, Decl(typePredicatesOptionalChaining1.ts, 9, 19))
27+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 9, 22))
28+
>A : Symbol(A, Decl(typePredicatesOptionalChaining1.ts, 9, 19))
29+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 9, 22))
30+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
31+
>A : Symbol(A, Decl(typePredicatesOptionalChaining1.ts, 9, 19))
32+
33+
return x !== null && x !== undefined;
34+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 9, 22))
35+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 9, 22))
36+
>undefined : Symbol(undefined)
37+
}
38+
// function which I want to call in the result of the expression
39+
function title(str: string) {
40+
>title : Symbol(title, Decl(typePredicatesOptionalChaining1.ts, 11, 1))
41+
>str : Symbol(str, Decl(typePredicatesOptionalChaining1.ts, 13, 15))
42+
43+
return str.length > 0 ? "Dear " + str : "Dear nobody";
44+
>str.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
45+
>str : Symbol(str, Decl(typePredicatesOptionalChaining1.ts, 13, 15))
46+
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
47+
>str : Symbol(str, Decl(typePredicatesOptionalChaining1.ts, 13, 15))
48+
}
49+
50+
isNotNull(x?.y?.z) ? title(x.y.z) : null; // should not error
51+
>isNotNull : Symbol(isNotNull, Decl(typePredicatesOptionalChaining1.ts, 7, 2))
52+
>x?.y?.z : Symbol(z, Decl(typePredicatesOptionalChaining1.ts, 1, 7))
53+
>x?.y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 0, 10))
54+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 5, 5))
55+
>y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 0, 10))
56+
>z : Symbol(z, Decl(typePredicatesOptionalChaining1.ts, 1, 7))
57+
>title : Symbol(title, Decl(typePredicatesOptionalChaining1.ts, 11, 1))
58+
>x.y.z : Symbol(z, Decl(typePredicatesOptionalChaining1.ts, 1, 7))
59+
>x.y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 0, 10))
60+
>x : Symbol(x, Decl(typePredicatesOptionalChaining1.ts, 5, 5))
61+
>y : Symbol(y, Decl(typePredicatesOptionalChaining1.ts, 0, 10))
62+
>z : Symbol(z, Decl(typePredicatesOptionalChaining1.ts, 1, 7))
63+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining1.ts] ////
2+
3+
=== typePredicatesOptionalChaining1.ts ===
4+
type X = {
5+
>X : { y?: { z?: string | undefined; } | undefined; }
6+
7+
y?: {
8+
>y : { z?: string | undefined; } | undefined
9+
10+
z?: string;
11+
>z : string | undefined
12+
13+
};
14+
};
15+
const x: X = {
16+
>x : X
17+
>{ y: {},} : { y: {}; }
18+
19+
y: {},
20+
>y : {}
21+
>{} : {}
22+
23+
};
24+
// type guard
25+
function isNotNull<A>(x: A): x is NonNullable<A> {
26+
>isNotNull : <A>(x: A) => x is NonNullable<A>
27+
>x : A
28+
29+
return x !== null && x !== undefined;
30+
>x !== null && x !== undefined : boolean
31+
>x !== null : boolean
32+
>x : A
33+
>x !== undefined : boolean
34+
>x : A & ({} | undefined)
35+
>undefined : undefined
36+
}
37+
// function which I want to call in the result of the expression
38+
function title(str: string) {
39+
>title : (str: string) => string
40+
>str : string
41+
42+
return str.length > 0 ? "Dear " + str : "Dear nobody";
43+
>str.length > 0 ? "Dear " + str : "Dear nobody" : string
44+
>str.length > 0 : boolean
45+
>str.length : number
46+
>str : string
47+
>length : number
48+
>0 : 0
49+
>"Dear " + str : string
50+
>"Dear " : "Dear "
51+
>str : string
52+
>"Dear nobody" : "Dear nobody"
53+
}
54+
55+
isNotNull(x?.y?.z) ? title(x.y.z) : null; // should not error
56+
>isNotNull(x?.y?.z) ? title(x.y.z) : null : string | null
57+
>isNotNull(x?.y?.z) : boolean
58+
>isNotNull : <A>(x: A) => x is NonNullable<A>
59+
>x?.y?.z : string | undefined
60+
>x?.y : { z?: string | undefined; } | undefined
61+
>x : X
62+
>y : { z?: string | undefined; } | undefined
63+
>z : string | undefined
64+
>title(x.y.z) : string
65+
>title : (str: string) => string
66+
>x.y.z : string
67+
>x.y : { z?: string | undefined; }
68+
>x : X
69+
>y : { z?: string | undefined; }
70+
>z : string
71+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining2.ts] ////
2+
3+
//// [typePredicatesOptionalChaining2.ts]
4+
type Person = { name: string; }
5+
6+
const getName1 = (person?: Person): string => {
7+
return typeof person?.name === 'string' ? person?.name : '';
8+
};
9+
10+
const isString = (value: any): value is string => {
11+
return typeof value === 'string';
12+
};
13+
14+
const getName2 = (person?: Person): string => {
15+
return isString(person?.name) ? person?.name : '';
16+
};
17+
18+
19+
//// [typePredicatesOptionalChaining2.js]
20+
"use strict";
21+
var getName1 = function (person) {
22+
return typeof (person === null || person === void 0 ? void 0 : person.name) === 'string' ? person === null || person === void 0 ? void 0 : person.name : '';
23+
};
24+
var isString = function (value) {
25+
return typeof value === 'string';
26+
};
27+
var getName2 = function (person) {
28+
return isString(person === null || person === void 0 ? void 0 : person.name) ? person === null || person === void 0 ? void 0 : person.name : '';
29+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining2.ts] ////
2+
3+
=== typePredicatesOptionalChaining2.ts ===
4+
type Person = { name: string; }
5+
>Person : Symbol(Person, Decl(typePredicatesOptionalChaining2.ts, 0, 0))
6+
>name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
7+
8+
const getName1 = (person?: Person): string => {
9+
>getName1 : Symbol(getName1, Decl(typePredicatesOptionalChaining2.ts, 2, 5))
10+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 2, 18))
11+
>Person : Symbol(Person, Decl(typePredicatesOptionalChaining2.ts, 0, 0))
12+
13+
return typeof person?.name === 'string' ? person?.name : '';
14+
>person?.name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
15+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 2, 18))
16+
>name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
17+
>person?.name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
18+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 2, 18))
19+
>name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
20+
21+
};
22+
23+
const isString = (value: any): value is string => {
24+
>isString : Symbol(isString, Decl(typePredicatesOptionalChaining2.ts, 6, 5))
25+
>value : Symbol(value, Decl(typePredicatesOptionalChaining2.ts, 6, 18))
26+
>value : Symbol(value, Decl(typePredicatesOptionalChaining2.ts, 6, 18))
27+
28+
return typeof value === 'string';
29+
>value : Symbol(value, Decl(typePredicatesOptionalChaining2.ts, 6, 18))
30+
31+
};
32+
33+
const getName2 = (person?: Person): string => {
34+
>getName2 : Symbol(getName2, Decl(typePredicatesOptionalChaining2.ts, 10, 5))
35+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 10, 18))
36+
>Person : Symbol(Person, Decl(typePredicatesOptionalChaining2.ts, 0, 0))
37+
38+
return isString(person?.name) ? person?.name : '';
39+
>isString : Symbol(isString, Decl(typePredicatesOptionalChaining2.ts, 6, 5))
40+
>person?.name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
41+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 10, 18))
42+
>name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
43+
>person?.name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
44+
>person : Symbol(person, Decl(typePredicatesOptionalChaining2.ts, 10, 18))
45+
>name : Symbol(name, Decl(typePredicatesOptionalChaining2.ts, 0, 15))
46+
47+
};
48+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining2.ts] ////
2+
3+
=== typePredicatesOptionalChaining2.ts ===
4+
type Person = { name: string; }
5+
>Person : { name: string; }
6+
>name : string
7+
8+
const getName1 = (person?: Person): string => {
9+
>getName1 : (person?: Person) => string
10+
>(person?: Person): string => { return typeof person?.name === 'string' ? person?.name : '';} : (person?: Person) => string
11+
>person : Person | undefined
12+
13+
return typeof person?.name === 'string' ? person?.name : '';
14+
>typeof person?.name === 'string' ? person?.name : '' : string
15+
>typeof person?.name === 'string' : boolean
16+
>typeof person?.name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
17+
>person?.name : string | undefined
18+
>person : Person | undefined
19+
>name : string | undefined
20+
>'string' : "string"
21+
>person?.name : string
22+
>person : Person
23+
>name : string
24+
>'' : ""
25+
26+
};
27+
28+
const isString = (value: any): value is string => {
29+
>isString : (value: any) => value is string
30+
>(value: any): value is string => { return typeof value === 'string';} : (value: any) => value is string
31+
>value : any
32+
33+
return typeof value === 'string';
34+
>typeof value === 'string' : boolean
35+
>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
36+
>value : any
37+
>'string' : "string"
38+
39+
};
40+
41+
const getName2 = (person?: Person): string => {
42+
>getName2 : (person?: Person) => string
43+
>(person?: Person): string => { return isString(person?.name) ? person?.name : '';} : (person?: Person) => string
44+
>person : Person | undefined
45+
46+
return isString(person?.name) ? person?.name : '';
47+
>isString(person?.name) ? person?.name : '' : string
48+
>isString(person?.name) : boolean
49+
>isString : (value: any) => value is string
50+
>person?.name : string | undefined
51+
>person : Person | undefined
52+
>name : string | undefined
53+
>person?.name : string
54+
>person : Person
55+
>name : string
56+
>'' : ""
57+
58+
};
59+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/typePredicatesOptionalChaining3.ts] ////
2+
3+
//// [typePredicatesOptionalChaining3.ts]
4+
interface Animal {
5+
breed?: Breed;
6+
}
7+
interface Breed {
8+
size?: string;
9+
}
10+
11+
declare function isNil(value: unknown): value is undefined | null;
12+
13+
function getBreedSizeWithoutFunction(animal: Animal): string | undefined {
14+
if (animal?.breed?.size != null) {
15+
return animal.breed.size;
16+
} else {
17+
return undefined;
18+
}
19+
}
20+
21+
function getBreedSizeWithFunction(animal: Animal): string | undefined {
22+
if (!isNil(animal?.breed?.size)) {
23+
return animal.breed.size;
24+
} else {
25+
return undefined;
26+
}
27+
}
28+
29+
30+
//// [typePredicatesOptionalChaining3.js]
31+
"use strict";
32+
function getBreedSizeWithoutFunction(animal) {
33+
var _a;
34+
if (((_a = animal === null || animal === void 0 ? void 0 : animal.breed) === null || _a === void 0 ? void 0 : _a.size) != null) {
35+
return animal.breed.size;
36+
}
37+
else {
38+
return undefined;
39+
}
40+
}
41+
function getBreedSizeWithFunction(animal) {
42+
var _a;
43+
if (!isNil((_a = animal === null || animal === void 0 ? void 0 : animal.breed) === null || _a === void 0 ? void 0 : _a.size)) {
44+
return animal.breed.size;
45+
}
46+
else {
47+
return undefined;
48+
}
49+
}

0 commit comments

Comments
 (0)