Skip to content

Latest commit

 

History

History
1076 lines (776 loc) · 29.9 KB

js_of_ocaml.md

File metadata and controls

1076 lines (776 loc) · 29.9 KB

ts2ocaml for js_of_ocaml

Generates binding for js_of_ocaml.

Overview

ts2ocaml is a powerful tool, but there are so many options and also some caverts.

Therefore, we first provide a walkthrough to use this tool for your project.

The documentation for the ts2ocaml command and its options comes after the walkthrough, starting with the Usage setion.

Requirements

ts2ocaml for js_of_ocaml generates .mli files, which should then be processed with LexiFi/gen_js_api.

You should use gen_js_api version 1.0.9 or higher, as ts2ocaml uses the latest features of gen_js_api.

Adding the standard library

Any bindings to JS packages, not limited to the ones generated by ts2ocaml, need a standard library to compile and run, which mainly consists of the bindings to JS and DOM APIs.

Actually, ts2ocaml is so powerful that it is capable of generating the standard library for itself.

However, the resulting code is rather big (~20MB in .ml, ~40MB in .cma), so letting users to generate it themselves and add it to their project is not really a good option. Most users would want to use an OPAM package instead.

Also, we understand not everyone wants to install such a big library, especially when they already have their preferred bindings for JS and DOM APIs. Such users would only want a minimal standard library.

To fulfill both needs, we've made two ways to add the standard library.

Using ts2ocaml-jsoo-stdlib package

This package contains the full bindings for JS, DOM, and Web Worker API, generated with the full preset.

ts2ocaml-jsoo-stdlib is currently not in OPAM repository.

To install it to your OPAM switch, we recommend you to use opam pin.

The standard library for ts2ocaml version X.Y.Z is available as the jsoo-stdlib-vX.Y.Z tag in this repository. Check the version of ts2ocaml with ts2ocaml --version and do the following:

opam pin add ts2ocaml-jsoo-stdlib https://github.com/ocsigen/ts2ocaml.git#jsoo-stdlib-vX.Y.Z

You can take a look at the contents of ts2ocaml-jsoo-stdlib on https://github.com/ocsigen/ts2ocaml/tree/jsoo-stdlib.

ts2ocaml jsoo --create-minimal-stdlib generates a minimal standard library for ts2ocaml.

It only contains the following definitions:

  • -'tags intf type, which is used for tag-based subtyping.
  • TypeScript-specific primitive types, such as any, never, unknown, etc.
  • Utility types for handling TypeScript's union types and intersection types.

You can safely add it to your project, and even modify it for your needs.

Note that you have to modify the bindings generated by ts2ocaml to make it work with the minimal standard library.

  • Remove the open Ts2ocaml statements and then add open Ts2ocaml_min (or your own name if you renamed it).
  • All the JS, DOM, and Web Worker types are left unknown. You have to replace them with the types from your binding by hand.
    • We recommend using --preset=minimal when generating bindings, which disables all the features irrelevant to the bindings not from ts2ocaml.

Choosing a preset

ts2ocaml has many options, so there is an option --preset to set multiple options at once which is commonly used together.

  • --preset=minimal
    • A preset to minimize the output.
    • Intended for library authors, who will modify the output and build a binding library upon it.
      • It generates the simplest binding.
      • However, it lacks subtyping and it will not compile if the package depends on another package.
  • --preset=safe
    • A preset to generate a code which just compiles and works.
    • Suited for generating bindings for relatively small packages, which involve less inheritance and slightly depend on other packages.
      • e.g. yargs, which has a minimal dependency and does not make use of extends so much.
  • --preset=full
    • A preset to generate a code with more type safety and more support for package dependency.
    • Suited for generating bindings for large packages, which have many extends and/or heavily depend on another package.
      • e.g. React component packages, which almost certainly inherits many interfaces from React.

--preset doesn't override options you explicitly set. See --preset for the options which will be set by each preset.

Hint: if a package foo depends only on bar and bar depends on many other packages, it's safe to use --preset=safe to foo and --preset=full to bar, but not vice versa.

Generating and using the bindings

Once you figure out which preset (and some additional options if any) to use, you are now ready to run ts2ocaml.

ts2ocaml jsoo --preset full --output-dir src node_modules/typescript/lib/typescript.d.ts

A binding (typescript.mli in this example) and a JS stub file stub.js will be generated in the src directory.

Modify your dune file to include them in your project.

  • Use (js_of_ocaml (javascript_files stub.js)) to include the stub JS to the executable.
  • Add a rule to run gen_js_api on the generated .mli file.

The resulting dune will look like below:

(executable
  (name your_app)
  (libraries gen_js_api ts2ocaml-jsoo-stdlib)
  (link_flags -no-check-prims)
  (preprocess (pps gen_js_api.ppx))
  (modes js)
  (js_of_ocaml
    (javascript_files stub.js)))

(rule
  (targets typescript.ml)
  (deps typescript.mli)
  (action (run %{bin:gen_js_api} %{deps})))

(rule
  (targets main.js)
  (deps main.bc.js)
  (action (copy %{deps} %{targets})))

(alias
  (name DEFAULT)
  (deps main.js main.html))

Note: if you are using ts2ocaml-jsoo-stdlib, don't forget to set --profile=release when running dune! There is no dead code elimination without that option, so it would result in a 40MB+ JavaScript executable.

Each binding has an Export module which corresponds to the package's default export (export default .. or export = .. in TypeScript).

Define a module alias to "import" the package:

module TypeScript = Typescript.Export

let () =
  Printf.printf "typescript version: %s\n" (TypeScript.version ())
;;

See also the documentation of:

Handling import and export

To work with multiple files and packages, ts2ocaml has some conventions around the name of the generated OCaml source codes.

  1. If not known, ts2ocaml computes the JS module name of the input .d.ts file by heuristics.
  2. ts2ocaml converts the JS module name to a OCaml module name by the followings:
    • Removes @ at the top of the module name
    • Replaces / with __
    • Replaces any other signs (such as -) to _
  3. ts2ocaml uses the OCaml module name as the output file name.

How the heuristics work

  • If the filename is equal to types or typings of package.json, then ts2ocaml will use the package name itself.
    • input: node_modules/typescript/lib/typescript.d.ts
    • package.json: "typings": "./lib/typescript.d.ts",
    • getJsModuleName: typescript
    • output file: typescript.mli
  • If the filename is present in exports of package.json, then ts2ocaml will combine the package name and the exported module name.
    • input: node_modules/@angular/common/http/http.d.ts
    • package.json: "exports": { .., "./http": { "types": "./http/http.d.ts", .. }, .. }
    • getJsModuleName: @angualr/common/http
    • output file: angular__common__http.mli
  • Otherwise, ts2ocaml uses a heuristic module name: it will combine the package name and the filename. index.d.ts is handled specially.
    • input: node_modules/cassandra-driver/lib/auth/index.d.ts
    • getJsModuleName: cassandra-driver/auth
    • output file: cassandra_driver__auth.mli
    • if package.json is not present, the package name is also inferred heuristically from the filename.

How the import statements are translated

  • import of another package from node_modules will be converted to an open statement or a module alias.
    • The OCaml module name of the imported package is computed by the step 2 of the above.
// node_modules/@types/react/index.d.ts
import * as CSS from 'csstype';
import { Interaction as SchedulerInteraction } from 'scheduler/tracing';
...
(* react.mli *)
module CSS = Csstype.Export
module SchedulerInteraction = Scheduler__tracing.Export.Interaction
...
  • import of relative path will be converted to an open statement or a module alias.
    • The OCaml module name of the imported file will also be inferred by heuristics.
// node_modules/cassandra-driver/index.d.ts
import { auth } from './lib/auth';
(* cassandra_driver.mli *)
module Auth = Cassandra_driver__auth.Export.Auth
// node_modules/cassandra-driver/lib/mapping/index.d.ts
import { Client } from '../../';
(* cassandra_driver__mapping.mli *)
module Client = Cassandra_driver.Export.Client
  • Indirect import using identifiers is not yet be supported.
import { types } from './lib/types';
import Uuid = types.Uuid; // we should be able to convert this to `module Uuid = Type.Uuid`, but not yet
  • Direct export of an external module will not be supported.
export { someFunction } from './lib/functions'; // this is VERY hard to do in OCaml!

How the export statements are translated

ts2ocaml will create a module named Export to represent the exported definitions.

  • If an export assignment export = Something is used, the Export module will be an alias to the Something module.
(* export = Something *)
module Export = Something
  • If ES6 exports export interface Foo or export { Bar } are used, the Export module will contain the exported modules.
module Export : sig
  (* export interface Foo *)
  module Foo = Foo
  (* export { Bar } *)
  module Bar = Bar
  (* export { Baz as Buzz } *)
  module Buzz = Baz
end

This is why you are advised to use the generated bindings with the following:

(* This is analogous to `import * as TypeScript from "typescript";` *)
module TypeScript = Typescript.Export

