Skip to content

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

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
AWare opened this issue May 14, 2019 · 11 comments · Fixed by #37457
Closed

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

AWare opened this issue May 14, 2019 · 11 comments · Fixed by #37457
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@AWare
Copy link

AWare commented May 14, 2019

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

@RyanCavanaugh
Copy link
Member

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

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label May 17, 2019
@AWare
Copy link
Author

AWare commented May 18, 2019

Sorry @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)

@RyanCavanaugh RyanCavanaugh self-assigned this May 20, 2019
@AWare
Copy link
Author

AWare commented May 21, 2019

@RyanCavanaugh Would you mind if I attempted this and raised a PR please?

@donkeytronkilla
Copy link

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 @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.

@ipanasenko
Copy link

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

@AWare AWare mentioned this issue Jul 17, 2019
3 tasks
@matt-usurp
Copy link

matt-usurp commented Aug 21, 2019

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,
  },
}

@jpandl19
Copy link

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?

@RyanCavanaugh RyanCavanaugh added Needs Investigation This issue needs a team member to investigate its status. and removed Needs More Info The issue still hasn't been fully clarified labels Nov 4, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.8.0 milestone Nov 4, 2019
@sandersn sandersn changed the title ES2019 Object.fromEntries adds new key types to generated object ES2019 Object.fromEntries uses PropertyKeys as mapped key type Jan 13, 2020
@sandersn
Copy link
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 @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.

@nashspence
Copy link

nashspence commented Feb 2, 2020

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

@osdiab
Copy link

osdiab commented Mar 14, 2020

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.

sandersn added a commit that referenced this issue Mar 18, 2020
PropertyKey is accurate but not usable.

Fixes #31393
@sandersn sandersn added the Fix Available A PR has been opened for this issue label Mar 18, 2020
@sandersn
Copy link
Member

sandersn commented Mar 18, 2020

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

@sandersn sandersn added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Mar 18, 2020
sandersn added a commit that referenced this issue Mar 18, 2020
PropertyKey is accurate but not usable.

Fixes #31393
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants