Skip to content

Commit 16c695c

Browse files
authored
Improve uncalled function check related to parenthesized and binary expressions (#50756)
fixes #37598
1 parent f6fc444 commit 16c695c

14 files changed

+989
-163
lines changed

src/compiler/binder.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ import {
172172
isJSDocTypeAlias,
173173
isJsonSourceFile,
174174
isLeftHandSideExpression,
175+
isLogicalOrCoalescingAssignmentExpression,
175176
isLogicalOrCoalescingAssignmentOperator,
177+
isLogicalOrCoalescingBinaryExpression,
178+
isLogicalOrCoalescingBinaryOperator,
176179
isModuleAugmentationExternal,
177180
isModuleBlock,
178181
isModuleDeclaration,
@@ -1377,17 +1380,13 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
13771380
node = (node as PrefixUnaryExpression).operand;
13781381
}
13791382
else {
1380-
return node.kind === SyntaxKind.BinaryExpression && (
1381-
(node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken ||
1382-
(node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken ||
1383-
(node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken);
1383+
return isLogicalOrCoalescingBinaryExpression(node);
13841384
}
13851385
}
13861386
}
13871387

13881388
function isLogicalAssignmentExpression(node: Node) {
1389-
node = skipParentheses(node);
1390-
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
1389+
return isLogicalOrCoalescingAssignmentExpression(skipParentheses(node));
13911390
}
13921391

13931392
function isTopLevelLogicalExpression(node: Node): boolean {
@@ -1859,10 +1858,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
18591858
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
18601859
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
18611860
const operator = node.operatorToken.kind;
1862-
if (operator === SyntaxKind.AmpersandAmpersandToken ||
1863-
operator === SyntaxKind.BarBarToken ||
1864-
operator === SyntaxKind.QuestionQuestionToken ||
1865-
isLogicalOrCoalescingAssignmentOperator(operator)) {
1861+
if (isLogicalOrCoalescingBinaryOperator(operator) || isLogicalOrCoalescingAssignmentOperator(operator)) {
18661862
if (isTopLevelLogicalExpression(node)) {
18671863
const postExpressionLabel = createBranchLabel();
18681864
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);

src/compiler/checker.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,8 @@ import {
591591
isLiteralExpressionOfObject,
592592
isLiteralImportTypeNode,
593593
isLiteralTypeNode,
594+
isLogicalOrCoalescingBinaryExpression,
595+
isLogicalOrCoalescingBinaryOperator,
594596
isMetaProperty,
595597
isMethodDeclaration,
596598
isMethodSignature,
@@ -35561,13 +35563,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3556135563
setLeftType(state, leftType);
3556235564
setLastResult(state, /*type*/ undefined);
3556335565
const operator = operatorToken.kind;
35564-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
35565-
if (operator === SyntaxKind.AmpersandAmpersandToken) {
35566-
let parent = node.parent;
35567-
while (parent.kind === SyntaxKind.ParenthesizedExpression
35568-
|| isBinaryExpression(parent) && (parent.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === SyntaxKind.BarBarToken)) {
35569-
parent = parent.parent;
35570-
}
35566+
if (isLogicalOrCoalescingBinaryOperator(operator)) {
35567+
let parent = node.parent;
35568+
while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) {
35569+
parent = parent.parent;
35570+
}
35571+
if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) {
3557135572
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
3557235573
}
3557335574
checkTruthinessOfType(leftType, node.left);
@@ -35656,7 +35657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3565635657
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword);
3565735658
}
3565835659
let leftType: Type;
35659-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
35660+
if (isLogicalOrCoalescingBinaryOperator(operator)) {
3566035661
leftType = checkTruthinessExpression(left, checkMode);
3566135662
}
3566235663
else {
@@ -39921,19 +39922,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3992139922

3992239923
function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) {
3992339924
if (!strictNullChecks) return;
39925+
bothHelper(condExpr, body);
39926+
39927+
function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) {
39928+
condExpr = skipParentheses(condExpr);
3992439929

39925-
helper(condExpr, body);
39926-
while (isBinaryExpression(condExpr) && condExpr.operatorToken.kind === SyntaxKind.BarBarToken) {
39927-
condExpr = condExpr.left;
3992839930
helper(condExpr, body);
39931+
39932+
while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) {
39933+
condExpr = skipParentheses(condExpr.left);
39934+
helper(condExpr, body);
39935+
}
3992939936
}
3993039937

3993139938
function helper(condExpr: Expression, body: Expression | Statement | undefined) {
39932-
const location = isBinaryExpression(condExpr) &&
39933-
(condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)
39934-
? condExpr.right
39935-
: condExpr;
39936-
if (isModuleExportsAccessExpression(location)) return;
39939+
const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr;
39940+
if (isModuleExportsAccessExpression(location)) {
39941+
return;
39942+
}
39943+
if (isLogicalOrCoalescingBinaryExpression(location)) {
39944+
bothHelper(location, body);
39945+
return;
39946+
}
3993739947
const type = location === condExpr ? condType : checkTruthinessExpression(location);
3993839948
const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression);
3993939949
if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return;
@@ -39951,7 +39961,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3995139961

3995239962
const testedNode = isIdentifier(location) ? location
3995339963
: isPropertyAccessExpression(location) ? location.name
39954-
: isBinaryExpression(location) && isIdentifier(location.right) ? location.right
3995539964
: undefined;
3995639965
const testedSymbol = testedNode && getSymbolAtLocation(testedNode);
3995739966
if (!testedSymbol && !isPromise) {

src/compiler/transformers/es2021.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
AssignmentExpression,
3-
BinaryExpression,
43
Bundle,
54
chainBundle,
65
getNonAssignmentOperatorForCompoundAssignment,
@@ -14,7 +13,6 @@ import {
1413
Node,
1514
skipParentheses,
1615
SourceFile,
17-
SyntaxKind,
1816
Token,
1917
TransformationContext,
2018
TransformFlags,
@@ -43,16 +41,10 @@ export function transformES2021(context: TransformationContext): (x: SourceFile
4341
if ((node.transformFlags & TransformFlags.ContainsES2021) === 0) {
4442
return node;
4543
}
46-
switch (node.kind) {
47-
case SyntaxKind.BinaryExpression:
48-
const binaryExpression = node as BinaryExpression;
49-
if (isLogicalOrCoalescingAssignmentExpression(binaryExpression)) {
50-
return transformLogicalAssignment(binaryExpression);
51-
}
52-
// falls through
53-
default:
54-
return visitEachChild(node, visitor, context);
44+
if (isLogicalOrCoalescingAssignmentExpression(node)) {
45+
return transformLogicalAssignment(node);
5546
}
47+
return visitEachChild(node, visitor, context);
5648
}
5749

5850
function transformLogicalAssignment(binaryExpression: AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>>): VisitResult<Node> {

src/compiler/utilities.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ import {
362362
LiteralImportTypeNode,
363363
LiteralLikeElementAccessExpression,
364364
LiteralLikeNode,
365+
LogicalOperator,
365366
LogicalOrCoalescingAssignmentOperator,
366367
map,
367368
mapDefined,
@@ -6246,11 +6247,13 @@ export function modifierToFlag(token: SyntaxKind): ModifierFlags {
62466247
return ModifierFlags.None;
62476248
}
62486249

6250+
function isBinaryLogicalOperator(token: SyntaxKind): boolean {
6251+
return token === SyntaxKind.BarBarToken || token === SyntaxKind.AmpersandAmpersandToken;
6252+
}
6253+
62496254
/** @internal */
62506255
export function isLogicalOperator(token: SyntaxKind): boolean {
6251-
return token === SyntaxKind.BarBarToken
6252-
|| token === SyntaxKind.AmpersandAmpersandToken
6253-
|| token === SyntaxKind.ExclamationToken;
6256+
return isBinaryLogicalOperator(token) || token === SyntaxKind.ExclamationToken;
62546257
}
62556258

62566259
/** @internal */
@@ -6261,8 +6264,18 @@ export function isLogicalOrCoalescingAssignmentOperator(token: SyntaxKind): toke
62616264
}
62626265

62636266
/** @internal */
6264-
export function isLogicalOrCoalescingAssignmentExpression(expr: BinaryExpression): expr is AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>> {
6265-
return isLogicalOrCoalescingAssignmentOperator(expr.operatorToken.kind);
6267+
export function isLogicalOrCoalescingAssignmentExpression(expr: Node): expr is AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>> {
6268+
return isBinaryExpression(expr) && isLogicalOrCoalescingAssignmentOperator(expr.operatorToken.kind);
6269+
}
6270+
6271+
/** @internal */
6272+
export function isLogicalOrCoalescingBinaryOperator(token: SyntaxKind): token is LogicalOperator | SyntaxKind.QuestionQuestionToken {
6273+
return isBinaryLogicalOperator(token) || token === SyntaxKind.QuestionQuestionToken;
6274+
}
6275+
6276+
/** @internal */
6277+
export function isLogicalOrCoalescingBinaryExpression(expr: Node): expr is BinaryExpression {
6278+
return isBinaryExpression(expr) && isLogicalOrCoalescingBinaryOperator(expr.operatorToken.kind);
62666279
}
62676280

62686281
/** @internal */

0 commit comments

Comments
 (0)