Improve uncalled function check related to parenthesized and binary expressions (#50756)

fixes https://github.com/microsoft/TypeScript/issues/37598
This commit is contained in:
Mateusz Burzyński
2023-01-17 21:22:16 +00:00
committed by GitHub
parent f6fc444b33
commit 16c695cdbb
14 changed files with 989 additions and 163 deletions

View File

@@ -172,7 +172,10 @@ import {
isJSDocTypeAlias,
isJsonSourceFile,
isLeftHandSideExpression,
isLogicalOrCoalescingAssignmentExpression,
isLogicalOrCoalescingAssignmentOperator,
isLogicalOrCoalescingBinaryExpression,
isLogicalOrCoalescingBinaryOperator,
isModuleAugmentationExternal,
isModuleBlock,
isModuleDeclaration,
@@ -1377,17 +1380,13 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
node = (node as PrefixUnaryExpression).operand;
}
else {
return node.kind === SyntaxKind.BinaryExpression && (
(node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken ||
(node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken ||
(node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken);
return isLogicalOrCoalescingBinaryExpression(node);
}
}
}
function isLogicalAssignmentExpression(node: Node) {
node = skipParentheses(node);
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
return isLogicalOrCoalescingAssignmentExpression(skipParentheses(node));
}
function isTopLevelLogicalExpression(node: Node): boolean {
@@ -1859,10 +1858,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken ||
operator === SyntaxKind.BarBarToken ||
operator === SyntaxKind.QuestionQuestionToken ||
isLogicalOrCoalescingAssignmentOperator(operator)) {
if (isLogicalOrCoalescingBinaryOperator(operator) || isLogicalOrCoalescingAssignmentOperator(operator)) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);

View File

@@ -591,6 +591,8 @@ import {
isLiteralExpressionOfObject,
isLiteralImportTypeNode,
isLiteralTypeNode,
isLogicalOrCoalescingBinaryExpression,
isLogicalOrCoalescingBinaryOperator,
isMetaProperty,
isMethodDeclaration,
isMethodSignature,
@@ -35561,13 +35563,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
setLeftType(state, leftType);
setLastResult(state, /*type*/ undefined);
const operator = operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (operator === SyntaxKind.AmpersandAmpersandToken) {
let parent = node.parent;
while (parent.kind === SyntaxKind.ParenthesizedExpression
|| isBinaryExpression(parent) && (parent.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === SyntaxKind.BarBarToken)) {
parent = parent.parent;
}
if (isLogicalOrCoalescingBinaryOperator(operator)) {
let parent = node.parent;
while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) {
parent = parent.parent;
}
if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) {
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
}
checkTruthinessOfType(leftType, node.left);
@@ -35656,7 +35657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword);
}
let leftType: Type;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (isLogicalOrCoalescingBinaryOperator(operator)) {
leftType = checkTruthinessExpression(left, checkMode);
}
else {
@@ -39921,19 +39922,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) {
if (!strictNullChecks) return;
bothHelper(condExpr, body);
function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) {
condExpr = skipParentheses(condExpr);
helper(condExpr, body);
while (isBinaryExpression(condExpr) && condExpr.operatorToken.kind === SyntaxKind.BarBarToken) {
condExpr = condExpr.left;
helper(condExpr, body);
while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) {
condExpr = skipParentheses(condExpr.left);
helper(condExpr, body);
}
}
function helper(condExpr: Expression, body: Expression | Statement | undefined) {
const location = isBinaryExpression(condExpr) &&
(condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)
? condExpr.right
: condExpr;
if (isModuleExportsAccessExpression(location)) return;
const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr;
if (isModuleExportsAccessExpression(location)) {
return;
}
if (isLogicalOrCoalescingBinaryExpression(location)) {
bothHelper(location, body);
return;
}
const type = location === condExpr ? condType : checkTruthinessExpression(location);
const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression);
if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return;
@@ -39951,7 +39961,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const testedNode = isIdentifier(location) ? location
: isPropertyAccessExpression(location) ? location.name
: isBinaryExpression(location) && isIdentifier(location.right) ? location.right
: undefined;
const testedSymbol = testedNode && getSymbolAtLocation(testedNode);
if (!testedSymbol && !isPromise) {

View File

@@ -1,6 +1,5 @@
import {
AssignmentExpression,
BinaryExpression,
Bundle,
chainBundle,
getNonAssignmentOperatorForCompoundAssignment,
@@ -14,7 +13,6 @@ import {
Node,
skipParentheses,
SourceFile,
SyntaxKind,
Token,
TransformationContext,
TransformFlags,
@@ -43,16 +41,10 @@ export function transformES2021(context: TransformationContext): (x: SourceFile
if ((node.transformFlags & TransformFlags.ContainsES2021) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.BinaryExpression:
const binaryExpression = node as BinaryExpression;
if (isLogicalOrCoalescingAssignmentExpression(binaryExpression)) {
return transformLogicalAssignment(binaryExpression);
}
// falls through
default:
return visitEachChild(node, visitor, context);
if (isLogicalOrCoalescingAssignmentExpression(node)) {
return transformLogicalAssignment(node);
}
return visitEachChild(node, visitor, context);
}
function transformLogicalAssignment(binaryExpression: AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>>): VisitResult<Node> {

View File

@@ -362,6 +362,7 @@ import {
LiteralImportTypeNode,
LiteralLikeElementAccessExpression,
LiteralLikeNode,
LogicalOperator,
LogicalOrCoalescingAssignmentOperator,
map,
mapDefined,
@@ -6246,11 +6247,13 @@ export function modifierToFlag(token: SyntaxKind): ModifierFlags {
return ModifierFlags.None;
}
function isBinaryLogicalOperator(token: SyntaxKind): boolean {
return token === SyntaxKind.BarBarToken || token === SyntaxKind.AmpersandAmpersandToken;
}
/** @internal */
export function isLogicalOperator(token: SyntaxKind): boolean {
return token === SyntaxKind.BarBarToken
|| token === SyntaxKind.AmpersandAmpersandToken
|| token === SyntaxKind.ExclamationToken;
return isBinaryLogicalOperator(token) || token === SyntaxKind.ExclamationToken;
}
/** @internal */
@@ -6261,8 +6264,18 @@ export function isLogicalOrCoalescingAssignmentOperator(token: SyntaxKind): toke
}
/** @internal */
export function isLogicalOrCoalescingAssignmentExpression(expr: BinaryExpression): expr is AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>> {
return isLogicalOrCoalescingAssignmentOperator(expr.operatorToken.kind);
export function isLogicalOrCoalescingAssignmentExpression(expr: Node): expr is AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>> {
return isBinaryExpression(expr) && isLogicalOrCoalescingAssignmentOperator(expr.operatorToken.kind);
}
/** @internal */
export function isLogicalOrCoalescingBinaryOperator(token: SyntaxKind): token is LogicalOperator | SyntaxKind.QuestionQuestionToken {
return isBinaryLogicalOperator(token) || token === SyntaxKind.QuestionQuestionToken;
}
/** @internal */
export function isLogicalOrCoalescingBinaryExpression(expr: Node): expr is BinaryExpression {
return isBinaryExpression(expr) && isLogicalOrCoalescingBinaryOperator(expr.operatorToken.kind);
}
/** @internal */