Common pitfalls

Due to the limitation of js_of_ocaml and gen_js_api, there are several pitfalls when using bindings generated by ts2ocaml.

Be careful with type variables, it can generate broken JS code!

See also: https://github.com/LexiFi/gen_js_api/blob/master/TYPES.md#type-variables

gen_js_api does not convert between OCaml values and JS values if the argument or the return type of a function is a type parameter.

So, if you use a type which has a different runtime representation in OCaml world, the ML-to/from-JS conversion won't happen, resulting in a runtime error.

Here we list the "safe" types, of which runtime representation is the same both in JS world and in OCaml world:

  • int and float.
  • Primitive types in the Ts2ocaml_min module, such as any, never, bigint, or symbol.
  • (Non-discriminated) union and intersection types, in the form [ ... ] Union.t or [ ... ] Intersection.t.
    • These modules has get_n' or inject_n' functions, which takes the conversion function of the contained type.
  • Union of primitive types, in the form [ ... ] Primitive.t.
    • These modules has inject' and classify', which takes the conversion function of the 'other type.
  • Bindings to classes and interfaces.
    • It is an alias to Ojs.t (or Ts2ocaml.intf), which means the runtime representation is the same both in JS and in OCaml, and so no conversion is required.

Here we list the common "unsafe" types, which can cause problem when used as type parameter.

  • string and bool.
    • Use Ojs.string_to_js and Ojs.string_of_js to explicitly convert these values.
    • You can also use String.of_ml and String.to_ml if you are using ts2ocaml-jsoo-stdlib.
    • The same applies to bool.
  • 'a list.
    • Use Ojs.list_to_js and Ojs.list_of_js to explicitly convert these values.
  • Bindings to enums.
    • Use the t_to_js and t_of_js functions in the binding module.
  • Bindings to discriminated unions, which is represented as a polymorphic variant with [@js.union on_field "..."].
    • Use the t_to_js and t_of_js functions in the binding module.

Also, with the --functor option (which is enabled by default), ts2ocaml will generate a functor for each generic classes/interfaces.

You can use the generated functor to "erase" the type parameters so that the conversion is enforced.

Usage

$ ts2ocaml jsoo [options] <inputs..>

See also the common options.

General Options

--preset

Specify the preset to use.

  • --preset=minimal
    • It sets --simplify=all and --rec-module=optimized.
  • --preset=safe
    • It sets --subtyping=cast-function.
    • It also sets all the options --preset=minimal sets.
  • --preset=full
    • It sets --inherit-with-tags=full and --subtyping=tag.
    • It also sets all the options --preset=safe sets.

--create-minimal-stdlib

Create ts2ocaml_min.mli, which is the minimal standard library for ts2ocaml without any bindings for

  • JS standard API (Ts2ocaml_es),
  • DOM API (Ts2ocaml_dom), or
  • web worker API (Ts2ocaml_webworker).

When this option is used, ts2ocaml requires no input files. So most of the other options will be ignored.

Output Options

-o, --output-dir

The directory to place the generated bindings. If not set, it will be the current directory.

-s, --stub-file

The name of the JS stub file to import/require JS modules. If not set, it will be stub.js.

If the stub file already exists, ts2ocaml will append new entries.

The resulting stub.js will look like:

joo_global_object["React"] = require('react')
joo_global_object["ReactModal"] = require('react-modal')
joo_global_object["prop-types"] = require('prop-types') /* need Babel */
joo_global_object["scheduler/tracing"] = require('scheduler/tracing') /* need Babel */
joo_global_object["ts"] = require('typescript')
joo_global_object["yargs"] = require('yargs')
joo_global_object["yargsParser"] = require('yargs-parser')

The stub file uses require for importing packages. /* need Babel */ indicates the referenced package is actually a ES6 module and so it needs to be converted by Babel.

Typer Options

--int, --number-as-int

Treat number types as int. If not set, float will be used.

--subtyping

See also the detailed docs about modeling TypeScript's subtyping in OCaml.

Turn on subtyping features.

You can use --subtyping=foo,bar to turn on multiple features. Also, use --subtyping=off to explicitly disable subtyping features.

Feature: tag

Use -'tags intf for class and interface types, which simulates nominal subtyping by putting to 'tags the class names as a polymorphic variant.

For example, assume we have the following input:

interface A { methA(a: number): number; }

interface B extends A { methB(a: number, b: number): number; }

interface C extends B { methC(a: number, b: number, c: number): number; }

