Skip to content

Use type parameter constraints for computed property types #17404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 8, 2017
104 changes: 45 additions & 59 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7558,11 +7558,11 @@ namespace ts {
return getTypeOfSymbol(prop);
}
}
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) {
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) {
if (isTypeAny(objectType)) {
return anyType;
}
const indexInfo = isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
getIndexInfoOfType(objectType, IndexKind.String) ||
undefined;
if (indexInfo) {
Expand Down Expand Up @@ -11386,7 +11386,7 @@ namespace ts {
(<BinaryExpression>parent.parent).operatorToken.kind === SyntaxKind.EqualsToken &&
(<BinaryExpression>parent.parent).left === parent &&
!isAssignmentTarget(parent.parent) &&
isTypeAnyOrAllConstituentTypesHaveKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike | TypeFlags.Undefined);
isTypeAssignableToKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike);
return isLengthPushOrUnshift || isElementAssignment;
}

Expand Down Expand Up @@ -11558,7 +11558,7 @@ namespace ts {
}
else {
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | TypeFlags.Undefined)) {
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
}
}
Expand Down Expand Up @@ -13400,11 +13400,7 @@ namespace ts {
function isNumericComputedName(name: ComputedPropertyName): boolean {
// It seems odd to consider an expression of type Any to result in a numeric name,
// but this behavior is consistent with checkIndexedAccess
return isTypeAnyOrAllConstituentTypesHaveKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
}

function isTypeAnyOrAllConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
return isTypeAny(type) || isTypeOfKind(type, kind);
return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
}

function isInfinityOrNaNString(name: string | __String): boolean {
Expand Down Expand Up @@ -13440,10 +13436,11 @@ namespace ts {
const links = getNodeLinks(node.expression);
if (!links.resolvedType) {
links.resolvedType = checkExpression(node.expression);

// This will allow types number, string, symbol or any. It will also allow enums, the unknown
// type, and any union of these types (like string | number).
if (!isTypeAnyOrAllConstituentTypesHaveKind(links.resolvedType, TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.ESSymbol)) {
if (links.resolvedType.flags & TypeFlags.Nullable ||
!isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol) &&
!isTypeAssignableTo(links.resolvedType, getUnionType([stringType, numberType, esSymbolType]))) {
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
}
else {
Expand Down Expand Up @@ -15548,7 +15545,7 @@ namespace ts {

case SyntaxKind.ComputedPropertyName:
const nameType = checkComputedPropertyName(element.name);
if (isTypeOfKind(nameType, TypeFlags.ESSymbol)) {
if (isTypeAssignableToKind(nameType, TypeFlags.ESSymbol)) {
return nameType;
}
else {
Expand Down Expand Up @@ -16938,7 +16935,7 @@ namespace ts {
}

function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
if (!isTypeAnyOrAllConstituentTypesHaveKind(type, TypeFlags.NumberLike)) {
if (!isTypeAssignableToKind(type, TypeFlags.NumberLike)) {
error(operand, diagnostic);
return false;
}
Expand Down Expand Up @@ -17116,31 +17113,22 @@ namespace ts {
return false;
}

// Return true if type is of the given kind. A union type is of a given kind if all constituent types
// are of the given kind. An intersection type is of a given kind if at least one constituent type is
// of the given kind.
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
if (type.flags & kind) {
function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean {
if (source.flags & kind) {
return true;
}
if (type.flags & TypeFlags.Union) {
const types = (<UnionOrIntersectionType>type).types;
for (const t of types) {
if (!isTypeOfKind(t, kind)) {
return false;
}
}
return true;
}
if (type.flags & TypeFlags.Intersection) {
const types = (<UnionOrIntersectionType>type).types;
for (const t of types) {
if (isTypeOfKind(t, kind)) {
return true;
}
}
if (strict && source.flags & (TypeFlags.Any | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) {
return false;
}
return false;
return (kind & TypeFlags.NumberLike && isTypeAssignableTo(source, numberType)) ||
(kind & TypeFlags.StringLike && isTypeAssignableTo(source, stringType)) ||
(kind & TypeFlags.BooleanLike && isTypeAssignableTo(source, booleanType)) ||
(kind & TypeFlags.Void && isTypeAssignableTo(source, voidType)) ||
(kind & TypeFlags.Never && isTypeAssignableTo(source, neverType)) ||
(kind & TypeFlags.Null && isTypeAssignableTo(source, nullType)) ||
(kind & TypeFlags.Undefined && isTypeAssignableTo(source, undefinedType)) ||
(kind & TypeFlags.ESSymbol && isTypeAssignableTo(source, esSymbolType)) ||
(kind & TypeFlags.NonPrimitive && isTypeAssignableTo(source, nonPrimitiveType));
}

function isConstEnumObjectType(type: Type): boolean {
Expand All @@ -17160,7 +17148,7 @@ namespace ts {
// and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature.
// The result is always of the Boolean primitive type.
// NOTE: do not raise error if leftType is unknown as related error was already reported
if (isTypeOfKind(leftType, TypeFlags.Primitive)) {
if (!isTypeAny(leftType) && isTypeAssignableToKind(leftType, TypeFlags.Primitive)) {
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
}
// NOTE: do not raise error if right is unknown as related error was already reported
Expand All @@ -17183,10 +17171,10 @@ namespace ts {
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
// and the right operand to be of type Any, an object type, or a type parameter type.
// The result is always of the Boolean primitive type.
if (!(isTypeComparableTo(leftType, stringType) || isTypeOfKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) {
if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) {
error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
}
if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.NonPrimitive)) {
if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.TypeVariable)) {
error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
}
return booleanType;
Expand Down Expand Up @@ -17495,32 +17483,30 @@ namespace ts {
return silentNeverType;
}

if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike) && !isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.StringLike)) {
if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) {
leftType = checkNonNullType(leftType, left);
rightType = checkNonNullType(rightType, right);
}

let resultType: Type;
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) {
// Operands of an enum type are treated as having the primitive type Number.
// If both operands are of the Number primitive type, the result is of the Number primitive type.
resultType = numberType;
}
else {
if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) {
// If one or both operands are of the String primitive type, the result is of the String primitive type.
resultType = stringType;
}
else if (isTypeAny(leftType) || isTypeAny(rightType)) {
// Otherwise, the result is of type Any.
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
resultType = leftType === unknownType || rightType === unknownType ? unknownType : anyType;
}
}
else if (isTypeAny(leftType) || isTypeAny(rightType)) {
// Otherwise, the result is of type Any.
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
resultType = leftType === unknownType || rightType === unknownType ? unknownType : anyType;
}

// Symbols are not allowed at all in arithmetic expressions
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
return resultType;
}
// Symbols are not allowed at all in arithmetic expressions
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
return resultType;
}

if (!resultType) {
Expand Down Expand Up @@ -18734,7 +18720,7 @@ namespace ts {
}
// Check if we're indexing with a numeric type and the object type is a generic
// type with a constraint that has a numeric index signature.
if (maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && isTypeOfKind(indexType, TypeFlags.NumberLike)) {
if (maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
const constraint = getBaseConstraintOfType(objectType);
if (constraint && getIndexInfoOfType(constraint, IndexKind.Number)) {
return type;
Expand Down Expand Up @@ -20441,7 +20427,7 @@ namespace ts {

// unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
// in this case error about missing name is already reported - do not report extra one
if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.NonPrimitive)) {
if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.TypeVariable)) {
error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter);
}

Expand Down Expand Up @@ -23428,22 +23414,22 @@ namespace ts {
else if (type.flags & TypeFlags.Any) {
return TypeReferenceSerializationKind.ObjectType;
}
else if (isTypeOfKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) {
else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) {
return TypeReferenceSerializationKind.VoidNullableOrNeverType;
}
else if (isTypeOfKind(type, TypeFlags.BooleanLike)) {
else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) {
return TypeReferenceSerializationKind.BooleanType;
}
else if (isTypeOfKind(type, TypeFlags.NumberLike)) {
else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) {
return TypeReferenceSerializationKind.NumberLikeType;
}
else if (isTypeOfKind(type, TypeFlags.StringLike)) {
else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) {
return TypeReferenceSerializationKind.StringLikeType;
}
else if (isTupleType(type)) {
return TypeReferenceSerializationKind.ArrayLikeType;
}
else if (isTypeOfKind(type, TypeFlags.ESSymbol)) {
else if (isTypeAssignableToKind(type, TypeFlags.ESSymbol)) {
return TypeReferenceSerializationKind.ESSymbolType;
}
else if (isFunctionType(type)) {
Expand Down
Loading