From 6dde1621cb0f113d4a2006fa17861e2c19661405 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 21 Oct 2020 12:31:29 -0700 Subject: [PATCH] Track control flow for comma expressions in call expressions --- src/compiler/binder.ts | 1 + src/compiler/checker.ts | 3 +- .../controlFlowCommaExpressionFunctionCall.js | 23 +++++++++++++ ...rolFlowCommaExpressionFunctionCall.symbols | 25 ++++++++++++++ ...ntrolFlowCommaExpressionFunctionCall.types | 33 +++++++++++++++++++ .../controlFlowCommaExpressionFunctionCall.ts | 11 +++++++ 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/controlFlowCommaExpressionFunctionCall.js create mode 100644 tests/baselines/reference/controlFlowCommaExpressionFunctionCall.symbols create mode 100644 tests/baselines/reference/controlFlowCommaExpressionFunctionCall.types create mode 100644 tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5e7695ffef366..9fcb5d09a4349 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -858,6 +858,7 @@ namespace ts { function isNarrowableReference(expr: Expression): boolean { return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.PrivateIdentifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || + isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) || isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || isAssignmentExpression(expr) && isNarrowableReference(expr.left); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 601948a3ea08d..feb8ccd636466 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20720,7 +20720,8 @@ namespace ts { case SyntaxKind.NonNullExpression: return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: - return isAssignmentExpression(target) && isMatchingReference(source, target.left); + return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || + (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); } switch (source.kind) { case SyntaxKind.Identifier: diff --git a/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.js b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.js new file mode 100644 index 0000000000000..c27f3ee472308 --- /dev/null +++ b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.js @@ -0,0 +1,23 @@ +//// [controlFlowCommaExpressionFunctionCall.ts] +const otherValue = () => true; +const value : number | string = null as any; + +function isNumber(obj: any): obj is number { + return true; // method implementation irrelevant +} + +// Bad case - fails +if (isNumber((otherValue(), value))) { + const b = value; // string | number , but should be number +} + +//// [controlFlowCommaExpressionFunctionCall.js] +var otherValue = function () { return true; }; +var value = null; +function isNumber(obj) { + return true; // method implementation irrelevant +} +// Bad case - fails +if (isNumber((otherValue(), value))) { + var b = value; // string | number , but should be number +} diff --git a/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.symbols b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.symbols new file mode 100644 index 0000000000000..4372e3f1ebe2b --- /dev/null +++ b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.symbols @@ -0,0 +1,25 @@ +=== tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts === +const otherValue = () => true; +>otherValue : Symbol(otherValue, Decl(controlFlowCommaExpressionFunctionCall.ts, 0, 5)) + +const value : number | string = null as any; +>value : Symbol(value, Decl(controlFlowCommaExpressionFunctionCall.ts, 1, 5)) + +function isNumber(obj: any): obj is number { +>isNumber : Symbol(isNumber, Decl(controlFlowCommaExpressionFunctionCall.ts, 1, 44)) +>obj : Symbol(obj, Decl(controlFlowCommaExpressionFunctionCall.ts, 3, 18)) +>obj : Symbol(obj, Decl(controlFlowCommaExpressionFunctionCall.ts, 3, 18)) + + return true; // method implementation irrelevant +} + +// Bad case - fails +if (isNumber((otherValue(), value))) { +>isNumber : Symbol(isNumber, Decl(controlFlowCommaExpressionFunctionCall.ts, 1, 44)) +>otherValue : Symbol(otherValue, Decl(controlFlowCommaExpressionFunctionCall.ts, 0, 5)) +>value : Symbol(value, Decl(controlFlowCommaExpressionFunctionCall.ts, 1, 5)) + + const b = value; // string | number , but should be number +>b : Symbol(b, Decl(controlFlowCommaExpressionFunctionCall.ts, 9, 9)) +>value : Symbol(value, Decl(controlFlowCommaExpressionFunctionCall.ts, 1, 5)) +} diff --git a/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.types b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.types new file mode 100644 index 0000000000000..0464f85610247 --- /dev/null +++ b/tests/baselines/reference/controlFlowCommaExpressionFunctionCall.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts === +const otherValue = () => true; +>otherValue : () => boolean +>() => true : () => boolean +>true : true + +const value : number | string = null as any; +>value : string | number +>null as any : any +>null : null + +function isNumber(obj: any): obj is number { +>isNumber : (obj: any) => obj is number +>obj : any + + return true; // method implementation irrelevant +>true : true +} + +// Bad case - fails +if (isNumber((otherValue(), value))) { +>isNumber((otherValue(), value)) : boolean +>isNumber : (obj: any) => obj is number +>(otherValue(), value) : string | number +>otherValue(), value : string | number +>otherValue() : boolean +>otherValue : () => boolean +>value : string | number + + const b = value; // string | number , but should be number +>b : number +>value : number +} diff --git a/tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts b/tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts new file mode 100644 index 0000000000000..ff1d48c3ae783 --- /dev/null +++ b/tests/cases/compiler/controlFlowCommaExpressionFunctionCall.ts @@ -0,0 +1,11 @@ +const otherValue = () => true; +const value : number | string = null as any; + +function isNumber(obj: any): obj is number { + return true; // method implementation irrelevant +} + +// Bad case - fails +if (isNumber((otherValue(), value))) { + const b = value; // string | number , but should be number +} \ No newline at end of file