When this feature is used, the resulting binding will look like:

module A : sig
  type t = [ `A ] intf
  val methA: t -> a:float -> float
  val cast_from: [> `A] intf -> t
end

module B : sig
  type t = [ `B | `A ] intf
  val methB: t -> a:float -> b:float -> float
  val cast_from: [> `B] intf -> t
end

module C : sig
  type t = [ `C | `B | `A ] intf
  val methC: t -> a:float -> b:float -> c:float -> float
  val cast_from: [> `C] intf -> t
end

So if we have a val x : C.t, you can directly cast it to A.t by writing x :> A.t.

Alternatively, you can also write A.cast_from x, which uses a generic cast function cast_from.

let c : C.t = ...

let a1 : A.t = c :> A.t
let a2 : A.t = A.cast_from c

Feature: cast-function

Add cast functions to cast types around.

For example, assume we have the following input:

interface A { methA(a: number): number; }

interface B extends A { methB(a: number, b: number): number; }

interface C extends B { methC(a: number, b: number, c: number): number; }

When this feature is used, the resulting binding will look like:

module A : sig
  type t
  val methA: t -> a:float -> float
end

module B : sig
  type t
  val methB: t -> a:float -> b:float -> float
  val cast_to_A: t -> A.t
end

module C : sig
  type t
  val methC: t -> a:float -> b:float -> c:float -> float
  val cast_to_B: t -> B.t
end

So if we have a val x : C.t, you can cast it to A.t by writing B.cast_to_A (C.cast_to_B x).

let c : C.t = ...

let a : A.t = x |> C.cast_to_B |> B.cast_to_A

This feature is less powerful than tag, but it has some use cases tag doesn't cover.

--inherit-with-tags

Note: This options requires --subtyping=tag. If the tag feature is not specified, it will fail with an error.

Use TypeName.tags type names to inherit types from other packages.

  • --inherit-with-tags=full (default)
    • It generates tags types in the module, and tries to use tags type to inherit a type if it is unknown (e.g. from another package).
  • --inherit-with-tags=provide
    • It only generates tags types in the module.
  • --inherit-with-tags=consume
    • It only tries to use tags type if the inherited type is unknown.
  • --inherit-with-tags=off
    • It disables any usage of tags types.

For example, assume we have node_modules/foo/index.d.ts and node_modules/bar/index.d.ts as the following:

// foo/index.d.ts

declare namespace foo {
  interface A { ... }
}

export = foo;
// bar/index.d.ts

import * as Foo from 'foo';

declare namespace bar {
  interface B extends A { ... }
}

export = bar;

Then the outputs will look like depending on the option you set:

(* Foo.mli *)

module Foo : sig
  module A : sig
    type t = [`A] intf

    (* this will be generated if `full` or `provide` is set *)
    type tags =  [`A]

    (* this will be generated regardless of the option *)
    val cast_from: [> `A] intf -> t

    ...
  end
end

(* export = foo; *)
module Export = Foo
(* Bar.mli *)

(* import * as Foo from "foo"; *)
module Foo = Foo.Export

