Did you forget to use await? for operators

This commit is contained in:
Andrew Branch
2019-07-02 17:06:42 -07:00
parent fb50920a67
commit c48e34ef91
4 changed files with 146 additions and 28 deletions

View File

@@ -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, well 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;
}
}

View File

@@ -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",

View File

@@ -1,3 +0,0 @@
function f(x: Promise<string>) {
x.toLowerCase();
}

View 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();
}