Skip to content

Commit c48e34e

Browse files
committed
Did you forget to use await? for operators
1 parent fb50920 commit c48e34e

File tree

4 files changed

+146
-28
lines changed

4 files changed

+146
-28
lines changed

src/compiler/checker.ts

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,18 @@ namespace ts {
932932
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message));
933933
}
934934

935+
function errorAndMaybeSuggestAwait(
936+
location: Node | undefined,
937+
maybeMissingAwait: boolean,
938+
defaultMessage: DiagnosticMessage,
939+
missingAwaitMessage: DiagnosticMessage,
940+
arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic {
941+
if (maybeMissingAwait) {
942+
return error(location, missingAwaitMessage, arg0, arg1, arg2, arg3);
943+
}
944+
return error(location, defaultMessage, arg0, arg1, arg2, arg3);
945+
}
946+
935947
function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) {
936948
symbolCount++;
937949
const symbol = <TransientSymbol>(new Symbol(flags | SymbolFlags.Transient, name));
@@ -23662,9 +23674,14 @@ namespace ts {
2366223674
}
2366323675
}
2366423676

23665-
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
23677+
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, missingAwaitDiagnostic: DiagnosticMessage): boolean {
2366623678
if (!isTypeAssignableTo(type, numberOrBigIntType)) {
23667-
error(operand, diagnostic);
23679+
const awaitedType = getAwaitedType(type);
23680+
errorAndMaybeSuggestAwait(
23681+
operand,
23682+
!!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType),
23683+
diagnostic,
23684+
missingAwaitDiagnostic);
2366823685
return false;
2366923686
}
2367023687
return true;
@@ -23862,7 +23879,8 @@ namespace ts {
2386223879
case SyntaxKind.PlusPlusToken:
2386323880
case SyntaxKind.MinusMinusToken:
2386423881
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
23865-
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
23882+
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type,
23883+
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
2386623884
if (ok) {
2386723885
// run check only if former checks succeeded to avoid reporting cascading errors
2386823886
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
@@ -23880,7 +23898,8 @@ namespace ts {
2388023898
const ok = checkArithmeticOperandType(
2388123899
node.operand,
2388223900
checkNonNullType(operandType, node.operand),
23883-
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
23901+
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type,
23902+
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
2388423903
if (ok) {
2388523904
// run check only if former checks succeeded to avoid reporting cascading errors
2388623905
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
@@ -24272,8 +24291,8 @@ namespace ts {
2427224291
}
2427324292
else {
2427424293
// otherwise just check each operand separately and report errors as normal
24275-
const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type);
24276-
const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type);
24294+
const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
24295+
const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
2427724296
let resultType: Type;
2427824297
// If both are any or unknown, allow operation; assume it will resolve to number
2427924298
if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) ||
@@ -24282,7 +24301,7 @@ namespace ts {
2428224301
) {
2428324302
resultType = numberType;
2428424303
}
24285-
// At least one is assignable to bigint, so both should be only assignable to bigint
24304+
// At least one is assignable to bigint, so check that both are
2428624305
else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike)) {
2428724306
switch (operator) {
2428824307
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
@@ -24291,8 +24310,9 @@ namespace ts {
2429124310
}
2429224311
resultType = bigintType;
2429324312
}
24313+
// Exactly one of leftType/rightType is assignable to bigint
2429424314
else {
24295-
reportOperatorError();
24315+
reportOperatorError((awaitedLeft, awaitedRight) => isTypeAssignableToKind(awaitedLeft, TypeFlags.BigIntLike) && isTypeAssignableToKind(awaitedRight, TypeFlags.BigIntLike));
2429624316
resultType = errorType;
2429724317
}
2429824318
if (leftOk && rightOk) {
@@ -24337,7 +24357,14 @@ namespace ts {
2433724357
}
2433824358

2433924359
if (!resultType) {
24340-
reportOperatorError();
24360+
// Types that have a reasonably good chance of being a valid operand type.
24361+
// If both types have an awaited type of one of these, we’ll assume the user
24362+
// might be missing an await without doing an exhaustive check that inserting
24363+
// await(s) will actually be a completely valid binary expression.
24364+
const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown;
24365+
reportOperatorError((awaitedLeft, awaitedRight) =>
24366+
isTypeAssignableToKind(awaitedLeft, closeEnoughKind) &&
24367+
isTypeAssignableToKind(awaitedRight, closeEnoughKind));
2434124368
return anyType;
2434224369
}
2434324370

@@ -24352,21 +24379,18 @@ namespace ts {
2435224379
if (checkForDisallowedESSymbolOperand(operator)) {
2435324380
leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left));
2435424381
rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right));
24355-
if (!(isTypeComparableTo(leftType, rightType) || isTypeComparableTo(rightType, leftType) ||
24356-
(isTypeAssignableTo(leftType, numberOrBigIntType) && isTypeAssignableTo(rightType, numberOrBigIntType))
24357-
)) {
24358-
reportOperatorError();
24359-
}
24382+
reportOperatorErrorUnless((left, right) =>
24383+
isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || (
24384+
isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType)));
2436024385
}
2436124386
return booleanType;
2436224387
case SyntaxKind.EqualsEqualsToken:
2436324388
case SyntaxKind.ExclamationEqualsToken:
2436424389
case SyntaxKind.EqualsEqualsEqualsToken:
2436524390
case SyntaxKind.ExclamationEqualsEqualsToken:
24366-
if (!isTypeEqualityComparableTo(leftType, rightType) && !isTypeEqualityComparableTo(rightType, leftType)) {
24367-
reportOperatorError();
24368-
}
24391+
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
2436924392
return booleanType;
24393+
2437024394
case SyntaxKind.InstanceOfKeyword:
2437124395
return checkInstanceOfExpression(left, right, leftType, rightType);
2437224396
case SyntaxKind.InKeyword:
@@ -24493,29 +24517,61 @@ namespace ts {
2449324517
}
2449424518
}
2449524519

