Revert "Revert "feat(40197): handle uncalled function checks in binary expressions (#40260)"" (#41462)

This reverts commit cf3e28ea66.
This commit is contained in:
Nathan Shively-Sanders 2020-11-09 11:34:41 -08:00 committed by GitHub
parent 06a2210eb5
commit 64be2a8d16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1069 additions and 24 deletions

View file

@ -670,7 +670,7 @@ namespace ts {
}
// We create a return control flow graph for IIFEs and constructors. For constructors
// we use the return control flow graph in strict property initialization checks.
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentExceptionTarget = undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
@ -691,7 +691,7 @@ namespace ts {
if (currentReturnTarget) {
addAntecedent(currentReturnTarget, currentFlow);
currentFlow = finishFlowLabel(currentReturnTarget);
if (node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) {
if (node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) {
(<FunctionLikeDeclaration>node).returnFlowNode = currentFlow;
}
}

View file

@ -30156,6 +30156,9 @@ namespace ts {
workStacks.leftType[stackIndex] = leftType;
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (operator === SyntaxKind.AmpersandAmpersandToken) {
checkTestingKnownTruthyCallableType(node.left, leftType);
}
checkTruthinessOfType(leftType, node.left);
}
advanceState(CheckBinaryExpressionState.FinishCheck);
@ -30689,7 +30692,7 @@ namespace ts {
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
const type = checkTruthinessExpression(node.condition);
checkTestingKnownTruthyCallableType(node.condition, node.whenTrue, type);
checkTestingKnownTruthyCallableType(node.condition, type, node.whenTrue);
const type1 = checkExpression(node.whenTrue, checkMode);
const type2 = checkExpression(node.whenFalse, checkMode);
return getUnionType([type1, type2], UnionReduction.Subtype);
@ -33926,7 +33929,7 @@ namespace ts {
// Grammar checking
checkGrammarStatementInAmbientContext(node);
const type = checkTruthinessExpression(node.expression);
checkTestingKnownTruthyCallableType(node.expression, node.thenStatement, type);
checkTestingKnownTruthyCallableType(node.expression, type, node.thenStatement);
checkSourceElement(node.thenStatement);
if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
@ -33936,16 +33939,16 @@ namespace ts {
checkSourceElement(node.elseStatement);
}
function checkTestingKnownTruthyCallableType(condExpr: Expression, body: Statement | Expression, type: Type) {
function checkTestingKnownTruthyCallableType(condExpr: Expression, type: Type, body?: Statement | Expression) {
if (!strictNullChecks) {
return;
}
const testedNode = isIdentifier(condExpr)
? condExpr
: isPropertyAccessExpression(condExpr)
? condExpr.name
: undefined;
const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr;
const testedNode = isIdentifier(location) ? location
: isPropertyAccessExpression(location) ? location.name
: isBinaryExpression(location) && isIdentifier(location.right) ? location.right
: undefined;
if (!testedNode) {
return;
@ -33966,27 +33969,34 @@ namespace ts {
return;
}
const testedFunctionSymbol = getSymbolAtLocation(testedNode);
if (!testedFunctionSymbol) {
const testedSymbol = getSymbolAtLocation(testedNode);
if (!testedSymbol) {
return;
}
const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined {
const isUsed = isBinaryExpression(condExpr.parent) ? isFunctionUsedInBinaryExpressionChain(condExpr.parent, testedSymbol)
: body ? isFunctionUsedInConditionBody(condExpr, body, testedNode, testedSymbol)
: false;
if (!isUsed) {
error(location, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
}
}
function isFunctionUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean {
return !!forEachChild(body, function check(childNode): boolean | undefined {
if (isIdentifier(childNode)) {
const childSymbol = getSymbolAtLocation(childNode);
if (childSymbol && childSymbol === testedFunctionSymbol) {
if (childSymbol && childSymbol === testedSymbol) {
// If the test was a simple identifier, the above check is sufficient
if (isIdentifier(condExpr)) {
if (isIdentifier(expr)) {
return true;
}
// Otherwise we need to ensure the symbol is called on the same target
let testedExpression = testedNode.parent;
let childExpression = childNode.parent;
while (testedExpression && childExpression) {
if (isIdentifier(testedExpression) && isIdentifier(childExpression) ||
testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword
) {
testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) {
return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression);
}
@ -34003,13 +34013,18 @@ namespace ts {
}
}
}
return forEachChild(childNode, check);
});
}
if (!functionIsUsedInBody) {
error(condExpr, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
function isFunctionUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean {
while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
if (isCallExpression(node.right) && testedSymbol === getSymbolAtLocation(node.right.expression)) {
return true;
}
node = node.parent;
}
return false;
}
function checkDoStatement(node: DoStatement) {

View file

@ -2812,7 +2812,7 @@ namespace ts {
if (isSimilarNode && currentSourceFile) {
pos = skipTrivia(currentSourceFile.text, pos);
}
if (emitLeadingCommentsOfPosition && isSimilarNode && contextNode.pos !== startPos) {
if (isSimilarNode && contextNode.pos !== startPos) {
const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile);
if (needsIndent) {
increaseIndent();
@ -2823,7 +2823,7 @@ namespace ts {
}
}
pos = writeTokenText(token, writer, pos);
if (emitTrailingCommentsOfPosition && isSimilarNode && contextNode.end !== pos) {
if (isSimilarNode && contextNode.end !== pos) {
emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ true);
}
return pos;
@ -3469,7 +3469,7 @@ namespace ts {
// "comment1" is not considered to be leading comment for node.initializer
// but rather a trailing comment on the previous node.
const initializer = node.initializer;
if (emitTrailingCommentsOfPosition && (getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) {
if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) {
const commentRange = getCommentRange(initializer);
emitTrailingCommentsOfPosition(commentRange.pos);
}

View file

@ -0,0 +1,108 @@
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(3,5): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(6,10): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(30,18): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(36,46): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(47,5): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(50,10): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(66,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
tests/cases/compiler/truthinessCallExpressionCoercion2.ts(69,14): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
==== tests/cases/compiler/truthinessCallExpressionCoercion2.ts (8 errors) ====
function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) {
// error
required1 && console.log('required');
~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// error
1 && required1 && console.log('required');
~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// ok
required1 && required1();
// ok
required1 && 1 && required1();
// ok
optional && console.log('optional');
// ok
1 && optional && console.log('optional');
// ok
!!required1 && console.log('not required');
// ok
required1() && console.log('required call');
// ok
required1 && required2 && required1() && required2();
// error
required1 && required2 && required1() && console.log('foo');
~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
}
function checksConsole() {
// error
typeof window !== 'undefined' && window.console &&
((window.console as any).firebug || (window.console.exception && window.console.table));
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
}
function checksPropertyAccess() {
const x = {
foo: {
bar() { return true; }
}
}
// error
x.foo.bar && console.log('x.foo.bar');
~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// error
1 && x.foo.bar && console.log('x.foo.bar');
~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// ok
x.foo.bar && x.foo.bar();
// ok
x.foo.bar && 1 && x.foo.bar();
}
class Foo {
optional?: () => boolean;
required() {
return true;
}
test() {
// error
this.required && console.log('required');
~~~~~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// error
1 && this.required && console.log('required');
~~~~~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
// ok
this.required && this.required();
// ok
this.required && 1 && this.required();
// ok
1 && this.optional && console.log('optional');
}
}

View file

@ -0,0 +1,146 @@
//// [truthinessCallExpressionCoercion2.ts]
function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) {
// error
required1 && console.log('required');
// error
1 && required1 && console.log('required');
// ok
required1 && required1();
// ok
required1 && 1 && required1();
// ok
optional && console.log('optional');
// ok
1 && optional && console.log('optional');
// ok
!!required1 && console.log('not required');
// ok
required1() && console.log('required call');
// ok
required1 && required2 && required1() && required2();
// error
required1 && required2 && required1() && console.log('foo');
}
function checksConsole() {
// error
typeof window !== 'undefined' && window.console &&
((window.console as any).firebug || (window.console.exception && window.console.table));
}
function checksPropertyAccess() {
const x = {
foo: {
bar() { return true; }
}
}
// error
x.foo.bar && console.log('x.foo.bar');
// error
1 && x.foo.bar && console.log('x.foo.bar');
// ok
x.foo.bar && x.foo.bar();
// ok
x.foo.bar && 1 && x.foo.bar();
}
class Foo {
optional?: () => boolean;
required() {
return true;
}
test() {
// error
this.required && console.log('required');
// error
1 && this.required && console.log('required');
// ok
this.required && this.required();
// ok
this.required && 1 && this.required();
// ok
1 && this.optional && console.log('optional');
}
}
//// [truthinessCallExpressionCoercion2.js]
function test(required1, required2, optional) {
// error
required1 && console.log('required');
// error
1 && required1 && console.log('required');
// ok
required1 && required1();
// ok
required1 && 1 && required1();
// ok
optional && console.log('optional');
// ok
1 && optional && console.log('optional');
// ok
!!required1 && console.log('not required');
// ok
required1() && console.log('required call');
// ok
required1 && required2 && required1() && required2();
// error
required1 && required2 && required1() && console.log('foo');
}
function checksConsole() {
// error
typeof window !== 'undefined' && window.console &&
(window.console.firebug || (window.console.exception && window.console.table));
}
function checksPropertyAccess() {
var x = {
foo: {
bar: function () { return true; }
}
};
// error
x.foo.bar && console.log('x.foo.bar');
// error
1 && x.foo.bar && console.log('x.foo.bar');
// ok
x.foo.bar && x.foo.bar();
// ok
x.foo.bar && 1 && x.foo.bar();
}
var Foo = /** @class */ (function () {
function Foo() {
}
Foo.prototype.required = function () {
return true;
};
Foo.prototype.test = function () {
// error
this.required && console.log('required');
// error
1 && this.required && console.log('required');
// ok
this.required && this.required();
// ok
this.required && 1 && this.required();
// ok
1 && this.optional && console.log('optional');
};
return Foo;
}());

View file

@ -0,0 +1,226 @@
=== tests/cases/compiler/truthinessCallExpressionCoercion2.ts ===
function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) {
>test : Symbol(test, Decl(truthinessCallExpressionCoercion2.ts, 0, 0))
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39))
>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65))
// error
required1 && console.log('required');
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// error
1 && required1 && console.log('required');
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
required1 && required1();
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
// ok
required1 && 1 && required1();
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
// ok
optional && console.log('optional');
>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
1 && optional && console.log('optional');
>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
!!required1 && console.log('not required');
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
required1() && console.log('required call');
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
required1 && required2 && required1() && required2();
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39))
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39))
// error
required1 && required2 && required1() && console.log('foo');
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39))
>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
}
function checksConsole() {
>checksConsole : Symbol(checksConsole, Decl(truthinessCallExpressionCoercion2.ts, 30, 1))
// error
typeof window !== 'undefined' && window.console &&
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
((window.console as any).firebug || (window.console.exception && window.console.table));
>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>window.console.exception : Symbol(Console.exception, Decl(lib.dom.d.ts, --, --))
>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>exception : Symbol(Console.exception, Decl(lib.dom.d.ts, --, --))
>window.console.table : Symbol(Console.table, Decl(lib.dom.d.ts, --, --))
>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>window : Symbol(window, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>table : Symbol(Console.table, Decl(lib.dom.d.ts, --, --))
}
function checksPropertyAccess() {
>checksPropertyAccess : Symbol(checksPropertyAccess, Decl(truthinessCallExpressionCoercion2.ts, 36, 1))
const x = {
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
foo: {
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
bar() { return true; }
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
}
}
// error
x.foo.bar && console.log('x.foo.bar');
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// error
1 && x.foo.bar && console.log('x.foo.bar');
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
x.foo.bar && x.foo.bar();
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
// ok
x.foo.bar && 1 && x.foo.bar();
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9))
>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15))
>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14))
}
class Foo {
>Foo : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
optional?: () => boolean;
>optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11))
required() {
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
return true;
}
test() {
>test : Symbol(Foo.test, Decl(truthinessCallExpressionCoercion2.ts, 62, 5))
// error
this.required && console.log('required');
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// error
1 && this.required && console.log('required');
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
// ok
this.required && this.required();
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
// ok
this.required && 1 && this.required();
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29))
// ok
1 && this.optional && console.log('optional');
>this.optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11))
>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1))
>optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
}
}

