ianstormtaylor / superstruct

A simple and composable way to validate data in JavaScript (and TypeScript).
https://docs.superstructjs.org
MIT License
7.02k stars 224 forks source link

`assign()` does not work on `object()` with `optional()` field #734

Open VeryCrazyDog opened 3 years ago

VeryCrazyDog commented 3 years ago

Below is the minimal example to reproduce the error using superstruct 0.15.1 and TypeScript 4.2.3. Expect the not working potion of code to work without error but this is not the case.

import {
  assign,
  Describe,
  object,
  optional,
  string
} from 'superstruct'

interface Person {
  name: string
  description?: string
}

interface Student extends Person {
  studentId: string
}

const personNoTc = object({
  name: string(),
  description: optional(string())
})

const personTc: Describe<Person> = personNoTc

// Not working, error on `personTc`
const student1NoTc = assign(personTc, object({
  studentId: string()
}))
// Not working, error on `student1Tc`
const student1Tc: Describe<Student> = student1NoTc

// Working
const student2: Describe<Student> = assign(personNoTc, object({
  studentId: string()
}))

The following errors are observed:

Argument of type 'Describe<Person>' is not assignable to parameter of type 'Struct<{ name: string; description?: unknown; }, { name: Describe<string>; description?: Describe<string | undefined> | undefined; }>'.
  Types of property 'refiner' are incompatible.
    Type '(value: Person, context: Context) => Iterable<Failure>' is not assignable to type '(value: { name: string; description?: unknown; }, context: Context) => Iterable<Failure>'.
      Types of parameters 'value' and 'value' are incompatible.
        Type '{ name: string; description?: unknown; }' is not assignable to type 'Person'.
Type 'Struct<{ name: string; description?: unknown; studentId: string; }, { studentId: Struct<string, null>; name: Describe<string>; description?: Describe<string | undefined> | undefined; }>' is not assignable to type 'Describe<Student>'.
  Types of property 'TYPE' are incompatible.
    Type '{ name: string; description?: unknown; studentId: string; }' is not assignable to type 'Student'.
kevcao-certik commented 1 year ago

Also getting this same problem in superstruct@1.0.3 and typescript@5.1.3. The issue also seems to only occur if you use Describe:

import { optional, assign, string, Describe, type } from "superstruct";

interface Foo {
  bar?: string;
}

const Foo: Describe<Foo> = type({
  bar: optional(string()),
});

const Baz = assign(Foo, type({ qux: string() }));

Error:

Argument of type 'Describe<Foo>' is not assignable to parameter of type 'Struct<{ bar?: unknown; }, { bar?: Describe<string | undefined> | undefined; }>'.
  Types of property 'refiner' are incompatible.
    Type '(value: Foo, context: Context) => Iterable<Failure>' is not assignable to type '(value: { bar?: unknown; }, context: Context) => Iterable<Failure>'.
      Types of parameters 'value' and 'value' are incompatible.
        Type '{ bar?: unknown; }' is not assignable to type 'Foo'.

If Describe is not used, then no errors reported.

BuyMyBeard commented 2 months ago

Infer has the same problem