Repository: jquense/yup (ts)

Issue:
TypeScript complains when chaining .when() calls in v1
**Describe the bug**
When you call `when()` a 2nd time in a schema, TypeScript complains with an arcane type error

**To Reproduce**

This is an example schema that used to work before v1.

```typescript
{
    appointmentStatus: mixed<AppointmentStatus>()
      .oneOf(Object.values(AppointmentStatus))
      .required()
      .label("Appointment Status"),
    firstComeFirstServe: boolean()
      .when(["appointmentStatus"], {
        is: AppointmentStatus.Scheduled,
        then: (schema) => schema.required(),
      })
      .label("First Come First Serve"),
    timeEarly: string()
      .label("Time")
      .when(["appointmentStatus"], {
        is: AppointmentStatus.Scheduled,
        then: (schema) => schema.required(),
      })
      .when(["firstComeFirstServe"], {
        is: true,
        then: (schema) =>
          schema.label(
            "Time Early"
          ),
      }),
  };
```

TypeScript gives this error, concerning the 2nd `when` call for the `timeEarly` field.
```
This expression is not callable.    
  Each member of the union type '{ <U extends ISchema<any, any, any, any> = StringSchema<string | undefined, AnyObject, undefined, "">>(builder: ConditionBuilder<StringSchema<string | undefined, AnyObject, undefined, "">, U>): U; <U extends ISchema<...> = StringSchema<...>>(keys: string | string[], builder: ConditionBuilder<...>): U; <UThen extends...' has signatures, but none of those signatures are compatible with each other.
```

**Expected behavior**
This worked fine with no type errors in `v0.x`


**Platform (please complete the following information):**
TypeScript v4.9.5
yup v1.0.0


Pull request:
Followups
closes #1912  #1899 

Patch:
diff --git a/src/Condition.ts b/src/Condition.ts
index 63692f6..6a3676d 100644
--- a/src/Condition.ts
+++ b/src/Condition.ts
@@ -2,19 +2,16 @@ import isSchema from './util/isSchema';
 import Reference from './Reference';
 import type { ISchema } from './types';
 
-export type ConditionBuilder<
-  T extends ISchema<any, any>,
-  U extends ISchema<any, any> = T,
-> = (values: any[], schema: T, options: ResolveOptions) => U;
-
-export type ConditionConfig<
-  T extends ISchema<any>,
-  TThen extends ISchema<any, any> = T,
-  TOtherwise extends ISchema<any, any> = T,
-> = {
+export type ConditionBuilder<T extends ISchema<any, any>> = (
+  values: any[],
+  schema: T,
+  options: ResolveOptions,
+) => ISchema<any>;
+
+export type ConditionConfig<T extends ISchema<any>> = {
   is: any | ((...values: any[]) => boolean);
-  then?: (schema: T) => TThen;
-  otherwise?: (schema: T) => TOtherwise;
+  then?: (schema: T) => ISchema<any>;
+  otherwise?: (schema: T) => ISchema<any>;
 };
 
 export type ResolveOptions<TContext = any> = {
@@ -23,17 +20,13 @@ export type ResolveOptions<TContext = any> = {
   context?: TContext;
 };
 
-class Condition<
-  TIn extends ISchema<any, any> = ISchema<any, any>,
-  TOut extends ISchema<any, any> = TIn,
-> {
-  fn: ConditionBuilder<TIn, TOut>;
-
-  static fromOptions<
-    TIn extends ISchema<any, any>,
-    TThen extends ISchema<any, any>,
-    TOtherwise extends ISchema<any, any>,
-  >(refs: Reference[], config: ConditionConfig<TIn, TThen, TOtherwise>) {
+class Condition<TIn extends ISchema<any, any> = ISchema<any, any>> {
+  fn: ConditionBuilder<TIn>;
+
+  static fromOptions<TIn extends ISchema<any, any>>(
+    refs: Reference[],
+    config: ConditionConfig<TIn>,
+  ) {
     if (!config.then && !config.otherwise)
       throw new TypeError(
         'either `then:` or `otherwise:` is required for `when()` conditions',
@@ -46,19 +39,16 @@ class Condition<
         ? is
         : (...values: any[]) => values.every((value) => value === is);
 
-    return new Condition<TIn, TThen | TOtherwise>(
-      refs,
-      (values, schema: any) => {
-        let branch = check(...values) ? then : otherwise;
+    return new Condition<TIn>(refs, (values, schema: any) => {
+      let branch = check(...values) ? then : otherwise;
 
-        return branch?.(schema) ?? schema;
-      },
-    );
+      return branch?.(schema) ?? schema;
+    });
   }
 
   constructor(
     public refs: readonly Reference[],
-    builder: ConditionBuilder<TIn, TOut>,
+    builder: ConditionBuilder<TIn>,
   ) {
     this.refs = refs;
     this.fn = builder;
diff --git a/src/index.ts b/src/index.ts
index 8300d83..b6c0ef6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -16,6 +16,7 @@ import ValidationError from './ValidationError';
 import reach, { getIn } from './util/reach';
 import isSchema from './util/isSchema';
 import setLocale, { LocaleObject } from './setLocale';
+import defaultLocale from './locale';
 import Schema, {
   AnySchema,
   CastOptions as BaseCastOptions,
@@ -28,12 +29,12 @@ import Schema, {
 } from './schema';
 import type { InferType, ISchema, Message, ValidateOptions } from './types';
 
-function addMethod<T extends AnySchema>(
+function addMethod<T extends ISchema<any>>(
   schemaType: (...arg: any[]) => T,
   name: string,
   fn: (this: T, ...args: any[]) => T,
 ): void;
-function addMethod<T extends new (...args: any) => AnySchema>(
+function addMethod<T extends new (...args: any) => ISchema<any>>(
   schemaType: T,
   name: string,
   fn: (this: InstanceType<T>, ...args: any[]) => InstanceType<T>,
@@ -90,6 +91,7 @@ export {
   isSchema,
   addMethod,
   setLocale,
+  defaultLocale,
   ValidationError,
 };
 
diff --git a/src/schema.ts b/src/schema.ts
index 1e30628..a1f46dc 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -763,29 +763,13 @@ export default abstract class Schema<
     return next;
   }
 
-  when<U extends ISchema<any> = this>(builder: ConditionBuilder<this, U>): U;
-  when<U extends ISchema<any> = this>(
-    keys: string | string[],
-    builder: ConditionBuilder<this, U>,
-  ): U;
-  when<
-    UThen extends ISchema<any> = this,
-    UOtherwise extends ISchema<any> = this,
-  >(options: ConditionConfig<this, UThen, UOtherwise>): UThen | UOtherwise;
-  when<
-    UThen extends ISchema<any> = this,
-    UOtherwise extends ISchema<any> = this,
-  >(
-    keys: string | string[],
-    options: ConditionConfig<this, UThen, UOtherwise>,
-  ): UThen | UOtherwise;
+  when(builder: ConditionBuilder<this>): this;
+  when(keys: string | string[], builder: ConditionBuilder<this>): this;
+  when(options: ConditionConfig<this>): this;
+  when(keys: string | string[], options: ConditionConfig<this>): this;
   when(
-    keys:
-      | string
-      | string[]
-      | ConditionBuilder<this, any>
-      | ConditionConfig<this, any, any>,
-    options?: ConditionBuilder<this, any> | ConditionConfig<this, any, any>,
+    keys: string | string[] | ConditionBuilder<this> | ConditionConfig<this>,
+    options?: ConditionBuilder<this> | ConditionConfig<this>,
   ) {
     if (!Array.isArray(keys) && typeof keys !== 'string') {
       options = keys;


Test patch:
diff --git a/test/types/types.ts b/test/types/types.ts
index 2949d44..1bbf082 100644
--- a/test/types/types.ts
+++ b/test/types/types.ts
@@ -9,6 +9,7 @@ import {
   mixed,
   bool,
   reach,
+  addMethod,
 } from '../../src';
 import { create as tuple } from '../../src/tuple';
 import { create as lazy } from '../../src/Lazy';
@@ -969,36 +970,38 @@ Object: {
   }
 }
 
-Conditions: {
-  // $ExpectType NumberSchema<number | undefined, AnyObject, undefined, ""> | StringSchema<string, AnyObject, undefined, "">
-  string().when('foo', ([foo], schema) => (foo ? schema.required() : number()));
-
-  // $ExpectType StringSchema<string | undefined, AnyObject, undefined, "">
-  string().when('foo', ([foo], schema) => (foo ? schema.required() : schema));
-
-  // $ExpectType NumberSchema<number | undefined, AnyObject, undefined, ""> | StringSchema<string, AnyObject, undefined, "">
-  string().when('foo', {
-    is: true,
-    then: () => number(),
-    otherwise: (s) => s.required(),
-  });
-
-  const result = object({
-    foo: bool().defined(),
-    polyField: mixed<string>().when('foo', {
-      is: true,
-      then: () => number(),
-      otherwise: (s) => s.required(),
-    }),
-  }).cast({ foo: true, polyField: '1' });
-
-  // $ExpectType { polyField?: string | number | undefined; foo: boolean; }
-  result;
-
-  mixed()
-    .when('foo', ([foo]) => (foo ? string() : number()))
-    .min(1);
-}
+// Conditions: {
+//   // $ExpectType NumberSchema<number | undefined, AnyObject, undefined, ""> | StringSchema<string, AnyObject, undefined, "">
+//   string().when('foo', ([foo], schema) => (foo ? schema.required() : number()));
+
+//   // $ExpectType StringSchema<string | undefined, AnyObject, undefined, "">
+//   string()
+//     .when('foo', ([foo], schema) => (foo ? schema.required() : schema))
+//     .when('foo', ([foo], schema) => (foo ? schema.required() : schema));
+
+//   // $ExpectType NumberSchema<number | undefined, AnyObject, undefined, ""> | StringSchema<string, AnyObject, undefined, "">
+//   string().when('foo', {
+//     is: true,
+//     then: () => number(),
+//     otherwise: (s) => s.required(),
+//   });
+
+//   const result = object({
+//     foo: bool().defined(),
+//     polyField: mixed<string>().when('foo', {
+//       is: true,
+//       then: () => number(),
+//       otherwise: (s) => s.required(),
+//     }),
+//   }).cast({ foo: true, polyField: '1' });
+
+//   // $ExpectType { polyField?: string | number | undefined; foo: boolean; }
+//   result;
+
+//   mixed()
+//     .when('foo', ([foo]) => (foo ? string() : number()))
+//     .min(1);
+// }
 
 TypeAssigning: {
   const _schema: ObjectSchema<{
@@ -1036,3 +1039,9 @@ reach: {
   // $ExpectType Reference<"foo"> | ISchema<"foo", AnyObject, any, any>
   const _3 = reach(obj, 'ref');
 }
+
+addMethod: {
+  addMethod(string, 'foo', function () {
+    return this.clone();
+  });
+}