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

Proposal: Exact and ExactDeep #156

Open
stephenh opened this issue Nov 26, 2020 · 5 comments
Open

Proposal: Exact and ExactDeep #156

stephenh opened this issue Nov 26, 2020 · 5 comments
Labels
help wanted Extra attention is needed type addition

Comments

@stephenh
Copy link

stephenh commented Nov 26, 2020

Hi! Not sure if you take requests, but I've wanted an Exact type in TS for awhile, primarily to model wire calls (i.e. HTTP / DB / RPC calls) where the TS semantics of "including a few extra keys won't hurt" is (imo) not the best b/c it can make the caller think it's data is going over the wire, when really unknown keys are being dropped.

There is a TS issue about this:

microsoft/TypeScript#12936

And a few ways of doing a non-deep Exact that all look good / work great afaict.

However, at least to my initial efforts/attempts, a DeepExact / ExactDeep is more nuanced / not available / solved yet, where, similar to your impl of PartialDeep, values of map/array/set/etc. all need to be handled differently/recursively.

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar
@sindresorhus
Copy link
Owner

sindresorhus commented Nov 28, 2020

Yeah, those sound like useful types for me. It's the kind of types I didn't know I needed.

PR welcome if anyone wants to implement this. Please follow: https://github.com/sindresorhus/type-fest/blob/master/.github/contributing.md

@sindresorhus sindresorhus added help wanted Extra attention is needed type addition labels Nov 28, 2020
@sindresorhus sindresorhus changed the title Proposal: Exact and ExactDeep Proposal: Exact and ExactDeep Nov 28, 2020
@kainiedziela
Copy link
Contributor

I see no clean way to implement this type. In my mind the perfect solution would be Exact<T>, but as of today it would have to be implemented as Exact<Expected, Actual>, which I think lessers the usability of this type to be the equivalent of a helper function that strips unwanted props.

I'd love to see someone tackle this, godspeed my friend.

@zorji
Copy link
Contributor

zorji commented Nov 7, 2021

Hi @stephenh do you have an example that you need to differentiate Exact and ExactDeep?

I was trying to implement an ExactDeep implementation in #259

But this type Exact is by default deep already. If there is a true use case of Exact instead of ExactDeep, I can split them into 2.

As @kainiedziela suggested I don't see there is a way to implement it without Actual. So the implementation is Exact<Expected, Actual>.

@stephenh
Copy link
Author

stephenh commented Nov 7, 2021

@zorji hey! Nope, I'd be fine with just ExactDeep.

Really great to here you're working on this. FWIW I copied your current version into a project that is perhaps a little too fancy with mapped types and ran into these two reproductions:

  it("works with get opts", () => {
    type T1 = { opts: { a: number } };
    type GetOpts<T> = T extends { opts: infer O } ? O : never;
    // fails in `O extends Exact` b/c O doesn't extend GetOpts
    function f<T, O extends Exact<GetOpts<T>, O>>(t: T, p: O) {}
    const t: T1 = { opts: { a: 1 } };
    const o = { a: 1, b: null };
    // @ts-expect-error
    f(t, o);
  });

  it("o keeps its type", () => {
    type T1 = { opts: { a: number } };
    type New<T, O extends GetOpts<T>> = T;
    type GetOpts<T> = T extends { opts: infer O } ? O : never;
    // fails in `New<T, O>` b/c O doesn't extend GetOpts
    function f<T, O extends Exact<GetOpts<T>, O>>(t: T, p: O): New<T, O> {
      return null!;
    }
    const t: T1 = { opts: { a: 1 } };
    const o = { a: 1, b: null };
    // @ts-expect-error
    f(t, o);
  });

I was able to fix both of these by adding a very naive ParameterType &:

export type Exact<ParameterType, InputType extends ParameterType> = ParameterType extends Primitive
  ? ParameterType
  : ParameterType &
      { [Key in keyof ParameterType]: Exact<ParameterType[Key], InputType[Key]> } &
      Record<Exclude<keyof InputType, KeysOfUnion<ParameterType>>, never>;

Which, AFAICT does not break any of the other tests. Not sure if that's okay/the best way or not, but FWIW thought I would mention it, and if anything maybe provide a few examples of "the type you're trying to Exact is a mapped type".

Thanks!

@stephenh
Copy link
Author

stephenh commented Nov 7, 2021

I also get this fun guy:

image

:-) However it must be something esoteric with our project-specific DeepPartialOrNull, as it works with a vanilla DeepPartial, which is great:

  it("works with DeepPartial", () => {
    type DeepPartial<T> = {
      [P in keyof T]?: T[P] extends Array<infer U>
        ? Array<DeepPartial<U>>
        : T[P] extends ReadonlyArray<infer U>
        ? ReadonlyArray<DeepPartial<U>>
        : T[P] extends object
        ? DeepPartial<T[P]>
        : T[P];
    };
    type T1 = { a: number; b: { c: number; d: number } };
    function f<T, O extends Exact<DeepPartial<T>, O>>(t: T, p: O): void {}
    const t: T1 = { a: 1, b: { c: 2, d: 3 } };
    // c is allowed to be dropped, but e is not allowed
    const o = { a: 1, b: { d: 4, e: 5 } };
    // @ts-expect-error
    f(t, o);
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed type addition
Projects
None yet
Development

No branches or pull requests

4 participants