镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Unexpected intersection inferred from indexed type #35695

Description

@shigma

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

Activity

  1. dragomirtitian commented on Dec 16, 2019

    @dragomirtitian
    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 Joe Calzaretta (@jcalz) proposes here.

  2. shigma commented on Dec 16, 2019

    @shigma
    Author

    Titian Cernicova-Dragomir (@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.

  3. shigma commented on Dec 16, 2019

    @shigma
    Author

    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)?

  4. dragomirtitian commented on Dec 16, 2019

    @dragomirtitian
    Contributor

    Shigma (@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

  5. RyanCavanaugh commented on Dec 20, 2019

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

  6. shigma commented on Dec 22, 2019

    @shigma
    Author

    Thanks. I will close this issue.

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

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions