Ts-toolbelt: Strange behaviour in generics

Created on 20 Aug 2019  Â·  13Comments  Â·  Source: millsp/ts-toolbelt

🤔 Question

Describe your question

Hi @pirix-gh, can you please explain the following error?
It seems that typescript is able to realize that

T[O.SelectKeys<T, V>] === V

When used with concrete types, but when using generics, it fails miserably...

interface IA {
  a: string;
  b: number;
}

const x: IA[O.SelectKeys<IA, string>] = ''; // valid as the type resolves to string

const extractFieldOfType = <T extends object, V>(object: T, field: O.SelectKeys<T, V>): V => {
  return object[field];
};

/*
 error TS2322: Type 'T[{ 1: keyof T & string; 0: never; }[Extends<T[keyof T & string], V>]] | T[{ 1: keyof T & number; 0: never; }[Extends<T[keyof T & number], V>]] | T[{ 1: keyof T & symbol; 0: never; }[Extends<...>]]' is not assignable to type 'V'.
  'T[{ 1: keyof T & string; 0: never; }[Extends<T[keyof T & string], V>]] | T[{ 1: keyof T & number; 0: never; }[Extends<T[keyof T & number], V>]] | T[{ 1: keyof T & symbol; 0: never; }[Extends<...>]]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
    Type 'T[{ 1: keyof T & string; 0: never; }[Extends<T[keyof T & string], V>]]' is not assignable to type 'V'.
      'T[{ 1: keyof T & string; 0: never; }[Extends<T[keyof T & string], V>]]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
        Type 'T[{ 1: keyof T & string; 0: never; }[0 | (T[keyof T & string] extends V ? 1 : 0)]]' is not assignable to type 'V'.
          'T[{ 1: keyof T & string; 0: never; }[0 | (T[keyof T & string] extends V ? 1 : 0)]]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
            Type 'T[{ 1: keyof T & string; 0: never; }[T[keyof T & string] extends V ? 1 : 0]]' is not assignable to type 'V'.
              'T[{ 1: keyof T & string; 0: never; }[T[keyof T & string] extends V ? 1 : 0]]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
                Type 'T[{ 1: keyof T & string; 0: never; }[T[string] extends V ? 1 : 0]]' is not assignable to type 'V'.
                  'T[{ 1: keyof T & string; 0: never; }[T[string] extends V ? 1 : 0]]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
                    Type 'T[keyof T & string]' is not assignable to type 'V'.
                      'T[keyof T & string]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.
                        Type 'T[string]' is not assignable to type 'V'.
                          'T[string]' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint '{}'.

/*
question

Most helpful comment

Strange enough, I am looking into this at the same time!

All 13 comments

Strange enough, I am looking into this at the same time!

haha that is strange! Hope you find something soon :-)

I'll come back to you, it might take me some time... :thinking:
I have to figure out a few things about type inference

Thanks, I appreciate it!

On Tue, Aug 20, 2019, 21:19 Pierre-Antoine Mills notifications@github.com
wrote:

I'll come back to you, it might take me some time... 🤔
I have to figure out a few things about type inference

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/pirix-gh/ts-toolbelt/issues/39?email_source=notifications&email_token=ABMCBH5NW4USUTYTXN7TLSTQFQYUHA5CNFSM4IN2HA22YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4XGXLQ#issuecomment-523135918,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABMCBH7WEJWRKHPGNFPS32TQFQYUHANCNFSM4IN2HA2Q
.

@regevbr are you willing to extract a list of fields or just a single field?

@pirix-gh can you please elaborate? I'm not sure on what part you are talking about...

What is the purpose of this? Just return the value of a field?

const extractFieldOfType = <T extends object, V>(object: T, field: O.SelectKeys<T, V>): V => {
  return object[field];
};

Yes, I use it when I don't know what field I'm getting (plain string), but I do know that its value will be V.

It is just an elaborate way of avoiding ts-ignore on

```typescript
const x: string = object[field]
````
Where the field is an arbitrary string;

@regevbr this version is more typesafe as V might not exist on your type. The stuff below is still very WIP, but is something I would like to integrate to the library:

import {O} from 'ts-toolbelt'

const extractFieldOfType = <O extends object, T, F extends O.SelectKeys<O, T>>(object: O, type: T, field: F): O.At<O, F>  => {
    return object[field] as any;
};

const o = {
    a: 1,
    b: '',
    c: 2
}

const Type = { // Create a bond type-runtime
    array    : 'array',
    boolean  : 'boolean',
    function : 'function',
    object   : 'object',
    null     : 'null',
    number   : 'number',
    string   : 'string',
    symbol   : 'symbol',
    undefined: 'undefined',
} as unknown as {
    array    : any[],
    boolean  : boolean,
    function : (...args: any) => any,
    object   : object,
    null     : null,
    number   : number,
    string   : string,
    symbol   : symbol,
    undefined: undefined,
}

const t = extractFieldOfType(o, Type.number, 'a')

This way, you also don't have to worry about passing the type of your object and V, it's done (inferred) automatically.

That is fairly complicated to understand but I got it :-)
But you still have to coerce the return type to any which I'm trying to avoid...
Which types do you want to put in the library? The Type one?

Yhea, don't worry about the Types there. It's just that I have a preference in avoiding to pass generics.

Ah, well, this comes from the At type (in my example). But in yours, I am afraid that we can't do anything about it. TypeScript is not able to narrow all the computations down, even though it is correct. So, if you are willing to do this, you will have no other option than using as or:

const extractFieldOfType = <T extends object, V>(object: T, field: O.SelectKeys<T, V>): T[keyof T] => {
    return object[field];
};

Let me know if you find anything else :)

I use as almost every day to work around this.

@pirix-gh thanks for investigating this.
My final result is keeping the code I already used...

export const extractFieldOfType = <T extends object, V>(object: T, field: O.SelectKeys<T, V>): V => {
  // @ts-ignore
  return object[field];
};

Which produces the best results without any typescript extremely strange and long errors all over the place....

Was this page helpful?
0 / 5 - 0 ratings