I'm looking to provide a generic type as a type argument without resolving it to a concrete type first. Put another way, I'm looking for a way to specify a type mapping function that I can use when inheriting from a base class.
Example (incorrect) syntax which hopefully explains better than I can:
abstract class RunIt<SomeMagicConverter> {
// The return type for this function depends on the type of the
// argument *and* on the way the implementation class was declared:
run<T>(o: T): SomeMagicConverter<T> { // (syntax error)
return this.handle(o); // (imagine this does something more interesting)
}
protected abstract handle<T>(o: T): SomeMagicConverter<T>; // (syntax error)
}
type MyMagicConverter<T> = TypeIWantToReturn<T>; // MyMagicConverter is generic
class MyRunIt extends RunIt<MyMagicConverter> { // but we don't specify T here
// [...]
}
new MyRunIt().run(7); // call infers T is number, so returns TypeIWantToReturn<number>
new MyRunIt().run(''); // now T is string, so returns TypeIWantToReturn<string>
Additionally, I want to restrict this so that SomeMagicConverter<T> extends SomeBase<T> is guaranteed. i.e.
abstract class RunIt<SomeMagicConverter extends SomeBase>
For a more concrete example of how I hope to use this, here's a basic base-class for a wrap-with-caching (not quite my actual use-case but demonstrates the need):
interface Wrapped<T> {
contains(other: T): boolean;
}
abstract class Store {
private readonly cached = new Map<any, any>();
protected abstract applyWrap<T>(o: T): Wrapped<T>;
wrap<T>(o: T): Wrapped<T> { // <-- this should return something more specific
if (!this.cached.has(o)) {
this.cached.set(o, this.applyWrap(o));
}
return this.cached.get(o);
}
}
class Foo<T> implements Wrapped<T> {
constructor(private readonly o: T) {}
contains(other: T): boolean { return other === this.o; }
extraFooFunc(): void {}
}
class FooWrapper extends Store {
constructor() { super(); }
protected applyWrap<T>(o: T): Foo<T> { return new Foo<T>(o); }
}
new FooWrapper().wrap(4).extraFooFunc(); // syntax error because extraFooFunc is not defined on Wrapped
Clearly I can work around this by defining wrapping methods, but I'd like to avoid having to do that on every child class:
class FooWrapper extends Store {
// [...]
wrap<T>(o: T): Foo<T> { return super.wrap(o) as Foo<T>; }
}