View file

@ -0,0 +1,318 @@
=== tests/cases/compiler/truthinessCallExpressionCoercion2.ts ===
function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) {
>test : (required1: () => boolean, required2: () => boolean, optional?: (() => boolean) | undefined) => void
>required1 : () => boolean
>required2 : () => boolean
>optional : (() => boolean) | undefined
// error
required1 && console.log('required');
>required1 && console.log('required') : void
>required1 : () => boolean
>console.log('required') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'required' : "required"
// error
1 && required1 && console.log('required');
>1 && required1 && console.log('required') : void
>1 && required1 : () => boolean
>1 : 1
>required1 : () => boolean
>console.log('required') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'required' : "required"
// ok
required1 && required1();
>required1 && required1() : boolean
>required1 : () => boolean
>required1() : boolean
>required1 : () => boolean
// ok
required1 && 1 && required1();
>required1 && 1 && required1() : boolean
>required1 && 1 : 1
>required1 : () => boolean
>1 : 1
>required1() : boolean
>required1 : () => boolean
// ok
optional && console.log('optional');
>optional && console.log('optional') : void | undefined
>optional : (() => boolean) | undefined
>console.log('optional') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'optional' : "optional"
// ok
1 && optional && console.log('optional');
>1 && optional && console.log('optional') : void | undefined
>1 && optional : (() => boolean) | undefined
>1 : 1
>optional : (() => boolean) | undefined
>console.log('optional') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'optional' : "optional"
// ok
!!required1 && console.log('not required');
>!!required1 && console.log('not required') : void
>!!required1 : true
>!required1 : false
>required1 : () => boolean
>console.log('not required') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'not required' : "not required"
// ok
required1() && console.log('required call');
>required1() && console.log('required call') : false | void
>required1() : boolean
>required1 : () => boolean
>console.log('required call') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'required call' : "required call"
// ok
required1 && required2 && required1() && required2();
>required1 && required2 && required1() && required2() : boolean
>required1 && required2 && required1() : boolean
>required1 && required2 : () => boolean
>required1 : () => boolean
>required2 : () => boolean
>required1() : boolean
>required1 : () => boolean
>required2() : boolean
>required2 : () => boolean
// error
required1 && required2 && required1() && console.log('foo');
>required1 && required2 && required1() && console.log('foo') : false | void
>required1 && required2 && required1() : boolean
>required1 && required2 : () => boolean
>required1 : () => boolean
>required2 : () => boolean
>required1() : boolean
>required1 : () => boolean
>console.log('foo') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'foo' : "foo"
}
function checksConsole() {
>checksConsole : () => void
// error
typeof window !== 'undefined' && window.console &&
>typeof window !== 'undefined' && window.console && ((window.console as any).firebug || (window.console.exception && window.console.table)) : any
>typeof window !== 'undefined' && window.console : false | Console
>typeof window !== 'undefined' : boolean
>typeof window : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>window : Window & typeof globalThis
>'undefined' : "undefined"
>window.console : Console
>window : Window & typeof globalThis
>console : Console
((window.console as any).firebug || (window.console.exception && window.console.table));
>((window.console as any).firebug || (window.console.exception && window.console.table)) : any
>(window.console as any).firebug || (window.console.exception && window.console.table) : any
>(window.console as any).firebug : any
>(window.console as any) : any
>window.console as any : any
>window.console : Console
>window : Window & typeof globalThis
>console : Console
>firebug : any
>(window.console.exception && window.console.table) : (tabularData?: any, properties?: string[] | undefined) => void
>window.console.exception && window.console.table : (tabularData?: any, properties?: string[] | undefined) => void
>window.console.exception : (message?: string | undefined, ...optionalParams: any[]) => void
>window.console : Console
>window : Window & typeof globalThis
>console : Console
>exception : (message?: string | undefined, ...optionalParams: any[]) => void
>window.console.table : (tabularData?: any, properties?: string[] | undefined) => void
>window.console : Console
>window : Window & typeof globalThis
>console : Console
>table : (tabularData?: any, properties?: string[] | undefined) => void
}
function checksPropertyAccess() {
>checksPropertyAccess : () => void
const x = {
>x : { foo: { bar(): boolean; }; }
>{ foo: { bar() { return true; } } } : { foo: { bar(): boolean; }; }
foo: {
>foo : { bar(): boolean; }
>{ bar() { return true; } } : { bar(): boolean; }
bar() { return true; }
>bar : () => boolean
>true : true
}
}
// error
x.foo.bar && console.log('x.foo.bar');
>x.foo.bar && console.log('x.foo.bar') : void
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
>console.log('x.foo.bar') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'x.foo.bar' : "x.foo.bar"
// error
1 && x.foo.bar && console.log('x.foo.bar');
>1 && x.foo.bar && console.log('x.foo.bar') : void
>1 && x.foo.bar : () => boolean
>1 : 1
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
>console.log('x.foo.bar') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'x.foo.bar' : "x.foo.bar"
// ok
x.foo.bar && x.foo.bar();
>x.foo.bar && x.foo.bar() : boolean
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
>x.foo.bar() : boolean
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
// ok
x.foo.bar && 1 && x.foo.bar();
>x.foo.bar && 1 && x.foo.bar() : boolean
>x.foo.bar && 1 : 1
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
>1 : 1
>x.foo.bar() : boolean
>x.foo.bar : () => boolean
>x.foo : { bar(): boolean; }
>x : { foo: { bar(): boolean; }; }
>foo : { bar(): boolean; }
>bar : () => boolean
}
class Foo {
>Foo : Foo
optional?: () => boolean;
>optional : (() => boolean) | undefined
required() {
>required : () => boolean
return true;
>true : true
}
test() {
>test : () => void
// error
this.required && console.log('required');
>this.required && console.log('required') : void
>this.required : () => boolean
>this : this
>required : () => boolean
>console.log('required') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'required' : "required"
// error
1 && this.required && console.log('required');
>1 && this.required && console.log('required') : void
>1 && this.required : () => boolean
>1 : 1
>this.required : () => boolean
>this : this
>required : () => boolean
>console.log('required') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'required' : "required"
// ok
this.required && this.required();
>this.required && this.required() : boolean
>this.required : () => boolean
>this : this
>required : () => boolean
>this.required() : boolean
>this.required : () => boolean
>this : this
>required : () => boolean
// ok
this.required && 1 && this.required();
>this.required && 1 && this.required() : boolean
>this.required && 1 : 1
>this.required : () => boolean
>this : this
>required : () => boolean
>1 : 1
>this.required() : boolean
>this.required : () => boolean
>this : this
>required : () => boolean
// ok
1 && this.optional && console.log('optional');
>1 && this.optional && console.log('optional') : void | undefined
>1 && this.optional : (() => boolean) | undefined
>1 : 1
>this.optional : (() => boolean) | undefined
>this : this
>optional : (() => boolean) | undefined
>console.log('optional') : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>'optional' : "optional"
}
}

View file

@ -0,0 +1,83 @@
// @strictNullChecks: true
// @lib: esnext,dom
function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) {
// error
required1 && console.log('required');
// error
1 && required1 && console.log('required');
// ok
required1 && required1();
// ok
required1 && 1 && required1();
// ok
optional && console.log('optional');
// ok
1 && optional && console.log('optional');
// ok
!!required1 && console.log('not required');
// ok
required1() && console.log('required call');
// ok
required1 && required2 && required1() && required2();
// error
required1 && required2 && required1() && console.log('foo');
}
function checksConsole() {
// error
typeof window !== 'undefined' && window.console &&
((window.console as any).firebug || (window.console.exception && window.console.table));
}
function checksPropertyAccess() {
const x = {
foo: {
bar() { return true; }
}
}
// error
x.foo.bar && console.log('x.foo.bar');
// error
1 && x.foo.bar && console.log('x.foo.bar');
// ok
x.foo.bar && x.foo.bar();
// ok
x.foo.bar && 1 && x.foo.bar();
}
class Foo {
optional?: () => boolean;
required() {
return true;
}
test() {
// error
this.required && console.log('required');
// error
1 && this.required && console.log('required');
// ok
this.required && this.required();
// ok
this.required && 1 && this.required();
// ok
1 && this.optional && console.log('optional');
}
}

View file

@ -7,12 +7,14 @@
//// if (this.#test) {
//// console.log('test')
//// }
//// this.#test && console.log('test');
//// }
////}
////
////function foo() {
//// function test() { return Math.random() > 0.5; }
//// test ? console.log('test') : undefined;
//// test && console.log('test');
////}
////
////function foo() {
@ -23,6 +25,7 @@
//// }
//// x.foo.bar ? console.log('test') : undefined;
//// if (x.foo.bar) {}
//// x.foo.bar && console.log('test');
////}
verify.codeFixAll({
@ -35,12 +38,14 @@ verify.codeFixAll({
if (this.#test()) {
console.log('test')
}
this.#test() && console.log('test');
}
}
function foo() {
function test() { return Math.random() > 0.5; }
test() ? console.log('test') : undefined;
test() && console.log('test');
}
function foo() {
@ -51,5 +56,6 @@ function foo() {
}
x.foo.bar() ? console.log('test') : undefined;
if (x.foo.bar()) {}
x.foo.bar() && console.log('test');
}`,
});

View file

@ -0,0 +1,19 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////function foo(fn: () => boolean) {
//// fn/**/ && console.log('test');
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`function foo(fn: () => boolean) {
fn() && console.log('test');
}`,
});

View file

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////function foo() {
//// function test() { return Math.random() > 0.5; }
//// test/**/ && console.log('test');
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`function foo() {
function test() { return Math.random() > 0.5; }
test() && console.log('test');
}`,
});

View file

@ -0,0 +1,29 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////function foo() {
//// const x = {
//// foo: {
//// bar() { return true; }
//// }
//// }
//// x.foo.bar/**/ && console.log('test');
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`function foo() {
const x = {
foo: {
bar() { return true; }
}
}
x.foo.bar() && console.log('test');
}`,
});

View file

@ -0,0 +1,30 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////class Foo {
//// test() {
//// return true;
//// }
//// run() {
//// this.test/**/ && console.log('test');
//// }
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`class Foo {
test() {
return true;
}
run() {
this.test() && console.log('test');
}
}`,
});

View file

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////class Foo {
//// #test = () => true;
//// run() {
//// this.#test/**/ && console.log('test');
//// }
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`class Foo {
#test = () => true;
run() {
this.#test() && console.log('test');
}
}`,
});

View file

@ -0,0 +1,19 @@
/// <reference path='fourslash.ts'/>
// @strictNullChecks: true
////function foo(fn: () => boolean) {
//// 1 && fn/**/ && console.log('test');
////}
verify.codeFixAvailable([
{ description: ts.Diagnostics.Add_missing_call_parentheses.message }
]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_call_parentheses.message,
index: 0,
newFileContent:
`function foo(fn: () => boolean) {
1 && fn() && console.log('test');
}`,
});