runtypes
runtypes copied to clipboard
`Union` type checks successfully when all members individually do not when using `Intersect`
Hi folks. So I've noticed an issue where, given some input, a Union(A, B, C) is type checking when each of its members A, B and C all fail type checking individually.
Minimal reproduction:
import { Record, Literal, Intersect, Union, Static } from "runtypes";
const A = Record({
foo: Literal("bar"),
});
const B = Intersect(
A,
Record({
bar: Literal("foo"),
})
);
const C = Intersect(
A,
Record({
foobar: Literal("foobar"),
})
);
const ABC = Union(A, B, C);
// type ABCType =
// | {
// foo: "bar";
// }
// | ({
// foo: "bar";
// } & {
// bar: "foo";
// })
// | ({
// foo: "bar";
// } & {
// foobar: "foobar";
// });
type ABCType = Static<typeof ABC>;
// Typescript Error:
// Type '{ bar: "foo"; }' is not assignable to type '{ foo: "bar"; } | ({ foo: "bar"; } & { bar: "foo"; }) | ({ foo: "bar"; } & { foobar: "foobar"; })'.
// Type '{ bar: "foo"; }' is not assignable to type '{ foo: "bar"; } & { bar: "foo"; }'.
// Property 'foo' is missing in type '{ bar: "foo"; }' but required in type '{ foo: "bar"; }'.ts(2322)
const input: ABCType = {
bar: "foo",
};
console.log(ABC.guard(input)); // true
console.log(A.guard(input)); // false
console.log(B.guard(input)); // false
console.log(C.guard(input)); // false
Typescript correctly spots the error when assigning input to ABCType however Runtypes ABC.guard(input) does not.
If we remove the Intersect, it works as expected.
import { Record, Literal, Union, Static } from "runtypes";
const A = Record({
foo: Literal("bar"),
});
const B = Record({
foo: Literal("bar"),
bar: Literal("foo"),
});
const C = Record({
foo: Literal("bar"),
foobar: Literal("foobar"),
});
const ABC = Union(A, B, C);
// type ABCType =
// | {
// foo: "bar",
// }
// | {
// foo: "bar",
// bar: "foo",
// }
// | {
// foo: "bar",
// foobar: "foobar",
// };
type ABCType = Static<typeof ABC>;
const input = {
bar: "foo",
};
console.log(ABC.guard(input)); // false
console.log(A.guard(input)); // false
console.log(B.guard(input)); // false
console.log(C.guard(input)); // false
Sounds like there's a bug when Intersect'ing the records. Maybe I'm misusing the feature?