Skip to content

Inconsistent generic type inference between record type and object literal type #56821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hkleungai opened this issue Dec 18, 2023 · 4 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@hkleungai
Copy link

hkleungai commented Dec 18, 2023

🔎 Search Terms

record, generics, object literal

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics

⏯ Playground Link

https://tsplay.dev/mq0kQN

💻 Code

declare function forEach<T extends string | number | symbol>(
    data: { [a in T]: unknown },
    cb: (x: T) => unknown
): void;

declare let record: Record<string, unknown>;
forEach(record, key => {})
//              ^?

declare let obj: { [a: string]: unknown };
forEach(obj,    key => {})
//              ^?

🙁 Actual behavior

  • key for record: Record<string, unknown> is inferred as string type
  • key for { [a: string]: unknown } is inferred as string | number type

🙂 Expected behavior

both key should be inferred as the same type, i.e. string type.

Additional information about the issue

No response

@fatcerberus
Copy link

fatcerberus commented Dec 18, 2023

This is intentional, if surprising. Normally keyof a string index signature is string | number, because the type is indexable by either. Record, however, is defined as a mapped type which produces a keyof of just string. Duplicate of Related to #55706.

@fatcerberus
Copy link

fatcerberus commented Dec 18, 2023

Duplicate of #49540 and/or #48269.

@hkleungai
Copy link
Author

hkleungai commented Dec 18, 2023

Edit: Thanks @fatcerberus for the quick response. I think record / mapped types are the way then :)

Thanks @fatcerberus for the quick response. I have read through the issues you mentioned, and I want to further ask something.

I find that by changing the obj type from index signature to mapped type, it gives the desired type.

Playground link: https://tsplay.dev/mb1nBN

Code:

declare function forEach<T extends string | number | symbol>(
    data: { [a in T]: unknown },
    cb: (x: T) => unknown
): void;

declare let               obj_index_sig: { [s: string]: unknown };
//                        ^?
forEach(obj_index_sig,    key => {})
//                        ^?

declare let               obj_mapped_type: { [s in string]: unknown } 
//                        ^?
forEach(obj_mapped_type,  key => {})
//                        ^?

But then, what is the actual difference between { [s: string]: unknown } and { [s in string]: unknown }?

On one hand, it seems the mapped type does not just devolve into an index signature, based on how the tsc successfully interpreted the string type from key for obj_mapped_type.

But on the other hand, the ts playground inlay indicates they have the same "shape" (with the only difference in the LHS key, s vs x).

let obj_index_sig{ [sstring]unknown; }
let obj_mapped_type{ [xstring]unknown; }

How should I understand the difference between { [s: string]: unknown } and { [s in string]: unknown }?

If i understand it correctly, mapped type is essentially a record. But then why the ts inlay shows that mapped type and index-signature type are in same shape?

let obj_index_sig{ [sstring]unknown; }
let obj_mapped_type{ [xstring]unknown; }

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Dec 19, 2023
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Dec 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants