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

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

Closed
jcready opened this issue Oct 13, 2022 · 9 comments · Fixed by #53098
Closed

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

jcready opened this issue Oct 13, 2022 · 9 comments · Fixed by #53098
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@jcready
Copy link

jcready commented Oct 13, 2022

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

@jcready
Copy link
Author

jcready commented Oct 13, 2022

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

@MartinJohns
Copy link
Contributor

You can write:

type AnyOneofKind<T extends AnyOneof> = T extends { oneofKind: string & keyof T }
    ? T['oneofKind']
    : never;

@jcready
Copy link
Author

jcready commented Oct 13, 2022

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.

@MartinJohns
Copy link
Contributor

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

@jcready
Copy link
Author

jcready commented Oct 13, 2022

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

@typescript-bot
Copy link
Collaborator

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

@RyanCavanaugh
Copy link
Member

PR for that commit is #46812

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

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Oct 13, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Oct 13, 2022
@RyanCavanaugh RyanCavanaugh added the Help Wanted You can do this label Oct 13, 2022
@RyanCavanaugh
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment