The first issue is capturing the string literals when you call make. You could do this in one of several ways. You could use tuples in rest parameters (make<L extends string[]>(...keys: L): Foo<L> but this will require you call the make function as Foo.make('a', 'b').
Another option is to use the fact that the compiler will preserve literal types if they are assigned to a type parameter constrained to string. This will preserve the current way of calling.
The issue of getting the union is simple, you can use a type query.
export class Foo<K extends string[]> {
static make<L extends V[], V extends string>(keys: L): Foo<L> {
return new Foo();
}
get(key: K[number]) { // index type
}
}
const x = Foo.make(['a', 'b'])
x.get("a")
x.get("A") // err
Playground Link
Another option is not to use the tuple type in L, just capture the union directly when calling make:
export class Foo<K extends string> {
static make<V extends string>(keys: V[]): Foo<V> {
return new Foo();
}
get(key: K) { // index type
}
}
const x = Foo.make(['a', 'b'])
x.get("a")
x.get("A") // err
Playground Link