24496-
function reportOperatorError() {
24497-
const [leftStr, rightStr] = getTypeNamesForErrorDisplay(leftType, rightType);
24520+
/**
24521+
* Returns true if an error is reported
24522+
*/
24523+
function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean {
24524+
if (!typesAreCompatible(leftType, rightType)) {
24525+
reportOperatorError(typesAreCompatible);
24526+
return true;
24527+
}
24528+
return false;
24529+
}
24530+
24531+
function reportOperatorError(awaitedTypesAreCompatible?: (left: Type, right: Type) => boolean) {
24532+
let wouldWorkWithAwait = false;
2449824533
const errNode = errorNode || operatorToken;
24499-
if (!tryGiveBetterPrimaryError(errNode, leftStr, rightStr)) {
24500-
error(
24534+
const [leftStr, rightStr] = getTypeNamesForErrorDisplay(leftType, rightType);
24535+
if (awaitedTypesAreCompatible) {
24536+
const awaitedLeftType = getAwaitedType(leftType);
24537+
const awaitedRightType = getAwaitedType(rightType);
24538+
wouldWorkWithAwait = !!(awaitedLeftType && awaitedRightType) && awaitedTypesAreCompatible(awaitedLeftType, awaitedRightType);
24539+
}
24540+
24541+
if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) {
24542+
errorAndMaybeSuggestAwait(
2450124543
errNode,
24544+
wouldWorkWithAwait,
2450224545
Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2,
24546+
Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2_Did_you_forget_to_use_await,
2450324547
tokenToString(operatorToken.kind),
2450424548
leftStr,
2450524549
rightStr,
2450624550
);
2450724551
}
2450824552
}
2450924553

24510-
function tryGiveBetterPrimaryError(errNode: Node, leftStr: string, rightStr: string) {
24554+
function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) {
24555+
let typeName: string | undefined;
2451124556
switch (operatorToken.kind) {
2451224557
case SyntaxKind.EqualsEqualsEqualsToken:
2451324558
case SyntaxKind.EqualsEqualsToken:
24514-
return error(errNode, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, "false", leftStr, rightStr);
24559+
typeName = "true";
24560+
break;
2451524561
case SyntaxKind.ExclamationEqualsEqualsToken:
2451624562
case SyntaxKind.ExclamationEqualsToken:
24517-
return error(errNode, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, "true", leftStr, rightStr);
24518-
}
24563+
typeName = "false";
24564+
}
24565+
24566+
if (typeName) {
24567+
return errorAndMaybeSuggestAwait(
24568+
errNode,
24569+
maybeMissingAwait,
24570+
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap,
24571+
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap_Did_you_forget_to_use_await,
24572+
typeName, leftStr, rightStr);
24573+
}
24574+
2451924575
return undefined;
2452024576
}
2452124577
}

src/compiler/diagnosticMessages.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,6 +2693,46 @@
26932693
"category": "Error",
26942694
"code": 2772
26952695
},
2696+
"Operator '{0}' cannot be applied to types '{1}' and '{2}'. Did you forget to use 'await'?": {
2697+
"category": "Error",
2698+
"code": 2773
2699+
},
2700+
"An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type. Did you forget to use 'await'?": {
2701+
"category": "Error",
2702+
"code": 2774
2703+
},
2704+
"The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. Did you forget to use 'await'?": {
2705+
"category": "Error",
2706+
"code": 2775
2707+
},
2708+
"The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. Did you forget to use 'await'?": {
2709+
"category": "Error",
2710+
"code": 2777
2711+
},
2712+
"Type '{0}' must have a '[Symbol.iterator]()' method that returns an iterator. Did you forget to use 'await'?": {
2713+
"category": "Error",
2714+
"code": 2777
2715+
},
2716+
"Type '{0}' is not an array type or a string type. Did you forget to use 'await'?": {
2717+
"category": "Error",
2718+
"code": 2778
2719+
},
2720+
"Argument of type '{0}' is not assignable to parameter of type '{1}'. Did you forget to use 'await'?": {
2721+
"category": "Error",
2722+
"code": 2779
2723+
},
2724+
"Type '{0}' has no call signatures. Did you forget to use 'await'?": {
2725+
"category": "Error",
2726+
"code": 2780
2727+
},
2728+
"Type '{0}' has no construct signatures. Did you forget to use 'await'?": {
2729+
"category": "Error",
2730+
"code": 2781
2731+
},
2732+
"This condition will always return '{0}' since the types '{1}' and '{2}' have no overlap. Did you forget to use 'await'?": {
2733+
"category": "Error",
2734+
"code": 2782
2735+
},
26962736

26972737
"Import declaration '{0}' is using private name '{1}'.": {
26982738
"category": "Error",

tests/cases/compiler/nonexistentPropertyAvailableOnPromisedType.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function fn(
2+
a: number,
3+
b: Promise<number>,
4+
c: Promise<string[]>,
5+
d: Promise<{ prop: string }>,
6+
e: Promise<() => void>,
7+
f: Promise<() => void> | (() => void),
8+
g: Promise<{ new(): any }>
9+
) {
10+
// All errors
11+
a | b;
12+
b | a;
13+
a + b;
14+
a > b;
15+
b++;
16+
--b;
17+
a === b;
18+
for (const s of c) {
19+
fn(b, b, c, d);
20+
d.prop;
21+
}
22+
e();
23+
f();
24+
new g();
25+
}

0 commit comments

Comments
 (0)