joshhunt
joshhunt

Reputation: 5335

Typescript: Generic type that extends a generic type on another class

Example

class BaseOptions {
    a: 1;
}

class OptionsA extends BaseOptions {
    b: 2;
}

class OptionsB extends BaseOptions {
    c: 3;
}

class Child<TOptions extends BaseOptions> {
    options: TOptions;
}

// Parent must use a child that supports TOptions
class Parent<TOptions extends BaseOptions, TChild extends Child<TOptions>> { }

// Supports OptionsA
class StrictChild extends Child<OptionsA> { }
// Supports OptionsA and OptionsB
class FlexibleChild extends Child<OptionsA | OptionsB> {
    doSomething() {
        if (this.options instanceof OptionsA) {
            const test1 = this.options.b;
        }
        // This should fail because no instanceof check
        const test2 = this.options.c;
    }
}

// Expected to fail because StrictChild can't have OptionsB
const parentA = new Parent<OptionsB, StrictChild>();
// Expected to pass because FlexibleChild can have OptionsA or OptionsB
// but fails because "OptionsA" is not assignable to "OptionsA | OptionsB"
const parentB = new Parent<OptionsB, FlexibleChild>();

I think the problem is

class Parent<TOptions extends BaseOptions, TChild extends Child<TOptions>> { }

specifically Child<TOptions> because I think it needs to be something like Child<TOptions extends Child<T>> but I have no idea how to do that.

Typescript playground

Upvotes: 2

Views: 125

Answers (2)

joshhunt
joshhunt

Reputation: 5335

I think I have it working so long as I set a default generic on Child so that I can use TChild['options']

class Child<TOptions extends BaseOptions = BaseOptions> {
    options: TOptions;
}

// Parent must use a child that supports TOptions
class Parent<TOptions extends TChild['options'], TChild extends Child> { } 

Typescript Playground

Edit

Alternatively you can just do

class Parent<TOptions extends TChild['options'], TChild extends Child<BaseOptions>> { }

Upvotes: 1

Fernando Espinosa
Fernando Espinosa

Reputation: 4815

You are looking for intersection types, not union types!

// Supports OptionsA and OptionsB
class FlexibleChild extends Child<OptionsA & OptionsB> { }

Now this is a perfectly valid statement:

const parentB = new Parent<OptionsB, FlexibleChild>();

Reference: https://www.typescriptlang.org/docs/handbook/advanced-types.html

An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need. For example, Person & Serializable & Loggable is a Person and Serializable and Loggable. That means an object of this type will have all members of all three types.

Upvotes: 0

Related Questions