Skip to content

ES2019 Object.fromEntries uses PropertyKeys as mapped key type #31393

Closed

Description

@AWare

TypeScript Version: 3.5.0-dev.20190514

Search Terms:
fromEntries
Code

// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.
type T = {}

const a: { [key: string]: T } = { "key": {} }

const b: [string, T][] = Object.entries(a)

const c : {[key: number]: T; [key: string]: T } = Object.fromEntries(b)

const d : { [key: string]: T } = Object.fromEntries(b)

Expected behavior:

Compilation should fail because number is not a key of a.

edit: The return type of Object.fromEntries(b) should be inferred as d: { [key: string]: T } .

Actual behavior:

edit: The return type of Object.fromEntries(b) is {[key: number]: T; [key: string]: T } Adding an erroneous [key: number].

Object.fromEntries has type:

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

However if I make a second type parameter for fromEntries which extends PropertyKey and use that for the key type in forEntries:

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

I'm willing to make a PR for this if this sounds like a reasonable approach.

Playground Link:

Apologies, but I could not use es2019 in the playground.

Related Issues:

#30934

#30933 #25999

Activity

  1. RyanCavanaugh commented on May 17, 2019

    @RyanCavanaugh
    Member

    Which line are you expecting to fail? c? Why?

  2. AWare commented on May 18, 2019

    @AWare
    Author

    Sorry Ryan Cavanaugh (@RyanCavanaugh) I didn't explain my test case correctly.

    Even with the desired behaviour I think c would compile. I've edited my case to show what I expect to happen more clearly. (Namely the return type of Object.fromEntries taking the key type information from the tuple array instead of PropertyKey)

  3. AWare commented on May 21, 2019

    @AWare
    Author

    Ryan Cavanaugh (@RyanCavanaugh) Would you mind if I attempted this and raised a PR please?

  4. donkeytronkilla commented on Jun 18, 2019

    @donkeytronkilla

    I believe this is also causing an error in our project. I have distilled the error down to the following simple repo which I think is the same issue Alex Ware (@AWare) is describing here.

    package.json:

    {
      "name": "tserror",
      "version": "1.0.0",
      "main": "index.js",
      "scripts": {
        "build-good": "rimraf index.js && tsc --build tsconfig.good.json",
        "build-bad": "rimraf index.js && tsc --build tsconfig.bad.json"
      },
      "devDependencies": {
        "typescript": "^3.5.2",
        "rimraf": "^2.6.3"
      }
    }
    

    tsconfig.good.json:

    {
        "compilerOptions": {
            "lib": ["esnext"],
            "strict": true,
            "keyofStringsOnly": false,
        },
        "include": ["index.ts"],
        "exclude": ["node_modules"]
    }
    

    tsconfig.bad.json:

    {
        "compilerOptions": {
            "lib": ["esnext"],
            "strict": true,
            "keyofStringsOnly": true,
        },
        "include": ["index.ts"],
        "exclude": ["node_modules"]
    }
    

    index.ts:

    const moo: string = "oh oh" 
    

    When I run npm run build-good this simple example builds as expected. However, if I then go and run npm run build-bad I get the following error:

    Screen Shot 2019-06-17 at 8 16 08 PM

    The only difference in the "bad" build is that i've set "keyofStringsOnly": true instead of false. This is a stripped down version of my actual configuration but I believe it is just the interaction of "keyofStringsOnly":true and lib:["esnext"] that is relevant to this issue.

  5. ipanasenko commented on Jul 9, 2019

    @ipanasenko

    Having same issue on my side, same configuration ("keyofStringsOnly": true)

  6. matt-usurp commented on Aug 21, 2019

    @matt-usurp

    Got the same issue randomly, not sure what triggered it as things were compiling fine. But using a target of either esnext or es2019 will cause this compilation issue. Also confirmed this by updating to typescript@next and running the same code.

    Replication details are as follows, I did this in my /tmp directory and you can pretty much copy and paste these steps to reproduce.

    tsconfig.json

    {
      "compilerOptions": {
        "target": "es2019",
        "keyofStringsOnly": true,
      },
    }

    Running tsc will complain that it has nothing to do, so touch index.ts.
    Then running the tsc command will result in the following error:

    *chop*/typescript/lib/lib.es2019.object.d.ts:28:81 - error TS2322: Type 'string | number | symbol' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.
    
    28     fromEntries<T = any>(entries: Iterable<readonly [PropertyKey, T]>): { [k in PropertyKey]: T };
                                                                                       ~~~~~~~~~~~
    
    Found 1 error.
    

    This is specifically part of es2019 (currently esnext aliases this I believe).
    The fix, for now, is to "target": "es2018" instead in your tsconfig.json.

    {
      "compilerOptions": {
        "target": "es2018",
        "keyofStringsOnly": true,
      },
    }
  7. jpandl19 commented on Oct 26, 2019

    @jpandl19

    I changed target to "target": "es2018", but I am still getting the following error:

    node_modules/typescript/lib/lib.es2019.object.d.ts:28:81 - error TS2322: Type 'string | number | symbol' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.
    
    28     fromEntries<T = any>(entries: Iterable<readonly [PropertyKey, T]>): { [k in PropertyKey]: T };

    Here is my tsconfig.json

    {
      "compilerOptions": {
        "target": "es2018",
        "moduleResolution": "node",
        "module": "commonjs",
        "sourceMap": true,
        "rootDir": "src",
        "outDir": "dist",
        "keyofStringsOnly": true,
        "lib": ["esnext", "dom"],
        "esModuleInterop": true,
      },
      "exclude": ["node_modules", "**/*.spec.ts"]
    }

    The only thing that works is removing "keyofStringsOnly": true,.

    I am not sure why this is still happening. Has anyone else experienced this/ any suggestions on a fix?

  8. added
    Needs InvestigationThis issue needs a team member to investigate its status.
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Nov 4, 2019
  9. changed the title [-]ES2019 Object.fromEntries adds new key types to generated object[/-] [+]ES2019 Object.fromEntries uses PropertyKeys as mapped key type[/+] on Jan 13, 2020
  10. sandersn commented on Jan 13, 2020

    @sandersn
    Member

    It sounds like there are really two bugs here: the more serious is that the current definition of fromEntries errors when keyofStringsOnly: true. The cause (and likely fix) is similar to the original bug so I'm not going to open a new bug, though.

    I see 3 possible fixes for these two bugs:

    1. Do nothing -- this has been a bug for a couple versions of TS already.
    2. Add a type parameter for the key type K extends PropertyKey. This is what Alex Ware (@AWare) suggests, but it doesn't fix the second bug.
    3. Simplify the key type to string instead of PropertyKey. This fixes both bugs raised here, but may break other code. The related issues the OP links have some discussion of this.

    Because this isn't a 3.8 regression, I'm not going to fix this in the 3.8 beta period — any change to the DOM types is going to break someone. I'll look at this when 3.9 starts.

  11. nashspence commented on Feb 2, 2020

    @nashspence

    Is there a particularly good workaround for this in the meantime without setting keyofStringsOnly: false?

    *edit - for now I have set "skipDefaultLibCheck": true which seems okay

  12. osdiab commented on Mar 14, 2020

    @osdiab

    My question is this: I want Object.fromEntries() to be generic on the key type, something like this:

    fromEntries = <Key extends PropertyKey, Value>(entries: Iterable<[Key, Value]>) => { [key: Key]: Value }
    

    Is there a motivation for why a more precise typing like that would be unsafe at the library level? I use enums a lot as keys and I'd like for mapping across them to preserve the fact that the keys are from my enum, not just a string or maybe a number or something.

  13. added a commit that references this issue on Mar 18, 2020
    02b2cde
  14. sandersn commented on Mar 18, 2020

    @sandersn
    Member

    I opened #37457 which makes the return type { [k: string]: T }. Compared to the current declaration, this just drops the numeric index signature.

  15. added a commit that references this issue on Mar 18, 2020
    062104d
  16. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions