Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save joedski/5607d1310971e2d0ce8ad3747cb5805b to your computer and use it in GitHub Desktop.
Save joedski/5607d1310971e2d0ce8ad3747cb5805b to your computer and use it in GitHub Desktop.
Wherein I finally learn how to unbox and rebox things in Typescript for great genericization justice and the great annoyance of my coworkers.
interface BoxA<T> {
a: T;
}
interface BoxB<T> {
b: T;
}
type BoxerA<T> = (a: T) => BoxA<T>;
interface ThingGetters {
[key: string]: <R>() => R;
}
const gs = {
foo: () => 'foo',
bar: () => 2,
baz: () => 14,
};
type KeyofGS = keyof typeof gs;
type FnsofGS = (typeof gs)[KeyofGS];
type RetsofGS = ReturnType<FnsofGS>;
type WGS = {
[K in keyof typeof gs]: () => BoxA<ReturnType<(typeof gs)[K]>>
};
// prewrapped
function boxA<T>(a: T): BoxA<T> {
return { a };
}
function boxB<T>(b: T): BoxB<T> {
return { b };
}
const pwgs = {
foo: () => boxA('foo'),
bar: () => boxA(3),
};
// The magic. (actually just the same as ReturnType.)
type UnboxA<B extends BoxA<any>> =
B extends BoxA<infer R> ? R : never;
// Proof of concept: Unbox a BoxA<T> (T = number, here)
type BoxANum = BoxA<number>;
type Num = UnboxA<BoxANum>;
// :: { foo: () => BoxA<string>; bar: () => BoxA<number> }
type PWGS = typeof pwgs;
// :: "foo" | "bar"
type PWGS_K = keyof PWGS;
// :: (() => BoxA<string>) | (() => BoxA<number>)
type PWGS_Gs = PWGS[PWGS_K];
// :: BoxA<string> | BoxA<number>
type PWGS_Gs_Rt = ReturnType<PWGS_Gs>;
// :: BoxB<string | number> which is the same I guess?
// We don't have a specific key (K in keyof T) to bind to,
// so we get the type covering every case.
type PWGS_Gs_Rw = BoxB<UnboxA<ReturnType<PWGS_Gs>>>;
// :: BoxA<string>
type tfr = ReturnType<typeof pwgs.foo>;
// :: string
type tfrub = UnboxA<tfr>;
// :: () => BoxA<string>
// This works before the const statement. Hm.
type trwfr = ReturnType<typeof tmpwgs.foo>;
type ThunkMakifiedPWGS<PWGS extends typeof pwgs> = {
[K in keyof PWGS]: () => PWGS[K];
}
const tmpwgs: ThunkMakifiedPWGS<typeof pwgs> = {
foo: () => pwgs.foo,
bar: () => pwgs.bar,
};
// :: <T extends PWGS>{ [K in keyof T]: () => BoxB<UnboxA<ReturnType<T[K]>>> }
type RewrappedPWGS<PWGS extends typeof pwgs> = {
[K in keyof PWGS]: () => (
// This is mildly annoying because we're basically repeating most of ReturnType<T>.
PWGS[K] extends (...args: any[]) => any
? BoxB<UnboxA<ReturnType<PWGS[K]>>>
: never
);
}
// Seems to check out.
const rewrappedPwgs: RewrappedPWGS<typeof pwgs> = {
// :: () => BoxB<string>
foo: () => boxB(pwgs.foo().a),
// :: () => BoxB<number>
bar: () => boxB(pwgs.bar().a),
}
// Finally, the genericized form.
type RewrappedGetters<T> = {
[K in keyof T]: (
// This is mildly annoying because
// we're basically repeating most of ReturnType<T>.
T[K] extends (...args: any[]) => any
? (() => BoxB<UnboxA<ReturnType<T[K]>>>)
: never
)
}
// Use of that type.
function rewrappifyGetters<T>(gs: T): RewrappedGetters<T> {
const rwgs = {} as RewrappedGetters<T>;
Object.keys(gs).forEach((key) => {
rwgs[key] = () => boxB(gs[key]());
});
return rwgs;
}
// Example base item. (could have just used pwgs above, but eh.)
const wrappedGetters = {
foo: () => boxA('foo'),
bar: () => boxA(5),
};
// Rewrapped.
const rewrappedGetters = rewrappifyGetters(wrappedGetters);
// :: () => BoxA<string>
wrappedGetters.foo;
// :: () => BoxB<string> awww yiss
rewrappedGetters.foo;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment