Original code that does not accept readonly
array:
public transform<T>(value: T[], options?: (item: T) => boolean): T[] {
if (!Array.isArray(value) || value.length <= 1 || options == null) {
return value;
}
return value.filter(options);
}
First iteration:
public transform<
T extends unknown[] | readonly unknown[],
E = T extends Array<infer U> ? U : T extends ReadonlyArray<infer U> ? U : never
>(value: T, options?: (item: E) => boolean): T {
if (!Array.isArray(value) || value.length <= 1 || options == null) {
return value;
}
return value.filter(options) as T;
}
Has cast to T
, incorrect return type.
Second iteration:
public transform<T extends readonly unknown[]>(
value: T,
options?: (item: T[number]) => boolean
): T extends unknown[] ? T[number][] : readonly T[number][] {
if (!Array.isArray(value) || value.length <= 1 || options == null) {
return value as any;
}
return value.filter(options) as any;
}
Has cast to any
.
Without using a cast, I believe the simplest solution would be to use an overload.
// mutable interface where a mutable array stays mutable
function transform<T>(value: T[], options?: (item: T) => boolean): T[];
// specific readonly interface where a readonly array stays readonly
function transform<T>(
value: readonly T[], options?: (item: T) => boolean
): readonly T[];
// implementation has to explicitly accept a readonly array or you'll get errors
function transform<T>(
value: T[] | readonly T[],
options?: (item: T) => boolean
): T[] | readonly T[] {
if (!Array.isArray(value) || value.length <= 1 || !options) {
return value;
}
return value.filter(options);
}
NB. You MUST provide the mutable overload, and it MUST come before the readonly
overload. Otherwise, the compiler will happily choose the readonly
overload for mutable arrays. That is, mutable arrays can do everything a readonly array can do. For instance, this is what would happen if the readonly
implementation came first:
let items: number[] = [1, 2, 3];
items[0] = 0; // works
let transformed = transform(items);
transformed[0] = 0;
// ^-- Index signature in type 'readonly number[]' only permits reading.(2542)