mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Did you forget to use await? for operators
This commit is contained in:
@@ -932,6 +932,18 @@ namespace ts {
|
||||
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message));
|
||||
}
|
||||
|
||||
function errorAndMaybeSuggestAwait(
|
||||
location: Node | undefined,
|
||||
maybeMissingAwait: boolean,
|
||||
defaultMessage: DiagnosticMessage,
|
||||
missingAwaitMessage: DiagnosticMessage,
|
||||
arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic {
|
||||
if (maybeMissingAwait) {
|
||||
return error(location, missingAwaitMessage, arg0, arg1, arg2, arg3);
|
||||
}
|
||||
return error(location, defaultMessage, arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) {
|
||||
symbolCount++;
|
||||
const symbol = <TransientSymbol>(new Symbol(flags | SymbolFlags.Transient, name));
|
||||
@@ -23662,9 +23674,14 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
|
||||
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, missingAwaitDiagnostic: DiagnosticMessage): boolean {
|
||||
if (!isTypeAssignableTo(type, numberOrBigIntType)) {
|
||||
error(operand, diagnostic);
|
||||
const awaitedType = getAwaitedType(type);
|
||||
errorAndMaybeSuggestAwait(
|
||||
operand,
|
||||
!!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType),
|
||||
diagnostic,
|
||||
missingAwaitDiagnostic);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -23862,7 +23879,8 @@ namespace ts {
|
||||
case SyntaxKind.PlusPlusToken:
|
||||
case SyntaxKind.MinusMinusToken:
|
||||
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type,
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
|
||||
if (ok) {
|
||||
// run check only if former checks succeeded to avoid reporting cascading errors
|
||||
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 {
|
||||
const ok = checkArithmeticOperandType(
|
||||
node.operand,
|
||||
checkNonNullType(operandType, node.operand),
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type,
|
||||
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type_Did_you_forget_to_use_await);
|
||||
if (ok) {
|
||||
// run check only if former checks succeeded to avoid reporting cascading errors
|
||||
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 {
|
||||
}
|
||||
else {
|
||||
// otherwise just check each operand separately and report errors as normal
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
let resultType: Type;
|
||||
// If both are any or unknown, allow operation; assume it will resolve to number
|
||||
if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) ||
|
||||
@@ -24282,7 +24301,7 @@ namespace ts {
|
||||
) {
|
||||
resultType = numberType;
|
||||
}
|
||||
// At least one is assignable to bigint, so both should be only assignable to bigint
|
||||
// At least one is assignable to bigint, so check that both are
|
||||
else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike)) {
|
||||
switch (operator) {
|
||||
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
||||
@@ -24291,8 +24310,9 @@ namespace ts {
|
||||
}
|
||||
resultType = bigintType;
|
||||
}
|
||||
// Exactly one of leftType/rightType is assignable to bigint
|
||||
else {
|
||||
reportOperatorError();
|
||||
reportOperatorError((awaitedLeft, awaitedRight) => isTypeAssignableToKind(awaitedLeft, TypeFlags.BigIntLike) && isTypeAssignableToKind(awaitedRight, TypeFlags.BigIntLike));
|
||||
resultType = errorType;
|
||||
}
|
||||
if (leftOk && rightOk) {
|
||||
@@ -24337,7 +24357,14 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (!resultType) {
|
||||
reportOperatorError();
|
||||
// Types that have a reasonably good chance of being a valid operand type.
|
||||
// If both types have an awaited type of one of these, we’ll assume the user
|
||||
// might be missing an await without doing an exhaustive check that inserting
|
||||
// await(s) will actually be a completely valid binary expression.
|
||||
const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown;
|
||||
reportOperatorError((awaitedLeft, awaitedRight) =>
|
||||
isTypeAssignableToKind(awaitedLeft, closeEnoughKind) &&
|
||||
isTypeAssignableToKind(awaitedRight, closeEnoughKind));
|
||||
return anyType;
|
||||
}
|
||||
|
||||
@@ -24352,21 +24379,18 @@ namespace ts {
|
||||
if (checkForDisallowedESSymbolOperand(operator)) {
|
||||
leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left));
|
||||
rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right));
|
||||
if (!(isTypeComparableTo(leftType, rightType) || isTypeComparableTo(rightType, leftType) ||
|
||||
(isTypeAssignableTo(leftType, numberOrBigIntType) && isTypeAssignableTo(rightType, numberOrBigIntType))
|
||||
)) {
|
||||
reportOperatorError();
|
||||
}
|
||||
reportOperatorErrorUnless((left, right) =>
|
||||
isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || (
|
||||
isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType)));
|
||||
}
|
||||
return booleanType;
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
if (!isTypeEqualityComparableTo(leftType, rightType) && !isTypeEqualityComparableTo(rightType, leftType)) {
|
||||
reportOperatorError();
|
||||
}
|
||||
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
|
||||
return booleanType;
|
||||
|
||||
case SyntaxKind.InstanceOfKeyword:
|
||||
return checkInstanceOfExpression(left, right, leftType, rightType);
|
||||
case SyntaxKind.InKeyword:
|
||||
@@ -24493,13 +24517,33 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function reportOperatorError() {
|
||||
const [leftStr, rightStr] = getTypeNamesForErrorDisplay(leftType, rightType);
|
||||
/**
|
||||
* Returns true if an error is reported
|
||||
*/
|
||||
function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean {
|
||||
if (!typesAreCompatible(leftType, rightType)) {
|
||||
reportOperatorError(typesAreCompatible);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function reportOperatorError(awaitedTypesAreCompatible?: (left: Type, right: Type) => boolean) {
|
||||
let wouldWorkWithAwait = false;
|
||||
const errNode = errorNode || operatorToken;
|
||||
if (!tryGiveBetterPrimaryError(errNode, leftStr, rightStr)) {
|
||||
error(
|
||||
const [leftStr, rightStr] = getTypeNamesForErrorDisplay(leftType, rightType);
|
||||
if (awaitedTypesAreCompatible) {
|
||||
const awaitedLeftType = getAwaitedType(leftType);
|
||||
const awaitedRightType = getAwaitedType(rightType);
|
||||
wouldWorkWithAwait = !!(awaitedLeftType && awaitedRightType) && awaitedTypesAreCompatible(awaitedLeftType, awaitedRightType);
|
||||
}
|
||||
|
||||
if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) {
|
||||
errorAndMaybeSuggestAwait(
|
||||
errNode,
|
||||
wouldWorkWithAwait,
|
||||
Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2,
|
||||
Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2_Did_you_forget_to_use_await,
|
||||
tokenToString(operatorToken.kind),
|
||||
leftStr,
|
||||
rightStr,
|
||||
@@ -24507,15 +24551,27 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function tryGiveBetterPrimaryError(errNode: Node, leftStr: string, rightStr: string) {
|
||||
function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) {
|
||||
let typeName: string | undefined;
|
||||
switch (operatorToken.kind) {
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
return error(errNode, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, "false", leftStr, rightStr);
|
||||
typeName = "true";
|
||||
break;
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
return error(errNode, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, "true", leftStr, rightStr);
|
||||
}
|
||||
typeName = "false";
|
||||
}
|
||||
|
||||
if (typeName) {
|
||||
return errorAndMaybeSuggestAwait(
|
||||
errNode,
|
||||
maybeMissingAwait,
|
||||
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap,
|
||||
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap_Did_you_forget_to_use_await,
|
||||
typeName, leftStr, rightStr);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2693,6 +2693,46 @@
|
||||
"category": "Error",
|
||||
"code": 2772
|
||||
},
|
||||
"Operator '{0}' cannot be applied to types '{1}' and '{2}'. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2773
|
||||
},
|
||||
"An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2774
|
||||
},
|
||||
"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'?": {
|
||||
"category": "Error",
|
||||
"code": 2775
|
||||
},
|
||||
"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'?": {
|
||||
"category": "Error",
|
||||
"code": 2777
|
||||
},
|
||||
"Type '{0}' must have a '[Symbol.iterator]()' method that returns an iterator. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2777
|
||||
},
|
||||
"Type '{0}' is not an array type or a string type. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2778
|
||||
},
|
||||
"Argument of type '{0}' is not assignable to parameter of type '{1}'. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2779
|
||||
},
|
||||
"Type '{0}' has no call signatures. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2780
|
||||
},
|
||||
"Type '{0}' has no construct signatures. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2781
|
||||
},
|
||||
"This condition will always return '{0}' since the types '{1}' and '{2}' have no overlap. Did you forget to use 'await'?": {
|
||||
"category": "Error",
|
||||
"code": 2782
|
||||
},
|
||||
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
function f(x: Promise<string>) {
|
||||
x.toLowerCase();
|
||||
}
|
||||
25
tests/cases/compiler/operationsAvailableOnPromisedType.ts
Normal file
25
tests/cases/compiler/operationsAvailableOnPromisedType.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
function fn(
|
||||
a: number,
|
||||
b: Promise<number>,
|
||||
c: Promise<string[]>,
|
||||
d: Promise<{ prop: string }>,
|
||||
e: Promise<() => void>,
|
||||
f: Promise<() => void> | (() => void),
|
||||
g: Promise<{ new(): any }>
|
||||
) {
|
||||
// All errors
|
||||
a | b;
|
||||
b | a;
|
||||
a + b;
|
||||
a > b;
|
||||
b++;
|
||||
--b;
|
||||
a === b;
|
||||
for (const s of c) {
|
||||
fn(b, b, c, d);
|
||||
d.prop;
|
||||
}
|
||||
e();
|
||||
f();
|
||||
new g();
|
||||
}
|
||||
Reference in New Issue
Block a user