0
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?

5
  • 1
    It works here though: Playground Link Commented Jun 1, 2020 at 13:46
  • 1
    There is no error playground Commented Jun 1, 2020 at 13:46
  • 1
    Please consider providing a minimal reproducible example suitable for dropping into a standalone IDE so that others can demonstrate your issue for themselves. Right now you haven't included the code that gives you the error you're mentioning, so it's unlikely anyone will be able to help you effectively. Good luck! Commented Jun 1, 2020 at 13:48
  • @PoulKruijt Updated the question to show the exact error. Tested in the playground. Commented Jun 1, 2020 at 14:17
  • @v.kostenko Updated the question to show the exact error. Tested in the playground. Commented Jun 1, 2020 at 14:17

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Removing generics from function won't make the function reusable. The only reason I introduced the generic to the system to enforce the type of bar.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.