Skip to content

Commit

Permalink
Merge pull request #1 from DeepDoge/sheraff/array-at
Browse files Browse the repository at this point in the history
[ReadonlyArray] `.at()` fix `undefined` issue
  • Loading branch information
Sheraff committed Aug 22, 2023
2 parents 1f0e77b + 064d896 commit 11f16cc
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 39 deletions.
21 changes: 15 additions & 6 deletions src/entrypoints/array-at.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
/// <reference path="utils.d.ts" />

interface ReadonlyArray<T> {
at<I extends number> (index: I): `${I}` extends `-${infer J extends number}`
? `${TSReset.Subtract<this["length"], J>}` extends `-${number}`
? undefined
: this[TSReset.Subtract<this["length"], J>]
: this[I]
}
at<
const N extends number,
I extends number = `${N}` extends `${infer J extends number}.${number}`
? J
: N,
>(
index: N,
): TSReset.Equal<I, number> extends true
? T | undefined
: `${I}` extends `-${infer J extends number}`
? `${TSReset.Subtract<this["length"], J>}` extends `-${number}`
? undefined
: this[TSReset.Subtract<this["length"], J>]
: this[I];
}
30 changes: 20 additions & 10 deletions src/entrypoints/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@ declare namespace TSReset {
? symbol
: T;

type Length<T extends any[]> = T extends { length: infer L }
? L
: never

type BuildTuple<L extends number, T extends any[] = []> = T extends { length: L }
type BuildTuple<L extends number, T extends any[] = []> = T extends {
length: L;
}
? T
: BuildTuple<L, [...T, any]>

type Subtract<A extends number, B extends number> = BuildTuple<A> extends [...(infer U), ...BuildTuple<B>]
? Length<U>
: never
: BuildTuple<L, [...T, unknown]>;

// Extra `A extends number` and `B extends number` needed for union types to work Such as Subtract<10 | 20, 1>
type Subtract<A extends number, B extends number> = A extends number
? B extends number
? BuildTuple<A> extends [...infer U, ...BuildTuple<B>]
? U["length"]
: never
: never
: never;

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type NotEqual<X, Y> = true extends Equal<X, Y> ? false : true;
}
64 changes: 41 additions & 23 deletions src/tests/array-at.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
import { doNotExecute, Equal, Expect } from "./utils";

doNotExecute(async () => {
const arr = [false, 1, '2'] as const;
const arr = [false, 1, "2"] as const;

const a = arr.at(0)
const b = arr.at(1)
const c = arr.at(2)
const d = arr.at(3)
const a = arr.at(0);
const b = arr.at(1);
const c = arr.at(2);
const d = arr.at(3);
const e = arr.at(1.5);
type tests = [
Expect<Equal<typeof a, false>>,
Expect<Equal<typeof b, 1>>,
Expect<Equal<typeof c, '2'>>,
Expect<Equal<typeof c, "2">>,
Expect<Equal<typeof d, undefined>>,
]
Expect<Equal<typeof e, 1>>,
];
});

doNotExecute(async () => {
const arr = [false, 1, '2'] as const;
const arr = [false, 1, "2"] as const;

const a = arr.at(-1)
const b = arr.at(-2)
const c = arr.at(-3)
const d = arr.at(-4)
const a = arr.at(-1);
const b = arr.at(-2);
const c = arr.at(-3);
const d = arr.at(-4);
const e = arr.at(-1.5);
type tests = [
Expect<Equal<typeof a, '2'>>,
Expect<Equal<typeof a, "2">>,
Expect<Equal<typeof b, 1>>,
Expect<Equal<typeof c, false>>,
Expect<Equal<typeof d, undefined>>,
]
Expect<Equal<typeof e, "2">>,
];
});

doNotExecute(async () => {
const arr = [false, 1, '2'] as const;
const index: number = 1
const a = arr.at(index)
// WARN: with `"strictNullChecks": true,` the correct type here should include `undefined`
// but the current implementation does not type this as including `undefined`
type tests = [
Expect<Equal<typeof a, false | 1 | "2">>,
]
});
const arr = [false, 1, "2"] as const;

const index = 0 as 0 | 1

const a = arr.at(index);
type tests = [Expect<Equal<typeof a, false | 1>>];
});

doNotExecute(async () => {
const arr = [false, true, 1, "2"] as const;

const index = -1 as -1 | -2

const a = arr.at(index);
type tests = [Expect<Equal<typeof a, "2" | 1>>];
});

doNotExecute(async () => {
const arr = [false, 1, "2"] as const;
const index = 1 as number;
const a = arr.at(index);
type tests = [Expect<Equal<typeof a, false | 1 | "2" | undefined>>];
});

0 comments on commit 11f16cc

Please sign in to comment.