Skip to content

Key that must exist on generic type T cannot be used to index type type T #51161

Closed

Description

Bug Report

🔎 Search Terms

"Cannot be used to index", "generic conditional", "generic extends ternary branch"

Seems similar to #50462 and #50465 but both of those tickets have since been fixed in 4.8.1 and 4.9 respectively while my example is still broken in the playground using 4.8 or nightly (which as of writing this is v4.9.0-dev.20221013)

🕗 Version & Regression Information

  • This changed between versions 4.6.4 and 4.7.4

⏯ Playground Link

Playground link with relevant code

💻 Code

export type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined };
export type AnyOneofKind<T extends AnyOneof> = T extends { oneofKind: keyof T }
    ? T['oneofKind'] // error after v4.6.4
    : never;

🙁 Actual behavior

In TS versions greater than v4.6.4 (I've only tested versions available in the playground) the T['oneofKind'] on line 3 is highlighted as an error: Type '"oneofKind"' cannot be used to index type 'T'.(2536).

🙂 Expected behavior

No error on line 3 as "oneofKind" can in fact be used to index into type T since T must have property "oneofKind".

Activity

  1. jcready commented on Oct 13, 2022

    @jcready
    Author

    Some things to note about this. If you remove | { oneofKind: undefined } from the AnyOneof type the error goes away (but is obviously no longer the type I need). In fact, it seems like simply having the AnyOneof not be a union removes the error so perhaps this is just an issue with indexing into a union. 🤔

    The other odd thing about this is that the AnyOneofKind still computes the correct results even with the error.

    // should be never
    type wrongKey = AnyOneofKind<{
        oneofKind: 'foo',
        bar: 1, // wrong key
    }>;
    
    // should be "foo"
    type foo = AnyOneofKind<{
        oneofKind: 'foo',
        foo: 1,
    }>;
    
    // should be "foo" | "bar"
    type foobar = AnyOneofKind<{
        oneofKind: 'foo',
        foo: 1,
    } | {
        oneofKind: 'bar',
        bar: 1,
    }>;
  2. MartinJohns commented on Oct 13, 2022

    @MartinJohns
    Contributor

    You can write:

    type AnyOneofKind<T extends AnyOneof> = T extends { oneofKind: string & keyof T }
        ? T['oneofKind']
        : never;
  3. jcready commented on Oct 13, 2022

    @jcready
    Author

    Thanks, that certainly fixes the error. I still don't understand why since the oneofKind property could only ever have a value of type string | undefined, so I'd have assumed constraining that union to keyof T would've constrained the union to string already.

  4. MartinJohns commented on Oct 13, 2022

    @MartinJohns
    Contributor

    I'm not certain, but I believe the issue is that keyof T now includes symbol.

  5. jcready commented on Oct 13, 2022

    @jcready
    Author

    Right, but (string | undefined) & (string | symbol) is just string. So this still seems like a bug unless I'm misunderstanding how the generic constraints work.

    type PossibleValuesForOneofKindProperty = string | undefined;
    type KeyofT = string | symbol;
    
    type JustString = PossibleValuesForOneofKindProperty & KeyofT; // string
  6. typescript-bot commented on Oct 13, 2022

    @typescript-bot
    Contributor

    The change between release-4.5 and main occurred at fc82c67.

  7. RyanCavanaugh commented on Oct 13, 2022

    @RyanCavanaugh
    Member

    PR for that commit is #46812

  8. RyanCavanaugh commented on Oct 13, 2022

    @RyanCavanaugh
    Member

    An equivalent type, as far as I can tell, is

    export type AnyOneofKind<T extends AnyOneof> = T extends unknown ? keyof T & T['oneofKind'] : T

    Right, but (string | undefined) & (string | symbol) is just string

    (T | U) & (T | V) is T, but (T & U) | (T & V) is correctly possibly never. I think the logic in the linked PR is correct and it's proper to defer the type computation here, otherwise it'd be possible to construct some nonsense types.

    It happens in this case that the definition of AnyOneof precludes that possibility, since it's seeable in advance that "oneofKind" & string isn't never. But we (as the authors) can also use that information to choose the simpler definition above.

    If someone is able to fix this without regressing other cases, that's fine, but this is Backlog milestone unless there are definitions without an available rewrite.

  9. RyanCavanaugh commented on Oct 13, 2022

    @RyanCavanaugh
    Member

    FYI Wesley Wigham (@weswigham) in case it turns out there's a trivial fix you can think of

  10. locked as resolved and limited conversation to collaborators on Oct 22, 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