Skip to content

Improve generic keyof relationship checking for generic conditionals with keyless branch types #49563

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

weswigham
Copy link
Member

Originally from #49091. This essentially applies keyof to the constraint of a conditional in a deferred way, ignoring branches which don't contribute to the final result set of keys. This allows, eg, keyof (T extends null ? never : T) to be related to keyof T. In addition, intersection members for whom keyof returns never are omitted from the relationship check, allowing a deferred effective keyof (T & object) to be related to keyof T (which already works when not indirected via a conditional via construction).

So in total, given a type like

type FilterNull<T> = T extends null ? never : T;

then keyof FilterNull<(T & object) | null> is now able to be related to keyof T.

Practically, I initially looked into this to support using the conditional-based NonNullable in more places, but now that it's just an intersection, this is moreso just for users using keyof over their own filtering conditionals (as is common in react libraries nowadays).

@weswigham weswigham requested review from sandersn and ahejlsberg June 15, 2022 18:20
@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Jun 15, 2022
@sandersn
Copy link
Member

Can you give an example of how a particular react libraries would benefit from this? A concrete example of usage would help me understand how widely applicable it might be.

@ahejlsberg
Copy link
Member

Hmm, I'm not sure these changes are desirable. See the discussion here. We effectively rely on the fact that keyof T resolves to never for any T that includes null or undefined.

@weswigham
Copy link
Member Author

I dunno, all the cases in the test in this PR seem pretty desirable...

@weswigham
Copy link
Member Author

(also that's unchanged by this PR?)

@ahejlsberg
Copy link
Member

With this PR there is no error in the example below where previously there was. The error is desirable because it protects us from accessing through a possibly-null object without requiring local proof that the object is non-null. The thing that saves us is that keyof T for a T that includes null or undefined is never, but it isn't with this PR.

type NonNull<T> = T extends null ? never : T;

function foo<T>(t: T, k: keyof NonNull<T>) {
    t[k];  // Was error, now isn't
}

foo(null, 'foo');

@weswigham
Copy link
Member Author

weswigham commented Jun 30, 2022

If that should be invalid, it should be because T is possibly undefined, not because of anything else - keyof NonNull<T> is legitimately always a safe key to index T with, if T isn't undefined. If we want to catch stuff like that, it should be a simple check to add - we just need to add an error if the thing we're indexing is possibly undefined.

@ahejlsberg
Copy link
Member

ahejlsberg commented Jun 30, 2022

If we want to catch stuff like that, it should be a simple check to add - we just need to add an error if the thing we're indexing is possibly undefined.

I'm quite certain that would be a severe breaking change, and it would force checks that are unnecessary because we're already protecting you by having keyof T be never when T is possibly null or undefined.

@weswigham
Copy link
Member Author

weswigham commented Jun 30, 2022

we're already protecting you by having keyof T be never when T is possibly null or undefined.

Except it's not never is it? It's an unresolved generic. Certainly when I write keyof T it doesn't become never. It's related like the useful keyof T it is everywhere, except, maybe, in some specific instance where it's falling back to the constraint incorrectly? (Because our constraint for generic keyof is a least bound, not a constraint in the same sense as other types.) Certainly if it was just never then two unrelated keyof T and keyof U would be equivalent, which is patently false.

Point being we can clearly do better here. Using one problem to excuse another isn't a great place to be - both probably need fixing.

@sandersn
Copy link
Member

sandersn commented Dec 4, 2023

@weswigham do you want to keep working on this? Is there a reasonable path to fixing the keyof T-where-T-contains-undefined problem? I believe Anders' recent PR drops the undefined in more locations, but I'm not sure it gets dropped in all of them.

@weswigham weswigham marked this pull request as draft August 13, 2024 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Status: Waiting on author
Development

Successfully merging this pull request may close these issues.

4 participants