0

This is a follow up to this SO question to try and imitate an Exact type.

I think I have something close to DeepExact working:

// This is the good type/that we want to only accept Exact versions of
type Opts = {
  firstName?: string;
  parent?: string;
  children?: { firstName?: string }[];
};

// This is the incoming type that has unknown fields which should be rejected.
type Args = {
  firstName?: string;
  parentId?: string;
  children?: { firstName?: string; unknownField?: string }[];
};

const a: Args = null!;
// This compiles as expected but I want parentId and children.unknownField to cause type failures
const o: Opts = a;

// function that enforces only the exact Opts type
function onlyOpts<O>(o: Exact<Opts, O>): void {}

// good examples, using known fields
const g1 = { firstName: "f" };
onlyOpts(g1);
const g2 = { parent: "p", children: [{ firstName: "c1" }] };
onlyOpts(g2);

// bad examples, using unknown fields
const b1 = { firstName: "f", parentId: undefined };
onlyOpts(b1); // correctly fails b/c parentId is `never`

const b2 = { firstName: "f", children: [{ firstName: "c1", unknownField: "c1" }] };
onlyOpts(b2); // does not fail but should, unknownField is still `string`

// type alias to show B2["children"][]["unknownField"] is still string
type B2 = Exact<Opts, typeof b2>;

// Prototype Exact/DeepExact
type Exact<T, U> = T &
  {
    [K in keyof U]: K extends keyof T
      ? T[K] extends Array<infer TU>
        ? U[K] extends Array<infer UU>
          ? Array<Exact<TU, UU>>
          : never
        : U[K]
      : never;
  };

But my b2 example is not failing, i.e. the deep/recursive part. The B2 type has still children with unknownField: string instead of the unknownField: never.

I thought that separately inferring about TU and UU, and then recursing them into Exact would work, but for some reason it's not.

Any ideas?

Stephen Haberman
  • 1,454
  • 1
  • 9
  • 12
  • 1
    Looks like `T[K] extends Array` was missing a `| undefined` because of the `children?` in my `Opts` type. I've added that and it seems to be working as expected now... will verify and then mark as answered. – Stephen Haberman Jul 09 '20 at 22:58

1 Answers1

0

This is currently working for most of my trivial test cases:

export type Exact<T, U> = T &
  {
    [K in keyof U]: K extends keyof T
      ? T[K] extends Array<infer TU> | undefined | null
        ? U[K] extends Array<infer UU> | undefined | null
          ? Array<Exact<TU, UU>> | undefined | null
          : never
        : U[K]
      : never;
  };

But fails in a larger application with more esoteric generics/usages/etc. So still working on it, but I've basically-answered my original question.

Stephen Haberman
  • 1,454
  • 1
  • 9
  • 12