Skip to content

Commit e198e43

Browse files
committed
👍 Error when as const is missing on isTupleOf
Now type error is shown when `as const` is required and missing on `isTupleOf` and `isReadonlyTupleOf`. Note that it seems `predTup` that is directly assigned to `isTupleOf` or `isReadonlyTupleOf` are automatically become `readonly` in recent TypeScript, meaning that `as const` is not required in that case. Additionally, empty `predTup` is no longer valid (type error will be shown) for `isTupleOf` and `isReadonlyTupleOf`.
1 parent d24f029 commit e198e43

File tree

2 files changed

+89
-123
lines changed

2 files changed

+89
-123
lines changed

is.ts

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export function isArrayOf<T>(
189189
* // type A = [string, number];
190190
* ```
191191
*/
192-
export type TupleOf<T extends readonly Predicate<unknown>[]> = {
192+
export type TupleOf<T> = {
193193
-readonly [P in keyof T]: T[P] extends Predicate<infer U> ? U : never;
194194
};
195195

@@ -204,37 +204,33 @@ export type TupleOf<T extends readonly Predicate<unknown>[]> = {
204204
* // type A = readonly [string, number];
205205
* ```
206206
*/
207-
export type ReadonlyTupleOf<T extends readonly Predicate<unknown>[]> = {
207+
export type ReadonlyTupleOf<T> = {
208208
[P in keyof T]: T[P] extends Predicate<infer U> ? U : never;
209209
};
210210

211211
/**
212-
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
212+
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>` or `TupleOf<T, E>`.
213213
*
214214
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
215215
*
216216
* ```ts
217217
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
218218
*
219-
* const isMyType = is.TupleOf([is.Number, is.String, is.Boolean] as const);
219+
* const isMyType = is.TupleOf([is.Number, is.String, is.Boolean]);
220220
* const a: unknown = [0, "a", true];
221221
* if (isMyType(a)) {
222222
* // a is narrowed to [number, string, boolean]
223223
* const _: [number, string, boolean] = a;
224224
* }
225225
* ```
226226
*
227-
* Note that `predTup` must be `readonly` (`as const`) to infer the type of `a` correctly.
228-
* TypeScript won't argues if `predTup` is not `readonly` because of its design limitation.
229-
* https://github.com/microsoft/TypeScript/issues/34274#issuecomment-541691353
230-
*
231-
* It can also be used to check the type of the rest of the tuple like:
227+
* With `predElse`:
232228
*
233229
* ```ts
234230
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
235231
*
236232
* const isMyType = is.TupleOf(
237-
* [is.Number, is.String, is.Boolean] as const,
233+
* [is.Number, is.String, is.Boolean],
238234
* is.ArrayOf(is.Number),
239235
* );
240236
* const a: unknown = [0, "a", true, 0, 1, 2];
@@ -243,33 +239,44 @@ export type ReadonlyTupleOf<T extends readonly Predicate<unknown>[]> = {
243239
* const _: [number, string, boolean, ...number[]] = a;
244240
* }
245241
* ```
242+
*
243+
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array
244+
* used as `predTup`. If a type error occurs, try adding `as const` as follows:
245+
*
246+
* ```ts
247+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
248+
*
249+
* const predTup = [is.Number, is.String, is.Boolean] as const;
250+
* const isMyType = is.TupleOf(predTup);
251+
* const a: unknown = [0, "a", true];
252+
* if (isMyType(a)) {
253+
* // a is narrowed to [number, string, boolean]
254+
* const _: [number, string, boolean] = a;
255+
* }
256+
* ```
246257
*/
247258
export function isTupleOf<
248-
T extends readonly Predicate<unknown>[],
249-
R extends TupleOf<T>,
259+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
250260
>(
251261
predTup: T,
252-
): Predicate<R>;
262+
): Predicate<TupleOf<T>>;
253263
export function isTupleOf<
254-
T extends readonly Predicate<unknown>[],
264+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
255265
E extends Predicate<unknown[]>,
256-
R extends [...TupleOf<T>, ...PredicateType<E>],
257266
>(
258267
predTup: T,
259268
predElse: E,
260-
): Predicate<R>;
269+
): Predicate<[...TupleOf<T>, ...PredicateType<E>]>;
261270
export function isTupleOf<
262-
T extends readonly Predicate<unknown>[],
271+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
263272
E extends Predicate<unknown[]>,
264-
R1 extends TupleOf<T>,
265-
R2 extends [...TupleOf<T>, ...PredicateType<E>],
266273
>(
267274
predTup: T,
268275
predElse?: E,
269-
): Predicate<R1 | R2> {
276+
): Predicate<TupleOf<T> | [...TupleOf<T>, ...PredicateType<E>]> {
270277
if (!predElse) {
271278
return Object.defineProperties(
272-
(x: unknown): x is R1 => {
279+
(x: unknown): x is TupleOf<T> => {
273280
if (!isArray(x) || x.length !== predTup.length) {
274281
return false;
275282
}
@@ -283,7 +290,7 @@ export function isTupleOf<
283290
);
284291
} else {
285292
return Object.defineProperties(
286-
(x: unknown): x is R2 => {
293+
(x: unknown): x is [...TupleOf<T>, ...PredicateType<E>] => {
287294
if (!isArray(x) || x.length < predTup.length) {
288295
return false;
289296
}
@@ -308,59 +315,69 @@ export function isTupleOf<
308315
* ```ts
309316
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
310317
*
311-
* const isMyType = is.ReadonlyTupleOf([is.Number, is.String, is.Boolean] as const);
318+
* const isMyType = is.ReadonlyTupleOf([is.Number, is.String, is.Boolean]);
312319
* const a: unknown = [0, "a", true];
313320
* if (isMyType(a)) {
314321
* // a is narrowed to readonly [number, string, boolean]
315322
* const _: readonly [number, string, boolean] = a;
316323
* }
317324
* ```
318325
*
319-
* Note that `predTup` must be `readonly` (`as const`) to infer the type of `a` correctly.
320-
* TypeScript won't argues if `predTup` is not `readonly` because of its design limitation.
321-
* https://github.com/microsoft/TypeScript/issues/34274#issuecomment-541691353
322-
*
323-
* It can also be used to check the type of the rest of the tuple like:
326+
* With `predElse`:
324327
*
325328
* ```ts
326329
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
327330
*
328331
* const isMyType = is.ReadonlyTupleOf(
329-
* [is.Number, is.String, is.Boolean] as const,
332+
* [is.Number, is.String, is.Boolean],
330333
* is.ArrayOf(is.Number),
331334
* );
332335
* const a: unknown = [0, "a", true, 0, 1, 2];
333336
* if (isMyType(a)) {
334337
* // a is narrowed to readonly [number, string, boolean, ...number[]]
335338
* const _: readonly [number, string, boolean, ...number[]] = a;
336339
* }
340+
* ```
341+
*
342+
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array
343+
* used as `predTup`. If a type error occurs, try adding `as const` as follows:
344+
*
345+
* ```ts
346+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
347+
*
348+
* const predTup = [is.Number, is.String, is.Boolean] as const;
349+
* const isMyType = is.ReadonlyTupleOf(predTup);
350+
* const a: unknown = [0, "a", true];
351+
* if (isMyType(a)) {
352+
* // a is narrowed to readonly [number, string, boolean]
353+
* const _: readonly [number, string, boolean] = a;
354+
* }
355+
* ```
337356
*/
338357
export function isReadonlyTupleOf<
339-
T extends readonly Predicate<unknown>[],
340-
R extends ReadonlyTupleOf<T>,
358+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
341359
>(
342360
predTup: T,
343-
): Predicate<R>;
361+
): Predicate<ReadonlyTupleOf<T>>;
344362
export function isReadonlyTupleOf<
345-
T extends readonly Predicate<unknown>[],
363+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
346364
E extends Predicate<unknown[]>,
347-
R extends readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>],
348365
>(
349366
predTup: T,
350367
predElse: E,
351-
): Predicate<R>;
368+
): Predicate<readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>]>;
352369
export function isReadonlyTupleOf<
353-
T extends readonly Predicate<unknown>[],
370+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
354371
E extends Predicate<unknown[]>,
355-
R1 extends ReadonlyTupleOf<T>,
356-
R2 extends readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>],
357372
>(
358373
predTup: T,
359374
predElse?: E,
360-
): Predicate<R1 | R2> {
375+
): Predicate<
376+
ReadonlyTupleOf<T> | readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>]
377+
> {
361378
if (!predElse) {
362379
return Object.defineProperties(
363-
isTupleOf(predTup) as Predicate<R1>,
380+
isTupleOf(predTup) as Predicate<ReadonlyTupleOf<T>>,
364381
{
365382
name: {
366383
get: () => `isReadonlyTupleOf(${inspect(predTup)})`,
@@ -369,7 +386,9 @@ export function isReadonlyTupleOf<
369386
);
370387
} else {
371388
return Object.defineProperties(
372-
isTupleOf(predTup, predElse) as unknown as Predicate<R2>,
389+
isTupleOf(predTup, predElse) as unknown as Predicate<
390+
readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>]
391+
>,
373392
{
374393
name: {
375394
get: () =>
@@ -449,9 +468,13 @@ export function isUniformTupleOf<T, N extends number>(
449468
n: N,
450469
pred: Predicate<T> = isAny,
451470
): Predicate<UniformTupleOf<T, N>> {
452-
const predInner = isTupleOf(Array(n).fill(pred));
453471
return Object.defineProperties(
454-
(x: unknown): x is UniformTupleOf<T, N> => predInner(x),
472+
(x: unknown): x is UniformTupleOf<T, N> => {
473+
if (!isArray(x) || x.length !== n) {
474+
return false;
475+
}
476+
return x.every((v) => pred(v));
477+
},
455478
{
456479
name: {
457480
get: () => `isUniformTupleOf(${n}, ${inspect(pred)})`,

0 commit comments

Comments
 (0)