In my class, I am using a generic type to represent the known elements of its map, as shown below:
abstract class Component { ... }
class Test<Known extends Component[]> {
components: Map<string, Component> ...
has<C extends Component>(comp: C): this is Test<[...Known, C]> {
...
}
}
I was hoping to utilize Test.has()
to change its type when checking for available components, but it seems to append to the type instead of replacing it:
// test is Test<[AComp, BComp]>, all good so far
const test = new Test<[AComp, BComp]>();
const comp = new CComp();
if (sometest.has(comp)) {
// now test becomes "Test<[AComp, BComp]> & Test<[AComp, BComp, CComp]>"
}
Essentially, test.has()
correctly gets the type, but instead of replacing it, it combines them with an "&".
I understand that TypeScript handles it this way because it doesn't recognize that the second type extends the first, treating them as distinct entities.
Is there a way to communicate to the engine that the second type extends the first, if at all possible?
Edit with Reproducible example:
abstract class Component {
}
class AComp extends Component {
aValue = 1;
}
class BComp extends Component {
bValue = 3;
}
type ClassOf<C extends Component> = new (...args: any[]) => C;
type TestGetResult<C extends Component, K extends Component[]> = C extends K[number] ? C : (C | undefined);
class Test<K extends Component[]> {
components = new Map<string, Component>();
constructor(components: Component[]) {
components.forEach(comp => this.components.set(comp.constructor.name, comp));
}
has<C extends Component>(comp: ClassOf<C>): this is Test<[...K, C]> {
return [...this.components.keys()].includes(comp.name);
}
get<C extends Component>(comp: ClassOf<C>): TestGetResult<C, K> {
return this.components.get(comp.name) as TestGetResult<C, K>;
}
}
// This class simulates us getting the Test instances from somewhere else in the code, their type is not immediately known when we return them but by
// using .has() we can infer some of it
class SomeStore {
list: Test<any>[] = [];
add(test: Test<any>) { this.list.push(test); }
getTestWithSomeComponent<C extends Component>(comp: ClassOf<C>): Test<[C]> {
for (let test of this.list) {
if (test.has(comp)) {
return test;
}
}
throw new Error('No test found');
}
}
///////////////////////////////////////////////////////////////////////////////////////
const store = new SomeStore();
const test1 = new Test<[AComp, BComp]>([new AComp(), new BComp()])
store.add(test1);
const testAfterGet = store.getTestWithSomeComponent(AComp);
if (testAfterGet.has(BComp)) {
const a = testAfterGet.get(AComp);
// Here is the issue
const b = testAfterGet.get(BComp) // at this point, testAfterGet has the 'and' type
console.log(a.aValue);
console.log(b.bValue); // b should not be undefined
}