-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Use type parameter constraints for computed property types #17404
Changes from 3 commits
43981ea
1fb6d34
838fbdd
d07eca7
011f712
aed386c
a0333b5
e494d73
e47df36
fac93a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7526,11 +7526,11 @@ namespace ts { | |
return getTypeOfSymbol(prop); | ||
} | ||
} | ||
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) { | ||
if (!(indexType.flags & TypeFlags.Nullable) && isTypeOfKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) { | ||
if (isTypeAny(objectType)) { | ||
return anyType; | ||
} | ||
const indexInfo = isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || | ||
const indexInfo = isTypeOfKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || | ||
getIndexInfoOfType(objectType, IndexKind.String) || | ||
undefined; | ||
if (indexInfo) { | ||
|
@@ -11286,7 +11286,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); | ||
isTypeOfKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike); | ||
return isLengthPushOrUnshift || isElementAssignment; | ||
} | ||
|
||
|
@@ -11458,7 +11458,7 @@ namespace ts { | |
} | ||
else { | ||
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression); | ||
if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | TypeFlags.Undefined)) { | ||
if (isTypeOfKind(indexType, TypeFlags.NumberLike)) { | ||
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right); | ||
} | ||
} | ||
|
@@ -13290,11 +13290,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 isTypeOfKind(checkComputedPropertyName(name), TypeFlags.NumberLike); | ||
} | ||
|
||
function isInfinityOrNaNString(name: string | __String): boolean { | ||
|
@@ -13330,10 +13326,9 @@ 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 || !isTypeOfKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) { | ||
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); | ||
} | ||
else { | ||
|
@@ -16818,7 +16813,7 @@ namespace ts { | |
} | ||
|
||
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage): boolean { | ||
if (!isTypeAnyOrAllConstituentTypesHaveKind(type, TypeFlags.NumberLike)) { | ||
if (!isTypeOfKind(type, TypeFlags.NumberLike)) { | ||
error(operand, diagnostic); | ||
return false; | ||
} | ||
|
@@ -16991,31 +16986,43 @@ 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 isTypeOfKind(source: Type, kind: TypeFlags, excludeAny?: 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 (excludeAny && source.flags & (TypeFlags.Any | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { | ||
// TODO: The callers who want this should really handle these cases FIRST | ||
return false; | ||
} | ||
if (type.flags & TypeFlags.Intersection) { | ||
const types = (<UnionOrIntersectionType>type).types; | ||
for (const t of types) { | ||
if (isTypeOfKind(t, kind)) { | ||
return true; | ||
} | ||
} | ||
const targets = []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you building up a union type here? It causes allocations and ends up being broken down to discrete checks for each contained type anyway. I think it would be better to just have a sequence of conditional return kind & TypeFlags.NumberLike && isTypeAssignableTo(source, numberType) ||
kind & TypeFlags.StringLike && isTypeAssignableTo(source, stringType) ||
kind & TypeFlags.BooleanLike && isTypeAssignableTo(source, BooleanType) ||
... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. var p1: number | string
var o = {
[p1]() { }
//~~~~ A computed property name must be of type 'string', 'number', ...
} But I think I can fix that by adding a fallback to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. The computed property name type check now has a fallback call to |
||
if (kind & TypeFlags.NumberLike) { | ||
targets.push(numberType); | ||
} | ||
return false; | ||
if (kind & TypeFlags.StringLike) { | ||
targets.push(stringType); | ||
} | ||
if (kind & TypeFlags.BooleanLike) { | ||
targets.push(booleanType); | ||
} | ||
if (kind & TypeFlags.Void) { | ||
targets.push(voidType); | ||
} | ||
if (kind & TypeFlags.Never) { | ||
targets.push(neverType); | ||
} | ||
if (kind & TypeFlags.Null) { | ||
targets.push(nullType); | ||
} | ||
if (kind & TypeFlags.Undefined) { | ||
targets.push(undefinedType); | ||
} | ||
if (kind & TypeFlags.ESSymbol) { | ||
targets.push(esSymbolType); | ||
} | ||
if (kind & TypeFlags.NonPrimitive) { | ||
targets.push(nonPrimitiveType); | ||
} | ||
return isTypeAssignableTo(source, getUnionType(targets)); | ||
} | ||
|
||
function isConstEnumObjectType(type: Type): boolean { | ||
|
@@ -17035,7 +17042,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 (isTypeOfKind(leftType, TypeFlags.Primitive, /*excludeAny*/ true)) { | ||
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 | ||
|
@@ -17061,7 +17068,7 @@ namespace ts { | |
if (!(isTypeComparableTo(leftType, stringType) || isTypeOfKind(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 (!isTypeOfKind(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; | ||
|
@@ -17370,25 +17377,26 @@ namespace ts { | |
return silentNeverType; | ||
} | ||
|
||
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike) && !isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.StringLike)) { | ||
if (!isTypeOfKind(leftType, TypeFlags.StringLike) && !isTypeOfKind(rightType, TypeFlags.StringLike)) { | ||
leftType = checkNonNullType(leftType, left); | ||
rightType = checkNonNullType(rightType, right); | ||
} | ||
|
||
let resultType: Type; | ||
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) { | ||
if (isTypeOfKind(leftType, TypeFlags.NumberLike, /*excludeAny*/ true) && isTypeOfKind(rightType, TypeFlags.NumberLike, /*excludeAny*/ 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)) { | ||
if (isTypeOfKind(leftType, TypeFlags.StringLike, /*excludeAny*/ true) || isTypeOfKind(rightType, TypeFlags.StringLike, /*excludeAny*/ 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. | ||
// TODO: Reorder this to check for any (plus void/undefined/null) first | ||
resultType = leftType === unknownType || rightType === unknownType ? unknownType : anyType; | ||
} | ||
|
||
|
@@ -20314,7 +20322,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 (!isTypeOfKind(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); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I get why we need this
excludeAny
parameter?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkBinaryLikeExpression
has a complicated way of checking legal types for+
:null + number
andnumber + undefined
, even withstrictNullChecks
off. I like this check and want to keep it.string + any ==> string
vsT + any ==> any
andT + unknown ==> unknown
.It does this with the following (simplified) structure (using the abbreviation
tak
forisTypeAssignableToKind
):Without
excludeAny
(now calledstrict
),isTypeAssignableTo(number, undefined)
is true, andnumber + undefined/null
is now legal. Also,isTypeAssignableTo(number, any)
is true, andnumber + any ==> number
, andany + any ==> number
.I tried a few things to pull the
any
checks before the calls toisTypeAssignableToKind
, but was not successful. If you see a way, let me know.