@@ -41,6 +41,49 @@ const {
4141/** Detached {@linkcode Array.isArray}; see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray MDN Reference}. */
4242const { isArray } = Array ;
4343
44+ /** The most extensible array type. */
45+ type BaseArray = readonly unknown [ ] ;
46+ /** Names of methods that can be used on a readonly array. */
47+ type ArrayPureMethodNames = {
48+ [ K in keyof BaseArray ] : K extends string
49+ ? BaseArray [ K ] extends ( ...args : any ) => any
50+ ? K
51+ : never
52+ : never ;
53+ } [ keyof BaseArray ] ;
54+ /**
55+ * Unbound array methods, re-typed so that `.call` and `.apply` correctly report type errors.
56+ * @example
57+ * const arr = ['a', 'b', 'c']
58+ * const trim = (str: string) => str.trim()
59+ * const sq = (num: number) => num ** 2
60+ * const unboundForEach = arr.forEach
61+ * unboundForEach.call(arr, trim) // passes - good
62+ * unboundForEach.call(arr, sq) // passes - BAD!
63+ * const fixedForEach = arr.forEach as UnboundArrayPureMethods['forEach']
64+ * fixedForEach.call(arr, trim) // passes - good
65+ * fixedForEach.call(arr, sq) // error - yay!
66+ */
67+ type UnboundArrayPureMethods = {
68+ [ K in ArrayPureMethodNames ] : {
69+ call : < T extends BaseArray > ( thisArg : T , ...args : Parameters < T [ K ] > ) => ReturnType < T [ K ] > ;
70+ apply : < T extends BaseArray > ( thisArg : T , args : Parameters < T [ K ] > ) => ReturnType < T [ K ] > ;
71+ } ;
72+ } ;
73+
74+ /** Names of methods that mutate an array (cannot be used on a readonly array). */
75+ type ArrayMutationMethodNames = Exclude < keyof unknown [ ] , keyof BaseArray > ;
76+ /**
77+ * Unbound array mutation methods, re-typed so that `.call` and `.apply` correctly report type errors.
78+ * @see {@link UnboundArrayPureMethods } for an example showing why this is needed.
79+ */
80+ type UnboundArrayMutationMethods = {
81+ [ K in ArrayMutationMethodNames ] : {
82+ call : < T extends unknown [ ] > ( thisArg : T , ...args : Parameters < T [ K ] > ) => ReturnType < T [ K ] > ;
83+ apply : < T extends unknown [ ] > ( thisArg : T , args : Parameters < T [ K ] > ) => ReturnType < T [ K ] > ;
84+ } ;
85+ } ;
86+
4487// For some reason, JSDoc don't get picked up for multiple renamed destructured constants (even
4588// though it works fine for one, e.g. isArray), so comments for these are added to the export
4689// statement, rather than this declaration.
@@ -67,7 +110,7 @@ const {
67110 splice : ArraySplice ,
68111 unshift : ArrayUnshift ,
69112 forEach, // Weird anomaly!
70- } = Array . prototype ;
113+ } : UnboundArrayPureMethods & UnboundArrayMutationMethods = Array . prototype ;
71114
72115// The type of the return value of Array.prototype.every is `this is T[]`. However, once this
73116// Array method is pulled out of the prototype, the function is now referencing `this` where
@@ -82,10 +125,10 @@ const {
82125 * @param predicate A function to execute for each element of the array.
83126 * @returns Whether all elements in the array pass the test provided by the predicate.
84127 */
85- function arrayEvery < T > (
86- arr : unknown [ ] ,
87- predicate : ( value : any , index : number , array : typeof arr ) => value is T
88- ) : arr is T [ ] {
128+ function arrayEvery < S extends T , T = unknown > (
129+ arr : readonly T [ ] ,
130+ predicate : ( value : any , index : number , array : readonly T [ ] ) => value is S
131+ ) : arr is readonly S [ ] {
89132 return ArrayEvery . call ( arr , predicate ) ;
90133}
91134
0 commit comments