Skip to content

Commit db813d5

Browse files
authored
Merge pull request microsoft#12210 from Microsoft/typePredicateCheck
Fix type predicate check circularity
2 parents 56f97e1 + 8570219 commit db813d5

File tree

6 files changed

+207
-1
lines changed

6 files changed

+207
-1
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8981,6 +8981,28 @@ namespace ts {
89818981
return isLengthPushOrUnshift || isElementAssignment;
89828982
}
89838983

8984+
function maybeTypePredicateCall(node: CallExpression) {
8985+
const links = getNodeLinks(node);
8986+
if (links.maybeTypePredicate === undefined) {
8987+
links.maybeTypePredicate = getMaybeTypePredicate(node);
8988+
}
8989+
return links.maybeTypePredicate;
8990+
}
8991+
8992+
function getMaybeTypePredicate(node: CallExpression) {
8993+
if (node.expression.kind !== SyntaxKind.SuperKeyword) {
8994+
const funcType = checkNonNullExpression(node.expression);
8995+
if (funcType !== silentNeverType) {
8996+
const apparentType = getApparentType(funcType);
8997+
if (apparentType !== unknownType) {
8998+
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
8999+
return !!forEach(callSignatures, sig => sig.typePredicate);
9000+
}
9001+
}
9002+
}
9003+
return false;
9004+
}
9005+
89849006
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) {
89859007
let key: string;
89869008
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
@@ -9495,7 +9517,7 @@ namespace ts {
94959517
}
94969518

94979519
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
9498-
if (!hasMatchingArgument(callExpression, reference)) {
9520+
if (!hasMatchingArgument(callExpression, reference) || !maybeTypePredicateCall(callExpression)) {
94999521
return type;
95009522
}
95019523
const signature = getResolvedSignature(callExpression);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,7 @@ namespace ts {
26682668
resolvedSignature?: Signature; // Cached signature of signature node or call expression
26692669
resolvedSymbol?: Symbol; // Cached name resolution result
26702670
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
2671+
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
26712672
enumMemberValue?: number; // Constant value of enum member
26722673
isVisible?: boolean; // Is this node visible
26732674
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [typePredicateInLoop.ts]
2+
// Repro from #12101
3+
4+
interface Type {
5+
type: number;
6+
}
7+
8+
interface TypeExt extends Type {
9+
arr: Type[];
10+
}
11+
12+
const guard = (arg: Type): arg is TypeExt => arg.type === 1;
13+
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
14+
15+
export function y(arg: Type): void {
16+
if (guard(arg)) {
17+
for (const ITEM of arg.arr) {
18+
if (otherFunc(ITEM, arg)) {
19+
}
20+
}
21+
}
22+
}
23+
24+
//// [typePredicateInLoop.js]
25+
// Repro from #12101
26+
"use strict";
27+
var guard = function (arg) { return arg.type === 1; };
28+
var otherFunc = function (arg1, arg2) { };
29+
function y(arg) {
30+
if (guard(arg)) {
31+
for (var _i = 0, _a = arg.arr; _i < _a.length; _i++) {
32+
var ITEM = _a[_i];
33+
if (otherFunc(ITEM, arg)) {
34+
}
35+
}
36+
}
37+
}
38+
exports.y = y;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
=== tests/cases/compiler/typePredicateInLoop.ts ===
2+
// Repro from #12101
3+
4+
interface Type {
5+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
6+
7+
type: number;
8+
>type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))
9+
}
10+
11+
interface TypeExt extends Type {
12+
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))
13+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
14+
15+
arr: Type[];
16+
>arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))
17+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
18+
}
19+
20+
const guard = (arg: Type): arg is TypeExt => arg.type === 1;
21+
>guard : Symbol(guard, Decl(typePredicateInLoop.ts, 10, 5))
22+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
23+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
24+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
25+
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))
26+
>arg.type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))
27+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
28+
>type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))
29+
30+
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
31+
>otherFunc : Symbol(otherFunc, Decl(typePredicateInLoop.ts, 11, 5))
32+
>arg1 : Symbol(arg1, Decl(typePredicateInLoop.ts, 11, 19))
33+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
34+
>arg2 : Symbol(arg2, Decl(typePredicateInLoop.ts, 11, 30))
35+
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))
36+
37+
export function y(arg: Type): void {
38+
>y : Symbol(y, Decl(typePredicateInLoop.ts, 11, 58))
39+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
40+
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
41+
42+
if (guard(arg)) {
43+
>guard : Symbol(guard, Decl(typePredicateInLoop.ts, 10, 5))
44+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
45+
46+
for (const ITEM of arg.arr) {
47+
>ITEM : Symbol(ITEM, Decl(typePredicateInLoop.ts, 15, 14))
48+
>arg.arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))
49+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
50+
>arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))
51+
52+
if (otherFunc(ITEM, arg)) {
53+
>otherFunc : Symbol(otherFunc, Decl(typePredicateInLoop.ts, 11, 5))
54+
>ITEM : Symbol(ITEM, Decl(typePredicateInLoop.ts, 15, 14))
55+
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
56+
}
57+
}
58+
}
59+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/compiler/typePredicateInLoop.ts ===
2+
// Repro from #12101
3+
4+
interface Type {
5+
>Type : Type
6+
7+
type: number;
8+
>type : number
9+
}
10+
11+
interface TypeExt extends Type {
12+
>TypeExt : TypeExt
13+
>Type : Type
14+
15+
arr: Type[];
16+
>arr : Type[]
17+
>Type : Type
18+
}
19+
20+
const guard = (arg: Type): arg is TypeExt => arg.type === 1;
21+
>guard : (arg: Type) => arg is TypeExt
22+
>(arg: Type): arg is TypeExt => arg.type === 1 : (arg: Type) => arg is TypeExt
23+
>arg : Type
24+
>Type : Type
25+
>arg : any
26+
>TypeExt : TypeExt
27+
>arg.type === 1 : boolean
28+
>arg.type : number
29+
>arg : Type
30+
>type : number
31+
>1 : 1
32+
33+
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
34+
>otherFunc : (arg1: Type, arg2: TypeExt) => void
35+
>(arg1: Type, arg2: TypeExt): void => {} : (arg1: Type, arg2: TypeExt) => void
36+
>arg1 : Type
37+
>Type : Type
38+
>arg2 : TypeExt
39+
>TypeExt : TypeExt
40+
41+
export function y(arg: Type): void {
42+
>y : (arg: Type) => void
43+
>arg : Type
44+
>Type : Type
45+
46+
if (guard(arg)) {
47+
>guard(arg) : boolean
48+
>guard : (arg: Type) => arg is TypeExt
49+
>arg : Type
50+
51+
for (const ITEM of arg.arr) {
52+
>ITEM : Type
53+
>arg.arr : Type[]
54+
>arg : TypeExt
55+
>arr : Type[]
56+
57+
if (otherFunc(ITEM, arg)) {
58+
>otherFunc(ITEM, arg) : void
59+
>otherFunc : (arg1: Type, arg2: TypeExt) => void
60+
>ITEM : Type
61+
>arg : TypeExt
62+
}
63+
}
64+
}
65+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Repro from #12101
2+
3+
interface Type {
4+
type: number;
5+
}
6+
7+
interface TypeExt extends Type {
8+
arr: Type[];
9+
}
10+
11+
const guard = (arg: Type): arg is TypeExt => arg.type === 1;
12+
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
13+
14+
export function y(arg: Type): void {
15+
if (guard(arg)) {
16+
for (const ITEM of arg.arr) {
17+
if (otherFunc(ITEM, arg)) {
18+
}
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)