Skip to content

Commit

Permalink
Merge pull request #12210 from Microsoft/typePredicateCheck
Browse files Browse the repository at this point in the history
Fix type predicate check circularity
  • Loading branch information
ahejlsberg committed Nov 13, 2016
2 parents 56f97e1 + 8570219 commit db813d5
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 1 deletion.
24 changes: 23 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8981,6 +8981,28 @@ namespace ts {
return isLengthPushOrUnshift || isElementAssignment;
}

function maybeTypePredicateCall(node: CallExpression) {
const links = getNodeLinks(node);
if (links.maybeTypePredicate === undefined) {
links.maybeTypePredicate = getMaybeTypePredicate(node);
}
return links.maybeTypePredicate;
}

function getMaybeTypePredicate(node: CallExpression) {
if (node.expression.kind !== SyntaxKind.SuperKeyword) {
const funcType = checkNonNullExpression(node.expression);
if (funcType !== silentNeverType) {
const apparentType = getApparentType(funcType);
if (apparentType !== unknownType) {
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
return !!forEach(callSignatures, sig => sig.typePredicate);
}
}
}
return false;
}

function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) {
let key: string;
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
Expand Down Expand Up @@ -9495,7 +9517,7 @@ namespace ts {
}

function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
if (!hasMatchingArgument(callExpression, reference)) {
if (!hasMatchingArgument(callExpression, reference) || !maybeTypePredicateCall(callExpression)) {
return type;
}
const signature = getResolvedSignature(callExpression);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2668,6 +2668,7 @@ namespace ts {
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
enumMemberValue?: number; // Constant value of enum member
isVisible?: boolean; // Is this node visible
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
Expand Down
38 changes: 38 additions & 0 deletions tests/baselines/reference/typePredicateInLoop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//// [typePredicateInLoop.ts]
// Repro from #12101

interface Type {
type: number;
}

interface TypeExt extends Type {
arr: Type[];
}

const guard = (arg: Type): arg is TypeExt => arg.type === 1;
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};

export function y(arg: Type): void {
if (guard(arg)) {
for (const ITEM of arg.arr) {
if (otherFunc(ITEM, arg)) {
}
}
}
}

//// [typePredicateInLoop.js]
// Repro from #12101
"use strict";
var guard = function (arg) { return arg.type === 1; };
var otherFunc = function (arg1, arg2) { };
function y(arg) {
if (guard(arg)) {
for (var _i = 0, _a = arg.arr; _i < _a.length; _i++) {
var ITEM = _a[_i];
if (otherFunc(ITEM, arg)) {
}
}
}
}
exports.y = y;
59 changes: 59 additions & 0 deletions tests/baselines/reference/typePredicateInLoop.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
=== tests/cases/compiler/typePredicateInLoop.ts ===
// Repro from #12101

interface Type {
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))

type: number;
>type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))
}

interface TypeExt extends Type {
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))

arr: Type[];
>arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
}

const guard = (arg: Type): arg is TypeExt => arg.type === 1;
>guard : Symbol(guard, Decl(typePredicateInLoop.ts, 10, 5))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))
>arg.type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 10, 15))
>type : Symbol(Type.type, Decl(typePredicateInLoop.ts, 2, 16))

const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
>otherFunc : Symbol(otherFunc, Decl(typePredicateInLoop.ts, 11, 5))
>arg1 : Symbol(arg1, Decl(typePredicateInLoop.ts, 11, 19))
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))
>arg2 : Symbol(arg2, Decl(typePredicateInLoop.ts, 11, 30))
>TypeExt : Symbol(TypeExt, Decl(typePredicateInLoop.ts, 4, 1))

export function y(arg: Type): void {
>y : Symbol(y, Decl(typePredicateInLoop.ts, 11, 58))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
>Type : Symbol(Type, Decl(typePredicateInLoop.ts, 0, 0))

if (guard(arg)) {
>guard : Symbol(guard, Decl(typePredicateInLoop.ts, 10, 5))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))

for (const ITEM of arg.arr) {
>ITEM : Symbol(ITEM, Decl(typePredicateInLoop.ts, 15, 14))
>arg.arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
>arr : Symbol(TypeExt.arr, Decl(typePredicateInLoop.ts, 6, 32))

if (otherFunc(ITEM, arg)) {
>otherFunc : Symbol(otherFunc, Decl(typePredicateInLoop.ts, 11, 5))
>ITEM : Symbol(ITEM, Decl(typePredicateInLoop.ts, 15, 14))
>arg : Symbol(arg, Decl(typePredicateInLoop.ts, 13, 18))
}
}
}
}
65 changes: 65 additions & 0 deletions tests/baselines/reference/typePredicateInLoop.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
=== tests/cases/compiler/typePredicateInLoop.ts ===
// Repro from #12101

interface Type {
>Type : Type

type: number;
>type : number
}

interface TypeExt extends Type {
>TypeExt : TypeExt
>Type : Type

arr: Type[];
>arr : Type[]
>Type : Type
}

const guard = (arg: Type): arg is TypeExt => arg.type === 1;
>guard : (arg: Type) => arg is TypeExt
>(arg: Type): arg is TypeExt => arg.type === 1 : (arg: Type) => arg is TypeExt
>arg : Type
>Type : Type
>arg : any
>TypeExt : TypeExt
>arg.type === 1 : boolean
>arg.type : number
>arg : Type
>type : number
>1 : 1

const otherFunc = (arg1: Type, arg2: TypeExt): void => {};
>otherFunc : (arg1: Type, arg2: TypeExt) => void
>(arg1: Type, arg2: TypeExt): void => {} : (arg1: Type, arg2: TypeExt) => void
>arg1 : Type
>Type : Type
>arg2 : TypeExt
>TypeExt : TypeExt

export function y(arg: Type): void {
>y : (arg: Type) => void
>arg : Type
>Type : Type

if (guard(arg)) {
>guard(arg) : boolean
>guard : (arg: Type) => arg is TypeExt
>arg : Type

for (const ITEM of arg.arr) {
>ITEM : Type
>arg.arr : Type[]
>arg : TypeExt
>arr : Type[]

if (otherFunc(ITEM, arg)) {
>otherFunc(ITEM, arg) : void
>otherFunc : (arg1: Type, arg2: TypeExt) => void
>ITEM : Type
>arg : TypeExt
}
}
}
}
21 changes: 21 additions & 0 deletions tests/cases/compiler/typePredicateInLoop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Repro from #12101

interface Type {
type: number;
}

interface TypeExt extends Type {
arr: Type[];
}

const guard = (arg: Type): arg is TypeExt => arg.type === 1;
const otherFunc = (arg1: Type, arg2: TypeExt): void => {};

export function y(arg: Type): void {
if (guard(arg)) {
for (const ITEM of arg.arr) {
if (otherFunc(ITEM, arg)) {
}
}
}
}

0 comments on commit db813d5

Please sign in to comment.