Skip to content

Inconsistent 'keyof' keyword in Record type and Object literal type #49540

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
toy0605 opened this issue Jun 14, 2022 · 7 comments
Closed

Inconsistent 'keyof' keyword in Record type and Object literal type #49540

toy0605 opened this issue Jun 14, 2022 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@toy0605
Copy link

toy0605 commented Jun 14, 2022

Bug Report

πŸ”Ž Search Terms

  • keyof
  • Record
  • Object literal

πŸ•— Version & Regression Information

testing in 4.7.3. but it occured every version.

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type A = Record<string, unknown>;
type B = { [x: string]: unknown };
type C = { [x in string]: unknown };

const a: keyof A = 1; // Type 'number' is not assignable to type 'string'.
const b: keyof B = 1; 
const c: keyof C = 1; // Type 'number' is not assignable to type 'string'.

πŸ™ Actual behavior

keyof A is string.
keyof B is string | number
keyof C is string.

πŸ™‚ Expected behavior

Every keyof A, B, C types should be string | number OR string.


https://www.typescriptlang.org/docs/handbook/2/keyof-types.html

Note that in this example, M is string | number β€” this is because JavaScript object keys are always coerced to a string, so obj[0] is always the same as obj["0"].

Honestly, I don't know what is difference using 'in' keyword at key of this code. but it makes different result so I post this issue.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jun 14, 2022

The case A and B are working as intended. See #48269 (comment). Case C is essentially a mapped type as well, so the same as A.

@toy0605
Copy link
Author

toy0605 commented Jun 14, 2022

The case A and B are working as intended. See #48269 (comment). Case C is essentially a mapped type as well, so the same as A.

Record<K, T> is equivalent to { [P in K]: T }. so Record<string, unknown> is equivalent to {[P in string]: unknown}.

but I can't understand how { [P in string]: unknown } and { [x: string]: unknown } is difference. Why Record Type needs P?

@MartinJohns
Copy link
Contributor

MartinJohns commented Jun 14, 2022

@toy0605
Copy link
Author

toy0605 commented Jun 14, 2022

type A = { [x: string] : unknown}
type B = Record<string, unknown>;

const a: A = { 0: "test", a: "test" };
const b: B = { 0: "test", b: "test" };

function testA<T extends A>(a: T) {
    return a;
}

function testB<T extends B>(b: T) {
    return b;
}

testA(a);
testA(b);
testB(a);
testB(b);

Playground link

It absolutely works same. but using 'keyof' is difference. is it intended? but why?

@MartinJohns
Copy link
Contributor

MartinJohns commented Jun 14, 2022

Because these are different features with different purposes. With Record<> you can limit the keys, e.g. Record<"a" | "b", unknown>, but this is not possible for types with index signatures. A record means "for every key specified" (just that you use string to say any string key), whereas with index signatures you say "for any string key".

The two having different keyof behavior is intentional, as mentioned in the issue I linked earlier.

@toy0605
Copy link
Author

toy0605 commented Jun 14, 2022

Because these are different features with different purposes. With Record<> you can limit the keys, e.g. Record<"a" | "b", unknown>, but this is not possible for types with index signatures. A record means "for every key specified" (just that you use string to say any string key), whereas with index signatures you say "for any string key".

I'm not sure why you expect different features with different functionality to behave exactly the same.

I didn't understand clearly but I got to understand it is different features.

Thank you for reply!

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 14, 2022
@typescript-bot
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants