From bf6be7513199bebe1f9549fe897cc77aa9aefe2c Mon Sep 17 00:00:00 2001 From: Alexander T Date: Thu, 20 Feb 2020 19:29:41 +0200 Subject: [PATCH] feat(36048): handle uncalled function checks in ternaries (#36402) --- src/compiler/checker.ts | 20 +- ...uthinessCallExpressionCoercion1.errors.txt | 85 +++++++ .../truthinessCallExpressionCoercion1.js | 122 ++++++++++ .../truthinessCallExpressionCoercion1.symbols | 175 ++++++++++++++ .../truthinessCallExpressionCoercion1.types | 228 ++++++++++++++++++ .../truthinessCallExpressionCoercion1.ts | 68 ++++++ 6 files changed, 688 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion1.js create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion1.symbols create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion1.types create mode 100644 tests/cases/compiler/truthinessCallExpressionCoercion1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfa4b0e63e..c3119b1e64 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); @@ -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) { @@ -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) { @@ -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) { @@ -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); } } diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt new file mode 100644 index 0000000000..6a32d1f050 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.errors.txt @@ -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; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.js b/tests/baselines/reference/truthinessCallExpressionCoercion1.js new file mode 100644 index 0000000000..4415cedf59 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.js @@ -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; +}()); diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.symbols b/tests/baselines/reference/truthinessCallExpressionCoercion1.symbols new file mode 100644 index 0000000000..1f729481ac --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.symbols @@ -0,0 +1,175 @@ +=== tests/cases/compiler/truthinessCallExpressionCoercion1.ts === +function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { +>onlyErrorsWhenTestingNonNullableFunctionType : Symbol(onlyErrorsWhenTestingNonNullableFunctionType, Decl(truthinessCallExpressionCoercion1.ts, 0, 0)) +>required : Symbol(required, Decl(truthinessCallExpressionCoercion1.ts, 0, 54)) +>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion1.ts, 0, 78)) + + // error + required ? console.log('required') : undefined; +>required : Symbol(required, Decl(truthinessCallExpressionCoercion1.ts, 0, 54)) +>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, --, --)) +>undefined : Symbol(undefined) + + // ok + optional ? console.log('optional') : undefined; +>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion1.ts, 0, 78)) +>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, --, --)) +>undefined : Symbol(undefined) + + // ok + !!required ? console.log('not required') : undefined; +>required : Symbol(required, Decl(truthinessCallExpressionCoercion1.ts, 0, 54)) +>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, --, --)) +>undefined : Symbol(undefined) + + // ok + required() ? console.log('required call') : undefined; +>required : Symbol(required, Decl(truthinessCallExpressionCoercion1.ts, 0, 54)) +>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, --, --)) +>undefined : Symbol(undefined) +} + +function onlyErrorsWhenUnusedInBody() { +>onlyErrorsWhenUnusedInBody : Symbol(onlyErrorsWhenUnusedInBody, Decl(truthinessCallExpressionCoercion1.ts, 12, 1)) + + function test() { return Math.random() > 0.5; } +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + // error + test ? console.log('test') : undefined; +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>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, --, --)) +>undefined : Symbol(undefined) + + // ok + test ? console.log(test) : undefined; +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>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, --, --)) +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>undefined : Symbol(undefined) + + // ok + test ? test() : undefined; +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) +>undefined : Symbol(undefined) + + // ok + test +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) + + ? [() => null].forEach(() => { test(); }) +>[() => null].forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) + + : undefined; +>undefined : Symbol(undefined) + + // error + test +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 14, 39)) + + ? [() => null].forEach(test => { test() }) +>[() => null].forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 33, 31)) +>test : Symbol(test, Decl(truthinessCallExpressionCoercion1.ts, 33, 31)) + + : undefined; +>undefined : Symbol(undefined) +} + +function checksPropertyAccess() { +>checksPropertyAccess : Symbol(checksPropertyAccess, Decl(truthinessCallExpressionCoercion1.ts, 35, 1)) + + const x = { +>x : Symbol(x, Decl(truthinessCallExpressionCoercion1.ts, 38, 9)) + + foo: { +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) + + bar() { return true; } +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) + } + } + + // error + x.foo.bar ? console.log('x.foo.bar') : undefined; +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion1.ts, 38, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 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, --, --)) +>undefined : Symbol(undefined) + + // ok + x.foo.bar ? x.foo.bar : undefined; +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion1.ts, 38, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion1.ts, 38, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion1.ts, 38, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion1.ts, 39, 14)) +>undefined : Symbol(undefined) +} + +class Foo { +>Foo : Symbol(Foo, Decl(truthinessCallExpressionCoercion1.ts, 49, 1)) + + maybeIsUser?: () => boolean; +>maybeIsUser : Symbol(Foo.maybeIsUser, Decl(truthinessCallExpressionCoercion1.ts, 51, 11)) + + isUser() { +>isUser : Symbol(Foo.isUser, Decl(truthinessCallExpressionCoercion1.ts, 52, 32)) + + return true; + } + + test() { +>test : Symbol(Foo.test, Decl(truthinessCallExpressionCoercion1.ts, 56, 5)) + + // error + this.isUser ? console.log('this.isUser') : undefined; +>this.isUser : Symbol(Foo.isUser, Decl(truthinessCallExpressionCoercion1.ts, 52, 32)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion1.ts, 49, 1)) +>isUser : Symbol(Foo.isUser, Decl(truthinessCallExpressionCoercion1.ts, 52, 32)) +>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, --, --)) +>undefined : Symbol(undefined) + + // ok + this.maybeIsUser ? console.log('this.maybeIsUser') : undefined; +>this.maybeIsUser : Symbol(Foo.maybeIsUser, Decl(truthinessCallExpressionCoercion1.ts, 51, 11)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion1.ts, 49, 1)) +>maybeIsUser : Symbol(Foo.maybeIsUser, Decl(truthinessCallExpressionCoercion1.ts, 51, 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, --, --)) +>undefined : Symbol(undefined) + } +} + diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion1.types b/tests/baselines/reference/truthinessCallExpressionCoercion1.types new file mode 100644 index 0000000000..37667c1697 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion1.types @@ -0,0 +1,228 @@ +=== tests/cases/compiler/truthinessCallExpressionCoercion1.ts === +function onlyErrorsWhenTestingNonNullableFunctionType(required: () => boolean, optional?: () => boolean) { +>onlyErrorsWhenTestingNonNullableFunctionType : (required: () => boolean, optional?: (() => boolean) | undefined) => void +>required : () => boolean +>optional : (() => boolean) | undefined + + // error + required ? console.log('required') : undefined; +>required ? console.log('required') : undefined : void +>required : () => boolean +>console.log('required') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'required' : "required" +>undefined : undefined + + // ok + optional ? console.log('optional') : undefined; +>optional ? console.log('optional') : undefined : void +>optional : (() => boolean) | undefined +>console.log('optional') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'optional' : "optional" +>undefined : undefined + + // ok + !!required ? console.log('not required') : undefined; +>!!required ? console.log('not required') : undefined : void +>!!required : true +>!required : false +>required : () => boolean +>console.log('not required') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'not required' : "not required" +>undefined : undefined + + // ok + required() ? console.log('required call') : undefined; +>required() ? console.log('required call') : undefined : void +>required() : boolean +>required : () => boolean +>console.log('required call') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'required call' : "required call" +>undefined : undefined +} + +function onlyErrorsWhenUnusedInBody() { +>onlyErrorsWhenUnusedInBody : () => void + + function test() { return Math.random() > 0.5; } +>test : () => boolean +>Math.random() > 0.5 : boolean +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>0.5 : 0.5 + + // error + test ? console.log('test') : undefined; +>test ? console.log('test') : undefined : void +>test : () => boolean +>console.log('test') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'test' : "test" +>undefined : undefined + + // ok + test ? console.log(test) : undefined; +>test ? console.log(test) : undefined : void +>test : () => boolean +>console.log(test) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>test : () => boolean +>undefined : undefined + + // ok + test ? test() : undefined; +>test ? test() : undefined : boolean | undefined +>test : () => boolean +>test() : boolean +>test : () => boolean +>undefined : undefined + + // ok + test +>test ? [() => null].forEach(() => { test(); }) : undefined : void +>test : () => boolean + + ? [() => null].forEach(() => { test(); }) +>[() => null].forEach(() => { test(); }) : void +>[() => null].forEach : (callbackfn: (value: () => null, index: number, array: (() => null)[]) => void, thisArg?: any) => void +>[() => null] : (() => null)[] +>() => null : () => null +>null : null +>forEach : (callbackfn: (value: () => null, index: number, array: (() => null)[]) => void, thisArg?: any) => void +>() => { test(); } : () => void +>test() : boolean +>test : () => boolean + + : undefined; +>undefined : undefined + + // error + test +>test ? [() => null].forEach(test => { test() }) : undefined : void +>test : () => boolean + + ? [() => null].forEach(test => { test() }) +>[() => null].forEach(test => { test() }) : void +>[() => null].forEach : (callbackfn: (value: () => null, index: number, array: (() => null)[]) => void, thisArg?: any) => void +>[() => null] : (() => null)[] +>() => null : () => null +>null : null +>forEach : (callbackfn: (value: () => null, index: number, array: (() => null)[]) => void, thisArg?: any) => void +>test => { test() } : (test: () => null) => void +>test : () => null +>test() : null +>test : () => null + + : undefined; +>undefined : undefined +} + +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') : undefined; +>x.foo.bar ? console.log('x.foo.bar') : undefined : 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 : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'x.foo.bar' : "x.foo.bar" +>undefined : undefined + + // ok + x.foo.bar ? x.foo.bar : undefined; +>x.foo.bar ? x.foo.bar : undefined : (() => boolean) | undefined +>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; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +>undefined : undefined +} + +class Foo { +>Foo : Foo + + maybeIsUser?: () => boolean; +>maybeIsUser : (() => boolean) | undefined + + isUser() { +>isUser : () => boolean + + return true; +>true : true + } + + test() { +>test : () => void + + // error + this.isUser ? console.log('this.isUser') : undefined; +>this.isUser ? console.log('this.isUser') : undefined : void +>this.isUser : () => boolean +>this : this +>isUser : () => boolean +>console.log('this.isUser') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'this.isUser' : "this.isUser" +>undefined : undefined + + // ok + this.maybeIsUser ? console.log('this.maybeIsUser') : undefined; +>this.maybeIsUser ? console.log('this.maybeIsUser') : undefined : void +>this.maybeIsUser : (() => boolean) | undefined +>this : this +>maybeIsUser : (() => boolean) | undefined +>console.log('this.maybeIsUser') : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>'this.maybeIsUser' : "this.maybeIsUser" +>undefined : undefined + } +} + diff --git a/tests/cases/compiler/truthinessCallExpressionCoercion1.ts b/tests/cases/compiler/truthinessCallExpressionCoercion1.ts new file mode 100644 index 0000000000..04331cbead --- /dev/null +++ b/tests/cases/compiler/truthinessCallExpressionCoercion1.ts @@ -0,0 +1,68 @@ +// @strictNullChecks:true + +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; + } +}