1

Here's the code:

function id<T>(x: T) {
    return x
}

const keys: string[] = []

const obj1 = Object.fromEntries(keys.map(k => [
    k, 
    {test() {}} // OK
]))

const obj2 = Object.fromEntries(keys.map(k => [
    k, 
    id({test() {}}) // Error
]))

const obj3 = Object.fromEntries(keys.map(k => [
    k, 
    id({test: new Date()}) // OK
]))

I have no idea why typescript gives this error:

Argument of type '{ test(): void; }' is not assignable to parameter of type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{ test(): void; }'.

It seems that only functions in object literal cause this error.

The id function here is just for demo. Actual function has this signature:

<T>(x: T) => T & SomeExtraFields

Typescript version: 4.2.3

2 Answers 2

1

YOu have sevaral ways to handle it:

First:

If you use reference instead of literal type, it works as expected:

const id = <T,>(x: T): T => x

const keys: string[] = []

const iter = keys.map(k => [
    k,
    id({ test() { } }) // ok
])

const obj2 = Object.fromEntries(iter)

OR:

const idResult = id({ test() { } })
const obj2 = Object.fromEntries(keys.map(k => [
    k,
    idResult
]))

Second

Let's try to use arrow function as property instead of method:

const obj2 = Object.fromEntries(keys.map(k => [
    k,
    id({ test: () => { } }) // ok
]))

Strange behavior, is not it?

Let's take a look on Object.fromEntries signature:

fromEntries<T = any>(entries: Iterable<readonly [PropertyKey, T]>): { [k: string]: T };

So, TS tries to infer T generic type. In our case it is ReturnType of id function.

But for some reason, TS is unable to do it.

Third:

In order to help TS to infer the type, you can provide explicit generic argument for Array.prototype.map

const obj2 = Object.fromEntries(
  keys.map<[string, { test(): void }]>((k) => [k, id({ test() {} })])
);

Fourth

You can also handle it without any explicit generic arguments.

You can just add constraint to T generic in your id function definition.

interface Method {
  (): void;
}
const id = <T extends { [prop: string]: Method }>(x: T) => x;

const keys: string[] = [];

const obj2 = Object.fromEntries(keys.map((k) => [k, id({ test() {} })])); // ok


/////////////////////////

type ArrowProp = () => any;
const id = <T extends { [prop: string]: ArrowProp }>(x: T) => x;

const keys: string[] = [];

const obj2 = Object.fromEntries(keys.map((k) => [k, id({ test() {} })]));

Fifth

You can even use second generic to help TS to infer the type

const id = <Prop, T extends Record<string, Prop>>(x: T) => x;
// OR
const id = <T extends object>(x: T) => x;


const keys: string[] = [];

const obj2 = Object.fromEntries(keys.map((k) => [k, id({ test() {} })]));

Unfortunately, I'm unable to explain why TS can't infer method property without any workarounds

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

4 Comments

Maybe that's a bug. By the way, in your second example if an arrow function takes any arguments, then the error shows again. I will use explicit generic type then. Sadly, because I love auto type inference.
Yep, it might be some inconcistency betwee method type an property with arrow function. Will you raise an issue on github? I can raise as well
I would be grateful if you could do this
@dhmk083 here github.com/microsoft/TypeScript/issues/43948 you have a link, hence you can track the issue
0

Try it now.

function id<T>(x: T): T {
   return x
}

const keys: string[] = []

const obj1 = Object.entries(keys.map(k => [
    k,
    { test() { } } // OK
]))

const obj2 = Object.entries(keys.map(k => [
    k,
    id({ test() { } }) // Ok
]))

const obj3 = Object.entries(keys.map(k => [
    k,
    id({ test: new Date() }) // OK
]))

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.