Skip to content
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

Unexpected intersection inferred from indexed type #35695

Closed
shigma opened this issue Dec 16, 2019 · 6 comments
Closed

Unexpected intersection inferred from indexed type #35695

shigma opened this issue Dec 16, 2019 · 6 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@shigma
Copy link

shigma commented Dec 16, 2019

TypeScript Version: 3.7.2

Search Terms: type alias index signature intersection TS2345

Expected behavior: pass the type checks

Actual behavior: fail with a TS2345 error

data[key]
// expected: (value: DataTypes[K]) => void
// actual: Data[K], with value: never

Related Issues: #31445, #35613

Note: Although #35613 is marked as duplicated, I think a different approach than #31445 can be used to address this issue. Actually data[key] can be inferred as (value: DataTypes[K]) => void, which solves the problem.

Code

interface DataTypes {
  a: string
  b: number
}

type Data = {
  [K in keyof DataTypes]: (value: DataTypes[K]) => void
}

function myFunc <K extends keyof DataTypes> (data: Data, key: K, value: DataTypes[K]) {
  data[key](value)
}
Output
"use strict";
function myFunc(data, key, value) {
    data[key](value);
}
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

@dragomirtitian
Copy link
Contributor

Not really unexpected. Inside the function K can be any keyof DataTypes. This means that data[key] could be ((value: string) => void) | ((value: number) => void). Such a union is invokable only with an intersection of all possible parameter types (see PR). So this means the parameter would need to be string & number. Intersections of incompatible primitive types reduce to never so you end up with data[key] being effectively (value: never) => void.

To get this to work you would need some kind of correlated types such as @jcalz proposes here.

@shigma
Copy link
Author

shigma commented Dec 16, 2019

@dragomirtitian Thanks for your detailed explanation.

I know the inner logic now but my question is can the inferred type of data[key] be just more specific (from Data[K] to the definition of Data[K]), which may be easier to implement? I think it's different from #31445 and #30581.

@shigma
Copy link
Author

shigma commented Dec 16, 2019

Or, in other words, can we get actual typings for type parameters from indexed types (or inferences, however an index signature parameter type cannot be a union type)?

@dragomirtitian
Copy link
Contributor

@shigma I don't think there is any way to get the implementation types to work out except for a type assertion or using a separate implementation signature:

function myFunc<K extends keyof DataTypes>(data: Data, key: K, value: DataTypes[K]): void {
  (data[key] as (value: DataTypes[K]) => void)(value);
}

Playground Link

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 20, 2019
@RyanCavanaugh
Copy link
Member

This is a correct error; a legal call to myFunc is

declare const data: Data;
declare const ab: "a" | "b";
myFunc(data, ab, "");

which corrupts data when ab is "b"

There isn't a sound way to write the function signature, thus no way to correctly implement the body. You can use a type assertion if you pinky-swear to only call it with unit keys.

@shigma
Copy link
Author

shigma commented Dec 22, 2019

Thanks. I will close this issue.

@shigma shigma closed this as completed Dec 22, 2019
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

3 participants