Skip to content

Commit 847d7fe

Browse files
authored
Merge pull request #17404 from Microsoft/use-type-param-constraints-for-computed-prop-types
Use type parameter constraints for computed property types
2 parents 75c8ecb + fac93a3 commit 847d7fe

31 files changed

+696
-494
lines changed

src/compiler/checker.ts

Lines changed: 45 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7556,11 +7556,11 @@ namespace ts {
75567556
return getTypeOfSymbol(prop);
75577557
}
75587558
}
7559-
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) {
7559+
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) {
75607560
if (isTypeAny(objectType)) {
75617561
return anyType;
75627562
}
7563-
const indexInfo = isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
7563+
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
75647564
getIndexInfoOfType(objectType, IndexKind.String) ||
75657565
undefined;
75667566
if (indexInfo) {
@@ -11384,7 +11384,7 @@ namespace ts {
1138411384
(<BinaryExpression>parent.parent).operatorToken.kind === SyntaxKind.EqualsToken &&
1138511385
(<BinaryExpression>parent.parent).left === parent &&
1138611386
!isAssignmentTarget(parent.parent) &&
11387-
isTypeAnyOrAllConstituentTypesHaveKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike | TypeFlags.Undefined);
11387+
isTypeAssignableToKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike);
1138811388
return isLengthPushOrUnshift || isElementAssignment;
1138911389
}
1139011390

