You can implement remake using this.constructor and type it using the this type. recast needs higher-kinded types or a way of faking them, as below, and needs you to explicitly specify the type constructor for each subclass (it might be possible to infer this automatically with a native implementation of higher-kinded types).
// Matt's mini "type functions" library
const INVARIANT_MARKER = Symbol();
type Invariant<T> = { [INVARIANT_MARKER](t: T): T };
interface TypeFuncs<C, X> {}
const FUN_MARKER = Symbol();
type Fun<K extends keyof TypeFuncs<{}, {}>, C> = Invariant<[typeof FUN_MARKER, K, C]>;
const BAD_APP_MARKER = Symbol();
type BadApp<F, X> = Invariant<[typeof BAD_APP_MARKER, F, X]>;
type App<F, X> = [F] extends [Fun<infer K, infer C>] ? TypeFuncs<C, X>[K] : BadApp<F, X>;
// Example
const F_Foo = Symbol();
type F_Foo = Fun<typeof F_Foo, never>;
const F_Bar = Symbol();
type F_Bar = Fun<typeof F_Bar, never>;
interface TypeFuncs<C, X> {
[F_Foo]: Foo<X>;
[F_Bar]: Bar<X>;
}
class Foo<T, F = F_Foo> {
remake(): this {
return new (<any>this.constructor)();
}
recast(): App<F, T[]> {
return this as any as App<F, T[]>;
}
}
class Bar<T, F = F_Bar> extends Foo<T, F> { }
let b = new Bar<number>();
let b2 = b.remake();
console.log(b2.constructor.name); // Bar
let b3 = b.recast(); // Bar<number[]>