I think I’m missing something very simple here. I’m trying to export some types from rescript to typescript.
Repo here: https://github.com/tom-sherman/rescript-xstate You just have to install the dependencies, build the rescript, then node src/XStateFunctor.bs.js
I have some rescript like this:
@genType
type rec stateNode<'state, 'event> =
| State({name: 'state, on: array<('event, 'state)>})
| Compound({name: 'state, children: array<stateNode<'state, 'event>>, initial: 'state})
| Final({name: 'state})
@genType
type stateList<'state, 'event> = array<stateNode<'state, 'event>>
Which generates the following typescript:
export type stateNode<state,event> =
{ tag: "State"; value: { readonly name: state; readonly on: Array<[event, state]> } }
| { tag: "Compound"; value: {
readonly name: state;
readonly children: stateNode<state,event>[];
readonly initial: state
} }
| { tag: "Final"; value: { readonly name: state } };
export type stateList<state,event> = stateNode<state,event>[];
However a stateNode
isn’t of that shape when I pass it into JS-land, here’s an example of one stateNode object:
{ TAG: 0, name: 'idle', on: [ [ 'FETCH', 'loading' ] ] }
I think this is an issue with GenType. For all boxed variants, it adds a field based on whether it’s a polymorphic variant or not. See: https://github.com/rescript-association/genType/blob/master/src/EmitType.ml#L260-L263 and https://github.com/rescript-association/genType/blob/master/src/Runtime.ml#L78-L82 .
It should be noted that the code that ReScript generates differs based on whether inline records are used or not, see https://github.com/rescript-association/genType/blob/master/src/EmitType.ml#L260-L263 .
Given how dependent this is on ReScript’s code generation, I think merging GenType into ReScript itself might make sense.
1 Like
Thanks, I’ve raised it here: https://github.com/rescript-association/genType/issues/585
I’m sidestepping it for now by using polymorphic variants.
spyder
December 24, 2021, 6:23am
4
I believe this is because @genType
needs to be added to both the type and the functions using that type. Wrapper functions are then generated to perform the conversion and map the ReScript implementation to the TypeScript type.
e.g. this code:
@genType
type variantWithPayloads =
| @genType.as("ARenamed") A
| B(int)
| C(int, int)
| D((int, int))
| E(int, string, int)
@genType
let testVariantWithPayloads = (x: variantWithPayloads) => x
Generates:
export type variantWithPayloads =
"ARenamed"
| {| tag: "B", value: number |}
| {| tag: "C", value: [number, number] |}
| {| tag: "D", value: [number, number] |}
| {| tag: "E", value: [number, string, number] |};
export const testVariantWithPayloads: (variantWithPayloads) => variantWithPayloads = function (Arg1: $any) {
const result = VariantsWithPayloadBS.testVariantWithPayloads(typeof(Arg1) === 'object'
? Arg1.tag==="B"
? {TAG: 0, _0:Arg1.value}
: Arg1.tag==="C"
? {TAG: 1, _0:Arg1.value[0], _1:Arg1.value[1]}
: Arg1.tag==="D"
? {TAG: 2, _0:Arg1.value}
: {TAG: 3, _0:Arg1.value[0], _1:Arg1.value[1], _2:Arg1.value[2]}
: $$toRE13337556[Arg1]);
return typeof(result) === 'object'
? result.TAG===0
? {tag:"B", value:result._0}
: result.TAG===1
? {tag:"C", value:[result._0, result._1]}
: result.TAG===2
? {tag:"D", value:result._0}
: {tag:"E", value:[result._0, result._1, result._2]}
: $$toJS13337556[result]
};
1 Like
Hi,
Yeah the representation of variants in the generated javascript and the typescript are not same. But this would not break the app and is type safe.
So genType generated bindings are in such a way that it accepts the typescript type representation
and then converts them to ReScript representation
before passing to rescript functions.
For the following ReScript code
@genType
type rec stateNode<'state, 'event> =
| State({name: 'state, on: array<('event, 'state)>})
| Compound({name: 'state, children: array<stateNode<'state, 'event>>, initial: 'state})
| Final({name: 'state})
@genType
let logStateNode = (a: stateNode<string, string>) => Js.log(a)
The generated typescript bindings are
/* TypeScript file generated from Test2.res by genType. */
/* eslint-disable import/first */
// @ts-ignore: Implicit any on import
import * as Test2BS__Es6Import from './Test2.bs';
const Test2BS: any = Test2BS__Es6Import;
// tslint:disable-next-line:interface-over-type-literal
export type stateNode<state,event> =
{ tag: "State"; value: { readonly name: state; readonly on: Array<[event, state]> } }
| { tag: "Compound"; value: {
readonly name: state;
readonly children: stateNode<state,event>[];
readonly initial: state
} }
| { tag: "Final"; value: { readonly name: state } };
export const logStateNode: (a:stateNode<string,string>) => void = function (Arg1: any) {
const result =
/* WARNING: circular type stateNode. Only shallow converter applied. */
Test2BS.logStateNode(Arg1.tag==="State"
? Object.assign({TAG: 0}, Arg1.value)
: Arg1.tag==="Compound"
? Object.assign({TAG: 1}, Arg1.value)
: Object.assign({TAG: 2}, Arg1.value));
return result
};