Closed AlbertMarashi closed 3 weeks ago
even if this is kinda expected behavior, I think there should at least be an error thrown and/or a type error
@AlbertMarashi Hi, sorry for the delay.
Yeah, this looks like a bug. Unfortunately I don't think I will be able to resolve it on this 0.33.x revision as the functionality for Composite is essentially frozen on 0.33.x until such time as I'm able to get a better solution up and running (which is being worked on, but is taking some time)
I would like to deprecate Composite as this type was provided as a backwards compatible version of Intersect on prior versions. The Composite was the previous Intersect in those older versions, but has progressively been updated (mostly inference related updates) but unfortunately the complexity mapping all permutations of Composite hasn't worked out very well as TypeBox moved more towards a computed type inference infrastructure.
The future solution is to implement something along the following lines, where rather than using Composite, you would construct types using Intersect (as you are in your workaround) then call Simplify<T>
to evaluate the schematics into a normative form. Something like the following.
type Simplify<T> = { [K in keyof T]: T[K] } & {}
// TypeScript
type T = (
{ x: number } |
{ y: number }
) & { z: number }
type E = Simplify<T> // type E = {
// x: number;
// z: number;
// } | {
// y: number;
// z: number;
// }
// TypeBox (Future)
const T = Type.Intersect([
Type.Union([
Type.Object({ x: Type.Number() }),
Type.Object({ y: Type.Number() })
]),
Type.Object({ z: Type.Number() })
])
const E = Type.Simplify(T) // const E: TUnion<[
// TObject<{
// x: TNumber,
// z: TNumber,
// }>,
// TObject<{
// y: TNumber,
// z: TNumber,
// }>,
// ]>
It's a little bit difficult to see in your example with the various generics, but at the core of the problem is getting the type evaluation above implemented correctly in the type system (which is an extremely difficult type level challenge which also requires implementing at runtime on schematics) ... but correctly implementing it would effectively solve this issue. This functionality around type evaluation should land in the next minor revision (0.34.x later this year or early next), but until then, additional work on Composite is effectively locked down.
Will close off this issue for now, as I can't action anything on Composite today (other than small fixes). Happy to field any questions you may have around this, feel free to ping on this thread. Again, sorry for the delay.
Cheers! S
@sinclairzx81 I hope you don't plan to remove Composite, as I view it as a highly valuable type which has different semantics to allOf
@AlbertMarashi Hi,
@sinclairzx81 I hope you don't plan to remove Composite, as I view it as a highly valuable type which has different semantics to allOf
I don't think Composite will disappear entirely (but it does have an uncertain future). Implementations will be encouraged to adopt the following which has the same effect as Composite.
// Current
const T = Type.Composite([
Type.Object({ x: Type.Number() }),
Type.Object({ y: Type.Number() }),
])
// Planned
const T = Type.Evaluate(Type.Intersect([
Type.Object({ x: Type.Number() }),
Type.Object({ y: Type.Number() }),
])])
Implementations can then replicate Composite in the following way
function Composite<T extends TSchema[]>(types: [...T]) {
return Type.Evaluate(Type.Intersect([...types])])
}
The planned Evaluate type can be thought of as a generalized Composite that aims to evaluate a type into it's most normalized form. Unlike Composite, Evaluate will be better equipped to handle Union and Intersect type expressions, and is meant to return simplified schematics (so would evaluate a allOf into an Object if the evaluation is possible)
This functionality probably won't land until 2025.
Hope this brings some insight Cheers! S
I ran into an odd behavior which I think might be a bug?
The workaround I used was: