This proposal adds an Inverse Offset Type to the TypeScript type-space. An Inverse Offset Type is a type notation used to indicate that Type should be considered as an offset from the end of an Array Type or Tuple Type.
An Inverse Offset Type is denoted using a "hat" operator (^
) preceding a type:
type T = ^0;
NOTE: The Inverse Offset Type is a type-space only addition and has no impact on value-space syntax as it cannot be used in a JavaScript expression.
- Support for "negative" indexes in Indexed Access Types against Array Types and Tuple Types, since
-1
already has a valid meaning in an Indexed Access Type. - A mechanism for representing the exclusive upper bound of an Array Type or Tuple Type (
^0
), since the numeric literal type-0
is equivalent to0
. - A mechanism for negating a generic type.
The ^
operator is parsed at the same precedence level as TypeOperator and InferType:
// NOTE: parens below indicate precedence
^A -> (^(A))
^A | B -> (^(A)) | (B)
keyof ^A -> (keyof (^(A)))
^A[] -> error: A[] does not satisfy the constraint ...
(^A)[] -> ((^(A))[])
- An Inverse Offset Type is deferred until applied as the index type of an Indexed Access Type or Range Type.
- An Inverse Offset Type is deferred if its index type is generic.
- An Inverse Offset Type is considered a "generic index type" if its index type is a "generic index type".
- An Inverse Offset Type's index type is constrained to
string | number
.- We allow
string
for the purpose of coercing numeric index strings to their number equivalent for use with mapped types andkeyof
(i.e.,"1"
is equiavalent to1
).
- We allow
- The Inverse Offset Type of an Inverse Offset Type
I
is the index type ofI
:^^I -> I
- An Inverse Offset Type for negative index type is instead a Literal Type for the absolute value of the index type:
^-1 -> 1
- 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
- When applied as the index type of an Indexed Access Type or the start type or end type of a Range Type:
- If the object type of the Indexed Access Type or Range Type is a Union Type, the Inverse Offset Type is distributed over the object type:
This is necessary as an Inverse Offset Type can have a different outcome depending on the object type it is resolved against.(T | U)[^N] -> T[^N] | U[^N] (reading) (T | U)[^N] -> T[^N] & U[^N] (writing)
- If the outer object type of a containing Indexed Access Type is an Array Type, the Inverse Offset Type is essentially
number
:[1, 2][^N] -> (1 | 2)
- If the outer object type of a containing Indexed Access Type is a Tuple Type with optional elements or a rest argument, the object type is distributed over
the indices representing the rest element type, all optional types, and
n
fixed element types of the tuple starting from the right-most fixed element wheren
is the numerical index value of index type of the Inverse Offset Type:[1, 2, 3?, ...4[]][^1] -> [1, 2][^1] | [1, 2, 3 | undefined][^1] | [1, 2, 3 | undefined, 4][^1] -> 2 | (3 | undefined) | 4 -> 2 | 3 | 4 | undefined
- The index type is bounded by the upper and lower bounds of the Indexed Access Type or Range Type (i.e., for
T[^N]
,N
is clamped to the range0 <= N <= T["length"]
).- If the outer object type is an Array Type, or is a Tuple Type with a rest argument, there is no upper bound.
- If the object type of the Indexed Access Type or Range Type is a Union Type, the Inverse Offset Type is distributed over the object type:
^S
is assignable tostring | number
.^S
is assignable to^T
ifS
is assignable toT
.
// Indexed Access Types
type A = [1, 2, 3];
type B = A[^0]; // undefined
type C = A[^1]; // 3
// Range Types
type A = [1, 2, 3];
type B = A[0:^0]; // [1, 2, 3]
type C = A[0:^1]; // [1, 2]
type D = A[^1:^0]; // [3]
- C# 8.0: Index types