module Bar : sig
  module B : sig
    (* if `full` or `consume` is set, this will be generated *)
    type t = [`B | Foo.A.tags] intf
    (* otherwise, this will be generated *)
    type t = [`B] intf

    (* if `full` is set, this will be generated *)
    type tags = [`B | Foo.A.tags]
    (* else if `provide` is set, this will be generated *)
    type tags = [`B]

    (* this will be generated regardless of the option *)
    val cast_from: [> `B] intf -> t

    ...
  end
end

(* export = bar; *)
module Export = Bar

If provide or full is used for foo.d.ts and consume or full is used for bar.d.ts, you will be able to safely cast B.t to A.t, although they come from different packages.

module Foo = Foo.Export
module Bar = Bar.Export

let bar : Bar.B.t = ...

let foo1 : Foo.A.t = bar :> Foo.A.t
let foo2 : Foo.A.t = Foo.A.cast_from bar

Otherwise, you can't safely cast B.t to A.t. To do it, you will have to

  • set --subtyping=cast-function to obtain val cast_to_A: t -> A.t, or
  • manually add `A to the definition of B.t (and B.tags if you choose to provide).

Code Generator Options

--rec-module

Use recursive modules to simplify the output. Can impact the compilation time.

Assume we have the following input:

interface A {
  readonly b: B;
}

interface B {
  readonly a: A;
}

interface C {
  readonly a: A;
  readonly b: B;
}
module rec A : sig
  type t = ...
  val get_b: unit -> B.t
end

and B : sig
  type t = ...
  val get_a: unit -> A.t
end

module C : sig
  type t = ...
  val get_a: unit -> A.t
  val get_b: unit -> B.t
end
  • --rec-module=naive
    • It simply makes every module recursive.
    • Not very recommended because hundreds of recursive modules would torture OCaml compiler.
module rec A : sig
  type t = ...
  val get_b: unit -> B.t
end

and B : sig
  type t = ...
  val get_a: unit -> A.t
end

and C : sig
  type t = ...
  val get_a: unit -> A.t
  val get_b: unit -> B.t
end
  • --rec-module=off (default)
    • It generates types and the corresponding modules (which contain methods and fields for the type) separately.
type _A = ...
type _B = ...
type _C = ...

module A : sig
  type t = _A
  val get_b: unit -> _B
end

module B : sig
  type t = _B
  val get_a: unit -> _A
end

module C : sig
  type t = _C
  val get_a: unit -> _A
  val get_b: unit -> _B
end

--simplify

Turn on simplification features.

You can use --simplify=foo,bar to turn on multiple features. Also, --simplify=all enables all the features and --simplify=off explicitly disables simplification features.

Feature: immediate-instance

Simplifies a value definition of an interface type with the same name (case sensitive) to a module.

Assume we have the following input:

interface Foo = {
  someMethod(value: number): void;
}

declare var Foo: Foo;

If this option is set, the output will be:

module [@js.scope "Foo"] Foo : sig
  val someMethod: float -> unit [@@js.global "someMethod"]
end

(* usage *)
let _ = Foo.someMethod 42.0

Otherwise, the output will be:

module Foo : sig
  type t = ...

  val someMethod: t -> float -> unit [@@js.call "someMethod"]
end

val foo: unit -> Foo.t [@@js.global "Foo"]

(* usage *)
let _ = Foo.someMethod (foo ()) 42.0

A notable example is the Math object in ES5 (https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts).

Feature: immediate-constructor

Simplifies so-called constructor pattern.

Assume we have the following input:

interface Foo = {
  someMethod(value: number): void;
}

interface FooConstructor {
  new(name: string) : Foo;

  anotherMethod(): number;
}

declare var Foo: FooConstructor;

If this option is set, the output will be:

module [@js.scope "Foo"] Foo : sig
  type t = ...
  val someMethod: t -> float -> unit [@@js.call "someMethod"]

  val create: string -> t [@@js.create]
  val anotherMethod: unit -> float [@@js.global "anotherMethod"]
end

(* usage *)
let _ =
  let foo = Foo.create "foo" in
  let num = Foo.anotherMethod () in
  Foo.someMethod foo num

Otherwise, the output will be:

module Foo : sig
  type t = ...

  val someMethod: t -> float -> unit [@js.call "someMethod"]
end

module FooConstructor : sig
  type t = ...

  val create: t -> name:string -> Foo.t [@@js.apply_newable]
  val anotherMethod: t -> unit -> float [@@js.call "anotherMethod"]
end

val foo: unit -> FooConstructor [@@js.global "Foo"]

(* usage *)
let _ =
  let foo = FooConstructor.create (foo ()) "foo" in
  let num = FooConstructor.anotherMethod (foo ()) () in
  Foo.someMethod foo num

A notable example is the ArrayConstructor type in ES5 (https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts).

Feature: anonymous-interface-value

Simplifies a value definition of an anonymous interface type to a module.

Assume we have the following input:

declare var Foo: {
  someMethod(value: number): void;
};

If this option is set, the output will be:

module [@js.scope "Foo"] Foo : sig
  val someMethod: float -> unit [@@js.global "someMethod"]
end

(* usage *)
let _ = Foo.someMethod 42.0

Otherwise, the output will be:

module AnonymousInterfaceN : sig
  type t = private Ojs.t

  val someMethod: t -> float -> unit [@@js.call "someMethod"]
end

val foo: unit -> AnonymousInterfaceN.t [@@js.global "Foo"]

(* usage *)
let _ = AnonymousInterfaceN.someMethod (foo ()) 42.0

A notable example is the Document variable in DOM (https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts).

Feature: named-interface-value

Note: immediate-instance and immediate-constructor will override this feature if the name of the value definition is the same as the corresponding interface.

Defines additional module with a suffix Static for a value definition of some interface type.

Assume we have the following input:

interface Foo = {
  someMethod(value: number): void;
}

declare var foo: Foo;

If this option is set, the output will be:

module Foo : sig
  type t = ...

  val someMethod: t -> float -> unit [@@js.call "someMethod"]
end

module [@js.scope "foo"] FooStatic : sig
  val someMethod: float -> unit [@@js.global "someMethod"]
end

val foo: unit -> Foo.t [@@js.global "Foo"]

(* usage *)
let _ = FooStatic.someMethod 42.0
let _ = Foo.someMethod (foo ()) 42.0 (* "instance call" is also available *)

Otherwise, the output will be:

module Foo : sig
  type t = ...

  val someMethod: t -> float -> unit [@@js.call "someMethod"]
end

val foo: unit -> Foo.t [@@js.global "Foo"]

(* usage *)
let _ = Foo.someMethod (foo ()) 42.0

A notable example is the document variable in DOM (https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts).

--readable-names

Try to use more readable names instead of AnonymousInterfaceN.

  • If the anonymous interface is an argument of a function, the name of the argument will be used.
declare function foo(person: { name: string; age: number }) : void;
module PersonN : sig
  type t
  val get_name: t -> string
  val set_name: t -> string -> unit
  val get_age:  t -> float
  val set_age:  t -> float -> unit

  val create: name:string -> age:float -> unit -> t
end

val foo: person:PersonN.t -> unit
  • If the anonymous interface is the type of a field or the return type of a function, the name of the field/function will be used.
declare const foo: { name: string };

declare function bar(age: number) : { age: number };
module FooN : sig
  type t
  val get_name: t -> string
  ...
end

val get_foo: unit -> FooN.t

module BarN : sig
  type t
  val get_age: t -> float
  ...
end

val bar: float -> BarN.t

--functor

Emit functor for generic classes and interfaces.

Assume we have the following input:

interface Foo<T> {
  ...
}

--functor=off: no functor is generated.

--functor=on: generates a functor in addition to the original Foo module (default).

  • If --rec-module=off, the functor will be generated as Foo.Make:
module Foo : sig
  type 'T t
  ...
  module Make (T: Ojs.T) : sig
    type 'T parent = 'T t
    type t = T.t parent
    ...
  end
end
  • Otherwise, the functor will be generated as Foo_Make:
module Foo : sig
  type 'T t
  ...
end

module Foo_Make (T: Ojs.T) : sig
  type 'T parent = 'T Foo.t
  type t = T.t parent
  ...
end

--functor=force-inline: always generates a functor as Foo.Make, even when recursive modules are used.

module Foo : sig
  type 'T t
  ...
  module Make (T: Ojs.T) : sig
    type 'T parent = 'T t
    type t = T.t parent
    ...
  end
end

Warning: --functor=force-inline can produce a code which does not compile, since functors are considered "unsafe" in mutually-recursive modules. For example, the following doesn't compile:

module rec Foo: sig
  type 'T t
  val meth: 'T t -> 'T Bar.t -> unit

  module Make (T: Ojs.T) : sig
    type t
    val meth: t -> T.t Bar.t -> unit
  end
end

and Bar: sig
  type 'T t
  val meth: 'T t -> 'T Foo.t -> unit

  module Make (T: Ojs.T) : sig
    type t
    val meth: t -> T.t Foo.t -> unit
  end
end

Deprecated Options in v2

--safe-arity (deprecated)

Use TypeName.t_n type names to safely use overloaded types from other packages.

From v2, ts2ocaml uses TypeScript API to check if a generic type has optional type parameters, and generates the minimum type-safe output. This option is deprecated because the current behavior is the optimal.

For example, assume we have node_modules/foo/index.d.ts and node_modules/bar/index.d.ts as the following:

// foo/index.d.ts

declare namespace foo {
  interface A<T> { ... }

  interface B<T = any> { ... }
}

export = foo;
// bar/index.d.ts

import * as Foo from 'foo';

declare function useA(a: Foo.A<T>) : void;

declare function useB(b: Foo.B<T>) : void;

declare function useBDefault(b: Foo.B) : void;

Then the outputs will look like this:

(* Foo.mli *)

module Foo : sig
  module A : sig
    type 'T t = [`A of 'T] intf

    ...
  end

  module B : sig
    type 'T t = [`B of 'T] intf

    type t_0 = any t

    ...
  end
end

(* export = foo; *)
module Export = Foo
(* Bar.mli *)

(* import * as Foo from "foo"; *)
module Foo = Foo.Export

val useA: 'T Foo.A.t -> unit
val useB: 'T Foo.B.t -> unit
val useBDefault: Foo.B.t_0 -> unit