Add support for Inverse Offset and Range types

This commit is contained in:
Ron Buckton 2020-02-13 13:04:46 -08:00
parent 2e97918d43
commit c03b2eed76
17 changed files with 2212 additions and 21 deletions

View File

@ -673,6 +673,8 @@ namespace ts {
const literalTypes = createMap<LiteralType>();
const indexedAccessTypes = createMap<IndexedAccessType>();
const substitutionTypes = createMap<SubstitutionType>();
const rangeTypes = createMap<RangeType>();
const inverseOffsetTypes = createMap<InverseOffsetType>();
const evolvingArrayTypes: EvolvingArrayType[] = [];
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
@ -721,6 +723,7 @@ namespace ts {
const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object");
const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]);
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
const offsetConstraintType = getUnionType([stringType, numberType]);
const numberOrBigIntType = getUnionType([numberType, bigintType]);
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
@ -876,6 +879,10 @@ namespace ts {
const zeroType = getLiteralType(0);
const zeroBigIntType = getLiteralType({ negative: false, base10Value: "0" });
const boundarySymbol = createSymbol(SymbolFlags.None, InternalSymbolName.Boundary);
const lowerBoundType = createLiteralType(TypeFlags.NumberLiteral, 0, boundarySymbol);
const upperBoundType = getInverseOffsetType(lowerBoundType);
const resolutionTargets: TypeSystemEntity[] = [];
const resolutionResults: boolean[] = [];
const resolutionPropertyNames: TypeSystemPropertyName[] = [];
@ -982,6 +989,7 @@ namespace ts {
}
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
if (message === Diagnostics.Type_0_cannot_be_used_to_index_type_1) debugger;
const diagnostic = location
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
@ -4218,6 +4226,12 @@ namespace ts {
const indexTypeNode = typeToTypeNodeHelper(indexedType, context);
return createTypeOperatorNode(indexTypeNode);
}
if (type.flags & TypeFlags.InverseOffset) {
const indexType = (<InverseOffsetType>type).indexType;
context.approximateLength += 1;
const indexTypeNode = typeToTypeNodeHelper(indexType, context);
return createInverseOffsetTypeNode(indexTypeNode);
}
if (type.flags & TypeFlags.IndexedAccess) {
const objectTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).objectType, context);
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
@ -4235,6 +4249,12 @@ namespace ts {
context.approximateLength += 15;
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}
if (type.flags & TypeFlags.Range) {
const objectTypeNode = typeToTypeNodeHelper((<RangeType>type).objectType, context);
const startTypeNode = (<RangeType>type).startType !== lowerBoundType ? typeToTypeNodeHelper((<RangeType>type).startType, context) : undefined;
const endTypeNode = (<RangeType>type).endType !== upperBoundType ? typeToTypeNodeHelper((<RangeType>type).endType, context) : undefined;
return createRangeTypeNode(objectTypeNode, startTypeNode, endTypeNode);
}
if (type.flags & TypeFlags.Substitution) {
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
}
@ -9691,13 +9711,13 @@ namespace ts {
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
resolveClassOrInterfaceMembers(<InterfaceType>type);
}
else if ((<ReverseMappedType>type).objectFlags & ObjectFlags.ReverseMapped) {
else if ((<ObjectType>type).objectFlags & ObjectFlags.ReverseMapped) {
resolveReverseMappedTypeMembers(type as ReverseMappedType);
}
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
resolveAnonymousTypeMembers(<AnonymousType>type);
}
else if ((<MappedType>type).objectFlags & ObjectFlags.Mapped) {
else if ((<ObjectType>type).objectFlags & ObjectFlags.Mapped) {
resolveMappedTypeMembers(<MappedType>type);
}
}
@ -10007,6 +10027,9 @@ namespace ts {
if (t.flags & TypeFlags.Index) {
return keyofConstraintType;
}
if (t.flags & TypeFlags.InverseOffset) {
return offsetConstraintType;
}
if (t.flags & TypeFlags.IndexedAccess) {
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
@ -10020,6 +10043,13 @@ namespace ts {
constraintDepth--;
return result;
}
if (t.flags & TypeFlags.Range) {
const baseObjectType = getBaseConstraint((<RangeType>t).objectType);
const baseStartType = getBaseConstraint((<RangeType>t).startType);
const baseEndType = getBaseConstraint((<RangeType>t).endType);
const baseRangeType = baseObjectType && baseStartType && baseEndType && getRangeTypeOrUndefined(baseObjectType, baseStartType, baseEndType);
return baseRangeType && getBaseConstraint(baseRangeType);
}
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint((<SubstitutionType>t).substitute);
}
@ -12218,6 +12248,7 @@ namespace ts {
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
type.flags & TypeFlags.InverseOffset ? getIndexType(offsetConstraintType, stringsOnly, noIndexSignatures) :
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
@ -12346,6 +12377,7 @@ namespace ts {
if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) {
return objectType;
}
const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String);
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || stringIndexInfo;
if (indexInfo) {
@ -12462,7 +12494,18 @@ namespace ts {
}
function isGenericIndexType(type: Type): boolean {
return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index);
if (maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index)) {
return true;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (const t of (type as UnionOrIntersectionType).types) {
if (t.flags & TypeFlags.InverseOffset && isGenericIndexType((t as InverseOffsetType).indexType)) {
return true;
}
}
return false;
}
return !!(type.flags & TypeFlags.InverseOffset) && isGenericIndexType((type as InverseOffsetType).indexType);
}
function isThisTypeParameter(type: Type): boolean {
@ -12471,6 +12514,7 @@ namespace ts {
function getSimplifiedType(type: Type, writing: boolean): Type {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type, writing) :
type.flags & TypeFlags.Range ? getSimplifiedRangeType(<RangeType>type, writing) :
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(<ConditionalType>type, writing) :
type;
}
@ -12542,6 +12586,65 @@ namespace ts {
return type[cache] = type;
}
function distributeRangeOverObjectType(objectType: Type, startType: Type, endType: Type, writing: boolean) {
// (T | U)[X:Y] -> T[X:Y] | U[X:Y]
// (T & U)[X:Y] -> T[X:Y] & U[X:Y]
if (objectType.flags & TypeFlags.UnionOrIntersection) {
const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getRangeType(t, startType, endType), writing));
return objectType.flags & TypeFlags.Intersection ? getIntersectionType(types) : getUnionType(types);
}
}
function distributeObjectOverRangeTypes(objectType: Type, startType: Type, endType: Type, writing: boolean) {
// T[A|B:C|D] -> T[A:C] | T[A:D] | T[B:C] | T[B:D]
if (startType.flags & TypeFlags.Union || endType.flags & TypeFlags.Union) {
const types: Type[] = [];
for (const start of getConstituents(startType)) {
for (const end of getConstituents(endType)) {
types.push(getSimplifiedType(getRangeType(objectType, start, end), writing));
}
}
return getUnionType(types);
}
}
function getSimplifiedRangeType(type: RangeType, writing: boolean) {
const cache = writing ? "simplifiedForWriting" : "simplifiedForReading";
if (type[cache]) {
return type[cache] === circularConstraintType ? type : type[cache]!;
}
type[cache] = circularConstraintType;
// We recursively simplify the object type as it may in turn be a range type. For example, with
// '{ [P in T]: { [Q in U]: number } }[T:][U:]' we want to first simplify the inner range type.
const objectType = unwrapSubstitution(getSimplifiedType(type.objectType, writing));
const startType = getSimplifiedType(type.startType, writing);
const endType = getSimplifiedType(type.endType, writing);
// T[A|B:C|D] -> T[A:C] | T[A:D] | T[B:C] | T[B:D]
const distributedOverStartEnd = distributeObjectOverRangeTypes(objectType, startType, endType, writing);
if (distributedOverStartEnd) {
return type[cache] = distributedOverStartEnd;
}
// Only do the inner distribution if the start/end offsets can no longer be instantiated to cause distribution again
if (!(startType.flags & TypeFlags.Instantiable) && !(endType.flags & TypeFlags.Instantiable)) {
// (T | U)[A:B] -> T[A:B] | U[A:B]
// (T & U)[A:B] -> T[A:B] & U[A:B]
const distributedOverObject = distributeRangeOverObjectType(objectType, startType, endType, writing);
if (distributedOverObject) {
return type[cache] = distributedOverObject;
}
}
// So ultimately:
// ((A & B) | C)[K1 | K2:K3 | K4]
// -> ((A & B) | C)[K1:K3 | K4] | ((A & B) | C)[K2:K3 | K4]
// -> ((A & B) | C)[K1:K3] | ((A & B) | C)[K1:K4] | ((A & B) | C)[K2:K4] | ((A & B) | C)[K2:K4]
// -> (A & B)[K1:K3] | C[K1:K3] | (A & B)[K1:K4] | C[K1:K4] | (A & B)[K2:K3] | C[K2:K3] | (A & B)[K2:K4] | C[K2:K4]
// -> (A[K1:K3] & B[K1:K3]) | C[K1:K3] | (A[K1:K4] & B[K1:K4]) | C[K1:K4] | (A[K2:K3] & B[K2:K3]) | C[K2:K3] | (A[K2:K4] & B[K2:K4]) | C[K2:K4]
return type[cache] = type;
}
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
const checkType = type.checkType;
const extendsType = type.extendsType;
@ -12580,6 +12683,71 @@ namespace ts {
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
}
function getInvertedIndexType(indexType: Type, objectType: Type, errorNode: Node | undefined) {
if (indexType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Number | TypeFlags.String)) {
return indexType;
}
let index = getNumericIndexFromIndexType(indexType);
if (index !== undefined) {
if (isArrayType(objectType)) {
return numberType;
}
if (isTupleType(objectType)) {
if (index < 0) {
return getLiteralType(-index);
}
const length = getLengthOfTupleType(objectType);
index = length - index;
if (index < 0) index = 0;
if (objectType.target.hasRestElement) {
if (index < length) {
const indexTypes: Type[] = [];
for (let i = index; i <= length; i++) {
indexTypes.push(getLiteralType(i));
}
return getUnionType(indexTypes);
}
}
return getLiteralType(index);
}
}
if (errorNode) {
error(errorNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
}
}
function resolveInverseOffsets(indexType: Type, objectType: Type, errorNode: Node | undefined) {
if (indexType.flags & TypeFlags.Union) {
let indexTypes: Type[] | undefined;
let hasErrors = false;
const types = (<UnionType>indexType).types;
for (let i = 0; i < types.length; i++) {
const t = types[i];
if (t.flags & TypeFlags.InverseOffset) {
if (!indexTypes) indexTypes = types.slice(0, i);
const inverted = getInvertedIndexType((<InverseOffsetType>t).indexType, objectType, errorNode);
if (inverted) {
indexTypes.push(inverted);
}
else if (!errorNode) {
return undefined;
}
else {
hasErrors = true;
}
}
else if (indexTypes) {
indexTypes.push(t);
}
}
return hasErrors ? undefined : indexTypes ? getUnionType(indexTypes) : indexType;
}
if (indexType.flags & TypeFlags.InverseOffset) {
return getInvertedIndexType((<InverseOffsetType>indexType).indexType, objectType, errorNode);
}
return indexType;
}
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type {
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType);
}
@ -12603,13 +12771,18 @@ namespace ts {
return objectType;
}
// Defer the operation by creating an indexed access type.
const id = objectType.id + "," + indexType.id;
const id = `${objectType.id},${indexType.id}`;
let type = indexedAccessTypes.get(id);
if (!type) {
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType));
}
return type;
}
const resolvedIndexType = resolveInverseOffsets(indexType, objectType, accessNode);
if (!resolvedIndexType) {
return undefined;
}
indexType = resolvedIndexType;
// In the following we resolve T[K] to the type of the property in T selected by K.
// We treat boolean as different from other unions to improve errors;
// skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'.
@ -12668,6 +12841,315 @@ namespace ts {
return links.resolvedType;
}
function getRangeType(objectType: Type, startType: Type, endType: Type, rangeNode?: RangeTypeNode, accessFlags?: AccessFlags): Type {
return getRangeTypeOrUndefined(objectType, startType, endType, rangeNode, accessFlags) ?? (rangeNode ? errorType : unknownType);
}
function getRangeTypeOrUndefined(objectType: Type, startType: Type, endType: Type, rangeNode?: RangeTypeNode, accessFlags = AccessFlags.None): Type | undefined {
if (objectType === wildcardType || startType === wildcardType || endType === wildcardType) {
return wildcardType;
}
// The object type is constrained to be an array or tuple type
if (!isTypeAssignableTo(objectType, anyReadonlyArrayType)) {
if (rangeNode) {
error(rangeNode, Diagnostics.Type_0_is_not_an_array_type, typeToString(objectType));
}
return undefined;
}
// The start type is constrained to be a string- or number-like type
if (!isTypeAssignableTo(startType, offsetConstraintType)) {
if (rangeNode?.startType) {
error(rangeNode.startType, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(startType), typeToString(objectType));
}
return undefined;
}
// The end type is constrained to be a string- or number-like type
if (!isTypeAssignableTo(endType, offsetConstraintType)) {
if (rangeNode?.endType) {
error(rangeNode.endType, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(endType), typeToString(objectType));
}
return undefined;
}
// If the object, start, or end type is generic, we are performing a higher-order slice, and the operation
// is deferred until it can be instantiated.
if (isGenericObjectType(objectType) || isGenericIndexType(startType) || isGenericIndexType(endType)) {
// A generic start or end on 'any' or 'unkown' is 'any' or 'unknown' (respectively).
if (objectType.flags & TypeFlags.AnyOrUnknown) {
return objectType;
}
// If we can eagerly resolve start and end and they indicate the range is either
// all inclusive or empty, we return the object type or an empty tuple (respectively).
let start = getNumericIndexFromIndexType(startType);
let end = getNumericIndexFromIndexType(endType);
if (start !== undefined && end !== undefined) {
// A range consisting of the entire array is just the array:
// T[:] -> T
// T[0:^0] -> T
if (start === 0 && end === ~0) {
return objectType;
}
// A range that starts at the upper bound (^0), or ends at the lower bound (0), is just an empty tuple:
// T[^0:] -> []
// T[:0] -> []
// A range that starts at or after it ends is just an empty tuple:
// T[2:1] -> []
// T[1:1] -> []
// T[^1:^2] -> []
// T[^1:^1] -> []
if (start === ~0 || end === 0 ||
(start < 0 === end < 0) && start >= end) {
return createTupleType(emptyArray, 0, /*hasRestElement*/ false, !isMutableArrayOrTuple(objectType));
}
}
// Defer the operation until it can be fully instantiated.
const id = `${objectType.id},${startType.id},${endType.id}`;
let type = rangeTypes.get(id);
if (!type) {
type = <RangeType>createType(TypeFlags.Range);
type.objectType = objectType;
type.startType = startType;
type.endType = endType;
rangeTypes.set(id, type);
}
return type;
}
if (objectType.flags & TypeFlags.Union || startType.flags & TypeFlags.Union || endType.flags & TypeFlags.Union) {
const results: Type[] = [];
let hasErrors = false;
for (const object of getConstituents(objectType)) {
for (const start of getConstituents(startType)) {
for (const end of getConstituents(endType)) {
const type = getTupleSliceFromRangeType(object, objectType, start, end, rangeNode);
if (type) {
results.push(type);
}
else if (!rangeNode) {
// If there's no error node, we can immediately stop, since error reporting is off
return undefined;
}
else {
// Otherwise we set a flag and return at the end of the loop so we still mark all errors
hasErrors = true;
}
}
}
}
if (hasErrors) {
return undefined;
}
return accessFlags & AccessFlags.Writing ? getIntersectionType(results) : getUnionType(results);
}
return getTupleSliceFromRangeType(objectType, objectType, startType, endType, rangeNode);
}
function getTupleSliceFromRangeType(objectType: Type, fullObjectType: Type, startType: Type, endType: Type, rangeNode: RangeTypeNode | undefined): Type | undefined {
Debug.assert(!(objectType.flags & TypeFlags.Union));
Debug.assert(!(startType.flags & TypeFlags.Union));
Debug.assert(!(endType.flags & TypeFlags.Union));
// If the start type is `string`, `number`, `any`, or `never`, the result is an array
// of a union of all element types from the lower bound (0) to end.
//
// If the end type is `string`, `number`, `any`, or `never`, the result is an array
// of a union of all element types from start to the upper bound (^0).
//
// T[number:number] -> T[number][]
// T[number:Y] -> T[0:Y][number][]
// T[X:number] -> T[X:^0][number][]
const startIndexType = startType.flags & TypeFlags.InverseOffset ? (<InverseOffsetType>startType).indexType : startType;
const endIndexType = endType.flags & TypeFlags.InverseOffset ? (<InverseOffsetType>endType).indexType : endType;
const hasNonLiteralStart = startIndexType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Number | TypeFlags.String);
const hasNonLiteralEnd = endIndexType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Number | TypeFlags.String);
if (hasNonLiteralStart && hasNonLiteralEnd) {
const accessType = getIndexedAccessType(objectType, numberType);
return createArrayType(accessType, !isMutableArrayOrTuple(objectType));
}
if (hasNonLiteralStart) {
const limitedRange = getTupleSliceFromRangeType(objectType, fullObjectType, lowerBoundType, endType, rangeNode);
if (!limitedRange) {
return undefined;
}
const accessType = getIndexedAccessType(limitedRange, numberType);
return createArrayType(accessType, !isMutableArrayOrTuple(objectType));
}
if (hasNonLiteralEnd) {
const limitedRange = getTupleSliceFromRangeType(objectType, fullObjectType, startType, upperBoundType, rangeNode);
if (!limitedRange) {
return undefined;
}
const accessType = getIndexedAccessType(limitedRange, numberType);
return createArrayType(accessType, !isMutableArrayOrTuple(objectType));
}
let start = getNumericIndexFromIndexType(startType);
if (start === undefined) {
if (rangeNode) {
error(rangeNode.startType ?? rangeNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(startType), typeToString(fullObjectType));
}
return undefined;
}
let end = getNumericIndexFromIndexType(endType);
if (end === undefined) {
if (rangeNode) {
error(rangeNode.endType ?? rangeNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(endType), typeToString(fullObjectType));
}
return undefined;
}
// A range consisting of the entire array is just the array:
// T[:] -> T
// T[0:^0] -> T
if (start === 0 && end === ~0) {
return objectType;
}
// A range that starts at the upper bound (`^0`) or ends at the lower bound (`0`) is just an empty tuple:
// T[^0:] -> []
// T[:0] -> []
// A range that starts at or after it ends is just an empty tuple:
// T[2:1] -> []
// T[1:1] -> []
// T[^1:^2] -> []
// T[^1:^1] -> []
if (start === ~0 || end === 0 ||
(start < 0 === end < 0) && start >= end) {
return createTupleType(emptyArray, 0, /*hasRestElement*/ false, !isMutableArrayOrTuple(objectType));
}
const elementTypes: Type[] = [];
let minLength = 0;
let hasRestElement = false;
let associatedNames: __String[] | undefined;
if (!isTupleType(objectType)) {
if (start < 0 === end < 0) {
// If both signs agree, we can collect a tuple of a fixed length:
// T[][0:1] -> [T?]
// T[][^1:^0] -> [T?]
const elementType = getElementTypeOfArrayType(objectType);
Debug.assert(elementType !== undefined);
const length = end - start;
for (let i = 0; i < length; i++) {
elementTypes.push(elementType);
}
}
else if (start < 0 && end > 0) {
// If start is inverse and end is not, we can create a tuple of min(abs(start), end) optional elements.
const elementType = getElementTypeOfArrayType(objectType);
Debug.assert(elementType !== undefined);
const length = Math.min(end, ~start);
for (let i = 0; i < length; i++) {
elementTypes.push(elementType);
}
}
else {
// If the signs disagree, we cannot determine the lower and upper bound, thus we should return the entire array:
// T[][0:^1] -> T[]
// T[][^1:10] -> T[]
return objectType;
}
}
else {
const length = getLengthOfTupleType(objectType);
if (objectType.target.hasRestElement) {
if (start < 0) {
// If our start position is inverted, the result is an array of all of the optional or rest element types
// along with the set of required element types starting from the right equal to start:
// [A, B, ...C[]][^0:] -> C[]
// [A, B, ...C[]][^1:] -> (B | C)[]
// [A, B, ...C[]][^2:] -> (A | B | C)[]
const types: Type[] = [];
for (let i = objectType.target.minLength; i <= length; i++) {
const elementType = getTupleElementType(objectType, i);
Debug.assert(elementType !== undefined);
types.push(elementType);
}
for (let i = 0; i < ~start && i < objectType.target.minLength; i++) {
const elementType = getTupleElementType(objectType, objectType.target.minLength - i);
Debug.assert(elementType !== undefined);
types.push(elementType);
}
return createArrayType(getUnionType(types), !isMutableArrayOrTuple(objectType));
}
if (end < 0) {
// If our end position is inverted, the result is a tuple whose minimum length
// is reduced by the inverted amount:
// [A, B, C, ...D[]][1:^0] -> [B, C, ...D[]]
// [A, B, C, ...D[]][1:^1] -> [B, C?, ...D[]]
// [A, B, C, ...D[]][1:^2] -> [B?, C?, ...D[]]
hasRestElement = true;
minLength = -Math.min(~end, length);
end = length;
}
}
else {
// If we do not have a rest element, then we can explicitly adjust inverted offsets
// relative to the length of the tuple:
// [A, B, C][:^2] -> [A, B, C][:1] -> [A]
// [A, B, C][^2:] -> [A, B, C][1:] -> [B, C]
if (start < 0) start = Math.max(0, length - ~start);
if (end < 0) end = Math.max(0, length - ~end);
// We also clamp start and end to the length of the fixed tuple:
// [A, B][1:3] -> [A, B][1:2] -> [B]
if (start > length) start = length;
if (end > length) end = length;
}
// At this point our offsets should no longer be inverted.
Debug.assert(start >= 0 && end >= 0);
if (objectType.target.associatedNames) {
associatedNames = [];
}
for (let i = start; i < end; i++) {
if (i < objectType.target.minLength) minLength++;
const elementType = getTupleElementType(objectType, i);
Debug.assert(elementType !== undefined);
elementTypes.push(elementType);
if (objectType.target.associatedNames && associatedNames && i < objectType.target.associatedNames.length) {
associatedNames.push(objectType.target.associatedNames[i]);
}
}
if (minLength < 0) minLength = 0;
if (hasRestElement) {
const restType = getTupleElementType(objectType, end);
Debug.assert(restType !== undefined);
if (objectType.target.associatedNames && associatedNames && end < objectType.target.associatedNames.length) {
associatedNames.push(objectType.target.associatedNames[end]);
}
elementTypes.push(restType);
}
}
return createTupleType(elementTypes, minLength, hasRestElement, !isMutableArrayOrTuple(objectType), associatedNames);
}
function getTypeFromRangeTypeNode(node: RangeTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const objectType = getTypeFromTypeNode(node.objectType);
// A missing start is the lower bound (0):
// T[:Y] -> T[0:Y]
const startType = node.startType ? getTypeFromTypeNode(node.startType) : lowerBoundType;
// A missing end is the upper bound (^0):
// T[X:] -> T[X:^0]
const endOffset = node.endType ? getTypeFromTypeNode(node.endType) : upperBoundType;
const resolved = getRangeType(objectType, startType, endOffset, node);
links.resolvedType = resolved.flags & TypeFlags.Range &&
(<RangeType>resolved).objectType === objectType &&
(<RangeType>resolved).startType === startType &&
(<RangeType>resolved).endType === endOffset ?
getConstrainedTypeVariable(<RangeType>resolved, node) :
resolved;
}
return links.resolvedType;
}
function getActualTypeVariable(type: Type): Type {
if (type.flags & TypeFlags.Substitution) {
return (<SubstitutionType>type).typeVariable;
@ -12677,6 +13159,15 @@ namespace ts {
(<IndexedAccessType>type).indexType.flags & TypeFlags.Substitution)) {
return getIndexedAccessType(getActualTypeVariable((<IndexedAccessType>type).objectType), getActualTypeVariable((<IndexedAccessType>type).indexType));
}
if (type.flags & TypeFlags.Range && (
(<RangeType>type).objectType.flags & TypeFlags.Substitution ||
(<RangeType>type).startType.flags & TypeFlags.Substitution ||
(<RangeType>type).endType.flags & TypeFlags.Substitution)) {
return getRangeType(
getActualTypeVariable((<RangeType>type).objectType),
getActualTypeVariable((<RangeType>type).startType),
getActualTypeVariable((<RangeType>type).endType));
}
return type;
}
@ -12804,6 +13295,71 @@ namespace ts {
return links.resolvedType;
}
/**
* Determines if `type` is a string or numeric literal type for an numeric index. If `type` was
* an inverted offset, the twos-complement of the index is returned.
* @param invertNegativeOffsets Indicates whether negative offsets from a non-inverted offset type
* should be treated as an inverted offset (default `true`). For an indexed access type, negative offsets should not
* be inverted:
* ```
* // indexed access
* T[-1] -> T["-1"]
*
* // range
* T[-1:] -> T[^1]
* ```
*/
function getNumericIndexFromIndexType(type: Type, invertNegativeOffsets = true) {
const indexType = type.flags & TypeFlags.InverseOffset ? (type as InverseOffsetType).indexType : type;
let index =
isNumericLiteralType(indexType) ? indexType.value :
isStringLiteralType(indexType) ? canonicalNumericIndex(indexType.value) :
undefined;
if (index === undefined) {
return undefined;
}
if (!isInteger(index)) {
return undefined;
}
let inverted = indexType !== type;
if (index < 0 && (inverted || invertNegativeOffsets)) {
index = -index;
inverted = !inverted;
}
return inverted ? ~index : index;
}
function getInverseOffsetType(indexType: Type): Type {
if (indexType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Number | TypeFlags.String)) {
return indexType;
}
// The inverse offset of an inverse offset is the normal offset.
if (indexType.flags & TypeFlags.InverseOffset) {
return (<InverseOffsetType>indexType).indexType;
}
if (indexType.flags & TypeFlags.Union) {
return mapType(indexType, getInverseOffsetType);
}
// An inverse offset cannot be otherwise resolved without some context object from which to derive
// the length.
const id = "" + indexType.id;
let type = inverseOffsetTypes.get(id);
if (!type) {
type = <InverseOffsetType>createType(TypeFlags.InverseOffset);
type.indexType = indexType;
inverseOffsetTypes.set(id, type);
}
return type;
}
function getTypeFromInverseOffsetTypeNode(node: InverseOffsetTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getInverseOffsetType(getTypeFromTypeNode(node.indexType));
}
return links.resolvedType;
}
function getIdentifierChain(node: EntityName): Identifier[] {
if (isIdentifier(node)) {
return [node];
@ -13292,10 +13848,14 @@ namespace ts {
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
case SyntaxKind.MappedType:
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
case SyntaxKind.RangeType:
return getTypeFromRangeTypeNode(<RangeTypeNode>node);
case SyntaxKind.ConditionalType:
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return getTypeFromInferTypeNode(<InferTypeNode>node);
case SyntaxKind.InverseOffsetType:
return getTypeFromInverseOffsetTypeNode(<InverseOffsetTypeNode>node);
case SyntaxKind.ImportType:
return getTypeFromImportTypeNode(<ImportTypeNode>node);
// This function assumes that an identifier or qualified name is a type expression
@ -13749,6 +14309,15 @@ namespace ts {
if (flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
}
if (flags & TypeFlags.InverseOffset) {
return getInverseOffsetType(instantiateType((<InverseOffsetType>type).indexType, mapper));
}
if (flags & TypeFlags.Range) {
return getRangeType(
instantiateType((<RangeType>type).objectType, mapper),
instantiateType((<RangeType>type).startType, mapper),
instantiateType((<RangeType>type).endType, mapper));
}
if (flags & TypeFlags.Substitution) {
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
if (maybeVariable.flags & TypeFlags.TypeVariable) {
@ -15545,6 +16114,9 @@ namespace ts {
if (flags & TypeFlags.Index) {
return isRelatedTo((<IndexType>source).type, (<IndexType>target).type, /*reportErrors*/ false);
}
if (flags & TypeFlags.InverseOffset) {
return isRelatedTo((<InverseOffsetType>source).indexType, (<InverseOffsetType>target).indexType, /*reportErrors*/ false);
}
let result = Ternary.False;
if (flags & TypeFlags.IndexedAccess) {
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, /*reportErrors*/ false)) {
@ -15642,6 +16214,39 @@ namespace ts {
}
}
}
else if (target.flags & TypeFlags.InverseOffset) {
// A ^S is related to a ^T if S is related to T
if (source.flags & TypeFlags.InverseOffset) {
if (result = isRelatedTo((<InverseOffsetType>source).indexType, (<InverseOffsetType>target).indexType, /*reportErrors*/ false)) {
return result;
}
}
// TODO(rbuckton): Are they any other possible assignability relations?
}
else if (target.flags & TypeFlags.Range) {
// A type S is related to a type T[X:Y] if S is related to C, where C is the base
// constraint of T[X:Y] for writing.
if (relation !== identityRelation) {
const objectType = (<RangeType>target).objectType;
const startType = (<RangeType>target).startType;
const endType = (<RangeType>target).endType;
const baseObjectType = getBaseConstraintOfType(objectType) ?? objectType;
const baseStartType = getBaseConstraintOfType(startType) ?? startType;
const baseEndType = getBaseConstraintOfType(endType) ?? endType;
if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseStartType) && !isGenericIndexType(baseEndType)) {
const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0);
const constraint = getRangeTypeOrUndefined(
baseObjectType,
baseStartType,
baseEndType,
/*rangeNode*/ undefined,
accessFlags);
if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) {
return result;
}
}
}
}
else if (isGenericMappedType(target)) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(target);
@ -15686,6 +16291,18 @@ namespace ts {
return result;
}
}
if (source.flags & TypeFlags.Range && target.flags & TypeFlags.Range) {
// A type S[XS:YS] is related to a type T[XT:YT] if S is related to T, XS is related to XT, and YX is related to YT.
if (result = isRelatedTo((<RangeType>source).objectType, (<RangeType>target).objectType, reportErrors)) {
if (result &= isRelatedTo((<RangeType>source).startType, (<RangeType>target).startType, reportErrors)) {
result &= isRelatedTo((<RangeType>source).endType, (<RangeType>target).endType, reportErrors);
}
}
if (result) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
else {
const constraint = getConstraintOfType(<TypeVariable>source);
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
@ -15713,6 +16330,12 @@ namespace ts {
return result;
}
}
else if (source.flags & TypeFlags.InverseOffset) {
if (result = isRelatedTo(numberType, target, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
else if (source.flags & TypeFlags.Conditional) {
if (target.flags & TypeFlags.Conditional) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
@ -17038,6 +17661,14 @@ namespace ts {
isUnitType(type);
}
function isNumericLiteralType(type: Type): type is LiteralType & { value: number } {
return !!(type.flags & TypeFlags.NumberLiteral);
}
function isStringLiteralType(type: Type): type is LiteralType & { value: string } {
return !!(type.flags & TypeFlags.StringLiteral);
}
function getBaseTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
type.flags & TypeFlags.StringLiteral ? stringType :
@ -19071,6 +19702,10 @@ namespace ts {
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
}
function getConstituents(type: Type) {
return type.flags & TypeFlags.Union ? (type as UnionType).types : [type];
}
// Apply a mapping function to a type and return the resulting type. If the source type
// is a union type, the mapping function is applied to each constituent type and a union
// of the resulting types is returned.
@ -22285,7 +22920,7 @@ namespace ts {
// Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional.
// This is desired behavior, because when indexing with them as numeric entities, you are indexing
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
return (+name).toString() === name;
return canonicalNumericIndex(name) !== undefined;
}
function checkComputedPropertyName(node: ComputedPropertyName): Type {
@ -29162,6 +29797,43 @@ namespace ts {
checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node);
}
function checkOffsetIndexType(offsetType: Type, offsetNode: InverseOffsetTypeNode) {
if (!(offsetType.flags & TypeFlags.InverseOffset)) {
return offsetType;
}
if (!checkTypeAssignableTo((<InverseOffsetType>offsetType).indexType, offsetConstraintType, offsetNode.indexType, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) {
return errorType;
}
let hasErrors = false;
forEachType((<InverseOffsetType>offsetType).indexType, t => {
if (isLiteralType(t) && getNumericIndexFromIndexType(t) === undefined) {
hasErrors = true;
if (offsetNode) {
error(offsetNode.indexType, Diagnostics.Type_0_cannot_be_used_as_an_index_type_in_an_offset_or_range_type, typeToString(t));
}
else {
return true;
}
}
});
if (hasErrors) {
return errorType;
}
return offsetType;
}
function checkOffsetType(node: InverseOffsetTypeNode) {
checkSourceElement(node.indexType);
checkOffsetIndexType(getTypeFromInverseOffsetTypeNode(node), node);
}
function checkRangeType(node: RangeTypeNode) {
checkSourceElement(node.objectType);
checkSourceElement(node.startType);
checkSourceElement(node.endType);
getTypeFromRangeTypeNode(node);
}
function checkMappedType(node: MappedTypeNode) {
checkSourceElement(node.typeParameter);
checkSourceElement(node.type);
@ -33671,6 +34343,10 @@ namespace ts {
return checkSourceElement((node as JSDocTypeExpression).type);
case SyntaxKind.IndexedAccessType:
return checkIndexedAccessType(<IndexedAccessTypeNode>node);
case SyntaxKind.InverseOffsetType:
return checkOffsetType(<InverseOffsetTypeNode>node);
case SyntaxKind.RangeType:
return checkRangeType(<RangeTypeNode>node);
case SyntaxKind.MappedType:
return checkMappedType(<MappedTypeNode>node);
case SyntaxKind.FunctionDeclaration:

View File

@ -1351,11 +1351,21 @@ namespace ts {
}
}
let lazyIsArray = (value: any): boolean => {
if (typeof Array.isArray === "function") {
lazyIsArray = Array.isArray;
}
else {
lazyIsArray = value => value instanceof Array;
}
return lazyIsArray(value);
}
/**
* Tests whether a value is an array.
*/
export function isArray(value: any): value is readonly {}[] {
return Array.isArray ? Array.isArray(value) : value instanceof Array;
return lazyIsArray(value);
}
export function toArray<T>(value: T | T[]): T[];
@ -1367,13 +1377,63 @@ namespace ts {
/**
* Tests whether a value is string
*/
export function isString(text: unknown): text is string {
return typeof text === "string";
export function isString(x: unknown): x is string {
return typeof x === "string";
}
export function isNumber(x: unknown): x is number {
return typeof x === "number";
}
let lazyIsInteger = (x: number): boolean => {
if (typeof Number.isInteger === "function") {
lazyIsInteger = Number.isInteger;
}
else {
// https://tc39.es/ecma262/#sec-isinteger
lazyIsInteger = x =>
typeof x === "number" &&
!isNaN(x) &&
isFinite(x) &&
Math.floor(Math.abs(x)) === Math.abs(x);
}
return lazyIsInteger(x);
}
/**
* Tests whether the provided value is an integer.
*
* This emulates the behavior of [IsInteger (ECMA-262)](https://tc39.es/ecma262/#sec-isinteger).
*/
export function isInteger(x: number): boolean {
return lazyIsInteger(x);
}
/**
* Returns the numeric value of a string, if that numeric value's string representation is equal
* to the source string; otherwise, returns `undefined`.
*
* This determines whether a numeric string is a valid numeric index (e.g., the string representation
* can be round-tripped through `ToString(ToNumber(x))`).
*
* For example:
*
* - `"1"` returns `1`, because:
* - `ToNumber("1") === 1`
* - `ToString(1) === "1"`
* - `"1.0"` returns `undefined`, because:
* - `ToNumber("1.0") === 1`
* - `ToString(1) !== "1.0"`.
*
* This emulates the behavior of [CanonicalNumericIndexString (ECMA-262)](https://tc39.es/ecma262/#sec-canonicalnumericindexstring).
*/
export function canonicalNumericIndex(x: string | __String): number | undefined {
if (x === "-0") return -0;
const n = +x;
if (x !== n + "") return undefined;
return n;
}
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined;
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined;
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined {

View File

@ -10,7 +10,7 @@ namespace ts {
return currentAssertionLevel >= level;
}
export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void {
export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression {
if (!expression) {
if (verboseDebugInfo) {
message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo());

View File

@ -2879,6 +2879,10 @@
"category": "Message",
"code": 2782
},
"Type '{0}' cannot be used as an index type in an offset or range type.": {
"category": "Error",
"code": 2783
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",

View File

@ -1332,6 +1332,8 @@ namespace ts {
return emitConditionalType(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return emitInferType(<InferTypeNode>node);
case SyntaxKind.InverseOffsetType:
return emitOffsetType(<InverseOffsetTypeNode>node);
case SyntaxKind.ParenthesizedType:
return emitParenthesizedType(<ParenthesizedTypeNode>node);
case SyntaxKind.ExpressionWithTypeArguments:
@ -1344,6 +1346,8 @@ namespace ts {
return emitIndexedAccessType(<IndexedAccessTypeNode>node);
case SyntaxKind.MappedType:
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.RangeType:
return emitRangeType(<RangeTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.ImportType:
@ -2131,6 +2135,11 @@ namespace ts {
emit(node.typeParameter);
}
function emitOffsetType(node: InverseOffsetTypeNode) {
writePunctuation("^");
emit(node.indexType);
}
function emitParenthesizedType(node: ParenthesizedTypeNode) {
writePunctuation("(");
emit(node.type);
@ -2196,6 +2205,15 @@ namespace ts {
writePunctuation("}");
}
function emitRangeType(node: RangeTypeNode) {
emit(node.objectType);
writePunctuation("[");
emit(node.startType);
writePunctuation(":");
emit(node.endType);
writePunctuation("]");
}
function emitLiteralType(node: LiteralTypeNode) {
emitExpression(node.literal);
}

View File

@ -904,6 +904,18 @@ namespace ts {
: node;
}
export function createInverseOffsetTypeNode(indexType: TypeNode) {
const node = <InverseOffsetTypeNode>createSynthesizedNode(SyntaxKind.InverseOffsetType);
node.indexType = parenthesizeElementTypeMember(indexType);
return node;
}
export function updateInverseOffsetTypeNode(node: InverseOffsetTypeNode, indexType: TypeNode) {
return node.indexType !== indexType
? updateNode(createInverseOffsetTypeNode(indexType), node)
: node;
}
export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) {
const node = <ImportTypeNode>createSynthesizedNode(SyntaxKind.ImportType);
node.argument = argument;
@ -958,7 +970,7 @@ namespace ts {
return node;
}
export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) {
export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode {
return node.objectType !== objectType
|| node.indexType !== indexType
? updateNode(createIndexedAccessTypeNode(objectType, indexType), node)
@ -983,6 +995,22 @@ namespace ts {
: node;
}
export function createRangeTypeNode(objectType: TypeNode, startType: TypeNode | undefined, endType: TypeNode | undefined) {
const node = createSynthesizedNode(SyntaxKind.RangeType) as RangeTypeNode;
node.objectType = objectType;
node.startType = startType;
node.endType = endType;
return node;
}
export function updateRangeTypeNode(node: RangeTypeNode, objectType: TypeNode, startType: TypeNode | undefined, endType: TypeNode | undefined) {
return node.objectType !== objectType
|| node.startType !== startType
|| node.endType !== endType
? updateNode(createRangeTypeNode(objectType, startType, endType), node)
: node;
}
export function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) {
const node = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode;
node.literal = literal;

View File

@ -190,6 +190,8 @@ namespace ts {
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
case SyntaxKind.InferType:
return visitNode(cbNode, (<InferTypeNode>node).typeParameter);
case SyntaxKind.InverseOffsetType:
return visitNode(cbNode, (<InverseOffsetTypeNode>node).indexType);
case SyntaxKind.ImportType:
return visitNode(cbNode, (<ImportTypeNode>node).argument) ||
visitNode(cbNode, (<ImportTypeNode>node).qualifier) ||
@ -205,6 +207,10 @@ namespace ts {
visitNode(cbNode, (<MappedTypeNode>node).typeParameter) ||
visitNode(cbNode, (<MappedTypeNode>node).questionToken) ||
visitNode(cbNode, (<MappedTypeNode>node).type);
case SyntaxKind.RangeType:
return visitNode(cbNode, (<RangeTypeNode>node).objectType) ||
visitNode(cbNode, (<RangeTypeNode>node).startType) ||
visitNode(cbNode, (<RangeTypeNode>node).endType);
case SyntaxKind.LiteralType:
return visitNode(cbNode, (<LiteralTypeNode>node).literal);
case SyntaxKind.ObjectBindingPattern:
@ -3165,6 +3171,7 @@ namespace ts {
case SyntaxKind.InferKeyword:
case SyntaxKind.ImportKeyword:
case SyntaxKind.AssertsKeyword:
case SyntaxKind.CaretToken:
return true;
case SyntaxKind.FunctionKeyword:
return !inStartOfParameter;
@ -3200,10 +3207,19 @@ namespace ts {
break;
case SyntaxKind.OpenBracketToken:
parseExpected(SyntaxKind.OpenBracketToken);
if (isStartOfType()) {
const indexType = isStartOfType() ? parseType() : undefined;
if (parseOptionalToken(SyntaxKind.ColonToken)) {
const node = createNode(SyntaxKind.RangeType, type.pos) as RangeTypeNode;
node.objectType = type;
node.startType = indexType;
node.endType = isStartOfType() ? parseType() : undefined;
parseExpected(SyntaxKind.CloseBracketToken);
type = finishNode(node);
}
else if (indexType) {
const node = createNode(SyntaxKind.IndexedAccessType, type.pos) as IndexedAccessTypeNode;
node.objectType = type;
node.indexType = parseType();
node.indexType = indexType;
parseExpected(SyntaxKind.CloseBracketToken);
type = finishNode(node);
}
@ -3245,6 +3261,13 @@ namespace ts {
return finishNode(node);
}
function parseOffsetType(): InverseOffsetTypeNode {
const node = <InverseOffsetTypeNode>createNode(SyntaxKind.InverseOffsetType);
parseExpected(SyntaxKind.CaretToken);
node.indexType = parseType();
return finishNode(node);
}
function parseTypeOperatorOrHigher(): TypeNode {
const operator = token();
switch (operator) {
@ -3254,6 +3277,8 @@ namespace ts {
return parseTypeOperator(operator);
case SyntaxKind.InferKeyword:
return parseInferType();
case SyntaxKind.CaretToken:
return parseOffsetType();
}
return parsePostfixTypeOrHigher();
}

View File

@ -322,11 +322,13 @@ namespace ts {
IntersectionType,
ConditionalType,
InferType,
InverseOffsetType,
ParenthesizedType,
ThisType,
TypeOperator,
IndexedAccessType,
MappedType,
RangeType,
LiteralType,
ImportType,
// Binding patterns
@ -1310,6 +1312,11 @@ namespace ts {
typeParameter: TypeParameterDeclaration;
}
export interface InverseOffsetTypeNode extends TypeNode {
kind: SyntaxKind.InverseOffsetType;
indexType: TypeNode;
}
export interface ParenthesizedTypeNode extends TypeNode {
kind: SyntaxKind.ParenthesizedType;
type: TypeNode;
@ -1340,6 +1347,13 @@ namespace ts {
type?: TypeNode;
}
export interface RangeTypeNode extends TypeNode {
kind: SyntaxKind.RangeType;
objectType: TypeNode;
startType: TypeNode | undefined;
endType: TypeNode | undefined;
}
export interface LiteralTypeNode extends TypeNode {
kind: SyntaxKind.LiteralType;
literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression;
@ -4153,6 +4167,7 @@ namespace ts {
Function = "__function", // Unnamed function expression
Computed = "__computed", // Computed property name declaration with dynamic name
Resolving = "__resolving__", // Indicator symbol used to mark partially resolved type aliases
Boundary = "__boundary", // Indicator symbol for the unspecified upper or lower bound in a range type (i.e., `T[:]`)
ExportEquals = "export=", // Export assignment symbol
Default = "default", // Default export symbol (technically not wholly internal, but included here for usability)
This = "this",
@ -4281,6 +4296,8 @@ namespace ts {
Conditional = 1 << 24, // T extends U ? X : Y
Substitution = 1 << 25, // Type parameter substitution
NonPrimitive = 1 << 26, // intrinsic object type
Range = 1 << 27, // T[X:Y]
InverseOffset = 1 << 28, // ^T
/* @internal */
AnyOrUnknown = Any | Unknown,
@ -4299,7 +4316,7 @@ namespace ts {
/* @internal */
Primitive = String | Number | BigInt | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal | UniqueESSymbol,
StringLike = String | StringLiteral,
NumberLike = Number | NumberLiteral | Enum,
NumberLike = Number | NumberLiteral | Enum | InverseOffset,
BigIntLike = BigInt | BigIntLiteral,
BooleanLike = Boolean | BooleanLiteral,
EnumLike = Enum | EnumLiteral,
@ -4309,15 +4326,15 @@ namespace ts {
DisjointDomains = NonPrimitive | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbolLike | VoidLike | Null,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
InstantiablePrimitive = Index,
TypeVariable = TypeParameter | IndexedAccess | Range,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution | Range,
InstantiablePrimitive = Index | InverseOffset,
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
/* @internal */
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
/* @internal */
Simplifiable = IndexedAccess | Conditional,
Simplifiable = IndexedAccess | Range | Conditional,
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
@ -4687,7 +4704,7 @@ namespace ts {
simplifiedForWriting?: Type;
}
export type TypeVariable = TypeParameter | IndexedAccessType;
export type TypeVariable = TypeParameter | IndexedAccessType | RangeType;
// keyof T types (TypeFlags.Index)
export interface IndexType extends InstantiableType {
@ -4738,6 +4755,20 @@ namespace ts {
substitute: Type; // Type to substitute for type parameter
}
// ^T (TypeFlags.Offset)
export interface InverseOffsetType extends InstantiableType {
indexType: Type;
}
// T[X:Y] (TypeFlags.Range)
export interface RangeType extends InstantiableType {
objectType: Type;
startType: Type;
endType: Type;
simplifiedForReading?: Type;
simplifiedForWriting?: Type;
}
/* @internal */
export const enum JsxReferenceKind {
Component,

View File

@ -1000,6 +1000,10 @@ namespace ts {
return node.kind === SyntaxKind.InferType;
}
export function isOffsetTypeNode(node: Node): node is InverseOffsetTypeNode {
return node.kind === SyntaxKind.InverseOffsetType;
}
export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
return node.kind === SyntaxKind.ParenthesizedType;
}

View File

@ -398,6 +398,10 @@ namespace ts {
return updateInferTypeNode(<InferTypeNode>node,
visitNode((<InferTypeNode>node).typeParameter, visitor, isTypeParameterDeclaration));
case SyntaxKind.InverseOffsetType:
return updateInverseOffsetTypeNode(<InverseOffsetTypeNode>node,
visitNode((<InverseOffsetTypeNode>node).indexType, visitor, isTypeNode));
case SyntaxKind.ImportType:
return updateImportTypeNode(<ImportTypeNode>node,
visitNode((<ImportTypeNode>node).argument, visitor, isTypeNode),
@ -426,6 +430,12 @@ namespace ts {
visitNode((<MappedTypeNode>node).questionToken, tokenVisitor, isToken),
visitNode((<MappedTypeNode>node).type, visitor, isTypeNode));
case SyntaxKind.RangeType:
return updateRangeTypeNode((<RangeTypeNode>node),
visitNode((<RangeTypeNode>node).objectType, visitor, isTypeNode),
visitNode((<RangeTypeNode>node).startType, visitor, isTypeNode),
visitNode((<RangeTypeNode>node).endType, visitor, isTypeNode));
case SyntaxKind.LiteralType:
return updateLiteralTypeNode(<LiteralTypeNode>node,
visitNode((<LiteralTypeNode>node).literal, visitor, isExpression));

View File

@ -35,16 +35,17 @@ namespace ts.codefix {
const checker = context.program.getTypeChecker();
let suggestedSymbol: Symbol | undefined;
if (isPropertyAccessExpression(node.parent) && node.parent.name === node) {
const { parent } = node;
if (isPropertyAccessExpression(parent) && parent.name === node) {
Debug.assert(isIdentifierOrPrivateIdentifier(node), "Expected an identifier for spelling (property access)");
let containingType = checker.getTypeAtLocation(node.parent.expression);
let containingType = checker.getTypeAtLocation(parent.expression);
if (node.parent.flags & NodeFlags.OptionalChain) {
containingType = checker.getNonNullableType(containingType);
}
const name = node as Identifier | PrivateIdentifier;
suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(name, containingType);
}
else if (isImportSpecifier(node.parent) && node.parent.name === node) {
else if (isImportSpecifier(parent) && parent.name === node) {
Debug.assert(node.kind === SyntaxKind.Identifier, "Expected an identifier for spelling (import)");
const importDeclaration = findAncestor(node, isImportDeclaration)!;
const resolvedSourceFile = getResolvedSourceFileFromImportDeclaration(sourceFile, context, importDeclaration);

View File

@ -0,0 +1,196 @@
tests/cases/conformance/types/range/semantics.ts(14,15): error TS2461: Type '{}' is not an array type.
tests/cases/conformance/types/range/semantics.ts(21,25): error TS2536: Type 'true' cannot be used to index type '[1, 2, 3]'.
==== tests/cases/conformance/types/range/semantics.ts (2 errors) ====
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// Semantic Rules
{
// - The result of a *Range Type* has the same mutability as its *object type*.
type T1 = [1, 2, 3][0:2];
type T2 = (readonly [1, 2, 3])[0:2];
}
{
// - The *object type* of a *Range Type* is constrained to be an *Array Type* or a *Tuple Type*.
type T1 = [1, 2, 3][0:2];
type T2 = number[][0:2];
type T3 = {}[0:2]; // error
~~~~~~~
!!! error TS2461: Type '{}' is not an array type.
}
{
// - The *start type* and *end type* of a *Range Type* are constrained to `string | number`.
type T1 = [1, 2, 3][0:2];
type T2 = [1, 2, 3]["0":"2"];
type T3 = [1, 2, 3][true:false]; // error
~~~~
!!! error TS2536: Type 'true' cannot be used to index type '[1, 2, 3]'.
}
{
// - The *start type* of a *Range Type* is optional. If not present, the *lower bound type* (`0`) is used.
type T2 = [1, 2, 3][:1];
// - The *end type* of a *Range Type* is optional. If not present, the *upper bound type* (`^0`) is used.
type T3 = [1, 2, 3][1:];
type T1 = [1, 2, 3][:];
}
{
// - If the *start type* or *end type* of a *Range Type* are negative-valued *Numeric Literal Types* (or numeric index-valued *String Literal Types*), they are treated
// as an *Inverse Offset Type* for the absolute value of the numeric index value of the respective type.
// - NOTE: This does not work for `-0` as JavaScript generally treats `-0` and `0` as the same value except for a few small corner cases and we must
// align with this behavior.
type T1 = [1, 2, 3, 4][1:-1];
type T2 = [1, 2, 3, 4][1:-0];
}
{
// - If the *object type* is a "generic object type", or if either of the *start type* or *end type* are
// "generic index types", then the operation is deferred until it they can be instantiated.
type T1<A extends any[]> = A[0:2];
type T2 = T1<[1, 2, 3]>;
type T3 = T1<number[]>;
type T4<X extends number> = [1, 2, 3][X:];
type T5 = T4<1>;
type T6<Y extends number> = [1, 2, 3][:Y];
type T7 = T6<^1>;
}
{
// - If either the *object type*, *start type*, or *end type* are *Union Types*, the result is distributed over
// each constituent in the following manner:
// - The *object type* is distributed over any *Inverse Offset Type* constituent of *start type* or *end type*.
// This is necessary as an *Inverse Offset Type* can have a different outcome depending on the *object type*
// it is resolved against.
// - The *start type* and *end type* are distributed over each constituent of the *object type*.
// - The *object type* is distributed over each constituent of the *start type* and *end type*.
// - The results of the distribution are either an *Intersection Type* (if the *Range Type* was a "write"
// location), or a *Union Type* (if the *Range Type* was a "read" location).
type T1 = ([1, 2, 3] | [2, 3, 4])[0:2];
type T2 = [1, 2, 3][0|1:2];
type T3 = [1, 2, 3][0:1|2];
type T4 = ([1, 2, 3] | [2, 3, 4])[0|1:2|3];
type T5 = ([1, 2, 3] | [9, 8])[0:^1];
}
{
// - Otherwise (or for each constituent of the distribution),
{
// - If neither the *start type* nor the *end type* are *String Literal Types* or a *Numeric Literal Types*, then
// - Return an *Array Type* for the union of each element of the *object type*: `T[any:any] -> T[number][]`.
type T1 = [1, 2, 3][number:number];
type T2 = [1, 2, 3][string:string];
type T3 = [1, 2, 3][any:any];
type T4 = [1, 2, 3][never:never];
}
{
// - If the *start type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *end type*, but with a *start type* of `0`: `T[any:Y] -> T[:Y][number][]`.
type T1 = [1, 2, 3][number:2];
type T2 = [1, 2, 3][string:2];
type T3 = [1, 2, 3][any:2];
type T4 = [1, 2, 3][never:2];
}
{
// - If the *end type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *start type*, but with an *end type* of `^0`: `T[X:any] -> T[X:][number][]`.
type T1 = [1, 2, 3][1:number];
type T2 = [1, 2, 3][1:string];
type T3 = [1, 2, 3][1:any];
type T4 = [1, 2, 3][1:never];
}
{
// - If the *start type* is the *lower bound type* (`0`) and the *end type* is the *upper bound type* (`^0`),
// then we are including all elements: return the *object type* of the *Range Type*.
type T1 = [1, 2, 3][0:^0];
}
{
// - If the *start type* is the *upper bound type* (`^0`) or the *end type* is the *lower bound type* (`0`),
// then we are including no elements: return the empty *Tuple Type* (`[]`).
type T1 = [1, 2, 3][^0:^0];
type T2 = [1, 2, 3][0:0];
}
{
// - If the *object type* is an *Array Type*, then
{
// - If the signs of both the *start type* and *end type* agree, then return
// a fixed-length *Tuple Type* with a minimum length of `0` for the difference between the *end type* and
// the *start type* whose elements are the element type of the *object type*: `T[][0:1] -> [T?]`.
type T1 = number[][0:2];
type T2 = number[][^2:^0];
}
{
// - If the *start type* is an *Inverse Offset Type* and the *end type* is not, we can create a tuple of
// `min(abs(start), end)` optional elements.
type T1 = number[][^2:4];
}
{
// - Otherwise, we cannot derive a fixed length: return the *object type* of the *Range Type*.
type T1 = number[][0:^2];
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
// - If the *start type* is an *Inverse Offset Type*, return an *Array Type* for the union of each
// *optional type* and the *rest element type*, along with the set of `n` right-most required
// elements of the *object type* where `n` is the absolute value of the *start type*.
type T1 = [1, 2, 3, ...4[]][^1:];
type T2 = [1, 2, 3, ...4[]][^2:];
type T3 = [1, 2, 3, ...4[]][^3:];
type T4 = [1, 2, 3?, ...4[]][^1:];
type T5 = [1, 2, 3?, ...4[]][^2:];
type T6 = [1, 2, 3?, ...4[]][^3:];
}
{
// - If the *end type* is an *Inverse Offset Type*, return a *Tuple Type* of the elements of the
// *object type* starting from the index at *start type*, but whose minimum length is reduced by the
// absolute value of the *end type*.
type T1 = [1, 2, 3, ...4[]][1:^1];
type T2 = [1, 2, 3, ...4[]][1:^2];
type T3 = [1, 2, 3, ...4[]][1:^3];
type T4 = [1, 2, 3?, ...4[]][1:^1];
type T5 = [1, 2, 3?, ...4[]][1:^2];
type T6 = [1, 2, 3?, ...4[]][1:^3];
}
}
{
// - Otherwise,
{
// - Clamp the *start type* and *end type* to values between `0` and the length of *object type*.
type T1 = [1, 2, 3][^5:10];
}
{
// - Return a *Tuple Type* for the elements of *object type* starting at *start type* and ending at
// *end type*.
type T1 = [1, 2, 3][1: 2];
}
}
}
}
==== tests/cases/conformance/types/range/assignability.ts (0 errors) ====
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1<T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) {
t = s;
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2<T, S extends [T, T, T]>(t: T[], s: S[0:2]) {
t = s;
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3<T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) {
t = s;
}

View File

@ -0,0 +1,262 @@
//// [tests/cases/conformance/types/range/ranges.ts] ////
//// [semantics.ts]
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// Semantic Rules
{
// - The result of a *Range Type* has the same mutability as its *object type*.
type T1 = [1, 2, 3][0:2];
type T2 = (readonly [1, 2, 3])[0:2];
}
{
// - The *object type* of a *Range Type* is constrained to be an *Array Type* or a *Tuple Type*.
type T1 = [1, 2, 3][0:2];
type T2 = number[][0:2];
type T3 = {}[0:2]; // error
}
{
// - The *start type* and *end type* of a *Range Type* are constrained to `string | number`.
type T1 = [1, 2, 3][0:2];
type T2 = [1, 2, 3]["0":"2"];
type T3 = [1, 2, 3][true:false]; // error
}
{
// - The *start type* of a *Range Type* is optional. If not present, the *lower bound type* (`0`) is used.
type T2 = [1, 2, 3][:1];
// - The *end type* of a *Range Type* is optional. If not present, the *upper bound type* (`^0`) is used.
type T3 = [1, 2, 3][1:];
type T1 = [1, 2, 3][:];
}
{
// - If the *start type* or *end type* of a *Range Type* are negative-valued *Numeric Literal Types* (or numeric index-valued *String Literal Types*), they are treated
// as an *Inverse Offset Type* for the absolute value of the numeric index value of the respective type.
// - NOTE: This does not work for `-0` as JavaScript generally treats `-0` and `0` as the same value except for a few small corner cases and we must
// align with this behavior.
type T1 = [1, 2, 3, 4][1:-1];
type T2 = [1, 2, 3, 4][1:-0];
}
{
// - If the *object type* is a "generic object type", or if either of the *start type* or *end type* are
// "generic index types", then the operation is deferred until it they can be instantiated.
type T1<A extends any[]> = A[0:2];
type T2 = T1<[1, 2, 3]>;
type T3 = T1<number[]>;
type T4<X extends number> = [1, 2, 3][X:];
type T5 = T4<1>;
type T6<Y extends number> = [1, 2, 3][:Y];
type T7 = T6<^1>;
}
{
// - If either the *object type*, *start type*, or *end type* are *Union Types*, the result is distributed over
// each constituent in the following manner:
// - The *object type* is distributed over any *Inverse Offset Type* constituent of *start type* or *end type*.
// This is necessary as an *Inverse Offset Type* can have a different outcome depending on the *object type*
// it is resolved against.
// - The *start type* and *end type* are distributed over each constituent of the *object type*.
// - The *object type* is distributed over each constituent of the *start type* and *end type*.
// - The results of the distribution are either an *Intersection Type* (if the *Range Type* was a "write"
// location), or a *Union Type* (if the *Range Type* was a "read" location).
type T1 = ([1, 2, 3] | [2, 3, 4])[0:2];
type T2 = [1, 2, 3][0|1:2];
type T3 = [1, 2, 3][0:1|2];
type T4 = ([1, 2, 3] | [2, 3, 4])[0|1:2|3];
type T5 = ([1, 2, 3] | [9, 8])[0:^1];
}
{
// - Otherwise (or for each constituent of the distribution),
{
// - If neither the *start type* nor the *end type* are *String Literal Types* or a *Numeric Literal Types*, then
// - Return an *Array Type* for the union of each element of the *object type*: `T[any:any] -> T[number][]`.
type T1 = [1, 2, 3][number:number];
type T2 = [1, 2, 3][string:string];
type T3 = [1, 2, 3][any:any];
type T4 = [1, 2, 3][never:never];
}
{
// - If the *start type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *end type*, but with a *start type* of `0`: `T[any:Y] -> T[:Y][number][]`.
type T1 = [1, 2, 3][number:2];
type T2 = [1, 2, 3][string:2];
type T3 = [1, 2, 3][any:2];
type T4 = [1, 2, 3][never:2];
}
{
// - If the *end type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *start type*, but with an *end type* of `^0`: `T[X:any] -> T[X:][number][]`.
type T1 = [1, 2, 3][1:number];
type T2 = [1, 2, 3][1:string];
type T3 = [1, 2, 3][1:any];
type T4 = [1, 2, 3][1:never];
}
{
// - If the *start type* is the *lower bound type* (`0`) and the *end type* is the *upper bound type* (`^0`),
// then we are including all elements: return the *object type* of the *Range Type*.
type T1 = [1, 2, 3][0:^0];
}
{
// - If the *start type* is the *upper bound type* (`^0`) or the *end type* is the *lower bound type* (`0`),
// then we are including no elements: return the empty *Tuple Type* (`[]`).
type T1 = [1, 2, 3][^0:^0];
type T2 = [1, 2, 3][0:0];
}
{
// - If the *object type* is an *Array Type*, then
{
// - If the signs of both the *start type* and *end type* agree, then return
// a fixed-length *Tuple Type* with a minimum length of `0` for the difference between the *end type* and
// the *start type* whose elements are the element type of the *object type*: `T[][0:1] -> [T?]`.
type T1 = number[][0:2];
type T2 = number[][^2:^0];
}
{
// - If the *start type* is an *Inverse Offset Type* and the *end type* is not, we can create a tuple of
// `min(abs(start), end)` optional elements.
type T1 = number[][^2:4];
}
{
// - Otherwise, we cannot derive a fixed length: return the *object type* of the *Range Type*.
type T1 = number[][0:^2];
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
// - If the *start type* is an *Inverse Offset Type*, return an *Array Type* for the union of each
// *optional type* and the *rest element type*, along with the set of `n` right-most required
// elements of the *object type* where `n` is the absolute value of the *start type*.
type T1 = [1, 2, 3, ...4[]][^1:];
type T2 = [1, 2, 3, ...4[]][^2:];
type T3 = [1, 2, 3, ...4[]][^3:];
type T4 = [1, 2, 3?, ...4[]][^1:];
type T5 = [1, 2, 3?, ...4[]][^2:];
type T6 = [1, 2, 3?, ...4[]][^3:];
}
{
// - If the *end type* is an *Inverse Offset Type*, return a *Tuple Type* of the elements of the
// *object type* starting from the index at *start type*, but whose minimum length is reduced by the
// absolute value of the *end type*.
type T1 = [1, 2, 3, ...4[]][1:^1];
type T2 = [1, 2, 3, ...4[]][1:^2];
type T3 = [1, 2, 3, ...4[]][1:^3];
type T4 = [1, 2, 3?, ...4[]][1:^1];
type T5 = [1, 2, 3?, ...4[]][1:^2];
type T6 = [1, 2, 3?, ...4[]][1:^3];
}
}
{
// - Otherwise,
{
// - Clamp the *start type* and *end type* to values between `0` and the length of *object type*.
type T1 = [1, 2, 3][^5:10];
}
{
// - Return a *Tuple Type* for the elements of *object type* starting at *start type* and ending at
// *end type*.
type T1 = [1, 2, 3][1: 2];
}
}
}
}
//// [assignability.ts]
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1<T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) {
t = s;
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2<T, S extends [T, T, T]>(t: T[], s: S[0:2]) {
t = s;
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3<T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) {
t = s;
}
//// [semantics.js]
"use strict";
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// Semantic Rules
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
// - Otherwise (or for each constituent of the distribution),
{
}
{
}
{
}
{
}
{
}
{
// - If the *object type* is an *Array Type*, then
{
}
{
}
{
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
}
{
}
}
{
// - Otherwise,
{
}
{
}
}
}
}
//// [assignability.js]
"use strict";
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1(t, s) {
t = s;
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2(t, s) {
t = s;
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3(t, s) {
t = s;
}

View File

@ -0,0 +1,326 @@
=== tests/cases/conformance/types/range/semantics.ts ===
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// Semantic Rules
{
// - The result of a *Range Type* has the same mutability as its *object type*.
type T1 = [1, 2, 3][0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 3, 1))
type T2 = (readonly [1, 2, 3])[0:2];
>T2 : Symbol(T2, Decl(semantics.ts, 5, 29))
}
{
// - The *object type* of a *Range Type* is constrained to be an *Array Type* or a *Tuple Type*.
type T1 = [1, 2, 3][0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 9, 1))
type T2 = number[][0:2];
>T2 : Symbol(T2, Decl(semantics.ts, 11, 29))
type T3 = {}[0:2]; // error
>T3 : Symbol(T3, Decl(semantics.ts, 12, 28))
}
{
// - The *start type* and *end type* of a *Range Type* are constrained to `string | number`.
type T1 = [1, 2, 3][0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 16, 1))
type T2 = [1, 2, 3]["0":"2"];
>T2 : Symbol(T2, Decl(semantics.ts, 18, 29))
type T3 = [1, 2, 3][true:false]; // error
>T3 : Symbol(T3, Decl(semantics.ts, 19, 33))
}
{
// - The *start type* of a *Range Type* is optional. If not present, the *lower bound type* (`0`) is used.
type T2 = [1, 2, 3][:1];
>T2 : Symbol(T2, Decl(semantics.ts, 23, 1))
// - The *end type* of a *Range Type* is optional. If not present, the *upper bound type* (`^0`) is used.
type T3 = [1, 2, 3][1:];
>T3 : Symbol(T3, Decl(semantics.ts, 25, 28))
type T1 = [1, 2, 3][:];
>T1 : Symbol(T1, Decl(semantics.ts, 28, 28))
}
{
// - If the *start type* or *end type* of a *Range Type* are negative-valued *Numeric Literal Types* (or numeric index-valued *String Literal Types*), they are treated
// as an *Inverse Offset Type* for the absolute value of the numeric index value of the respective type.
// - NOTE: This does not work for `-0` as JavaScript generally treats `-0` and `0` as the same value except for a few small corner cases and we must
// align with this behavior.
type T1 = [1, 2, 3, 4][1:-1];
>T1 : Symbol(T1, Decl(semantics.ts, 32, 1))
type T2 = [1, 2, 3, 4][1:-0];
>T2 : Symbol(T2, Decl(semantics.ts, 37, 33))
}
{
// - If the *object type* is a "generic object type", or if either of the *start type* or *end type* are
// "generic index types", then the operation is deferred until it they can be instantiated.
type T1<A extends any[]> = A[0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 41, 1))
>A : Symbol(A, Decl(semantics.ts, 44, 12))
>A : Symbol(A, Decl(semantics.ts, 44, 12))
type T2 = T1<[1, 2, 3]>;
>T2 : Symbol(T2, Decl(semantics.ts, 44, 38))
>T1 : Symbol(T1, Decl(semantics.ts, 41, 1))
type T3 = T1<number[]>;
>T3 : Symbol(T3, Decl(semantics.ts, 45, 28))
>T1 : Symbol(T1, Decl(semantics.ts, 41, 1))
type T4<X extends number> = [1, 2, 3][X:];
>T4 : Symbol(T4, Decl(semantics.ts, 46, 27))
>X : Symbol(X, Decl(semantics.ts, 48, 12))
>X : Symbol(X, Decl(semantics.ts, 48, 12))
type T5 = T4<1>;
>T5 : Symbol(T5, Decl(semantics.ts, 48, 46))
>T4 : Symbol(T4, Decl(semantics.ts, 46, 27))
type T6<Y extends number> = [1, 2, 3][:Y];
>T6 : Symbol(T6, Decl(semantics.ts, 49, 20))
>Y : Symbol(Y, Decl(semantics.ts, 51, 12))
>Y : Symbol(Y, Decl(semantics.ts, 51, 12))
type T7 = T6<^1>;
>T7 : Symbol(T7, Decl(semantics.ts, 51, 46))
>T6 : Symbol(T6, Decl(semantics.ts, 49, 20))
}
{
// - If either the *object type*, *start type*, or *end type* are *Union Types*, the result is distributed over
// each constituent in the following manner:
// - The *object type* is distributed over any *Inverse Offset Type* constituent of *start type* or *end type*.
// This is necessary as an *Inverse Offset Type* can have a different outcome depending on the *object type*
// it is resolved against.
// - The *start type* and *end type* are distributed over each constituent of the *object type*.
// - The *object type* is distributed over each constituent of the *start type* and *end type*.
// - The results of the distribution are either an *Intersection Type* (if the *Range Type* was a "write"
// location), or a *Union Type* (if the *Range Type* was a "read" location).
type T1 = ([1, 2, 3] | [2, 3, 4])[0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 55, 1))
type T2 = [1, 2, 3][0|1:2];
>T2 : Symbol(T2, Decl(semantics.ts, 65, 43))
type T3 = [1, 2, 3][0:1|2];
>T3 : Symbol(T3, Decl(semantics.ts, 66, 31))
type T4 = ([1, 2, 3] | [2, 3, 4])[0|1:2|3];
>T4 : Symbol(T4, Decl(semantics.ts, 67, 31))
type T5 = ([1, 2, 3] | [9, 8])[0:^1];
>T5 : Symbol(T5, Decl(semantics.ts, 68, 47))
}
{
// - Otherwise (or for each constituent of the distribution),
{
// - If neither the *start type* nor the *end type* are *String Literal Types* or a *Numeric Literal Types*, then
// - Return an *Array Type* for the union of each element of the *object type*: `T[any:any] -> T[number][]`.
type T1 = [1, 2, 3][number:number];
>T1 : Symbol(T1, Decl(semantics.ts, 74, 5))
type T2 = [1, 2, 3][string:string];
>T2 : Symbol(T2, Decl(semantics.ts, 77, 43))
type T3 = [1, 2, 3][any:any];
>T3 : Symbol(T3, Decl(semantics.ts, 78, 43))
type T4 = [1, 2, 3][never:never];
>T4 : Symbol(T4, Decl(semantics.ts, 79, 37))
}
{
// - If the *start type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *end type*, but with a *start type* of `0`: `T[any:Y] -> T[:Y][number][]`.
type T1 = [1, 2, 3][number:2];
>T1 : Symbol(T1, Decl(semantics.ts, 82, 5))
type T2 = [1, 2, 3][string:2];
>T2 : Symbol(T2, Decl(semantics.ts, 86, 38))
type T3 = [1, 2, 3][any:2];
>T3 : Symbol(T3, Decl(semantics.ts, 87, 38))
type T4 = [1, 2, 3][never:2];
>T4 : Symbol(T4, Decl(semantics.ts, 88, 35))
}
{
// - If the *end type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *start type*, but with an *end type* of `^0`: `T[X:any] -> T[X:][number][]`.
type T1 = [1, 2, 3][1:number];
>T1 : Symbol(T1, Decl(semantics.ts, 91, 5))
type T2 = [1, 2, 3][1:string];
>T2 : Symbol(T2, Decl(semantics.ts, 95, 38))
type T3 = [1, 2, 3][1:any];
>T3 : Symbol(T3, Decl(semantics.ts, 96, 38))
type T4 = [1, 2, 3][1:never];
>T4 : Symbol(T4, Decl(semantics.ts, 97, 35))
}
{
// - If the *start type* is the *lower bound type* (`0`) and the *end type* is the *upper bound type* (`^0`),
// then we are including all elements: return the *object type* of the *Range Type*.
type T1 = [1, 2, 3][0:^0];
>T1 : Symbol(T1, Decl(semantics.ts, 100, 5))
}
{
// - If the *start type* is the *upper bound type* (`^0`) or the *end type* is the *lower bound type* (`0`),
// then we are including no elements: return the empty *Tuple Type* (`[]`).
type T1 = [1, 2, 3][^0:^0];
>T1 : Symbol(T1, Decl(semantics.ts, 105, 5))
type T2 = [1, 2, 3][0:0];
>T2 : Symbol(T2, Decl(semantics.ts, 108, 35))
}
{
// - If the *object type* is an *Array Type*, then
{
// - If the signs of both the *start type* and *end type* agree, then return
// a fixed-length *Tuple Type* with a minimum length of `0` for the difference between the *end type* and
// the *start type* whose elements are the element type of the *object type*: `T[][0:1] -> [T?]`.
type T1 = number[][0:2];
>T1 : Symbol(T1, Decl(semantics.ts, 113, 9))
type T2 = number[][^2:^0];
>T2 : Symbol(T2, Decl(semantics.ts, 117, 36))
}
{
// - If the *start type* is an *Inverse Offset Type* and the *end type* is not, we can create a tuple of
// `min(abs(start), end)` optional elements.
type T1 = number[][^2:4];
>T1 : Symbol(T1, Decl(semantics.ts, 120, 9))
}
{
// - Otherwise, we cannot derive a fixed length: return the *object type* of the *Range Type*.
type T1 = number[][0:^2];
>T1 : Symbol(T1, Decl(semantics.ts, 125, 9))
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
// - If the *start type* is an *Inverse Offset Type*, return an *Array Type* for the union of each
// *optional type* and the *rest element type*, along with the set of `n` right-most required
// elements of the *object type* where `n` is the absolute value of the *start type*.
type T1 = [1, 2, 3, ...4[]][^1:];
>T1 : Symbol(T1, Decl(semantics.ts, 134, 13))
type T2 = [1, 2, 3, ...4[]][^2:];
>T2 : Symbol(T2, Decl(semantics.ts, 138, 49))
type T3 = [1, 2, 3, ...4[]][^3:];
>T3 : Symbol(T3, Decl(semantics.ts, 139, 49))
type T4 = [1, 2, 3?, ...4[]][^1:];
>T4 : Symbol(T4, Decl(semantics.ts, 140, 49))
type T5 = [1, 2, 3?, ...4[]][^2:];
>T5 : Symbol(T5, Decl(semantics.ts, 141, 50))
type T6 = [1, 2, 3?, ...4[]][^3:];
>T6 : Symbol(T6, Decl(semantics.ts, 142, 50))
}
{
// - If the *end type* is an *Inverse Offset Type*, return a *Tuple Type* of the elements of the
// *object type* starting from the index at *start type*, but whose minimum length is reduced by the
// absolute value of the *end type*.
type T1 = [1, 2, 3, ...4[]][1:^1];
>T1 : Symbol(T1, Decl(semantics.ts, 145, 13))
type T2 = [1, 2, 3, ...4[]][1:^2];
>T2 : Symbol(T2, Decl(semantics.ts, 149, 50))
type T3 = [1, 2, 3, ...4[]][1:^3];
>T3 : Symbol(T3, Decl(semantics.ts, 150, 50))
type T4 = [1, 2, 3?, ...4[]][1:^1];
>T4 : Symbol(T4, Decl(semantics.ts, 151, 50))
type T5 = [1, 2, 3?, ...4[]][1:^2];
>T5 : Symbol(T5, Decl(semantics.ts, 152, 51))
type T6 = [1, 2, 3?, ...4[]][1:^3];
>T6 : Symbol(T6, Decl(semantics.ts, 153, 51))
}
}
{
// - Otherwise,
{
// - Clamp the *start type* and *end type* to values between `0` and the length of *object type*.
type T1 = [1, 2, 3][^5:10];
>T1 : Symbol(T1, Decl(semantics.ts, 159, 13))
}
{
// - Return a *Tuple Type* for the elements of *object type* starting at *start type* and ending at
// *end type*.
type T1 = [1, 2, 3][1: 2];
>T1 : Symbol(T1, Decl(semantics.ts, 163, 13))
}
}
}
}
=== tests/cases/conformance/types/range/assignability.ts ===
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1<T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) {
>f1 : Symbol(f1, Decl(assignability.ts, 0, 0))
>T : Symbol(T, Decl(assignability.ts, 1, 12))
>U : Symbol(U, Decl(assignability.ts, 1, 35))
>t : Symbol(t, Decl(assignability.ts, 1, 53))
>T : Symbol(T, Decl(assignability.ts, 1, 12))
>U : Symbol(U, Decl(assignability.ts, 1, 35))
>s : Symbol(s, Decl(assignability.ts, 1, 64))
t = s;
>t : Symbol(t, Decl(assignability.ts, 1, 53))
>s : Symbol(s, Decl(assignability.ts, 1, 64))
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2<T, S extends [T, T, T]>(t: T[], s: S[0:2]) {
>f2 : Symbol(f2, Decl(assignability.ts, 3, 1))
>T : Symbol(T, Decl(assignability.ts, 6, 12))
>S : Symbol(S, Decl(assignability.ts, 6, 14))
>T : Symbol(T, Decl(assignability.ts, 6, 12))
>T : Symbol(T, Decl(assignability.ts, 6, 12))
>T : Symbol(T, Decl(assignability.ts, 6, 12))
>t : Symbol(t, Decl(assignability.ts, 6, 36))
>T : Symbol(T, Decl(assignability.ts, 6, 12))
>s : Symbol(s, Decl(assignability.ts, 6, 43))
>S : Symbol(S, Decl(assignability.ts, 6, 14))
t = s;
>t : Symbol(t, Decl(assignability.ts, 6, 36))
>s : Symbol(s, Decl(assignability.ts, 6, 43))
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3<T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) {
>f3 : Symbol(f3, Decl(assignability.ts, 8, 1))
>T : Symbol(T, Decl(assignability.ts, 11, 12))
>S : Symbol(S, Decl(assignability.ts, 11, 51))
>T : Symbol(T, Decl(assignability.ts, 11, 12))
>t : Symbol(t, Decl(assignability.ts, 11, 65))
>T : Symbol(T, Decl(assignability.ts, 11, 12))
>s : Symbol(s, Decl(assignability.ts, 11, 79))
>S : Symbol(S, Decl(assignability.ts, 11, 51))
t = s;
>t : Symbol(t, Decl(assignability.ts, 11, 65))
>s : Symbol(s, Decl(assignability.ts, 11, 79))
}

View File

@ -0,0 +1,309 @@
=== tests/cases/conformance/types/range/semantics.ts ===
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// Semantic Rules
{
// - The result of a *Range Type* has the same mutability as its *object type*.
type T1 = [1, 2, 3][0:2];
>T1 : [1, 2]
type T2 = (readonly [1, 2, 3])[0:2];
>T2 : readonly [1, 2]
}
{
// - The *object type* of a *Range Type* is constrained to be an *Array Type* or a *Tuple Type*.
type T1 = [1, 2, 3][0:2];
>T1 : [1, 2]
type T2 = number[][0:2];
>T2 : [number?, number?]
type T3 = {}[0:2]; // error
>T3 : any
}
{
// - The *start type* and *end type* of a *Range Type* are constrained to `string | number`.
type T1 = [1, 2, 3][0:2];
>T1 : [1, 2]
type T2 = [1, 2, 3]["0":"2"];
>T2 : [1, 2]
type T3 = [1, 2, 3][true:false]; // error
>T3 : any
>true : true
>false : false
}
{
// - The *start type* of a *Range Type* is optional. If not present, the *lower bound type* (`0`) is used.
type T2 = [1, 2, 3][:1];
>T2 : [1]
// - The *end type* of a *Range Type* is optional. If not present, the *upper bound type* (`^0`) is used.
type T3 = [1, 2, 3][1:];
>T3 : [2, 3]
type T1 = [1, 2, 3][:];
>T1 : [1, 2, 3]
}
{
// - If the *start type* or *end type* of a *Range Type* are negative-valued *Numeric Literal Types* (or numeric index-valued *String Literal Types*), they are treated
// as an *Inverse Offset Type* for the absolute value of the numeric index value of the respective type.
// - NOTE: This does not work for `-0` as JavaScript generally treats `-0` and `0` as the same value except for a few small corner cases and we must
// align with this behavior.
type T1 = [1, 2, 3, 4][1:-1];
>T1 : [2, 3]
>-1 : -1
>1 : 1
type T2 = [1, 2, 3, 4][1:-0];
>T2 : []
>-0 : 0
>0 : 0
}
{
// - If the *object type* is a "generic object type", or if either of the *start type* or *end type* are
// "generic index types", then the operation is deferred until it they can be instantiated.
type T1<A extends any[]> = A[0:2];
>T1 : A[0:2]
type T2 = T1<[1, 2, 3]>;
>T2 : [1, 2]
type T3 = T1<number[]>;
>T3 : [number?, number?]
type T4<X extends number> = [1, 2, 3][X:];
>T4 : [1, 2, 3][X:]
type T5 = T4<1>;
>T5 : [2, 3]
type T6<Y extends number> = [1, 2, 3][:Y];
>T6 : [1, 2, 3][:Y]
type T7 = T6<^1>;
>T7 : [1, 2]
}
{
// - If either the *object type*, *start type*, or *end type* are *Union Types*, the result is distributed over
// each constituent in the following manner:
// - The *object type* is distributed over any *Inverse Offset Type* constituent of *start type* or *end type*.
// This is necessary as an *Inverse Offset Type* can have a different outcome depending on the *object type*
// it is resolved against.
// - The *start type* and *end type* are distributed over each constituent of the *object type*.
// - The *object type* is distributed over each constituent of the *start type* and *end type*.
// - The results of the distribution are either an *Intersection Type* (if the *Range Type* was a "write"
// location), or a *Union Type* (if the *Range Type* was a "read" location).
type T1 = ([1, 2, 3] | [2, 3, 4])[0:2];
>T1 : [1, 2] | [2, 3]
type T2 = [1, 2, 3][0|1:2];
>T2 : [1, 2] | [2]
type T3 = [1, 2, 3][0:1|2];
>T3 : [1, 2] | [1]
type T4 = ([1, 2, 3] | [2, 3, 4])[0|1:2|3];
>T4 : [1, 2, 3] | [1, 2] | [2, 3] | [2, 3, 4] | [2] | [3, 4] | [3]
type T5 = ([1, 2, 3] | [9, 8])[0:^1];
>T5 : [1, 2] | [9]
}
{
// - Otherwise (or for each constituent of the distribution),
{
// - If neither the *start type* nor the *end type* are *String Literal Types* or a *Numeric Literal Types*, then
// - Return an *Array Type* for the union of each element of the *object type*: `T[any:any] -> T[number][]`.
type T1 = [1, 2, 3][number:number];
>T1 : (3 | 1 | 2)[]
type T2 = [1, 2, 3][string:string];
>T2 : (3 | 1 | 2)[]
type T3 = [1, 2, 3][any:any];
>T3 : (3 | 1 | 2)[]
type T4 = [1, 2, 3][never:never];
>T4 : (3 | 1 | 2)[]
}
{
// - If the *start type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *end type*, but with a *start type* of `0`: `T[any:Y] -> T[:Y][number][]`.
type T1 = [1, 2, 3][number:2];
>T1 : (1 | 2)[]
type T2 = [1, 2, 3][string:2];
>T2 : (1 | 2)[]
type T3 = [1, 2, 3][any:2];
>T3 : (1 | 2)[]
type T4 = [1, 2, 3][never:2];
>T4 : (1 | 2)[]
}
{
// - If the *end type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *start type*, but with an *end type* of `^0`: `T[X:any] -> T[X:][number][]`.
type T1 = [1, 2, 3][1:number];
>T1 : (3 | 2)[]
type T2 = [1, 2, 3][1:string];
>T2 : (3 | 2)[]
type T3 = [1, 2, 3][1:any];
>T3 : (3 | 2)[]
type T4 = [1, 2, 3][1:never];
>T4 : (3 | 2)[]
}
{
// - If the *start type* is the *lower bound type* (`0`) and the *end type* is the *upper bound type* (`^0`),
// then we are including all elements: return the *object type* of the *Range Type*.
type T1 = [1, 2, 3][0:^0];
>T1 : [1, 2, 3]
}
{
// - If the *start type* is the *upper bound type* (`^0`) or the *end type* is the *lower bound type* (`0`),
// then we are including no elements: return the empty *Tuple Type* (`[]`).
type T1 = [1, 2, 3][^0:^0];
>T1 : []
type T2 = [1, 2, 3][0:0];
>T2 : []
}
{
// - If the *object type* is an *Array Type*, then
{
// - If the signs of both the *start type* and *end type* agree, then return
// a fixed-length *Tuple Type* with a minimum length of `0` for the difference between the *end type* and
// the *start type* whose elements are the element type of the *object type*: `T[][0:1] -> [T?]`.
type T1 = number[][0:2];
>T1 : [number?, number?]
type T2 = number[][^2:^0];
>T2 : [number?, number?]
}
{
// - If the *start type* is an *Inverse Offset Type* and the *end type* is not, we can create a tuple of
// `min(abs(start), end)` optional elements.
type T1 = number[][^2:4];
>T1 : [number?, number?]
}
{
// - Otherwise, we cannot derive a fixed length: return the *object type* of the *Range Type*.
type T1 = number[][0:^2];
>T1 : number[]
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
// - If the *start type* is an *Inverse Offset Type*, return an *Array Type* for the union of each
// *optional type* and the *rest element type*, along with the set of `n` right-most required
// elements of the *object type* where `n` is the absolute value of the *start type*.
type T1 = [1, 2, 3, ...4[]][^1:];
>T1 : 4[]
type T2 = [1, 2, 3, ...4[]][^2:];
>T2 : (3 | 4)[]
type T3 = [1, 2, 3, ...4[]][^3:];
>T3 : (3 | 2 | 4)[]
type T4 = [1, 2, 3?, ...4[]][^1:];
>T4 : (3 | 4 | undefined)[]
type T5 = [1, 2, 3?, ...4[]][^2:];
>T5 : (3 | 2 | 4 | undefined)[]
type T6 = [1, 2, 3?, ...4[]][^3:];
>T6 : (3 | 2 | 4 | undefined)[]
}
{
// - If the *end type* is an *Inverse Offset Type*, return a *Tuple Type* of the elements of the
// *object type* starting from the index at *start type*, but whose minimum length is reduced by the
// absolute value of the *end type*.
type T1 = [1, 2, 3, ...4[]][1:^1];
>T1 : [2, 3?, ...4[]]
type T2 = [1, 2, 3, ...4[]][1:^2];
>T2 : [2?, 3?, ...4[]]
type T3 = [1, 2, 3, ...4[]][1:^3];
>T3 : [2?, 3?, ...4[]]
type T4 = [1, 2, 3?, ...4[]][1:^1];
>T4 : [2?, (3 | undefined)?, ...4[]]
type T5 = [1, 2, 3?, ...4[]][1:^2];
>T5 : [2?, (3 | undefined)?, ...4[]]
type T6 = [1, 2, 3?, ...4[]][1:^3];
>T6 : [2?, (3 | undefined)?, ...4[]]
}
}
{
// - Otherwise,
{
// - Clamp the *start type* and *end type* to values between `0` and the length of *object type*.
type T1 = [1, 2, 3][^5:10];
>T1 : [1, 2, 3]
}
{
// - Return a *Tuple Type* for the elements of *object type* starting at *start type* and ending at
// *end type*.
type T1 = [1, 2, 3][1: 2];
>T1 : [2]
}
}
}
}
=== tests/cases/conformance/types/range/assignability.ts ===
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1<T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) {
>f1 : <T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) => void
>t : T[U:^1]
>s : [3] & [2, 3]
t = s;
>t = s : [3] & [2, 3]
>t : T[U:^1]
>s : [3] & [2, 3]
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2<T, S extends [T, T, T]>(t: T[], s: S[0:2]) {
>f2 : <T, S extends [T, T, T]>(t: T[], s: S[0:2]) => void
>t : T[]
>s : S[0:2]
t = s;
>t = s : S[0:2]
>t : T[]
>s : S[0:2]
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3<T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) {
>f3 : <T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) => void
>t : T[1 | 2:3]
>s : S[1:3]
t = s;
>t = s : S[1:3]
>t : T[1 | 2:3]
>s : S[1:3]
}

View File

@ -0,0 +1,52 @@
// @strict: true
// Rules (from https://gist.github.com/rbuckton/53e335ce3d63686e229bc4ae25017756)
// @filename: parsing.ts
// Parsing Rules
type A = 0;
type B = 1;
type T0 = ^A;
type T1 = ^A | B;
type T2 = ^(A | B);
type T3 = keyof ^A;
type T4 = ^A[]; // error
type T5 = (^A)[];
// @filename: semantics.ts
// Semantic Rules
// - An *Inverse Offset Type*'s *index type* is constrained to `string | number`.
type T6 = ^0;
type T7 = ^"0";
type T8 = ^true; // error
// - The *Inverse Offset Type* of an *Inverse Offset Type* `I` is the *index type* of `I`: `^^I -> I`
type T9 = ^^0; // inverse rule (double negation = non-negated)
// - An *Inverse Offset Type* for negative *index type* is instead a *Literal Type* for the absolute value of the *index type*: `^-1 -> 1`
type T10 = ^-1; // inverse rule (double negation = non-negated)
// - If the *index type* of an *Inverse Offset Type* is a union, the *Inverse Offset Type* is distributed over the union: `^(A | B) -> ^A | ^B`
type T11 = ^(0 | 1); // distributes
// - An *Inverse Offset Type* is deferred until applied as the *index type* of an *Indexed Access Type* or *Range Type*.
type T12 = ^0;
// - An *Inverse Offset Type* is deferred if its *index type* is generic.
type T13<A extends string | number> = ^A;
type T14 = T13<1>;
type AR = [1, 2, 3];
type X = AR[^1]; // 3
// @filename: assignability.ts
// Assignability Rules
// - `^S` is assignable to `string | number`.
type Constrained0<T extends string | number> = never;
type Constrained1<T extends boolean | bigint | symbol | undefined | null | object> = never;
type T15 = Constrained0<^0>;
type T16 = Constrained1<^0>; // error
// - `^S` is assignable to `^T` if `S` is assignable to `T`.
function f<T extends string | number, S extends T>(s: ^S, t: ^T) {
t = s;
}

View File

@ -0,0 +1,189 @@
// @strict: true
// Rules (from https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c)
// @filename: semantics.ts
// Semantic Rules
{
// - The result of a *Range Type* has the same mutability as its *object type*.
type T1 = [1, 2, 3][0:2];
type T2 = (readonly [1, 2, 3])[0:2];
}
{
// - The *object type* of a *Range Type* is constrained to be an *Array Type* or a *Tuple Type*.
type T1 = [1, 2, 3][0:2];
type T2 = number[][0:2];
type T3 = {}[0:2]; // error
}
{
// - The *start type* and *end type* of a *Range Type* are constrained to `string | number`.
type T1 = [1, 2, 3][0:2];
type T2 = [1, 2, 3]["0":"2"];
type T3 = [1, 2, 3][true:false]; // error
}
{
// - The *start type* of a *Range Type* is optional. If not present, the *lower bound type* (`0`) is used.
type T2 = [1, 2, 3][:1];
// - The *end type* of a *Range Type* is optional. If not present, the *upper bound type* (`^0`) is used.
type T3 = [1, 2, 3][1:];
type T1 = [1, 2, 3][:];
}
{
// - If the *start type* or *end type* of a *Range Type* are negative-valued *Numeric Literal Types* (or numeric index-valued *String Literal Types*), they are treated
// as an *Inverse Offset Type* for the absolute value of the numeric index value of the respective type.
// - NOTE: This does not work for `-0` as JavaScript generally treats `-0` and `0` as the same value except for a few small corner cases and we must
// align with this behavior.
type T1 = [1, 2, 3, 4][1:-1];
type T2 = [1, 2, 3, 4][1:-0];
}
{
// - If the *object type* is a "generic object type", or if either of the *start type* or *end type* are
// "generic index types", then the operation is deferred until it they can be instantiated.
type T1<A extends any[]> = A[0:2];
type T2 = T1<[1, 2, 3]>;
type T3 = T1<number[]>;
type T4<X extends number> = [1, 2, 3][X:];
type T5 = T4<1>;
type T6<Y extends number> = [1, 2, 3][:Y];
type T7 = T6<^1>;
}
{
// - If either the *object type*, *start type*, or *end type* are *Union Types*, the result is distributed over
// each constituent in the following manner:
// - The *object type* is distributed over any *Inverse Offset Type* constituent of *start type* or *end type*.
// This is necessary as an *Inverse Offset Type* can have a different outcome depending on the *object type*
// it is resolved against.
// - The *start type* and *end type* are distributed over each constituent of the *object type*.
// - The *object type* is distributed over each constituent of the *start type* and *end type*.
// - The results of the distribution are either an *Intersection Type* (if the *Range Type* was a "write"
// location), or a *Union Type* (if the *Range Type* was a "read" location).
type T1 = ([1, 2, 3] | [2, 3, 4])[0:2];
type T2 = [1, 2, 3][0|1:2];
type T3 = [1, 2, 3][0:1|2];
type T4 = ([1, 2, 3] | [2, 3, 4])[0|1:2|3];
type T5 = ([1, 2, 3] | [9, 8])[0:^1];
}
{
// - Otherwise (or for each constituent of the distribution),
{
// - If neither the *start type* nor the *end type* are *String Literal Types* or a *Numeric Literal Types*, then
// - Return an *Array Type* for the union of each element of the *object type*: `T[any:any] -> T[number][]`.
type T1 = [1, 2, 3][number:number];
type T2 = [1, 2, 3][string:string];
type T3 = [1, 2, 3][any:any];
type T4 = [1, 2, 3][never:never];
}
{
// - If the *start type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *end type*, but with a *start type* of `0`: `T[any:Y] -> T[:Y][number][]`.
type T1 = [1, 2, 3][number:2];
type T2 = [1, 2, 3][string:2];
type T3 = [1, 2, 3][any:2];
type T4 = [1, 2, 3][never:2];
}
{
// - If the *end type* is neither a *String Literal Type* nor a *Numeric Literal Type*, then
// - Return an *Array Type* for the union of each element of the *Range Type* for the same *object type* and
// *start type*, but with an *end type* of `^0`: `T[X:any] -> T[X:][number][]`.
type T1 = [1, 2, 3][1:number];
type T2 = [1, 2, 3][1:string];
type T3 = [1, 2, 3][1:any];
type T4 = [1, 2, 3][1:never];
}
{
// - If the *start type* is the *lower bound type* (`0`) and the *end type* is the *upper bound type* (`^0`),
// then we are including all elements: return the *object type* of the *Range Type*.
type T1 = [1, 2, 3][0:^0];
}
{
// - If the *start type* is the *upper bound type* (`^0`) or the *end type* is the *lower bound type* (`0`),
// then we are including no elements: return the empty *Tuple Type* (`[]`).
type T1 = [1, 2, 3][^0:^0];
type T2 = [1, 2, 3][0:0];
}
{
// - If the *object type* is an *Array Type*, then
{
// - If the signs of both the *start type* and *end type* agree, then return
// a fixed-length *Tuple Type* with a minimum length of `0` for the difference between the *end type* and
// the *start type* whose elements are the element type of the *object type*: `T[][0:1] -> [T?]`.
type T1 = number[][0:2];
type T2 = number[][^2:^0];
}
{
// - If the *start type* is an *Inverse Offset Type* and the *end type* is not, we can create a tuple of
// `min(abs(start), end)` optional elements.
type T1 = number[][^2:4];
}
{
// - Otherwise, we cannot derive a fixed length: return the *object type* of the *Range Type*.
type T1 = number[][0:^2];
}
}
{
// - Otherwise, the *object type* is a *Tuple Type*:
{
// - If the *object type* has a rest element, then
{
// - If the *start type* is an *Inverse Offset Type*, return an *Array Type* for the union of each
// *optional type* and the *rest element type*, along with the set of `n` right-most required
// elements of the *object type* where `n` is the absolute value of the *start type*.
type T1 = [1, 2, 3, ...4[]][^1:];
type T2 = [1, 2, 3, ...4[]][^2:];
type T3 = [1, 2, 3, ...4[]][^3:];
type T4 = [1, 2, 3?, ...4[]][^1:];
type T5 = [1, 2, 3?, ...4[]][^2:];
type T6 = [1, 2, 3?, ...4[]][^3:];
}
{
// - If the *end type* is an *Inverse Offset Type*, return a *Tuple Type* of the elements of the
// *object type* starting from the index at *start type*, but whose minimum length is reduced by the
// absolute value of the *end type*.
type T1 = [1, 2, 3, ...4[]][1:^1];
type T2 = [1, 2, 3, ...4[]][1:^2];
type T3 = [1, 2, 3, ...4[]][1:^3];
type T4 = [1, 2, 3?, ...4[]][1:^1];
type T5 = [1, 2, 3?, ...4[]][1:^2];
type T6 = [1, 2, 3?, ...4[]][1:^3];
}
}
{
// - Otherwise,
{
// - Clamp the *start type* and *end type* to values between `0` and the length of *object type*.
type T1 = [1, 2, 3][^5:10];
}
{
// - Return a *Tuple Type* for the elements of *object type* starting at *start type* and ending at
// *end type*.
type T1 = [1, 2, 3][1: 2];
}
}
}
}
// @filename: assignability.ts
// - `S` is assignable to `T[X:Y]` if `S` is assignable to `C`, where `C` is the base constraint of `T[X:Y]` for writing.
function f1<T extends [1, 2, 3, 4], U extends 1 | 2>(t: T[U:^1], s: [3] & [2, 3]) {
t = s;
}
// - `S[X:Y]` is assignable to `T[]` if `S[number]` is assignable to `T`.
function f2<T, S extends [T, T, T]>(t: T[], s: S[0:2]) {
t = s;
}
// - `S[XS:YS]` is assignable to `T[XT:YT]` if `S` is assignable to `T`, `XS` is assignable to `XT`, and `YS` is assignable to `YT`.
function f3<T extends [1 | 9, 2 | 8, 3 | 7, 4 | 6], S extends T>(t: T[1 | 2:3], s: S[1:3]) {
t = s;
}