Make `Jsx.component` abstract
I mentioned this quite a few times already, I think we should absolutely make Jsx.component abstract.
Reasons:
First, the existing type definition as
type component<'props> = 'props => Jsx.element
is simply wrong. There are a lot of React components that do not have this shape, like class components, fragments, memoized components, etc., etc.
Second, the existing definition leads to weird errors / weird workarounds when writing bindings. Even though Jsx.component<'props> is defined as 'props => Jsx.element, the two are not equivalent when writing bindings.
You need to use Jsx.component<'props> (or @jsx.component, but that's not always what you want) and not props => Jsx.element. Otherwise you end up with things like <prim => SomeLib.Head(prim)> instead of <Head> in the JS output (latest instance of this problem in #8047).
We tried to do this before, but hit a roadblock in #6304 that basically boils down to the problem in this example:
type component<'props> // abstract React component
external component: ('props => React.element) => component<'props> = "%identity"
module Test = {
type props<'x> = {x: 'x}
let make = component(props =>
switch props.x {
| #a => React.string("A")
| #b => React.string("B")
| _ => React.string("other")
}
)
}
giving
The type of this module contains type variables that cannot be generalized:
{
type props<'x> = {x: 'x}
let make: component<props<_[> #a | #b]>>
}
This happens when the type system senses there's a mutation/side-effect,
in combination with a polymorphic value.
Using or annotating that value usually solves it.
Note that this occurs because of the lower bound in the polymorphic variant type. It does not occur when we have
type props = {x: [#a | #b | #c]}
I wonder if this is maybe a restriction we just need to live with? How often does this occur in practice? Any thoughts?
I already hit this issue today when using React.memo in conjunction with polymorphic variants, typically when using fragment refs in rescript-relay. All I do is using closed polymorphic variants in this case, it's a limit I can live with if we don't want to touch these types.
Though, I don't really think JSX components can be mutated so why don't we just change the type of components to make them covariants @cknitt?
type component<+'props> // abstract but covariant React component
external component: ('props => React.element) => component<'props> = "%identity"
module Test = {
type props<'x> = {x: 'x}
let make = component(props =>
switch props.x {
| #a => React.string("A")
| #b => React.string("B")
| _ => React.string("other")
}
)
}
Thanks for the hint @tsnobip! Would be great if the solution was that easy!
But should React.component<'props> not actually be contravariant?
As it takes 'props as input and 'props => React.element would also be contravariant?
/cc @cristianoc