Skip to content
Merged
20 changes: 10 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27929,7 +27929,8 @@ namespace ts {
}

function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
checkTruthinessExpression(node.condition);
const type = checkTruthinessExpression(node.condition);
checkTestingKnownTruthyCallableType(node.condition, node.whenTrue, type);
const type1 = checkExpression(node.whenTrue, checkMode);
const type2 = checkExpression(node.whenFalse, checkMode);
return getUnionType([type1, type2], UnionReduction.Subtype);
Expand Down Expand Up @@ -31051,9 +31052,8 @@ namespace ts {
function checkIfStatement(node: IfStatement) {
// Grammar checking
checkGrammarStatementInAmbientContext(node);

const type = checkTruthinessExpression(node.expression);
checkTestingKnownTruthyCallableType(node, type);
checkTestingKnownTruthyCallableType(node.expression, node.thenStatement, type);
checkSourceElement(node.thenStatement);

if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
Expand All @@ -31063,15 +31063,15 @@ namespace ts {
checkSourceElement(node.elseStatement);
}

function checkTestingKnownTruthyCallableType(ifStatement: IfStatement, type: Type) {
function checkTestingKnownTruthyCallableType(condExpr: Expression, body: Statement | Expression, type: Type) {
if (!strictNullChecks) {
return;
}

const testedNode = isIdentifier(ifStatement.expression)
? ifStatement.expression
: isPropertyAccessExpression(ifStatement.expression)
? ifStatement.expression.name
const testedNode = isIdentifier(condExpr)
? condExpr
: isPropertyAccessExpression(condExpr)
? condExpr.name
: undefined;

if (!testedNode) {
Expand All @@ -31098,7 +31098,7 @@ namespace ts {
return;
}

const functionIsUsedInBody = forEachChild(ifStatement.thenStatement, function check(childNode): boolean | undefined {
const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined {
if (isIdentifier(childNode)) {
const childSymbol = getSymbolAtLocation(childNode);
if (childSymbol && childSymbol.id === testedFunctionSymbol.id) {
Expand All @@ -31110,7 +31110,7 @@ namespace ts {
});

if (!functionIsUsedInBody) {
error(ifStatement.expression, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
error(condExpr, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
tests/cases/compiler/truthinessCallExpressionCoercion1.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/truthinessCallExpressionCoercion1.ts(19,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/truthinessCallExpressionCoercion1.ts(33,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/truthinessCallExpressionCoercion1.ts(46,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/truthinessCallExpressionCoercion1.ts(61,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/truthinessCallExpressionCoercion1.ts (5 errors) ====
function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) {
// error
required ? console.log('required') : undefined;
~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?

// ok
optional ? console.log('optional') : undefined;

// ok
!!required ? console.log('not required') : undefined;

// ok
required() ? console.log('required call') : undefined;
}

function onlyErrorsWhenUnusedInBody() {
function test() { return Math.random() > 0.5; }

// error
test ? console.log('test') : undefined;
~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?

// ok
test ? console.log(test) : undefined;

// ok
test ? test() : undefined;

// ok
test
? [() => null].forEach(() => { test(); })
: undefined;

// error
test
~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
? [() => null].forEach(test => { test() })
: undefined;
}

function checksPropertyAccess() {
const x = {
foo: {
bar() { return true; }
}
}

// error
x.foo.bar ? console.log('x.foo.bar') : undefined;
~~~~~~~~~
!!! 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 : undefined;
}

class Foo {
maybeIsUser?: () => boolean;

isUser() {
return true;
}

test() {
// error
this.isUser ? console.log('this.isUser') : undefined;
~~~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?

// ok
this.maybeIsUser ? console.log('this.maybeIsUser') : undefined;
}
}

122 changes: 122 additions & 0 deletions tests/baselines/reference/truthinessCallExpressionCoercion1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//// [truthinessCallExpressionCoercion1.ts]
function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) {
// error
required ? console.log('required') : undefined;

// ok
optional ? console.log('optional') : undefined;

// ok
!!required ? console.log('not required') : undefined;

// ok
required() ? console.log('required call') : undefined;
}

function onlyErrorsWhenUnusedInBody() {
function test() { return Math.random() > 0.5; }

// error
test ? console.log('test') : undefined;

// ok
test ? console.log(test) : undefined;

// ok
test ? test() : undefined;

// ok
test
? [() => null].forEach(() => { test(); })
: undefined;

// error
test
? [() => null].forEach(test => { test() })
: undefined;
}

function checksPropertyAccess() {
const x = {
foo: {
bar() { return true; }
}
}

// error
x.foo.bar ? console.log('x.foo.bar') : undefined;

// ok
x.foo.bar ? x.foo.bar : undefined;
}

class Foo {
maybeIsUser?: () => boolean;

isUser() {
return true;
}

test() {
// error
this.isUser ? console.log('this.isUser') : undefined;

// ok
this.maybeIsUser ? console.log('this.maybeIsUser') : undefined;
}
}


//// [truthinessCallExpressionCoercion1.js]
function onlyErrorsWhenTestingNonNullableFunctionType(required, optional) {
// error
required ? console.log('required') : undefined;
// ok
optional ? console.log('optional') : undefined;
// ok
!!required ? console.log('not required') : undefined;
// ok
required() ? console.log('required call') : undefined;
}
function onlyErrorsWhenUnusedInBody() {
function test() { return Math.random() > 0.5; }
// error
test ? console.log('test') : undefined;
// ok
test ? console.log(test) : undefined;
// ok
test ? test() : undefined;
// ok
test
? [function () { return null; }].forEach(function () { test(); })
: undefined;
// error
test
? [function () { return null; }].forEach(function (test) { test(); })
: undefined;
}
function checksPropertyAccess() {
var x = {
foo: {
bar: function () { return true; }
}
};
// error
x.foo.bar ? console.log('x.foo.bar') : undefined;
// ok
x.foo.bar ? x.foo.bar : undefined;
}
var Foo = /** @class */ (function () {
function Foo() {
}
Foo.prototype.isUser = function () {
return true;
};
Foo.prototype.test = function () {
// error
this.isUser ? console.log('this.isUser') : undefined;
// ok
this.maybeIsUser ? console.log('this.maybeIsUser') : undefined;
};
return Foo;
}());
Loading