-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Fix indexing and destructuring of unions of tuple types #27587
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
Changes from all commits
4d9a202
c9ea6c3
7f3f98e
1299c93
919fce9
d4f480c
86704e5
48f2dd9
62aeead
2e5a39a
209f30c
840214f
dd63656
b040ea6
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 |
---|---|---|
|
@@ -4683,14 +4683,14 @@ namespace ts { | |
// If the parent is a tuple type, the rest element has a tuple type of the | ||
// remaining tuple element types. Otherwise, the rest element has an array type with same | ||
// element type as the parent type. | ||
type = isTupleType(parentType) ? | ||
sliceTupleType(parentType, index) : | ||
type = everyType(parentType, isTupleType) ? | ||
mapType(parentType, t => sliceTupleType(<TupleTypeReference>t, index)) : | ||
createArrayType(elementType); | ||
} | ||
else { | ||
// Use specific property type when parent is a tuple or numeric index type when parent is an array | ||
const index = pattern.elements.indexOf(declaration); | ||
type = isTupleLikeType(parentType) ? | ||
type = everyType(parentType, isTupleLikeType) ? | ||
getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) : | ||
elementType; | ||
if (!type) { | ||
|
@@ -4706,11 +4706,11 @@ namespace ts { | |
} | ||
// In strict null checking mode, if a default value of a non-undefined type is specified, remove | ||
// undefined from the final type. | ||
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) { | ||
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) { | ||
type = getTypeWithFacts(type, TypeFacts.NEUndefined); | ||
} | ||
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ? | ||
getUnionType([type, checkExpressionCached(declaration.initializer)], UnionReduction.Subtype) : | ||
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) : | ||
type; | ||
} | ||
|
||
|
@@ -7309,10 +7309,10 @@ namespace ts { | |
} | ||
} | ||
else if (isUnion) { | ||
const index = !isLateBoundName(name) && ((isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number)) || getIndexInfoOfType(type, IndexKind.String)); | ||
if (index) { | ||
checkFlags |= index.isReadonly ? CheckFlags.Readonly : 0; | ||
indexTypes = append(indexTypes, index.type); | ||
const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); | ||
if (indexInfo) { | ||
checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0; | ||
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); | ||
} | ||
else { | ||
checkFlags |= CheckFlags.Partial; | ||
|
@@ -9311,11 +9311,12 @@ namespace ts { | |
getFlowTypeOfReference(accessExpression, propType) : | ||
propType; | ||
} | ||
if (isTupleType(objectType)) { | ||
const restType = getRestTypeOfTupleType(objectType); | ||
if (restType && isNumericLiteralName(propName) && +propName >= 0) { | ||
return restType; | ||
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { | ||
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) { | ||
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType; | ||
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. type UndefinedTuple = [string, ...undefined[]]; // ??? 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. Now fixed. 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.
|
||
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); | ||
} | ||
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType); | ||
} | ||
} | ||
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { | ||
|
@@ -12877,9 +12878,14 @@ namespace ts { | |
} | ||
|
||
function getTupleElementType(type: Type, index: number) { | ||
return isTupleType(type) ? | ||
index < getLengthOfTupleType(type) ? type.typeArguments![index] : getRestTypeOfTupleType(type) : | ||
getTypeOfPropertyOfType(type, "" + index as __String); | ||
const propType = getTypeOfPropertyOfType(type, "" + index as __String); | ||
if (propType) { | ||
return propType; | ||
} | ||
if (everyType(type, isTupleType) && !everyType(type, t => !(<TupleTypeReference>t).target.hasRestElement)) { | ||
return mapType(type, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType); | ||
} | ||
return undefined; | ||
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. Yeah, same remark - this needs to use a unique marker and not the |
||
} | ||
|
||
function isNeitherUnitTypeNorNever(type: Type): boolean { | ||
|
@@ -14371,7 +14377,7 @@ namespace ts { | |
} | ||
|
||
function getTypeOfDestructuredArrayElement(type: Type, index: number) { | ||
return isTupleLikeType(type) && getTupleElementType(type, index) || | ||
return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || | ||
checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || | ||
errorType; | ||
} | ||
|
@@ -14569,6 +14575,10 @@ namespace ts { | |
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type); | ||
} | ||
|
||
function everyType(type: Type, f: (t: Type) => boolean): boolean { | ||
return type.flags & TypeFlags.Union ? every((<UnionType>type).types, f) : f(type); | ||
} | ||
|
||
function filterType(type: Type, f: (t: Type) => boolean): Type { | ||
if (type.flags & TypeFlags.Union) { | ||
const types = (<UnionType>type).types; | ||
|
@@ -16673,11 +16683,6 @@ namespace ts { | |
return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); | ||
} | ||
|
||
// Return true if the given contextual type is a tuple-like type | ||
function contextualTypeIsTupleLikeType(type: Type): boolean { | ||
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isTupleLikeType) : isTupleLikeType(type)); | ||
} | ||
|
||
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of | ||
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one | ||
// exists. Otherwise, it is the type of the string index signature in T, if one exists. | ||
|
@@ -17229,7 +17234,8 @@ namespace ts { | |
} | ||
|
||
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length) { | ||
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) { | ||
// Infer a tuple type when the contextual type is or contains a tuple-like type | ||
if (contextualType && forEachType(contextualType, isTupleLikeType)) { | ||
const minLength = elementCount - (hasRestElement ? 1 : 0); | ||
const pattern = contextualType.pattern; | ||
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting | ||
|
@@ -18959,13 +18965,6 @@ namespace ts { | |
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); | ||
return errorType; | ||
} | ||
if (isTupleType(objectType) && !objectType.target.hasRestElement && isNumericLiteral(indexExpression)) { | ||
const index = +indexExpression.text; | ||
const maximumIndex = length(objectType.target.typeParameters); | ||
if (index >= maximumIndex) { | ||
error(indexExpression, Diagnostics.Index_0_is_out_of_bounds_in_tuple_of_length_1, index, maximumIndex); | ||
} | ||
} | ||
|
||
return checkIndexedAccessIndexType(getIndexedAccessType(objectType, isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType, node), node); | ||
} | ||
|
@@ -21704,7 +21703,7 @@ namespace ts { | |
if (element.kind !== SyntaxKind.SpreadElement) { | ||
const propName = "" + elementIndex as __String; | ||
const type = isTypeAny(sourceType) ? sourceType : | ||
isTupleLikeType(sourceType) ? getTupleElementType(sourceType, elementIndex) : | ||
everyType(sourceType, isTupleLikeType) ? getTupleElementType(sourceType, elementIndex) : | ||
elementType; | ||
if (type) { | ||
return checkDestructuringAssignment(element, type, checkMode); | ||
|
@@ -21730,8 +21729,8 @@ namespace ts { | |
} | ||
else { | ||
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); | ||
const type = isTupleType(sourceType) ? | ||
sliceTupleType(sourceType, elementIndex) : | ||
const type = everyType(sourceType, isTupleType) ? | ||
mapType(sourceType, t => sliceTupleType(<TupleTypeReference>t, elementIndex)) : | ||
createArrayType(elementType); | ||
return checkDestructuringAssignment(restExpression, type, checkMode); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,23 +36,6 @@ namespace ts { | |
Low = 250 | ||
} | ||
|
||
function getPriorityValues(highPriorityValue: number): [number, number, number] { | ||
const mediumPriorityValue = highPriorityValue * 2; | ||
const lowPriorityValue = mediumPriorityValue * 4; | ||
return [highPriorityValue, mediumPriorityValue, lowPriorityValue]; | ||
} | ||
|
||
function pollingInterval(watchPriority: PollingInterval): number { | ||
return pollingIntervalsForPriority[watchPriority]; | ||
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. The line above causes an error with this PR because we now correctly include |
||
} | ||
|
||
const pollingIntervalsForPriority = getPriorityValues(250); | ||
|
||
/* @internal */ | ||
export function watchFileUsingPriorityPollingInterval(host: System, fileName: string, callback: FileWatcherCallback, watchPriority: PollingInterval): FileWatcher { | ||
return host.watchFile!(fileName, callback, pollingInterval(watchPriority)); | ||
} | ||
|
||
/* @internal */ | ||
export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval | undefined) => FileWatcher; | ||
/* @internal */ | ||
|
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.
Hm, this isn't using a
undefinedWideningType
which means outside ofstrict
mode, the new types are going to be strictly less permissive than they were before. Are we OK with that?Uh oh!
There was an error while loading. Please reload this page.
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.
They're going to be less permissive no matter which
undefined
we use because previously, when the object type was a union type, we'd just fall back to the numeric index signature. For example:I would argue that the non-widening
undefined
is the right choice above. It only matters in non-strict mode and the only time you'd ever observe a difference is when it is not in a union with something else, i.e.T2
above. I don't think we'd help anybody ifT2
was magically going to widen toany
.