microsoft/TypeScript

Object.entries/values incorrectly narrows

Closed this issue ยท 3 comments

๐Ÿ”Ž Search Terms

Object.entries Object.values

๐Ÿ•— Version & Regression Information

This has existed since Typescript 3.9.7, which is when definitions for Object.entries/values were added.

โฏ Playground Link

https://www.typescriptlang.org/play/?ts=6.0.0-dev.20251029#code/MYewdgzgLgBAsgTwPICMBWMC8MDeAoGGAUwA8AHI4KIgEwGsiEAuGAIgAsBLVvAXxgCGEGKEhQ8eKAgrxk6ACrSiWGFIogAZrNRoJGgK5gqncDCRgANgkQ6AFCHQsbCpQEp8hAPSeYANwEWMJzCnAC2ZABOIBQRVrYA-PGuMGACEVEA7rQwNCAZYKogbFw8hBogETC2otAwANoMCAA0fgEAujCaZuiUUAB0RGBQEZxEEPboru4EhCLgECAWRH0WIADmto0t-hauM7wz5ZXV87A7nVo6vX07+mMTaFMeszWLy6sbO3uEBwd4BkYoCYCtRoLZks8arBQhVlNhnoRSBQqLRGiwONwmjNCIYkb1UYx0SgQCBSjB+EI5mI8ABIcxWZxoWyM75mSzWORMmERIh7Gl8SRjKDgoA

๐Ÿ’ป Code

const MyObj = {
  expectedkey: "hi"
} as const

type MyObjType = typeof MyObj

function OnlyMyObj(obj: MyObjType){
  // val is improperly(??) narrowed down to "hi"
  for (const [key, val] of Object.entries(obj)){
    console.log(key, val)
  }
  for (const val of Object.values(obj)){
    console.log(val)
  }
}

function test() {
  const more = {
    expectedkey: "hi",
    unexpectedkey: "boo"
  } as const
	OnlyMyObj(MyObj)
  OnlyMyObj(more)
	
}
test()

๐Ÿ™ Actual behavior

The type for val in OnlyMyObj() is incorrectly narrowed to "hi" when it's entirely possible to get other values if there are unexpected keys.

๐Ÿ™‚ Expected behavior

Since the key from Object.entries/keys is currently not narrowed to account for cases of there being extraneous keys, I believe the proper type for val would be unknown.

Additional information about the issue

Typescript already catches a very similar error and has the error at the call site which would be preferable to changing the return type of entries/values. Not sure if that can be done though.

https://www.typescriptlang.org/play/?ts=6.0.0-dev.20251029#code/MYewdgzgLgBAsgTwPICMBWMC8MDeAoGGAUwA8AHI4KIgEwGsiEAuGAIgAsBLVvAXxgCGEGKEhQ8eKAgrxk6ACrSiWGFIogAZrNRoJeDQFcwVTuBhIwAGwSIdAChDoWAJUogATjQA80d5zAA5gA0HNwAfACU+IQA9DEwAG4CljCcwpwAtmTuIBTu1nYA-IURMGAC7jkA7rQwNCBVYKogbFw8hBoeMHai0DAA2gwIQYnJALowmubolFAAdERgUH5EEA7oEVEEhCLgECCWRHOWIAF2QyNJlhHbvNud7t29sFeTWjqzc1cGq+tom9Edr0DkcTmcrjdCHc7vojCYzNRoHZSoDnjAMh5lNhAYRSBQqLQhixQqwgttCEY8bNCYwWABOW6CYTPPAASAs1ls6DsXP+2w5NjkaDsGPcRBurL4klWUGRQA

const MyObj = {
  expectedkey: "hi"
} as const

type MyObjType = typeof MyObj


function OnlyMyObj(obj: Record<string,"hi">){
  // val is improperly(??) narrowed down to "hi"
  for (const [key, val] of Object.entries(obj)){
    console.log(key, val)
  }
  for (const val of Object.values(obj)){
    console.log(val)
  }
}

function test() {
  const more = {
    expectedkey: "hi",
    unexpectedkey: 9
  } as const
	OnlyMyObj(MyObj)
  OnlyMyObj(more)
	
}
test()

This may be a dupe of #38520, I think this is a little more specific though. I don't think Object.entries and Object.values have to be return unknown in all cases, just in this case where it narrows to literal. Maybe it would narrow to the primitive and treat the Readonly<Object> like a Record?

Duplicate of #38520.

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