Reputation: 28770
I have created this codesanbox
I have 2 functions called slice
in 2 classes. Both have the same set of horrible overrieds:
export class Atom<S> {
constructor(private initial: S) {
}
slice<Key extends keyof S>(key: Key): Slice<S[Key], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1]>(key1: Key1, key2: Key2): Slice<S[Key1][Key2], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2]>(key1: Key1, key2: Key2, key3: Key3): Slice<S[Key1][Key2][Key3], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4): Slice<S[Key1][Key2][Key3][Key4], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): Slice<S[Key1][Key2][Key3][Key4][Key5], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6][Key7], S>;
slice<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6], Key8 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6][Key7]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6][Key7][Key8], S>;
slice(...keys: string[]): any{
return new Slice(this, keys);
}
}
If I ever have to change these horrible overrides, then I will need to do it in 2 places.
Is there any way I can reuse the same function in two different places?
I've tried creating a generic slice
function
export function slice<A, S, Key extends keyof A>(atom: A, key: Key): Slice<A[Key], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1]>(atom: A, key1: Key1, key2: Key2): Slice<A[Key1][Key2], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2]>(atom: A, key1: Key1, key2: Key2, key3: Key3): Slice<A[Key1][Key2][Key3], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2], Key4 extends keyof A[Key1][Key2][Key3]>(atom: A, key1: Key1, key2: Key2, key3: Key3, key4: Key4): Slice<A[Key1][Key2][Key3][Key4], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2], Key4 extends keyof A[Key1][Key2][Key3], Key5 extends keyof A[Key1][Key2][Key3][Key4]>(atom: A, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): Slice<A[Key1][Key2][Key3][Key4][Key5], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2], Key4 extends keyof A[Key1][Key2][Key3], Key5 extends keyof A[Key1][Key2][Key3][Key4], Key6 extends keyof A[Key1][Key2][Key3][Key4][Key5]>(atom: A, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key: Key6): Slice<A[Key1][Key2][Key3][Key4][Key5][Key6], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2], Key4 extends keyof A[Key1][Key2][Key3], Key5 extends keyof A[Key1][Key2][Key3][Key4], Key6 extends keyof A[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof A[Key1][Key2][Key3][Key4][Key5][Key6]>(atom: A, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key: Key6, key7: Key7): Slice<A[Key1][Key2][Key3][Key4][Key5][Key6][Key7], S>;
export function slice<A, S, Key1 extends keyof A, Key2 extends keyof A[Key1], Key3 extends keyof A[Key1][Key2], Key4 extends keyof A[Key1][Key2][Key3], Key5 extends keyof A[Key1][Key2][Key3][Key4], Key6 extends keyof A[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof A[Key1][Key2][Key3][Key4][Key5][Key6], Key8 extends keyof A[Key1][Key2][Key3][Key4][Key5][Key6][Key7]>(atom: A, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key: Key6, key7: Key7, key8: Key8): Slice<A[Key1][Key2][Key3][Key4][Key5][Key6][Key7][Key8], S>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function slice<A, S>(atom: Atom<A>, ...keys: Key[]): Slice<S, A> {
return new Slice(atom, keys);
}
and then calling it like this:
slice<T>(...keys: Key[]): Slice<T, S> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return slice(this, keys as any);
}
But the typing gets lost without the overloads on each function.
Upvotes: 1
Views: 236
Reputation: 21453
At one point I thought I saw someone post a comment about using typescript 4.0 beta as it has variadic tuples, not sure what happened to that comment but that is also a valid option: (playground link)
I took a while crafting this code sample, the NestedIndexList
type has a number of quirks due to how it is used as a circular constraint in order to get nice meaningful error messages when ever possible.
/**
* new single signature for slice function, no overrides needed.
*/
declare function slice<T, K extends [keyof T, ...NestedIndex<T,K>]>(obj: T, ...keys: K): ResolveKeys<T,K>
/**
* resolves the type of an object from a list of nesting keys.
*/
type ResolveKeys<T, K extends unknown[]> = K extends [] ? T
:K extends [infer A, ...infer Rest]
? {[kk in A & keyof T]: ResolveKeys<T[kk], Rest>}[A & keyof T]
: unknown;
/**
* returns extension on valid keys list
* this is set to be specifically recursive to mimic the behaviour of something like this:
| [keyof T, keyof T[K[0]]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][NonNullable<K[1]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][NonNullable<K[1]>], keyof T[K[0]][NonNullable<K[1]>][NonNullable<K[2]>]]
* this is obviously not maintainable when hardcoded in that way, so this intends to read the length of arguments given and
* return a suitable list of valid arguments.
*/
type NestedIndex<T, K extends [keyof T, ...unknown[]]> =
// if there are no common keys given the current info then don't have any valid keys.
keyof T[K[0]] extends never ? []
// if we end up with a total wild card then stop the list, this can happen if using a union variable and we lose all safety for fields after it if we don't check here.
: (keyof any) extends keyof T[K[0]] ? []
// otherwise see if we have at least one extra element in our list
: K extends [K[0], infer A, ...infer R]
? [A] extends [keyof T[K[0]]]
// if A is valid then we can keep going recursively, using a bit of object literal trickery
// so typescript doesn't complain we are circularly defined.
? {[k in A]: [keyof T[K[0]], ...NestedIndex<T[K[0]], [A, ...R]>]}[A]
// if an element of the key list isn't a valid key then we use never[] to short circuit a bunch of irrelevent possibilities from being generated in invalid cases.
: [keyof T[K[0]], ...never[]]
// if we don't have an extra element then we could choose to extend the list by 1 or not.
// since this is in the constraint I don't think this is noticable ever, but still in theory it's the right way to put it.
: [] | [keyof T[K[0]]]
// ######################### FOR TESTING ###########################
interface Data {
a: Data;
b: {
foo: {
c: string
d: string
},
bar:{
c: number
}
}
}
declare const x: Data;
declare const field: "bar" | "foo"
const a = slice(x, "a", "a", "b", field, "d", "")
The key here is that the Keys
list generic is constrained to a type defined in terms of earlier elements in the list. Variadic tuples makes this a little nicer but it is also possible for a working implementation although the equivalent definition of NestedIndex
isn't nearly as pretty (playground link)
type _N<T> = NonNullable<T>
type KeyArr<T,K extends [keyof T]
| [keyof T, keyof T[K[0]]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>][_N<K[7]>]]
> = [keyof T]
| [keyof T, keyof T[K[0]]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>]]
| [keyof T, keyof T[K[0]], keyof T[K[0]][_N<K[1]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>],
keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>], keyof T[K[0]][_N<K[1]>][_N<K[2]>][_N<K[3]>][_N<K[4]>][_N<K[5]>][_N<K[6]>][_N<K[7]>]]
interface _ArrSizes {
1: [any]
2: [any,any]
3: [any,any,any]
4: [any,any,any,any]
5: [any,any,any,any,any]
6: [any,any,any,any,any,any]
7: [any,any,any,any,any,any,any]
8: [any,any,any,any,any,any,any,any]
}
type ResolveKeys<T,K extends KeyArr<T,K>> =
K extends _ArrSizes[1] ? T[K[0]]
: K extends _ArrSizes[2] ? T[K[0]][K[1]]
: K extends _ArrSizes[3] ? T[K[0]][K[1]][K[2]]
: K extends _ArrSizes[4] ? T[K[0]][K[1]][K[2]][K[3]]
: K extends _ArrSizes[5] ? T[K[0]][K[1]][K[2]][K[3]][K[4]]
: K extends _ArrSizes[6] ? T[K[0]][K[1]][K[2]][K[3]][K[4]][K[5]]
: K extends _ArrSizes[7] ? T[K[0]][K[1]][K[2]][K[3]][K[4]][K[5]][K[6]]
: K extends _ArrSizes[8] ? T[K[0]][K[1]][K[2]][K[3]][K[4]][K[5]][K[6]][K[7]]
: unknown;
/// #### TESTING #####
interface Data {
foo: number
bar: {
baz: {
thing: string
}
}
}
declare const x: Data
// this is the new signature, using KeyArr<T,K> as constraint makes this very easy to replicate.
declare function slice<T,K extends KeyArr<T,K>>(obj: T, ...keys: K): ResolveKeys<T,K>;
const y = slice(x, "bar", "baz", "thing")
Upvotes: 1
Reputation: 21453
I thought there was a simple way using interface merging but I was unable to get it working, one I was able to get working is to use arrow function property instead of a proper method since this gives you a place to declare a function type with many overloads:
type terribleSliceSignature<S> = {
<Key extends keyof S>(key: Key): Slice<S[Key], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1]>(key1: Key1, key2: Key2): Slice<S[Key1][Key2], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2]>(key1: Key1, key2: Key2, key3: Key3): Slice<S[Key1][Key2][Key3], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4): Slice<S[Key1][Key2][Key3][Key4], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): Slice<S[Key1][Key2][Key3][Key4][Key5], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6][Key7], S>;
<Key1 extends keyof S, Key2 extends keyof S[Key1], Key3 extends keyof S[Key1][Key2], Key4 extends keyof S[Key1][Key2][Key3], Key5 extends keyof S[Key1][Key2][Key3][Key4], Key6 extends keyof S[Key1][Key2][Key3][Key4][Key5], Key7 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6], Key8 extends keyof S[Key1][Key2][Key3][Key4][Key5][Key6][Key7]>(key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): Slice<S[Key1][Key2][Key3][Key4][Key5][Key6][Key7][Key8], S>;
}
export class Atom<S> {
constructor(private initial: S) {
}
slice: terribleSliceSignature<S> = (...keys: string[]): any => {
return new Slice(this, keys);
}
}
I know this isn't ideal but it is at least better than listing that overload twice.
Upvotes: 1