@@ -11556,7 +11556,7 @@ namespace ts {
1155611556
}
1155711557
else {
1155811558
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
11559-
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | TypeFlags.Undefined)) {
11559+
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
1156011560
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
1156111561
}
1156211562
}
@@ -13398,11 +13398,7 @@ namespace ts {
1339813398
function isNumericComputedName(name: ComputedPropertyName): boolean {
1339913399
// It seems odd to consider an expression of type Any to result in a numeric name,
1340013400
// but this behavior is consistent with checkIndexedAccess
13401-
return isTypeAnyOrAllConstituentTypesHaveKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
13402-
}
13403-
13404-
function isTypeAnyOrAllConstituentTypesHaveKind(type: Type, kind: TypeFlags): boolean {
13405-
return isTypeAny(type) || isTypeOfKind(type, kind);
13401+
return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
1340613402
}
1340713403

1340813404
function isInfinityOrNaNString(name: string | __String): boolean {
@@ -13438,10 +13434,11 @@ namespace ts {
1343813434
const links = getNodeLinks(node.expression);
1343913435
if (!links.resolvedType) {
1344013436
links.resolvedType = checkExpression(node.expression);
13441-
1344213437
// This will allow types number, string, symbol or any. It will also allow enums, the unknown
1344313438
// type, and any union of these types (like string | number).
13444-
if (!isTypeAnyOrAllConstituentTypesHaveKind(links.resolvedType, TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.ESSymbol)) {
13439+
if (links.resolvedType.flags & TypeFlags.Nullable ||
13440+
!isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol) &&
13441+
!isTypeAssignableTo(links.resolvedType, getUnionType([stringType, numberType, esSymbolType]))) {
1344513442
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
1344613443
}
1344713444
else {
@@ -15546,7 +15543,7 @@ namespace ts {
1554615543

1554715544
case SyntaxKind.ComputedPropertyName:
1554815545
const nameType = checkComputedPropertyName(element.name);
15549-
if (isTypeOfKind(nameType, TypeFlags.ESSymbol)) {
15546+
if (isTypeAssignableToKind(nameType, TypeFlags.ESSymbol)) {
1555015547
return nameType;
1555115548
}
1555215549
else {
@@ -16936,7 +16933,7 @@ namespace ts {
1693616933
}
1693716934

1693816935
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean {
16939-
if (!isTypeAnyOrAllConstituentTypesHaveKind(type, TypeFlags.NumberLike)) {
16936+
if (!isTypeAssignableToKind(type, TypeFlags.NumberLike)) {
1694016937
error(operand, diagnostic);
1694116938
return false;
1694216939
}
@@ -17114,31 +17111,22 @@ namespace ts {
1711417111
return false;
1711517112
}
1711617113

17117-
// Return true if type is of the given kind. A union type is of a given kind if all constituent types
17118-
// are of the given kind. An intersection type is of a given kind if at least one constituent type is
17119-
// of the given kind.
17120-
function isTypeOfKind(type: Type, kind: TypeFlags): boolean {
17121-
if (type.flags & kind) {
17114+
function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean {
17115+
if (source.flags & kind) {
1712217116
return true;
1712317117
}
17124-
if (type.flags & TypeFlags.Union) {
17125-
const types = (<UnionOrIntersectionType>type).types;
17126-
for (const t of types) {
17127-
if (!isTypeOfKind(t, kind)) {
17128-
return false;
17129-
}
17130-
}
17131-
return true;
17132-
}
17133-
if (type.flags & TypeFlags.Intersection) {
17134-
const types = (<UnionOrIntersectionType>type).types;
17135-
for (const t of types) {
17136-
if (isTypeOfKind(t, kind)) {
17137-
return true;
17138-
}
17139-
}
17118+
if (strict && source.flags & (TypeFlags.Any | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) {
17119+
return false;
1714017120
}
17141-
return false;
17121+
return (kind & TypeFlags.NumberLike && isTypeAssignableTo(source, numberType)) ||
17122+
(kind & TypeFlags.StringLike && isTypeAssignableTo(source, stringType)) ||
17123+
(kind & TypeFlags.BooleanLike && isTypeAssignableTo(source, booleanType)) ||
17124+
(kind & TypeFlags.Void && isTypeAssignableTo(source, voidType)) ||
17125+
(kind & TypeFlags.Never && isTypeAssignableTo(source, neverType)) ||
17126+
(kind & TypeFlags.Null && isTypeAssignableTo(source, nullType)) ||
17127+
(kind & TypeFlags.Undefined && isTypeAssignableTo(source, undefinedType)) ||
17128+
(kind & TypeFlags.ESSymbol && isTypeAssignableTo(source, esSymbolType)) ||
17129+
(kind & TypeFlags.NonPrimitive && isTypeAssignableTo(source, nonPrimitiveType));
1714217130
}
1714317131

1714417132
function isConstEnumObjectType(type: Type): boolean {
@@ -17158,7 +17146,7 @@ namespace ts {
1715817146
// and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature.
1715917147
// The result is always of the Boolean primitive type.
1716017148
// NOTE: do not raise error if leftType is unknown as related error was already reported
17161-
if (isTypeOfKind(leftType, TypeFlags.Primitive)) {
17149+
if (!isTypeAny(leftType) && isTypeAssignableToKind(leftType, TypeFlags.Primitive)) {
1716217150
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
1716317151
}
1716417152
// NOTE: do not raise error if right is unknown as related error was already reported
@@ -17181,10 +17169,10 @@ namespace ts {
1718117169
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
1718217170
// and the right operand to be of type Any, an object type, or a type parameter type.
1718317171
// The result is always of the Boolean primitive type.
17184-
if (!(isTypeComparableTo(leftType, stringType) || isTypeOfKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) {
17172+
if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) {
1718517173
error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
1718617174
}
17187-
if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.NonPrimitive)) {
17175+
if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.TypeVariable)) {
1718817176
error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
1718917177
}
1719017178
return booleanType;
@@ -17493,32 +17481,30 @@ namespace ts {
1749317481
return silentNeverType;
1749417482
}
1749517483

17496-
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike) && !isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.StringLike)) {
17484+
if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) {
1749717485
leftType = checkNonNullType(leftType, left);
1749817486
rightType = checkNonNullType(rightType, right);
1749917487
}
1750017488

1750117489
let resultType: Type;
17502-
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
17490+
if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) {
1750317491
// Operands of an enum type are treated as having the primitive type Number.
1750417492
// If both operands are of the Number primitive type, the result is of the Number primitive type.
1750517493
resultType = numberType;
1750617494
}
17507-
else {
17508-
if (isTypeOfKind(leftType, TypeFlags.StringLike) || isTypeOfKind(rightType, TypeFlags.StringLike)) {
17495+
else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) {
1750917496
// If one or both operands are of the String primitive type, the result is of the String primitive type.
1751017497
resultType = stringType;
17511-
}
17512-
else if (isTypeAny(leftType) || isTypeAny(rightType)) {
17513-
// Otherwise, the result is of type Any.
17514-
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
17515-
resultType = leftType === unknownType || rightType === unknownType ? unknownType : anyType;
17516-
}
17498+
}
17499+
else if (isTypeAny(leftType) || isTypeAny(rightType)) {
17500+
// Otherwise, the result is of type Any.
17501+
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
17502+
resultType = leftType === unknownType || rightType === unknownType ? unknownType : anyType;
17503+
}
1751717504

17518-
// Symbols are not allowed at all in arithmetic expressions
17519-
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
17520-
return resultType;
17521-
}
17505+
// Symbols are not allowed at all in arithmetic expressions
17506+
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
17507+
return resultType;
1752217508
}
1752317509

1752417510
if (!resultType) {
@@ -18732,7 +18718,7 @@ namespace ts {
1873218718
}
1873318719
// Check if we're indexing with a numeric type and the object type is a generic
1873418720
// type with a constraint that has a numeric index signature.
18735-
if (maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && isTypeOfKind(indexType, TypeFlags.NumberLike)) {
18721+
if (maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
1873618722
const constraint = getBaseConstraintOfType(objectType);
1873718723
if (constraint && getIndexInfoOfType(constraint, IndexKind.Number)) {
1873818724
return type;
@@ -20439,7 +20425,7 @@ namespace ts {
2043920425

2044020426
// unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
2044120427
// in this case error about missing name is already reported - do not report extra one
20442-
if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.NonPrimitive)) {
20428+
if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.TypeVariable)) {
2044320429
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);
2044420430
}
2044520431

@@ -23426,22 +23412,22 @@ namespace ts {
2342623412
else if (type.flags & TypeFlags.Any) {
2342723413
return TypeReferenceSerializationKind.ObjectType;
2342823414
}
23429-
else if (isTypeOfKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) {
23415+
else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) {
2343023416
return TypeReferenceSerializationKind.VoidNullableOrNeverType;
2343123417
}
23432-
else if (isTypeOfKind(type, TypeFlags.BooleanLike)) {
23418+
else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) {
2343323419
return TypeReferenceSerializationKind.BooleanType;
2343423420
}
23435-
else if (isTypeOfKind(type, TypeFlags.NumberLike)) {
23421+
else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) {
2343623422
return TypeReferenceSerializationKind.NumberLikeType;
2343723423
}
23438-
else if (isTypeOfKind(type, TypeFlags.StringLike)) {
23424+
else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) {
2343923425
return TypeReferenceSerializationKind.StringLikeType;
2344023426
}
2344123427
else if (isTupleType(type)) {
2344223428
return TypeReferenceSerializationKind.ArrayLikeType;
2344323429
}
23444-
else if (isTypeOfKind(type, TypeFlags.ESSymbol)) {
23430+
else if (isTypeAssignableToKind(type, TypeFlags.ESSymbol)) {
2344523431
return TypeReferenceSerializationKind.ESSymbolType;
2344623432
}
2344723433
else if (isFunctionType(type)) {

0 commit comments

Comments
 (0)