user311413
user311413

Reputation: 177

How to constrain Interface generic to only string or object or null?

export interface Foo<T extends string | object | null> {
  bar: T;
}

type Callback<T = any> = (res: T) => void

function hydra<T extends string | object | null>(callback: Callback<Foo<T>>) {
    callback({
        bar: 'Hello World'
    });
}

I doesn't work. It throws this error:

Type '"Hello World"' is not assignable to type 'T'.
  '"Hello World"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | object | null'.

Playground Link

How can I solve this issue?

Upvotes: 0

Views: 87

Answers (2)

Poul Kruijt
Poul Kruijt

Reputation: 71911

You can make a subtype which contains your constraints, and have that declared as the value of bar. This way you don't have the constraint issues where T could be something else than the provided string:

export type FooType = string | object | null;

export interface Foo {
  bar: FooType;
}

type Callback<T = any> = (res: T) => void

function returnResponse(callback: Callback<Foo>) {
  callback({
    bar: 'INVAILD_REQUEST'
  });
}

This will make your lose your generic type hinting though. So another solution would be to use as T.

export interface Foo<T extends string | object | null> {
  bar: T;
}

type Callback<T = any> = (res: T) => void

function hydra<T extends string | object | null>(callback: Callback<Foo<T>>) {
  callback({
    bar: 'Hello World' as T
  });
}

The reason you have to do that is because if a consumer of the hydra function passes in a callback with a different generic type, your type string does not suffice anymore. Like the error message says:

'"Hello World"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | object | null'.

If you have an error like this, you always need to do a as T to make the compiler happy

Upvotes: 0

VRoxa
VRoxa

Reputation: 1051

The main problem here is, although the literal Hello World is assignable to a string, the fact of having a generic argument (as a constraint) relies on having possibly a subtype of string which Hello World would not satisfy.

Suppose this scenario

type StringSubtype = 'Not Hello World';
hydra<StringSubtype>(res => {
    // ...
});

The generic argument of the function hydra is now a subset of string. And, indeed, it satisfies the extends string | object | null constraint. All valid.
But, res.bar is of type 'Not Hello World' which means not other string value can match the type.

hydra<StringSubtype>(res => {
    // ...

    // Type '"Hello World"' is not assignable to type '"Not Hello World"'
    res = {
        bar: `Hello World`
    };
});

This is what your compilation error says

Type '"Hello World"' is not assignable to type 'T'. '"Hello World"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | object | null'.

Even if Hello World is assignable to string, the compiler does not guarantee that T could be a subtype of string.

Solution

Simply don't declare your hydra function as generic. Use the more flexible union type as a type argument of Foo<> directly.

function hydra(callback: Callback<Foo<string | object | null>>) {
    callback({
        bar: 'Hello World'
    });
}

EDIT: Alternative solutions

  1. Force casting Hello World as T
function hydra<T extends string | object | null>(callback: Callback<Foo<T>>) {
    callback({
        bar: 'Hello World' as unknown as T
    });
}
  1. Union the T with the forced value
function hydra<T extends string | object | null>(callback: Callback<Foo<T | 'Hello World'>>) {
    callback({
        bar: 'Hello World'
    });
}

But, deeply enough, if you are forcing a value to res.bar, there's no reason to have any generic argument.
However, your example is a sample and I guess there should be a reason for this... I hope any of these three workarounds matches with your intention.

I explained what the error was, at least. So you can achieve what you need with this knowledge, I guess.

Hope it helps.

Upvotes: 1

Related Questions