Definitelytyped: [@types/yup] Optional subfields get marked as required with InferType

Created on 27 May 2020  路  9Comments  路  Source: DefinitelyTyped/DefinitelyTyped

If you know how to fix the issue, make a pull request instead.

  • [x] I tried using the @types/yup package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.
  • @dhardtke
  • @vtserman
  • @MoretonBayRC
  • @sseppola
  • @YashdalfTheGray
  • @vincentjames501
  • @robertbullen
  • @sat0yu
  • @deskoh
  • @mauricedb
  • @kalley
  • @elias-garcia
  • @iansan5653

If you do not mention the authors the issue will be ignored.

Describe the bug

When when an object schema is created, it's Typescript types are faithfully inferred via InferType, but when that same schema is included as a nested schema within another object, the resulting inferred types incorrectly drop the optional status of any nested fields.

To Reproduce

Minimal repro here:
https://codesandbox.io/s/yup-infertype-nested-optional-issue-v63dd

import { object, string, InferType } from "yup";

const nameSchema = object({
  first: string().notRequired(),
  last: string().notRequired()
});

type NameType = InferType<typeof nameSchema>;

const userSchema = object({
  name: nameSchema.notRequired()
});

type UserType = InferType<typeof userSchema>;

class User implements UserType {
  name?: NameType;
}

Results in error:

Property 'name' in type 'User' is not assignable to the same property in base type 'Id<Partial<Pick<{ name: { first: ...; last: ...; }; }, "name">> & Pick<{ name: { first: ...; last: ...; }; }, never>>'.
  Type 'Id<Partial<Pick<{ first: string; last: string; }, "first" | "last">> & Pick<{ first: string; last: string; }, never>>' is not assignable to type '{ first: string; last: string; }'.
    Property 'first' is optional in type 'Id<Partial<Pick<{ first: string; last: string; }, "first" | "last">> & Pick<{ first: string; last: string; }, never>>' but required in type '{ first: string; last: string; }'.ts(2416)

Based on the hints, NameType by itself is correct:

type NameType = {
    first?: string;
    last?: string;
}

but once included in UserType, the optionals get dropped:

type UserType = {
    name?: {
        first: string;
        last: string;
    };
}

Expected behavior
Optional state of fields is preserved for nested schemas

Most helpful comment

I believe I have resolved this issue and have raised a PR for it here: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/46518

All 9 comments

Here's a more minimal repro (https://codesandbox.io/s/yup-infertype-nested-optional-issue-mgnhg):

const exampleSchema = object({
  b: object({
      a: number()
  })
});

const a: InferType<typeof exampleSchema> = {
  b: {
  }
};

Oddly though, I can only reproduce in your sandbox, not on my machine. It's possible that this has something to do with TypeScript versions, as I'm using 3.9.3 on my machine and the sandbox is is on 3.8.3. I can't play with it more at the moment though.

Thanks for the _much_ better repro and for looking into it. I was running 3.8.3 on my machine but have since upgraded and it still repros:
image

OK, I think I've tracked down the difference between our setups. You need to enable strict mode by creating in your project a tsconfig.json file with:

{
  "compilerOptions": {
    "strict": true
  }
}

If you do this, then the type inference will work. Otherwise TypeScript considers undefined to be assignable to anything so it ignores it, so (for example) number().notRequired() gives us NumberSchema<number> instead of NumberSchema<number | undefined>. The undefined part is discarded. Then because the schema type doesn't extend undefined, it's not marked as optional by the utility types. See also https://github.com/jquense/yup#typescript-setting.

The minimal bug still reproduces for me even with the correct tsconfig.json

~/typesyupbug $ npx tsc --showConfig
{
    "compilerOptions": {
        "strict": true,
        "strictNullChecks": true
    },
    "files": [
        "./test.ts"
    ]
}
~/typesyupbug $ npx tsc test.ts
test.ts:10:3 - error TS2741: Property 'a' is missing in type '{}' but required in type '{ a: number; }'.

10   b: {
     ~

  test.ts:5:7
    5       a: number()
            ~~~~~~~~~~~
    'a' is declared here.
  test.ts:4:3
    4   b: object({
        ~~~~~~~~~~~
    5       a: number()
      ~~~~~~~~~~~~~~~~~
    6   })
      ~~~~
    The expected type comes from property 'b' which is declared here on type 'Id<Partial<Pick<{ b: { a: ...; }; }, "b">> & Pick<{ b: { a: ...; }; }, never>>'


Found 1 error.

~/typesyupbug $ npx tsc --version
Version 3.9.3

This is also an issue for me, would appreciate any support on it

This is also an issue for me, would appreciate any support on it

Make sure you're using strictnullchecks please. You can test that by seeing if this errors:

const x: number = null;

Make sure you're using strictnullchecks please.

As you can see in my comment back in May, enabling strictnullchecks was not sufficient to fix this bug. I just ran it again to verify. I'm using the options to specified and the minimal repro you wrote. Modules are up to date

Agreed, similar to @anstosa I have tsconfig.json

"strictNullChecks": true,
"strict": true

Also, the original repro @anstosa provided works correctly for me: https://codesandbox.io/s/yup-infertype-nested-optional-issue-v63dd

However the one @iansan5653 provided does not: https://codesandbox.io/s/yup-infertype-nested-optional-issue-mgnhg

I have forked your repro to get it working, it was missing @types/yup in the package.json, so all your types were resolving to any. The fixed repro is available here: https://codesandbox.io/s/yup-infertype-nested-optional-issue-90sgo

If you hover over line 12's b you'll see this:
image

This means that key a needs to exist but can have an undefined value, what we would prefer is for key a not to be required at all.

Our current workaround is to Omit<T, 'a'> & { a?: number;}

I believe I have resolved this issue and have raised a PR for it here: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/46518

Was this page helpful?
0 / 5 - 0 ratings