diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b117771204..3ad07e8f6d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -320,6 +320,10 @@ jobs:
       - name: Check for diffs in tests folder
         run: git diff --ignore-cr-at-eol --exit-code tests
 
+      - name: Run analysis / tools tests
+        if: runner.os != 'Windows' && matrix.os != 'buildjet-2vcpu-ubuntu-2204-arm'
+        run: opam exec -- make -C tests/analysis_tests test && make -C tests/tools_tests test
+
       - name: Run gentype tests
         if: runner.os != 'Windows'
         run: make -C tests/gentype_tests/typescript-react-example clean test
diff --git a/.gitignore b/.gitignore
index 5165ad1cba..45475b4fad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,3 +76,8 @@ playground/compiler.js
 
 rewatch/target/
 rewatch/rewatch
+
+tests/tools_tests/**/*.res.js
+tests/tools_tests/lib
+tests/analysis_tests*/lib
+tests/analysis_tests/**/*.bs.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d31df4e152..2985a80e16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,11 +17,12 @@
 - Introduce "Unified operators" for arithmetic operators (`+`, `-`, `*`, `/`, `mod`). https://github.com/rescript-lang/rescript-compiler/pull/7057
 - Add remainder (`%`, aka modulus) operator. https://github.com/rescript-lang/rescript-compiler/pull/7152
 
-
 #### :bug: Bug fix
+
 - Fix and clean up boolean and/or optimizations. https://github.com/rescript-lang/rescript-compiler/pull/7134 https://github.com/rescript-lang/rescript-compiler/pull/7151
 
 #### :nail_care: Polish
+
 - Improve code generation for pattern matching of untagged variants. https://github.com/rescript-lang/rescript-compiler/pull/7128
 - Improve negation handling in combination with and/or to simplify generated code (especially coming out of pattern matching). https://github.com/rescript-lang/rescript-compiler/pull/7138
 - optimize JavaScript code generation by using x == null checks and improving type-based optimizations for string/number literals. https://github.com/rescript-lang/rescript-compiler/pull/7141
@@ -30,6 +31,9 @@
 - Further improve boolean optimizations. https://github.com/rescript-lang/rescript-compiler/pull/7149
 - Simplify code generated for conditionals. https://github.com/rescript-lang/rescript-compiler/pull/7151
 
+#### :house: Internal
+
+- Move rescript-editor-analysis and rescript-tools into compiler repo. https://github.com/rescript-lang/rescript-compiler/pull/7000
 
 # 12.0.0-alpha.4
 
@@ -61,6 +65,7 @@
 - Fix genType JSX component compilation. https://github.com/rescript-lang/rescript-compiler/pull/7107
 
 #### :nail_care: Polish
+
 - Add some context to error message for unused variables. https://github.com/rescript-lang/rescript-compiler/pull/7050
 - Improve error message when passing `children` prop to a component that doesn't accept it. https://github.com/rescript-lang/rescript-compiler/pull/7044
 - Improve error messages for pattern matching on option vs non-option, and vice versa. https://github.com/rescript-lang/rescript-compiler/pull/7035
@@ -71,7 +76,6 @@
 - Provide additional context in error message when `unit` is expected. https://github.com/rescript-lang/rescript-compiler/pull/7045
 - Improve error message when passing an object where a record is expected. https://github.com/rescript-lang/rescript-compiler/pull/7101
 
-
 #### :house: Internal
 
 - Remove uncurried flag from bsb. https://github.com/rescript-lang/rescript-compiler/pull/7049
diff --git a/analysis.opam b/analysis.opam
new file mode 100644
index 0000000000..65bd0be75e
--- /dev/null
+++ b/analysis.opam
@@ -0,0 +1,27 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "ReScript Analysis"
+maintainer: ["Hongbo Zhang <bobzhang1988@gmail.com>" "Cristiano Calcagno"]
+authors: ["Hongbo Zhang <bobzhang1988@gmail.com>"]
+license: "LGPL-3.0-or-later"
+homepage: "https://github.com/rescript-lang/rescript-compiler"
+bug-reports: "https://github.com/rescript-lang/rescript-compiler/issues"
+depends: [
+  "ocaml" {>= "4.10"}
+  "cppo" {= "1.6.9"}
+  "dune"
+]
+build: [
+  ["dune" "subst"] {pinned}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
diff --git a/analysis/README.md b/analysis/README.md
new file mode 100644
index 0000000000..2ce1bf5a2d
--- /dev/null
+++ b/analysis/README.md
@@ -0,0 +1,26 @@
+# Analysis Library and Binary
+
+This subfolder builds a private command line binary used by the plugin to power a few functionalities such as jump to definition, hover and autocomplete.
+
+The binary reads the `.cmt` and `.cmti` files and analyses them.
+
+For installation & build instructions, see the main CONTRIBUTING.md.
+
+## Overview
+
+See main CONTRIBUTING.md's repo structure. Additionally, `examples/` is a convenience debugging repo. Check out `test.sh` (invoked through `make test`) to see the snapshots testing workflow stored in `tests/`.
+
+## Usage
+
+At root:
+```sh
+./rescript-editor-analysis.exe --help
+
+# or
+
+dune exec -- rescript-editor-analysis --help
+```
+
+## History
+
+This project is based on a fork of [Reason Language Server](https://github.com/jaredly/reason-language-server).
diff --git a/analysis/bin/dune b/analysis/bin/dune
new file mode 100644
index 0000000000..64c2a78156
--- /dev/null
+++ b/analysis/bin/dune
@@ -0,0 +1,11 @@
+(env
+ (static
+  (flags
+   (:standard -ccopt -static))))
+
+(executable
+ (public_name rescript-editor-analysis)
+ (package analysis)
+ (modes byte exe)
+ (name main)
+ (libraries analysis))
diff --git a/analysis/bin/main.ml b/analysis/bin/main.ml
new file mode 100644
index 0000000000..259b1a3004
--- /dev/null
+++ b/analysis/bin/main.ml
@@ -0,0 +1,218 @@
+open Analysis
+
+let help =
+  {|
+**Private CLI For rescript-vscode usage only**
+
+API examples:
+  ./rescript-editor-analysis.exe completion src/MyFile.res 0 4 currentContent.res true
+  ./rescript-editor-analysis.exe definition src/MyFile.res 9 3
+  ./rescript-editor-analysis.exe typeDefinition src/MyFile.res 9 3
+  ./rescript-editor-analysis.exe documentSymbol src/Foo.res
+  ./rescript-editor-analysis.exe hover src/MyFile.res 10 2 true
+  ./rescript-editor-analysis.exe references src/MyFile.res 10 2
+  ./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo
+  ./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
+  ./rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25
+  ./rescript-editor-analysis.exe codeLens src/MyFile.res
+
+Dev-time examples:
+  ./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res
+  ./rescript-editor-analysis.exe test src/MyFile.res
+
+Note: positions are zero-indexed (start at 0 0), following LSP.
+https://microsoft.github.io/language-server-protocol/specification#position
+
+Options:
+  completion: compute autocomplete for MyFile.res at line 0 and column 4,
+    where MyFile.res is being edited and the editor content is in file current.res.
+
+    ./rescript-editor-analysis.exe completion src/MyFile.res 0 4 current.res
+
+  definition: get definition for item in MyFile.res at line 10 column 2:
+
+    ./rescript-editor-analysis.exe definition src/MyFile.res 10 2
+
+  typeDefinition: get type definition for item in MyFile.res at line 10 column 2:
+
+    ./rescript-editor-analysis.exe typeDefinition src/MyFile.res 10 2
+
+  documentSymbol: get all symbols declared in MyFile.res
+
+    ./rescript-editor-analysis.exe documentSymbol src/MyFile.res
+
+  hover: get inferred type for MyFile.res at line 10 column 2 (supporting markdown links):
+
+    ./rescript-editor-analysis.exe hover src/MyFile.res 10 2 true
+
+  references: get all references to item in MyFile.res at line 10 column 2:
+
+    ./rescript-editor-analysis.exe references src/MyFile.res 10 2
+
+  rename: rename all appearances of item in MyFile.res at line 10 column 2 with foo:
+
+    ./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo
+
+  semanticTokens: return token semantic highlighting info for MyFile.res
+
+    ./rescript-editor-analysis.exe semanticTokens src/MyFile.res
+
+  createInterface: print to stdout the interface file for src/MyFile.res
+
+    ./rescript-editor-analysis.exe createInterface src/MyFile.res lib/bs/src/MyFile.cmi
+
+  format: print to stdout the formatted version of the provided file
+
+    ./rescript-editor-analysis.exe format src/MyFile.res
+
+  diagnosticSyntax: print to stdout diagnostic for syntax
+
+    ./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
+
+  inlayHint: get all inlay Hint between line 0 and 3 declared in MyFile.res. Last argument is maximum of character length for inlay hints
+
+    ./rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25
+
+  codeLens: get all code lens entries for file src/MyFile.res
+
+    ./rescript-editor-analysis.exe codeLens src/MyFile.res
+
+  signatureHelp: get signature help if available for position at line 10 column 2 in src/MyFile.res
+
+    ./rescript-editor-analysis.exe signatureHelp src/MyFile.res 10 2
+
+  test: run tests specified by special comments in file src/MyFile.res
+
+    ./rescript-editor-analysis.exe test src/src/MyFile.res
+|}
+
+let main () =
+  let args = Array.to_list Sys.argv in
+  let debugLevel, args =
+    match args with
+    | _ :: "debug-dump" :: logLevel :: rest ->
+      ( (match logLevel with
+        | "verbose" -> Debug.Verbose
+        | "regular" -> Regular
+        | _ -> Off),
+        "dummy" :: rest )
+    | args -> (Off, args)
+  in
+  Debug.debugLevel := debugLevel;
+  let debug = debugLevel <> Debug.Off in
+  let printHeaderInfo path line col =
+    if debug then
+      Printf.printf "Debug level: %s\n%s:%s-%s\n\n"
+        (match debugLevel with
+        | Debug.Verbose -> "verbose"
+        | Regular -> "regular"
+        | Off -> "off")
+        path line col
+  in
+  match args with
+  | [_; "cache-project"; rootPath] -> (
+    Cfg.readProjectConfigCache := false;
+    let uri = Uri.fromPath rootPath in
+    match Packages.getPackage ~uri with
+    | Some package -> Cache.cacheProject package
+    | None -> print_endline "\"ERR\"")
+  | [_; "cache-delete"; rootPath] -> (
+    Cfg.readProjectConfigCache := false;
+    let uri = Uri.fromPath rootPath in
+    match Packages.findRoot ~uri (Hashtbl.create 0) with
+    | Some (`Bs rootPath) -> (
+      match BuildSystem.getLibBs rootPath with
+      | None -> print_endline "\"ERR\""
+      | Some libBs ->
+        Cache.deleteCache (Cache.targetFileFromLibBs libBs);
+        print_endline "\"OK\"")
+    | _ -> print_endline "\"ERR: Did not find root \"")
+  | [_; "completion"; path; line; col; currentFile] ->
+    printHeaderInfo path line col;
+    Commands.completion ~debug ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~currentFile
+  | [_; "completionResolve"; path; modulePath] ->
+    Commands.completionResolve ~path ~modulePath
+  | [_; "definition"; path; line; col] ->
+    Commands.definition ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~debug
+  | [_; "typeDefinition"; path; line; col] ->
+    Commands.typeDefinition ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~debug
+  | [_; "documentSymbol"; path] -> DocumentSymbol.command ~path
+  | [_; "hover"; path; line; col; currentFile; supportsMarkdownLinks] ->
+    Commands.hover ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~currentFile ~debug
+      ~supportsMarkdownLinks:
+        (match supportsMarkdownLinks with
+        | "true" -> true
+        | _ -> false)
+  | [
+   _; "signatureHelp"; path; line; col; currentFile; allowForConstructorPayloads;
+  ] ->
+    Commands.signatureHelp ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~currentFile ~debug
+      ~allowForConstructorPayloads:
+        (match allowForConstructorPayloads with
+        | "true" -> true
+        | _ -> false)
+  | [_; "inlayHint"; path; line_start; line_end; maxLength] ->
+    Commands.inlayhint ~path
+      ~pos:(int_of_string line_start, int_of_string line_end)
+      ~maxLength ~debug
+  | [_; "codeLens"; path] -> Commands.codeLens ~path ~debug
+  | [_; "codeAction"; path; startLine; startCol; endLine; endCol; currentFile]
+    ->
+    Commands.codeAction ~path
+      ~startPos:(int_of_string startLine, int_of_string startCol)
+      ~endPos:(int_of_string endLine, int_of_string endCol)
+      ~currentFile ~debug
+  | [_; "codemod"; path; line; col; typ; hint] ->
+    let typ =
+      match typ with
+      | "add-missing-cases" -> Codemod.AddMissingCases
+      | _ -> raise (Failure "unsupported type")
+    in
+    let res =
+      Codemod.transform ~path
+        ~pos:(int_of_string line, int_of_string col)
+        ~debug ~typ ~hint
+      |> Json.escape
+    in
+    Printf.printf "\"%s\"" res
+  | [_; "diagnosticSyntax"; path] -> Commands.diagnosticSyntax ~path
+  | _ :: "reanalyze" :: _ ->
+    let len = Array.length Sys.argv in
+    for i = 1 to len - 2 do
+      Sys.argv.(i) <- Sys.argv.(i + 1)
+    done;
+    Sys.argv.(len - 1) <- "";
+    Reanalyze.cli ()
+  | [_; "references"; path; line; col] ->
+    Commands.references ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~debug
+  | [_; "rename"; path; line; col; newName] ->
+    Commands.rename ~path
+      ~pos:(int_of_string line, int_of_string col)
+      ~newName ~debug
+  | [_; "semanticTokens"; currentFile] ->
+    SemanticTokens.semanticTokens ~currentFile
+  | [_; "createInterface"; path; cmiFile] ->
+    Printf.printf "\"%s\""
+      (Json.escape (CreateInterface.command ~path ~cmiFile))
+  | [_; "format"; path] ->
+    Printf.printf "\"%s\"" (Json.escape (Commands.format ~path))
+  | [_; "test"; path] -> Commands.test ~path
+  | args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
+  | _ ->
+    prerr_endline help;
+    exit 1
+;;
+
+main ()
diff --git a/analysis/dune b/analysis/dune
new file mode 100644
index 0000000000..6b297d2e58
--- /dev/null
+++ b/analysis/dune
@@ -0,0 +1,16 @@
+(dirs bin src reanalyze vendor)
+
+(env
+ (dev
+  (env-vars
+   (CPPO_FLAGS -U=RELEASE)))
+ (release
+  (env-vars
+   (CPPO_FLAGS -D=RELEASE))
+  (ocamlopt_flags
+   (:standard -O3 -unbox-closures)))
+ (static
+  (env-vars
+   (CPPO_FLAGS -D=RELEASE))
+  (ocamlopt_flags
+   (:standard -O3 -unbox-closures))))
diff --git a/analysis/examples/example-project/.gitignore b/analysis/examples/example-project/.gitignore
new file mode 100644
index 0000000000..4ae6e66ef3
--- /dev/null
+++ b/analysis/examples/example-project/.gitignore
@@ -0,0 +1,2 @@
+lib
+.merlin
\ No newline at end of file
diff --git a/analysis/examples/example-project/bsconfig.json b/analysis/examples/example-project/bsconfig.json
new file mode 100644
index 0000000000..f592900000
--- /dev/null
+++ b/analysis/examples/example-project/bsconfig.json
@@ -0,0 +1,14 @@
+{
+  "name": "tryit",
+  "sources": "src",
+  "bsc-flags": ["-bs-super-errors", "-open Belt"],
+  "warnings": {
+    "number": "-32-26-27-33"
+  },
+  "bs-dependencies": ["reason-react"],
+  "reason": { "react-jsx": 3 },
+  "namespace": "my-namespace",
+  "reanalyze": {
+    "analysis": ["dce", "exception"]
+  }
+}
diff --git a/analysis/examples/example-project/package-lock.json b/analysis/examples/example-project/package-lock.json
new file mode 100644
index 0000000000..61650f5294
--- /dev/null
+++ b/analysis/examples/example-project/package-lock.json
@@ -0,0 +1,16 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "reason-react": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/reason-react/-/reason-react-0.9.1.tgz",
+      "integrity": "sha512-nlH0O2TDy9KzOLOW+vlEQk4ExHOeciyzFdoLcsmmiit6hx6H5+CVDrwJ+8aiaLT/kqK5xFOjy4PS7PftWz4plA=="
+    },
+    "rescript": {
+      "version": "9.1.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-9.1.2.tgz",
+      "integrity": "sha512-4wHvTDv3nyYnAPJHcg1RGG8z7u3HDiBf6RN3P/dITDv859Qo35aKOzJWQtfBzbAs0EKNafLqei3TnUqiAv6BwQ=="
+    }
+  }
+}
diff --git a/analysis/examples/example-project/package.json b/analysis/examples/example-project/package.json
new file mode 100644
index 0000000000..b948ffc3a9
--- /dev/null
+++ b/analysis/examples/example-project/package.json
@@ -0,0 +1,11 @@
+{
+  "dependencies": {
+    "reason-react": "^0.9.1",
+    "rescript": "^9.1.2"
+  },
+  "scripts": {
+    "build": "rescript",
+    "start": "rescript build -w",
+    "clean": "rescript clean -with-deps"
+  }
+}
diff --git a/analysis/examples/example-project/src/B.re b/analysis/examples/example-project/src/B.re
new file mode 100644
index 0000000000..2ad1ee8305
--- /dev/null
+++ b/analysis/examples/example-project/src/B.re
@@ -0,0 +1,9 @@
+
+
+let x = 12
+
+
+let y = 44
+
+
+let z = 123
diff --git a/analysis/examples/example-project/src/Embeded.md b/analysis/examples/example-project/src/Embeded.md
new file mode 100644
index 0000000000..ede126c0f2
--- /dev/null
+++ b/analysis/examples/example-project/src/Embeded.md
@@ -0,0 +1,55 @@
+# Markdown Embedded Fenced Code Regression Test
+
+```re
+module Something = {
+  open Other;
+
+  let m = {name: "Me", age: 0};
+  let animal = Things(10);
+  let other = Things(2);
+  let me: animals = People("Hie");
+  let x = something + 10;
+  let r = m.name;
+
+  let awesome = 20;
+  if (true) {
+    ()
+  }
+};
+```
+
+```reason
+module Something = {
+  open Other;
+
+  let m = {name: "Me", age: 0};
+  let animal = Things(10);
+  let other = Things(2);
+  let me: animals = People("Hie");
+  let x = something + 10;
+  let r = m.name;
+
+  let awesome = 20;
+  if (true) {
+    ()
+  }
+};
+```
+
+```reasonml
+module Something = {
+  open Other;
+
+  let m = {name: "Me", age: 0};
+  let animal = Things(10);
+  let other = Things(2);
+  let me: animals = People("Hie");
+  let x = something + 10;
+  let r = m.name;
+
+  let awesome = 20;
+  if (true) {
+    ()
+  }
+};
+```
\ No newline at end of file
diff --git a/analysis/examples/example-project/src/Hello.res b/analysis/examples/example-project/src/Hello.res
new file mode 100644
index 0000000000..db525a7e73
--- /dev/null
+++ b/analysis/examples/example-project/src/Hello.res
@@ -0,0 +1,173 @@
+let someLongName = 10
+
+let otherLongName = "string"
+
+let x = {"a": 3}
+
+let r = Other.something
+
+let l = More.inner + More.n + Other.inner
+
+let n = More.n
+
+let _ = More.party
+let _ = string_of_bool
+
+/* let m = {More.a: 2, b: 32.}; */
+
+module Something = {
+  open Other
+
+  let m = {name: "Me", age: 0}
+  let animal = Things(10)
+  let other = Things(2)
+  let me: animals = People("Hie")
+  let x = something + 10
+  let r = m.name
+
+  let awesome = 20
+  if true {
+    ()
+  }
+}
+
+open! Something
+
+let y = x + 10
+
+switch me {
+| Things(n) => ()
+| _ => ()
+}
+
+let z = x * x
+
+let aThing = 10 + Other.something
+
+@ocaml.doc(" Some docs about this **awesome** thing. ")
+let awesome =
+  100 + m.age
+
+let thing = "thing"
+
+let transform = (x, y) => x ++ Js.Float.toString(y)
+
+let z = transform("hello ", 5.)
+
+let zzz = 1
+
+let more = 20
+
+@ocaml.doc(" Something here ")
+let added =
+  10 + awesome
+
+open Other
+
+open Hashtbl
+
+@ocaml.doc(" Some more documentation about this ")
+let awesome = x => x + 2
+
+let a = list{"hello", "my fine" ++ "folks", "in boonville"}
+
+let div = (~x, ~y, ~children, ()) => 10
+
+let m = <div x="10" y="20" />
+
+let something = animal =>
+  switch animal {
+  | blank => ()
+  }
+
+something(animal)
+
+let someFunction = (memorableName, {contents}) => {
+  let innerMemorable = 20
+  memorableName + innerMemorable
+}
+
+/* let awesome = 10000; */
+
+/* let awesome = 111; */
+
+let z = 10
+
+let z = find
+
+let z = later
+
+let m = Other.later
+
+for _index in 0 to 10 {
+  print_endline("hellO")
+}
+
+module OneOneOneOne = {
+  module TwoTwoTwoTwo = {
+    let xxxxxxxxxx = 10
+  }
+}
+let r = OneOneOneOne.TwoTwoTwoTwo.xxxxxxxxxx
+
+type awesome = {
+  one: string,
+  two: float,
+}
+
+open OneOneOneOne.TwoTwoTwoTwo
+
+include OneOneOneOne.TwoTwoTwoTwo
+
+include More
+
+let _ = Other.oo.person.name
+
+type lots =
+  | Parties
+  | Plutocrats(int, float)
+  | Possums
+  | Oppossums
+
+let y = Some(10 + awesome(3))
+
+let z = {contents: 30}
+let party = {one: "one", two: 2.}
+
+let {one, two} = party
+
+let thing = () => 34 + 43
+
+type more = awesome
+
+let {contents} = z
+
+switch y {
+| Some(u) => ()
+| None => ()
+}
+
+/* let x = [%raw " hello"]; */
+
+let awesome = "hello"
+
+type shortReference = (string, list<string>, string)
+
+type reference = {
+  uri: string,
+  moduleName: string,
+  modulePath: list<string>,
+  name: string,
+}
+
+type typeSource =
+  | Builtin(string)
+  | Public(reference)
+  | NotFound
+
+type lockfile = {
+  version: int,
+  pastVersions: Belt.HashMap.Int.t<list<(shortReference, int)>>,
+  current: list<(shortReference, int)>,
+}
+
diff --git a/analysis/examples/example-project/src/Json.res b/analysis/examples/example-project/src/Json.res
new file mode 100644
index 0000000000..7cbfbbe037
--- /dev/null
+++ b/analysis/examples/example-project/src/Json.res
@@ -0,0 +1,607 @@
+@@ocaml.doc(" # Json parser
+ *
+ * Works with bucklescript and bsb-native
+ *
+ * ## Basics
+ *
+ * ```
+ * open Json.Infix; /* for the nice infix operators */
+ * let raw = {|{\"hello\": \"folks\"}|};
+ * let who = Json.parse(raw) |> Json.get(\"hello\") |?> Json.string;
+ * Js.log(who);
+ * ```
+ *
+ * ## Parse & stringify
+ *
+ * @doc parse, stringify
+ *
+ * ## Accessing descendents
+ *
+ * @doc get, nth, getPath
+ *
+ * ## Coercing to types
+ *
+ * @doc string, number, array, obj, bool, null
+ *
+ * ## The JSON type
+ *
+ * @doc t
+ *
+ * ## Infix operators for easier working
+ *
+ * @doc Infix
+ ")
+
+type rec t =
+  | String(string)
+  | Number(float)
+  | Array(list<t>)
+  | Object(list<(string, t)>)
+  | True
+  | False
+  | Null
+
+let string_of_number = f => {
+  let s = Js.Float.toString(f)
+  if String.get(s, String.length(s) - 1) == '.' {
+    String.sub(s, 0, String.length(s) - 1)
+  } else {
+    s
+  }
+}
+
+@ocaml.doc("
+ * This module is provided for easier working with optional values.
+ ")
+module Infix = {
+  @ocaml.doc(" The \"force unwrap\" operator
+   *
+   * If you're sure there's a value, you can force it.
+   * ```
+   * open Json.Infix;
+   * let x: int = Some(10) |! \"Expected this to be present\";
+   * Js.log(x);
+   * ```
+   *
+   * But you gotta be sure, otherwise it will throw.
+   * ```reason;raises
+   * open Json.Infix;
+   * let x: int = None |! \"This will throw\";
+   * ```
+   ")
+  let \"|!" = (o, d) =>
+    switch o {
+    | None => failwith(d)
+    | Some(v) => v
+    }
+  @ocaml.doc(" The \"upwrap with default\" operator
+   * ```
+   * open Json.Infix;
+   * let x: int = Some(10) |? 4;
+   * let y: int = None |? 5;
+   * Js.log2(x, y);
+   * ```
+   ")
+  let \"|?" = (o, d) =>
+    switch o {
+    | None => d
+    | Some(v) => v
+    }
+  @ocaml.doc(" The \"transform contents into new optional\" operator
+   * ```
+   * open Json.Infix;
+   * let maybeInc = x => x > 5 ? Some(x + 1) : None;
+   * let x: option(int) = Some(14) |?> maybeInc;
+   * let y: option(int) = None |?> maybeInc;
+   * ```
+   ")
+  let \"|?>" = (o, fn) =>
+    switch o {
+    | None => None
+    | Some(v) => fn(v)
+    }
+  @ocaml.doc(" The \"transform contents into new value & then re-wrap\" operator
+   * ```
+   * open Json.Infix;
+   * let inc = x => x + 1;
+   * let x: option(int) = Some(7) |?>> inc;
+   * let y: option(int) = None |?>> inc;
+   * Js.log2(x, y);
+   * ```
+   ")
+  let \"|?>>" = (o, fn) =>
+    switch o {
+    | None => None
+    | Some(v) => Some(fn(v))
+    }
+  @ocaml.doc(" \"handle the value if present, otherwise here's the default\"
+   *
+   * It's called fold because that's what people call it :?. It's the same as \"transform contents to new value\" + \"unwrap with default\".
+   *
+   * ```
+   * open Json.Infix;
+   * let inc = x => x + 1;
+   * let x: int = fold(Some(4), 10, inc);
+   * let y: int = fold(None, 2, inc);
+   * Js.log2(x, y);
+   * ```
+   ")
+  let fold = (o, d, f) =>
+    switch o {
+    | None => d
+    | Some(v) => f(v)
+    }
+}
+
+let escape = text => {
+  let ln = String.length(text)
+  let buf = Buffer.create(ln)
+  let rec loop = i =>
+    if i < ln {
+      switch String.get(text, i) {
+      | '\012' => Buffer.add_string(buf, "\\f")
+      | '\\' => Buffer.add_string(buf, "\\\\")
+      | '"' => Buffer.add_string(buf, "\\\"")
+      | '\n' => Buffer.add_string(buf, "\\n")
+      | '\b' => Buffer.add_string(buf, "\\b")
+      | '\r' => Buffer.add_string(buf, "\\r")
+      | '\t' => Buffer.add_string(buf, "\\t")
+      | c => Buffer.add_char(buf, c)
+      }
+      loop(i + 1)
+    }
+  loop(0)
+  Buffer.contents(buf)
+}
+
+@ocaml.doc(" ```
+ * let text = {|{\"hello\": \"folks\", \"aa\": [2, 3, \"four\"]}|};
+ * let result = Json.stringify(Json.parse(text));
+ * Js.log(result);
+ * assert(text == result);
+ * ```
+ ")
+let rec stringify = t =>
+  switch t {
+  | String(value) => "\"" ++ (escape(value) ++ "\"")
+  | Number(num) => string_of_number(num)
+  | Array(items) => "[" ++ (String.concat(", ", List.map(items, stringify)) ++ "]")
+  | Object(items) =>
+    "{" ++
+    (String.concat(
+      ", ",
+      List.map(items, ((k, v)) => "\"" ++ (String.escaped(k) ++ ("\": " ++ stringify(v)))),
+    ) ++
+    "}")
+  | True => "true"
+  | False => "false"
+  | Null => "null"
+  }
+
+let white = n => {
+  let buffer = Buffer.create(n)
+  for _ in 0 to n - 1 {
+    Buffer.add_char(buffer, ' ')
+  }
+  Buffer.contents(buffer)
+}
+
+let rec stringifyPretty = (~indent=0, t) =>
+  switch t {
+  | String(value) => "\"" ++ (escape(value) ++ "\"")
+  | Number(num) => string_of_number(num)
+  | Array(list{}) => "[]"
+  | Array(items) =>
+    "[\n" ++
+    (white(indent) ++
+    (String.concat(",\n" ++ white(indent), List.map(items, stringifyPretty(~indent=indent + 2))) ++
+    ("\n" ++
+    (white(indent) ++ "]"))))
+  | Object(list{}) => "{}"
+  | Object(items) =>
+    "{\n" ++
+    (white(indent) ++
+    (String.concat(
+      ",\n" ++ white(indent),
+      List.map(items, ((k, v)) =>
+        "\"" ++ (String.escaped(k) ++ ("\": " ++ stringifyPretty(~indent=indent + 2, v)))
+      ),
+    ) ++
+    ("\n" ++
+    (white(indent) ++ "}"))))
+  | True => "true"
+  | False => "false"
+  | Null => "null"
+  }
+
+let unwrap = (message, t) =>
+  switch t {
+  | Some(v) => v
+  | None => failwith(message)
+  }
+
+@nodoc
+module Parser = {
+  let split_by = (~keep_empty=false, is_delim, str) => {
+    let len = String.length(str)
+    let rec loop = (acc, last_pos, pos) =>
+      if pos == -1 {
+        if last_pos == 0 && !keep_empty {
+          acc
+        } else {
+          list{String.sub(str, 0, last_pos), ...acc}
+        }
+      } else if is_delim(String.get(str, pos)) {
+        let new_len = last_pos - pos - 1
+        if new_len != 0 || keep_empty {
+          let v = String.sub(str, pos + 1, new_len)
+          loop(list{v, ...acc}, pos, pos - 1)
+        } else {
+          loop(acc, pos, pos - 1)
+        }
+      } else {
+        loop(acc, last_pos, pos - 1)
+      }
+    loop(list{}, len, len - 1)
+  }
+  let fail = (text, pos, message) => {
+    let pre = String.sub(text, 0, pos)
+    let lines = split_by(c => c == '\n', pre)
+    let count = List.length(lines)
+    let last = count > 0 ? List.getExn(lines, count - 1) : ""
+    let col = String.length(last) + 1
+    let line = List.length(lines)
+    let string = Printf.sprintf("Error \"%s\" at %d:%d -> %s\n", message, line, col, last)
+    failwith(string)
+  }
+  let rec skipToNewline = (text, pos) =>
+    if pos >= String.length(text) {
+      pos
+    } else if String.get(text, pos) == '\n' {
+      pos + 1
+    } else {
+      skipToNewline(text, pos + 1)
+    }
+  let stringTail = text => {
+    let len = String.length(text)
+    if len > 1 {
+      String.sub(text, 1, len - 1)
+    } else {
+      ""
+    }
+  }
+  let rec skipToCloseMultilineComment = (text, pos) =>
+    if pos + 1 >= String.length(text) {
+      failwith("Unterminated comment")
+    } else if String.get(text, pos) == '*' && String.get(text, pos + 1) == '/' {
+      pos + 2
+    } else {
+      skipToCloseMultilineComment(text, pos + 1)
+    }
+  let rec skipWhite = (text, pos) =>
+    if (
+      pos < String.length(text) &&
+        (String.get(text, pos) == ' ' ||
+          (String.get(text, pos) == '\t' ||
+          (String.get(text, pos) == '\n' || String.get(text, pos) == '\r')))
+    ) {
+      skipWhite(text, pos + 1)
+    } else {
+      pos
+    }
+  let parseString = (text, pos) => {
+    /* let i = ref(pos); */
+    let buffer = Buffer.create(String.length(text))
+    let ln = String.length(text)
+    let rec loop = i =>
+      i >= ln
+        ? fail(text, i, "Unterminated string")
+        : switch String.get(text, i) {
+          | '"' => i + 1
+          | '\\' =>
+            i + 1 >= ln
+              ? fail(text, i, "Unterminated string")
+              : switch String.get(text, i + 1) {
+                | '/' =>
+                  Buffer.add_char(buffer, '/')
+                  loop(i + 2)
+                | 'f' =>
+                  Buffer.add_char(buffer, '\012')
+                  loop(i + 2)
+                | _ =>
+                  Buffer.add_string(buffer, Scanf.unescaped(String.sub(text, i, 2)))
+                  loop(i + 2)
+                }
+          | c =>
+            Buffer.add_char(buffer, c)
+            loop(i + 1)
+          }
+    let final = loop(pos)
+    (Buffer.contents(buffer), final)
+  }
+  let parseDigits = (text, pos) => {
+    let len = String.length(text)
+    let rec loop = i =>
+      if i >= len {
+        i
+      } else {
+        switch String.get(text, i) {
+        | '0' .. '9' => loop(i + 1)
+        | _ => i
+        }
+      }
+    loop(pos + 1)
+  }
+  let parseWithDecimal = (text, pos) => {
+    let pos = parseDigits(text, pos)
+    if pos < String.length(text) && String.get(text, pos) == '.' {
+      let pos = parseDigits(text, pos + 1)
+      pos
+    } else {
+      pos
+    }
+  }
+  let parseNumber = (text, pos) => {
+    let pos = parseWithDecimal(text, pos)
+    let ln = String.length(text)
+    if pos < ln - 1 && (String.get(text, pos) == 'E' || String.get(text, pos) == 'e') {
+      let pos = switch String.get(text, pos + 1) {
+      | '-'
+      | '+' =>
+        pos + 2
+      | _ => pos + 1
+      }
+      parseDigits(text, pos)
+    } else {
+      pos
+    }
+  }
+  let parseNegativeNumber = (text, pos) => {
+    let final = if String.get(text, pos) == '-' {
+      parseNumber(text, pos + 1)
+    } else {
+      parseNumber(text, pos)
+    }
+    (Number(float_of_string(String.sub(text, pos, final - pos))), final)
+  }
+  let expect = (char, text, pos, message) =>
+    if String.get(text, pos) != char {
+      fail(text, pos, "Expected: " ++ message)
+    } else {
+      pos + 1
+    }
+  let parseComment: 'a. (string, int, (string, int) => 'a) => 'a = (text, pos, next) =>
+    if String.get(text, pos) != '/' {
+      if String.get(text, pos) == '*' {
+        next(text, skipToCloseMultilineComment(text, pos + 1))
+      } else {
+        failwith("Invalid syntax")
+      }
+    } else {
+      next(text, skipToNewline(text, pos + 1))
+    }
+  let maybeSkipComment = (text, pos) =>
+    if pos < String.length(text) && String.get(text, pos) == '/' {
+      if pos + 1 < String.length(text) && String.get(text, pos + 1) == '/' {
+        skipToNewline(text, pos + 1)
+      } else if pos + 1 < String.length(text) && String.get(text, pos + 1) == '*' {
+        skipToCloseMultilineComment(text, pos + 1)
+      } else {
+        fail(text, pos, "Invalid synatx")
+      }
+    } else {
+      pos
+    }
+  let rec skip = (text, pos) =>
+    if pos == String.length(text) {
+      pos
+    } else {
+      let n = skipWhite(text, pos) |> maybeSkipComment(text)
+      if n > pos {
+        skip(text, n)
+      } else {
+        n
+      }
+    }
+  let rec parse = (text, pos) =>
+    if pos >= String.length(text) {
+      fail(text, pos, "Reached end of file without being done parsing")
+    } else {
+      switch String.get(text, pos) {
+      | '/' => parseComment(text, pos + 1, parse)
+      | '[' => parseArray(text, pos + 1)
+      | '{' => parseObject(text, pos + 1)
+      | 'n' =>
+        if String.sub(text, pos, 4) == "null" {
+          (Null, pos + 4)
+        } else {
+          fail(text, pos, "unexpected character")
+        }
+      | 't' =>
+        if String.sub(text, pos, 4) == "true" {
+          (True, pos + 4)
+        } else {
+          fail(text, pos, "unexpected character")
+        }
+      | 'f' =>
+        if String.sub(text, pos, 5) == "false" {
+          (False, pos + 5)
+        } else {
+          fail(text, pos, "unexpected character")
+        }
+      | '\n'
+      | '\t'
+      | ' '
+      | '\r' =>
+        parse(text, skipWhite(text, pos))
+      | '"' =>
+        let (s, pos) = parseString(text, pos + 1)
+        (String(s), pos)
+      | '-'
+      | '0' .. '9' =>
+        parseNegativeNumber(text, pos)
+      | _ => fail(text, pos, "unexpected character")
+      }
+    }
+  and parseArrayValue = (text, pos) => {
+    let pos = skip(text, pos)
+    let (value, pos) = parse(text, pos)
+    let pos = skip(text, pos)
+    switch String.get(text, pos) {
+    | ',' =>
+      let pos = skip(text, pos + 1)
+      if String.get(text, pos) == ']' {
+        (list{value}, pos + 1)
+      } else {
+        let (rest, pos) = parseArrayValue(text, pos)
+        (list{value, ...rest}, pos)
+      }
+    | ']' => (list{value}, pos + 1)
+    | _ => fail(text, pos, "unexpected character")
+    }
+  }
+  and parseArray = (text, pos) => {
+    let pos = skip(text, pos)
+    switch String.get(text, pos) {
+    | ']' => (Array(list{}), pos + 1)
+    | _ =>
+      let (items, pos) = parseArrayValue(text, pos)
+      (Array(items), pos)
+    }
+  }
+  and parseObjectValue = (text, pos) => {
+    let pos = skip(text, pos)
+    if String.get(text, pos) != '"' {
+      fail(text, pos, "Expected string")
+    } else {
+      let (key, pos) = parseString(text, pos + 1)
+      let pos = skip(text, pos)
+      let pos = expect(':', text, pos, "Colon")
+      let (value, pos) = parse(text, pos)
+      let pos = skip(text, pos)
+      switch String.get(text, pos) {
+      | ',' =>
+        let pos = skip(text, pos + 1)
+        if String.get(text, pos) == '}' {
+          (list{(key, value)}, pos + 1)
+        } else {
+          let (rest, pos) = parseObjectValue(text, pos)
+          (list{(key, value), ...rest}, pos)
+        }
+      | '}' => (list{(key, value)}, pos + 1)
+      | _ =>
+        let (rest, pos) = parseObjectValue(text, pos)
+        (list{(key, value), ...rest}, pos)
+      }
+    }
+  }
+  and parseObject = (text, pos) => {
+    let pos = skip(text, pos)
+    if String.get(text, pos) == '}' {
+      (Object(list{}), pos + 1)
+    } else {
+      let (pairs, pos) = parseObjectValue(text, pos)
+      (Object(pairs), pos)
+    }
+  }
+}
+
+@ocaml.doc(" Turns some text into a json object. throws on failure ")
+let parse = text => {
+  let (item, pos) = Parser.parse(text, 0)
+  let pos = Parser.skip(text, pos)
+  if pos < String.length(text) {
+    failwith(
+      "Extra data after parse finished: " ++ String.sub(text, pos, String.length(text) - pos),
+    )
+  } else {
+    item
+  }
+}
+
+/* Accessor helpers */
+let bind = (v, fn) =>
+  switch v {
+  | None => None
+  | Some(v) => fn(v)
+  }
+
+@ocaml.doc(" If `t` is an object, get the value associated with the given string key ")
+let get = (key, t) =>
+  switch t {
+  | Object(items) => List.getAssoc(items, key, \"=")
+  | _ => None
+  }
+
+@ocaml.doc(" If `t` is an array, get the value associated with the given index ")
+let nth = (n, t) =>
+  switch t {
+  | Array(items) =>
+    if n < List.length(items) {
+      Some(List.getExn(items, n))
+    } else {
+      None
+    }
+  | _ => None
+  }
+
+let string = t =>
+  switch t {
+  | String(s) => Some(s)
+  | _ => None
+  }
+
+let number = t =>
+  switch t {
+  | Number(s) => Some(s)
+  | _ => None
+  }
+
+let array = t =>
+  switch t {
+  | Array(s) => Some(s)
+  | _ => None
+  }
+
+let obj = t =>
+  switch t {
+  | Object(s) => Some(s)
+  | _ => None
+  }
+
+let bool = t =>
+  switch t {
+  | True => Some(true)
+  | False => Some(false)
+  | _ => None
+  }
+
+let null = t =>
+  switch t {
+  | Null => Some()
+  | _ => None
+  }
+
+let rec parsePath = (keyList, t) =>
+  switch keyList {
+  | list{} => Some(t)
+  | list{head, ...rest} =>
+    switch get(head, t) {
+    | None => None
+    | Some(value) => parsePath(rest, value)
+    }
+  }
+
+@ocaml.doc(" Get a deeply nested value from an object `t`.
+ * ```
+ * open Json.Infix;
+ * let json = Json.parse({|{\"a\": {\"b\": {\"c\": 2}}}|});
+ * let num = Json.getPath(\"a.b.c\", json) |?> Json.number;
+ * assert(num == Some(2.))
+ * ```
+ ")
+let getPath = (path, t) => {
+  let keys = Parser.split_by(c => c == '.', path)
+  parsePath(keys, t)
+}
+
diff --git a/analysis/examples/example-project/src/ModuleWithDocComment.res b/analysis/examples/example-project/src/ModuleWithDocComment.res
new file mode 100644
index 0000000000..a09e2692f9
--- /dev/null
+++ b/analysis/examples/example-project/src/ModuleWithDocComment.res
@@ -0,0 +1,13 @@
+@@ocaml.doc("This comment is for the **toplevel** module.")
+
+@ocaml.doc("This comment is for the first **nested** module.")
+module Nested = {
+  let x = "123"
+
+  @ocaml.doc("This comment is for the inner **nested-again** module.")
+  module NestedAgain = {
+    let y = 123
+  }
+}
+
+module M = Nested.NestedAgain
diff --git a/analysis/examples/example-project/src/More.res b/analysis/examples/example-project/src/More.res
new file mode 100644
index 0000000000..a1775e21ae
--- /dev/null
+++ b/analysis/examples/example-project/src/More.res
@@ -0,0 +1,13 @@
+@@ocaml.doc(" Toplevel docs ")
+
+@ocaml.doc(" Some contents ")
+let contnets = "here"
+
+let inner = 20
+
+let n = 10
+
+let party = 30
+
+let awesome = 200
+
diff --git a/analysis/examples/example-project/src/More.resi b/analysis/examples/example-project/src/More.resi
new file mode 100644
index 0000000000..023a7222cb
--- /dev/null
+++ b/analysis/examples/example-project/src/More.resi
@@ -0,0 +1,5 @@
+let contnets: string
+let inner: int
+let n: int
+let party: int
+
diff --git a/analysis/examples/example-project/src/Other.res b/analysis/examples/example-project/src/Other.res
new file mode 100644
index 0000000000..d7a5bf4329
--- /dev/null
+++ b/analysis/examples/example-project/src/Other.res
@@ -0,0 +1,30 @@
+/* let later = 10; */
+
+/* Ok testing things */
+
+let something = 10
+
+type person = {name: string, age: int}
+
+type animals = Things(int) | People(string) | Mouse
+
+let inner = 10
+/* More.outer; */
+
+let m = Things(1)
+
+/* working on things. */
+
+let z = {name: "hi", age: 20}
+
+let later = 20
+
+let concat = (~first, ~second) => first + second
+
+type other = {person: person, height: float}
+let oo = {person: z, height: 34.2}
+
+let show = o => {
+  let m = o.height
+}
+
diff --git a/analysis/examples/example-project/src/Serde.res b/analysis/examples/example-project/src/Serde.res
new file mode 100644
index 0000000000..1c516c2100
--- /dev/null
+++ b/analysis/examples/example-project/src/Serde.res
@@ -0,0 +1,222 @@
+let rec deserialize_Hello__TryIt____lockfile: Json.t => Belt.Result.t<
+  MyNamespace.Hello.lockfile,
+  string,
+> = record =>
+  switch record {
+  | Json.Object(items) =>
+    switch Belt.List.getAssoc(items, "current", \"=") {
+    | None => Belt.Result.Error(@reason.raw_literal("No attribute ") "No attribute " ++ "current")
+    | Some(json) =>
+      switch (
+        list =>
+          switch list {
+          | Json.Array(items) =>
+            let transformer = json =>
+              switch json {
+              | Json.Array(list{arg0, arg1}) =>
+                switch (
+                  number =>
+                    switch number {
+                    | Json.Number(number) => Belt.Result.Ok(int_of_float(number))
+                    | _ => Error(@reason.raw_literal("Expected a float") "Expected a float")
+                    }
+                )(arg1) {
+                | Belt.Result.Ok(arg1) =>
+                  switch deserialize_Hello__TryIt____shortReference(arg0) {
+                  | Belt.Result.Ok(arg0) => Belt.Result.Ok(arg0, arg1)
+                  | Error(error) => Error(error)
+                  }
+                | Error(error) => Error(error)
+                }
+              | _ => Belt.Result.Error(@reason.raw_literal("Expected array") "Expected array")
+              }
+            let rec loop = items =>
+              switch items {
+              | list{} => Belt.Result.Ok(list{})
+              | list{one, ...rest} =>
+                switch transformer(one) {
+                | Belt.Result.Error(error) => Belt.Result.Error(error)
+                | Belt.Result.Ok(value) =>
+                  switch loop(rest) {
+                  | Belt.Result.Error(error) => Belt.Result.Error(error)
+                  | Belt.Result.Ok(rest) => Belt.Result.Ok(list{value, ...rest})
+                  }
+                }
+              }
+            loop(items)
+          | _ => Belt.Result.Error(@reason.raw_literal("expected an array") "expected an array")
+          }
+      )(json) {
+      | Belt.Result.Error(error) => Belt.Result.Error(error)
+      | Belt.Result.Ok(attr_current) =>
+        switch Belt.List.getAssoc(items, "pastVersions", \"=") {
+        | None =>
+          Belt.Result.Error(@reason.raw_literal("No attribute ") "No attribute " ++ "pastVersions")
+        | Some(json) =>
+          switch deserialize_Belt_HashMapInt____t(list =>
+            switch list {
+            | Json.Array(items) =>
+              let transformer = json =>
+                switch json {
+                | Json.Array(list{arg0, arg1}) =>
+                  switch (
+                    number =>
+                      switch number {
+                      | Json.Number(number) => Belt.Result.Ok(int_of_float(number))
+                      | _ => Error(@reason.raw_literal("Expected a float") "Expected a float")
+                      }
+                  )(arg1) {
+                  | Belt.Result.Ok(arg1) =>
+                    switch deserialize_Hello__TryIt____shortReference(arg0) {
+                    | Belt.Result.Ok(arg0) => Belt.Result.Ok(arg0, arg1)
+                    | Error(error) => Error(error)
+                    }
+                  | Error(error) => Error(error)
+                  }
+                | _ => Belt.Result.Error(@reason.raw_literal("Expected array") "Expected array")
+                }
+              let rec loop = items =>
+                switch items {
+                | list{} => Belt.Result.Ok(list{})
+                | list{one, ...rest} =>
+                  switch transformer(one) {
+                  | Belt.Result.Error(error) => Belt.Result.Error(error)
+                  | Belt.Result.Ok(value) =>
+                    switch loop(rest) {
+                    | Belt.Result.Error(error) => Belt.Result.Error(error)
+                    | Belt.Result.Ok(rest) => Belt.Result.Ok(list{value, ...rest})
+                    }
+                  }
+                }
+              loop(items)
+            | _ => Belt.Result.Error(@reason.raw_literal("expected an array") "expected an array")
+            }
+          )(json) {
+          | Belt.Result.Error(error) => Belt.Result.Error(error)
+          | Belt.Result.Ok(attr_pastVersions) =>
+            switch Belt.List.getAssoc(items, "version", \"=") {
+            | None =>
+              Belt.Result.Error(@reason.raw_literal("No attribute ") "No attribute " ++ "version")
+            | Some(json) =>
+              switch (
+                number =>
+                  switch number {
+                  | Json.Number(number) => Belt.Result.Ok(int_of_float(number))
+                  | _ => Error(@reason.raw_literal("Expected a float") "Expected a float")
+                  }
+              )(json) {
+              | Belt.Result.Error(error) => Belt.Result.Error(error)
+              | Belt.Result.Ok(attr_version) =>
+                Belt.Result.Ok({
+                  version: attr_version,
+                  pastVersions: attr_pastVersions,
+                  current: attr_current,
+                })
+              }
+            }
+          }
+        }
+      }
+    }
+  | _ => Belt.Result.Error(@reason.raw_literal("Expected an object") "Expected an object")
+  }
+and deserialize_Hello__TryIt____shortReference: Json.t => Belt.Result.t<
+  MyNamespace.Hello.shortReference,
+  string,
+> = value =>
+  (
+    json =>
+      switch json {
+      | Json.Array(list{arg0, arg1, arg2}) =>
+        switch (
+          string =>
+            switch string {
+            | Json.String(string) => Belt.Result.Ok(string)
+            | _ => Error(@reason.raw_literal("epected a string") "epected a string")
+            }
+        )(arg2) {
+        | Belt.Result.Ok(arg2) =>
+          switch (
+            list =>
+              switch list {
+              | Json.Array(items) =>
+                let transformer = string =>
+                  switch string {
+                  | Json.String(string) => Belt.Result.Ok(string)
+                  | _ => Error(@reason.raw_literal("epected a string") "epected a string")
+                  }
+                let rec loop = items =>
+                  switch items {
+                  | list{} => Belt.Result.Ok(list{})
+                  | list{one, ...rest} =>
+                    switch transformer(one) {
+                    | Belt.Result.Error(error) => Belt.Result.Error(error)
+                    | Belt.Result.Ok(value) =>
+                      switch loop(rest) {
+                      | Belt.Result.Error(error) => Belt.Result.Error(error)
+                      | Belt.Result.Ok(rest) => Belt.Result.Ok(list{value, ...rest})
+                      }
+                    }
+                  }
+                loop(items)
+              | _ => Belt.Result.Error(@reason.raw_literal("expected an array") "expected an array")
+              }
+          )(arg1) {
+          | Belt.Result.Ok(arg1) =>
+            switch (
+              string =>
+                switch string {
+                | Json.String(string) => Belt.Result.Ok(string)
+                | _ => Error(@reason.raw_literal("epected a string") "epected a string")
+                }
+            )(arg0) {
+            | Belt.Result.Ok(arg0) => Belt.Result.Ok(arg0, arg1, arg2)
+            | Error(error) => Error(error)
+            }
+          | Error(error) => Error(error)
+          }
+        | Error(error) => Error(error)
+        }
+      | _ => Belt.Result.Error(@reason.raw_literal("Expected array") "Expected array")
+      }
+  )(value)
+and deserialize_Belt_HashMapInt____t: 'arg0. (
+  Json.t => Belt.Result.t<'arg0, string>,
+  Json.t,
+) => Belt.Result.t<Belt_HashMapInt.t<'arg0>, string> = bTransformer =>
+  TransformHelpers.deserialize_Belt_HashMapInt____t(bTransformer)
+let rec serialize_Hello__TryIt____lockfile: MyNamespace.Hello.lockfile => Json.t = record => Json.Object(list{
+  ("version", (i => Json.Number(float_of_int(i)))(record.version)),
+  (
+    "pastVersions",
+    serialize_Belt_HashMapInt____t(list => Json.Array(
+      Belt.List.map(list, ((arg0, arg1)) => Json.Array(list{
+        serialize_Hello__TryIt____shortReference(arg0),
+        (i => Json.Number(float_of_int(i)))(arg1),
+      })),
+    ))(record.pastVersions),
+  ),
+  (
+    "current",
+    (
+      list => Json.Array(
+        Belt.List.map(list, ((arg0, arg1)) => Json.Array(list{
+          serialize_Hello__TryIt____shortReference(arg0),
+          (i => Json.Number(float_of_int(i)))(arg1),
+        })),
+      )
+    )(record.current),
+  ),
+})
+and serialize_Hello__TryIt____shortReference: MyNamespace.Hello.shortReference => Json.t = value =>
+  (
+    ((arg0, arg1, arg2)) => Json.Array(list{
+      (s => Json.String(s))(arg0),
+      (list => Json.Array(Belt.List.map(list, s => Json.String(s))))(arg1),
+      (s => Json.String(s))(arg2),
+    })
+  )(value)
+and serialize_Belt_HashMapInt____t: 'arg0. (
+  'arg0 => Json.t,
+  Belt_HashMapInt.t<'arg0>,
+) => Json.t = bTransformer => TransformHelpers.serialize_Belt_HashMapInt____t(bTransformer)
diff --git a/analysis/examples/example-project/src/TransformHelpers.res b/analysis/examples/example-project/src/TransformHelpers.res
new file mode 100644
index 0000000000..b0ab646850
--- /dev/null
+++ b/analysis/examples/example-project/src/TransformHelpers.res
@@ -0,0 +1,12 @@
+let deserialize_Belt__HashMapInt__t = (transformer, t) => assert false
+
+let deserialize_Belt_HashMapInt____t = (a, b) => assert false
+
+let deserialize_Belt__HashMap__Int__t = (a, b) => assert false
+
+let serialize_Belt_HashMapInt____t = (a, b) => assert false
+
+let serialize_Belt__HashMap__Int__t = (a, b) => assert false
+
+let serialize_Belt_HashMapInt____t = (transformer, t) => assert false
+
diff --git a/analysis/examples/example-project/src/ZZ.res b/analysis/examples/example-project/src/ZZ.res
new file mode 100644
index 0000000000..6a3c9ddcd5
--- /dev/null
+++ b/analysis/examples/example-project/src/ZZ.res
@@ -0,0 +1,145 @@
+let a = 12
+
+let b = [1, 2, 3, a]
+
+let c = <div />
+
+let s = React.string
+
+module M = {
+  @react.component
+  let make = (~x) => React.string(x)
+}
+
+let d = <M x="abc" />
+
+module J = {
+  @react.component
+  export make = (~children: React.element) => React.null
+}
+
+let z = <J> {React.string("")} {React.string("")} </J>
+
+type inline =
+  | A({x: int, y: string})
+  | B({x: int, y: string})
+  | C({
+      x: int,
+      y: string,
+      z: string,
+      w: string,
+      x0: string,
+      q1: string,
+      q2: string,
+      q3: string,
+      q4: string,
+    })
+  | D({x: int, y: string})
+  | E({x: int, y: string})
+  | F
+
+module MSig: {
+  type rec t = A(list<s>)
+  and s = list<t>
+
+  let x: int
+} = {
+  type rec t = A(list<s>)
+  and s = list<t>
+
+  let x = 14
+}
+
+module Impl = {
+  type rec t = A(list<s>)
+  and s = list<t>
+
+  type w = int
+
+  let x = 14
+}
+
+module Impl2 = {
+  include Impl
+}
+
+module D = MSig
+module E = Impl
+module F = Impl2
+
+@ocaml.doc("str docstring")
+type str = string
+
+@ocaml.doc("gr docstring")
+type gr = {x: int, s: str}
+
+let testRecordFields = (gr: gr) => {
+  let str = gr.s
+  str
+}
+
+@ocaml.doc("vr docstring")
+type vr = V1 | V2
+
+let v1 = V1
+
+module DoubleNested = ModuleWithDocComment.Nested.NestedAgain
+
+let uncurried = (. x) => x + 1
+
+module Inner = {
+  type tInner = int
+  let vInner = 34
+}
+
+type typeInner = Inner.tInner
+
+let valueInner = Inner.vInner
+
+@ocaml.doc("Doc comment for functionWithTypeAnnotation")
+let functionWithTypeAnnotation: unit => int = () => 1
+
+module HoverInsideModuleWithComponent = {
+  let x = 2 // check that hover on x works
+
+  @react.component
+  let make = () => React.null
+}
+
+module Lib = {
+  let foo = (~age, ~name) => name ++ string_of_int(age)
+  let next = (~number=0, ~year) => number + year
+}
+
+@ocaml.doc("This module is commented") @deprecated("This module is deprecated")
+module Dep: {
+  @ocaml.doc("Some doc comment") @deprecated("Use customDouble instead")
+  let customDouble: int => int
+
+  let customDouble2: int => int
+} = {
+  let customDouble = foo => foo * 2
+  let customDouble2 = foo => foo * 2
+}
+
+let cc = Dep.customDouble(11)
+
+module O = {
+  module Comp = {
+    @react.component
+    let make = (~first="", ~kas=11, ~foo=3, ~second, ~v) =>
+      React.string(first ++ second ++ string_of_int(foo))
+  }
+}
+
+let comp = <O.Comp key="12" second="abcc" v=12 />
+
+let lll = List.make(3, 4)
+
+let abc = "abc"
+
+let arr = [1, 2, 3]
+
+let some7 = Some(7)
+
+
diff --git a/analysis/examples/example-project/src/syntax/sample-highlighting.res b/analysis/examples/example-project/src/syntax/sample-highlighting.res
new file mode 100644
index 0000000000..3d8930c31e
--- /dev/null
+++ b/analysis/examples/example-project/src/syntax/sample-highlighting.res
@@ -0,0 +1,77 @@
+// Bindings
+let numberBinding = 123
+
+let someFunction = (param: int): int => {
+  let innerBinding = param + 2
+  innerBinding
+}
+
+// Types
+type someRecord<'typeParameter> = {
+  someField: int,
+  someOtherField: string,
+  theParam: typeParameter,
+  another: bool,
+  to: string,
+}
+
+type someEnum =
+  | SomeMember
+  | AnotherMember
+  | SomeMemberWithPayload(someRecord<int>)
+
+type somePolyEnum = [
+  | #someMember
+  | #AnotherMember
+  | #SomeMemberWithPayload(someRecord<int>)
+  | #"fourth Member"
+]
+
+// Destructuring
+let destructuring = () => {
+  let someVar = (1, 2, 3)
+  let (one, two, three) = someVar
+  let someObj: someRecord<int> = {
+    someField: 1,
+    someOtherField: "hello",
+    theParam: 2,
+    another: true,
+    to: "123",
+  }
+  let {someField, someOtherField, theParam} = someObj
+
+  someField
+}
+
+module SomeModule = {
+  type t = Some | Value | Here
+}
+
+// Strings
+let interpolated = `${numberBinding} ${"123"}`
+
+// JSX
+module SomeComponent = {
+  @react.component
+  let make = (
+    ~someProp: int,
+    ~otherProp: string,
+    ~thirdProp: SomeModule.t,
+    ~fourth: somePolyEnum=#"fourth member",
+  ) => {
+    React.null
+  }
+
+  module Nested = {
+    @react.component
+    let make = (~children) => {
+      <> {children} </>
+    }
+  }
+}
+
+let jsx =
+  <div>
+    <SomeComponent someProp=123 otherProp="hello" thirdProp=Value fourth=#AnotherMember />
+    <SomeComponent.Nested> {React.string("Nested")} </SomeComponent.Nested>
+  </div>
diff --git a/analysis/examples/example-project/src/syntax/sample-highlighting.rs b/analysis/examples/example-project/src/syntax/sample-highlighting.rs
new file mode 100644
index 0000000000..7131ba6008
--- /dev/null
+++ b/analysis/examples/example-project/src/syntax/sample-highlighting.rs
@@ -0,0 +1,31 @@
+// Bindings
+fn some_function(param: usize) -> usize {
+    let innerBinding = param + 2;
+    innerBinding
+}
+
+// Types
+struct someRecord<typeParameter> {
+    someField: usize,
+    someOtherField: String,
+    theParam: typeParameter,
+}
+
+enum someEnum {
+    SomeMember,
+    AnotherMember,
+    SomeMemberWithPayload(someRecord<usize>),
+}
+
+// Destructuring
+fn destructuring() -> usize {
+    let someVar = (1, 2, 3);
+    let (one, two, three) = someVar;
+    let someObj = someRecord::<usize> {
+        someField: 1,
+        someOtherField: String::new("HEllo"),
+        theParam: 2,
+    };
+
+    someObj.someField
+}
diff --git a/analysis/examples/example-project/src/syntax/sample-highlighting.tsx b/analysis/examples/example-project/src/syntax/sample-highlighting.tsx
new file mode 100644
index 0000000000..b646a4cfb1
--- /dev/null
+++ b/analysis/examples/example-project/src/syntax/sample-highlighting.tsx
@@ -0,0 +1,94 @@
+// Bindings
+let numberBinding = 123;
+
+const SomeComp = {
+  Nested: () => null,
+};
+
+let someFunction = (param: number): number => {
+  let innerBinding = param + 2;
+  return innerBinding;
+};
+
+// Types
+type someRecord<typeParameter> = {
+  someField: number;
+  someOtherField: string;
+  theParam: typeParameter;
+  another: boolean;
+  to: string;
+};
+
+enum someEnum {
+  SomeMember,
+  AnotherMember,
+}
+
+// Destructuring
+let destructuring = () => {
+  let someVar = [1, 2, 3];
+  let [one, two, three] = someVar;
+  let someObj: someRecord<number> = {
+    someField: 1,
+    someOtherField: "hello",
+    theParam: 2,
+    another: true,
+    to: "123",
+  };
+  let { someField, someOtherField, theParam } = someObj;
+
+  return someField;
+};
+
+namespace SomeModule {
+  export enum t {
+    Some,
+    Value,
+    Here,
+  }
+}
+
+// Decorators and classes
+function someDecorator() {
+  return function (
+    target: any,
+    propertyKey: string,
+    descriptor: PropertyDescriptor
+  ) {
+    console.log("first(): called");
+  };
+}
+
+class SomeClass {
+  @someDecorator() doStuff() {
+    return 123;
+  }
+}
+
+// Strings
+let interpolated = `${numberBinding} ${"123"}`;
+
+// JSX
+interface Props {
+  someProp: number;
+  otherProp: string;
+  thirdProp: SomeModule.t;
+}
+const SomeComponent = ({ someProp, otherProp, thirdProp }: Props) => {
+  return null;
+};
+
+let jsx = (
+  <div>
+    <SomeComponent
+      someProp={123}
+      otherProp="hello"
+      thirdProp={SomeModule.t.Value}
+    />
+    <SomeComp.Nested />
+    {"Hello"}
+  </div>
+);
+function Property() {
+  throw new Error("Function not implemented.");
+}
diff --git a/analysis/examples/example-project/types.json b/analysis/examples/example-project/types.json
new file mode 100644
index 0000000000..904cdf616e
--- /dev/null
+++ b/analysis/examples/example-project/types.json
@@ -0,0 +1,13 @@
+{
+  "output": "src/Serde.ml",
+  "engine": "rex-json",
+  "entries": [
+    {
+      "file": "src/Hello.re",
+      "type": "lockfile"
+    }
+  ],
+  "custom": [
+    {"module": "Belt_HashMapInt", "path": [], "name": "t", "args": 1}
+  ]
+}
\ No newline at end of file
diff --git a/analysis/examples/larger-project/.gitignore b/analysis/examples/larger-project/.gitignore
new file mode 100644
index 0000000000..1ccc52a7fb
--- /dev/null
+++ b/analysis/examples/larger-project/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/lib
\ No newline at end of file
diff --git a/analysis/examples/larger-project/.merlin b/analysis/examples/larger-project/.merlin
new file mode 100644
index 0000000000..05434917e9
--- /dev/null
+++ b/analysis/examples/larger-project/.merlin
@@ -0,0 +1,14 @@
+####{BSB GENERATED: NO EDIT
+FLG -ppx '/home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/linux/bsc.exe -as-ppx -bs-jsx 3'
+S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
+B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
+FLG -w +a-4-9-20-40-41-42-50-61-102
+S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
+B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
+S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
+B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
+S src
+B lib/bs/src
+S src/exception
+B lib/bs/src/exception
+####BSB GENERATED: NO EDIT}
diff --git a/analysis/examples/larger-project/.watchmanconfig b/analysis/examples/larger-project/.watchmanconfig
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/bsconfig.json b/analysis/examples/larger-project/bsconfig.json
new file mode 100644
index 0000000000..e3a2f6f551
--- /dev/null
+++ b/analysis/examples/larger-project/bsconfig.json
@@ -0,0 +1,21 @@
+{
+  "reanalyze": {
+    "analysis": ["dce"],
+    "suppress": [],
+    "unsuppress": []
+  },
+  "name": "sample-typescript-app",
+  "bsc-flags": ["-bs-super-errors -w a"],
+  "reason": { "react-jsx": 3 },
+  "bs-dependencies": ["@rescript/react", "@glennsl/bs-json"],
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "package-specs": {
+    "module": "es6",
+    "in-source": true
+  }
+}
diff --git a/analysis/examples/larger-project/package-lock.json b/analysis/examples/larger-project/package-lock.json
new file mode 100644
index 0000000000..8747db2f28
--- /dev/null
+++ b/analysis/examples/larger-project/package-lock.json
@@ -0,0 +1,192 @@
+{
+  "name": "large-project",
+  "version": "0.1.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "large-project",
+      "version": "0.1.0",
+      "dependencies": {
+        "@glennsl/bs-json": "^5.0.4",
+        "@rescript/react": "^0.10.3"
+      },
+      "devDependencies": {
+        "react": "^16.13.1",
+        "react-dom": "^16.8.6",
+        "rescript": "^9.1.4"
+      }
+    },
+    "../../../../../rescript-compiler": {
+      "name": "rescript",
+      "version": "10.0.0",
+      "extraneous": true,
+      "hasInstallScript": true,
+      "license": "SEE LICENSE IN LICENSE",
+      "bin": {
+        "bsc": "bsc",
+        "bsrefmt": "bsrefmt",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      },
+      "devDependencies": {
+        "mocha": "^7.2.0",
+        "nyc": "^15.0.0"
+      }
+    },
+    "node_modules/@glennsl/bs-json": {
+      "version": "5.0.4",
+      "license": "(LGPL-3.0 OR MPL-2.0)"
+    },
+    "node_modules/@rescript/react": {
+      "version": "0.10.3",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=16.8.1",
+        "react-dom": ">=16.8.1"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "license": "MIT"
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/prop-types": {
+      "version": "15.8.1",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "node_modules/react": {
+      "version": "16.14.0",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "16.14.0",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
+      },
+      "peerDependencies": {
+        "react": "^16.14.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "license": "MIT"
+    },
+    "node_modules/rescript": {
+      "version": "9.1.4",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-9.1.4.tgz",
+      "integrity": "sha512-aXANK4IqecJzdnDpJUsU6pxMViCR5ogAxzuqS0mOr8TloMnzAjJFu63fjD6LCkWrKAhlMkFFzQvVQYaAaVkFXw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "bsc": "bsc",
+        "bsrefmt": "bsrefmt",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.19.1",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1"
+      }
+    }
+  },
+  "dependencies": {
+    "@glennsl/bs-json": {
+      "version": "5.0.4"
+    },
+    "@rescript/react": {
+      "version": "0.10.3",
+      "requires": {}
+    },
+    "js-tokens": {
+      "version": "4.0.0"
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "object-assign": {
+      "version": "4.1.1"
+    },
+    "prop-types": {
+      "version": "15.8.1",
+      "requires": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "react": {
+      "version": "16.14.0",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
+      }
+    },
+    "react-dom": {
+      "version": "16.14.0",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
+      }
+    },
+    "react-is": {
+      "version": "16.13.1"
+    },
+    "rescript": {
+      "version": "9.1.4",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-9.1.4.tgz",
+      "integrity": "sha512-aXANK4IqecJzdnDpJUsU6pxMViCR5ogAxzuqS0mOr8TloMnzAjJFu63fjD6LCkWrKAhlMkFFzQvVQYaAaVkFXw==",
+      "dev": true
+    },
+    "scheduler": {
+      "version": "0.19.1",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1"
+      }
+    }
+  }
+}
diff --git a/analysis/examples/larger-project/package.json b/analysis/examples/larger-project/package.json
new file mode 100644
index 0000000000..1426ee18c6
--- /dev/null
+++ b/analysis/examples/larger-project/package.json
@@ -0,0 +1,19 @@
+{
+  "name": "large-project",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "start": "rescript build -w",
+    "build": "rescript build",
+    "clean": "rescript clean -with-deps"
+  },
+  "devDependencies": {
+    "react": "^16.13.1",
+    "react-dom": "^16.8.6",
+    "rescript": "^9.1.4"
+  },
+  "dependencies": {
+    "@glennsl/bs-json": "^5.0.4",
+    "@rescript/react": "^0.10.3"
+  }
+}
diff --git a/analysis/examples/larger-project/src/AutoAnnotate.js b/analysis/examples/larger-project/src/AutoAnnotate.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/AutoAnnotate.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/AutoAnnotate.res b/analysis/examples/larger-project/src/AutoAnnotate.res
new file mode 100644
index 0000000000..71f125728a
--- /dev/null
+++ b/analysis/examples/larger-project/src/AutoAnnotate.res
@@ -0,0 +1,15 @@
+type variant = R(int)
+
+@genType
+type record = {variant: variant}
+
+type r2 = {r2: int}
+
+type r3 = {r3: int}
+
+type r4 = {r4: int}
+
+@genType
+type annotatedVariant =
+  | R2(r2, r3)
+  | R4(r4)
diff --git a/analysis/examples/larger-project/src/BootloaderResource.js b/analysis/examples/larger-project/src/BootloaderResource.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/BootloaderResource.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/BootloaderResource.res b/analysis/examples/larger-project/src/BootloaderResource.res
new file mode 100644
index 0000000000..a215607645
--- /dev/null
+++ b/analysis/examples/larger-project/src/BootloaderResource.res
@@ -0,0 +1,4 @@
+/* NOTE: This is a spooky interface that provides no type safety. It should be
+ * improved. Use with caution. */
+@module("BootloaderResource")
+external read: JSResource.t<'a> => 'a = "read"
diff --git a/analysis/examples/larger-project/src/BucklescriptAnnotations.js b/analysis/examples/larger-project/src/BucklescriptAnnotations.js
new file mode 100644
index 0000000000..7dab449cd0
--- /dev/null
+++ b/analysis/examples/larger-project/src/BucklescriptAnnotations.js
@@ -0,0 +1,13 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function bar(x) {
+  var f = x.twoArgs;
+  return f(3, "a");
+}
+
+export {
+  bar ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/BucklescriptAnnotations.res b/analysis/examples/larger-project/src/BucklescriptAnnotations.res
new file mode 100644
index 0000000000..053bca66b0
--- /dev/null
+++ b/analysis/examples/larger-project/src/BucklescriptAnnotations.res
@@ -0,0 +1,28 @@
+@genType
+type someMutableFields = {
+  @set
+  "mutable0": string,
+  "immutable": int,
+  @set
+  "mutable1": string,
+  @set
+  "mutable2": string,
+}
+
+@genType
+type someMethods = {
+  @meth
+  "send": string => unit,
+  @meth
+  "on": (string, (. int) => unit) => unit,
+  @meth
+  "threeargs": (int, string, int) => string,
+  "twoArgs": (. int, string) => int,
+}
+
+// let foo = (x: someMethods) => x["threeargs"](3, "a", 4)
+
+let bar = (x: someMethods) => {
+  let f = x["twoArgs"]
+  f(. 3, "a")
+}
diff --git a/analysis/examples/larger-project/src/ComponentAsProp.js b/analysis/examples/larger-project/src/ComponentAsProp.js
new file mode 100644
index 0000000000..f3eba08a72
--- /dev/null
+++ b/analysis/examples/larger-project/src/ComponentAsProp.js
@@ -0,0 +1,19 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as React from "react";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+
+function ComponentAsProp(Props) {
+  var title = Props.title;
+  var description = Props.description;
+  var button = Props.button;
+  return React.createElement("div", undefined, React.createElement("div", undefined, title, description, button !== undefined ? Caml_option.valFromOption(button) : null));
+}
+
+var make = ComponentAsProp;
+
+export {
+  make ,
+  
+}
+/* react Not a pure module */
diff --git a/analysis/examples/larger-project/src/ComponentAsProp.res b/analysis/examples/larger-project/src/ComponentAsProp.res
new file mode 100644
index 0000000000..a1f3d45658
--- /dev/null
+++ b/analysis/examples/larger-project/src/ComponentAsProp.res
@@ -0,0 +1,17 @@
+@ocaml.doc(
+  " This is like declaring a normal ReasonReact component's `make` function, except the body is a the interop hook wrapJsForReason "
+)
+@genType
+@react.component
+let make = (~title, ~description, ~button=?) => {
+  <div>
+    <div>
+      title
+      description
+      {switch button {
+      | Some(button) => button
+      | None => React.null
+      }}
+    </div>
+  </div>
+}
diff --git a/analysis/examples/larger-project/src/CreateErrorHandler1.js b/analysis/examples/larger-project/src/CreateErrorHandler1.js
new file mode 100644
index 0000000000..6adeaad3fe
--- /dev/null
+++ b/analysis/examples/larger-project/src/CreateErrorHandler1.js
@@ -0,0 +1,26 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as ErrorHandler from "./ErrorHandler.js";
+
+function notification(s) {
+  return [
+          s,
+          s
+        ];
+}
+
+var Error1 = {
+  notification: notification
+};
+
+var MyErrorHandler = ErrorHandler.Make(Error1);
+
+Curry._1(MyErrorHandler.notify, "abc");
+
+export {
+  Error1 ,
+  MyErrorHandler ,
+  
+}
+/* MyErrorHandler Not a pure module */
diff --git a/analysis/examples/larger-project/src/CreateErrorHandler1.res b/analysis/examples/larger-project/src/CreateErrorHandler1.res
new file mode 100644
index 0000000000..e10b58c1d3
--- /dev/null
+++ b/analysis/examples/larger-project/src/CreateErrorHandler1.res
@@ -0,0 +1,8 @@
+module Error1 = {
+  type t = string
+  let notification = s => (s, s)
+}
+
+module MyErrorHandler = ErrorHandler.Make(Error1)
+
+MyErrorHandler.notify("abc")
diff --git a/analysis/examples/larger-project/src/CreateErrorHandler2.js b/analysis/examples/larger-project/src/CreateErrorHandler2.js
new file mode 100644
index 0000000000..6855cbd830
--- /dev/null
+++ b/analysis/examples/larger-project/src/CreateErrorHandler2.js
@@ -0,0 +1,23 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as ErrorHandler from "./ErrorHandler.js";
+
+function notification(n) {
+  return [
+          String(n),
+          ""
+        ];
+}
+
+var Error2 = {
+  notification: notification
+};
+
+var MyErrorHandler = ErrorHandler.Make(Error2);
+
+export {
+  Error2 ,
+  MyErrorHandler ,
+  
+}
+/* MyErrorHandler Not a pure module */
diff --git a/analysis/examples/larger-project/src/CreateErrorHandler2.res b/analysis/examples/larger-project/src/CreateErrorHandler2.res
new file mode 100644
index 0000000000..394907545e
--- /dev/null
+++ b/analysis/examples/larger-project/src/CreateErrorHandler2.res
@@ -0,0 +1,6 @@
+module Error2 = {
+  type t = int
+  let notification = n => (string_of_int(n), "")
+}
+
+module MyErrorHandler = ErrorHandler.Make(Error2) /* MyErrorHandler.notify(42) */
diff --git a/analysis/examples/larger-project/src/DeadCodeImplementation.js b/analysis/examples/larger-project/src/DeadCodeImplementation.js
new file mode 100644
index 0000000000..457027834c
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadCodeImplementation.js
@@ -0,0 +1,12 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var M = {
+  x: 42
+};
+
+export {
+  M ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/DeadCodeImplementation.res b/analysis/examples/larger-project/src/DeadCodeImplementation.res
new file mode 100644
index 0000000000..54abe4eadf
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadCodeImplementation.res
@@ -0,0 +1,3 @@
+module M: DeadCodeInterface.T = {
+  let x = 42
+}
diff --git a/analysis/examples/larger-project/src/DeadCodeInterface.js b/analysis/examples/larger-project/src/DeadCodeInterface.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadCodeInterface.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/DeadCodeInterface.res b/analysis/examples/larger-project/src/DeadCodeInterface.res
new file mode 100644
index 0000000000..069bea77a0
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadCodeInterface.res
@@ -0,0 +1,3 @@
+module type T = {
+  let x: int
+}
diff --git a/analysis/examples/larger-project/src/DeadExn.js b/analysis/examples/larger-project/src/DeadExn.js
new file mode 100644
index 0000000000..c2a21594d7
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadExn.js
@@ -0,0 +1,19 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var Etoplevel = /* @__PURE__ */Caml_exceptions.create("DeadExn.Etoplevel");
+
+var Einside = /* @__PURE__ */Caml_exceptions.create("DeadExn.Inside.Einside");
+
+var eInside = {
+  RE_EXN_ID: Einside
+};
+
+console.log(eInside);
+
+export {
+  Etoplevel ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/DeadExn.res b/analysis/examples/larger-project/src/DeadExn.res
new file mode 100644
index 0000000000..a515033a0d
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadExn.res
@@ -0,0 +1,12 @@
+exception Etoplevel
+
+module Inside = {
+  exception Einside
+}
+
+exception DeadE
+let eToplevel = Etoplevel
+
+let eInside = Inside.Einside
+
+Js.log(eInside)
diff --git a/analysis/examples/larger-project/src/DeadExn.resi b/analysis/examples/larger-project/src/DeadExn.resi
new file mode 100644
index 0000000000..1686894210
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadExn.resi
@@ -0,0 +1,2 @@
+// empty
+exception Etoplevel
diff --git a/analysis/examples/larger-project/src/DeadRT.js b/analysis/examples/larger-project/src/DeadRT.js
new file mode 100644
index 0000000000..028c363b5a
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadRT.js
@@ -0,0 +1,9 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+console.log(/* Kaboom */0);
+
+export {
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/DeadRT.res b/analysis/examples/larger-project/src/DeadRT.res
new file mode 100644
index 0000000000..1ebbc4c32b
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadRT.res
@@ -0,0 +1,11 @@
+type moduleAccessPath =
+  | Root(string)
+  | Kaboom
+
+let rec emitModuleAccessPath = moduleAccessPath =>
+  switch moduleAccessPath {
+  | Root(s) => s
+  | Kaboom => ""
+  }
+
+let () = Js.log(Kaboom)
diff --git a/analysis/examples/larger-project/src/DeadRT.resi b/analysis/examples/larger-project/src/DeadRT.resi
new file mode 100644
index 0000000000..123cd38f6f
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadRT.resi
@@ -0,0 +1,3 @@
+type moduleAccessPath =
+  | Root(string)
+  | Kaboom
diff --git a/analysis/examples/larger-project/src/DeadTest.js b/analysis/examples/larger-project/src/DeadTest.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTest.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/DeadTest.res b/analysis/examples/larger-project/src/DeadTest.res
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/src/DeadTestBlacklist.js b/analysis/examples/larger-project/src/DeadTestBlacklist.js
new file mode 100644
index 0000000000..9a29139bd4
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTestBlacklist.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var x = 34;
+
+export {
+  x ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/DeadTestBlacklist.res b/analysis/examples/larger-project/src/DeadTestBlacklist.res
new file mode 100644
index 0000000000..5681c08c62
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTestBlacklist.res
@@ -0,0 +1 @@
+let x = 34
diff --git a/analysis/examples/larger-project/src/DeadTestWithInterface.js b/analysis/examples/larger-project/src/DeadTestWithInterface.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTestWithInterface.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/DeadTestWithInterface.res b/analysis/examples/larger-project/src/DeadTestWithInterface.res
new file mode 100644
index 0000000000..4d50cd03fc
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTestWithInterface.res
@@ -0,0 +1,5 @@
+module Ext_buffer: {
+  let x: int
+} = {
+  let x = 42
+}
diff --git a/analysis/examples/larger-project/src/DeadTestWithInterface.resi b/analysis/examples/larger-project/src/DeadTestWithInterface.resi
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTestWithInterface.resi
@@ -0,0 +1 @@
+
diff --git a/analysis/examples/larger-project/src/DeadTypeTest.js b/analysis/examples/larger-project/src/DeadTypeTest.js
new file mode 100644
index 0000000000..9740261e39
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTypeTest.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var a = /* A */0;
+
+export {
+  a ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/DeadTypeTest.res b/analysis/examples/larger-project/src/DeadTypeTest.res
new file mode 100644
index 0000000000..6b70da5546
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTypeTest.res
@@ -0,0 +1,16 @@
+type t =
+  | A
+  | B
+let a = A
+
+type deadType =
+  | OnlyInImplementation
+  | OnlyInInterface
+  | InBoth
+  | InNeither
+
+let _ = OnlyInImplementation
+let _ = InBoth
+
+@live
+type record = {x: int, y: string, z: float}
diff --git a/analysis/examples/larger-project/src/DeadTypeTest.resi b/analysis/examples/larger-project/src/DeadTypeTest.resi
new file mode 100644
index 0000000000..317bc02c14
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadTypeTest.resi
@@ -0,0 +1,10 @@
+type t =
+  | A
+  | B
+let a: t
+
+type deadType =
+  | OnlyInImplementation
+  | OnlyInInterface
+  | InBoth
+  | InNeither
diff --git a/analysis/examples/larger-project/src/DeadValueTest.js b/analysis/examples/larger-project/src/DeadValueTest.js
new file mode 100644
index 0000000000..2b37b2ec1b
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadValueTest.js
@@ -0,0 +1,13 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var valueAlive = 1;
+
+var valueDead = 2;
+
+export {
+  valueAlive ,
+  valueDead ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/DeadValueTest.res b/analysis/examples/larger-project/src/DeadValueTest.res
new file mode 100644
index 0000000000..a3fd03b6fc
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadValueTest.res
@@ -0,0 +1,21 @@
+let valueAlive = 1
+let valueDead = 2
+
+let valueOnlyInImplementation = 3
+
+@raises(Failure)
+let rec subList = (b, e, l) =>
+  switch l {
+  | list{} => failwith("subList")
+  | list{h, ...t} =>
+    let tail = if e == 0 {
+      list{}
+    } else {
+      subList(b - 1, e - 1, t)
+    }
+    if b > 0 {
+      tail
+    } else {
+      list{h, ...tail}
+    }
+  }
diff --git a/analysis/examples/larger-project/src/DeadValueTest.resi b/analysis/examples/larger-project/src/DeadValueTest.resi
new file mode 100644
index 0000000000..4f6bb0cc2f
--- /dev/null
+++ b/analysis/examples/larger-project/src/DeadValueTest.resi
@@ -0,0 +1,2 @@
+let valueAlive: int
+let valueDead: int
diff --git a/analysis/examples/larger-project/src/Docstrings.js b/analysis/examples/larger-project/src/Docstrings.js
new file mode 100644
index 0000000000..e06b04eb77
--- /dev/null
+++ b/analysis/examples/larger-project/src/Docstrings.js
@@ -0,0 +1,100 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function signMessage(message, key) {
+  return message + String(key);
+}
+
+function one(a) {
+  return a + 0 | 0;
+}
+
+function two(a, b) {
+  return (a + b | 0) + 0 | 0;
+}
+
+function tree(a, b, c) {
+  return ((a + b | 0) + c | 0) + 0 | 0;
+}
+
+function oneU(a) {
+  return a + 0 | 0;
+}
+
+function twoU(a, b) {
+  return (a + b | 0) + 0 | 0;
+}
+
+function treeU(a, b, c) {
+  return ((a + b | 0) + c | 0) + 0 | 0;
+}
+
+function useParam(param) {
+  return param + 34 | 0;
+}
+
+function useParamU(param) {
+  return param + 34 | 0;
+}
+
+function unnamed1(param) {
+  return 34;
+}
+
+function unnamed1U(param) {
+  return 34;
+}
+
+function unnamed2(param, param$1) {
+  return 34;
+}
+
+function unnamed2U(param, param$1) {
+  return 34;
+}
+
+function grouped(x, y, a, b, c, z) {
+  return ((((x + y | 0) + a | 0) + b | 0) + c | 0) + z | 0;
+}
+
+function unitArgWithoutConversion(param) {
+  return "abc";
+}
+
+function unitArgWithoutConversionU() {
+  return "abc";
+}
+
+function unitArgWithConversion(param) {
+  return /* A */0;
+}
+
+function unitArgWithConversionU() {
+  return /* A */0;
+}
+
+var flat = 34;
+
+export {
+  flat ,
+  signMessage ,
+  one ,
+  two ,
+  tree ,
+  oneU ,
+  twoU ,
+  treeU ,
+  useParam ,
+  useParamU ,
+  unnamed1 ,
+  unnamed1U ,
+  unnamed2 ,
+  unnamed2U ,
+  grouped ,
+  unitArgWithoutConversion ,
+  unitArgWithoutConversionU ,
+  unitArgWithConversion ,
+  unitArgWithConversionU ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Docstrings.res b/analysis/examples/larger-project/src/Docstrings.res
new file mode 100644
index 0000000000..ca3d7ee035
--- /dev/null
+++ b/analysis/examples/larger-project/src/Docstrings.res
@@ -0,0 +1,67 @@
+@ocaml.doc(" hello ") @genType
+let flat = 34
+
+@ocaml.doc("
+  * Sign a message with a key.
+  *
+  * @param message - A message to be signed
+  * @param key - The key with which to sign the message
+  * @returns A signed message
+ ")
+@genType
+let signMessage = (. message, key) => message ++ string_of_int(key)
+
+@genType
+let one = a => a + 0
+
+@genType
+let two = (a, b) => a + b + 0
+
+@genType
+let tree = (a, b, c) => a + b + c + 0
+
+@genType
+let oneU = (. a) => a + 0
+
+@genType
+let twoU = (. a, b) => a + b + 0
+
+@genType
+let treeU = (. a, b, c) => a + b + c + 0
+
+@genType
+let useParam = param => param + 34
+
+@genType
+let useParamU = (. param) => param + 34
+
+@genType
+let unnamed1 = (_: int) => 34
+
+@genType
+let unnamed1U = (. _: int) => 34
+
+@genType
+let unnamed2 = (_: int, _: int) => 34
+
+@genType
+let unnamed2U = (. _: int, _: int) => 34
+
+@genType
+let grouped = (~x, ~y, a, b, c, ~z) => x + y + a + b + c + z
+
+@genType
+let unitArgWithoutConversion = () => "abc"
+
+@genType
+let unitArgWithoutConversionU = (. ()) => "abc"
+
+type t =
+  | A
+  | B
+
+@genType
+let unitArgWithConversion = () => A
+
+@genType
+let unitArgWithConversionU = (. ()) => A
diff --git a/analysis/examples/larger-project/src/DynamicallyLoadedComponent.js b/analysis/examples/larger-project/src/DynamicallyLoadedComponent.js
new file mode 100644
index 0000000000..984fa357b1
--- /dev/null
+++ b/analysis/examples/larger-project/src/DynamicallyLoadedComponent.js
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function DynamicallyLoadedComponent(Props) {
+  return Props.s;
+}
+
+var make = DynamicallyLoadedComponent;
+
+export {
+  make ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/DynamicallyLoadedComponent.res b/analysis/examples/larger-project/src/DynamicallyLoadedComponent.res
new file mode 100644
index 0000000000..b7b93b52c1
--- /dev/null
+++ b/analysis/examples/larger-project/src/DynamicallyLoadedComponent.res
@@ -0,0 +1,2 @@
+@react.component
+let make = (~s) => React.string(s)
diff --git a/analysis/examples/larger-project/src/EmptyArray.js b/analysis/examples/larger-project/src/EmptyArray.js
new file mode 100644
index 0000000000..6e55be63af
--- /dev/null
+++ b/analysis/examples/larger-project/src/EmptyArray.js
@@ -0,0 +1,19 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as React from "react";
+
+function EmptyArray$Z(Props) {
+  return React.createElement("br", undefined);
+}
+
+var Z = {
+  make: EmptyArray$Z
+};
+
+React.createElement(EmptyArray$Z, {});
+
+export {
+  Z ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/EmptyArray.res b/analysis/examples/larger-project/src/EmptyArray.res
new file mode 100644
index 0000000000..9c44bd9a7b
--- /dev/null
+++ b/analysis/examples/larger-project/src/EmptyArray.res
@@ -0,0 +1,10 @@
+// @@config({flags : ["-dsource"]});
+
+module Z = {
+  @react.component
+  let make = () => {
+    <br />
+  }
+}
+
+let _ = <Z />
diff --git a/analysis/examples/larger-project/src/ErrorHandler.js b/analysis/examples/larger-project/src/ErrorHandler.js
new file mode 100644
index 0000000000..b79220461e
--- /dev/null
+++ b/analysis/examples/larger-project/src/ErrorHandler.js
@@ -0,0 +1,21 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+
+function Make($$Error) {
+  var notify = function (x) {
+    return Curry._1($$Error.notification, x);
+  };
+  return {
+          notify: notify
+        };
+}
+
+var x = 42;
+
+export {
+  Make ,
+  x ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/ErrorHandler.res b/analysis/examples/larger-project/src/ErrorHandler.res
new file mode 100644
index 0000000000..557272ca13
--- /dev/null
+++ b/analysis/examples/larger-project/src/ErrorHandler.res
@@ -0,0 +1,12 @@
+module type Error = {
+  type t
+  let notification: t => (string, string)
+}
+
+module Make = (Error: Error) => {
+  let notify = x => Error.notification(x)
+}
+
+// This is ignored as there's an interface file
+@genType
+let x = 42
diff --git a/analysis/examples/larger-project/src/ErrorHandler.resi b/analysis/examples/larger-project/src/ErrorHandler.resi
new file mode 100644
index 0000000000..fac11f7511
--- /dev/null
+++ b/analysis/examples/larger-project/src/ErrorHandler.resi
@@ -0,0 +1,10 @@
+module type Error = {
+  type t
+  let notification: t => (string, string)
+}
+module Make: (Error: Error) =>
+{
+  let notify: Error.t => (string, string)
+}
+
+let x: int
diff --git a/analysis/examples/larger-project/src/EverythingLiveHere.js b/analysis/examples/larger-project/src/EverythingLiveHere.js
new file mode 100644
index 0000000000..864f5020ec
--- /dev/null
+++ b/analysis/examples/larger-project/src/EverythingLiveHere.js
@@ -0,0 +1,16 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var x = 1;
+
+var y = 3;
+
+var z = 4;
+
+export {
+  x ,
+  y ,
+  z ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/EverythingLiveHere.res b/analysis/examples/larger-project/src/EverythingLiveHere.res
new file mode 100644
index 0000000000..86ff46d5c9
--- /dev/null
+++ b/analysis/examples/larger-project/src/EverythingLiveHere.res
@@ -0,0 +1,5 @@
+let x = 1
+
+let y = 3
+
+let z = 4
diff --git a/analysis/examples/larger-project/src/FC.js b/analysis/examples/larger-project/src/FC.js
new file mode 100644
index 0000000000..1beb8f4437
--- /dev/null
+++ b/analysis/examples/larger-project/src/FC.js
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function foo(impl) {
+  return impl.make;
+}
+
+console.log(foo);
+
+export {
+  foo ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/FC.res b/analysis/examples/larger-project/src/FC.res
new file mode 100644
index 0000000000..6a55ba0a20
--- /dev/null
+++ b/analysis/examples/larger-project/src/FC.res
@@ -0,0 +1,11 @@
+module type ReplacebleComponent = {
+  @react.component
+  let make: unit => React.element
+}
+
+let foo = (~impl: module(ReplacebleComponent)) => {
+  let module(X) = impl
+  X.make
+}
+
+Js.log(foo)
diff --git a/analysis/examples/larger-project/src/FirstClassModules.js b/analysis/examples/larger-project/src/FirstClassModules.js
new file mode 100644
index 0000000000..d83705e55a
--- /dev/null
+++ b/analysis/examples/larger-project/src/FirstClassModules.js
@@ -0,0 +1,69 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var y = "abc";
+
+var EmptyInnerModule = {};
+
+var InnerModule2 = {
+  k: 4242
+};
+
+function k3(x) {
+  return x + 1 | 0;
+}
+
+var InnerModule3 = {
+  k3: k3
+};
+
+var Z = {
+  u: [
+    0,
+    0
+  ]
+};
+
+var M = {
+  y: y,
+  EmptyInnerModule: EmptyInnerModule,
+  InnerModule2: InnerModule2,
+  InnerModule3: InnerModule3,
+  Z: Z,
+  x: 42
+};
+
+var firstClassModule = {
+  x: 42,
+  EmptyInnerModule: EmptyInnerModule,
+  InnerModule2: InnerModule2,
+  InnerModule3: InnerModule3,
+  Z: Z,
+  y: y
+};
+
+function testConvert(m) {
+  return m;
+}
+
+function SomeFunctor(X) {
+  return {
+          ww: X.y
+        };
+}
+
+function someFunctorAsFunction(x) {
+  return {
+          ww: x.y
+        };
+}
+
+export {
+  M ,
+  firstClassModule ,
+  testConvert ,
+  SomeFunctor ,
+  someFunctorAsFunction ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/FirstClassModules.res b/analysis/examples/larger-project/src/FirstClassModules.res
new file mode 100644
index 0000000000..674d243e3a
--- /dev/null
+++ b/analysis/examples/larger-project/src/FirstClassModules.res
@@ -0,0 +1,65 @@
+module type MT = {
+  let x: int
+  type t = int
+  @module("foo") external f: int => int = "f"
+  module type MT2 = {
+    type tt = string
+  }
+  module EmptyInnerModule: {}
+  module InnerModule2: {
+    let k: t
+  }
+  module InnerModule3: {
+    type inner = int
+    let k3: inner => inner
+  }
+  module type TT = {
+    let u: (int, int)
+  }
+  module Z: TT
+  let y: string
+}
+module M = {
+  let y = "abc"
+  module type MT2 = {
+    type tt = string
+  }
+  module EmptyInnerModule = {}
+  module InnerModule2 = {
+    let k = 4242
+  }
+  module InnerModule3 = {
+    type inner = int
+    let k3 = x => x + 1
+  }
+
+  module type TT = {
+    let u: (int, int)
+  }
+  module Z = {
+    let u = (0, 0)
+  }
+  type t = int
+  @module("foo") external f: int => int = "f"
+  let x = 42
+}
+
+@genType
+type firstClassModule = module(MT)
+
+@genType
+let firstClassModule: firstClassModule = module(M)
+
+@genType
+let testConvert = (m: module(MT)) => m
+
+module type ResT = {
+  let ww: string
+}
+
+module SomeFunctor = (X: MT): ResT => {
+  let ww = X.y
+}
+
+@genType
+let someFunctorAsFunction = (x: module(MT)): module(ResT) => module(SomeFunctor(unpack(x)))
diff --git a/analysis/examples/larger-project/src/FirstClassModulesInterface.js b/analysis/examples/larger-project/src/FirstClassModulesInterface.js
new file mode 100644
index 0000000000..56783f6a44
--- /dev/null
+++ b/analysis/examples/larger-project/src/FirstClassModulesInterface.js
@@ -0,0 +1,13 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var r = {
+  x: 3,
+  y: "hello"
+};
+
+export {
+  r ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/FirstClassModulesInterface.res b/analysis/examples/larger-project/src/FirstClassModulesInterface.res
new file mode 100644
index 0000000000..9dd75a90b8
--- /dev/null
+++ b/analysis/examples/larger-project/src/FirstClassModulesInterface.res
@@ -0,0 +1,12 @@
+type record = {
+  x: int,
+  y: string,
+}
+
+let r = {x: 3, y: "hello"}
+
+module type MT = {
+  let x: int
+}
+
+type firstClassModule = module(MT)
diff --git a/analysis/examples/larger-project/src/FirstClassModulesInterface.resi b/analysis/examples/larger-project/src/FirstClassModulesInterface.resi
new file mode 100644
index 0000000000..658663cc45
--- /dev/null
+++ b/analysis/examples/larger-project/src/FirstClassModulesInterface.resi
@@ -0,0 +1,15 @@
+@genType
+type record = {
+  x: int,
+  y: string,
+}
+
+let r: record
+
+@genType
+module type MT = {
+  let x: int
+}
+
+@genType
+type firstClassModule = module(MT)
diff --git a/analysis/examples/larger-project/src/Hooks.js b/analysis/examples/larger-project/src/Hooks.js
new file mode 100644
index 0000000000..8b249e521d
--- /dev/null
+++ b/analysis/examples/larger-project/src/Hooks.js
@@ -0,0 +1,188 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as React from "react";
+import * as ImportHooks from "./ImportHooks.js";
+import * as ImportHookDefault from "./ImportHookDefault.js";
+
+function Hooks(Props) {
+  var vehicle = Props.vehicle;
+  var match = React.useState(function () {
+        return 0;
+      });
+  var setCount = match[1];
+  var count = match[0];
+  return React.createElement("div", undefined, React.createElement("p", undefined, "Hooks example " + (vehicle.name + (" clicked " + (String(count) + " times")))), React.createElement("button", {
+                  onClick: (function (param) {
+                      return Curry._1(setCount, (function (param) {
+                                    return count + 1 | 0;
+                                  }));
+                    })
+                }, "Click me"), React.createElement(ImportHooks.make, {
+                  person: {
+                    name: "Mary",
+                    age: 71
+                  },
+                  children: null,
+                  renderMe: (function (x) {
+                      return x.randomString;
+                    })
+                }, "child1", "child2"), React.createElement(ImportHookDefault.make, {
+                  person: {
+                    name: "DefaultImport",
+                    age: 42
+                  },
+                  children: null,
+                  renderMe: (function (x) {
+                      return x.randomString;
+                    })
+                }, "child1", "child2"));
+}
+
+function Hooks$anotherComponent(Props) {
+  var vehicle = Props.vehicle;
+  var callback = Props.callback;
+  Curry._1(callback, undefined);
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name);
+}
+
+function Hooks$Inner(Props) {
+  var vehicle = Props.vehicle;
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name);
+}
+
+function Hooks$Inner$anotherComponent(Props) {
+  var vehicle = Props.vehicle;
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name);
+}
+
+function Hooks$Inner$Inner2(Props) {
+  var vehicle = Props.vehicle;
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name);
+}
+
+function Hooks$Inner$Inner2$anotherComponent(Props) {
+  var vehicle = Props.vehicle;
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name);
+}
+
+var Inner2 = {
+  make: Hooks$Inner$Inner2,
+  anotherComponent: Hooks$Inner$Inner2$anotherComponent
+};
+
+var Inner = {
+  make: Hooks$Inner,
+  anotherComponent: Hooks$Inner$anotherComponent,
+  Inner2: Inner2
+};
+
+function Hooks$NoProps(Props) {
+  return React.createElement("div", undefined, null);
+}
+
+var NoProps = {
+  make: Hooks$NoProps
+};
+
+function functionWithRenamedArgs(_to, _Type, cb) {
+  Curry._1(cb, _to);
+  return _to.name + _Type.name;
+}
+
+function Hooks$componentWithRenamedArgs(Props) {
+  var _to = Props.to;
+  var _Type = Props.Type;
+  var cb = Props.cb;
+  Curry._1(cb, _to);
+  return _to.name + _Type.name;
+}
+
+function Hooks$makeWithRef(Props) {
+  var vehicle = Props.vehicle;
+  return function (ref) {
+    if (ref == null) {
+      return null;
+    } else {
+      return React.createElement("button", {
+                  ref: ref
+                }, vehicle.name);
+    }
+  };
+}
+
+var testForwardRef = React.forwardRef(function (param, param$1) {
+      return Hooks$makeWithRef(param)(param$1);
+    });
+
+var input = React.forwardRef(function (Props, param) {
+      var partial_arg = Props.r;
+      return React.createElement("div", {
+                  ref: param
+                }, partial_arg.x);
+    });
+
+function Hooks$polymorphicComponent(Props) {
+  var param = Props.p;
+  return param[0].name;
+}
+
+function Hooks$functionReturningReactElement(Props) {
+  return Props.name;
+}
+
+function Hooks$RenderPropRequiresConversion(Props) {
+  var renderVehicle = Props.renderVehicle;
+  return Curry._1(renderVehicle, {
+              vehicle: {
+                name: "Car"
+              },
+              number: 42
+            });
+}
+
+var RenderPropRequiresConversion = {
+  make: Hooks$RenderPropRequiresConversion
+};
+
+function Hooks$aComponentWithChildren(Props) {
+  var vehicle = Props.vehicle;
+  var children = Props.children;
+  return React.createElement("div", undefined, "Another Hook " + vehicle.name, React.createElement("div", undefined, children));
+}
+
+var make = Hooks;
+
+var $$default = Hooks;
+
+var anotherComponent = Hooks$anotherComponent;
+
+var componentWithRenamedArgs = Hooks$componentWithRenamedArgs;
+
+var makeWithRef = Hooks$makeWithRef;
+
+var polymorphicComponent = Hooks$polymorphicComponent;
+
+var functionReturningReactElement = Hooks$functionReturningReactElement;
+
+var aComponentWithChildren = Hooks$aComponentWithChildren;
+
+export {
+  make ,
+  $$default ,
+  $$default as default,
+  anotherComponent ,
+  Inner ,
+  NoProps ,
+  functionWithRenamedArgs ,
+  componentWithRenamedArgs ,
+  makeWithRef ,
+  testForwardRef ,
+  input ,
+  polymorphicComponent ,
+  functionReturningReactElement ,
+  RenderPropRequiresConversion ,
+  aComponentWithChildren ,
+  
+}
+/* testForwardRef Not a pure module */
diff --git a/analysis/examples/larger-project/src/Hooks.res b/analysis/examples/larger-project/src/Hooks.res
new file mode 100644
index 0000000000..f35c242879
--- /dev/null
+++ b/analysis/examples/larger-project/src/Hooks.res
@@ -0,0 +1,115 @@
+type vehicle = {name: string}
+
+@react.component
+let make = (~vehicle) => {
+  let (count, setCount) = React.useState(() => 0)
+
+  <div>
+    <p>
+      {React.string(
+        "Hooks example " ++ (vehicle.name ++ (" clicked " ++ (string_of_int(count) ++ " times"))),
+      )}
+    </p>
+    <button onClick={_ => setCount(_ => count + 1)}> {React.string("Click me")} </button>
+    <ImportHooks person={name: "Mary", age: 71} renderMe={x => React.string(x["randomString"])}>
+      {React.string("child1")} {React.string("child2")}
+    </ImportHooks>
+    <ImportHookDefault
+      person={name: "DefaultImport", age: 42} renderMe={x => React.string(x["randomString"])}>
+      {React.string("child1")} {React.string("child2")}
+    </ImportHookDefault>
+  </div>
+}
+
+@genType
+let default = make
+
+@genType @react.component
+let anotherComponent = (~vehicle, ~callback: unit => unit) => {
+  callback()
+  <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+}
+
+module Inner = {
+  @genType @react.component
+  let make = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+  @genType @react.component
+  let anotherComponent = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+  module Inner2 = {
+    @genType @react.component
+    let make = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+    @genType @react.component
+    let anotherComponent = (~vehicle) =>
+      <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+  }
+}
+
+module NoProps = {
+  @genType @react.component
+  let make = () => <div> React.null </div>
+}
+
+type cb = (~_to: vehicle) => unit
+
+@genType
+let functionWithRenamedArgs = (~_to, ~_Type, ~cb: cb) => {
+  cb(~_to)
+  _to.name ++ _Type.name
+}
+
+@genType @react.component
+let componentWithRenamedArgs = (~_to, ~_Type, ~cb: cb) => {
+  cb(~_to)
+  React.string(_to.name ++ _Type.name)
+}
+
+@genType @react.component
+let makeWithRef = (~vehicle) => {
+  let _ = 34
+  ref =>
+    switch ref->Js.Nullable.toOption {
+    | Some(ref) => <button ref={ReactDOM.Ref.domRef(ref)}> {React.string(vehicle.name)} </button>
+    | None => React.null
+    }
+}
+
+@genType
+let testForwardRef = React.forwardRef(makeWithRef)
+
+type r = {x: string}
+
+@genType @react.component
+let input = React.forwardRef((~r, (), ref) => <div ref={Obj.magic(ref)}> {React.string(r.x)} </div>)
+
+@genType
+type callback<'input, 'output> = React.callback<'input, 'output>
+
+@genType
+type testReactContext = React.Context.t<int>
+
+@genType
+type testReactRef = React.Ref.t<int>
+
+@genType
+type testDomRef = ReactDOM.domRef
+
+@genType @react.component
+let polymorphicComponent = (~p as (x, _)) => React.string(x.name)
+
+@genType @react.component
+let functionReturningReactElement = (~name) => React.string(name)
+
+module RenderPropRequiresConversion = {
+  @genType @react.component
+  let make = (~renderVehicle: {"vehicle": vehicle, "number": int} => React.element) => {
+    let car = {name: "Car"}
+    renderVehicle({"vehicle": car, "number": 42})
+  }
+}
+
+@genType @react.component
+let aComponentWithChildren = (~vehicle, ~children) =>
+  <div> {React.string("Another Hook " ++ vehicle.name)} <div> children </div> </div>
diff --git a/analysis/examples/larger-project/src/IgnoreInterface.js b/analysis/examples/larger-project/src/IgnoreInterface.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/IgnoreInterface.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/IgnoreInterface.res b/analysis/examples/larger-project/src/IgnoreInterface.res
new file mode 100644
index 0000000000..d381c3d037
--- /dev/null
+++ b/analysis/examples/larger-project/src/IgnoreInterface.res
@@ -0,0 +1,2 @@
+@gentype
+type t = int
diff --git a/analysis/examples/larger-project/src/IgnoreInterface.resi b/analysis/examples/larger-project/src/IgnoreInterface.resi
new file mode 100644
index 0000000000..709cbb9642
--- /dev/null
+++ b/analysis/examples/larger-project/src/IgnoreInterface.resi
@@ -0,0 +1,5 @@
+// Use the annotations, and definitions, from the .re file
+@@genType.ignoreInterface
+
+@genType
+type t
diff --git a/analysis/examples/larger-project/src/ImmutableArray.js b/analysis/examples/larger-project/src/ImmutableArray.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImmutableArray.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/ImmutableArray.res b/analysis/examples/larger-project/src/ImmutableArray.res
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/src/ImmutableArray.resi b/analysis/examples/larger-project/src/ImmutableArray.resi
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/src/ImportHookDefault.js b/analysis/examples/larger-project/src/ImportHookDefault.js
new file mode 100644
index 0000000000..119015eb5f
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportHookDefault.js
@@ -0,0 +1,15 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import ImportHookDefaultGen from "./ImportHookDefault.gen";
+import * as ImportHookDefaultGen$1 from "./ImportHookDefault.gen";
+
+var make = ImportHookDefaultGen$1.make;
+
+var make2 = ImportHookDefaultGen;
+
+export {
+  make ,
+  make2 ,
+  
+}
+/* make Not a pure module */
diff --git a/analysis/examples/larger-project/src/ImportHookDefault.res b/analysis/examples/larger-project/src/ImportHookDefault.res
new file mode 100644
index 0000000000..61086d6eae
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportHookDefault.res
@@ -0,0 +1,18 @@
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType.import(("./hookExample", "default")) @react.component
+external make: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: ImportHooks.renderMe<string>,
+) => React.element = "make"
+
+@genType.import("./hookExample") @react.component
+external make2: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: ImportHooks.renderMe<string>,
+) => React.element = "default"
diff --git a/analysis/examples/larger-project/src/ImportHooks.js b/analysis/examples/larger-project/src/ImportHooks.js
new file mode 100644
index 0000000000..2296ade782
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportHooks.js
@@ -0,0 +1,16 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as ImportHooksGen from "./ImportHooks.gen";
+
+var make = ImportHooksGen.makeRenamed;
+
+function foo(prim) {
+  return ImportHooksGen.foo(prim);
+}
+
+export {
+  make ,
+  foo ,
+  
+}
+/* make Not a pure module */
diff --git a/analysis/examples/larger-project/src/ImportHooks.res b/analysis/examples/larger-project/src/ImportHooks.res
new file mode 100644
index 0000000000..85bfe4d18d
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportHooks.res
@@ -0,0 +1,21 @@
+@genType
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType
+type renderMe<'a> = React.component<{
+  "randomString": string,
+  "poly": 'a,
+}>
+
+@genType.import("./hookExample") @react.component
+external make: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: renderMe<'a>,
+) => React.element = "makeRenamed"
+
+@genType.import("./hookExample")
+external foo: (~person: person) => string = "foo"
diff --git a/analysis/examples/larger-project/src/ImportIndex.js b/analysis/examples/larger-project/src/ImportIndex.js
new file mode 100644
index 0000000000..d817c1940a
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportIndex.js
@@ -0,0 +1,11 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import ImportIndexGen from "./ImportIndex.gen";
+
+var make = ImportIndexGen;
+
+export {
+  make ,
+  
+}
+/* make Not a pure module */
diff --git a/analysis/examples/larger-project/src/ImportIndex.res b/analysis/examples/larger-project/src/ImportIndex.res
new file mode 100644
index 0000000000..61f8998a55
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportIndex.res
@@ -0,0 +1,3 @@
+// TODO: rename metodd back once remmt bug is fixed
+@genType.import("./") @react.component
+external make: (~method: @string [#push | #replace]=?) => React.element = "default"
diff --git a/analysis/examples/larger-project/src/ImportJsValue.js b/analysis/examples/larger-project/src/ImportJsValue.js
new file mode 100644
index 0000000000..b5447377e9
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportJsValue.js
@@ -0,0 +1,81 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import ImportJsValueGen from "./ImportJsValue.gen";
+import * as ImportJsValueGen$1 from "./ImportJsValue.gen";
+
+function round(prim) {
+  return ImportJsValueGen$1.round(prim);
+}
+
+function area(prim) {
+  return ImportJsValueGen$1.area(prim);
+}
+
+function returnMixedArray(prim) {
+  return ImportJsValueGen$1.returnMixedArray();
+}
+
+var roundedNumber = ImportJsValueGen$1.round(1.8);
+
+var areaValue = ImportJsValueGen$1.area({
+      x: 3,
+      y: undefined
+    });
+
+function getAbs(x) {
+  return x.getAbs();
+}
+
+var AbsoluteValue = {
+  getAbs: getAbs
+};
+
+function useGetProp(x) {
+  return x.getProp() + 1 | 0;
+}
+
+function useGetAbs(x) {
+  return x.getAbs() + 1 | 0;
+}
+
+function useColor(prim) {
+  return ImportJsValueGen$1.useColor(prim);
+}
+
+function higherOrder(prim) {
+  return ImportJsValueGen$1.higherOrder(prim);
+}
+
+var returnedFromHigherOrder = ImportJsValueGen$1.higherOrder(function (prim0, prim1) {
+      return prim0 + prim1 | 0;
+    });
+
+function convertVariant(prim) {
+  return ImportJsValueGen$1.convertVariant(prim);
+}
+
+function polymorphic(prim) {
+  return ImportJsValueGen$1.polymorphic(prim);
+}
+
+var $$default = ImportJsValueGen;
+
+export {
+  round ,
+  area ,
+  returnMixedArray ,
+  roundedNumber ,
+  areaValue ,
+  AbsoluteValue ,
+  useGetProp ,
+  useGetAbs ,
+  useColor ,
+  higherOrder ,
+  returnedFromHigherOrder ,
+  convertVariant ,
+  polymorphic ,
+  $$default ,
+  $$default as default,
+  
+}
+/* roundedNumber Not a pure module */
diff --git a/analysis/examples/larger-project/src/ImportJsValue.res b/analysis/examples/larger-project/src/ImportJsValue.res
new file mode 100644
index 0000000000..7728ca527c
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportJsValue.res
@@ -0,0 +1,84 @@
+@ocaml.doc("
+  * Wrap JS values to be used from Reason
+  ")
+@genType.import("./MyMath")
+external /* This is the module to import from. */
+/* Name and type of the JS value to bind to. */
+round: float => float = "round"
+
+@genType
+type point = {
+  x: int,
+  y: option<int>,
+}
+
+@genType.import("./MyMath")
+external /* This is the module to import from. */
+/* Name and type of the JS value to bind to. */
+area: point => int = "area"
+
+@genType.import("./MyMath")
+type numberOrString
+
+@genType.import("./MyMath")
+external returnMixedArray: unit => array<numberOrString> = "returnMixedArray"
+
+@genType
+let roundedNumber = round(1.8)
+
+@genType
+let areaValue = area({x: 3, y: None})
+
+module AbsoluteValue = {
+  @genType.import(("./MyMath", "AbsoluteValue"))
+  type t = {"getAbs": (. unit) => int}
+
+  /* This is untyped */
+  @send external getProp: t => int = "getProp"
+
+  /* This is also untyped, as we "trust" the type declaration in absoluteVaue */
+  let getAbs = (x: t) => {
+    let getAbs = x["getAbs"]
+    getAbs(.)
+  }
+}
+
+@genType
+let useGetProp = (x: AbsoluteValue.t) => x->AbsoluteValue.getProp + 1
+
+@genType
+let useGetAbs = (x: AbsoluteValue.t) => x->AbsoluteValue.getAbs + 1
+
+@genType.import("./MyMath")
+type stringFunction
+
+@genType
+type color = [#tomato | #gray]
+
+@genType.import("./MyMath") external useColor: color => int = "useColor"
+
+@genType.import("./MyMath")
+external higherOrder: ((int, int) => int) => int = "higherOrder"
+
+@genType
+let returnedFromHigherOrder = higherOrder(\"+")
+
+type variant =
+  | I(int)
+  | S(string)
+
+@genType.import("./MyMath")
+external convertVariant: variant => variant = "convertVariant"
+
+@genType.import("./MyMath") external polymorphic: 'a => 'a = "polymorphic"
+
+@genType.import("./MyMath") external default: int = "default"
+
+@genType.import(("./MyMath", "num"))
+type num
+
+@genType.import(("./MyMath", "num"))
+type myNum
+
+@genType.import("./MyMath")
+type polyType<'a>
diff --git a/analysis/examples/larger-project/src/ImportMyBanner.js b/analysis/examples/larger-project/src/ImportMyBanner.js
new file mode 100644
index 0000000000..ffc3d6ed53
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportMyBanner.js
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as ImportMyBannerGen from "./ImportMyBanner.gen";
+
+function make(prim0, prim1, prim2) {
+  return ImportMyBannerGen.make(prim0, prim1 !== undefined ? Caml_option.valFromOption(prim1) : undefined, prim2);
+}
+
+export {
+  make ,
+  
+}
+/* ./ImportMyBanner.gen Not a pure module */
diff --git a/analysis/examples/larger-project/src/ImportMyBanner.res b/analysis/examples/larger-project/src/ImportMyBanner.res
new file mode 100644
index 0000000000..9b26fa2366
--- /dev/null
+++ b/analysis/examples/larger-project/src/ImportMyBanner.res
@@ -0,0 +1,12 @@
+@ocaml.doc("
+  * Wrap component MyBanner to be used from Reason.
+  ")
+@genType
+type message = {text: string}
+
+@genType.import("./MyBanner")
+external /* Module with the JS component to be wrapped. */
+/* The make function will be automatically generated from the types below. */
+make: (~show: bool, ~message: option<message>=?, 'a) => React.element = "make"
+
+let make = make
diff --git a/analysis/examples/larger-project/src/JSResource.js b/analysis/examples/larger-project/src/JSResource.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/JSResource.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/JSResource.res b/analysis/examples/larger-project/src/JSResource.res
new file mode 100644
index 0000000000..432c4a4669
--- /dev/null
+++ b/analysis/examples/larger-project/src/JSResource.res
@@ -0,0 +1,3 @@
+type t<'a>
+
+@module external jSResource: string => t<'a> = "JSResource"
diff --git a/analysis/examples/larger-project/src/LetPrivate.js b/analysis/examples/larger-project/src/LetPrivate.js
new file mode 100644
index 0000000000..25dbf8d5bb
--- /dev/null
+++ b/analysis/examples/larger-project/src/LetPrivate.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var y = 34;
+
+export {
+  y ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/LetPrivate.res b/analysis/examples/larger-project/src/LetPrivate.res
new file mode 100644
index 0000000000..758270659a
--- /dev/null
+++ b/analysis/examples/larger-project/src/LetPrivate.res
@@ -0,0 +1,7 @@
+%%private(
+  @genType
+  let x = 34
+)
+
+@genType
+let y = x
diff --git a/analysis/examples/larger-project/src/ModuleAliases.js b/analysis/examples/larger-project/src/ModuleAliases.js
new file mode 100644
index 0000000000..bbe92275ae
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleAliases.js
@@ -0,0 +1,48 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var Inner = {};
+
+var Outer = {
+  Inner: Inner
+};
+
+var InnerNested = {};
+
+var Inner2 = {
+  InnerNested: InnerNested,
+  OuterInnerAlias2: undefined
+};
+
+var Outer2 = {
+  OuterInnerAlias: undefined,
+  Inner2: Inner2
+};
+
+function testNested(x) {
+  return x;
+}
+
+function testInner(x) {
+  return x;
+}
+
+function testInner2(x) {
+  return x;
+}
+
+var Outer2Alias;
+
+var InnerNestedAlias;
+
+export {
+  Outer ,
+  Outer2 ,
+  Outer2Alias ,
+  InnerNestedAlias ,
+  testNested ,
+  testInner ,
+  testInner2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/ModuleAliases.res b/analysis/examples/larger-project/src/ModuleAliases.res
new file mode 100644
index 0000000000..93c612acb0
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleAliases.res
@@ -0,0 +1,28 @@
+module Outer = {
+  module Inner = {
+    type innerT = {inner: string}
+  }
+}
+
+module Outer2 = {
+  module OuterInnerAlias = Outer.Inner
+  module Inner2 = {
+    module InnerNested = {
+      type t = {nested: int}
+    }
+    module OuterInnerAlias2 = OuterInnerAlias
+  }
+}
+
+module Outer2Alias = Outer2
+
+module InnerNestedAlias = Outer2.Inner2.InnerNested
+
+@genType
+let testNested = (x: InnerNestedAlias.t) => x
+
+@genType
+let testInner = (x: Outer2Alias.OuterInnerAlias.innerT) => x
+
+@genType
+let testInner2 = (x: Outer2Alias.Inner2.OuterInnerAlias2.innerT) => x
diff --git a/analysis/examples/larger-project/src/ModuleAliases2.js b/analysis/examples/larger-project/src/ModuleAliases2.js
new file mode 100644
index 0000000000..59a7eb6489
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleAliases2.js
@@ -0,0 +1,23 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var Inner = {};
+
+var Outer = {
+  Inner: Inner
+};
+
+var OuterAlias;
+
+var InnerAlias;
+
+var q = 42;
+
+export {
+  Outer ,
+  OuterAlias ,
+  InnerAlias ,
+  q ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/ModuleAliases2.res b/analysis/examples/larger-project/src/ModuleAliases2.res
new file mode 100644
index 0000000000..b0ce0265c6
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleAliases2.res
@@ -0,0 +1,21 @@
+@genType
+type record = {
+  x: int,
+  y: string,
+}
+
+module Outer = {
+  @genType
+  type outer = {outer: string}
+
+  module Inner = {
+    @genType
+    type inner = {inner: string}
+  }
+}
+
+module OuterAlias = Outer
+
+module InnerAlias = OuterAlias.Inner
+
+let q = 42
diff --git a/analysis/examples/larger-project/src/ModuleExceptionBug.js b/analysis/examples/larger-project/src/ModuleExceptionBug.js
new file mode 100644
index 0000000000..6679d3d6f6
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleExceptionBug.js
@@ -0,0 +1,25 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+function customDouble(foo) {
+  return (foo << 1);
+}
+
+var Dep = {
+  customDouble: customDouble
+};
+
+var MyOtherException = /* @__PURE__ */Caml_exceptions.create("ModuleExceptionBug.MyOtherException");
+
+console.log(34);
+
+var ddjdj = 34;
+
+export {
+  Dep ,
+  MyOtherException ,
+  ddjdj ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/ModuleExceptionBug.res b/analysis/examples/larger-project/src/ModuleExceptionBug.res
new file mode 100644
index 0000000000..f9b36ce235
--- /dev/null
+++ b/analysis/examples/larger-project/src/ModuleExceptionBug.res
@@ -0,0 +1,8 @@
+module Dep = {
+  let customDouble = foo => foo * 2
+}
+
+exception MyOtherException
+
+let ddjdj = 34
+Js.log(ddjdj)
diff --git a/analysis/examples/larger-project/src/NestedModules.js b/analysis/examples/larger-project/src/NestedModules.js
new file mode 100644
index 0000000000..fd32bddea7
--- /dev/null
+++ b/analysis/examples/larger-project/src/NestedModules.js
@@ -0,0 +1,43 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function nested3Function(x) {
+  return x;
+}
+
+var Nested3 = {
+  x: 0,
+  y: 1,
+  z: 2,
+  w: 3,
+  nested3Value: "nested3Value",
+  nested3Function: nested3Function
+};
+
+function nested2Function(x) {
+  return x;
+}
+
+var Nested2 = {
+  x: 0,
+  nested2Value: 1,
+  y: 2,
+  Nested3: Nested3,
+  nested2Function: nested2Function
+};
+
+var Universe = {
+  theAnswer: 42,
+  notExported: 33,
+  Nested2: Nested2,
+  someString: "some exported string"
+};
+
+var notNested = 1;
+
+export {
+  notNested ,
+  Universe ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/NestedModules.res b/analysis/examples/larger-project/src/NestedModules.res
new file mode 100644
index 0000000000..f2317d1945
--- /dev/null
+++ b/analysis/examples/larger-project/src/NestedModules.res
@@ -0,0 +1,51 @@
+@genType
+let notNested = 1
+
+module Universe = {
+  @genType
+  let theAnswer = 42
+
+  let notExported = 33
+
+  @genType
+  type nestedType = array<string>
+
+  module Nested2 = {
+    let x = 0
+
+    @genType
+    let nested2Value = 1
+
+    let y = 2
+
+    @genType
+    type nested2Type = array<array<string>>
+
+    module Nested3 = {
+      let x = 0
+      let y = 1
+      let z = 2
+      let w = 3
+
+      @genType
+      type nested3Type = array<array<array<string>>>
+
+      @genType
+      let nested3Value = "nested3Value"
+
+      @genType
+      let nested3Function = (x: nested2Type) => x
+    }
+
+    @genType
+    let nested2Function = (x: Nested3.nested3Type) => x
+  }
+
+  @genType
+  type variant =
+    | A
+    | B(string)
+
+  @genType
+  let someString = "some exported string"
+}
diff --git a/analysis/examples/larger-project/src/NestedModulesInSignature.js b/analysis/examples/larger-project/src/NestedModulesInSignature.js
new file mode 100644
index 0000000000..1019513009
--- /dev/null
+++ b/analysis/examples/larger-project/src/NestedModulesInSignature.js
@@ -0,0 +1,12 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var Universe = {
+  theAnswer: 42
+};
+
+export {
+  Universe ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/NestedModulesInSignature.res b/analysis/examples/larger-project/src/NestedModulesInSignature.res
new file mode 100644
index 0000000000..85f454db98
--- /dev/null
+++ b/analysis/examples/larger-project/src/NestedModulesInSignature.res
@@ -0,0 +1,3 @@
+module Universe = {
+  let theAnswer = 42
+}
diff --git a/analysis/examples/larger-project/src/NestedModulesInSignature.resi b/analysis/examples/larger-project/src/NestedModulesInSignature.resi
new file mode 100644
index 0000000000..482b8ca52a
--- /dev/null
+++ b/analysis/examples/larger-project/src/NestedModulesInSignature.resi
@@ -0,0 +1,4 @@
+module Universe: {
+  @genType
+  let theAnswer: int
+}
diff --git a/analysis/examples/larger-project/src/Newsyntax.js b/analysis/examples/larger-project/src/Newsyntax.js
new file mode 100644
index 0000000000..8a024f19da
--- /dev/null
+++ b/analysis/examples/larger-project/src/Newsyntax.js
@@ -0,0 +1,13 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var x = 34;
+
+var y = 11;
+
+export {
+  x ,
+  y ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Newsyntax.res b/analysis/examples/larger-project/src/Newsyntax.res
new file mode 100644
index 0000000000..45f118b404
--- /dev/null
+++ b/analysis/examples/larger-project/src/Newsyntax.res
@@ -0,0 +1,12 @@
+let x = 34
+
+let y = 11
+
+type record = {
+  xxx: int,
+  yyy: int,
+}
+
+type variant = A | B(int) | C
+
+type record2 = {xx: int, yy: int}
diff --git a/analysis/examples/larger-project/src/Newton.js b/analysis/examples/larger-project/src/Newton.js
new file mode 100644
index 0000000000..5f603b0e87
--- /dev/null
+++ b/analysis/examples/larger-project/src/Newton.js
@@ -0,0 +1,65 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+
+function $neg(prim0, prim1) {
+  return prim0 - prim1;
+}
+
+function $plus(prim0, prim1) {
+  return prim0 + prim1;
+}
+
+function $star(prim0, prim1) {
+  return prim0 * prim1;
+}
+
+function $slash(prim0, prim1) {
+  return prim0 / prim1;
+}
+
+function newton(f, fPrimed, initial, threshold) {
+  var current = {
+    contents: initial
+  };
+  var iterateMore = function (previous, next) {
+    var delta = next >= previous ? next - previous : previous - next;
+    current.contents = next;
+    return delta >= threshold;
+  };
+  var _param;
+  while(true) {
+    var previous = current.contents;
+    var next = previous - Curry._1(f, previous) / Curry._1(fPrimed, previous);
+    if (!iterateMore(previous, next)) {
+      return current.contents;
+    }
+    _param = undefined;
+    continue ;
+  };
+}
+
+function f(x) {
+  return x * x * x - 2.0 * x * x - 11.0 * x + 12.0;
+}
+
+function fPrimed(x) {
+  return 3.0 * x * x - 4.0 * x - 11.0;
+}
+
+var result = newton(f, fPrimed, 5.0, 0.0003);
+
+console.log(result, f(result));
+
+export {
+  $neg ,
+  $plus ,
+  $star ,
+  $slash ,
+  newton ,
+  f ,
+  fPrimed ,
+  result ,
+  
+}
+/* result Not a pure module */
diff --git a/analysis/examples/larger-project/src/Newton.res b/analysis/examples/larger-project/src/Newton.res
new file mode 100644
index 0000000000..4601b2bc46
--- /dev/null
+++ b/analysis/examples/larger-project/src/Newton.res
@@ -0,0 +1,32 @@
+let \"-" = \"-."
+let \"+" = \"+."
+let \"*" = \"*."
+let \"/" = \"/."
+
+let newton = (~f, ~fPrimed, ~initial, ~threshold) => {
+  let current = ref(initial)
+  let iterateMore = (previous, next) => {
+    let delta = next >= previous ? next - previous : previous - next
+    current := next
+    !(delta < threshold)
+  }
+
+  @progress(iterateMore)
+  let rec loop = () => {
+    let previous = current.contents
+    let next = previous - f(previous) / fPrimed(previous)
+    if iterateMore(previous, next) {
+      loop()
+    } else {
+      current.contents
+    }
+  }
+  loop()
+}
+let f = x => x * x * x - 2.0 * x * x - 11.0 * x + 12.0
+
+let fPrimed = x => 3.0 * x * x - 4.0 * x - 11.0
+
+let result = newton(~f, ~fPrimed, ~initial=5.0, ~threshold=0.0003)
+
+Js.log2(result, f(result))
diff --git a/analysis/examples/larger-project/src/Opaque.js b/analysis/examples/larger-project/src/Opaque.js
new file mode 100644
index 0000000000..17f41cac2e
--- /dev/null
+++ b/analysis/examples/larger-project/src/Opaque.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function noConversion(x) {
+  return x;
+}
+
+function testConvertNestedRecordFromOtherFile(x) {
+  return x;
+}
+
+export {
+  noConversion ,
+  testConvertNestedRecordFromOtherFile ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Opaque.res b/analysis/examples/larger-project/src/Opaque.res
new file mode 100644
index 0000000000..e6772142aa
--- /dev/null
+++ b/analysis/examples/larger-project/src/Opaque.res
@@ -0,0 +1,11 @@
+@genType.opaque
+type opaqueFromRecords = A(Records.coord)
+
+@genType
+let noConversion = (x: opaqueFromRecords) => x
+
+@genType
+type pair = (opaqueFromRecords, opaqueFromRecords)
+
+@genType
+let testConvertNestedRecordFromOtherFile = (x: Records.business) => x
diff --git a/analysis/examples/larger-project/src/OptArg.js b/analysis/examples/larger-project/src/OptArg.js
new file mode 100644
index 0000000000..1900ecd3c0
--- /dev/null
+++ b/analysis/examples/larger-project/src/OptArg.js
@@ -0,0 +1,60 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function foo(xOpt, yOpt, zOpt, w) {
+  var x = xOpt !== undefined ? xOpt : 1;
+  var y = yOpt !== undefined ? yOpt : 2;
+  var z = zOpt !== undefined ? zOpt : 3;
+  return ((x + y | 0) + z | 0) + w | 0;
+}
+
+function bar(x, y, z, w) {
+  return y + w | 0;
+}
+
+console.log(foo(3, undefined, undefined, 4));
+
+console.log(7);
+
+function threeArgs(aOpt, bOpt, cOpt, d) {
+  var a = aOpt !== undefined ? aOpt : 1;
+  var b = bOpt !== undefined ? bOpt : 2;
+  var c = cOpt !== undefined ? cOpt : 3;
+  return ((a + b | 0) + c | 0) + d | 0;
+}
+
+console.log(threeArgs(4, undefined, 7, 1));
+
+console.log(threeArgs(4, undefined, undefined, 1));
+
+function twoArgs(aOpt, bOpt, c) {
+  var a = aOpt !== undefined ? aOpt : 1;
+  var b = bOpt !== undefined ? bOpt : 2;
+  return (a + b | 0) + c | 0;
+}
+
+console.log(twoArgs(undefined, undefined, 1));
+
+var a = 3;
+
+console.log(a + 44 | 0);
+
+function wrapfourArgs(a, b, c, n) {
+  var dOpt;
+  var a$1 = a !== undefined ? a : 1;
+  var b$1 = b !== undefined ? b : 2;
+  var c$1 = c !== undefined ? c : 3;
+  var d = dOpt !== undefined ? dOpt : 4;
+  return (((a$1 + b$1 | 0) + c$1 | 0) + d | 0) + n | 0;
+}
+
+console.log(wrapfourArgs(3, undefined, 44, 44));
+
+console.log(wrapfourArgs(undefined, 4, 44, 44));
+
+export {
+  foo ,
+  bar ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/OptArg.res b/analysis/examples/larger-project/src/OptArg.res
new file mode 100644
index 0000000000..a38e8bfc42
--- /dev/null
+++ b/analysis/examples/larger-project/src/OptArg.res
@@ -0,0 +1,29 @@
+let foo = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let bar = (~x=?, ~y, ~z=?, w) => y + w
+
+Js.log(foo(~x=3, 4))
+
+Js.log(bar(~y=3, 4))
+
+let threeArgs = (~a=1, ~b=2, ~c=3, d) => a + b + c + d
+
+Js.log(threeArgs(~a=4, ~c=7, 1))
+Js.log(threeArgs(~a=4, 1))
+
+let twoArgs = (~a=1, ~b=2, c) => a + b + c
+
+Js.log(1 |> twoArgs)
+
+let oneArg = (~a=1, ~z, b) => a + b
+
+let wrapOneArg = (~a=?, n) => oneArg(~a?, ~z=33, n)
+
+Js.log(wrapOneArg(~a=3, 44))
+
+let fourArgs = (~a=1, ~b=2, ~c=3, ~d=4, n) => a + b + c + d + n
+
+let wrapfourArgs = (~a=?, ~b=?, ~c=?, n) => fourArgs(~a?, ~b?, ~c?, n)
+
+Js.log(wrapfourArgs(~a=3, ~c=44, 44))
+Js.log(wrapfourArgs(~b=4, ~c=44, 44))
diff --git a/analysis/examples/larger-project/src/OptArg.resi b/analysis/examples/larger-project/src/OptArg.resi
new file mode 100644
index 0000000000..8145a51165
--- /dev/null
+++ b/analysis/examples/larger-project/src/OptArg.resi
@@ -0,0 +1,2 @@
+let foo: (~x: int=?, ~y: int=?, ~z: int=?, int) => int
+let bar: (~x: 'a=?, ~y: int, ~z: 'b=?, int) => int
diff --git a/analysis/examples/larger-project/src/P.js b/analysis/examples/larger-project/src/P.js
new file mode 100644
index 0000000000..4a5fd32836
--- /dev/null
+++ b/analysis/examples/larger-project/src/P.js
@@ -0,0 +1,114 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var Sys_error = /* @__PURE__ */Caml_exceptions.create("P.Sys_error");
+
+function input(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          2,
+          17
+        ],
+        Error: new Error()
+      };
+}
+
+function output(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          3,
+          18
+        ],
+        Error: new Error()
+      };
+}
+
+function open_temp_file(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          4,
+          26
+        ],
+        Error: new Error()
+      };
+}
+
+function close_out(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          5,
+          21
+        ],
+        Error: new Error()
+      };
+}
+
+function output_char(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          6,
+          23
+        ],
+        Error: new Error()
+      };
+}
+
+function really_input(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          7,
+          24
+        ],
+        Error: new Error()
+      };
+}
+
+function pp_get_formatter_tag_functions(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "P.res",
+          8,
+          42
+        ],
+        Error: new Error()
+      };
+}
+
+throw {
+      RE_EXN_ID: "Assert_failure",
+      _1: [
+        "P.res",
+        11,
+        13
+      ],
+      Error: new Error()
+    };
+
+export {
+  Sys_error ,
+  input ,
+  output ,
+  open_temp_file ,
+  close_out ,
+  output_char ,
+  really_input ,
+  pp_get_formatter_tag_functions ,
+  stderr ,
+  print_char ,
+  
+}
+/* stderr Not a pure module */
diff --git a/analysis/examples/larger-project/src/P.res b/analysis/examples/larger-project/src/P.res
new file mode 100644
index 0000000000..3c69ae7816
--- /dev/null
+++ b/analysis/examples/larger-project/src/P.res
@@ -0,0 +1,14 @@
+exception Sys_error(string)
+let input = _ => assert false
+let output = _ => assert false
+let open_temp_file = _ => assert false
+let close_out = _ => assert false
+let output_char = _ => assert false
+let really_input = _ => assert false
+let pp_get_formatter_tag_functions = _ => assert false
+type ttt = Open_text
+type out_channel
+let stderr = assert false
+let print_char = _ => assert false
+
+type nativeint
\ No newline at end of file
diff --git a/analysis/examples/larger-project/src/Records.js b/analysis/examples/larger-project/src/Records.js
new file mode 100644
index 0000000000..016884c0e2
--- /dev/null
+++ b/analysis/examples/larger-project/src/Records.js
@@ -0,0 +1,170 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Belt_List from "rescript/lib/es6/belt_List.js";
+import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Belt_Option from "rescript/lib/es6/belt_Option.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+
+function computeArea(param) {
+  return Math.imul(Math.imul(param.x, param.y), Belt_Option.mapWithDefault(param.z, 1, (function (n) {
+                    return n;
+                  })));
+}
+
+function coord2d(x, y) {
+  return {
+          x: x,
+          y: y,
+          z: undefined
+        };
+}
+
+var getOpt = Belt_Option.mapWithDefault;
+
+function findAddress(business) {
+  return Belt_Option.mapWithDefault(business.address, /* [] */0, (function (a) {
+                return {
+                        hd: a,
+                        tl: /* [] */0
+                      };
+              }));
+}
+
+function findAllAddresses(businesses) {
+  return Belt_List.toArray(Belt_List.flatten(Belt_List.fromArray(Belt_Array.map(businesses, (function (business) {
+                            return Pervasives.$at(Belt_Option.mapWithDefault(business.address, /* [] */0, (function (a) {
+                                              return {
+                                                      hd: a,
+                                                      tl: /* [] */0
+                                                    };
+                                            })), Belt_Option.mapWithDefault(business.owner, /* [] */0, (function (p) {
+                                              return Belt_Option.mapWithDefault(p.address, /* [] */0, (function (a) {
+                                                            return {
+                                                                    hd: a,
+                                                                    tl: /* [] */0
+                                                                  };
+                                                          }));
+                                            })));
+                          })))));
+}
+
+function getPayload(param) {
+  return param.payload;
+}
+
+function getPayloadRecord(param) {
+  return param.payload;
+}
+
+var recordValue = {
+  v: 1,
+  w: 1
+};
+
+var payloadValue = {
+  num: 1,
+  payload: recordValue
+};
+
+function getPayloadRecordPlusOne(param) {
+  var payload = param.payload;
+  return {
+          v: payload.v + 1 | 0,
+          w: payload.w
+        };
+}
+
+function findAddress2(business) {
+  return Belt_Option.mapWithDefault(Caml_option.nullable_to_opt(business.address2), /* [] */0, (function (a) {
+                return {
+                        hd: a,
+                        tl: /* [] */0
+                      };
+              }));
+}
+
+var someBusiness2_owner = null;
+
+var someBusiness2_address2 = null;
+
+var someBusiness2 = {
+  name: "SomeBusiness",
+  owner: someBusiness2_owner,
+  address2: someBusiness2_address2
+};
+
+function computeArea3(o) {
+  return Math.imul(Math.imul(o.x, o.y), Belt_Option.mapWithDefault(Caml_option.nullable_to_opt(o.z), 1, (function (n) {
+                    return n;
+                  })));
+}
+
+function computeArea4(o) {
+  return Math.imul(Math.imul(o.x, o.y), Belt_Option.mapWithDefault(o.z, 1, (function (n) {
+                    return n;
+                  })));
+}
+
+function testMyRec(x) {
+  return x.type_;
+}
+
+function testMyRec2(x) {
+  return x;
+}
+
+function testMyObj(x) {
+  return x.type_;
+}
+
+function testMyObj2(x) {
+  return x;
+}
+
+function testMyRecBsAs(x) {
+  return x.type;
+}
+
+function testMyRecBsAs2(x) {
+  return x;
+}
+
+var origin = {
+  x: 0,
+  y: 0,
+  z: 0
+};
+
+var someBusiness = {
+  name: "SomeBusiness",
+  owner: undefined,
+  address: undefined
+};
+
+export {
+  origin ,
+  computeArea ,
+  coord2d ,
+  getOpt ,
+  findAddress ,
+  someBusiness ,
+  findAllAddresses ,
+  getPayload ,
+  getPayloadRecord ,
+  recordValue ,
+  payloadValue ,
+  getPayloadRecordPlusOne ,
+  findAddress2 ,
+  someBusiness2 ,
+  computeArea3 ,
+  computeArea4 ,
+  testMyRec ,
+  testMyRec2 ,
+  testMyObj ,
+  testMyObj2 ,
+  testMyRecBsAs ,
+  testMyRecBsAs2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Records.res b/analysis/examples/larger-project/src/Records.res
new file mode 100644
index 0000000000..e82745e485
--- /dev/null
+++ b/analysis/examples/larger-project/src/Records.res
@@ -0,0 +1,148 @@
+open Belt
+
+@genType
+type coord = {
+  x: int,
+  y: int,
+  z: option<int>,
+}
+
+@genType
+let origin = {x: 0, y: 0, z: Some(0)}
+
+@genType
+let computeArea = ({x, y, z}) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let coord2d = (x, y) => {x: x, y: y, z: None}
+
+@genType
+type person = {
+  name: string,
+  age: int,
+  address: option<string>,
+}
+
+@genType
+type business = {
+  name: string,
+  owner: option<person>,
+  address: option<string>,
+}
+
+let getOpt = (opt, default, foo) => opt->Option.mapWithDefault(default, foo)
+
+@genType
+let findAddress = (business: business): list<string> =>
+  business.address->getOpt(list{}, a => list{a})
+
+@genType
+let someBusiness = {name: "SomeBusiness", owner: None, address: None}
+
+@genType
+let findAllAddresses = (businesses: array<business>): array<string> =>
+  businesses
+  ->Array.map(business =>
+    \"@"(
+      business.address->getOpt(list{}, a => list{a}),
+      business.owner->getOpt(list{}, p => p.address->getOpt(list{}, a => list{a})),
+    )
+  )
+  ->List.fromArray
+  ->List.flatten
+  ->List.toArray
+
+@genType
+type payload<'a> = {
+  num: int,
+  payload: 'a,
+}
+
+@genType
+let getPayload = ({payload}) => payload
+
+@genType
+type record = {
+  v: int,
+  w: int,
+}
+
+@genType
+let getPayloadRecord = ({payload}): record => payload
+
+@genType
+let recordValue = {v: 1, w: 1}
+
+@genType
+let payloadValue = {num: 1, payload: recordValue}
+
+@genType
+let getPayloadRecordPlusOne = ({payload}): record => {
+  ...payload,
+  v: payload.v + 1,
+}
+
+@genType
+type business2 = {
+  name: string,
+  owner: Js.Nullable.t<person>,
+  address2: Js.Nullable.t<string>,
+}
+
+@genType
+let findAddress2 = (business: business2): list<string> =>
+  business.address2->Js.Nullable.toOption->getOpt(list{}, a => list{a})
+
+@genType
+let someBusiness2 = {
+  name: "SomeBusiness",
+  owner: Js.Nullable.null,
+  address2: Js.Nullable.null,
+}
+
+@genType
+let computeArea3 = (o: {"x": int, "y": int, "z": Js.Nullable.t<int>}) =>
+  o["x"] * o["y"] * o["z"]->Js.Nullable.toOption->Option.mapWithDefault(1, n => n)
+
+@genType
+let computeArea4 = (o: {"x": int, "y": int, "z": option<int>}) =>
+  o["x"] * o["y"] * o["z"]->Option.mapWithDefault(1, n => n)
+
+@genType
+type mix = {"a": int, "b": int, "c": option<{"name": string, "surname": string}>}
+
+@genType
+type myRec = {
+  @genType.as("type")
+  type_: string,
+}
+
+@genType
+type myObj = {"type_": string}
+
+@genType
+let testMyRec = (x: myRec) => x.type_
+
+@genType
+let testMyRec2 = (x: myRec) => x
+
+@genType
+let testMyObj = (x: myObj) => x["type_"]
+
+@genType
+let testMyObj2 = (x: myObj) => x
+
+@genType
+type myRecBsAs = {
+  @as("type")
+  type_: string,
+}
+
+@genType
+let testMyRecBsAs = (x: myRecBsAs) => x.type_
+
+@genType
+let testMyRecBsAs2 = (x: myRecBsAs) => x
diff --git a/analysis/examples/larger-project/src/References.js b/analysis/examples/larger-project/src/References.js
new file mode 100644
index 0000000000..41dbd5947a
--- /dev/null
+++ b/analysis/examples/larger-project/src/References.js
@@ -0,0 +1,60 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function create(x) {
+  return {
+          contents: x
+        };
+}
+
+function access(r) {
+  return r.contents + 1 | 0;
+}
+
+function update(r) {
+  r.contents = r.contents + 1 | 0;
+  
+}
+
+function get(r) {
+  return r.contents;
+}
+
+function make(prim) {
+  return {
+          contents: prim
+        };
+}
+
+function set(r, v) {
+  r.contents = v;
+  
+}
+
+var R = {
+  get: get,
+  make: make,
+  set: set
+};
+
+function destroysRefIdentity(x) {
+  return x;
+}
+
+function preserveRefIdentity(x) {
+  return x;
+}
+
+export {
+  create ,
+  access ,
+  update ,
+  R ,
+  get ,
+  make ,
+  set ,
+  destroysRefIdentity ,
+  preserveRefIdentity ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/References.res b/analysis/examples/larger-project/src/References.res
new file mode 100644
index 0000000000..fa4ae2bfc9
--- /dev/null
+++ b/analysis/examples/larger-project/src/References.res
@@ -0,0 +1,47 @@
+// Test pervasive references
+
+@genType
+let create = (x: int) => ref(x)
+
+@genType
+let access = r => r.contents + 1
+
+@genType
+let update = r => r.contents = r.contents + 1
+
+// Abstract version of references: works when conversion is required.
+
+module R: {
+  @genType
+  type t<'a>
+  let get: t<'a> => 'a
+  let make: 'a => t<'a>
+  let set: (t<'a>, 'a) => unit
+} = {
+  type t<'a> = ref<'a>
+  let get = r => r.contents
+  let make = ref
+  let set = (r, v) => r.contents = v
+}
+
+@genType
+type t<'a> = R.t<'a>
+
+@genType
+let get = R.get
+
+@gentype
+let make = R.make
+
+@genType
+let set = R.set
+
+type requiresConversion = {x: int}
+
+// Careful: conversion makes a copy and destroys the reference identity.
+@genType
+let destroysRefIdentity = (x: ref<requiresConversion>) => x
+
+// Using abstract references preserves the identity.
+@genType
+let preserveRefIdentity = (x: R.t<requiresConversion>) => x
diff --git a/analysis/examples/larger-project/src/RepeatedLabel.js b/analysis/examples/larger-project/src/RepeatedLabel.js
new file mode 100644
index 0000000000..fba30a9dd2
--- /dev/null
+++ b/analysis/examples/larger-project/src/RepeatedLabel.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function userData(param) {
+  return {
+          a: param.a,
+          b: param.b
+        };
+}
+
+console.log(userData);
+
+export {
+  userData ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/RepeatedLabel.res b/analysis/examples/larger-project/src/RepeatedLabel.res
new file mode 100644
index 0000000000..64fda72797
--- /dev/null
+++ b/analysis/examples/larger-project/src/RepeatedLabel.res
@@ -0,0 +1,14 @@
+type userData = {
+  a: bool,
+  b: int,
+}
+
+type tabState = {
+  a: bool,
+  b: int,
+  f: string,
+}
+
+let userData = ({a, b}): userData => {a: a, b: b}
+
+Js.log(userData)
diff --git a/analysis/examples/larger-project/src/RequireCond.js b/analysis/examples/larger-project/src/RequireCond.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/RequireCond.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/RequireCond.res b/analysis/examples/larger-project/src/RequireCond.res
new file mode 100644
index 0000000000..b7fdd67a36
--- /dev/null
+++ b/analysis/examples/larger-project/src/RequireCond.res
@@ -0,0 +1,19 @@
+@module
+@deprecated(
+  "Please use this syntax to guarantee safe usage: [%requireCond(`gk, \"gk_name\", ConditionalModule)]"
+)
+external make: (
+  @string [@as("qe.bool") #qeBool | @as("gk") #gk],
+  string,
+  string,
+) => Js.Nullable.t<'a> = "requireCond"
+
+@module
+@deprecated(
+  "Please use this syntax to guarantee safe usage: [%requireCond(`gk, \"gk_name\", {\"true\": ModuleA, \"false\": ModuleB})]"
+)
+external either: (
+  @string [@as("qe.bool") #qeBool | @as("gk") #gk],
+  string,
+  {"true": string, "false": string},
+) => 'b = "requireCond"
diff --git a/analysis/examples/larger-project/src/Shadow.js b/analysis/examples/larger-project/src/Shadow.js
new file mode 100644
index 0000000000..3d1c79858c
--- /dev/null
+++ b/analysis/examples/larger-project/src/Shadow.js
@@ -0,0 +1,21 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function test(param) {
+  return "a";
+}
+
+function test$1(param) {
+  return "a";
+}
+
+var M = {
+  test: test$1
+};
+
+export {
+  test ,
+  M ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Shadow.res b/analysis/examples/larger-project/src/Shadow.res
new file mode 100644
index 0000000000..7e6c5542a1
--- /dev/null
+++ b/analysis/examples/larger-project/src/Shadow.res
@@ -0,0 +1,12 @@
+@genType
+let test = () => 3
+
+@genType
+let test = () => "a"
+
+module M = {
+  @genType
+  let test = () => 3
+
+  let test = () => "a"
+}
diff --git a/analysis/examples/larger-project/src/TestDeadExn.js b/analysis/examples/larger-project/src/TestDeadExn.js
new file mode 100644
index 0000000000..5d949f4d8f
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestDeadExn.js
@@ -0,0 +1,12 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as DeadExn from "./DeadExn.js";
+
+console.log({
+      RE_EXN_ID: DeadExn.Etoplevel
+    });
+
+export {
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/TestDeadExn.res b/analysis/examples/larger-project/src/TestDeadExn.res
new file mode 100644
index 0000000000..b1569a222c
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestDeadExn.res
@@ -0,0 +1 @@
+Js.log(DeadExn.Etoplevel)
diff --git a/analysis/examples/larger-project/src/TestEmitInnerModules.js b/analysis/examples/larger-project/src/TestEmitInnerModules.js
new file mode 100644
index 0000000000..3d1c412ec0
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestEmitInnerModules.js
@@ -0,0 +1,26 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var Inner = {
+  x: 34,
+  y: "hello"
+};
+
+var Inner$1 = {
+  y: 44
+};
+
+var Medium = {
+  Inner: Inner$1
+};
+
+var Outer = {
+  Medium: Medium
+};
+
+export {
+  Inner ,
+  Outer ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TestEmitInnerModules.res b/analysis/examples/larger-project/src/TestEmitInnerModules.res
new file mode 100644
index 0000000000..eed5c4fe23
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestEmitInnerModules.res
@@ -0,0 +1,15 @@
+module Inner = {
+  @genType
+  let x = 34
+  @genType
+  let y = "hello"
+}
+
+module Outer = {
+  module Medium = {
+    module Inner = {
+      @genType
+      let y = 44
+    }
+  }
+}
diff --git a/analysis/examples/larger-project/src/TestFirstClassModules.js b/analysis/examples/larger-project/src/TestFirstClassModules.js
new file mode 100644
index 0000000000..d09433ad8c
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestFirstClassModules.js
@@ -0,0 +1,27 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function convert(x) {
+  return x;
+}
+
+function convertInterface(x) {
+  return x;
+}
+
+function convertRecord(x) {
+  return x;
+}
+
+function convertFirstClassModuleWithTypeEquations(x) {
+  return x;
+}
+
+export {
+  convert ,
+  convertInterface ,
+  convertRecord ,
+  convertFirstClassModuleWithTypeEquations ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TestFirstClassModules.res b/analysis/examples/larger-project/src/TestFirstClassModules.res
new file mode 100644
index 0000000000..a799e858ed
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestFirstClassModules.res
@@ -0,0 +1,30 @@
+@genType
+let convert = (x: FirstClassModules.firstClassModule) => x
+
+@genType
+let convertInterface = (x: FirstClassModulesInterface.firstClassModule) => x
+
+@genType
+let convertRecord = (x: FirstClassModulesInterface.record) => x
+
+module type MT = {
+  type outer
+  let out: outer => outer
+
+  module Inner: {
+    type inner
+    let inn: inner => inner
+  }
+}
+
+@genType
+type firstClassModuleWithTypeEquations<'i, 'o> = module(MT with
+  type Inner.inner = 'i
+  and type outer = 'o
+)
+
+@genType
+let convertFirstClassModuleWithTypeEquations = (
+  type o i,
+  x: module(MT with type Inner.inner = i and type outer = o),
+) => x
diff --git a/analysis/examples/larger-project/src/TestImmutableArray.js b/analysis/examples/larger-project/src/TestImmutableArray.js
new file mode 100644
index 0000000000..1054678078
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestImmutableArray.js
@@ -0,0 +1,24 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
+import * as Caml_array from "rescript/lib/es6/caml_array.js";
+
+function testImmutableArrayGet(arr) {
+  return Caml_array.get(arr, 3);
+}
+
+function testBeltArrayGet(arr) {
+  return Belt_Array.get(arr, 3);
+}
+
+function testBeltArraySet(arr) {
+  return Belt_Array.set(arr, 3, 4);
+}
+
+export {
+  testImmutableArrayGet ,
+  testBeltArrayGet ,
+  testBeltArraySet ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TestImmutableArray.res b/analysis/examples/larger-project/src/TestImmutableArray.res
new file mode 100644
index 0000000000..d4b49b7a2d
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestImmutableArray.res
@@ -0,0 +1,20 @@
+@genType
+let testImmutableArrayGet = arr => {
+  open ImmutableArray
+  arr[3]
+}
+
+/*
+   type error
+   let testImmutableArraySet = arr => ImmutableArray.(arr[3] = 4);
+ */
+
+let testBeltArrayGet = arr => {
+  open Belt
+  arr[3]
+}
+
+let testBeltArraySet = arr => {
+  open Belt
+  arr[3] = 4
+}
diff --git a/analysis/examples/larger-project/src/TestImport.js b/analysis/examples/larger-project/src/TestImport.js
new file mode 100644
index 0000000000..f38c53dbf6
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestImport.js
@@ -0,0 +1,29 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as TestImportGen from "./TestImport.gen";
+
+var innerStuffContents = TestImportGen.innerStuffContents;
+
+var innerStuffContentsAsEmptyObject = TestImportGen.innerStuffContentsAsEmptyObject;
+
+var valueStartingWithUpperCaseLetter = TestImportGen.valueStartingWithUpperCaseLetter;
+
+var defaultValue = TestImportGen.defaultValue;
+
+function make(prim0, prim1, prim2) {
+  return TestImportGen.make(prim0, prim1 !== undefined ? Caml_option.valFromOption(prim1) : undefined, prim2);
+}
+
+var defaultValue2 = TestImportGen.defaultValue2;
+
+export {
+  innerStuffContentsAsEmptyObject ,
+  innerStuffContents ,
+  valueStartingWithUpperCaseLetter ,
+  defaultValue ,
+  make ,
+  defaultValue2 ,
+  
+}
+/* innerStuffContents Not a pure module */
diff --git a/analysis/examples/larger-project/src/TestImport.res b/analysis/examples/larger-project/src/TestImport.res
new file mode 100644
index 0000000000..41d3371654
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestImport.res
@@ -0,0 +1,30 @@
+@genType.import((
+  "./exportNestedValues",
+  "TopLevelClass.MiddleLevelElements.stuff.InnerStuff.innerStuffContents",
+))
+external innerStuffContents: {"x": int} = "innerStuffContents"
+
+@genType.import((
+  "./exportNestedValues",
+  "TopLevelClass.MiddleLevelElements.stuff.InnerStuff.innerStuffContents",
+))
+external innerStuffContentsAsEmptyObject: {.} = "innerStuffContentsAsEmptyObject"
+
+let innerStuffContents = innerStuffContents
+
+@genType.import(("./exportNestedValues", "ValueStartingWithUpperCaseLetter"))
+external valueStartingWithUpperCaseLetter: string = "valueStartingWithUpperCaseLetter"
+
+@genType.import(("./exportNestedValues", "default"))
+external defaultValue: int = "defaultValue"
+
+@genType
+type message = {text: string}
+
+@genType.import(("./MyBanner", "TopLevelClass.MiddleLevelElements.MyBannerInternal"))
+external make: (~show: bool, ~message: option<message>=?, 'a) => React.element = "make"
+
+let make = make
+
+@genType.import(("./exportNestedValues", "default"))
+external defaultValue2: int = "defaultValue2"
diff --git a/analysis/examples/larger-project/src/TestModuleAliases.js b/analysis/examples/larger-project/src/TestModuleAliases.js
new file mode 100644
index 0000000000..50564c7a04
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestModuleAliases.js
@@ -0,0 +1,45 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function testInner1(x) {
+  return x;
+}
+
+function testInner1Expanded(x) {
+  return x;
+}
+
+function testInner2(x) {
+  return x;
+}
+
+function testInner2Expanded(x) {
+  return x;
+}
+
+var OtherFile;
+
+var OtherFileAlias;
+
+var OuterAlias;
+
+var OtherFile1;
+
+var Outer2;
+
+var Inner2;
+
+export {
+  OtherFile ,
+  OtherFileAlias ,
+  OuterAlias ,
+  OtherFile1 ,
+  Outer2 ,
+  Inner2 ,
+  testInner1 ,
+  testInner1Expanded ,
+  testInner2 ,
+  testInner2Expanded ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TestModuleAliases.res b/analysis/examples/larger-project/src/TestModuleAliases.res
new file mode 100644
index 0000000000..fff070c4b7
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestModuleAliases.res
@@ -0,0 +1,41 @@
+module OtherFile = ModuleAliases2
+module OtherFileAlias = OtherFile
+
+@genType
+type record = OtherFile.record
+
+@genType
+type record2 = OtherFileAlias.record
+
+module OuterAlias = OtherFile.Outer
+
+@genType
+type outer = OtherFileAlias.Outer.outer
+
+@genType
+type outer2 = OuterAlias.outer
+
+module OtherFile1 = OtherFile
+module Outer2 = OtherFile1.Outer
+module Inner2 = Outer2.Inner
+
+@genType
+type my2 = Inner2.inner
+
+@genType
+type inner1 = OtherFile.InnerAlias.inner
+
+@genType
+type inner2 = OtherFile.Outer.Inner.inner
+
+@genType
+let testInner1 = (x: inner1) => x
+
+@genType
+let testInner1Expanded = (x: OtherFile.InnerAlias.inner) => x
+
+@genType
+let testInner2 = (x: inner2) => x
+
+@genType
+let testInner2Expanded = (x: OtherFile.Outer.Inner.inner) => x
diff --git a/analysis/examples/larger-project/src/TestOptArg.js b/analysis/examples/larger-project/src/TestOptArg.js
new file mode 100644
index 0000000000..2369ece082
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestOptArg.js
@@ -0,0 +1,44 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as OptArg from "./OptArg.js";
+
+console.log(OptArg.bar(undefined, 3, 3, 4));
+
+function foo(xOpt, y) {
+  var x = xOpt !== undefined ? xOpt : 3;
+  return x + y | 0;
+}
+
+function bar(param) {
+  var x = 12;
+  return x + 3 | 0;
+}
+
+console.log(bar);
+
+function notSuppressesOptArgs(xOpt, yOpt, zOpt, w) {
+  var x = xOpt !== undefined ? xOpt : 1;
+  var y = yOpt !== undefined ? yOpt : 2;
+  var z = zOpt !== undefined ? zOpt : 3;
+  return ((x + y | 0) + z | 0) + w | 0;
+}
+
+notSuppressesOptArgs(undefined, undefined, undefined, 3);
+
+function liveSuppressesOptArgs(xOpt, yOpt, zOpt, w) {
+  var x = xOpt !== undefined ? xOpt : 1;
+  var y = yOpt !== undefined ? yOpt : 2;
+  var z = zOpt !== undefined ? zOpt : 3;
+  return ((x + y | 0) + z | 0) + w | 0;
+}
+
+liveSuppressesOptArgs(3, undefined, undefined, 3);
+
+export {
+  foo ,
+  bar ,
+  notSuppressesOptArgs ,
+  liveSuppressesOptArgs ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/TestOptArg.res b/analysis/examples/larger-project/src/TestOptArg.res
new file mode 100644
index 0000000000..ef38c7c7ec
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestOptArg.res
@@ -0,0 +1,16 @@
+Js.log(OptArg.bar(~z=3, ~y=3, 4))
+
+let foo = (~x=3, y) => x + y
+
+let bar = () => foo(~x=12, 3)
+
+Js.log(bar)
+
+let notSuppressesOptArgs = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let _ = notSuppressesOptArgs(3)
+
+@live
+let liveSuppressesOptArgs = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let _ = liveSuppressesOptArgs(~x=3, 3)
diff --git a/analysis/examples/larger-project/src/TestPromise.js b/analysis/examples/larger-project/src/TestPromise.js
new file mode 100644
index 0000000000..0289ad50c0
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestPromise.js
@@ -0,0 +1,16 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function convert(param) {
+  return param.then(function (param) {
+              return Promise.resolve({
+                          result: param.s
+                        });
+            });
+}
+
+export {
+  convert ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TestPromise.res b/analysis/examples/larger-project/src/TestPromise.res
new file mode 100644
index 0000000000..acf835948e
--- /dev/null
+++ b/analysis/examples/larger-project/src/TestPromise.res
@@ -0,0 +1,14 @@
+@genType
+type promise<'a> = Js.Promise.t<'a>
+
+@genType
+type fromPayload = {
+  x: int,
+  s: string,
+}
+
+@genType
+type toPayload = {result: string}
+
+@genType
+let convert = Js.Promise.then_(({s}) => Js.Promise.resolve({result: s}))
diff --git a/analysis/examples/larger-project/src/ToSuppress.js b/analysis/examples/larger-project/src/ToSuppress.js
new file mode 100644
index 0000000000..b16279a286
--- /dev/null
+++ b/analysis/examples/larger-project/src/ToSuppress.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var toSuppress = 0;
+
+export {
+  toSuppress ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/ToSuppress.res b/analysis/examples/larger-project/src/ToSuppress.res
new file mode 100644
index 0000000000..e21f46451b
--- /dev/null
+++ b/analysis/examples/larger-project/src/ToSuppress.res
@@ -0,0 +1 @@
+let toSuppress = 0
diff --git a/analysis/examples/larger-project/src/TransitiveType1.js b/analysis/examples/larger-project/src/TransitiveType1.js
new file mode 100644
index 0000000000..fb6d4e9f36
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType1.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function convert(x) {
+  return x;
+}
+
+function convertAlias(x) {
+  return x;
+}
+
+export {
+  convert ,
+  convertAlias ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TransitiveType1.res b/analysis/examples/larger-project/src/TransitiveType1.res
new file mode 100644
index 0000000000..3d16c5e3b9
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType1.res
@@ -0,0 +1,5 @@
+@genType
+let convert = (x: TransitiveType2.t2) => x
+
+@genType
+let convertAlias = (x: TransitiveType2.t2Alias) => x
diff --git a/analysis/examples/larger-project/src/TransitiveType2.js b/analysis/examples/larger-project/src/TransitiveType2.js
new file mode 100644
index 0000000000..e0bcc1c485
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType2.js
@@ -0,0 +1,12 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function convertT2(x) {
+  return x;
+}
+
+export {
+  convertT2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TransitiveType2.res b/analysis/examples/larger-project/src/TransitiveType2.res
new file mode 100644
index 0000000000..a0adb89742
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType2.res
@@ -0,0 +1,7 @@
+@genType
+type t2 = option<TransitiveType3.t3>
+
+@genType
+type t2Alias = t2
+
+let convertT2 = (x: t2) => x
diff --git a/analysis/examples/larger-project/src/TransitiveType3.js b/analysis/examples/larger-project/src/TransitiveType3.js
new file mode 100644
index 0000000000..c28c6fa11b
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType3.js
@@ -0,0 +1,12 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function convertT3(x) {
+  return x;
+}
+
+export {
+  convertT3 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TransitiveType3.res b/analysis/examples/larger-project/src/TransitiveType3.res
new file mode 100644
index 0000000000..0e561b0cbb
--- /dev/null
+++ b/analysis/examples/larger-project/src/TransitiveType3.res
@@ -0,0 +1,8 @@
+@genType
+type t3 = {
+  i: int,
+  s: string,
+}
+
+@genType
+let convertT3 = (x: t3) => x
diff --git a/analysis/examples/larger-project/src/Tuples.js b/analysis/examples/larger-project/src/Tuples.js
new file mode 100644
index 0000000000..3e1475545e
--- /dev/null
+++ b/analysis/examples/larger-project/src/Tuples.js
@@ -0,0 +1,73 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Belt_Option from "rescript/lib/es6/belt_Option.js";
+
+function testTuple(param) {
+  return param[0] + param[1] | 0;
+}
+
+function computeArea(param) {
+  return Math.imul(Math.imul(param[0], param[1]), Belt_Option.mapWithDefault(param[2], 1, (function (n) {
+                    return n;
+                  })));
+}
+
+function computeAreaWithIdent(param) {
+  return Math.imul(Math.imul(param[0], param[1]), Belt_Option.mapWithDefault(param[2], 1, (function (n) {
+                    return n;
+                  })));
+}
+
+function computeAreaNoConverters(param) {
+  return Math.imul(param[0], param[1]);
+}
+
+function coord2d(x, y) {
+  return [
+          x,
+          y,
+          undefined
+        ];
+}
+
+function getFirstName(param) {
+  return param[0].name;
+}
+
+function marry(first, second) {
+  return [
+          first,
+          second
+        ];
+}
+
+function changeSecondAge(param) {
+  var second = param[1];
+  return [
+          param[0],
+          {
+            name: second.name,
+            age: second.age + 1 | 0
+          }
+        ];
+}
+
+var origin = [
+  0,
+  0,
+  0
+];
+
+export {
+  testTuple ,
+  origin ,
+  computeArea ,
+  computeAreaWithIdent ,
+  computeAreaNoConverters ,
+  coord2d ,
+  getFirstName ,
+  marry ,
+  changeSecondAge ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Tuples.res b/analysis/examples/larger-project/src/Tuples.res
new file mode 100644
index 0000000000..e38ce79a85
--- /dev/null
+++ b/analysis/examples/larger-project/src/Tuples.res
@@ -0,0 +1,49 @@
+open Belt
+
+@genType
+let testTuple = ((a, b)) => a + b
+
+@genType
+type coord = (int, int, option<int>)
+
+@genType
+let origin = (0, 0, Some(0))
+
+@genType
+let computeArea = ((x, y, z)) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let computeAreaWithIdent = ((x, y, z): coord) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let computeAreaNoConverters = ((x: int, y: int)) => x * y
+
+@genType
+let coord2d = (x, y) => (x, y, None)
+
+@genType
+type coord2 = (int, int, Js.Nullable.t<int>)
+
+@genType
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType
+type couple = (person, person)
+
+@genType
+let getFirstName = ((first, _second): couple) => first.name
+
+@genType
+let marry = (first, second): couple => (first, second)
+
+@genType
+let changeSecondAge = ((first, second): couple): couple => (first, {...second, age: second.age + 1})
diff --git a/analysis/examples/larger-project/src/TypeParams1.js b/analysis/examples/larger-project/src/TypeParams1.js
new file mode 100644
index 0000000000..32eae0619e
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams1.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var exportSomething = 10;
+
+export {
+  exportSomething ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TypeParams1.res b/analysis/examples/larger-project/src/TypeParams1.res
new file mode 100644
index 0000000000..2e51defff8
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams1.res
@@ -0,0 +1,4 @@
+@gentype
+type ocaml_array<'a> = array<'a>
+
+let exportSomething = 10
diff --git a/analysis/examples/larger-project/src/TypeParams2.js b/analysis/examples/larger-project/src/TypeParams2.js
new file mode 100644
index 0000000000..32eae0619e
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams2.js
@@ -0,0 +1,10 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var exportSomething = 10;
+
+export {
+  exportSomething ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TypeParams2.res b/analysis/examples/larger-project/src/TypeParams2.res
new file mode 100644
index 0000000000..f6a2ec0b0c
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams2.res
@@ -0,0 +1,10 @@
+@genType
+type item = {id: int}
+
+@genType
+type items = TypeParams1.ocaml_array<item>
+
+@genType
+type items2 = array<item>
+
+let exportSomething = 10
diff --git a/analysis/examples/larger-project/src/TypeParams3.js b/analysis/examples/larger-project/src/TypeParams3.js
new file mode 100644
index 0000000000..5cca982d84
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams3.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function test(x) {
+  return x;
+}
+
+function test2(x) {
+  return x;
+}
+
+export {
+  test ,
+  test2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/TypeParams3.res b/analysis/examples/larger-project/src/TypeParams3.res
new file mode 100644
index 0000000000..6fd0044688
--- /dev/null
+++ b/analysis/examples/larger-project/src/TypeParams3.res
@@ -0,0 +1,5 @@
+@genType
+let test = (x: TypeParams2.items) => x
+
+@genType
+let test2 = (x: TypeParams2.items2) => x
diff --git a/analysis/examples/larger-project/src/Types.js b/analysis/examples/larger-project/src/Types.js
new file mode 100644
index 0000000000..5931d70cbb
--- /dev/null
+++ b/analysis/examples/larger-project/src/Types.js
@@ -0,0 +1,109 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Belt_Option from "rescript/lib/es6/belt_Option.js";
+
+function swap(tree) {
+  return {
+          label: tree.label,
+          left: Belt_Option.map(tree.right, swap),
+          right: Belt_Option.map(tree.left, swap)
+        };
+}
+
+function selfRecursiveConverter(param) {
+  return param.self;
+}
+
+function mutuallyRecursiveConverter(param) {
+  return param.b;
+}
+
+function testFunctionOnOptionsAsArgument(a, foo) {
+  return Curry._1(foo, a);
+}
+
+function jsonStringify(prim) {
+  return JSON.stringify(prim);
+}
+
+function testConvertNull(x) {
+  return x;
+}
+
+var testMarshalFields = {
+  rec: "rec",
+  _switch: "_switch",
+  switch: "switch",
+  __: "__",
+  _: "_",
+  foo: "foo",
+  _foo: "_foo",
+  Uppercase: "Uppercase",
+  _Uppercase: "_Uppercase"
+};
+
+function setMatch(x) {
+  x.match = 34;
+  
+}
+
+function testInstantiateTypeParameter(x) {
+  return x;
+}
+
+var currentTime = new Date();
+
+var optFunction = (function (param) {
+    return 3;
+  });
+
+var ObjectId = {};
+
+var someIntList = {
+  hd: 1,
+  tl: {
+    hd: 2,
+    tl: {
+      hd: 3,
+      tl: /* [] */0
+    }
+  }
+};
+
+var map = List.map;
+
+var stringT = "a";
+
+var jsStringT = "a";
+
+var jsString2T = "a";
+
+var i64Const = [
+  0,
+  34
+];
+
+export {
+  someIntList ,
+  map ,
+  swap ,
+  selfRecursiveConverter ,
+  mutuallyRecursiveConverter ,
+  testFunctionOnOptionsAsArgument ,
+  stringT ,
+  jsStringT ,
+  jsString2T ,
+  jsonStringify ,
+  testConvertNull ,
+  testMarshalFields ,
+  setMatch ,
+  testInstantiateTypeParameter ,
+  currentTime ,
+  i64Const ,
+  optFunction ,
+  ObjectId ,
+  
+}
+/* currentTime Not a pure module */
diff --git a/analysis/examples/larger-project/src/Types.res b/analysis/examples/larger-project/src/Types.res
new file mode 100644
index 0000000000..ee398c1fff
--- /dev/null
+++ b/analysis/examples/larger-project/src/Types.res
@@ -0,0 +1,167 @@
+@genType
+type t = int
+
+@genType
+let someIntList = list{1, 2, 3}
+
+@genType
+let map = List.map
+
+@genType
+type typeWithVars<'x, 'y, 'z> =
+  | A('x, 'y)
+  | B('z)
+
+@genType
+type rec tree = {"label": string, "left": option<tree>, "right": option<tree>}
+
+/*
+ * A tree is a recursive type which does not require any conversion (JS object).
+ * All is well.
+ */
+@genType
+let rec swap = (tree: tree): tree =>
+  {
+    "label": tree["label"],
+    "left": tree["right"]->Belt.Option.map(swap),
+    "right": tree["left"]->Belt.Option.map(swap),
+  }
+
+@genType
+type rec selfRecursive = {self: selfRecursive}
+
+@genType
+type rec mutuallyRecursiveA = {b: mutuallyRecursiveB}
+and mutuallyRecursiveB = {a: mutuallyRecursiveA}
+
+/*
+ * This is a recursive type which requires conversion (a record).
+ * Only a shallow conversion of the top-level element is performed.
+ */
+@genType
+let selfRecursiveConverter = ({self}) => self
+
+/*
+ * This is a mutually recursive type which requires conversion (a record).
+ * Only a shallow conversion of the two top-level elements is performed.
+ */
+@genType
+let mutuallyRecursiveConverter = ({b}) => b
+
+@genType
+let testFunctionOnOptionsAsArgument = (a: option<'a>, foo) => foo(a)
+
+@genType.opaque
+type opaqueVariant =
+  | A
+  | B
+
+@genType
+let stringT: String.t = "a"
+
+@genType
+let jsStringT: Js.String.t = "a"
+
+@genType
+let jsString2T: Js.String2.t = "a"
+
+@genType
+type twice<'a> = ('a, 'a)
+
+@gentype
+type genTypeMispelled = int
+
+@genType
+type dictString = Js.Dict.t<string>
+
+@genType
+let jsonStringify = Js.Json.stringify
+
+@genType
+type nullOrString = Js.Null.t<string>
+
+@genType
+type nullOrString2 = Js.null<string>
+
+type record = {
+  i: int,
+  s: string,
+}
+
+@genType
+let testConvertNull = (x: Js.Null.t<record>) => x
+
+@genType
+type decorator<'a, 'b> = 'a => 'b constraint 'a = int constraint 'b = _ => _
+
+/* Bucklescript's marshaling rules. */
+@genType
+type marshalFields = {
+  "_rec": string,
+  "_switch": string,
+  "switch": string,
+  "__": string,
+  "___": string,
+  "foo__": string,
+  "_foo__": string,
+  "_Uppercase": string,
+  "_Uppercase__": string,
+}
+
+@genType
+let testMarshalFields: marshalFields = {
+  "_rec": "rec",
+  "_switch" /* reason keywords are not recognized */: "_switch",
+  "switch": "switch",
+  "__": "__",
+  "___": "_",
+  "foo__": "foo",
+  "_foo__": "_foo",
+  "_Uppercase": "Uppercase",
+  "_Uppercase__": "_Uppercase",
+}
+
+@genType
+type marshalMutableField = {@set "_match": int}
+
+@genType
+let setMatch = (x: marshalMutableField) => x["_match"] = 34
+
+type ocaml_array<'a> = array<'a>
+
+// This should be considered annotated automatically.
+type someRecord = {id: int}
+
+type instantiateTypeParameter = ocaml_array<someRecord>
+
+@genType
+let testInstantiateTypeParameter = (x: instantiateTypeParameter) => x
+
+@genType @genType.as("Vector")
+type vector<'a> = ('a, 'a)
+
+@genType
+type date = Js.Date.t
+
+@genType
+let currentTime = Js.Date.make()
+
+@genType
+type i64A = Int64.t
+
+@genType
+type i64B = int64
+
+@genType
+let i64Const: i64B = 34L
+
+@genType
+let optFunction = Some(() => 3)
+
+module ObjectId: {
+  @genType
+  type t = int
+} = {
+  type t = int
+  let x = 1
+}
diff --git a/analysis/examples/larger-project/src/Unboxed.js b/analysis/examples/larger-project/src/Unboxed.js
new file mode 100644
index 0000000000..ff72d52a52
--- /dev/null
+++ b/analysis/examples/larger-project/src/Unboxed.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function testV1(x) {
+  return x;
+}
+
+function r2Test(x) {
+  return x;
+}
+
+export {
+  testV1 ,
+  r2Test ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Unboxed.res b/analysis/examples/larger-project/src/Unboxed.res
new file mode 100644
index 0000000000..fdc9c03bfb
--- /dev/null
+++ b/analysis/examples/larger-project/src/Unboxed.res
@@ -0,0 +1,17 @@
+@genType @ocaml.unboxed
+type v1 = A(int)
+
+@genType @unboxed
+type v2 = A(int)
+
+@genType
+let testV1 = (x: v1) => x
+
+@genType @unboxed
+type r1 = {x: int}
+
+@genType @ocaml.unboxed
+type r2 = B({g: string})
+
+@genType
+let r2Test = (x: r2) => x
diff --git a/analysis/examples/larger-project/src/Uncurried.js b/analysis/examples/larger-project/src/Uncurried.js
new file mode 100644
index 0000000000..1ab5858635
--- /dev/null
+++ b/analysis/examples/larger-project/src/Uncurried.js
@@ -0,0 +1,80 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+
+function uncurried0() {
+  return "";
+}
+
+function uncurried1(x) {
+  return String(x);
+}
+
+function uncurried2(x, y) {
+  return String(x) + y;
+}
+
+function uncurried3(x, y, z) {
+  return String(x) + (y + String(z));
+}
+
+function curried3(x, y, z) {
+  return String(x) + (y + String(z));
+}
+
+function callback(cb) {
+  return String(Curry._1(cb, undefined));
+}
+
+function callback2(auth) {
+  return Curry._1(auth.login, undefined);
+}
+
+function callback2U(auth) {
+  return auth.loginU();
+}
+
+function sumU(n, m) {
+  console.log("sumU 2nd arg", m, "result", n + m | 0);
+  
+}
+
+function sumU2(n) {
+  return function (m) {
+    console.log("sumU2 2nd arg", m, "result", n + m | 0);
+    
+  };
+}
+
+function sumCurried(n) {
+  console.log("sumCurried 1st arg", n);
+  return function (m) {
+    console.log("sumCurried 2nd arg", m, "result", n + m | 0);
+    
+  };
+}
+
+function sumLblCurried(s, n) {
+  console.log(s, "sumLblCurried 1st arg", n);
+  return function (m) {
+    console.log("sumLblCurried 2nd arg", m, "result", n + m | 0);
+    
+  };
+}
+
+export {
+  uncurried0 ,
+  uncurried1 ,
+  uncurried2 ,
+  uncurried3 ,
+  curried3 ,
+  callback ,
+  callback2 ,
+  callback2U ,
+  sumU ,
+  sumU2 ,
+  sumCurried ,
+  sumLblCurried ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Uncurried.res b/analysis/examples/larger-project/src/Uncurried.res
new file mode 100644
index 0000000000..641a8b6f6a
--- /dev/null
+++ b/analysis/examples/larger-project/src/Uncurried.res
@@ -0,0 +1,56 @@
+@genType
+type u0 = (. unit) => string
+
+@genType
+type u1 = (. int) => string
+
+@genType
+type u2 = (. int, string) => string
+
+@genType
+type u3 = (. int, string, int) => string
+
+@genType
+let uncurried0 = (. ()) => ""
+
+@genType
+let uncurried1 = (. x) => x |> string_of_int
+
+@genType
+let uncurried2 = (. x, y) => (x |> string_of_int) ++ y
+
+@genType
+let uncurried3 = (. x, y, z) => (x |> string_of_int) ++ (y ++ (z |> string_of_int))
+
+@genType
+let curried3 = (x, y, z) => (x |> string_of_int) ++ (y ++ (z |> string_of_int))
+
+@genType
+let callback = cb => cb() |> string_of_int
+
+type auth = {login: unit => string}
+type authU = {loginU: (. unit) => string}
+
+@genType
+let callback2 = auth => auth.login()
+
+@genType
+let callback2U = auth => auth.loginU(.)
+
+@genType
+let sumU = (. n, m) => Js.log4("sumU 2nd arg", m, "result", n + m)
+
+@genType
+let sumU2 = (. n, . m) => Js.log4("sumU2 2nd arg", m, "result", n + m)
+
+@genType
+let sumCurried = n => {
+  Js.log2("sumCurried 1st arg", n)
+  m => Js.log4("sumCurried 2nd arg", m, "result", n + m)
+}
+
+@genType
+let sumLblCurried = (s: string, ~n) => {
+  Js.log3(s, "sumLblCurried 1st arg", n)
+  (~m) => Js.log4("sumLblCurried 2nd arg", m, "result", n + m)
+}
diff --git a/analysis/examples/larger-project/src/Unison.js b/analysis/examples/larger-project/src/Unison.js
new file mode 100644
index 0000000000..1be71e8162
--- /dev/null
+++ b/analysis/examples/larger-project/src/Unison.js
@@ -0,0 +1,76 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function group(breakOpt, doc) {
+  var $$break = breakOpt !== undefined ? breakOpt : /* IfNeed */0;
+  return {
+          break: $$break,
+          doc: doc
+        };
+}
+
+function fits(_w, _stack) {
+  while(true) {
+    var stack = _stack;
+    var w = _w;
+    if (w < 0) {
+      return false;
+    }
+    if (!stack) {
+      return true;
+    }
+    _stack = stack._1;
+    _w = w - stack._0.doc.length | 0;
+    continue ;
+  };
+}
+
+function toString(width, stack) {
+  if (!stack) {
+    return "";
+  }
+  var stack$1 = stack._1;
+  var match = stack._0;
+  var doc = match.doc;
+  switch (match.break) {
+    case /* IfNeed */0 :
+        return (
+                fits(width, stack$1) ? "fits " : "no "
+              ) + toString(width - 1 | 0, stack$1);
+    case /* Never */1 :
+        return "never " + (doc + toString(width - 1 | 0, stack$1));
+    case /* Always */2 :
+        return "always " + (doc + toString(width - 1 | 0, stack$1));
+    
+  }
+}
+
+toString(80, /* Empty */0);
+
+var $$break = /* Never */1;
+
+toString(80, /* Cons */{
+      _0: {
+        break: $$break,
+        doc: "abc"
+      },
+      _1: /* Empty */0
+    });
+
+var $$break$1 = /* Always */2;
+
+toString(80, /* Cons */{
+      _0: {
+        break: $$break$1,
+        doc: "d"
+      },
+      _1: /* Empty */0
+    });
+
+export {
+  group ,
+  fits ,
+  toString ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/Unison.res b/analysis/examples/larger-project/src/Unison.res
new file mode 100644
index 0000000000..cfef1fa1d3
--- /dev/null
+++ b/analysis/examples/larger-project/src/Unison.res
@@ -0,0 +1,39 @@
+// Exmple of several DCE checks operating in unison
+
+type break =
+  | IfNeed
+  | Never
+  | Always
+
+type t = {
+  break: break,
+  doc: string,
+}
+
+type rec stack =
+  | Empty
+  | Cons(t, stack)
+
+let group = (~break=IfNeed, doc) => {break: break, doc: doc}
+
+let rec fits = (w, stack) =>
+  switch stack {
+  | _ if w < 0 => false
+  | Empty => true
+  | Cons({doc}, stack) => fits(w - String.length(doc), stack)
+  }
+
+let rec toString = (~width, stack) =>
+  switch stack {
+  | Cons({break, doc}, stack) =>
+    switch break {
+    | IfNeed => (fits(width, stack) ? "fits " : "no ") ++ (stack |> toString(~width=width - 1))
+    | Never => "never " ++ (doc ++ (stack |> toString(~width=width - 1)))
+    | Always => "always " ++ (doc ++ (stack |> toString(~width=width - 1)))
+    }
+  | Empty => ""
+  }
+
+toString(~width=80, Empty)
+toString(~width=80, Cons(group(~break=Never, "abc"), Empty))
+toString(~width=80, Cons(group(~break=Always, "d"), Empty))
diff --git a/analysis/examples/larger-project/src/UseImportJsValue.js b/analysis/examples/larger-project/src/UseImportJsValue.js
new file mode 100644
index 0000000000..45a04b55f1
--- /dev/null
+++ b/analysis/examples/larger-project/src/UseImportJsValue.js
@@ -0,0 +1,17 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function useGetProp(x) {
+  return x.getProp() + 1 | 0;
+}
+
+function useTypeImportedInOtherModule(x) {
+  return x;
+}
+
+export {
+  useGetProp ,
+  useTypeImportedInOtherModule ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/UseImportJsValue.res b/analysis/examples/larger-project/src/UseImportJsValue.res
new file mode 100644
index 0000000000..08d9aee52a
--- /dev/null
+++ b/analysis/examples/larger-project/src/UseImportJsValue.res
@@ -0,0 +1,5 @@
+@genType
+let useGetProp = (x: ImportJsValue.AbsoluteValue.t) => x->ImportJsValue.AbsoluteValue.getProp + 1
+
+@genType
+let useTypeImportedInOtherModule = (x: ImportJsValue.stringFunction) => x
diff --git a/analysis/examples/larger-project/src/Variants.js b/analysis/examples/larger-project/src/Variants.js
new file mode 100644
index 0000000000..925f338310
--- /dev/null
+++ b/analysis/examples/larger-project/src/Variants.js
@@ -0,0 +1,107 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function isWeekend(x) {
+  if (x === "sunday") {
+    return true;
+  } else {
+    return x === "saturday";
+  }
+}
+
+function onlySunday(param) {
+  
+}
+
+function swap(x) {
+  if (x === "sunday") {
+    return "saturday";
+  } else {
+    return "sunday";
+  }
+}
+
+function testConvert(x) {
+  return x;
+}
+
+function testConvert2(x) {
+  return x;
+}
+
+function testConvert3(x) {
+  return x;
+}
+
+function testConvert2to3(x) {
+  return x;
+}
+
+function id1(x) {
+  return x;
+}
+
+function id2(x) {
+  return x;
+}
+
+function polyWithOpt(foo) {
+  if (foo === "bar") {
+    return ;
+  } else if (foo !== "baz") {
+    return {
+            NAME: "One",
+            VAL: foo
+          };
+  } else {
+    return {
+            NAME: "Two",
+            VAL: 1
+          };
+  }
+}
+
+function restResult1(x) {
+  return x;
+}
+
+function restResult2(x) {
+  return x;
+}
+
+function restResult3(x) {
+  return x;
+}
+
+var monday = "monday";
+
+var saturday = "saturday";
+
+var sunday = "sunday";
+
+var fortytwoOK = "fortytwo";
+
+var fortytwoBAD = "fortytwo";
+
+export {
+  isWeekend ,
+  monday ,
+  saturday ,
+  sunday ,
+  onlySunday ,
+  swap ,
+  testConvert ,
+  fortytwoOK ,
+  fortytwoBAD ,
+  testConvert2 ,
+  testConvert3 ,
+  testConvert2to3 ,
+  id1 ,
+  id2 ,
+  polyWithOpt ,
+  restResult1 ,
+  restResult2 ,
+  restResult3 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/Variants.res b/analysis/examples/larger-project/src/Variants.res
new file mode 100644
index 0000000000..247a883a6b
--- /dev/null
+++ b/analysis/examples/larger-project/src/Variants.res
@@ -0,0 +1,118 @@
+@genType
+type weekday = [
+  | #monday
+  | #tuesday
+  | #wednesday
+  | #thursday
+  | #friday
+  | #saturday
+  | #sunday
+]
+
+@genType
+let isWeekend = (x: weekday) =>
+  switch x {
+  | #saturday
+  | #sunday => true
+  | _ => false
+  }
+
+@genType
+let monday = #monday
+@genType
+let saturday = #saturday
+@genType
+let sunday = #sunday
+
+@genType
+let onlySunday = (_: [#sunday]) => ()
+
+@genType
+let swap = x =>
+  switch x {
+  | #sunday => #saturday
+  | #saturday => #sunday
+  }
+
+@genType
+type testGenTypeAs = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("42") #fortytwo
+]
+
+@genType
+let testConvert = (x: testGenTypeAs) => x
+
+@genType
+let fortytwoOK: testGenTypeAs = #fortytwo
+
+/* Exporting this is BAD: type inference means it's not mapped to "42" */
+@genType
+let fortytwoBAD = #fortytwo
+
+@genType
+type testGenTypeAs2 = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("42") #fortytwo
+]
+
+/* Since testGenTypeAs2 is the same type as testGenTypeAs1,
+ share the conversion map. */
+@genType
+let testConvert2 = (x: testGenTypeAs2) => x
+
+@genType
+type testGenTypeAs3 = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("THIS IS DIFFERENT") #fortytwo
+]
+
+/* Since testGenTypeAs3 has a different representation:
+ use a new conversion map. */
+@genType
+let testConvert3 = (x: testGenTypeAs3) => x
+
+/* This converts between testGenTypeAs2 and testGenTypeAs3 */
+@genType
+let testConvert2to3 = (x: testGenTypeAs2): testGenTypeAs3 => x
+
+@genType
+type x1 = [#x | @genType.as("same") #x1]
+
+@genType
+type x2 = [#x | @genType.as("same") #x2]
+
+@genType
+let id1 = (x: x1) => x
+
+@genType
+let id2 = (x: x2) => x
+
+@genType @genType.as("type")
+type type_ = | @genType.as("type") Type
+
+@genType
+let polyWithOpt = foo => foo === "bar" ? None : foo !== "baz" ? Some(#One(foo)) : Some(#Two(1))
+
+@genType
+type result1<'a, 'b> =
+  | Ok('a)
+  | Error('b)
+
+@genType
+type result2<'a, 'b> = result<'a, 'b>
+
+@genType
+type result3<'a, 'b> = Belt.Result.t<'a, 'b>
+
+@genType
+let restResult1 = (x: result1<int, string>) => x
+
+@genType
+let restResult2 = (x: result2<int, string>) => x
+
+@genType
+let restResult3 = (x: result3<int, string>) => x
diff --git a/analysis/examples/larger-project/src/VariantsWithPayload.js b/analysis/examples/larger-project/src/VariantsWithPayload.js
new file mode 100644
index 0000000000..a04a25a65a
--- /dev/null
+++ b/analysis/examples/larger-project/src/VariantsWithPayload.js
@@ -0,0 +1,99 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function testWithPayload(x) {
+  return x;
+}
+
+function printVariantWithPayload(x) {
+  if (typeof x !== "object") {
+    if (x === "a") {
+      console.log("printVariantWithPayload: a");
+    } else if (x === "b") {
+      console.log("printVariantWithPayload: b");
+    } else if (x === "Half") {
+      console.log("printVariantWithPayload: Half");
+    } else if (x === "True") {
+      console.log("printVariantWithPayload: True");
+    } else {
+      console.log("printVariantWithPayload: Twenty");
+    }
+    return ;
+  }
+  var payload = x.VAL;
+  console.log("printVariantWithPayload x:", payload.x, "y:", payload.y);
+  
+}
+
+function testManyPayloads(x) {
+  return x;
+}
+
+function printManyPayloads(x) {
+  var variant = x.NAME;
+  if (variant === "two") {
+    var match = x.VAL;
+    console.log("printManyPayloads two:", match[0], match[1]);
+    return ;
+  }
+  if (variant === "three") {
+    var payload = x.VAL;
+    console.log("printManyPayloads x:", payload.x, "y:", payload.y);
+    return ;
+  }
+  console.log("printManyPayloads one:", x.VAL);
+  
+}
+
+function testSimpleVariant(x) {
+  return x;
+}
+
+function testVariantWithPayloads(x) {
+  return x;
+}
+
+function printVariantWithPayloads(x) {
+  if (typeof x === "number") {
+    console.log("printVariantWithPayloads", "A");
+    return ;
+  }
+  switch (x.TAG | 0) {
+    case /* B */0 :
+        console.log("printVariantWithPayloads", "B(" + (String(x._0) + ")"));
+        return ;
+    case /* C */1 :
+        console.log("printVariantWithPayloads", "C(" + (String(x._0) + (", " + (String(x._1) + ")"))));
+        return ;
+    case /* D */2 :
+        var match = x._0;
+        console.log("printVariantWithPayloads", "D((" + (String(match[0]) + (", " + (String(match[1]) + "))"))));
+        return ;
+    case /* E */3 :
+        console.log("printVariantWithPayloads", "E(" + (String(x._0) + (", " + (x._1 + (", " + (String(x._2) + ")"))))));
+        return ;
+    
+  }
+}
+
+function testVariant1Int(x) {
+  return x;
+}
+
+function testVariant1Object(x) {
+  return x;
+}
+
+export {
+  testWithPayload ,
+  printVariantWithPayload ,
+  testManyPayloads ,
+  printManyPayloads ,
+  testSimpleVariant ,
+  testVariantWithPayloads ,
+  printVariantWithPayloads ,
+  testVariant1Int ,
+  testVariant1Object ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/VariantsWithPayload.res b/analysis/examples/larger-project/src/VariantsWithPayload.res
new file mode 100644
index 0000000000..2252fa3da5
--- /dev/null
+++ b/analysis/examples/larger-project/src/VariantsWithPayload.res
@@ -0,0 +1,99 @@
+type payload = {
+  x: int,
+  y: option<string>,
+}
+
+type withPayload = [
+  | #a
+  | @genType.as("bRenamed") #b
+  | @genType.as(true) #True
+  | @genType.as(20) #Twenty
+  | @genType.as(0.5) #Half
+  | #c(payload)
+]
+
+@genType
+let testWithPayload = (x: withPayload) => x
+
+@genType
+let printVariantWithPayload = (x: withPayload) =>
+  switch x {
+  | #a => Js.log("printVariantWithPayload: a")
+  | #b => Js.log("printVariantWithPayload: b")
+  | #True => Js.log("printVariantWithPayload: True")
+  | #Twenty => Js.log("printVariantWithPayload: Twenty")
+  | #Half => Js.log("printVariantWithPayload: Half")
+  | #c(payload) => Js.log4("printVariantWithPayload x:", payload.x, "y:", payload.y)
+  }
+
+@genType
+type manyPayloads = [
+  | @genType.as("oneRenamed") #one(int)
+  | @genType.as(2) #two(string, string)
+  | #three(payload)
+]
+
+@genType
+let testManyPayloads = (x: manyPayloads) => x
+
+@genType
+let printManyPayloads = (x: manyPayloads) =>
+  switch x {
+  | #one(n) => Js.log2("printManyPayloads one:", n)
+  | #two(s1, s2) => Js.log3("printManyPayloads two:", s1, s2)
+  | #three(payload) => Js.log4("printManyPayloads x:", payload.x, "y:", payload.y)
+  }
+
+@genType
+type simpleVariant =
+  | A
+  | B
+  | C
+
+@genType
+let testSimpleVariant = (x: simpleVariant) => x
+
+@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
+
+@genType
+let printVariantWithPayloads = x =>
+  switch x {
+  | A => Js.log2("printVariantWithPayloads", "A")
+  | B(x) => Js.log2("printVariantWithPayloads", "B(" ++ (string_of_int(x) ++ ")"))
+  | C(x, y) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "C(" ++ (string_of_int(x) ++ (", " ++ (string_of_int(y) ++ ")"))),
+    )
+  | D((x, y)) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "D((" ++ (string_of_int(x) ++ (", " ++ (string_of_int(y) ++ "))"))),
+    )
+  | E(x, s, y) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "E(" ++ (string_of_int(x) ++ (", " ++ (s ++ (", " ++ (string_of_int(y) ++ ")"))))),
+    )
+  }
+
+@genType
+type variant1Int = R(int)
+
+@genType
+let testVariant1Int = (x: variant1Int) => x
+
+@genType
+type variant1Object = R(payload)
+
+@genType
+let testVariant1Object = (x: variant1Object) => x
diff --git a/analysis/examples/larger-project/src/arg_helper.js b/analysis/examples/larger-project/src/arg_helper.js
new file mode 100644
index 0000000000..41b8395517
--- /dev/null
+++ b/analysis/examples/larger-project/src/arg_helper.js
@@ -0,0 +1,229 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Printf from "./printf.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Printexc from "rescript/lib/es6/printexc.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function fatal(err) {
+  console.error(err);
+  return Pervasives.exit(2);
+}
+
+function Make(S) {
+  var $$default = function (v) {
+    return {
+            base_default: v,
+            base_override: S.Key.$$Map.empty,
+            user_default: undefined,
+            user_override: S.Key.$$Map.empty
+          };
+  };
+  var set_base_default = function (value, t) {
+    return {
+            base_default: value,
+            base_override: t.base_override,
+            user_default: t.user_default,
+            user_override: t.user_override
+          };
+  };
+  var add_base_override = function (key, value, t) {
+    return {
+            base_default: t.base_default,
+            base_override: Curry._3(S.Key.$$Map.add, key, value, t.base_override),
+            user_default: t.user_default,
+            user_override: t.user_override
+          };
+  };
+  var reset_base_overrides = function (t) {
+    return {
+            base_default: t.base_default,
+            base_override: S.Key.$$Map.empty,
+            user_default: t.user_default,
+            user_override: t.user_override
+          };
+  };
+  var set_user_default = function (value, t) {
+    return {
+            base_default: t.base_default,
+            base_override: t.base_override,
+            user_default: Caml_option.some(value),
+            user_override: t.user_override
+          };
+  };
+  var add_user_override = function (key, value, t) {
+    return {
+            base_default: t.base_default,
+            base_override: t.base_override,
+            user_default: t.user_default,
+            user_override: Curry._3(S.Key.$$Map.add, key, value, t.user_override)
+          };
+  };
+  var Parse_failure = /* @__PURE__ */Caml_exceptions.create("Arg_helper.Make(S).Parse_failure");
+  var parse_exn = function (str, update) {
+    var values = List.filter(function (param) {
+            return "" !== param;
+          })($$String.split_on_char(/* ',' */44, str));
+    var parsed = List.fold_left((function (acc, value) {
+            var equals;
+            try {
+              equals = $$String.index(value, /* '=' */61);
+            }
+            catch (raw_exn){
+              var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+              if (exn.RE_EXN_ID === "Not_found") {
+                var exit = 0;
+                var value$1;
+                try {
+                  value$1 = Curry._1(S.Value.of_string, value);
+                  exit = 2;
+                }
+                catch (raw_exn$1){
+                  var exn$1 = Caml_js_exceptions.internalToOCamlException(raw_exn$1);
+                  throw {
+                        RE_EXN_ID: Parse_failure,
+                        _1: exn$1,
+                        Error: new Error()
+                      };
+                }
+                if (exit === 2) {
+                  return set_user_default(value$1, acc);
+                }
+                
+              } else {
+                throw exn;
+              }
+            }
+            var length = value.length;
+            if (!(equals >= 0 && equals < length)) {
+              throw {
+                    RE_EXN_ID: "Assert_failure",
+                    _1: [
+                      "arg_helper.res",
+                      84,
+                      8
+                    ],
+                    Error: new Error()
+                  };
+            }
+            if (equals === 0) {
+              throw {
+                    RE_EXN_ID: Parse_failure,
+                    _1: {
+                      RE_EXN_ID: "Failure",
+                      _1: "Missing key in argument specification"
+                    },
+                    Error: new Error()
+                  };
+            }
+            var key = $$String.sub(value, 0, equals);
+            var key$1;
+            try {
+              key$1 = Curry._1(S.Key.of_string, key);
+            }
+            catch (raw_exn$2){
+              var exn$2 = Caml_js_exceptions.internalToOCamlException(raw_exn$2);
+              throw {
+                    RE_EXN_ID: Parse_failure,
+                    _1: exn$2,
+                    Error: new Error()
+                  };
+            }
+            var value$2 = $$String.sub(value, equals + 1 | 0, (length - equals | 0) - 1 | 0);
+            var value$3;
+            try {
+              value$3 = Curry._1(S.Value.of_string, value$2);
+            }
+            catch (raw_exn$3){
+              var exn$3 = Caml_js_exceptions.internalToOCamlException(raw_exn$3);
+              throw {
+                    RE_EXN_ID: Parse_failure,
+                    _1: exn$3,
+                    Error: new Error()
+                  };
+            }
+            return add_user_override(key$1, value$3, acc);
+          }), update.contents, values);
+    update.contents = parsed;
+    
+  };
+  var parse = function (str, help_text, update) {
+    try {
+      parse_exn(str, update);
+      return ;
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === Parse_failure) {
+        return fatal(Curry._2(Printf.sprintf("%s: %s"), Printexc.to_string(exn._1), help_text));
+      }
+      throw exn;
+    }
+  };
+  var parse_no_error = function (str, update) {
+    try {
+      parse_exn(str, update);
+      return /* Ok */0;
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === Parse_failure) {
+        return /* Parse_failed */{
+                _0: exn._1
+              };
+      }
+      throw exn;
+    }
+  };
+  var get = function (key, parsed) {
+    try {
+      return Curry._2(S.Key.$$Map.find, key, parsed.user_override);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        var value = parsed.user_default;
+        if (value !== undefined) {
+          return Caml_option.valFromOption(value);
+        }
+        try {
+          return Curry._2(S.Key.$$Map.find, key, parsed.base_override);
+        }
+        catch (raw_exn$1){
+          var exn$1 = Caml_js_exceptions.internalToOCamlException(raw_exn$1);
+          if (exn$1.RE_EXN_ID === "Not_found") {
+            return parsed.base_default;
+          }
+          throw exn$1;
+        }
+      } else {
+        throw exn;
+      }
+    }
+  };
+  return {
+          $$default: $$default,
+          set_base_default: set_base_default,
+          add_base_override: add_base_override,
+          reset_base_overrides: reset_base_overrides,
+          set_user_default: set_user_default,
+          add_user_override: add_user_override,
+          Parse_failure: Parse_failure,
+          parse_exn: parse_exn,
+          parse: parse,
+          parse_no_error: parse_no_error,
+          get: get
+        };
+}
+
+export {
+  fatal ,
+  Make ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/arg_helper.res b/analysis/examples/larger-project/src/arg_helper.res
new file mode 100644
index 0000000000..898906c6d4
--- /dev/null
+++ b/analysis/examples/larger-project/src/arg_helper.res
@@ -0,0 +1,142 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Pierre Chambart, OCamlPro */
+/* Mark Shinwell and Leo White, Jane Street Europe */
+/*  */
+/* Copyright 2015--2016 OCamlPro SAS */
+/* Copyright 2015--2016 Jane Street Group LLC */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+@raises(exit)
+let fatal = err => {
+  prerr_endline(err)
+  exit(2)
+}
+
+module Make = (
+  S: {
+    module Key: {
+      type t
+      let of_string: string => t
+      module Map: Map.S with type key = t
+    }
+
+    module Value: {
+      type t
+      let of_string: string => t
+    }
+  },
+) => {
+  type parsed = {
+    base_default: S.Value.t,
+    base_override: S.Key.Map.t<S.Value.t>,
+    user_default: option<S.Value.t>,
+    user_override: S.Key.Map.t<S.Value.t>,
+  }
+
+  let default = v => {
+    base_default: v,
+    base_override: S.Key.Map.empty,
+    user_default: None,
+    user_override: S.Key.Map.empty,
+  }
+
+  let set_base_default = (value, t) => {...t, base_default: value}
+
+  let add_base_override = (key, value, t) => {
+    ...t,
+    base_override: S.Key.Map.add(key, value, t.base_override),
+  }
+
+  let reset_base_overrides = t => {...t, base_override: S.Key.Map.empty}
+
+  let set_user_default = (value, t) => {...t, user_default: Some(value)}
+
+  let add_user_override = (key, value, t) => {
+    ...t,
+    user_override: S.Key.Map.add(key, value, t.user_override),
+  }
+
+  exception Parse_failure(exn)
+
+  @raises([Invalid_argument, Parse_failure])
+  let parse_exn = (str, ~update) => {
+    /* Is the removal of empty chunks really relevant here? */
+    /* (It has been added to mimic the old Misc.String.split.) */
+    let values = String.split_on_char(',', str) |> List.filter(\"<>"(""))
+    let parsed = List.fold_left((acc, value) =>
+      switch String.index(value, '=') {
+      | exception Not_found =>
+        switch S.Value.of_string(value) {
+        | value => set_user_default(value, acc)
+        | exception exn => raise(Parse_failure(exn))
+        }
+      | equals =>
+        let key_value_pair = value
+        let length = String.length(key_value_pair)
+        assert (equals >= 0 && equals < length)
+        if equals == 0 {
+          raise(Parse_failure(Failure("Missing key in argument specification")))
+        }
+        let key = {
+          let key = String.sub(key_value_pair, 0, equals)
+          try S.Key.of_string(key) catch {
+          | exn => raise(Parse_failure(exn))
+          }
+        }
+
+        let value = {
+          let value = String.sub(key_value_pair, equals + 1, length - equals - 1)
+
+          try S.Value.of_string(value) catch {
+          | exn => raise(Parse_failure(exn))
+          }
+        }
+
+        add_user_override(key, value, acc)
+      }
+    , update.contents, values)
+
+    update := parsed
+  }
+
+  @raises([Invalid_argument, exit])
+  let parse = (str, help_text, update) =>
+    switch parse_exn(str, ~update) {
+    | () => ()
+    | exception Parse_failure(exn) =>
+      fatal(Printf.sprintf("%s: %s", Printexc.to_string(exn), help_text))
+    }
+
+  type parse_result =
+    | Ok
+    | Parse_failed(exn)
+
+  @raises(Invalid_argument)
+  let parse_no_error = (str, update) =>
+    switch parse_exn(str, ~update) {
+    | () => Ok
+    | exception Parse_failure(exn) => Parse_failed(exn)
+    }
+
+  let get = (~key, parsed) =>
+    switch S.Key.Map.find(key, parsed.user_override) {
+    | value => value
+    | exception Not_found =>
+      switch parsed.user_default {
+      | Some(value) => value
+      | None =>
+        switch S.Key.Map.find(key, parsed.base_override) {
+        | value => value
+        | exception Not_found => parsed.base_default
+        }
+      }
+    }
+}
diff --git a/analysis/examples/larger-project/src/ast_helper.js b/analysis/examples/larger-project/src/ast_helper.js
new file mode 100644
index 0000000000..a74aa6d850
--- /dev/null
+++ b/analysis/examples/larger-project/src/ast_helper.js
@@ -0,0 +1,53 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as $$Location from "./location.js";
+import * as Syntaxerr from "./syntaxerr.js";
+
+throw {
+      RE_EXN_ID: "Assert_failure",
+      _1: [
+        "ast_helper.res",
+        23,
+        21
+      ],
+      Error: new Error()
+    };
+
+export {
+  docstring_body ,
+  docstring_loc ,
+  text_attr ,
+  empty_docs ,
+  add_docs_attrs ,
+  add_text_attrs ,
+  empty_info ,
+  add_info_attrs ,
+  default_loc ,
+  with_default_loc ,
+  Const ,
+  Typ ,
+  Pat ,
+  Exp ,
+  Mty ,
+  Mod ,
+  Sig ,
+  Str ,
+  Cl ,
+  Cty ,
+  Ctf ,
+  Cf ,
+  Val ,
+  Md ,
+  Mtd ,
+  Mb ,
+  Opn ,
+  Incl ,
+  Vb ,
+  Ci ,
+  Type ,
+  Te ,
+  Csig ,
+  Cstr ,
+  
+}
+/* docstring_body Not a pure module */
diff --git a/analysis/examples/larger-project/src/ast_helper.res b/analysis/examples/larger-project/src/ast_helper.res
new file mode 100644
index 0000000000..fa5c7cb594
--- /dev/null
+++ b/analysis/examples/larger-project/src/ast_helper.res
@@ -0,0 +1,628 @@
+@@ocaml.text(
+  /* ************************************************************************ */
+  /*  */
+  /* OCaml */
+  /*  */
+  /* Alain Frisch, LexiFi */
+  /*  */
+  /* Copyright 2012 Institut National de Recherche en Informatique et */
+  /* en Automatique. */
+  /*  */
+  /* All rights reserved.  This file is distributed under the terms of */
+  /* the GNU Lesser General Public License version 2.1, with the */
+  /* special exception on linking described in the file LICENSE. */
+  /*  */
+  /* ************************************************************************ */
+
+  " Helpers to produce Parsetree fragments "
+)
+
+open Asttypes
+open Parsetree
+open Docstrings
+let docstring_body = assert false
+let docstring_loc = assert false
+let text_attr = assert false
+let empty_docs = assert false
+let add_docs_attrs = assert false
+let add_text_attrs = assert false
+let empty_info = assert false
+let add_info_attrs = assert false
+
+type lid = loc<Longident.t>
+type str = loc<string>
+type loc = Location.t
+type attrs = list<attribute>
+
+let default_loc = ref(Location.none)
+
+@raises(genericException)
+let with_default_loc = (l, f) => {
+  let old = default_loc.contents
+  default_loc := l
+  try {
+    let r = f()
+    default_loc := old
+    r
+  } catch {
+  | exn =>
+    default_loc := old
+    raise(exn)
+  }
+}
+
+module Const = {
+  let integer = (~suffix=?, i) => Pconst_integer(i, suffix)
+  let int = (~suffix=?, i) => integer(~suffix?, string_of_int(i))
+  let int32 = (~suffix='l', i) => integer(~suffix, Int32.to_string(i))
+  let int64 = (~suffix='L', i) => integer(~suffix, Int64.to_string(i))
+  let nativeint = (~suffix='n', i) => integer(~suffix, Nativeint.to_string(i))
+  let float = (~suffix=?, f) => Pconst_float(f, suffix)
+  let char = c => Pconst_char(c)
+  let string = (~quotation_delimiter=?, s) => Pconst_string(s, quotation_delimiter)
+}
+
+module Typ = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    ptyp_desc: d,
+    ptyp_loc: loc,
+    ptyp_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, ptyp_attributes: \"@"(d.ptyp_attributes, list{a})}
+
+  let any = (~loc=?, ~attrs=?, ()) => mk(~loc?, ~attrs?, Ptyp_any)
+  let var = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ptyp_var(a))
+  let arrow = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Ptyp_arrow(a, b, c))
+  let tuple = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ptyp_tuple(a))
+  let constr = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_constr(a, b))
+  let object_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_object(a, b))
+  let class_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_class(a, b))
+  let alias = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_alias(a, b))
+  let variant = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Ptyp_variant(a, b, c))
+  let poly = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_poly(a, b))
+  let package = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ptyp_package(a, b))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ptyp_extension(a))
+
+  let force_poly = t =>
+    switch t.ptyp_desc {
+    | Ptyp_poly(_) => t
+    | _ => poly(~loc=t.ptyp_loc, list{}, t)
+    } /* -> ghost? */
+
+  @raises(Error)
+  let varify_constructors = (var_names, t) => {
+    @raises(Error)
+    let check_variable = (vl, loc, v) =>
+      if List.mem(v, vl) {
+        raise({
+          open Syntaxerr
+          Error(Variable_in_scope(loc, v))
+        })
+      }
+    let var_names = List.map(v => v.txt, var_names)
+
+    @raises(Error)
+    let rec loop = t => {
+      let desc = switch t.ptyp_desc {
+      | Ptyp_any => Ptyp_any
+      | Ptyp_var(x) =>
+        check_variable(var_names, t.ptyp_loc, x)
+        Ptyp_var(x)
+      | Ptyp_arrow(label, core_type, core_type') =>
+        Ptyp_arrow(label, loop(core_type), loop(core_type'))
+      | Ptyp_tuple(lst) => Ptyp_tuple(List.map(loop, lst))
+      | Ptyp_constr({txt: Longident.Lident(s)}, list{}) if List.mem(s, var_names) => Ptyp_var(s)
+      | Ptyp_constr(longident, lst) => Ptyp_constr(longident, List.map(loop, lst))
+      | Ptyp_object(lst, o) => Ptyp_object(List.map(loop_object_field, lst), o)
+      | Ptyp_class(longident, lst) => Ptyp_class(longident, List.map(loop, lst))
+      | Ptyp_alias(core_type, string) =>
+        check_variable(var_names, t.ptyp_loc, string)
+        Ptyp_alias(loop(core_type), string)
+      | Ptyp_variant(row_field_list, flag, lbl_lst_option) =>
+        Ptyp_variant(List.map(loop_row_field, row_field_list), flag, lbl_lst_option)
+      | Ptyp_poly(string_lst, core_type) =>
+        List.iter(v => check_variable(var_names, t.ptyp_loc, v.txt), string_lst)
+        Ptyp_poly(string_lst, loop(core_type))
+      | Ptyp_package(longident, lst) =>
+        Ptyp_package(longident, List.map(((n, typ)) => (n, loop(typ)), lst))
+      | Ptyp_extension(s, arg) => Ptyp_extension(s, arg)
+      }
+
+      {...t, ptyp_desc: desc}
+    }
+    @raises(Error)
+    and loop_row_field = x =>
+      switch x {
+      | Rtag(label, attrs, flag, lst) => Rtag(label, attrs, flag, List.map(loop, lst))
+      | Rinherit(t) => Rinherit(loop(t))
+      }
+    @raises(Error)
+    and loop_object_field = x =>
+      switch x {
+      | Otag(label, attrs, t) => Otag(label, attrs, loop(t))
+      | Oinherit(t) => Oinherit(loop(t))
+      }
+
+    loop(t)
+  }
+}
+
+module Pat = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    ppat_desc: d,
+    ppat_loc: loc,
+    ppat_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, ppat_attributes: \"@"(d.ppat_attributes, list{a})}
+
+  let any = (~loc=?, ~attrs=?, ()) => mk(~loc?, ~attrs?, Ppat_any)
+  let var = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_var(a))
+  let alias = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_alias(a, b))
+  let constant = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_constant(a))
+  let interval = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_interval(a, b))
+  let tuple = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_tuple(a))
+  let construct = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_construct(a, b))
+  let variant = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_variant(a, b))
+  let record = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_record(a, b))
+  let array = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_array(a))
+  let or_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_or(a, b))
+  let constraint_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_constraint(a, b))
+  let type_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_type(a))
+  let lazy_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_lazy(a))
+  let unpack = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_unpack(a))
+  let open_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Ppat_open(a, b))
+  let exception_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_exception(a))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Ppat_extension(a))
+}
+
+module Exp = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    pexp_desc: d,
+    pexp_loc: loc,
+    pexp_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, pexp_attributes: \"@"(d.pexp_attributes, list{a})}
+
+  let ident = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_ident(a))
+  let constant = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_constant(a))
+  let let_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_let(a, b, c))
+  let fun_ = (~loc=?, ~attrs=?, a, b, c, d) => mk(~loc?, ~attrs?, Pexp_fun(a, b, c, d))
+  let function_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_function(a))
+  let apply = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_apply(a, b))
+  let match_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_match(a, b))
+  let try_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_try(a, b))
+  let tuple = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_tuple(a))
+  let construct = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_construct(a, b))
+  let variant = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_variant(a, b))
+  let record = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_record(a, b))
+  let field = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_field(a, b))
+  let setfield = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_setfield(a, b, c))
+  let array = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_array(a))
+  let ifthenelse = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_ifthenelse(a, b, c))
+  let sequence = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_sequence(a, b))
+  let while_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_while(a, b))
+  let for_ = (~loc=?, ~attrs=?, a, b, c, d, e) => mk(~loc?, ~attrs?, Pexp_for(a, b, c, d, e))
+  let constraint_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_constraint(a, b))
+  let coerce = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_coerce(a, b, c))
+  let send = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_send(a, b))
+  let new_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_new(a))
+  let setinstvar = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_setinstvar(a, b))
+  let override = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_override(a))
+  let letmodule = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_letmodule(a, b, c))
+  let letexception = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_letexception(a, b))
+  let assert_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_assert(a))
+  let lazy_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_lazy(a))
+  let poly = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_poly(a, b))
+  let object_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_object(a))
+  let newtype = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pexp_newtype(a, b))
+  let pack = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_pack(a))
+  let open_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pexp_open(a, b, c))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pexp_extension(a))
+  let unreachable = (~loc=?, ~attrs=?, ()) => mk(~loc?, ~attrs?, Pexp_unreachable)
+
+  let case = (lhs, ~guard=?, rhs) => {
+    pc_lhs: lhs,
+    pc_guard: guard,
+    pc_rhs: rhs,
+  }
+}
+
+module Mty = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    pmty_desc: d,
+    pmty_loc: loc,
+    pmty_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, pmty_attributes: \"@"(d.pmty_attributes, list{a})}
+
+  let ident = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmty_ident(a))
+  let alias = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmty_alias(a))
+  let signature = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmty_signature(a))
+  let functor_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pmty_functor(a, b, c))
+  let with_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pmty_with(a, b))
+  let typeof_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmty_typeof(a))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmty_extension(a))
+}
+
+module Mod = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    pmod_desc: d,
+    pmod_loc: loc,
+    pmod_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, pmod_attributes: \"@"(d.pmod_attributes, list{a})}
+
+  let ident = (~loc=?, ~attrs=?, x) => mk(~loc?, ~attrs?, Pmod_ident(x))
+  let structure = (~loc=?, ~attrs=?, x) => mk(~loc?, ~attrs?, Pmod_structure(x))
+  let functor_ = (~loc=?, ~attrs=?, arg, arg_ty, body) =>
+    mk(~loc?, ~attrs?, Pmod_functor(arg, arg_ty, body))
+  let apply = (~loc=?, ~attrs=?, m1, m2) => mk(~loc?, ~attrs?, Pmod_apply(m1, m2))
+  let constraint_ = (~loc=?, ~attrs=?, m, mty) => mk(~loc?, ~attrs?, Pmod_constraint(m, mty))
+  let unpack = (~loc=?, ~attrs=?, e) => mk(~loc?, ~attrs?, Pmod_unpack(e))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pmod_extension(a))
+}
+
+module Sig = {
+  let mk = (~loc=default_loc.contents, d) => {psig_desc: d, psig_loc: loc}
+
+  let value = (~loc=?, a) => mk(~loc?, Psig_value(a))
+  let type_ = (~loc=?, rec_flag, a) => mk(~loc?, Psig_type(rec_flag, a))
+  let type_extension = (~loc=?, a) => mk(~loc?, Psig_typext(a))
+  let exception_ = (~loc=?, a) => mk(~loc?, Psig_exception(a))
+  let module_ = (~loc=?, a) => mk(~loc?, Psig_module(a))
+  let rec_module = (~loc=?, a) => mk(~loc?, Psig_recmodule(a))
+  let modtype = (~loc=?, a) => mk(~loc?, Psig_modtype(a))
+  let open_ = (~loc=?, a) => mk(~loc?, Psig_open(a))
+  let include_ = (~loc=?, a) => mk(~loc?, Psig_include(a))
+  let class_ = (~loc=?, a) => mk(~loc?, Psig_class(a))
+  let class_type = (~loc=?, a) => mk(~loc?, Psig_class_type(a))
+  let extension = (~loc=?, ~attrs=list{}, a) => mk(~loc?, Psig_extension(a, attrs))
+  let attribute = (~loc=?, a) => mk(~loc?, Psig_attribute(a))
+  let text = txt => {
+    let f_txt = List.filter(ds => docstring_body(ds) != "", txt)
+    List.map(ds => attribute(~loc=docstring_loc(ds), text_attr(ds)), f_txt)
+  }
+}
+
+module Str = {
+  let mk = (~loc=default_loc.contents, d) => {pstr_desc: d, pstr_loc: loc}
+
+  let eval = (~loc=?, ~attrs=list{}, a) => mk(~loc?, Pstr_eval(a, attrs))
+  let value = (~loc=?, a, b) => mk(~loc?, Pstr_value(a, b))
+  let primitive = (~loc=?, a) => mk(~loc?, Pstr_primitive(a))
+  let type_ = (~loc=?, rec_flag, a) => mk(~loc?, Pstr_type(rec_flag, a))
+  let type_extension = (~loc=?, a) => mk(~loc?, Pstr_typext(a))
+  let exception_ = (~loc=?, a) => mk(~loc?, Pstr_exception(a))
+  let module_ = (~loc=?, a) => mk(~loc?, Pstr_module(a))
+  let rec_module = (~loc=?, a) => mk(~loc?, Pstr_recmodule(a))
+  let modtype = (~loc=?, a) => mk(~loc?, Pstr_modtype(a))
+  let open_ = (~loc=?, a) => mk(~loc?, Pstr_open(a))
+  let class_ = (~loc=?, a) => mk(~loc?, Pstr_class(a))
+  let class_type = (~loc=?, a) => mk(~loc?, Pstr_class_type(a))
+  let include_ = (~loc=?, a) => mk(~loc?, Pstr_include(a))
+  let extension = (~loc=?, ~attrs=list{}, a) => mk(~loc?, Pstr_extension(a, attrs))
+  let attribute = (~loc=?, a) => mk(~loc?, Pstr_attribute(a))
+  let text = txt => {
+    let f_txt = List.filter(ds => docstring_body(ds) != "", txt)
+    List.map(ds => attribute(~loc=docstring_loc(ds), text_attr(ds)), f_txt)
+  }
+}
+
+module Cl = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    pcl_desc: d,
+    pcl_loc: loc,
+    pcl_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, pcl_attributes: \"@"(d.pcl_attributes, list{a})}
+
+  let constr = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pcl_constr(a, b))
+  let structure = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcl_structure(a))
+  let fun_ = (~loc=?, ~attrs=?, a, b, c, d) => mk(~loc?, ~attrs?, Pcl_fun(a, b, c, d))
+  let apply = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pcl_apply(a, b))
+  let let_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcl_let(a, b, c))
+  let constraint_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pcl_constraint(a, b))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcl_extension(a))
+  let open_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcl_open(a, b, c))
+}
+
+module Cty = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, d) => {
+    pcty_desc: d,
+    pcty_loc: loc,
+    pcty_attributes: attrs,
+  }
+  let attr = (d, a) => {...d, pcty_attributes: \"@"(d.pcty_attributes, list{a})}
+
+  let constr = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pcty_constr(a, b))
+  let signature = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcty_signature(a))
+  let arrow = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcty_arrow(a, b, c))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcty_extension(a))
+  let open_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcty_open(a, b, c))
+}
+
+module Ctf = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, ~docs=empty_docs, d) => {
+    pctf_desc: d,
+    pctf_loc: loc,
+    pctf_attributes: add_docs_attrs(docs, attrs),
+  }
+
+  let inherit_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pctf_inherit(a))
+  let val_ = (~loc=?, ~attrs=?, a, b, c, d) => mk(~loc?, ~attrs?, Pctf_val(a, b, c, d))
+  let method_ = (~loc=?, ~attrs=?, a, b, c, d) => mk(~loc?, ~attrs?, Pctf_method(a, b, c, d))
+  let constraint_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pctf_constraint(a, b))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pctf_extension(a))
+  let attribute = (~loc=?, a) => mk(~loc?, Pctf_attribute(a))
+  let text = txt => {
+    let f_txt = List.filter(ds => docstring_body(ds) != "", txt)
+    List.map(ds => attribute(~loc=docstring_loc(ds), text_attr(ds)), f_txt)
+  }
+
+  let attr = (d, a) => {...d, pctf_attributes: \"@"(d.pctf_attributes, list{a})}
+}
+
+module Cf = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, ~docs=empty_docs, d) => {
+    pcf_desc: d,
+    pcf_loc: loc,
+    pcf_attributes: add_docs_attrs(docs, attrs),
+  }
+
+  let inherit_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcf_inherit(a, b, c))
+  let val_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcf_val(a, b, c))
+  let method_ = (~loc=?, ~attrs=?, a, b, c) => mk(~loc?, ~attrs?, Pcf_method(a, b, c))
+  let constraint_ = (~loc=?, ~attrs=?, a, b) => mk(~loc?, ~attrs?, Pcf_constraint(a, b))
+  let initializer_ = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcf_initializer(a))
+  let extension = (~loc=?, ~attrs=?, a) => mk(~loc?, ~attrs?, Pcf_extension(a))
+  let attribute = (~loc=?, a) => mk(~loc?, Pcf_attribute(a))
+  let text = txt => {
+    let f_txt = List.filter(ds => docstring_body(ds) != "", txt)
+    List.map(ds => attribute(~loc=docstring_loc(ds), text_attr(ds)), f_txt)
+  }
+
+  let virtual_ = ct => Cfk_virtual(ct)
+  let concrete = (o, e) => Cfk_concrete(o, e)
+
+  let attr = (d, a) => {...d, pcf_attributes: \"@"(d.pcf_attributes, list{a})}
+}
+
+module Val = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~prim=list{},
+    name,
+    typ,
+  ) => {
+    pval_name: name,
+    pval_type: typ,
+    pval_attributes: add_docs_attrs(docs, attrs),
+    pval_loc: loc,
+    pval_prim: prim,
+  }
+}
+
+module Md = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    name,
+    typ,
+  ) => {
+    pmd_name: name,
+    pmd_type: typ,
+    pmd_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    pmd_loc: loc,
+  }
+}
+
+module Mtd = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    ~typ=?,
+    name,
+  ) => {
+    pmtd_name: name,
+    pmtd_type: typ,
+    pmtd_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    pmtd_loc: loc,
+  }
+}
+
+module Mb = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    name,
+    expr,
+  ) => {
+    pmb_name: name,
+    pmb_expr: expr,
+    pmb_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    pmb_loc: loc,
+  }
+}
+
+module Opn = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, ~docs=empty_docs, ~override=Fresh, lid) => {
+    popen_lid: lid,
+    popen_override: override,
+    popen_loc: loc,
+    popen_attributes: add_docs_attrs(docs, attrs),
+  }
+}
+
+module Incl = {
+  let mk = (~loc=default_loc.contents, ~attrs=list{}, ~docs=empty_docs, mexpr) => {
+    pincl_mod: mexpr,
+    pincl_loc: loc,
+    pincl_attributes: add_docs_attrs(docs, attrs),
+  }
+}
+
+module Vb = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    pat,
+    expr,
+  ) => {
+    pvb_pat: pat,
+    pvb_expr: expr,
+    pvb_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    pvb_loc: loc,
+  }
+}
+
+module Ci = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    ~virt=Concrete,
+    ~params=list{},
+    name,
+    expr,
+  ) => {
+    pci_virt: virt,
+    pci_params: params,
+    pci_name: name,
+    pci_expr: expr,
+    pci_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    pci_loc: loc,
+  }
+}
+
+module Type = {
+  let mk = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~text=list{},
+    ~params=list{},
+    ~cstrs=list{},
+    ~kind=Ptype_abstract,
+    ~priv=Public,
+    ~manifest=?,
+    name,
+  ) => {
+    ptype_name: name,
+    ptype_params: params,
+    ptype_cstrs: cstrs,
+    ptype_kind: kind,
+    ptype_private: priv,
+    ptype_manifest: manifest,
+    ptype_attributes: add_text_attrs(text, add_docs_attrs(docs, attrs)),
+    ptype_loc: loc,
+  }
+
+  let constructor = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~info=empty_info,
+    ~args=Pcstr_tuple(list{}),
+    ~res=?,
+    name,
+  ) => {
+    pcd_name: name,
+    pcd_args: args,
+    pcd_res: res,
+    pcd_loc: loc,
+    pcd_attributes: add_info_attrs(info, attrs),
+  }
+
+  let field = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~info=empty_info,
+    ~mut=Immutable,
+    name,
+    typ,
+  ) => {
+    pld_name: name,
+    pld_mutable: mut,
+    pld_type: typ,
+    pld_loc: loc,
+    pld_attributes: add_info_attrs(info, attrs),
+  }
+}
+
+@ocaml.doc(" Type extensions ")
+module Te = {
+  let mk = (~attrs=list{}, ~docs=empty_docs, ~params=list{}, ~priv=Public, path, constructors) => {
+    ptyext_path: path,
+    ptyext_params: params,
+    ptyext_constructors: constructors,
+    ptyext_private: priv,
+    ptyext_attributes: add_docs_attrs(docs, attrs),
+  }
+
+  let constructor = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~info=empty_info,
+    name,
+    kind,
+  ) => {
+    pext_name: name,
+    pext_kind: kind,
+    pext_loc: loc,
+    pext_attributes: add_docs_attrs(docs, add_info_attrs(info, attrs)),
+  }
+
+  let decl = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~info=empty_info,
+    ~args=Pcstr_tuple(list{}),
+    ~res=?,
+    name,
+  ) => {
+    pext_name: name,
+    pext_kind: Pext_decl(args, res),
+    pext_loc: loc,
+    pext_attributes: add_docs_attrs(docs, add_info_attrs(info, attrs)),
+  }
+
+  let rebind = (
+    ~loc=default_loc.contents,
+    ~attrs=list{},
+    ~docs=empty_docs,
+    ~info=empty_info,
+    name,
+    lid,
+  ) => {
+    pext_name: name,
+    pext_kind: Pext_rebind(lid),
+    pext_loc: loc,
+    pext_attributes: add_docs_attrs(docs, add_info_attrs(info, attrs)),
+  }
+}
+
+module Csig = {
+  let mk = (self, fields) => {
+    pcsig_self: self,
+    pcsig_fields: fields,
+  }
+}
+
+module Cstr = {
+  let mk = (self, fields) => {
+    pcstr_self: self,
+    pcstr_fields: fields,
+  }
+}
diff --git a/analysis/examples/larger-project/src/asttypes.js b/analysis/examples/larger-project/src/asttypes.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/asttypes.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/asttypes.res b/analysis/examples/larger-project/src/asttypes.res
new file mode 100644
index 0000000000..b21e2b722b
--- /dev/null
+++ b/analysis/examples/larger-project/src/asttypes.res
@@ -0,0 +1,62 @@
+open P
+
+@@ocaml.text(
+  /* ************************************************************************ */
+  /*  */
+  /* OCaml */
+  /*  */
+  /* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+  /*  */
+  /* Copyright 1996 Institut National de Recherche en Informatique et */
+  /* en Automatique. */
+  /*  */
+  /* All rights reserved.  This file is distributed under the terms of */
+  /* the GNU Lesser General Public License version 2.1, with the */
+  /* special exception on linking described in the file LICENSE. */
+  /*  */
+  /* ************************************************************************ */
+
+  " Auxiliary AST types used by parsetree and typedtree. "
+)
+
+type constant =
+  | Const_int(int)
+  | Const_char(char)
+  | Const_string(string, option<string>)
+  | Const_float(string)
+  | Const_int32(int32)
+  | Const_int64(int64)
+  | Const_nativeint(nativeint)
+
+type rec_flag = Nonrecursive | Recursive
+
+type direction_flag = Upto | Downto
+
+/* Order matters, used in polymorphic comparison */
+type private_flag = Private | Public
+
+type mutable_flag = Immutable | Mutable
+
+type virtual_flag = Virtual | Concrete
+
+type override_flag = Override | Fresh
+
+type closed_flag = Closed | Open
+
+type label = string
+
+type arg_label =
+  | Nolabel
+  | Labelled(string) /* label:T -> ... */
+  | Optional(string) /* ?label:T -> ... */
+
+type loc<'a> = Location.loc<'a> = {
+  txt: 'a,
+  loc: Location.t,
+}
+
+type variance =
+  | Covariant
+  | Contravariant
+  | Invariant
+
diff --git a/analysis/examples/larger-project/src/clflags.js b/analysis/examples/larger-project/src/clflags.js
new file mode 100644
index 0000000000..9b31202039
--- /dev/null
+++ b/analysis/examples/larger-project/src/clflags.js
@@ -0,0 +1,873 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Arg from "rescript/lib/es6/arg.js";
+import * as Sys from "rescript/lib/es6/sys.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as Misc from "./misc.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Config from "./config.js";
+import * as Printf from "./printf.js";
+import * as Filename from "rescript/lib/es6/filename.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+var Int_arg_helper = {};
+
+var Float_arg_helper = {};
+
+var objfiles = {
+  contents: /* [] */0
+};
+
+var ccobjs = {
+  contents: /* [] */0
+};
+
+var dllibs = {
+  contents: /* [] */0
+};
+
+var compile_only = {
+  contents: false
+};
+
+var output_name = {
+  contents: undefined
+};
+
+var include_dirs = {
+  contents: /* [] */0
+};
+
+var no_std_include = {
+  contents: false
+};
+
+var print_types = {
+  contents: false
+};
+
+var make_archive = {
+  contents: false
+};
+
+var debug = {
+  contents: false
+};
+
+var fast = {
+  contents: false
+};
+
+var use_linscan = {
+  contents: false
+};
+
+var link_everything = {
+  contents: false
+};
+
+var custom_runtime = {
+  contents: false
+};
+
+var no_check_prims = {
+  contents: false
+};
+
+var bytecode_compatible_32 = {
+  contents: false
+};
+
+var output_c_object = {
+  contents: false
+};
+
+var output_complete_object = {
+  contents: false
+};
+
+var all_ccopts = {
+  contents: /* [] */0
+};
+
+var classic = {
+  contents: false
+};
+
+var nopervasives = {
+  contents: false
+};
+
+var preprocessor = {
+  contents: undefined
+};
+
+var all_ppx = {
+  contents: /* [] */0
+};
+
+var annotations = {
+  contents: false
+};
+
+var binary_annotations = {
+  contents: false
+};
+
+var use_threads = {
+  contents: false
+};
+
+var use_vmthreads = {
+  contents: false
+};
+
+var noassert = {
+  contents: false
+};
+
+var verbose = {
+  contents: false
+};
+
+var noversion = {
+  contents: false
+};
+
+var noprompt = {
+  contents: false
+};
+
+var nopromptcont = {
+  contents: false
+};
+
+var init_file = {
+  contents: undefined
+};
+
+var noinit = {
+  contents: false
+};
+
+var open_modules = {
+  contents: /* [] */0
+};
+
+var use_prims = {
+  contents: ""
+};
+
+var use_runtime = {
+  contents: ""
+};
+
+var principal = {
+  contents: false
+};
+
+var real_paths = {
+  contents: true
+};
+
+var recursive_types = {
+  contents: false
+};
+
+var strict_sequence = {
+  contents: false
+};
+
+var strict_formats = {
+  contents: false
+};
+
+var applicative_functors = {
+  contents: true
+};
+
+var make_runtime = {
+  contents: false
+};
+
+var gprofile = {
+  contents: false
+};
+
+var c_compiler = {
+  contents: undefined
+};
+
+var no_auto_link = {
+  contents: false
+};
+
+var dllpaths = {
+  contents: /* [] */0
+};
+
+var make_package = {
+  contents: false
+};
+
+var for_package = {
+  contents: undefined
+};
+
+var error_size = {
+  contents: 500
+};
+
+var float_const_prop = {
+  contents: true
+};
+
+var transparent_modules = {
+  contents: false
+};
+
+var dump_source = {
+  contents: false
+};
+
+var dump_parsetree = {
+  contents: false
+};
+
+var dump_typedtree = {
+  contents: false
+};
+
+var dump_rawlambda = {
+  contents: false
+};
+
+var dump_lambda = {
+  contents: false
+};
+
+var dump_rawclambda = {
+  contents: false
+};
+
+var dump_clambda = {
+  contents: false
+};
+
+var dump_rawflambda = {
+  contents: false
+};
+
+var dump_flambda = {
+  contents: false
+};
+
+var dump_flambda_let = {
+  contents: undefined
+};
+
+var dump_flambda_verbose = {
+  contents: false
+};
+
+var dump_instr = {
+  contents: false
+};
+
+var keep_asm_file = {
+  contents: false
+};
+
+var optimize_for_speed = {
+  contents: true
+};
+
+var opaque = {
+  contents: false
+};
+
+var dump_cmm = {
+  contents: false
+};
+
+var dump_selection = {
+  contents: false
+};
+
+var dump_cse = {
+  contents: false
+};
+
+var dump_live = {
+  contents: false
+};
+
+var dump_avail = {
+  contents: false
+};
+
+var dump_spill = {
+  contents: false
+};
+
+var dump_split = {
+  contents: false
+};
+
+var dump_interf = {
+  contents: false
+};
+
+var dump_prefer = {
+  contents: false
+};
+
+var dump_regalloc = {
+  contents: false
+};
+
+var dump_reload = {
+  contents: false
+};
+
+var dump_scheduling = {
+  contents: false
+};
+
+var dump_linear = {
+  contents: false
+};
+
+var dump_interval = {
+  contents: false
+};
+
+var keep_startup_file = {
+  contents: false
+};
+
+var dump_combine = {
+  contents: false
+};
+
+var debug_runavail = {
+  contents: false
+};
+
+var native_code = {
+  contents: false
+};
+
+var force_slash = {
+  contents: false
+};
+
+var clambda_checks = {
+  contents: false
+};
+
+var flambda_invariant_checks = {
+  contents: true
+};
+
+var dont_write_files = {
+  contents: false
+};
+
+function std_include_flag(prefix) {
+  if (no_std_include.contents) {
+    return "";
+  } else {
+    return prefix + Curry._1(Filename.quote, Config.standard_library);
+  }
+}
+
+function std_include_dir(param) {
+  if (no_std_include.contents) {
+    return /* [] */0;
+  } else {
+    return {
+            hd: Config.standard_library,
+            tl: /* [] */0
+          };
+  }
+}
+
+var shared = {
+  contents: false
+};
+
+var dlcode = {
+  contents: true
+};
+
+var tmp = Config.architecture === "amd64" ? true : false;
+
+var pic_code = {
+  contents: tmp
+};
+
+var runtime_variant = {
+  contents: ""
+};
+
+var keep_docs = {
+  contents: false
+};
+
+var keep_locs = {
+  contents: true
+};
+
+var unsafe_string = {
+  contents: false
+};
+
+var classic_inlining = {
+  contents: false
+};
+
+var inlining_report = {
+  contents: false
+};
+
+var afl_instrument = {
+  contents: false
+};
+
+var afl_inst_ratio = {
+  contents: 100
+};
+
+var simplify_rounds = {
+  contents: undefined
+};
+
+var default_simplify_rounds = {
+  contents: 1
+};
+
+function rounds(param) {
+  var r = simplify_rounds.contents;
+  if (r !== undefined) {
+    return r;
+  } else {
+    return default_simplify_rounds.contents;
+  }
+}
+
+var default_inline_threshold = 10 / 8;
+
+var default_inline_toplevel_threshold = 16 * default_inline_threshold | 0;
+
+var unbox_specialised_args = {
+  contents: true
+};
+
+var unbox_free_vars_of_closures = {
+  contents: true
+};
+
+var unbox_closures = {
+  contents: false
+};
+
+var unbox_closures_factor = {
+  contents: 10
+};
+
+var remove_unused_arguments = {
+  contents: false
+};
+
+var classic_arguments_inline_threshold = 10 / 8;
+
+var classic_arguments_inline_toplevel_threshold = 1;
+
+var classic_arguments = {
+  inline_call_cost: undefined,
+  inline_alloc_cost: undefined,
+  inline_prim_cost: undefined,
+  inline_branch_cost: undefined,
+  inline_indirect_cost: undefined,
+  inline_lifting_benefit: undefined,
+  inline_branch_factor: undefined,
+  inline_max_depth: undefined,
+  inline_max_unroll: undefined,
+  inline_threshold: classic_arguments_inline_threshold,
+  inline_toplevel_threshold: classic_arguments_inline_toplevel_threshold
+};
+
+var o2_arguments_inline_call_cost = 10;
+
+var o2_arguments_inline_alloc_cost = 14;
+
+var o2_arguments_inline_prim_cost = 6;
+
+var o2_arguments_inline_branch_cost = 10;
+
+var o2_arguments_inline_indirect_cost = 8;
+
+var o2_arguments_inline_max_depth = 2;
+
+var o2_arguments_inline_threshold = 25;
+
+var o2_arguments_inline_toplevel_threshold = 400;
+
+var o2_arguments = {
+  inline_call_cost: o2_arguments_inline_call_cost,
+  inline_alloc_cost: o2_arguments_inline_alloc_cost,
+  inline_prim_cost: o2_arguments_inline_prim_cost,
+  inline_branch_cost: o2_arguments_inline_branch_cost,
+  inline_indirect_cost: o2_arguments_inline_indirect_cost,
+  inline_lifting_benefit: undefined,
+  inline_branch_factor: undefined,
+  inline_max_depth: o2_arguments_inline_max_depth,
+  inline_max_unroll: undefined,
+  inline_threshold: o2_arguments_inline_threshold,
+  inline_toplevel_threshold: o2_arguments_inline_toplevel_threshold
+};
+
+var o3_arguments_inline_call_cost = 15;
+
+var o3_arguments_inline_alloc_cost = 21;
+
+var o3_arguments_inline_prim_cost = 9;
+
+var o3_arguments_inline_branch_cost = 15;
+
+var o3_arguments_inline_indirect_cost = 12;
+
+var o3_arguments_inline_branch_factor = 0;
+
+var o3_arguments_inline_max_depth = 3;
+
+var o3_arguments_inline_max_unroll = 1;
+
+var o3_arguments_inline_threshold = 50;
+
+var o3_arguments_inline_toplevel_threshold = 800;
+
+var o3_arguments = {
+  inline_call_cost: o3_arguments_inline_call_cost,
+  inline_alloc_cost: o3_arguments_inline_alloc_cost,
+  inline_prim_cost: o3_arguments_inline_prim_cost,
+  inline_branch_cost: o3_arguments_inline_branch_cost,
+  inline_indirect_cost: o3_arguments_inline_indirect_cost,
+  inline_lifting_benefit: undefined,
+  inline_branch_factor: o3_arguments_inline_branch_factor,
+  inline_max_depth: o3_arguments_inline_max_depth,
+  inline_max_unroll: o3_arguments_inline_max_unroll,
+  inline_threshold: o3_arguments_inline_threshold,
+  inline_toplevel_threshold: o3_arguments_inline_toplevel_threshold
+};
+
+var all_passes = {
+  contents: /* [] */0
+};
+
+var dumped_passes_list = {
+  contents: /* [] */0
+};
+
+function dumped_pass(s) {
+  if (!List.mem(s, all_passes.contents)) {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "clflags.res",
+            276,
+            2
+          ],
+          Error: new Error()
+        };
+  }
+  return List.mem(s, dumped_passes_list.contents);
+}
+
+function set_dumped_pass(s, enabled) {
+  if (!List.mem(s, all_passes.contents)) {
+    return ;
+  }
+  var passes_without_s = List.filter(function (param) {
+          return s !== param;
+        })(dumped_passes_list.contents);
+  var dumped_passes = enabled ? ({
+        hd: s,
+        tl: passes_without_s
+      }) : passes_without_s;
+  dumped_passes_list.contents = dumped_passes;
+  
+}
+
+function parse_color_setting(x) {
+  switch (x) {
+    case "always" :
+        return /* Always */1;
+    case "auto" :
+        return /* Auto */0;
+    case "never" :
+        return /* Never */2;
+    default:
+      return ;
+  }
+}
+
+var color = {
+  contents: undefined
+};
+
+var unboxed_types = {
+  contents: false
+};
+
+var arg_spec = {
+  contents: /* [] */0
+};
+
+var arg_names = {
+  contents: Misc.StringMap.empty
+};
+
+function reset_arguments(param) {
+  arg_spec.contents = /* [] */0;
+  arg_names.contents = Misc.StringMap.empty;
+  
+}
+
+function add_arguments(loc, args) {
+  return List.iter((function (x) {
+                var arg_name = x[0];
+                try {
+                  var loc2 = Curry._2(Misc.StringMap.find, arg_name, arg_names.contents);
+                  Curry._1(Printf.eprintf("Warning: plugin argument %s is already defined:\n"), arg_name);
+                  Curry._1(Printf.eprintf("   First definition: %s\n"), loc2);
+                  return Curry._1(Printf.eprintf("   New definition: %s\n"), loc);
+                }
+                catch (raw_exn){
+                  var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+                  if (exn.RE_EXN_ID === "Not_found") {
+                    arg_spec.contents = Pervasives.$at(arg_spec.contents, {
+                          hd: x,
+                          tl: /* [] */0
+                        });
+                    arg_names.contents = Curry._3(Misc.StringMap.add, arg_name, loc, arg_names.contents);
+                    return ;
+                  }
+                  throw exn;
+                }
+              }), args);
+}
+
+function print_arguments(usage) {
+  return Arg.usage(arg_spec.contents, usage);
+}
+
+function parse_arguments(f, msg) {
+  try {
+    var argv = {
+      contents: Sys.argv
+    };
+    var current = {
+      contents: Arg.current.contents
+    };
+    return Arg.parse_and_expand_argv_dynamic(current, argv, arg_spec, f, msg);
+  }
+  catch (raw_msg){
+    var msg$1 = Caml_js_exceptions.internalToOCamlException(raw_msg);
+    if (msg$1.RE_EXN_ID === Arg.Bad) {
+      Curry._1(Printf.eprintf("%s"), msg$1._1);
+      return Pervasives.exit(2);
+    }
+    if (msg$1.RE_EXN_ID === Arg.Help) {
+      Curry._1(Printf.printf("%s"), msg$1._1);
+      return Pervasives.exit(0);
+    }
+    throw msg$1;
+  }
+}
+
+var inline_toplevel_multiplier = 16;
+
+var default_inline_call_cost = 5;
+
+var default_inline_alloc_cost = 7;
+
+var default_inline_prim_cost = 3;
+
+var default_inline_branch_cost = 5;
+
+var default_inline_indirect_cost = 4;
+
+var default_inline_branch_factor = 0.1;
+
+var default_inline_lifting_benefit = 1300;
+
+var default_inline_max_unroll = 0;
+
+var default_inline_max_depth = 1;
+
+var default_unbox_closures_factor = 10;
+
+var o1_arguments = {
+  inline_call_cost: undefined,
+  inline_alloc_cost: undefined,
+  inline_prim_cost: undefined,
+  inline_branch_cost: undefined,
+  inline_indirect_cost: undefined,
+  inline_lifting_benefit: undefined,
+  inline_branch_factor: undefined,
+  inline_max_depth: undefined,
+  inline_max_unroll: undefined,
+  inline_threshold: undefined,
+  inline_toplevel_threshold: undefined
+};
+
+export {
+  Int_arg_helper ,
+  Float_arg_helper ,
+  objfiles ,
+  ccobjs ,
+  dllibs ,
+  compile_only ,
+  output_name ,
+  include_dirs ,
+  no_std_include ,
+  print_types ,
+  make_archive ,
+  debug ,
+  fast ,
+  use_linscan ,
+  link_everything ,
+  custom_runtime ,
+  no_check_prims ,
+  bytecode_compatible_32 ,
+  output_c_object ,
+  output_complete_object ,
+  all_ccopts ,
+  classic ,
+  nopervasives ,
+  preprocessor ,
+  all_ppx ,
+  annotations ,
+  binary_annotations ,
+  use_threads ,
+  use_vmthreads ,
+  noassert ,
+  verbose ,
+  noversion ,
+  noprompt ,
+  nopromptcont ,
+  init_file ,
+  noinit ,
+  open_modules ,
+  use_prims ,
+  use_runtime ,
+  principal ,
+  real_paths ,
+  recursive_types ,
+  strict_sequence ,
+  strict_formats ,
+  applicative_functors ,
+  make_runtime ,
+  gprofile ,
+  c_compiler ,
+  no_auto_link ,
+  dllpaths ,
+  make_package ,
+  for_package ,
+  error_size ,
+  float_const_prop ,
+  transparent_modules ,
+  dump_source ,
+  dump_parsetree ,
+  dump_typedtree ,
+  dump_rawlambda ,
+  dump_lambda ,
+  dump_rawclambda ,
+  dump_clambda ,
+  dump_rawflambda ,
+  dump_flambda ,
+  dump_flambda_let ,
+  dump_flambda_verbose ,
+  dump_instr ,
+  keep_asm_file ,
+  optimize_for_speed ,
+  opaque ,
+  dump_cmm ,
+  dump_selection ,
+  dump_cse ,
+  dump_live ,
+  dump_avail ,
+  dump_spill ,
+  dump_split ,
+  dump_interf ,
+  dump_prefer ,
+  dump_regalloc ,
+  dump_reload ,
+  dump_scheduling ,
+  dump_linear ,
+  dump_interval ,
+  keep_startup_file ,
+  dump_combine ,
+  debug_runavail ,
+  native_code ,
+  force_slash ,
+  clambda_checks ,
+  flambda_invariant_checks ,
+  dont_write_files ,
+  std_include_flag ,
+  std_include_dir ,
+  shared ,
+  dlcode ,
+  pic_code ,
+  runtime_variant ,
+  keep_docs ,
+  keep_locs ,
+  unsafe_string ,
+  classic_inlining ,
+  inlining_report ,
+  afl_instrument ,
+  afl_inst_ratio ,
+  simplify_rounds ,
+  default_simplify_rounds ,
+  rounds ,
+  default_inline_threshold ,
+  inline_toplevel_multiplier ,
+  default_inline_toplevel_threshold ,
+  default_inline_call_cost ,
+  default_inline_alloc_cost ,
+  default_inline_prim_cost ,
+  default_inline_branch_cost ,
+  default_inline_indirect_cost ,
+  default_inline_branch_factor ,
+  default_inline_lifting_benefit ,
+  default_inline_max_unroll ,
+  default_inline_max_depth ,
+  unbox_specialised_args ,
+  unbox_free_vars_of_closures ,
+  unbox_closures ,
+  default_unbox_closures_factor ,
+  unbox_closures_factor ,
+  remove_unused_arguments ,
+  o1_arguments ,
+  classic_arguments ,
+  o2_arguments ,
+  o3_arguments ,
+  all_passes ,
+  dumped_passes_list ,
+  dumped_pass ,
+  set_dumped_pass ,
+  parse_color_setting ,
+  color ,
+  unboxed_types ,
+  arg_spec ,
+  arg_names ,
+  reset_arguments ,
+  add_arguments ,
+  print_arguments ,
+  parse_arguments ,
+  
+}
+/* pic_code Not a pure module */
diff --git a/analysis/examples/larger-project/src/clflags.res b/analysis/examples/larger-project/src/clflags.res
new file mode 100644
index 0000000000..106c215628
--- /dev/null
+++ b/analysis/examples/larger-project/src/clflags.res
@@ -0,0 +1,346 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* Command-line parameters */
+
+module Int_arg_helper = {}
+module Float_arg_helper = {
+
+}
+
+let objfiles = ref((list{}: list<string>)) /* .cmo and .cma files */
+and ccobjs = ref((list{}: list<string>)) /* .o, .a, .so and -cclib -lxxx */
+and dllibs = ref((list{}: list<string>)) /* .so and -dllib -lxxx */
+
+let compile_only = ref(false) /* -c */
+and output_name = ref((None: option<string>)) /* -o */
+and include_dirs = ref((list{}: list<string>)) /* -I */
+and no_std_include = ref(false) /* -nostdlib */
+and print_types = ref(false) /* -i */
+and make_archive = ref(false) /* -a */
+and debug = ref(false) /* -g */
+and fast = ref(false) /* -unsafe */
+and use_linscan = ref(false) /* -linscan */
+and link_everything = ref(false) /* -linkall */
+and custom_runtime = ref(false) /* -custom */
+and no_check_prims = ref(false) /* -no-check-prims */
+and bytecode_compatible_32 = ref(false) /* -compat-32 */
+and output_c_object = ref(false) /* -output-obj */
+and output_complete_object = ref(false) /* -output-complete-obj */
+and all_ccopts = ref((list{}: list<string>)) /* -ccopt */
+and classic = ref(false) /* -nolabels */
+and nopervasives = ref(false) /* -nopervasives */
+and preprocessor = ref((None: option<string>)) /* -pp */
+and all_ppx = ref((list{}: list<string>)) /* -ppx */
+let annotations = ref(false) /* -annot */
+let binary_annotations = ref(false) /* -annot */
+and use_threads = ref(false) /* -thread */
+and use_vmthreads = ref(false) /* -vmthread */
+and noassert = ref(false) /* -noassert */
+and verbose = ref(false) /* -verbose */
+and noversion = ref(false) /* -no-version */
+and noprompt = ref(false) /* -noprompt */
+and nopromptcont = ref(false) /* -nopromptcont */
+and init_file = ref((None: option<string>)) /* -init */
+and noinit = ref(false) /* -noinit */
+and open_modules: ref<list<string>> = ref(list{}) /* -open */
+and use_prims = ref("") /* -use-prims ... */
+and use_runtime = ref("") /* -use-runtime ... */
+and principal = ref(false) /* -principal */
+and real_paths = ref(true) /* -short-paths */
+and recursive_types = ref(false) /* -rectypes */
+and strict_sequence = ref(false) /* -strict-sequence */
+and strict_formats = ref(false) /* -strict-formats */
+and applicative_functors = ref(true) /* -no-app-funct */
+and make_runtime = ref(false) /* -make-runtime */
+and gprofile = ref(false) /* -p */
+and c_compiler = ref((None: option<string>)) /* -cc */
+and no_auto_link = ref(false) /* -noautolink */
+and dllpaths = ref((list{}: list<string>)) /* -dllpath */
+and make_package = ref(false) /* -pack */
+and for_package = ref((None: option<string>)) /* -for-pack */
+and error_size = ref(500) /* -error-size */
+and float_const_prop = ref(true) /* -no-float-const-prop */
+and transparent_modules = ref(false) /* -trans-mod */
+let dump_source = ref(false) /* -dsource */
+let dump_parsetree = ref(false) /* -dparsetree */
+and dump_typedtree = ref(false) /* -dtypedtree */
+and dump_rawlambda = ref(false) /* -drawlambda */
+and dump_lambda = ref(false) /* -dlambda */
+and dump_rawclambda = ref(false) /* -drawclambda */
+and dump_clambda = ref(false) /* -dclambda */
+and dump_rawflambda = ref(false) /* -drawflambda */
+and dump_flambda = ref(false) /* -dflambda */
+and dump_flambda_let = ref((None: option<int>)) /* -dflambda-let=... */
+and dump_flambda_verbose = ref(false) /* -dflambda-verbose */
+and dump_instr = ref(false) /* -dinstr */
+
+let keep_asm_file = ref(false) /* -S */
+let optimize_for_speed = ref(true) /* -compact */
+and opaque = ref(false) /* -opaque */
+
+and dump_cmm = ref(false) /* -dcmm */
+let dump_selection = ref(false) /* -dsel */
+let dump_cse = ref(false) /* -dcse */
+let dump_live = ref(false) /* -dlive */
+let dump_avail = ref(false) /* -davail */
+let dump_spill = ref(false) /* -dspill */
+let dump_split = ref(false) /* -dsplit */
+let dump_interf = ref(false) /* -dinterf */
+let dump_prefer = ref(false) /* -dprefer */
+let dump_regalloc = ref(false) /* -dalloc */
+let dump_reload = ref(false) /* -dreload */
+let dump_scheduling = ref(false) /* -dscheduling */
+let dump_linear = ref(false) /* -dlinear */
+let dump_interval = ref(false) /* -dinterval */
+let keep_startup_file = ref(false) /* -dstartup */
+let dump_combine = ref(false) /* -dcombine */
+
+let debug_runavail = ref(false) /* -drunavail */
+
+let native_code = ref(false) /* set to true under ocamlopt */
+
+let force_slash = ref(false) /* for ocamldep */
+let clambda_checks = ref(false) /* -clambda-checks */
+
+let flambda_invariant_checks = ref(true) /* -flambda-invariants */
+
+let dont_write_files = ref(false) /* set to true under ocamldoc */
+
+let std_include_flag = prefix =>
+  if no_std_include.contents {
+    ""
+  } else {
+    prefix ++ Filename.quote(Config.standard_library)
+  }
+
+let std_include_dir = () =>
+  if no_std_include.contents {
+    list{}
+  } else {
+    list{Config.standard_library}
+  }
+
+let shared = ref(false) /* -shared */
+let dlcode = ref(true) /* not -nodynlink */
+
+let pic_code = ref(
+  switch Config.architecture {
+  /* -fPIC */
+  | "amd64" => true
+  | _ => false
+  },
+)
+
+let runtime_variant = ref("") /* -runtime-variant */
+
+let keep_docs = ref(false) /* -keep-docs */
+let keep_locs = ref(true) /* -keep-locs */
+let unsafe_string = if Config.safe_string {
+  ref(false)
+} else {
+  ref(!Config.default_safe_string)
+}
+/* -safe-string / -unsafe-string */
+
+let classic_inlining = ref(false) /* -Oclassic */
+let inlining_report = ref(false) /* -inlining-report */
+
+let afl_instrument = ref(Config.afl_instrument) /* -afl-instrument */
+let afl_inst_ratio = ref(100) /* -afl-inst-ratio */
+
+let simplify_rounds = ref(None) /* -rounds */
+let default_simplify_rounds = ref(1) /* -rounds */
+let rounds = () =>
+  switch simplify_rounds.contents {
+  | None => default_simplify_rounds.contents
+  | Some(r) => r
+  }
+
+let default_inline_threshold = if Config.flambda {
+  10.
+} else {
+  10. /. 8.
+}
+let inline_toplevel_multiplier = 16
+let default_inline_toplevel_threshold = int_of_float(
+  float(inline_toplevel_multiplier) *. default_inline_threshold,
+)
+let default_inline_call_cost = 5
+let default_inline_alloc_cost = 7
+let default_inline_prim_cost = 3
+let default_inline_branch_cost = 5
+let default_inline_indirect_cost = 4
+let default_inline_branch_factor = 0.1
+let default_inline_lifting_benefit = 1300
+let default_inline_max_unroll = 0
+let default_inline_max_depth = 1
+
+let unbox_specialised_args = ref(true) /* -no-unbox-specialised-args */
+let unbox_free_vars_of_closures = ref(true)
+let unbox_closures = ref(false) /* -unbox-closures */
+let default_unbox_closures_factor = 10
+let unbox_closures_factor = ref(default_unbox_closures_factor) /* -unbox-closures-factor */
+let remove_unused_arguments = ref(false) /* -remove-unused-arguments */
+
+type inlining_arguments = {
+  inline_call_cost: option<int>,
+  inline_alloc_cost: option<int>,
+  inline_prim_cost: option<int>,
+  inline_branch_cost: option<int>,
+  inline_indirect_cost: option<int>,
+  inline_lifting_benefit: option<int>,
+  inline_branch_factor: option<float>,
+  inline_max_depth: option<int>,
+  inline_max_unroll: option<int>,
+  inline_threshold: option<float>,
+  inline_toplevel_threshold: option<int>,
+}
+
+/* o1 is the default */
+let o1_arguments = {
+  inline_call_cost: None,
+  inline_alloc_cost: None,
+  inline_prim_cost: None,
+  inline_branch_cost: None,
+  inline_indirect_cost: None,
+  inline_lifting_benefit: None,
+  inline_branch_factor: None,
+  inline_max_depth: None,
+  inline_max_unroll: None,
+  inline_threshold: None,
+  inline_toplevel_threshold: None,
+}
+
+let classic_arguments = {
+  inline_call_cost: None,
+  inline_alloc_cost: None,
+  inline_prim_cost: None,
+  inline_branch_cost: None,
+  inline_indirect_cost: None,
+  inline_lifting_benefit: None,
+  inline_branch_factor: None,
+  inline_max_depth: None,
+  inline_max_unroll: None,
+  /* [inline_threshold] matches the current compiler's default.
+     Note that this particular fraction can be expressed exactly in
+     floating point. */
+  inline_threshold: Some(10. /. 8.),
+  /* [inline_toplevel_threshold] is not used in classic mode. */
+  inline_toplevel_threshold: Some(1),
+}
+
+let o2_arguments = {
+  inline_call_cost: Some(2 * default_inline_call_cost),
+  inline_alloc_cost: Some(2 * default_inline_alloc_cost),
+  inline_prim_cost: Some(2 * default_inline_prim_cost),
+  inline_branch_cost: Some(2 * default_inline_branch_cost),
+  inline_indirect_cost: Some(2 * default_inline_indirect_cost),
+  inline_lifting_benefit: None,
+  inline_branch_factor: None,
+  inline_max_depth: Some(2),
+  inline_max_unroll: None,
+  inline_threshold: Some(25.),
+  inline_toplevel_threshold: Some(25 * inline_toplevel_multiplier),
+}
+
+let o3_arguments = {
+  inline_call_cost: Some(3 * default_inline_call_cost),
+  inline_alloc_cost: Some(3 * default_inline_alloc_cost),
+  inline_prim_cost: Some(3 * default_inline_prim_cost),
+  inline_branch_cost: Some(3 * default_inline_branch_cost),
+  inline_indirect_cost: Some(3 * default_inline_indirect_cost),
+  inline_lifting_benefit: None,
+  inline_branch_factor: Some(0.),
+  inline_max_depth: Some(3),
+  inline_max_unroll: Some(1),
+  inline_threshold: Some(50.),
+  inline_toplevel_threshold: Some(50 * inline_toplevel_multiplier),
+}
+
+let all_passes: ref<list<string>> = ref(list{})
+let dumped_passes_list = ref(list{})
+let dumped_pass = s => {
+  assert List.mem(s, all_passes.contents)
+  List.mem(s, dumped_passes_list.contents)
+}
+
+let set_dumped_pass = (s, enabled) =>
+  if List.mem(s, all_passes.contents) {
+    let passes_without_s = List.filter(\"<>"(s), dumped_passes_list.contents)
+    let dumped_passes = if enabled {
+      list{s, ...passes_without_s}
+    } else {
+      passes_without_s
+    }
+
+    dumped_passes_list := dumped_passes
+  }
+
+let parse_color_setting = x =>
+  switch x {
+  | "auto" => Some(Misc.Color.Auto)
+  | "always" => Some(Misc.Color.Always)
+  | "never" => Some(Misc.Color.Never)
+  | _ => None
+  }
+let color: ref<option<string>> = ref(None) /* -color */
+
+let unboxed_types = ref(false)
+
+let arg_spec = ref(list{})
+let arg_names : ref<Misc.StringMap.t<int>> = ref(Misc.StringMap.empty)
+
+let reset_arguments = () => {
+  arg_spec := list{}
+  arg_names := Misc.StringMap.empty
+}
+
+let add_arguments = (loc, args) => List.iter(x =>
+    switch x {
+    | (arg_name, _, _) as arg =>
+      try {
+        let loc2 = Misc.StringMap.find(arg_name, arg_names.contents)
+        Printf.eprintf("Warning: plugin argument %s is already defined:\n", arg_name)
+        Printf.eprintf("   First definition: %s\n", loc2)
+        Printf.eprintf("   New definition: %s\n", loc)
+      } catch {
+      | Not_found =>
+        arg_spec := \"@"(arg_spec.contents, list{arg})
+        arg_names := Misc.StringMap.add(arg_name, loc, arg_names.contents)
+      }
+    }
+  , args)
+
+let print_arguments = usage => Arg.usage(arg_spec.contents, usage)
+
+/* This function is almost the same as [Arg.parse_expand], except
+   that [Arg.parse_expand] could not be used because it does not take a
+   reference for [arg_spec].*/
+@raises(exit)
+let parse_arguments = (f, msg) =>
+  try {
+    let argv = ref(Sys.argv)
+    let current = ref(Arg.current.contents)
+    Arg.parse_and_expand_argv_dynamic(current, argv, arg_spec, f, msg)
+  } catch {
+  | Arg.Bad(msg) =>
+    Printf.eprintf("%s", msg)
+    exit(2)
+  | Arg.Help(msg) =>
+    Printf.printf("%s", msg)
+    exit(0)
+  }
+
diff --git a/analysis/examples/larger-project/src/config.js b/analysis/examples/larger-project/src/config.js
new file mode 100644
index 0000000000..50ce03020e
--- /dev/null
+++ b/analysis/examples/larger-project/src/config.js
@@ -0,0 +1,362 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Sys from "rescript/lib/es6/sys.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Printf from "./printf.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Caml_sys from "rescript/lib/es6/caml_sys.js";
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+var standard_library_default = "/usr/local/lib/ocaml";
+
+var standard_library;
+
+try {
+  standard_library = Caml_sys.caml_sys_getenv("OCAMLLIB");
+}
+catch (raw_exn){
+  var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+  if (exn.RE_EXN_ID === "Not_found") {
+    try {
+      standard_library = Caml_sys.caml_sys_getenv("CAMLLIB");
+    }
+    catch (raw_exn$1){
+      var exn$1 = Caml_js_exceptions.internalToOCamlException(raw_exn$1);
+      if (exn$1.RE_EXN_ID === "Not_found") {
+        standard_library = standard_library_default;
+      } else {
+        throw exn$1;
+      }
+    }
+  } else {
+    throw exn;
+  }
+}
+
+var standard_runtime = "/usr/local/bin/ocamlrun";
+
+var ccomp_type = "cc";
+
+var c_compiler = "gcc";
+
+var ocamlc_cflags = "-O2 -fno-strict-aliasing -fwrapv ";
+
+var ocamlc_cppflags = "-D_FILE_OFFSET_BITS=64 -D_REENTRANT";
+
+var ocamlopt_cflags = "-O2 -fno-strict-aliasing -fwrapv";
+
+var ocamlopt_cppflags = "-D_FILE_OFFSET_BITS=64 -D_REENTRANT";
+
+var bytecomp_c_libraries = "-lpthread                  ";
+
+var bytecomp_c_compiler = "gcc -O2 -fno-strict-aliasing -fwrapv  -D_FILE_OFFSET_BITS=64 -D_REENTRANT";
+
+var native_c_compiler = "gcc -O2 -fno-strict-aliasing -fwrapv -D_FILE_OFFSET_BITS=64 -D_REENTRANT";
+
+var native_c_libraries = "";
+
+var native_pack_linker = "ld -r -arch x86_64 -o ";
+
+var ranlib = "ranlib";
+
+var cc_profile = "-pg";
+
+var match;
+
+if (Sys.os_type === "Win32") {
+  try {
+    var flexlink = Caml_sys.caml_sys_getenv("OCAML_FLEXLINK");
+    var f = function (i) {
+      var c = Caml_string.get(flexlink, i);
+      if (c === /* '/' */47) {
+        return /* '\\' */92;
+      } else {
+        return c;
+      }
+    };
+    var flexlink$1 = $$String.init(flexlink.length, f) + " ";
+    match = [
+      flexlink$1,
+      flexlink$1 + " -exe",
+      flexlink$1 + " -maindll"
+    ];
+  }
+  catch (raw_exn$2){
+    var exn$2 = Caml_js_exceptions.internalToOCamlException(raw_exn$2);
+    if (exn$2.RE_EXN_ID === "Not_found") {
+      match = [
+        "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+        "gcc -O2 -fno-strict-aliasing -fwrapv -Wall -Werror -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DCAML_NAME_SPACE   -Wl,-no_compact_unwind",
+        "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind"
+      ];
+    } else {
+      throw exn$2;
+    }
+  }
+} else {
+  match = [
+    "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+    "gcc -O2 -fno-strict-aliasing -fwrapv -Wall -Werror -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DCAML_NAME_SPACE   -Wl,-no_compact_unwind",
+    "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind"
+  ];
+}
+
+var exec_magic_number = "Caml1999X011";
+
+var cmi_magic_number = "Caml1999I022";
+
+var cmo_magic_number = "Caml1999O022";
+
+var cma_magic_number = "Caml1999A022";
+
+var cmx_magic_number = "Caml1999Y022";
+
+var cmxa_magic_number = "Caml1999Z022";
+
+var ast_impl_magic_number = "Caml1999M022";
+
+var ast_intf_magic_number = "Caml1999N022";
+
+var cmxs_magic_number = "Caml1999D022";
+
+var cmt_magic_number = "Caml1999T022";
+
+var load_path = {
+  contents: /* [] */0
+};
+
+var interface_suffix = {
+  contents: ".mli"
+};
+
+var architecture = "amd64";
+
+var model = "default";
+
+var system = "macosx";
+
+var asm = "clang -arch x86_64 -Wno-trigraphs -c";
+
+var ext_exe = "";
+
+var ext_obj = ".o";
+
+var ext_asm = ".s";
+
+var ext_lib = ".a";
+
+var ext_dll = ".so";
+
+var host = "x86_64-apple-darwin21.4.0";
+
+var target = "x86_64-apple-darwin21.4.0";
+
+var default_executable_name;
+
+switch (Sys.os_type) {
+  case "Unix" :
+      default_executable_name = "a.out";
+      break;
+  case "Cygwin" :
+  case "Win32" :
+      default_executable_name = "camlprog.exe";
+      break;
+  default:
+    default_executable_name = "camlprog";
+}
+
+function print_config(oc) {
+  var p = function (name, valu) {
+    return Curry._3(Printf.fprintf(oc), "%s: %s\n", name, valu);
+  };
+  var p_int = function (name, valu) {
+    return Curry._3(Printf.fprintf(oc), "%s: %d\n", name, valu);
+  };
+  var p_bool = function (name, valu) {
+    return Curry._3(Printf.fprintf(oc), "%s: %B\n", name, valu);
+  };
+  p("version", Sys.ocaml_version);
+  p("standard_library_default", standard_library_default);
+  p("standard_library", standard_library);
+  p("standard_runtime", standard_runtime);
+  p("ccomp_type", ccomp_type);
+  p("c_compiler", c_compiler);
+  p("ocamlc_cflags", ocamlc_cflags);
+  p("ocamlc_cppflags", ocamlc_cppflags);
+  p("ocamlopt_cflags", ocamlopt_cflags);
+  p("ocamlopt_cppflags", ocamlopt_cppflags);
+  p("bytecomp_c_compiler", bytecomp_c_compiler);
+  p("native_c_compiler", native_c_compiler);
+  p("bytecomp_c_libraries", bytecomp_c_libraries);
+  p("native_c_libraries", native_c_libraries);
+  p("native_pack_linker", native_pack_linker);
+  p("ranlib", ranlib);
+  p("cc_profile", cc_profile);
+  p("architecture", architecture);
+  p("model", model);
+  p_int("int_size", Sys.int_size);
+  p_int("word_size", Sys.word_size);
+  p("system", system);
+  p("asm", asm);
+  p_bool("asm_cfi_supported", true);
+  p_bool("with_frame_pointers", false);
+  p("ext_exe", ext_exe);
+  p("ext_obj", ext_obj);
+  p("ext_asm", ext_asm);
+  p("ext_lib", ext_lib);
+  p("ext_dll", ext_dll);
+  p("os_type", Sys.os_type);
+  p("default_executable_name", default_executable_name);
+  p_bool("systhread_supported", true);
+  p("host", host);
+  p("target", target);
+  p_bool("profiling", true);
+  p_bool("flambda", false);
+  p_bool("spacetime", false);
+  p_bool("safe_string", false);
+  p_bool("default_safe_string", true);
+  p_bool("flat_float_array", true);
+  p_bool("afl_instrument", false);
+  p_bool("windows_unicode", false);
+  p("exec_magic_number", exec_magic_number);
+  p("cmi_magic_number", cmi_magic_number);
+  p("cmo_magic_number", cmo_magic_number);
+  p("cma_magic_number", cma_magic_number);
+  p("cmx_magic_number", cmx_magic_number);
+  p("cmxa_magic_number", cmxa_magic_number);
+  p("ast_impl_magic_number", ast_impl_magic_number);
+  p("ast_intf_magic_number", ast_intf_magic_number);
+  p("cmxs_magic_number", cmxs_magic_number);
+  return p("cmt_magic_number", cmt_magic_number);
+}
+
+var version = Sys.ocaml_version;
+
+var c_output_obj = "-o ";
+
+var ar = "ar";
+
+var mkdll = match[0];
+
+var mkexe = match[1];
+
+var mkmaindll = match[2];
+
+var profiling = true;
+
+var flambda = false;
+
+var safe_string = false;
+
+var default_safe_string = true;
+
+var windows_unicode = false;
+
+var flat_float_array = true;
+
+var afl_instrument = false;
+
+var max_tag = 245;
+
+var lazy_tag = 246;
+
+var max_young_wosize = 256;
+
+var stack_threshold = 256;
+
+var stack_safety_margin = 60;
+
+var asm_cfi_supported = true;
+
+var with_frame_pointers = false;
+
+var spacetime = false;
+
+var enable_call_counts = true;
+
+var libunwind_available = false;
+
+var libunwind_link_flags = "";
+
+var profinfo = false;
+
+var profinfo_width = 0;
+
+var systhread_supported = true;
+
+var flexdll_dirs = /* [] */0;
+
+export {
+  version ,
+  standard_library_default ,
+  standard_library ,
+  standard_runtime ,
+  ccomp_type ,
+  c_compiler ,
+  c_output_obj ,
+  ocamlc_cflags ,
+  ocamlc_cppflags ,
+  ocamlopt_cflags ,
+  ocamlopt_cppflags ,
+  bytecomp_c_libraries ,
+  bytecomp_c_compiler ,
+  native_c_compiler ,
+  native_c_libraries ,
+  native_pack_linker ,
+  ranlib ,
+  ar ,
+  cc_profile ,
+  mkdll ,
+  mkexe ,
+  mkmaindll ,
+  profiling ,
+  flambda ,
+  safe_string ,
+  default_safe_string ,
+  windows_unicode ,
+  flat_float_array ,
+  afl_instrument ,
+  exec_magic_number ,
+  cmi_magic_number ,
+  cmo_magic_number ,
+  cma_magic_number ,
+  cmx_magic_number ,
+  cmxa_magic_number ,
+  ast_impl_magic_number ,
+  ast_intf_magic_number ,
+  cmxs_magic_number ,
+  cmt_magic_number ,
+  load_path ,
+  interface_suffix ,
+  max_tag ,
+  lazy_tag ,
+  max_young_wosize ,
+  stack_threshold ,
+  stack_safety_margin ,
+  architecture ,
+  model ,
+  system ,
+  asm ,
+  asm_cfi_supported ,
+  with_frame_pointers ,
+  spacetime ,
+  enable_call_counts ,
+  libunwind_available ,
+  libunwind_link_flags ,
+  profinfo ,
+  profinfo_width ,
+  ext_exe ,
+  ext_obj ,
+  ext_asm ,
+  ext_lib ,
+  ext_dll ,
+  host ,
+  target ,
+  default_executable_name ,
+  systhread_supported ,
+  flexdll_dirs ,
+  print_config ,
+  
+}
+/* standard_library Not a pure module */
diff --git a/analysis/examples/larger-project/src/config.res b/analysis/examples/larger-project/src/config.res
new file mode 100644
index 0000000000..c856664d12
--- /dev/null
+++ b/analysis/examples/larger-project/src/config.res
@@ -0,0 +1,222 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* The main OCaml version string has moved to ../VERSION */
+let version = Sys.ocaml_version
+
+let standard_library_default = "/usr/local/lib/ocaml"
+
+let standard_library = try Sys.getenv("OCAMLLIB") catch {
+| Not_found =>
+  try Sys.getenv("CAMLLIB") catch {
+  | Not_found => standard_library_default
+  }
+}
+
+let standard_runtime = "/usr/local/bin/ocamlrun"
+let ccomp_type = "cc"
+let c_compiler = "gcc"
+let c_output_obj = "-o "
+let ocamlc_cflags = "-O2 -fno-strict-aliasing -fwrapv "
+let ocamlc_cppflags = "-D_FILE_OFFSET_BITS=64 -D_REENTRANT"
+let ocamlopt_cflags = "-O2 -fno-strict-aliasing -fwrapv"
+let ocamlopt_cppflags = "-D_FILE_OFFSET_BITS=64 -D_REENTRANT"
+let bytecomp_c_libraries = "-lpthread                  "
+/* bytecomp_c_compiler and native_c_compiler have been supported for a
+   long time and are retained for backwards compatibility.
+   For programs that don't need compatibility with older OCaml releases
+   the recommended approach is to use the constituent variables
+   c_compiler, ocamlc_cflags, ocamlc_cppflags etc., directly.
+*/
+let bytecomp_c_compiler = c_compiler ++ (" " ++ (ocamlc_cflags ++ (" " ++ ocamlc_cppflags)))
+let native_c_compiler = c_compiler ++ (" " ++ (ocamlopt_cflags ++ (" " ++ ocamlopt_cppflags)))
+let native_c_libraries = ""
+let native_pack_linker = "ld -r -arch x86_64 -o\ "
+let ranlib = "ranlib"
+let ar = "ar"
+let cc_profile = "-pg"
+let (mkdll, mkexe, mkmaindll) = /* @@DRA Cygwin - but only if shared libraries are enabled, which we
+ should be able to detect? */
+if Sys.os_type == "Win32" {
+  try {
+    @raises(Invalid_argument)
+    let flexlink = {
+      let flexlink = Sys.getenv("OCAML_FLEXLINK")
+
+      @raises(Invalid_argument)
+      let f = i => {
+        let c = String.get(flexlink, i)
+        if c == '/' {
+          '\\'
+        } else {
+          c
+        }
+      }
+      String.init(String.length(flexlink), f) ++ " "
+    }
+    (flexlink, flexlink ++ " -exe", flexlink ++ " -maindll")
+  } catch {
+  | Not_found => (
+      "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+      "gcc -O2 -fno-strict-aliasing -fwrapv -Wall -Werror -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DCAML_NAME_SPACE   -Wl,-no_compact_unwind",
+      "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+    )
+  }
+} else {
+  (
+    "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+    "gcc -O2 -fno-strict-aliasing -fwrapv -Wall -Werror -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DCAML_NAME_SPACE   -Wl,-no_compact_unwind",
+    "gcc -shared -flat_namespace -undefined suppress                    -Wl,-no_compact_unwind",
+  )
+}
+
+let profiling = true
+let flambda = false
+let safe_string = false
+let default_safe_string = true
+let windows_unicode = 0 !== 0
+
+let flat_float_array = true
+
+let afl_instrument = false
+
+let exec_magic_number = "Caml1999X011"
+and cmi_magic_number = "Caml1999I022"
+and cmo_magic_number = "Caml1999O022"
+and cma_magic_number = "Caml1999A022"
+and cmx_magic_number = if flambda {
+  "Caml1999y022"
+} else {
+  "Caml1999Y022"
+}
+and cmxa_magic_number = if flambda {
+  "Caml1999z022"
+} else {
+  "Caml1999Z022"
+}
+and ast_impl_magic_number = "Caml1999M022"
+and ast_intf_magic_number = "Caml1999N022"
+and cmxs_magic_number = "Caml1999D022"
+/* cmxs_magic_number is duplicated in otherlibs/dynlink/natdynlink.ml */
+and cmt_magic_number = "Caml1999T022"
+
+let load_path = ref((list{}: list<string>))
+
+let interface_suffix = ref(".mli")
+
+let max_tag = 245
+/* This is normally the same as in obj.ml, but we have to define it
+   separately because it can differ when we're in the middle of a
+   bootstrapping phase. */
+let lazy_tag = 246
+
+let max_young_wosize = 256
+let stack_threshold = 256 /* see byterun/config.h */
+let stack_safety_margin = 60
+
+let architecture = "amd64"
+let model = "default"
+let system = "macosx"
+
+let asm = "clang -arch x86_64 -Wno-trigraphs -c"
+let asm_cfi_supported = true
+let with_frame_pointers = false
+let spacetime = false
+let enable_call_counts = true
+let libunwind_available = false
+let libunwind_link_flags = ""
+let profinfo = false
+let profinfo_width = 0
+
+let ext_exe = ""
+let ext_obj = ".o"
+let ext_asm = ".s"
+let ext_lib = ".a"
+let ext_dll = ".so"
+
+let host = "x86_64-apple-darwin21.4.0"
+let target = "x86_64-apple-darwin21.4.0"
+
+let default_executable_name = switch Sys.os_type {
+| "Unix" => "a.out"
+| "Win32" | "Cygwin" => "camlprog.exe"
+| _ => "camlprog"
+}
+
+let systhread_supported = true
+
+let flexdll_dirs = list{}
+
+let print_config = oc => {
+  let p = (name, valu) => Printf.fprintf(oc, "%s: %s\n", name, valu)
+  let p_int = (name, valu) => Printf.fprintf(oc, "%s: %d\n", name, valu)
+  let p_bool = (name, valu) => Printf.fprintf(oc, "%s: %B\n", name, valu)
+  p("version", version)
+  p("standard_library_default", standard_library_default)
+  p("standard_library", standard_library)
+  p("standard_runtime", standard_runtime)
+  p("ccomp_type", ccomp_type)
+  p("c_compiler", c_compiler)
+  p("ocamlc_cflags", ocamlc_cflags)
+  p("ocamlc_cppflags", ocamlc_cppflags)
+  p("ocamlopt_cflags", ocamlopt_cflags)
+  p("ocamlopt_cppflags", ocamlopt_cppflags)
+  p("bytecomp_c_compiler", bytecomp_c_compiler)
+  p("native_c_compiler", native_c_compiler)
+  p("bytecomp_c_libraries", bytecomp_c_libraries)
+  p("native_c_libraries", native_c_libraries)
+  p("native_pack_linker", native_pack_linker)
+  p("ranlib", ranlib)
+  p("cc_profile", cc_profile)
+  p("architecture", architecture)
+  p("model", model)
+  p_int("int_size", Sys.int_size)
+  p_int("word_size", Sys.word_size)
+  p("system", system)
+  p("asm", asm)
+  p_bool("asm_cfi_supported", asm_cfi_supported)
+  p_bool("with_frame_pointers", with_frame_pointers)
+  p("ext_exe", ext_exe)
+  p("ext_obj", ext_obj)
+  p("ext_asm", ext_asm)
+  p("ext_lib", ext_lib)
+  p("ext_dll", ext_dll)
+  p("os_type", Sys.os_type)
+  p("default_executable_name", default_executable_name)
+  p_bool("systhread_supported", systhread_supported)
+  p("host", host)
+  p("target", target)
+  p_bool("profiling", profiling)
+  p_bool("flambda", flambda)
+  p_bool("spacetime", spacetime)
+  p_bool("safe_string", safe_string)
+  p_bool("default_safe_string", default_safe_string)
+  p_bool("flat_float_array", flat_float_array)
+  p_bool("afl_instrument", afl_instrument)
+  p_bool("windows_unicode", windows_unicode)
+
+  /* print the magic number */
+  p("exec_magic_number", exec_magic_number)
+  p("cmi_magic_number", cmi_magic_number)
+  p("cmo_magic_number", cmo_magic_number)
+  p("cma_magic_number", cma_magic_number)
+  p("cmx_magic_number", cmx_magic_number)
+  p("cmxa_magic_number", cmxa_magic_number)
+  p("ast_impl_magic_number", ast_impl_magic_number)
+  p("ast_intf_magic_number", ast_intf_magic_number)
+  p("cmxs_magic_number", cmxs_magic_number)
+  p("cmt_magic_number", cmt_magic_number)
+
+}
diff --git a/analysis/examples/larger-project/src/exception/Arr.js b/analysis/examples/larger-project/src/exception/Arr.js
new file mode 100644
index 0000000000..ec423e8722
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/Arr.js
@@ -0,0 +1,24 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
+
+function ff(a) {
+  Belt_Array.get(a, 3);
+  return 11;
+}
+
+var MM = {
+  ff: ff
+};
+
+var B;
+
+var $$Array;
+
+export {
+  B ,
+  $$Array ,
+  MM ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/Arr.res b/analysis/examples/larger-project/src/exception/Arr.res
new file mode 100644
index 0000000000..320a7c85d2
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/Arr.res
@@ -0,0 +1,9 @@
+module B = Belt
+module Array = B.Array
+
+module MM = {
+  let ff = a =>
+    switch a[3] {
+    | _ => 11
+    }
+}
diff --git a/analysis/examples/larger-project/src/exception/BeltTest.js b/analysis/examples/larger-project/src/exception/BeltTest.js
new file mode 100644
index 0000000000..693b400d47
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/BeltTest.js
@@ -0,0 +1,41 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Belt_Map from "rescript/lib/es6/belt_Map.js";
+import * as Belt_List from "rescript/lib/es6/belt_List.js";
+import * as Belt_MapInt from "rescript/lib/es6/belt_MapInt.js";
+import * as Belt_MapString from "rescript/lib/es6/belt_MapString.js";
+
+var lstHead1 = Belt_List.headExn;
+
+var lstHead2 = Belt_List.headExn;
+
+var mapGetExn1 = Belt_MapInt.getExn;
+
+var mapGetExn2 = Belt_MapInt.getExn;
+
+var mapGetExn3 = Belt_MapInt.getExn;
+
+var mapGetExn4 = Belt_MapString.getExn;
+
+var mapGetExn5 = Belt_MapString.getExn;
+
+var mapGetExn6 = Belt_MapString.getExn;
+
+var mapGetExn7 = Belt_Map.getExn;
+
+var mapGetExn8 = Belt_Map.getExn;
+
+export {
+  lstHead1 ,
+  lstHead2 ,
+  mapGetExn1 ,
+  mapGetExn2 ,
+  mapGetExn3 ,
+  mapGetExn4 ,
+  mapGetExn5 ,
+  mapGetExn6 ,
+  mapGetExn7 ,
+  mapGetExn8 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/BeltTest.res b/analysis/examples/larger-project/src/exception/BeltTest.res
new file mode 100644
index 0000000000..4205f3e0b7
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/BeltTest.res
@@ -0,0 +1,31 @@
+open Belt.List
+
+@raises(Not_found)
+let lstHead1 = l => l->Belt.List.headExn
+
+@raises(Not_found)
+let lstHead2 = l => l->Belt_List.headExn
+
+@raises(Not_found)
+let mapGetExn1 = (s, k) => s->Belt.Map.Int.getExn(k)
+
+@raises(Not_found)
+let mapGetExn2 = (s, k) => s->Belt_Map.Int.getExn(k)
+
+@raises(Not_found)
+let mapGetExn3 = (s, k) => s->Belt_MapInt.getExn(k)
+
+@raises(Not_found)
+let mapGetExn4 = (s, k) => s->Belt.Map.String.getExn(k)
+
+@raises(Not_found)
+let mapGetExn5 = (s, k) => s->Belt_Map.String.getExn(k)
+
+@raises(Not_found)
+let mapGetExn6 = (s, k) => s->Belt_MapString.getExn(k)
+
+@raises(Not_found)
+let mapGetExn7 = (s, k) => s->Belt.Map.getExn(k)
+
+@raises(Not_found)
+let mapGetExn8 = (s, k) => s->Belt_Map.getExn(k)
diff --git a/analysis/examples/larger-project/src/exception/BsJson.js b/analysis/examples/larger-project/src/exception/BsJson.js
new file mode 100644
index 0000000000..65921ddc65
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/BsJson.js
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Json_decode from "@glennsl/bs-json/src/Json_decode.js";
+
+var testBsJson = Json_decode.string;
+
+var testBsJson2 = Json_decode.string;
+
+export {
+  testBsJson ,
+  testBsJson2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/BsJson.res b/analysis/examples/larger-project/src/exception/BsJson.res
new file mode 100644
index 0000000000..ad892c8e7b
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/BsJson.res
@@ -0,0 +1,5 @@
+@raise(DecodeError)
+let testBsJson = x => Json_decode.string(x)
+
+@raise(DecodeError)
+let testBsJson2 = x => Json.Decode.string(x)
diff --git a/analysis/examples/larger-project/src/exception/Exn.js b/analysis/examples/larger-project/src/exception/Exn.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/Exn.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/exception/Exn.res b/analysis/examples/larger-project/src/exception/Exn.res
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/src/exception/ExnA.js b/analysis/examples/larger-project/src/exception/ExnA.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/ExnA.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/exception/ExnA.res b/analysis/examples/larger-project/src/exception/ExnA.res
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/analysis/examples/larger-project/src/exception/ExnB.js b/analysis/examples/larger-project/src/exception/ExnB.js
new file mode 100644
index 0000000000..4967199b18
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/ExnB.js
@@ -0,0 +1,15 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function foo(param) {
+  throw {
+        RE_EXN_ID: "Not_found",
+        Error: new Error()
+      };
+}
+
+export {
+  foo ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/ExnB.res b/analysis/examples/larger-project/src/exception/ExnB.res
new file mode 100644
index 0000000000..2a50ef4652
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/ExnB.res
@@ -0,0 +1,2 @@
+@raises(Not_found)
+let foo = () => raise(Not_found)
diff --git a/analysis/examples/larger-project/src/exception/ExportWithRename.js b/analysis/examples/larger-project/src/exception/ExportWithRename.js
new file mode 100644
index 0000000000..dbbd1bbf92
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/ExportWithRename.js
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function ExportWithRename(Props) {
+  return Props.s;
+}
+
+var make = ExportWithRename;
+
+export {
+  make ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/ExportWithRename.res b/analysis/examples/larger-project/src/exception/ExportWithRename.res
new file mode 100644
index 0000000000..b8b4865b2a
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/ExportWithRename.res
@@ -0,0 +1,2 @@
+@genType("ExportWithRename") @react.component
+let make = (~s) => React.string(s)
diff --git a/analysis/examples/larger-project/src/exception/InnerModules.js b/analysis/examples/larger-project/src/exception/InnerModules.js
new file mode 100644
index 0000000000..9ddf89f8ab
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/InnerModules.js
@@ -0,0 +1,40 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+
+var wrapExitTop = Pervasives.exit;
+
+var wrapExitM1 = Pervasives.exit;
+
+var callLocally = Pervasives.exit;
+
+var callTop = Pervasives.exit;
+
+var wrapExitM2 = Pervasives.exit;
+
+var callM1 = Pervasives.exit;
+
+var callTop$1 = Pervasives.exit;
+
+var M2 = {
+  wrapExitM2: wrapExitM2,
+  callM1: callM1,
+  callTop: callTop$1
+};
+
+var M1 = {
+  wrapExitM1: wrapExitM1,
+  callLocally: callLocally,
+  callTop: callTop,
+  M2: M2
+};
+
+var callM1$1 = Pervasives.exit;
+
+export {
+  wrapExitTop ,
+  M1 ,
+  callM1$1 as callM1,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/InnerModules.res b/analysis/examples/larger-project/src/exception/InnerModules.res
new file mode 100644
index 0000000000..7c7f9dc1a0
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/InnerModules.res
@@ -0,0 +1,27 @@
+@raises(exit)
+let wrapExitTop = x => exit(x)
+
+module M1 = {
+  @raises(exit)
+  let wrapExitM1 = x => exit(x)
+
+  @raises(exit)
+  let callLocally = x => wrapExitM1(x)
+
+  @raises(exit)
+  let callTop = x => wrapExitTop(x)
+
+  module M2 = {
+    @raises(exit)
+    let wrapExitM2 = x => exit(x)
+
+    @raises(exit)
+    let callM1 = x => wrapExitM1(x)
+
+    @raises(exit)
+    let callTop = x => wrapExitTop(x)
+  }
+}
+
+@raises(exit)
+let callM1 = x => M1.wrapExitM1(x)
diff --git a/analysis/examples/larger-project/src/exception/TestInnerModules.js b/analysis/examples/larger-project/src/exception/TestInnerModules.js
new file mode 100644
index 0000000000..0d15a24c7f
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/TestInnerModules.js
@@ -0,0 +1,22 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as InnerModules from "./InnerModules.js";
+
+var testTop = InnerModules.wrapExitTop;
+
+function testM1(x) {
+  return InnerModules.M1.wrapExitM1(x);
+}
+
+function testM2(x) {
+  return Curry._1(InnerModules.M1.M2.wrapExitM2, x);
+}
+
+export {
+  testTop ,
+  testM1 ,
+  testM2 ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/TestInnerModules.res b/analysis/examples/larger-project/src/exception/TestInnerModules.res
new file mode 100644
index 0000000000..6b1e88516b
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/TestInnerModules.res
@@ -0,0 +1,8 @@
+@raises(exit)
+let testTop = x => InnerModules.wrapExitTop(x)
+
+@raises(exit)
+let testM1 = x => InnerModules.M1.wrapExitM1(x)
+
+@raises(exit)
+let testM2 = x => InnerModules.M1.M2.wrapExitM2(x)
diff --git a/analysis/examples/larger-project/src/exception/TestYojson.js b/analysis/examples/larger-project/src/exception/TestYojson.js
new file mode 100644
index 0000000000..e7efc9a066
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/TestYojson.js
@@ -0,0 +1,46 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Yojson from "./Yojson.js";
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function foo(x) {
+  return Yojson.Basic.from_string(x);
+}
+
+function bar(str, json) {
+  try {
+    return Curry._2(Yojson.Basic.Util.member, str, json);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === Yojson.Basic.Util.Type_error) {
+      if (exn._1 === "a") {
+        if (Caml_obj.caml_equal(exn._2, json)) {
+          return json;
+        }
+        throw exn;
+      }
+      throw exn;
+    }
+    throw exn;
+  }
+}
+
+function toString(x) {
+  return Curry._1(Yojson.Basic.Util.to_string, x);
+}
+
+function toInt(x) {
+  return Curry._1(Yojson.Basic.Util.to_int, x);
+}
+
+export {
+  foo ,
+  bar ,
+  toString ,
+  toInt ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/TestYojson.res b/analysis/examples/larger-project/src/exception/TestYojson.res
new file mode 100644
index 0000000000..2614fd1f53
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/TestYojson.res
@@ -0,0 +1,17 @@
+@raises(Yojson.Json_error)
+let foo = x => Yojson.Basic.from_string(x)
+
+let bar = (str, json) =>
+  switch {
+    open Yojson.Basic.Util
+    json |> member(str)
+  } {
+  | j => j
+  | exception Yojson.Basic.Util.Type_error("a", d) if d == json => json
+  }
+
+@raises(Yojson.Basic.Util.Type_error)
+let toString = x => Yojson.Basic.Util.to_string(x)
+
+@raises(Yojson.Basic.Util.Type_error)
+let toInt = x => Yojson.Basic.Util.to_int(x)
diff --git a/analysis/examples/larger-project/src/exception/Yojson.js b/analysis/examples/larger-project/src/exception/Yojson.js
new file mode 100644
index 0000000000..ce892f7943
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/Yojson.js
@@ -0,0 +1,51 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var Json_error = /* @__PURE__ */Caml_exceptions.create("Yojson.Json_error");
+
+function from_string(param) {
+  throw {
+        RE_EXN_ID: Json_error,
+        _1: "Basic.from_string",
+        Error: new Error()
+      };
+}
+
+var Type_error = /* @__PURE__ */Caml_exceptions.create("Yojson.Basic.Util.Type_error");
+
+function member(_s, j) {
+  throw {
+        RE_EXN_ID: Type_error,
+        _1: "Basic.Util.member",
+        _2: j,
+        Error: new Error()
+      };
+}
+
+function to_int(param) {
+  return 34;
+}
+
+function to_string(param) {
+  return "";
+}
+
+var Util = {
+  Type_error: Type_error,
+  member: member,
+  to_int: to_int,
+  to_string: to_string
+};
+
+var Basic = {
+  from_string: from_string,
+  Util: Util
+};
+
+export {
+  Json_error ,
+  Basic ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/exception/Yojson.res b/analysis/examples/larger-project/src/exception/Yojson.res
new file mode 100644
index 0000000000..40758bda19
--- /dev/null
+++ b/analysis/examples/larger-project/src/exception/Yojson.res
@@ -0,0 +1,19 @@
+exception Json_error(string)
+
+module Basic = {
+  type t
+
+  @raises(Json_error)
+  let from_string: string => t = _ => raise(Json_error("Basic.from_string"))
+
+  module Util = {
+    exception Type_error(string, t)
+
+    @raises(Type_error)
+    let member: (string, t) => t = (_s, j) => raise(Type_error("Basic.Util.member", j))
+
+    let to_int: t => int = _ => 34
+
+    let to_string: t => string = _ => ""
+  }
+}
diff --git a/analysis/examples/larger-project/src/format.js b/analysis/examples/larger-project/src/format.js
new file mode 100644
index 0000000000..2a8e65c13e
--- /dev/null
+++ b/analysis/examples/larger-project/src/format.js
@@ -0,0 +1,29 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+throw {
+      RE_EXN_ID: "Assert_failure",
+      _1: [
+        "format.res",
+        3,
+        20
+      ],
+      Error: new Error()
+    };
+
+export {
+  std_formatter ,
+  err_formatter ,
+  str_formatter ,
+  fprintf ,
+  sprintf ,
+  kasprintf ,
+  asprintf ,
+  kfprintf ,
+  set_mark_tags ,
+  formatter_of_buffer ,
+  pp_print_flush ,
+  pp_print_as ,
+  
+}
+/* std_formatter Not a pure module */
diff --git a/analysis/examples/larger-project/src/format.res b/analysis/examples/larger-project/src/format.res
new file mode 100644
index 0000000000..007ed334ce
--- /dev/null
+++ b/analysis/examples/larger-project/src/format.res
@@ -0,0 +1,23 @@
+type formatter
+
+let std_formatter = assert false
+let err_formatter = assert false
+let str_formatter = assert false
+
+let fprintf = _ => assert false
+
+let sprintf = _ => assert false
+
+let kasprintf = _ => assert false
+
+let asprintf = _ => assert false
+
+let kfprintf = _ => assert false
+
+let set_mark_tags = _ => assert false
+
+let formatter_of_buffer = _ => assert false
+
+let pp_print_flush = _ => assert false
+
+let pp_print_as = _ => assert false
\ No newline at end of file
diff --git a/analysis/examples/larger-project/src/identifiable.js b/analysis/examples/larger-project/src/identifiable.js
new file mode 100644
index 0000000000..0e5002b547
--- /dev/null
+++ b/analysis/examples/larger-project/src/identifiable.js
@@ -0,0 +1,1312 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as $$Map from "rescript/lib/es6/map.js";
+import * as $$Set from "rescript/lib/es6/set.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as Misc from "./misc.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Format from "./format.js";
+import * as Printf from "./printf.js";
+import * as Hashtbl from "rescript/lib/es6/hashtbl.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function Pair(A, B) {
+  var compare = function (param, param$1) {
+    var c = Curry._2(A.compare, param[0], param$1[0]);
+    if (c !== 0) {
+      return c;
+    } else {
+      return Curry._2(B.compare, param[1], param$1[1]);
+    }
+  };
+  var output = function (oc, param) {
+    return Curry._5(Printf.fprintf(oc), " (%a, %a)", A.output, param[0], B.output, param[1]);
+  };
+  var hash = function (param) {
+    return Hashtbl.hash([
+                Curry._1(A.hash, param[0]),
+                Curry._1(B.hash, param[1])
+              ]);
+  };
+  var equal = function (param, param$1) {
+    if (Curry._2(A.equal, param[0], param$1[0])) {
+      return Curry._2(B.equal, param[1], param$1[1]);
+    } else {
+      return false;
+    }
+  };
+  var print = function (ppf, param) {
+    return Curry._5(Format.fprintf(ppf), " (%a, @ %a)", A.print, param[0], B.print, param[1]);
+  };
+  return {
+          equal: equal,
+          hash: hash,
+          compare: compare,
+          output: output,
+          print: print
+        };
+}
+
+function Make_map(T) {
+  var include = $$Map.Make({
+        compare: T.compare
+      });
+  var empty = include.empty;
+  var add = include.add;
+  var merge = include.merge;
+  var union = include.union;
+  var iter = include.iter;
+  var fold = include.fold;
+  var bindings = include.bindings;
+  var find = include.find;
+  var filter_map = function (t, f) {
+    return Curry._3(fold, (function (id, v, map) {
+                  var r = Curry._2(f, id, v);
+                  if (r !== undefined) {
+                    return Curry._3(add, id, Caml_option.valFromOption(r), map);
+                  } else {
+                    return map;
+                  }
+                }), t, empty);
+  };
+  var of_list = function (l) {
+    return List.fold_left((function (map, param) {
+                  return Curry._3(add, param[0], param[1], map);
+                }), empty, l);
+  };
+  var disjoint_union = function (eq, print, m1, m2) {
+    return Curry._3(union, (function (id, v1, v2) {
+                  var ok = eq !== undefined ? Curry._2(eq, v1, v2) : false;
+                  if (ok) {
+                    return Caml_option.some(v1);
+                  }
+                  var tmp;
+                  if (print !== undefined) {
+                    var print$1 = Caml_option.valFromOption(print);
+                    tmp = Curry._6(Format.asprintf("Map.disjoint_union %a => %a <> %a"), T.print, id, print$1, v1, print$1, v2);
+                  } else {
+                    tmp = Curry._2(Format.asprintf("Map.disjoint_union %a"), T.print, id);
+                  }
+                  return Misc.fatal_error(tmp);
+                }), m1, m2);
+  };
+  var union_right = function (m1, m2) {
+    return Curry._3(merge, (function (_id, x, y) {
+                  if (x !== undefined) {
+                    if (y !== undefined) {
+                      return Caml_option.some(Caml_option.valFromOption(y));
+                    } else {
+                      return Caml_option.some(Caml_option.valFromOption(x));
+                    }
+                  } else if (y !== undefined) {
+                    return Caml_option.some(Caml_option.valFromOption(y));
+                  } else {
+                    return ;
+                  }
+                }), m1, m2);
+  };
+  var union_left = function (m1, m2) {
+    return union_right(m2, m1);
+  };
+  var union_merge = function (f, m1, m2) {
+    var aux = function (param, m1, m2) {
+      if (m1 !== undefined) {
+        if (m2 !== undefined) {
+          return Caml_option.some(Curry._2(f, Caml_option.valFromOption(m1), Caml_option.valFromOption(m2)));
+        } else {
+          return m1;
+        }
+      } else {
+        return m2;
+      }
+    };
+    return Curry._3(merge, aux, m1, m2);
+  };
+  var rename = function (m, v) {
+    try {
+      return Curry._2(find, v, m);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        return v;
+      }
+      throw exn;
+    }
+  };
+  var map_keys = function (f, m) {
+    return of_list(List.map((function (param) {
+                      return [
+                              Curry._1(f, param[0]),
+                              param[1]
+                            ];
+                    }), Curry._1(bindings, m)));
+  };
+  var print = function (f, ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter, (function (id, v) {
+                    return Curry._5(Format.fprintf(ppf), "@ (@[%a@ %a@])", T.print, id, f, v);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var T_set = $$Set.Make({
+        compare: T.compare
+      });
+  var keys = function (map) {
+    return Curry._3(fold, (function (k, param, set) {
+                  return Curry._2(T_set.add, k, set);
+                }), map, T_set.empty);
+  };
+  var data = function (t) {
+    return List.map((function (prim) {
+                  return prim[1];
+                }), Curry._1(bindings, t));
+  };
+  var of_set = function (f, set) {
+    return Curry._3(T_set.fold, (function (e, map) {
+                  return Curry._3(add, e, Curry._1(f, e), map);
+                }), set, empty);
+  };
+  var transpose_keys_and_data = function (map) {
+    return Curry._3(fold, (function (k, v, m) {
+                  return Curry._3(add, v, k, m);
+                }), map, empty);
+  };
+  var transpose_keys_and_data_set = function (map) {
+    return Curry._3(fold, (function (k, v, m) {
+                  var set;
+                  var exit = 0;
+                  var set$1;
+                  try {
+                    set$1 = Curry._2(find, v, m);
+                    exit = 1;
+                  }
+                  catch (raw_exn){
+                    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+                    if (exn.RE_EXN_ID === "Not_found") {
+                      set = Curry._1(T_set.singleton, k);
+                    } else {
+                      throw exn;
+                    }
+                  }
+                  if (exit === 1) {
+                    set = Curry._2(T_set.add, k, set$1);
+                  }
+                  return Curry._3(add, v, set, m);
+                }), map, empty);
+  };
+  return {
+          empty: empty,
+          is_empty: include.is_empty,
+          mem: include.mem,
+          add: add,
+          update: include.update,
+          singleton: include.singleton,
+          remove: include.remove,
+          merge: merge,
+          union: union,
+          compare: include.compare,
+          equal: include.equal,
+          iter: iter,
+          fold: fold,
+          for_all: include.for_all,
+          exists: include.exists,
+          filter: include.filter,
+          partition: include.partition,
+          cardinal: include.cardinal,
+          bindings: bindings,
+          min_binding: include.min_binding,
+          min_binding_opt: include.min_binding_opt,
+          max_binding: include.max_binding,
+          max_binding_opt: include.max_binding_opt,
+          choose: include.choose,
+          choose_opt: include.choose_opt,
+          split: include.split,
+          find: find,
+          find_opt: include.find_opt,
+          find_first: include.find_first,
+          find_first_opt: include.find_first_opt,
+          find_last: include.find_last,
+          find_last_opt: include.find_last_opt,
+          map: include.map,
+          mapi: include.mapi,
+          filter_map: filter_map,
+          of_list: of_list,
+          disjoint_union: disjoint_union,
+          union_right: union_right,
+          union_left: union_left,
+          union_merge: union_merge,
+          rename: rename,
+          map_keys: map_keys,
+          print: print,
+          T_set: T_set,
+          keys: keys,
+          data: data,
+          of_set: of_set,
+          transpose_keys_and_data: transpose_keys_and_data,
+          transpose_keys_and_data_set: transpose_keys_and_data_set
+        };
+}
+
+function Make_set(T) {
+  var include = $$Set.Make({
+        compare: T.compare
+      });
+  var empty = include.empty;
+  var add = include.add;
+  var singleton = include.singleton;
+  var iter = include.iter;
+  var elements = include.elements;
+  var output = function (oc, s) {
+    Curry._1(Printf.fprintf(oc), " ( ");
+    Curry._2(iter, (function (v) {
+            return Curry._3(Printf.fprintf(oc), "%a ", T.output, v);
+          }), s);
+    return Curry._1(Printf.fprintf(oc), ")");
+  };
+  var print = function (ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter, (function (e) {
+                    return Curry._3(Format.fprintf(ppf), "@ %a", T.print, e);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var to_string = function (s) {
+    return Curry._2(Format.asprintf("%a"), print, s);
+  };
+  var of_list = function (l) {
+    if (!l) {
+      return empty;
+    }
+    var q = l.tl;
+    var t = l.hd;
+    if (q) {
+      return List.fold_left((function (acc, e) {
+                    return Curry._2(add, e, acc);
+                  }), Curry._1(singleton, t), q);
+    } else {
+      return Curry._1(singleton, t);
+    }
+  };
+  var map = function (f, s) {
+    return of_list(List.map(f, Curry._1(elements, s)));
+  };
+  return {
+          empty: empty,
+          is_empty: include.is_empty,
+          mem: include.mem,
+          add: add,
+          singleton: singleton,
+          remove: include.remove,
+          union: include.union,
+          inter: include.inter,
+          diff: include.diff,
+          compare: include.compare,
+          equal: include.equal,
+          subset: include.subset,
+          iter: iter,
+          fold: include.fold,
+          for_all: include.for_all,
+          exists: include.exists,
+          filter: include.filter,
+          partition: include.partition,
+          cardinal: include.cardinal,
+          elements: elements,
+          min_elt: include.min_elt,
+          min_elt_opt: include.min_elt_opt,
+          max_elt: include.max_elt,
+          max_elt_opt: include.max_elt_opt,
+          choose: include.choose,
+          choose_opt: include.choose_opt,
+          split: include.split,
+          find: include.find,
+          find_opt: include.find_opt,
+          find_first: include.find_first,
+          find_first_opt: include.find_first_opt,
+          find_last: include.find_last,
+          find_last_opt: include.find_last_opt,
+          output: output,
+          print: print,
+          to_string: to_string,
+          of_list: of_list,
+          map: map
+        };
+}
+
+function Make_tbl(T) {
+  var include = Hashtbl.Make(T);
+  var create = include.create;
+  var add = include.add;
+  var find = include.find;
+  var fold = include.fold;
+  var include$1 = $$Map.Make({
+        compare: T.compare
+      });
+  var empty = include$1.empty;
+  var add$1 = include$1.add;
+  var merge = include$1.merge;
+  var union = include$1.union;
+  var iter = include$1.iter;
+  var fold$1 = include$1.fold;
+  var cardinal = include$1.cardinal;
+  var bindings = include$1.bindings;
+  var find$1 = include$1.find;
+  var map = include$1.map;
+  var filter_map = function (t, f) {
+    return Curry._3(fold$1, (function (id, v, map) {
+                  var r = Curry._2(f, id, v);
+                  if (r !== undefined) {
+                    return Curry._3(add$1, id, Caml_option.valFromOption(r), map);
+                  } else {
+                    return map;
+                  }
+                }), t, empty);
+  };
+  var of_list = function (l) {
+    return List.fold_left((function (map, param) {
+                  return Curry._3(add$1, param[0], param[1], map);
+                }), empty, l);
+  };
+  var disjoint_union = function (eq, print, m1, m2) {
+    return Curry._3(union, (function (id, v1, v2) {
+                  var ok = eq !== undefined ? Curry._2(eq, v1, v2) : false;
+                  if (ok) {
+                    return Caml_option.some(v1);
+                  }
+                  var tmp;
+                  if (print !== undefined) {
+                    var print$1 = Caml_option.valFromOption(print);
+                    tmp = Curry._6(Format.asprintf("Map.disjoint_union %a => %a <> %a"), T.print, id, print$1, v1, print$1, v2);
+                  } else {
+                    tmp = Curry._2(Format.asprintf("Map.disjoint_union %a"), T.print, id);
+                  }
+                  return Misc.fatal_error(tmp);
+                }), m1, m2);
+  };
+  var union_right = function (m1, m2) {
+    return Curry._3(merge, (function (_id, x, y) {
+                  if (x !== undefined) {
+                    if (y !== undefined) {
+                      return Caml_option.some(Caml_option.valFromOption(y));
+                    } else {
+                      return Caml_option.some(Caml_option.valFromOption(x));
+                    }
+                  } else if (y !== undefined) {
+                    return Caml_option.some(Caml_option.valFromOption(y));
+                  } else {
+                    return ;
+                  }
+                }), m1, m2);
+  };
+  var union_left = function (m1, m2) {
+    return union_right(m2, m1);
+  };
+  var union_merge = function (f, m1, m2) {
+    var aux = function (param, m1, m2) {
+      if (m1 !== undefined) {
+        if (m2 !== undefined) {
+          return Caml_option.some(Curry._2(f, Caml_option.valFromOption(m1), Caml_option.valFromOption(m2)));
+        } else {
+          return m1;
+        }
+      } else {
+        return m2;
+      }
+    };
+    return Curry._3(merge, aux, m1, m2);
+  };
+  var rename = function (m, v) {
+    try {
+      return Curry._2(find$1, v, m);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        return v;
+      }
+      throw exn;
+    }
+  };
+  var map_keys = function (f, m) {
+    return of_list(List.map((function (param) {
+                      return [
+                              Curry._1(f, param[0]),
+                              param[1]
+                            ];
+                    }), Curry._1(bindings, m)));
+  };
+  var print = function (f, ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter, (function (id, v) {
+                    return Curry._5(Format.fprintf(ppf), "@ (@[%a@ %a@])", T.print, id, f, v);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var T_set = $$Set.Make({
+        compare: T.compare
+      });
+  var keys = function (map) {
+    return Curry._3(fold$1, (function (k, param, set) {
+                  return Curry._2(T_set.add, k, set);
+                }), map, T_set.empty);
+  };
+  var data = function (t) {
+    return List.map((function (prim) {
+                  return prim[1];
+                }), Curry._1(bindings, t));
+  };
+  var of_set = function (f, set) {
+    return Curry._3(T_set.fold, (function (e, map) {
+                  return Curry._3(add$1, e, Curry._1(f, e), map);
+                }), set, empty);
+  };
+  var transpose_keys_and_data = function (map) {
+    return Curry._3(fold$1, (function (k, v, m) {
+                  return Curry._3(add$1, v, k, m);
+                }), map, empty);
+  };
+  var transpose_keys_and_data_set = function (map) {
+    return Curry._3(fold$1, (function (k, v, m) {
+                  var set;
+                  var exit = 0;
+                  var set$1;
+                  try {
+                    set$1 = Curry._2(find$1, v, m);
+                    exit = 1;
+                  }
+                  catch (raw_exn){
+                    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+                    if (exn.RE_EXN_ID === "Not_found") {
+                      set = Curry._1(T_set.singleton, k);
+                    } else {
+                      throw exn;
+                    }
+                  }
+                  if (exit === 1) {
+                    set = Curry._2(T_set.add, k, set$1);
+                  }
+                  return Curry._3(add$1, v, set, m);
+                }), map, empty);
+  };
+  var T_map_is_empty = include$1.is_empty;
+  var T_map_mem = include$1.mem;
+  var T_map_update = include$1.update;
+  var T_map_singleton = include$1.singleton;
+  var T_map_remove = include$1.remove;
+  var T_map_compare = include$1.compare;
+  var T_map_equal = include$1.equal;
+  var T_map_for_all = include$1.for_all;
+  var T_map_exists = include$1.exists;
+  var T_map_filter = include$1.filter;
+  var T_map_partition = include$1.partition;
+  var T_map_min_binding = include$1.min_binding;
+  var T_map_min_binding_opt = include$1.min_binding_opt;
+  var T_map_max_binding = include$1.max_binding;
+  var T_map_max_binding_opt = include$1.max_binding_opt;
+  var T_map_choose = include$1.choose;
+  var T_map_choose_opt = include$1.choose_opt;
+  var T_map_split = include$1.split;
+  var T_map_find_opt = include$1.find_opt;
+  var T_map_find_first = include$1.find_first;
+  var T_map_find_first_opt = include$1.find_first_opt;
+  var T_map_find_last = include$1.find_last;
+  var T_map_find_last_opt = include$1.find_last_opt;
+  var T_map_mapi = include$1.mapi;
+  var T_map = {
+    empty: empty,
+    is_empty: T_map_is_empty,
+    mem: T_map_mem,
+    add: add$1,
+    update: T_map_update,
+    singleton: T_map_singleton,
+    remove: T_map_remove,
+    merge: merge,
+    union: union,
+    compare: T_map_compare,
+    equal: T_map_equal,
+    iter: iter,
+    fold: fold$1,
+    for_all: T_map_for_all,
+    exists: T_map_exists,
+    filter: T_map_filter,
+    partition: T_map_partition,
+    cardinal: cardinal,
+    bindings: bindings,
+    min_binding: T_map_min_binding,
+    min_binding_opt: T_map_min_binding_opt,
+    max_binding: T_map_max_binding,
+    max_binding_opt: T_map_max_binding_opt,
+    choose: T_map_choose,
+    choose_opt: T_map_choose_opt,
+    split: T_map_split,
+    find: find$1,
+    find_opt: T_map_find_opt,
+    find_first: T_map_find_first,
+    find_first_opt: T_map_find_first_opt,
+    find_last: T_map_find_last,
+    find_last_opt: T_map_find_last_opt,
+    map: map,
+    mapi: T_map_mapi,
+    filter_map: filter_map,
+    of_list: of_list,
+    disjoint_union: disjoint_union,
+    union_right: union_right,
+    union_left: union_left,
+    union_merge: union_merge,
+    rename: rename,
+    map_keys: map_keys,
+    print: print,
+    T_set: T_set,
+    keys: keys,
+    data: data,
+    of_set: of_set,
+    transpose_keys_and_data: transpose_keys_and_data,
+    transpose_keys_and_data_set: transpose_keys_and_data_set
+  };
+  var to_list = function (t) {
+    return Curry._3(fold, (function (key, datum, elts) {
+                  return {
+                          hd: [
+                            key,
+                            datum
+                          ],
+                          tl: elts
+                        };
+                }), t, /* [] */0);
+  };
+  var of_list$1 = function (elts) {
+    var t = Curry._1(create, 42);
+    List.iter((function (param) {
+            return Curry._3(add, t, param[0], param[1]);
+          }), elts);
+    return t;
+  };
+  var to_map = function (v) {
+    return Curry._3(fold, add$1, v, empty);
+  };
+  var of_map = function (m) {
+    var t = Curry._1(create, Curry._1(cardinal, m));
+    Curry._2(iter, (function (k, v) {
+            return Curry._3(add, t, k, v);
+          }), m);
+    return t;
+  };
+  var memoize = function (t, f, key) {
+    try {
+      return Curry._2(find, t, key);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        var r = Curry._1(f, key);
+        Curry._3(add, t, key, r);
+        return r;
+      }
+      throw exn;
+    }
+  };
+  var map$1 = function (t, f) {
+    return of_map(Curry._2(map, f, Curry._3(fold, add$1, t, empty)));
+  };
+  return {
+          create: create,
+          clear: include.clear,
+          reset: include.reset,
+          copy: include.copy,
+          add: add,
+          remove: include.remove,
+          find: find,
+          find_opt: include.find_opt,
+          find_all: include.find_all,
+          replace: include.replace,
+          mem: include.mem,
+          iter: include.iter,
+          filter_map_inplace: include.filter_map_inplace,
+          fold: fold,
+          length: include.length,
+          stats: include.stats,
+          T_map: T_map,
+          to_list: to_list,
+          of_list: of_list$1,
+          to_map: to_map,
+          of_map: of_map,
+          memoize: memoize,
+          map: map$1
+        };
+}
+
+function Make(T) {
+  var include = $$Set.Make({
+        compare: T.compare
+      });
+  var empty = include.empty;
+  var add = include.add;
+  var singleton = include.singleton;
+  var iter = include.iter;
+  var elements = include.elements;
+  var output = function (oc, s) {
+    Curry._1(Printf.fprintf(oc), " ( ");
+    Curry._2(iter, (function (v) {
+            return Curry._3(Printf.fprintf(oc), "%a ", T.output, v);
+          }), s);
+    return Curry._1(Printf.fprintf(oc), ")");
+  };
+  var print = function (ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter, (function (e) {
+                    return Curry._3(Format.fprintf(ppf), "@ %a", T.print, e);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var to_string = function (s) {
+    return Curry._2(Format.asprintf("%a"), print, s);
+  };
+  var of_list = function (l) {
+    if (!l) {
+      return empty;
+    }
+    var q = l.tl;
+    var t = l.hd;
+    if (q) {
+      return List.fold_left((function (acc, e) {
+                    return Curry._2(add, e, acc);
+                  }), Curry._1(singleton, t), q);
+    } else {
+      return Curry._1(singleton, t);
+    }
+  };
+  var map = function (f, s) {
+    return of_list(List.map(f, Curry._1(elements, s)));
+  };
+  var Set_is_empty = include.is_empty;
+  var Set_mem = include.mem;
+  var Set_remove = include.remove;
+  var Set_union = include.union;
+  var Set_inter = include.inter;
+  var Set_diff = include.diff;
+  var Set_compare = include.compare;
+  var Set_equal = include.equal;
+  var Set_subset = include.subset;
+  var Set_fold = include.fold;
+  var Set_for_all = include.for_all;
+  var Set_exists = include.exists;
+  var Set_filter = include.filter;
+  var Set_partition = include.partition;
+  var Set_cardinal = include.cardinal;
+  var Set_min_elt = include.min_elt;
+  var Set_min_elt_opt = include.min_elt_opt;
+  var Set_max_elt = include.max_elt;
+  var Set_max_elt_opt = include.max_elt_opt;
+  var Set_choose = include.choose;
+  var Set_choose_opt = include.choose_opt;
+  var Set_split = include.split;
+  var Set_find = include.find;
+  var Set_find_opt = include.find_opt;
+  var Set_find_first = include.find_first;
+  var Set_find_first_opt = include.find_first_opt;
+  var Set_find_last = include.find_last;
+  var Set_find_last_opt = include.find_last_opt;
+  var $$Set$1 = {
+    empty: empty,
+    is_empty: Set_is_empty,
+    mem: Set_mem,
+    add: add,
+    singleton: singleton,
+    remove: Set_remove,
+    union: Set_union,
+    inter: Set_inter,
+    diff: Set_diff,
+    compare: Set_compare,
+    equal: Set_equal,
+    subset: Set_subset,
+    iter: iter,
+    fold: Set_fold,
+    for_all: Set_for_all,
+    exists: Set_exists,
+    filter: Set_filter,
+    partition: Set_partition,
+    cardinal: Set_cardinal,
+    elements: elements,
+    min_elt: Set_min_elt,
+    min_elt_opt: Set_min_elt_opt,
+    max_elt: Set_max_elt,
+    max_elt_opt: Set_max_elt_opt,
+    choose: Set_choose,
+    choose_opt: Set_choose_opt,
+    split: Set_split,
+    find: Set_find,
+    find_opt: Set_find_opt,
+    find_first: Set_find_first,
+    find_first_opt: Set_find_first_opt,
+    find_last: Set_find_last,
+    find_last_opt: Set_find_last_opt,
+    output: output,
+    print: print,
+    to_string: to_string,
+    of_list: of_list,
+    map: map
+  };
+  var include$1 = $$Map.Make({
+        compare: T.compare
+      });
+  var empty$1 = include$1.empty;
+  var add$1 = include$1.add;
+  var merge = include$1.merge;
+  var union = include$1.union;
+  var iter$1 = include$1.iter;
+  var fold = include$1.fold;
+  var bindings = include$1.bindings;
+  var find = include$1.find;
+  var filter_map = function (t, f) {
+    return Curry._3(fold, (function (id, v, map) {
+                  var r = Curry._2(f, id, v);
+                  if (r !== undefined) {
+                    return Curry._3(add$1, id, Caml_option.valFromOption(r), map);
+                  } else {
+                    return map;
+                  }
+                }), t, empty$1);
+  };
+  var of_list$1 = function (l) {
+    return List.fold_left((function (map, param) {
+                  return Curry._3(add$1, param[0], param[1], map);
+                }), empty$1, l);
+  };
+  var disjoint_union = function (eq, print, m1, m2) {
+    return Curry._3(union, (function (id, v1, v2) {
+                  var ok = eq !== undefined ? Curry._2(eq, v1, v2) : false;
+                  if (ok) {
+                    return Caml_option.some(v1);
+                  }
+                  var tmp;
+                  if (print !== undefined) {
+                    var print$1 = Caml_option.valFromOption(print);
+                    tmp = Curry._6(Format.asprintf("Map.disjoint_union %a => %a <> %a"), T.print, id, print$1, v1, print$1, v2);
+                  } else {
+                    tmp = Curry._2(Format.asprintf("Map.disjoint_union %a"), T.print, id);
+                  }
+                  return Misc.fatal_error(tmp);
+                }), m1, m2);
+  };
+  var union_right = function (m1, m2) {
+    return Curry._3(merge, (function (_id, x, y) {
+                  if (x !== undefined) {
+                    if (y !== undefined) {
+                      return Caml_option.some(Caml_option.valFromOption(y));
+                    } else {
+                      return Caml_option.some(Caml_option.valFromOption(x));
+                    }
+                  } else if (y !== undefined) {
+                    return Caml_option.some(Caml_option.valFromOption(y));
+                  } else {
+                    return ;
+                  }
+                }), m1, m2);
+  };
+  var union_left = function (m1, m2) {
+    return union_right(m2, m1);
+  };
+  var union_merge = function (f, m1, m2) {
+    var aux = function (param, m1, m2) {
+      if (m1 !== undefined) {
+        if (m2 !== undefined) {
+          return Caml_option.some(Curry._2(f, Caml_option.valFromOption(m1), Caml_option.valFromOption(m2)));
+        } else {
+          return m1;
+        }
+      } else {
+        return m2;
+      }
+    };
+    return Curry._3(merge, aux, m1, m2);
+  };
+  var rename = function (m, v) {
+    try {
+      return Curry._2(find, v, m);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        return v;
+      }
+      throw exn;
+    }
+  };
+  var map_keys = function (f, m) {
+    return of_list$1(List.map((function (param) {
+                      return [
+                              Curry._1(f, param[0]),
+                              param[1]
+                            ];
+                    }), Curry._1(bindings, m)));
+  };
+  var print$1 = function (f, ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter$1, (function (id, v) {
+                    return Curry._5(Format.fprintf(ppf), "@ (@[%a@ %a@])", T.print, id, f, v);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var T_set = $$Set.Make({
+        compare: T.compare
+      });
+  var keys = function (map) {
+    return Curry._3(fold, (function (k, param, set) {
+                  return Curry._2(T_set.add, k, set);
+                }), map, T_set.empty);
+  };
+  var data = function (t) {
+    return List.map((function (prim) {
+                  return prim[1];
+                }), Curry._1(bindings, t));
+  };
+  var of_set = function (f, set) {
+    return Curry._3(T_set.fold, (function (e, map) {
+                  return Curry._3(add$1, e, Curry._1(f, e), map);
+                }), set, empty$1);
+  };
+  var transpose_keys_and_data = function (map) {
+    return Curry._3(fold, (function (k, v, m) {
+                  return Curry._3(add$1, v, k, m);
+                }), map, empty$1);
+  };
+  var transpose_keys_and_data_set = function (map) {
+    return Curry._3(fold, (function (k, v, m) {
+                  var set;
+                  var exit = 0;
+                  var set$1;
+                  try {
+                    set$1 = Curry._2(find, v, m);
+                    exit = 1;
+                  }
+                  catch (raw_exn){
+                    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+                    if (exn.RE_EXN_ID === "Not_found") {
+                      set = Curry._1(T_set.singleton, k);
+                    } else {
+                      throw exn;
+                    }
+                  }
+                  if (exit === 1) {
+                    set = Curry._2(T_set.add, k, set$1);
+                  }
+                  return Curry._3(add$1, v, set, m);
+                }), map, empty$1);
+  };
+  var Map_is_empty = include$1.is_empty;
+  var Map_mem = include$1.mem;
+  var Map_update = include$1.update;
+  var Map_singleton = include$1.singleton;
+  var Map_remove = include$1.remove;
+  var Map_compare = include$1.compare;
+  var Map_equal = include$1.equal;
+  var Map_for_all = include$1.for_all;
+  var Map_exists = include$1.exists;
+  var Map_filter = include$1.filter;
+  var Map_partition = include$1.partition;
+  var Map_cardinal = include$1.cardinal;
+  var Map_min_binding = include$1.min_binding;
+  var Map_min_binding_opt = include$1.min_binding_opt;
+  var Map_max_binding = include$1.max_binding;
+  var Map_max_binding_opt = include$1.max_binding_opt;
+  var Map_choose = include$1.choose;
+  var Map_choose_opt = include$1.choose_opt;
+  var Map_split = include$1.split;
+  var Map_find_opt = include$1.find_opt;
+  var Map_find_first = include$1.find_first;
+  var Map_find_first_opt = include$1.find_first_opt;
+  var Map_find_last = include$1.find_last;
+  var Map_find_last_opt = include$1.find_last_opt;
+  var Map_map = include$1.map;
+  var Map_mapi = include$1.mapi;
+  var $$Map$1 = {
+    empty: empty$1,
+    is_empty: Map_is_empty,
+    mem: Map_mem,
+    add: add$1,
+    update: Map_update,
+    singleton: Map_singleton,
+    remove: Map_remove,
+    merge: merge,
+    union: union,
+    compare: Map_compare,
+    equal: Map_equal,
+    iter: iter$1,
+    fold: fold,
+    for_all: Map_for_all,
+    exists: Map_exists,
+    filter: Map_filter,
+    partition: Map_partition,
+    cardinal: Map_cardinal,
+    bindings: bindings,
+    min_binding: Map_min_binding,
+    min_binding_opt: Map_min_binding_opt,
+    max_binding: Map_max_binding,
+    max_binding_opt: Map_max_binding_opt,
+    choose: Map_choose,
+    choose_opt: Map_choose_opt,
+    split: Map_split,
+    find: find,
+    find_opt: Map_find_opt,
+    find_first: Map_find_first,
+    find_first_opt: Map_find_first_opt,
+    find_last: Map_find_last,
+    find_last_opt: Map_find_last_opt,
+    map: Map_map,
+    mapi: Map_mapi,
+    filter_map: filter_map,
+    of_list: of_list$1,
+    disjoint_union: disjoint_union,
+    union_right: union_right,
+    union_left: union_left,
+    union_merge: union_merge,
+    rename: rename,
+    map_keys: map_keys,
+    print: print$1,
+    T_set: T_set,
+    keys: keys,
+    data: data,
+    of_set: of_set,
+    transpose_keys_and_data: transpose_keys_and_data,
+    transpose_keys_and_data_set: transpose_keys_and_data_set
+  };
+  var include$2 = Hashtbl.Make(T);
+  var create = include$2.create;
+  var add$2 = include$2.add;
+  var find$1 = include$2.find;
+  var fold$1 = include$2.fold;
+  var include$3 = $$Map.Make({
+        compare: T.compare
+      });
+  var empty$2 = include$3.empty;
+  var add$3 = include$3.add;
+  var merge$1 = include$3.merge;
+  var union$1 = include$3.union;
+  var iter$2 = include$3.iter;
+  var fold$2 = include$3.fold;
+  var cardinal = include$3.cardinal;
+  var bindings$1 = include$3.bindings;
+  var find$2 = include$3.find;
+  var map$1 = include$3.map;
+  var filter_map$1 = function (t, f) {
+    return Curry._3(fold$2, (function (id, v, map) {
+                  var r = Curry._2(f, id, v);
+                  if (r !== undefined) {
+                    return Curry._3(add$3, id, Caml_option.valFromOption(r), map);
+                  } else {
+                    return map;
+                  }
+                }), t, empty$2);
+  };
+  var of_list$2 = function (l) {
+    return List.fold_left((function (map, param) {
+                  return Curry._3(add$3, param[0], param[1], map);
+                }), empty$2, l);
+  };
+  var disjoint_union$1 = function (eq, print, m1, m2) {
+    return Curry._3(union$1, (function (id, v1, v2) {
+                  var ok = eq !== undefined ? Curry._2(eq, v1, v2) : false;
+                  if (ok) {
+                    return Caml_option.some(v1);
+                  }
+                  var tmp;
+                  if (print !== undefined) {
+                    var print$1 = Caml_option.valFromOption(print);
+                    tmp = Curry._6(Format.asprintf("Map.disjoint_union %a => %a <> %a"), T.print, id, print$1, v1, print$1, v2);
+                  } else {
+                    tmp = Curry._2(Format.asprintf("Map.disjoint_union %a"), T.print, id);
+                  }
+                  return Misc.fatal_error(tmp);
+                }), m1, m2);
+  };
+  var union_right$1 = function (m1, m2) {
+    return Curry._3(merge$1, (function (_id, x, y) {
+                  if (x !== undefined) {
+                    if (y !== undefined) {
+                      return Caml_option.some(Caml_option.valFromOption(y));
+                    } else {
+                      return Caml_option.some(Caml_option.valFromOption(x));
+                    }
+                  } else if (y !== undefined) {
+                    return Caml_option.some(Caml_option.valFromOption(y));
+                  } else {
+                    return ;
+                  }
+                }), m1, m2);
+  };
+  var union_left$1 = function (m1, m2) {
+    return union_right$1(m2, m1);
+  };
+  var union_merge$1 = function (f, m1, m2) {
+    var aux = function (param, m1, m2) {
+      if (m1 !== undefined) {
+        if (m2 !== undefined) {
+          return Caml_option.some(Curry._2(f, Caml_option.valFromOption(m1), Caml_option.valFromOption(m2)));
+        } else {
+          return m1;
+        }
+      } else {
+        return m2;
+      }
+    };
+    return Curry._3(merge$1, aux, m1, m2);
+  };
+  var rename$1 = function (m, v) {
+    try {
+      return Curry._2(find$2, v, m);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        return v;
+      }
+      throw exn;
+    }
+  };
+  var map_keys$1 = function (f, m) {
+    return of_list$2(List.map((function (param) {
+                      return [
+                              Curry._1(f, param[0]),
+                              param[1]
+                            ];
+                    }), Curry._1(bindings$1, m)));
+  };
+  var print$2 = function (f, ppf, s) {
+    var elts = function (ppf, s) {
+      return Curry._2(iter$2, (function (id, v) {
+                    return Curry._5(Format.fprintf(ppf), "@ (@[%a@ %a@])", T.print, id, f, v);
+                  }), s);
+    };
+    return Curry._3(Format.fprintf(ppf), "@[<1>{@[%a@ @]}@]", elts, s);
+  };
+  var T_set$1 = $$Set.Make({
+        compare: T.compare
+      });
+  var keys$1 = function (map) {
+    return Curry._3(fold$2, (function (k, param, set) {
+                  return Curry._2(T_set$1.add, k, set);
+                }), map, T_set$1.empty);
+  };
+  var data$1 = function (t) {
+    return List.map((function (prim) {
+                  return prim[1];
+                }), Curry._1(bindings$1, t));
+  };
+  var of_set$1 = function (f, set) {
+    return Curry._3(T_set$1.fold, (function (e, map) {
+                  return Curry._3(add$3, e, Curry._1(f, e), map);
+                }), set, empty$2);
+  };
+  var transpose_keys_and_data$1 = function (map) {
+    return Curry._3(fold$2, (function (k, v, m) {
+                  return Curry._3(add$3, v, k, m);
+                }), map, empty$2);
+  };
+  var transpose_keys_and_data_set$1 = function (map) {
+    return Curry._3(fold$2, (function (k, v, m) {
+                  var set;
+                  var exit = 0;
+                  var set$1;
+                  try {
+                    set$1 = Curry._2(find$2, v, m);
+                    exit = 1;
+                  }
+                  catch (raw_exn){
+                    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+                    if (exn.RE_EXN_ID === "Not_found") {
+                      set = Curry._1(T_set$1.singleton, k);
+                    } else {
+                      throw exn;
+                    }
+                  }
+                  if (exit === 1) {
+                    set = Curry._2(T_set$1.add, k, set$1);
+                  }
+                  return Curry._3(add$3, v, set, m);
+                }), map, empty$2);
+  };
+  var T_map_is_empty = include$3.is_empty;
+  var T_map_mem = include$3.mem;
+  var T_map_update = include$3.update;
+  var T_map_singleton = include$3.singleton;
+  var T_map_remove = include$3.remove;
+  var T_map_compare = include$3.compare;
+  var T_map_equal = include$3.equal;
+  var T_map_for_all = include$3.for_all;
+  var T_map_exists = include$3.exists;
+  var T_map_filter = include$3.filter;
+  var T_map_partition = include$3.partition;
+  var T_map_min_binding = include$3.min_binding;
+  var T_map_min_binding_opt = include$3.min_binding_opt;
+  var T_map_max_binding = include$3.max_binding;
+  var T_map_max_binding_opt = include$3.max_binding_opt;
+  var T_map_choose = include$3.choose;
+  var T_map_choose_opt = include$3.choose_opt;
+  var T_map_split = include$3.split;
+  var T_map_find_opt = include$3.find_opt;
+  var T_map_find_first = include$3.find_first;
+  var T_map_find_first_opt = include$3.find_first_opt;
+  var T_map_find_last = include$3.find_last;
+  var T_map_find_last_opt = include$3.find_last_opt;
+  var T_map_mapi = include$3.mapi;
+  var T_map = {
+    empty: empty$2,
+    is_empty: T_map_is_empty,
+    mem: T_map_mem,
+    add: add$3,
+    update: T_map_update,
+    singleton: T_map_singleton,
+    remove: T_map_remove,
+    merge: merge$1,
+    union: union$1,
+    compare: T_map_compare,
+    equal: T_map_equal,
+    iter: iter$2,
+    fold: fold$2,
+    for_all: T_map_for_all,
+    exists: T_map_exists,
+    filter: T_map_filter,
+    partition: T_map_partition,
+    cardinal: cardinal,
+    bindings: bindings$1,
+    min_binding: T_map_min_binding,
+    min_binding_opt: T_map_min_binding_opt,
+    max_binding: T_map_max_binding,
+    max_binding_opt: T_map_max_binding_opt,
+    choose: T_map_choose,
+    choose_opt: T_map_choose_opt,
+    split: T_map_split,
+    find: find$2,
+    find_opt: T_map_find_opt,
+    find_first: T_map_find_first,
+    find_first_opt: T_map_find_first_opt,
+    find_last: T_map_find_last,
+    find_last_opt: T_map_find_last_opt,
+    map: map$1,
+    mapi: T_map_mapi,
+    filter_map: filter_map$1,
+    of_list: of_list$2,
+    disjoint_union: disjoint_union$1,
+    union_right: union_right$1,
+    union_left: union_left$1,
+    union_merge: union_merge$1,
+    rename: rename$1,
+    map_keys: map_keys$1,
+    print: print$2,
+    T_set: T_set$1,
+    keys: keys$1,
+    data: data$1,
+    of_set: of_set$1,
+    transpose_keys_and_data: transpose_keys_and_data$1,
+    transpose_keys_and_data_set: transpose_keys_and_data_set$1
+  };
+  var to_list = function (t) {
+    return Curry._3(fold$1, (function (key, datum, elts) {
+                  return {
+                          hd: [
+                            key,
+                            datum
+                          ],
+                          tl: elts
+                        };
+                }), t, /* [] */0);
+  };
+  var of_list$3 = function (elts) {
+    var t = Curry._1(create, 42);
+    List.iter((function (param) {
+            return Curry._3(add$2, t, param[0], param[1]);
+          }), elts);
+    return t;
+  };
+  var to_map = function (v) {
+    return Curry._3(fold$1, add$3, v, empty$2);
+  };
+  var of_map = function (m) {
+    var t = Curry._1(create, Curry._1(cardinal, m));
+    Curry._2(iter$2, (function (k, v) {
+            return Curry._3(add$2, t, k, v);
+          }), m);
+    return t;
+  };
+  var memoize = function (t, f, key) {
+    try {
+      return Curry._2(find$1, t, key);
+    }
+    catch (raw_exn){
+      var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+      if (exn.RE_EXN_ID === "Not_found") {
+        var r = Curry._1(f, key);
+        Curry._3(add$2, t, key, r);
+        return r;
+      }
+      throw exn;
+    }
+  };
+  var map$2 = function (t, f) {
+    return of_map(Curry._2(map$1, f, Curry._3(fold$1, add$3, t, empty$2)));
+  };
+  var Tbl_clear = include$2.clear;
+  var Tbl_reset = include$2.reset;
+  var Tbl_copy = include$2.copy;
+  var Tbl_remove = include$2.remove;
+  var Tbl_find_opt = include$2.find_opt;
+  var Tbl_find_all = include$2.find_all;
+  var Tbl_replace = include$2.replace;
+  var Tbl_mem = include$2.mem;
+  var Tbl_iter = include$2.iter;
+  var Tbl_filter_map_inplace = include$2.filter_map_inplace;
+  var Tbl_length = include$2.length;
+  var Tbl_stats = include$2.stats;
+  var Tbl = {
+    create: create,
+    clear: Tbl_clear,
+    reset: Tbl_reset,
+    copy: Tbl_copy,
+    add: add$2,
+    remove: Tbl_remove,
+    find: find$1,
+    find_opt: Tbl_find_opt,
+    find_all: Tbl_find_all,
+    replace: Tbl_replace,
+    mem: Tbl_mem,
+    iter: Tbl_iter,
+    filter_map_inplace: Tbl_filter_map_inplace,
+    fold: fold$1,
+    length: Tbl_length,
+    stats: Tbl_stats,
+    T_map: T_map,
+    to_list: to_list,
+    of_list: of_list$3,
+    to_map: to_map,
+    of_map: of_map,
+    memoize: memoize,
+    map: map$2
+  };
+  return {
+          T: T,
+          equal: T.equal,
+          hash: T.hash,
+          compare: T.compare,
+          output: T.output,
+          print: T.print,
+          $$Set: $$Set$1,
+          $$Map: $$Map$1,
+          Tbl: Tbl
+        };
+}
+
+export {
+  Pair ,
+  Make_map ,
+  Make_set ,
+  Make_tbl ,
+  Make ,
+  
+}
+/* Misc Not a pure module */
diff --git a/analysis/examples/larger-project/src/location.js b/analysis/examples/larger-project/src/location.js
new file mode 100644
index 0000000000..4580019a27
--- /dev/null
+++ b/analysis/examples/larger-project/src/location.js
@@ -0,0 +1,369 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as P from "./P.js";
+import * as Misc from "./misc.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Buffer from "rescript/lib/es6/buffer.js";
+import * as Format from "./format.js";
+import * as Parsing from "rescript/lib/es6/parsing.js";
+import * as Caml_sys from "rescript/lib/es6/caml_sys.js";
+import * as Filename from "rescript/lib/es6/filename.js";
+import * as Printexc from "rescript/lib/es6/printexc.js";
+import * as Warnings from "./warnings.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var absname = {
+  contents: false
+};
+
+function in_file(name) {
+  var loc = {
+    pos_fname: name,
+    pos_lnum: 1,
+    pos_bol: 0,
+    pos_cnum: -1
+  };
+  return {
+          loc_start: loc,
+          loc_end: loc,
+          loc_ghost: true
+        };
+}
+
+var none = in_file("_none_");
+
+function curr(lexbuf) {
+  return {
+          loc_start: lexbuf.lex_start_p,
+          loc_end: lexbuf.lex_curr_p,
+          loc_ghost: false
+        };
+}
+
+function init(lexbuf, fname) {
+  lexbuf.lex_curr_p = {
+    pos_fname: fname,
+    pos_lnum: 1,
+    pos_bol: 0,
+    pos_cnum: 0
+  };
+  
+}
+
+function symbol_rloc(param) {
+  return {
+          loc_start: Parsing.symbol_start_pos(undefined),
+          loc_end: Parsing.symbol_end_pos(undefined),
+          loc_ghost: false
+        };
+}
+
+function symbol_gloc(param) {
+  return {
+          loc_start: Parsing.symbol_start_pos(undefined),
+          loc_end: Parsing.symbol_end_pos(undefined),
+          loc_ghost: true
+        };
+}
+
+function rhs_loc(n) {
+  return {
+          loc_start: Parsing.rhs_start_pos(n),
+          loc_end: Parsing.rhs_end_pos(n),
+          loc_ghost: false
+        };
+}
+
+var input_name = {
+  contents: "_none_"
+};
+
+var input_lexbuf = {
+  contents: undefined
+};
+
+function set_input_name(name) {
+  if (name !== "") {
+    input_name.contents = name;
+    return ;
+  }
+  
+}
+
+var num_loc_lines = {
+  contents: 0
+};
+
+function absolute_path(s) {
+  var s$1 = Curry._1(Filename.is_relative, s) ? Filename.concat(Caml_sys.caml_sys_getcwd(undefined), s) : s;
+  var aux = function (_s) {
+    while(true) {
+      var s = _s;
+      var base = Curry._1(Filename.basename, s);
+      var dir = Curry._1(Filename.dirname, s);
+      if (dir === s) {
+        return dir;
+      }
+      if (base !== Filename.current_dir_name) {
+        if (base === Filename.parent_dir_name) {
+          return Curry._1(Filename.dirname, aux(dir));
+        } else {
+          return Filename.concat(aux(dir), base);
+        }
+      }
+      _s = dir;
+      continue ;
+    };
+  };
+  return aux(s$1);
+}
+
+function show_filename(file) {
+  var file$1 = file === "_none_" ? input_name.contents : file;
+  if (absname.contents) {
+    return absolute_path(file$1);
+  } else {
+    return file$1;
+  }
+}
+
+function print_filename(ppf, file) {
+  return Curry._2(Format.fprintf(ppf), "%s", show_filename(file));
+}
+
+function reset(param) {
+  num_loc_lines.contents = 0;
+  
+}
+
+function get_pos_info(pos) {
+  return [
+          pos.pos_fname,
+          pos.pos_lnum,
+          pos.pos_cnum - pos.pos_bol | 0
+        ];
+}
+
+var error_prefix = "Error";
+
+function print_compact(ppf, loc) {
+  var match = get_pos_info(loc.loc_start);
+  var startchar = match[2];
+  var endchar = (loc.loc_end.pos_cnum - loc.loc_start.pos_cnum | 0) + startchar | 0;
+  Curry._4(Format.fprintf(ppf), "%a:%i", print_filename, match[0], match[1]);
+  if (startchar >= 0) {
+    return Curry._3(Format.fprintf(ppf), ",%i--%i", startchar, endchar);
+  }
+  
+}
+
+function echo_eof(param) {
+  Pervasives.print_newline(undefined);
+  num_loc_lines.contents = num_loc_lines.contents + 1 | 0;
+  
+}
+
+function mkloc(txt, loc) {
+  return {
+          txt: txt,
+          loc: loc
+        };
+}
+
+function mknoloc(txt) {
+  return {
+          txt: txt,
+          loc: none
+        };
+}
+
+function pp_ksprintf(before, k, fmt) {
+  var buf = $$Buffer.create(64);
+  var ppf = Format.formatter_of_buffer(buf);
+  Misc.Color.set_color_tag_handling(ppf);
+  if (before !== undefined) {
+    Curry._1(before, ppf);
+  }
+  return Curry._2(Format.kfprintf(function (param) {
+                  Curry._1(Format.pp_print_flush(ppf), undefined);
+                  return Curry._1(k, $$Buffer.contents(buf));
+                }), ppf, fmt);
+}
+
+function print_phanton_error_prefix(ppf) {
+  return Curry._2(Format.pp_print_as(ppf), error_prefix.length + 2 | 0, "");
+}
+
+function errorf(locOpt, subOpt, if_highlightOpt, fmt) {
+  var loc = locOpt !== undefined ? locOpt : none;
+  var sub = subOpt !== undefined ? subOpt : /* [] */0;
+  var if_highlight = if_highlightOpt !== undefined ? if_highlightOpt : "";
+  return pp_ksprintf(print_phanton_error_prefix, (function (msg) {
+                return {
+                        loc: loc,
+                        msg: msg,
+                        sub: sub,
+                        if_highlight: if_highlight
+                      };
+              }), fmt);
+}
+
+function error(locOpt, subOpt, if_highlightOpt, msg) {
+  var loc = locOpt !== undefined ? locOpt : none;
+  var sub = subOpt !== undefined ? subOpt : /* [] */0;
+  var if_highlight = if_highlightOpt !== undefined ? if_highlightOpt : "";
+  return {
+          loc: loc,
+          msg: msg,
+          sub: sub,
+          if_highlight: if_highlight
+        };
+}
+
+var error_of_exn = {
+  contents: /* [] */0
+};
+
+function register_error_of_exn(f) {
+  error_of_exn.contents = {
+    hd: f,
+    tl: error_of_exn.contents
+  };
+  
+}
+
+function error_of_exn$1(exn) {
+  if (exn.RE_EXN_ID === Warnings.Errors) {
+    return "Already_displayed";
+  }
+  var _x = error_of_exn.contents;
+  while(true) {
+    var x = _x;
+    if (!x) {
+      return ;
+    }
+    var error = Curry._1(x.hd, exn);
+    if (error !== undefined) {
+      return {
+              NAME: "Ok",
+              VAL: Caml_option.valFromOption(error)
+            };
+    }
+    _x = x.tl;
+    continue ;
+  };
+}
+
+function error_of_printer(loc, print, x) {
+  return Curry._2(errorf(loc, undefined, undefined, "%a@?"), print, x);
+}
+
+function error_of_printer_file(print, x) {
+  return error_of_printer(in_file(input_name.contents), print, x);
+}
+
+register_error_of_exn(function (x) {
+      if (x.RE_EXN_ID === P.Sys_error) {
+        return Curry._1(errorf(in_file(input_name.contents), undefined, undefined, "I/O error: %s"), x._1);
+      }
+      if (x.RE_EXN_ID !== Misc.HookExnWrapper) {
+        return ;
+      }
+      var e = x.error;
+      var match = error_of_exn$1(e);
+      var sub = match !== undefined && typeof match === "object" ? match.VAL : error(undefined, undefined, undefined, Printexc.to_string(e));
+      return Curry._1(errorf(in_file(x.hook_info.sourcefile), {
+                      hd: sub,
+                      tl: /* [] */0
+                    }, undefined, "In hook %S:"), x.hook_name);
+    });
+
+var $$Error = /* @__PURE__ */Caml_exceptions.create("Location.Error");
+
+register_error_of_exn(function (x) {
+      if (x.RE_EXN_ID === $$Error) {
+        return x._1;
+      }
+      
+    });
+
+function raise_errorf(locOpt, subOpt, if_highlightOpt) {
+  var loc = locOpt !== undefined ? locOpt : none;
+  var sub = subOpt !== undefined ? subOpt : /* [] */0;
+  var if_highlight = if_highlightOpt !== undefined ? if_highlightOpt : "";
+  var partial_arg = print_phanton_error_prefix;
+  return function (param) {
+    return pp_ksprintf(partial_arg, (function (msg) {
+                  throw {
+                        RE_EXN_ID: $$Error,
+                        _1: {
+                          loc: loc,
+                          msg: msg,
+                          sub: sub,
+                          if_highlight: if_highlight
+                        },
+                        Error: new Error()
+                      };
+                }), param);
+  };
+}
+
+var msg_file = "File \"";
+
+var msg_line = "\", line ";
+
+var msg_chars = ", characters ";
+
+var msg_to = "-";
+
+var msg_colon = ":";
+
+var warning_prefix = "Warning";
+
+var Already_displayed_error = Warnings.Errors;
+
+export {
+  absname ,
+  in_file ,
+  none ,
+  curr ,
+  init ,
+  symbol_rloc ,
+  symbol_gloc ,
+  rhs_loc ,
+  input_name ,
+  input_lexbuf ,
+  set_input_name ,
+  num_loc_lines ,
+  absolute_path ,
+  show_filename ,
+  print_filename ,
+  reset ,
+  msg_file ,
+  msg_line ,
+  msg_chars ,
+  msg_to ,
+  msg_colon ,
+  get_pos_info ,
+  error_prefix ,
+  warning_prefix ,
+  print_compact ,
+  echo_eof ,
+  mkloc ,
+  mknoloc ,
+  pp_ksprintf ,
+  print_phanton_error_prefix ,
+  errorf ,
+  error ,
+  register_error_of_exn ,
+  Already_displayed_error ,
+  error_of_exn$1 as error_of_exn,
+  error_of_printer ,
+  error_of_printer_file ,
+  $$Error ,
+  raise_errorf ,
+  
+}
+/* none Not a pure module */
diff --git a/analysis/examples/larger-project/src/location.res b/analysis/examples/larger-project/src/location.res
new file mode 100644
index 0000000000..332f0c40d6
--- /dev/null
+++ b/analysis/examples/larger-project/src/location.res
@@ -0,0 +1,261 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+open P
+
+open Lexing
+
+let absname = ref(false)
+/* This reference should be in Clflags, but it would create an additional
+ dependency and make bootstrapping Camlp4 more difficult. */
+
+type t = Warnings.loc = {loc_start: position, loc_end: position, loc_ghost: bool}
+
+let in_file = name => {
+  let loc = {
+    pos_fname: name,
+    pos_lnum: 1,
+    pos_bol: 0,
+    pos_cnum: -1,
+  }
+  {loc_start: loc, loc_end: loc, loc_ghost: true}
+}
+
+let none = in_file("_none_")
+
+let curr = lexbuf => {
+  loc_start: lexbuf.lex_start_p,
+  loc_end: lexbuf.lex_curr_p,
+  loc_ghost: false,
+}
+
+let init = (lexbuf, fname) =>
+  lexbuf.lex_curr_p = {
+    pos_fname: fname,
+    pos_lnum: 1,
+    pos_bol: 0,
+    pos_cnum: 0,
+  }
+
+let symbol_rloc = () => {
+  loc_start: Parsing.symbol_start_pos(),
+  loc_end: Parsing.symbol_end_pos(),
+  loc_ghost: false,
+}
+
+let symbol_gloc = () => {
+  loc_start: Parsing.symbol_start_pos(),
+  loc_end: Parsing.symbol_end_pos(),
+  loc_ghost: true,
+}
+
+let rhs_loc = n => {
+  loc_start: Parsing.rhs_start_pos(n),
+  loc_end: Parsing.rhs_end_pos(n),
+  loc_ghost: false,
+}
+
+let input_name = ref("_none_")
+let input_lexbuf = ref((None: option<lexbuf>))
+let set_input_name = name =>
+  if name != "" {
+    input_name := name
+  }
+/* Terminal info */
+
+let num_loc_lines = ref(0) /* number of lines already printed after input */
+
+/* Print the location in some way or another */
+
+open Format
+
+let absolute_path = s => {
+  /* This function could go into Filename */
+  open Filename
+  let s = if is_relative(s) {
+    concat(Sys.getcwd(), s)
+  } else {
+    s
+  }
+  /* Now simplify . and .. components */
+  let rec aux = s => {
+    let base = basename(s)
+    let dir = dirname(s)
+    if dir == s {
+      dir
+    } else if base == current_dir_name {
+      aux(dir)
+    } else if base == parent_dir_name {
+      dirname(aux(dir))
+    } else {
+      concat(aux(dir), base)
+    }
+  }
+
+  aux(s)
+}
+
+let show_filename = file => {
+  let file = if file == "_none_" {
+    input_name.contents
+  } else {
+    file
+  }
+  if absname.contents {
+    absolute_path(file)
+  } else {
+    file
+  }
+}
+
+let print_filename = (ppf, file) => Format.fprintf(ppf, "%s", show_filename(file))
+
+let reset = () => num_loc_lines := 0
+
+let (msg_file, msg_line, msg_chars, msg_to, msg_colon) = (
+  "File \"",
+  "\", line ",
+  ", characters ",
+  "-",
+  ":",
+)
+
+/* return file, line, char from the given position */
+let get_pos_info = pos => (pos.pos_fname, pos.pos_lnum, pos.pos_cnum - pos.pos_bol)
+
+let error_prefix = "Error"
+let warning_prefix = "Warning"
+
+let print_compact = (ppf, loc) => {
+  let (file, line, startchar) = get_pos_info(loc.loc_start)
+  let endchar = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum + startchar
+  fprintf(ppf, "%a:%i", print_filename, file, line)
+  if startchar >= 0 {
+    fprintf(ppf, ",%i--%i", startchar, endchar)
+  }
+}
+
+let echo_eof = () => {
+  print_newline()
+  incr(num_loc_lines)
+}
+
+type loc<'a> = {
+  txt: 'a,
+  loc: t,
+}
+
+let mkloc = (txt, loc) => {txt: txt, loc: loc}
+let mknoloc = txt => mkloc(txt, none)
+
+type rec error = {
+  loc: t,
+  msg: string,
+  sub: list<error>,
+  if_highlight: string /* alternative message if locations are highlighted */,
+}
+
+let pp_ksprintf = (~before=?, k, fmt) => {
+  let buf = Buffer.create(64)
+  let ppf = Format.formatter_of_buffer(buf)
+  Misc.Color.set_color_tag_handling(ppf)
+  switch before {
+  | None => ()
+  | Some(f) => f(ppf)
+  }
+  kfprintf(_ => {
+    pp_print_flush(ppf, ())
+    let msg = Buffer.contents(buf)
+    k(msg)
+  }, ppf, fmt)
+}
+
+/* Shift the formatter's offset by the length of the error prefix, which
+ is always added by the compiler after the message has been formatted */
+let print_phanton_error_prefix = ppf =>
+  Format.pp_print_as(ppf, String.length(error_prefix) + 2 /* ": " */, "")
+
+let errorf = (~loc=none, ~sub=list{}, ~if_highlight="", fmt) =>
+  pp_ksprintf(
+    ~before=print_phanton_error_prefix,
+    msg => {loc: loc, msg: msg, sub: sub, if_highlight: if_highlight},
+    fmt,
+  )
+
+let error = (~loc=none, ~sub=list{}, ~if_highlight="", msg) => {
+  loc: loc,
+  msg: msg,
+  sub: sub,
+  if_highlight: if_highlight,
+}
+
+let error_of_exn: ref<list<exn => option<error>>> = ref(list{})
+
+let register_error_of_exn = f => error_of_exn := list{f, ...error_of_exn.contents}
+
+exception Already_displayed_error = Warnings.Errors
+
+let error_of_exn = exn =>
+  switch exn {
+  | Already_displayed_error => Some(#Already_displayed)
+  | _ =>
+    let rec loop = x =>
+      switch x {
+      | list{} => None
+      | list{f, ...rest} =>
+        switch f(exn) {
+        | Some(error) => Some(#Ok(error))
+        | None => loop(rest)
+        }
+      }
+
+    loop(error_of_exn.contents)
+  }
+
+let error_of_printer = (loc, print, x) => errorf(~loc, "%a@?", print, x)
+
+let error_of_printer_file = (print, x) => error_of_printer(in_file(input_name.contents), print, x)
+
+let () = register_error_of_exn(x =>
+  switch x {
+  | Sys_error(msg) => Some(errorf(~loc=in_file(input_name.contents), "I/O error: %s", msg))
+
+  | Misc.HookExnWrapper({error: e, hook_name, hook_info: {Misc.sourcefile: sourcefile}}) =>
+    let sub = switch error_of_exn(e) {
+    | None | Some(#Already_displayed) => error(Printexc.to_string(e))
+    | Some(#Ok(err)) => err
+    }
+
+    Some(errorf(~loc=in_file(sourcefile), "In hook %S:", hook_name, ~sub=list{sub}))
+  | _ => None
+  }
+)
+
+external reraise: exn => 'a = "%reraise"
+
+exception Error(error)
+
+let () = register_error_of_exn(x =>
+  switch x {
+  | Error(e) => Some(e)
+  | _ => None
+  }
+)
+
+@raises(Error)
+let raise_errorf = (~loc=none, ~sub=list{}, ~if_highlight="") =>
+  pp_ksprintf(~before=print_phanton_error_prefix, msg =>
+    raise(Error({loc: loc, msg: msg, sub: sub, if_highlight: if_highlight}))
+  )
diff --git a/analysis/examples/larger-project/src/longident.js b/analysis/examples/larger-project/src/longident.js
new file mode 100644
index 0000000000..b9686fbe86
--- /dev/null
+++ b/analysis/examples/larger-project/src/longident.js
@@ -0,0 +1,105 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Misc from "./misc.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function flat(_accu, _x) {
+  while(true) {
+    var x = _x;
+    var accu = _accu;
+    switch (x.TAG | 0) {
+      case /* Lident */0 :
+          return {
+                  hd: x._0,
+                  tl: accu
+                };
+      case /* Ldot */1 :
+          _x = x._0;
+          _accu = {
+            hd: x._1,
+            tl: accu
+          };
+          continue ;
+      case /* Lapply */2 :
+          return Misc.fatal_error("Longident.flat");
+      
+    }
+  };
+}
+
+function flatten(lid) {
+  return flat(/* [] */0, lid);
+}
+
+function last(x) {
+  switch (x.TAG | 0) {
+    case /* Lident */0 :
+        return x._0;
+    case /* Ldot */1 :
+        return x._1;
+    case /* Lapply */2 :
+        return Misc.fatal_error("Longident.last");
+    
+  }
+}
+
+function split_at_dots(s, pos) {
+  try {
+    var dot = $$String.index_from(s, pos, /* '.' */46);
+    return {
+            hd: $$String.sub(s, pos, dot - pos | 0),
+            tl: split_at_dots(s, dot + 1 | 0)
+          };
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return {
+              hd: $$String.sub(s, pos, s.length - pos | 0),
+              tl: /* [] */0
+            };
+    }
+    throw exn;
+  }
+}
+
+function unflatten(l) {
+  if (l) {
+    return List.fold_left((function (p, s) {
+                  return {
+                          TAG: /* Ldot */1,
+                          _0: p,
+                          _1: s
+                        };
+                }), {
+                TAG: /* Lident */0,
+                _0: l.hd
+              }, l.tl);
+  }
+  
+}
+
+function parse(s) {
+  var v = unflatten(split_at_dots(s, 0));
+  if (v !== undefined) {
+    return v;
+  } else {
+    return {
+            TAG: /* Lident */0,
+            _0: ""
+          };
+  }
+}
+
+export {
+  flat ,
+  flatten ,
+  last ,
+  split_at_dots ,
+  unflatten ,
+  parse ,
+  
+}
+/* Misc Not a pure module */
diff --git a/analysis/examples/larger-project/src/longident.res b/analysis/examples/larger-project/src/longident.res
new file mode 100644
index 0000000000..5c5ceedf1f
--- /dev/null
+++ b/analysis/examples/larger-project/src/longident.res
@@ -0,0 +1,60 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+type rec t =
+  | Lident(string)
+  | Ldot(t, string)
+  | Lapply(t, t)
+
+let rec flat = (accu, x) =>
+  switch x {
+  | Lident(s) => list{s, ...accu}
+  | Ldot(lid, s) => flat(list{s, ...accu}, lid)
+  | Lapply(_, _) => Misc.fatal_error("Longident.flat")
+  }
+
+let flatten = lid => flat(list{}, lid)
+
+let last = x =>
+  switch x {
+  | Lident(s) => s
+  | Ldot(_, s) => s
+  | Lapply(_, _) => Misc.fatal_error("Longident.last")
+  }
+
+@raises(Invalid_argument)
+let rec split_at_dots = (s, pos) =>
+  try {
+    let dot = String.index_from(s, pos, '.')
+    list{String.sub(s, pos, dot - pos), ...split_at_dots(s, dot + 1)}
+  } catch {
+  | Not_found => list{String.sub(s, pos, String.length(s) - pos)}
+  }
+
+let unflatten = l =>
+  switch l {
+  | list{} => None
+  | list{hd, ...tl} => Some(List.fold_left((p, s) => Ldot(p, s), Lident(hd), tl))
+  }
+
+@raises(Invalid_argument)
+let parse = s =>
+  switch unflatten(split_at_dots(s, 0)) {
+  | None => Lident("") /* should not happen, but don't put assert false
+   so as not to crash the toplevel (see Genprintval) */
+
+  | Some(v) => v
+  }
+
diff --git a/analysis/examples/larger-project/src/loop.js b/analysis/examples/larger-project/src/loop.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/loop.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/loop.res b/analysis/examples/larger-project/src/loop.res
new file mode 100644
index 0000000000..10417269ac
--- /dev/null
+++ b/analysis/examples/larger-project/src/loop.res
@@ -0,0 +1,4 @@
+// let foo = x =>
+//   switch x {
+//   | `${
+
diff --git a/analysis/examples/larger-project/src/misc.js b/analysis/examples/larger-project/src/misc.js
new file mode 100644
index 0000000000..ca5c79b601
--- /dev/null
+++ b/analysis/examples/larger-project/src/misc.js
@@ -0,0 +1,1450 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as P from "./P.js";
+import * as $$Map from "rescript/lib/es6/map.js";
+import * as $$Set from "rescript/lib/es6/set.js";
+import * as Sys from "rescript/lib/es6/sys.js";
+import * as Caml from "rescript/lib/es6/caml.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as $$Array from "rescript/lib/es6/array.js";
+import * as Bytes from "rescript/lib/es6/bytes.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Buffer from "rescript/lib/es6/buffer.js";
+import * as Format from "./format.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Hashtbl from "rescript/lib/es6/hashtbl.js";
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as Caml_sys from "rescript/lib/es6/caml_sys.js";
+import * as Filename from "rescript/lib/es6/filename.js";
+import * as Caml_array from "rescript/lib/es6/caml_array.js";
+import * as Caml_bytes from "rescript/lib/es6/caml_bytes.js";
+import * as Caml_int32 from "rescript/lib/es6/caml_int32.js";
+import * as Caml_int64 from "rescript/lib/es6/caml_int64.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_format from "rescript/lib/es6/caml_format.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+import * as Caml_external_polyfill from "rescript/lib/es6/caml_external_polyfill.js";
+
+var Fatal_error = /* @__PURE__ */Caml_exceptions.create("Misc.Fatal_error");
+
+function fatal_error(msg) {
+  Pervasives.print_string(">> Fatal error: ");
+  console.error(msg);
+  throw {
+        RE_EXN_ID: Fatal_error,
+        Error: new Error()
+      };
+}
+
+function fatal_errorf(fmt) {
+  return Curry._1(Format.kasprintf(fatal_error), fmt);
+}
+
+function try_finally(work, cleanup) {
+  var result;
+  try {
+    result = Curry._1(work, undefined);
+  }
+  catch (e){
+    Curry._1(cleanup, undefined);
+    throw e;
+  }
+  Curry._1(cleanup, undefined);
+  return result;
+}
+
+function set_refs(l) {
+  return List.iter((function (param) {
+                param._0.contents = param._1;
+                
+              }), l);
+}
+
+function protect_refs(refs, f) {
+  var backup = List.map((function (param) {
+          var r = param._0;
+          return /* R */{
+                  _0: r,
+                  _1: r.contents
+                };
+        }), refs);
+  set_refs(refs);
+  var x;
+  try {
+    x = Curry._1(f, undefined);
+  }
+  catch (e){
+    set_refs(backup);
+    throw e;
+  }
+  set_refs(backup);
+  return x;
+}
+
+function map_end(f, l1, l2) {
+  if (l1) {
+    return {
+            hd: Curry._1(f, l1.hd),
+            tl: map_end(f, l1.tl, l2)
+          };
+  } else {
+    return l2;
+  }
+}
+
+function map_left_right(f, x) {
+  if (!x) {
+    return /* [] */0;
+  }
+  var res = Curry._1(f, x.hd);
+  return {
+          hd: res,
+          tl: map_left_right(f, x.tl)
+        };
+}
+
+function for_all2(pred, _l1, _l2) {
+  while(true) {
+    var l2 = _l2;
+    var l1 = _l1;
+    if (!l1) {
+      if (l2) {
+        return false;
+      } else {
+        return true;
+      }
+    }
+    if (!l2) {
+      return false;
+    }
+    if (!Curry._2(pred, l1.hd, l2.hd)) {
+      return false;
+    }
+    _l2 = l2.tl;
+    _l1 = l1.tl;
+    continue ;
+  };
+}
+
+function replicate_list(elem, n) {
+  if (n <= 0) {
+    return /* [] */0;
+  } else {
+    return {
+            hd: elem,
+            tl: replicate_list(elem, n - 1 | 0)
+          };
+  }
+}
+
+function list_remove(x, y) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "misc.res",
+          94,
+          32
+        ],
+        Error: new Error()
+      };
+}
+
+function split_last(x) {
+  if (x) {
+    var tl = x.tl;
+    var x$1 = x.hd;
+    if (!tl) {
+      return [
+              /* [] */0,
+              x$1
+            ];
+    }
+    var match = split_last(tl);
+    return [
+            {
+              hd: x$1,
+              tl: match[0]
+            },
+            match[1]
+          ];
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "misc.res",
+          98,
+          14
+        ],
+        Error: new Error()
+      };
+}
+
+function compare(cmp, _l1, _l2) {
+  while(true) {
+    var l2 = _l2;
+    var l1 = _l1;
+    if (!l1) {
+      if (l2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+    if (!l2) {
+      return 1;
+    }
+    var c = Curry._2(cmp, l1.hd, l2.hd);
+    if (c !== 0) {
+      return c;
+    }
+    _l2 = l2.tl;
+    _l1 = l1.tl;
+    continue ;
+  };
+}
+
+function equal(eq, _l1, _l2) {
+  while(true) {
+    var l2 = _l2;
+    var l1 = _l1;
+    if (!l1) {
+      if (l2) {
+        return false;
+      } else {
+        return true;
+      }
+    }
+    if (!l2) {
+      return false;
+    }
+    if (!Curry._2(eq, l1.hd, l2.hd)) {
+      return false;
+    }
+    _l2 = l2.tl;
+    _l1 = l1.tl;
+    continue ;
+  };
+}
+
+function filter_map(f, l) {
+  var _acc = /* [] */0;
+  var _l = l;
+  while(true) {
+    var l$1 = _l;
+    var acc = _acc;
+    if (!l$1) {
+      return List.rev(acc);
+    }
+    var t = l$1.tl;
+    var v = Curry._1(f, l$1.hd);
+    if (v !== undefined) {
+      _l = t;
+      _acc = {
+        hd: Caml_option.valFromOption(v),
+        tl: acc
+      };
+      continue ;
+    }
+    _l = t;
+    continue ;
+  };
+}
+
+function map2_prefix(f, l1, l2) {
+  var _acc = /* [] */0;
+  var _l1 = l1;
+  var _l2 = l2;
+  while(true) {
+    var l2$1 = _l2;
+    var l1$1 = _l1;
+    var acc = _acc;
+    if (!l1$1) {
+      return [
+              List.rev(acc),
+              l2$1
+            ];
+    }
+    if (l2$1) {
+      var h = Curry._2(f, l1$1.hd, l2$1.hd);
+      _l2 = l2$1.tl;
+      _l1 = l1$1.tl;
+      _acc = {
+        hd: h,
+        tl: acc
+      };
+      continue ;
+    }
+    throw {
+          RE_EXN_ID: "Invalid_argument",
+          _1: "map2_prefix",
+          Error: new Error()
+        };
+  };
+}
+
+function some_if_all_elements_are_some(l) {
+  var _acc = /* [] */0;
+  var _l = l;
+  while(true) {
+    var l$1 = _l;
+    var acc = _acc;
+    if (!l$1) {
+      return List.rev(acc);
+    }
+    var h = l$1.hd;
+    if (h === undefined) {
+      return ;
+    }
+    _l = l$1.tl;
+    _acc = {
+      hd: Caml_option.valFromOption(h),
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function split_at(n, l) {
+  var _n = n;
+  var _acc = /* [] */0;
+  var _l = l;
+  while(true) {
+    var l$1 = _l;
+    var acc = _acc;
+    var n$1 = _n;
+    if (n$1 === 0) {
+      return [
+              List.rev(acc),
+              l$1
+            ];
+    }
+    if (l$1) {
+      _l = l$1.tl;
+      _acc = {
+        hd: l$1.hd,
+        tl: acc
+      };
+      _n = n$1 - 1 | 0;
+      continue ;
+    }
+    throw {
+          RE_EXN_ID: "Invalid_argument",
+          _1: "split_at",
+          Error: new Error()
+        };
+  };
+}
+
+var List$1 = {
+  compare: compare,
+  equal: equal,
+  filter_map: filter_map,
+  map2_prefix: map2_prefix,
+  some_if_all_elements_are_some: some_if_all_elements_are_some,
+  split_at: split_at
+};
+
+function equal$1(eq, o1, o2) {
+  if (o1 !== undefined) {
+    if (o2 !== undefined) {
+      return Curry._2(eq, Caml_option.valFromOption(o1), Caml_option.valFromOption(o2));
+    } else {
+      return false;
+    }
+  } else {
+    return o2 === undefined;
+  }
+}
+
+function iter(f, x) {
+  if (x !== undefined) {
+    return Curry._1(f, Caml_option.valFromOption(x));
+  }
+  
+}
+
+function map(f, x) {
+  if (x !== undefined) {
+    return Caml_option.some(Curry._1(f, Caml_option.valFromOption(x)));
+  }
+  
+}
+
+function fold(f, a, b) {
+  if (a !== undefined) {
+    return Curry._2(f, Caml_option.valFromOption(a), b);
+  } else {
+    return b;
+  }
+}
+
+function value_default(f, $$default, a) {
+  if (a !== undefined) {
+    return Curry._1(f, Caml_option.valFromOption(a));
+  } else {
+    return $$default;
+  }
+}
+
+var $$Option = {
+  equal: equal$1,
+  iter: iter,
+  map: map,
+  fold: fold,
+  value_default: value_default
+};
+
+function exists2(p, a1, a2) {
+  var n = a1.length;
+  if (a2.length !== n) {
+    Pervasives.invalid_arg("Misc.Stdlib.Array.exists2");
+  }
+  var _i = 0;
+  while(true) {
+    var i = _i;
+    if (i === n) {
+      return false;
+    }
+    if (Curry._2(p, a1[i], a2[i])) {
+      return true;
+    }
+    _i = i + 1 | 0;
+    continue ;
+  };
+}
+
+var $$Array$1 = {
+  exists2: exists2
+};
+
+var Stdlib = {
+  List: List$1,
+  $$Option: $$Option,
+  $$Array: $$Array$1
+};
+
+function find_in_path(path, name) {
+  if (Curry._1(Filename.is_implicit, name)) {
+    var _x = path;
+    while(true) {
+      var x = _x;
+      if (x) {
+        var fullname = Filename.concat(x.hd, name);
+        if (Caml_external_polyfill.resolve("caml_sys_file_exists")(fullname)) {
+          return fullname;
+        }
+        _x = x.tl;
+        continue ;
+      }
+      throw {
+            RE_EXN_ID: "Not_found",
+            Error: new Error()
+          };
+    };
+  }
+  if (Caml_external_polyfill.resolve("caml_sys_file_exists")(name)) {
+    return name;
+  }
+  throw {
+        RE_EXN_ID: "Not_found",
+        Error: new Error()
+      };
+}
+
+function find_in_path_rel(path, name) {
+  var simplify = function (_s) {
+    while(true) {
+      var s = _s;
+      var base = Curry._1(Filename.basename, s);
+      var dir = Curry._1(Filename.dirname, s);
+      if (dir === s) {
+        return dir;
+      }
+      if (base !== Filename.current_dir_name) {
+        return Filename.concat(simplify(dir), base);
+      }
+      _s = dir;
+      continue ;
+    };
+  };
+  var _x = path;
+  while(true) {
+    var x = _x;
+    if (x) {
+      var fullname = simplify(Filename.concat(x.hd, name));
+      if (Caml_external_polyfill.resolve("caml_sys_file_exists")(fullname)) {
+        return fullname;
+      }
+      _x = x.tl;
+      continue ;
+    }
+    throw {
+          RE_EXN_ID: "Not_found",
+          Error: new Error()
+        };
+  };
+}
+
+function find_in_path_uncap(path, name) {
+  var uname = $$String.uncapitalize_ascii(name);
+  var _x = path;
+  while(true) {
+    var x = _x;
+    if (x) {
+      var dir = x.hd;
+      var fullname = Filename.concat(dir, name);
+      var ufullname = Filename.concat(dir, uname);
+      if (Caml_external_polyfill.resolve("caml_sys_file_exists")(ufullname)) {
+        return ufullname;
+      }
+      if (Caml_external_polyfill.resolve("caml_sys_file_exists")(fullname)) {
+        return fullname;
+      }
+      _x = x.tl;
+      continue ;
+    }
+    throw {
+          RE_EXN_ID: "Not_found",
+          Error: new Error()
+        };
+  };
+}
+
+function remove_file(filename) {
+  try {
+    if (Caml_external_polyfill.resolve("caml_sys_file_exists")(filename)) {
+      return Caml_external_polyfill.resolve("caml_sys_remove")(filename);
+    } else {
+      return ;
+    }
+  }
+  catch (raw__msg){
+    var _msg = Caml_js_exceptions.internalToOCamlException(raw__msg);
+    if (_msg.RE_EXN_ID === P.Sys_error) {
+      return ;
+    }
+    throw _msg;
+  }
+}
+
+function expand_directory(alt, s) {
+  if (s.length !== 0 && Caml_string.get(s, 0) === /* '+' */43) {
+    return Filename.concat(alt, $$String.sub(s, 1, s.length - 1 | 0));
+  } else {
+    return s;
+  }
+}
+
+function create_hashtable(size, init) {
+  var tbl = Hashtbl.create(undefined, size);
+  List.iter((function (param) {
+          return Hashtbl.add(tbl, param[0], param[1]);
+        }), init);
+  return tbl;
+}
+
+function copy_file(ic, oc) {
+  var buff = Caml_bytes.caml_create_bytes(4096);
+  var _param;
+  while(true) {
+    var n = Curry._3(P.input(ic), buff, 0, 4096);
+    if (n === 0) {
+      return ;
+    }
+    Curry._3(P.output(oc), buff, 0, n);
+    _param = undefined;
+    continue ;
+  };
+}
+
+function copy_file_chunk(ic, oc, len) {
+  var buff = Caml_bytes.caml_create_bytes(4096);
+  var _n = len;
+  while(true) {
+    var n = _n;
+    if (n <= 0) {
+      return ;
+    }
+    var r = Curry._3(P.input(ic), buff, 0, n < 4096 ? n : 4096);
+    if (r === 0) {
+      throw {
+            RE_EXN_ID: "End_of_file",
+            Error: new Error()
+          };
+    }
+    Curry._3(P.output(oc), buff, 0, r);
+    _n = n - r | 0;
+    continue ;
+  };
+}
+
+function string_of_file(ic) {
+  var b = $$Buffer.create(65536);
+  var buff = Caml_bytes.caml_create_bytes(4096);
+  var _param;
+  while(true) {
+    var n = Curry._3(P.input(ic), buff, 0, 4096);
+    if (n === 0) {
+      return $$Buffer.contents(b);
+    }
+    $$Buffer.add_subbytes(b, buff, 0, n);
+    _param = undefined;
+    continue ;
+  };
+}
+
+function output_to_file_via_temporary(modeOpt, filename, fn) {
+  var mode = modeOpt !== undefined ? modeOpt : ({
+        hd: /* Open_text */0,
+        tl: /* [] */0
+      });
+  var match = Curry._4(P.open_temp_file(Curry._1(Filename.basename, filename)), mode, 438, Curry._1(Filename.dirname, filename), ".tmp");
+  var oc = match[1];
+  var temp_filename = match[0];
+  var res;
+  try {
+    res = Curry._2(fn, temp_filename, oc);
+  }
+  catch (exn){
+    P.close_out(oc);
+    remove_file(temp_filename);
+    throw exn;
+  }
+  P.close_out(oc);
+  try {
+    Caml_external_polyfill.resolve("caml_sys_rename")(temp_filename, filename);
+    return res;
+  }
+  catch (exn$1){
+    remove_file(temp_filename);
+    throw exn$1;
+  }
+}
+
+function log2(n) {
+  if (n <= 1) {
+    return 0;
+  } else {
+    return 1 + log2((n >> 1)) | 0;
+  }
+}
+
+function align(n, a) {
+  if (n >= 0) {
+    return (n + a | 0) - 1 & (-a | 0);
+  } else {
+    return n & (-a | 0);
+  }
+}
+
+function no_overflow_add(a, b) {
+  return (a ^ b | a ^ Pervasives.lnot(a + b | 0)) < 0;
+}
+
+function no_overflow_sub(a, b) {
+  return (a ^ Pervasives.lnot(b) | b ^ (a - b | 0)) < 0;
+}
+
+function no_overflow_mul(a, b) {
+  if (b !== 0) {
+    return Caml_int32.div(Math.imul(a, b), b) === a;
+  } else {
+    return false;
+  }
+}
+
+function no_overflow_lsl(a, k) {
+  if (0 <= k && k < Sys.word_size && (Pervasives.min_int >> k) <= a) {
+    return a <= (Pervasives.max_int >> k);
+  } else {
+    return false;
+  }
+}
+
+function cvt_int_aux(str, neg, of_string) {
+  if (str.length === 0 || Caml_string.get(str, 0) === /* '-' */45) {
+    return Curry._1(of_string, str);
+  } else {
+    return Curry._1(neg, Curry._1(of_string, "-" + str));
+  }
+}
+
+function $$int(s) {
+  return cvt_int_aux(s, (function (prim) {
+                return -prim | 0;
+              }), Caml_format.caml_int_of_string);
+}
+
+function int32(s) {
+  return cvt_int_aux(s, (function (prim) {
+                return -prim | 0;
+              }), Caml_format.caml_int32_of_string);
+}
+
+function int64(s) {
+  return cvt_int_aux(s, Caml_int64.neg, Caml_format.caml_int64_of_string);
+}
+
+function nativeint(s) {
+  return cvt_int_aux(s, (function (prim) {
+                return -prim | 0;
+              }), Caml_format.caml_nativeint_of_string);
+}
+
+var Int_literal_converter = {
+  cvt_int_aux: cvt_int_aux,
+  $$int: $$int,
+  int32: int32,
+  int64: int64,
+  nativeint: nativeint
+};
+
+function chop_extensions(file) {
+  var dirname = Curry._1(Filename.dirname, file);
+  var basename = Curry._1(Filename.basename, file);
+  try {
+    var pos = $$String.index(basename, /* '.' */46);
+    var basename$1 = $$String.sub(basename, 0, pos);
+    if (Curry._1(Filename.is_implicit, file) && dirname === Filename.current_dir_name) {
+      return basename$1;
+    } else {
+      return Filename.concat(dirname, basename$1);
+    }
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return file;
+    }
+    throw exn;
+  }
+}
+
+function search_substring(pat, str, start) {
+  var _i = start;
+  var _j = 0;
+  while(true) {
+    var j = _j;
+    var i = _i;
+    if (j >= pat.length) {
+      return i;
+    }
+    if ((i + j | 0) >= str.length) {
+      throw {
+            RE_EXN_ID: "Not_found",
+            Error: new Error()
+          };
+    }
+    if (Caml_string.get(str, i + j | 0) === Caml_string.get(pat, j)) {
+      _j = j + 1 | 0;
+      continue ;
+    }
+    _j = 0;
+    _i = i + 1 | 0;
+    continue ;
+  };
+}
+
+function replace_substring(before, after, str) {
+  var search = function (_acc, _curr) {
+    while(true) {
+      var curr = _curr;
+      var acc = _acc;
+      var next;
+      try {
+        next = search_substring(before, str, curr);
+      }
+      catch (raw_exn){
+        var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+        if (exn.RE_EXN_ID === "Not_found") {
+          var suffix = $$String.sub(str, curr, str.length - curr | 0);
+          return List.rev({
+                      hd: suffix,
+                      tl: acc
+                    });
+        }
+        throw exn;
+      }
+      var prefix = $$String.sub(str, curr, next - curr | 0);
+      _curr = next + before.length | 0;
+      _acc = {
+        hd: prefix,
+        tl: acc
+      };
+      continue ;
+    };
+  };
+  return $$String.concat(after, search(/* [] */0, 0));
+}
+
+function rev_split_words(s) {
+  var split1 = function (res, _i) {
+    while(true) {
+      var i = _i;
+      if (i >= s.length) {
+        return res;
+      }
+      var match = Caml_string.get(s, i);
+      if (match > 13 || match < 9) {
+        if (match !== 32) {
+          return split2(res, i, i + 1 | 0);
+        }
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if (match === 12 || match === 11) {
+        return split2(res, i, i + 1 | 0);
+      }
+      _i = i + 1 | 0;
+      continue ;
+    };
+  };
+  var split2 = function (res, i, _j) {
+    while(true) {
+      var j = _j;
+      if (j >= s.length) {
+        return {
+                hd: $$String.sub(s, i, j - i | 0),
+                tl: res
+              };
+      }
+      var match = Caml_string.get(s, j);
+      if (match > 13 || match < 9) {
+        if (match !== 32) {
+          _j = j + 1 | 0;
+          continue ;
+        }
+        
+      } else if (match === 12 || match === 11) {
+        _j = j + 1 | 0;
+        continue ;
+      }
+      return split1({
+                  hd: $$String.sub(s, i, j - i | 0),
+                  tl: res
+                }, j + 1 | 0);
+    };
+  };
+  return split1(/* [] */0, 0);
+}
+
+function get_ref(r) {
+  var v = r.contents;
+  r.contents = /* [] */0;
+  return v;
+}
+
+function fst3(param) {
+  return param[0];
+}
+
+function snd3(param) {
+  return param[1];
+}
+
+function thd3(param) {
+  return param[2];
+}
+
+function fst4(param) {
+  return param[0];
+}
+
+function snd4(param) {
+  return param[1];
+}
+
+function thd4(param) {
+  return param[2];
+}
+
+function for4(param) {
+  return param[3];
+}
+
+function create(str_size) {
+  var tbl_size = Caml_int32.div(str_size, Sys.max_string_length) + 1 | 0;
+  var tbl = Caml_array.make(tbl_size, Bytes.empty);
+  for(var i = 0 ,i_finish = tbl_size - 2 | 0; i <= i_finish; ++i){
+    Caml_array.set(tbl, i, Caml_bytes.caml_create_bytes(Sys.max_string_length));
+  }
+  Caml_array.set(tbl, tbl_size - 1 | 0, Caml_bytes.caml_create_bytes(Caml_int32.mod_(str_size, Sys.max_string_length)));
+  return tbl;
+}
+
+function length(tbl) {
+  var tbl_size = tbl.length;
+  return Math.imul(Sys.max_string_length, tbl_size - 1 | 0) + Caml_array.get(tbl, tbl_size - 1 | 0).length | 0;
+}
+
+function get(tbl, ind) {
+  return Caml_bytes.get(Caml_array.get(tbl, Caml_int32.div(ind, Sys.max_string_length)), Caml_int32.mod_(ind, Sys.max_string_length));
+}
+
+function set(tbl, ind, c) {
+  return Caml_bytes.set(Caml_array.get(tbl, Caml_int32.div(ind, Sys.max_string_length)), Caml_int32.mod_(ind, Sys.max_string_length), c);
+}
+
+function blit(src, srcoff, dst, dstoff, len) {
+  for(var i = 0; i < len; ++i){
+    set(dst, dstoff + i | 0, get(src, srcoff + i | 0));
+  }
+  
+}
+
+function output(oc, tbl, pos, len) {
+  for(var i = pos ,i_finish = pos + len | 0; i < i_finish; ++i){
+    Curry._1(P.output_char(oc), get(tbl, i));
+  }
+  
+}
+
+function unsafe_blit_to_bytes(src, srcoff, dst, dstoff, len) {
+  for(var i = 0; i < len; ++i){
+    dst[dstoff + i | 0] = get(src, srcoff + i | 0);
+  }
+  
+}
+
+function input_bytes(ic, len) {
+  var tbl = create(len);
+  $$Array.iter((function (str) {
+          return Curry._3(P.really_input(ic), str, 0, str.length);
+        }), tbl);
+  return tbl;
+}
+
+var LongString = {
+  create: create,
+  length: length,
+  get: get,
+  set: set,
+  blit: blit,
+  output: output,
+  unsafe_blit_to_bytes: unsafe_blit_to_bytes,
+  input_bytes: input_bytes
+};
+
+function edit_distance(a, b, cutoff) {
+  var la = a.length;
+  var lb = b.length;
+  var cutoff$1 = Caml.caml_int_min(la > lb ? la : lb, cutoff);
+  if (Pervasives.abs(la - lb | 0) > cutoff$1) {
+    return ;
+  }
+  var m = $$Array.make_matrix(la + 1 | 0, lb + 1 | 0, cutoff$1 + 1 | 0);
+  Caml_array.set(Caml_array.get(m, 0), 0, 0);
+  for(var i = 1; i <= la; ++i){
+    Caml_array.set(Caml_array.get(m, i), 0, i);
+  }
+  for(var j = 1; j <= lb; ++j){
+    Caml_array.set(Caml_array.get(m, 0), j, j);
+  }
+  for(var i$1 = 1; i$1 <= la; ++i$1){
+    for(var j$1 = Caml.caml_int_max(1, (i$1 - cutoff$1 | 0) - 1 | 0) ,j_finish = Caml.caml_int_min(lb, (i$1 + cutoff$1 | 0) + 1 | 0); j$1 <= j_finish; ++j$1){
+      var cost = Caml_string.get(a, i$1 - 1 | 0) === Caml_string.get(b, j$1 - 1 | 0) ? 0 : 1;
+      var best = Caml.caml_int_min(1 + Caml.caml_int_min(Caml_array.get(Caml_array.get(m, i$1 - 1 | 0), j$1), Caml_array.get(Caml_array.get(m, i$1), j$1 - 1 | 0)) | 0, Caml_array.get(Caml_array.get(m, i$1 - 1 | 0), j$1 - 1 | 0) + cost | 0);
+      var best$1 = i$1 > 1 && j$1 > 1 && Caml_string.get(a, i$1 - 1 | 0) === Caml_string.get(b, j$1 - 2 | 0) && Caml_string.get(a, i$1 - 2 | 0) === Caml_string.get(b, j$1 - 1 | 0) ? Caml.caml_int_min(best, Caml_array.get(Caml_array.get(m, i$1 - 2 | 0), j$1 - 2 | 0) + cost | 0) : best;
+      Caml_array.set(Caml_array.get(m, i$1), j$1, best$1);
+    }
+  }
+  var result = Caml_array.get(Caml_array.get(m, la), lb);
+  if (result > cutoff$1) {
+    return ;
+  } else {
+    return result;
+  }
+}
+
+function spellcheck(env, name) {
+  var match = name.length;
+  var cutoff = match > 4 || match < 1 ? (
+      match === 6 || match === 5 ? 2 : 3
+    ) : (
+      match >= 3 ? 1 : 0
+    );
+  return List.fold_left((function (param, param$1) {
+                  var dist = edit_distance(name, param$1, cutoff);
+                  if (dist === undefined) {
+                    return param;
+                  }
+                  var best_dist = param[1];
+                  if (dist < best_dist) {
+                    return [
+                            {
+                              hd: param$1,
+                              tl: /* [] */0
+                            },
+                            dist
+                          ];
+                  } else if (dist === best_dist) {
+                    return [
+                            {
+                              hd: param$1,
+                              tl: param[0]
+                            },
+                            dist
+                          ];
+                  } else {
+                    return param;
+                  }
+                }), [
+                /* [] */0,
+                Pervasives.max_int
+              ], env)[0];
+}
+
+function did_you_mean(ppf, get_choices) {
+  Curry._1(Format.fprintf(ppf), "@?");
+  var choices = Curry._1(get_choices, undefined);
+  if (!choices) {
+    return ;
+  }
+  var match = split_last(choices);
+  var rest = match[0];
+  return Curry._4(Format.fprintf(ppf), "@\nHint: Did you mean %s%s%s?@?", $$String.concat(", ", rest), rest === /* [] */0 ? "" : " or ", match[1]);
+}
+
+function cut_at(s, c) {
+  var pos = $$String.index(s, c);
+  return [
+          $$String.sub(s, 0, pos),
+          $$String.sub(s, pos + 1 | 0, (s.length - pos | 0) - 1 | 0)
+        ];
+}
+
+var compare$1 = Caml_obj.caml_compare;
+
+var StringSet = $$Set.Make({
+      compare: compare$1
+    });
+
+var compare$2 = Caml_obj.caml_compare;
+
+var StringMap = $$Map.Make({
+      compare: compare$2
+    });
+
+function ansi_of_color(x) {
+  switch (x) {
+    case /* Black */0 :
+        return "0";
+    case /* Red */1 :
+        return "1";
+    case /* Green */2 :
+        return "2";
+    case /* Yellow */3 :
+        return "3";
+    case /* Blue */4 :
+        return "4";
+    case /* Magenta */5 :
+        return "5";
+    case /* Cyan */6 :
+        return "6";
+    case /* White */7 :
+        return "7";
+    
+  }
+}
+
+function code_of_style(x) {
+  if (typeof x === "number") {
+    if (x === /* Bold */0) {
+      return "1";
+    } else {
+      return "0";
+    }
+  } else if (x.TAG === /* FG */0) {
+    return "3" + ansi_of_color(x._0);
+  } else {
+    return "4" + ansi_of_color(x._0);
+  }
+}
+
+function ansi_of_style_l(l) {
+  var s = l ? (
+      l.tl ? $$String.concat(";", List.map(code_of_style, l)) : code_of_style(l.hd)
+    ) : "0";
+  return "\x11[" + (s + "m");
+}
+
+var default_styles = {
+  error: {
+    hd: /* Bold */0,
+    tl: {
+      hd: {
+        TAG: /* FG */0,
+        _0: /* Red */1
+      },
+      tl: /* [] */0
+    }
+  },
+  warning: {
+    hd: /* Bold */0,
+    tl: {
+      hd: {
+        TAG: /* FG */0,
+        _0: /* Magenta */5
+      },
+      tl: /* [] */0
+    }
+  },
+  loc: {
+    hd: /* Bold */0,
+    tl: /* [] */0
+  }
+};
+
+var cur_styles = {
+  contents: default_styles
+};
+
+function get_styles(param) {
+  return cur_styles.contents;
+}
+
+function set_styles(s) {
+  cur_styles.contents = s;
+  
+}
+
+function style_of_tag(s) {
+  switch (s) {
+    case "error" :
+        return cur_styles.contents.error;
+    case "loc" :
+        return cur_styles.contents.loc;
+    case "warning" :
+        return cur_styles.contents.warning;
+    default:
+      throw {
+            RE_EXN_ID: "Not_found",
+            Error: new Error()
+          };
+  }
+}
+
+var color_enabled = {
+  contents: true
+};
+
+function mark_open_tag(or_else, s) {
+  try {
+    var style = style_of_tag(s);
+    if (color_enabled.contents) {
+      return ansi_of_style_l(style);
+    } else {
+      return "";
+    }
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return Curry._1(or_else, s);
+    }
+    throw exn;
+  }
+}
+
+function mark_close_tag(or_else, s) {
+  try {
+    style_of_tag(s);
+    if (color_enabled.contents) {
+      return ansi_of_style_l({
+                  hd: /* Reset */1,
+                  tl: /* [] */0
+                });
+    } else {
+      return "";
+    }
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return Curry._1(or_else, s);
+    }
+    throw exn;
+  }
+}
+
+function set_color_tag_handling(ppf) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "misc.res",
+          866,
+          4
+        ],
+        Error: new Error()
+      };
+}
+
+function should_enable_color(param) {
+  var term;
+  try {
+    term = Caml_sys.caml_sys_getenv("TERM");
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      term = "";
+    } else {
+      throw exn;
+    }
+  }
+  if (term !== "dumb" && term !== "") {
+    return Caml_external_polyfill.resolve("caml_sys_isatty")(P.stderr);
+  } else {
+    return false;
+  }
+}
+
+var first = {
+  contents: true
+};
+
+var formatter_l_1 = {
+  hd: Format.err_formatter,
+  tl: {
+    hd: Format.str_formatter,
+    tl: /* [] */0
+  }
+};
+
+var formatter_l = {
+  hd: Format.std_formatter,
+  tl: formatter_l_1
+};
+
+function setup(o) {
+  if (first.contents) {
+    first.contents = false;
+    Format.set_mark_tags(true);
+    List.iter(set_color_tag_handling, formatter_l);
+    var tmp;
+    if (o !== undefined) {
+      switch (o) {
+        case /* Auto */0 :
+            tmp = should_enable_color(undefined);
+            break;
+        case /* Always */1 :
+            tmp = true;
+            break;
+        case /* Never */2 :
+            tmp = false;
+            break;
+        
+      }
+    } else {
+      tmp = should_enable_color(undefined);
+    }
+    color_enabled.contents = tmp;
+  }
+  
+}
+
+var Color = {
+  ansi_of_color: ansi_of_color,
+  code_of_style: code_of_style,
+  ansi_of_style_l: ansi_of_style_l,
+  default_styles: default_styles,
+  cur_styles: cur_styles,
+  get_styles: get_styles,
+  set_styles: set_styles,
+  style_of_tag: style_of_tag,
+  color_enabled: color_enabled,
+  mark_open_tag: mark_open_tag,
+  mark_close_tag: mark_close_tag,
+  set_color_tag_handling: set_color_tag_handling,
+  should_enable_color: should_enable_color,
+  setup: setup
+};
+
+function normalise_eol(s) {
+  var b = $$Buffer.create(80);
+  for(var i = 0 ,i_finish = s.length; i < i_finish; ++i){
+    if (Caml_string.get(s, i) !== /* '\r' */13) {
+      $$Buffer.add_char(b, Caml_string.get(s, i));
+    }
+    
+  }
+  return $$Buffer.contents(b);
+}
+
+function delete_eol_spaces(src) {
+  var len_src = src.length;
+  var dst = Caml_bytes.caml_create_bytes(len_src);
+  var loop = function (_i_src, _i_dst) {
+    while(true) {
+      var i_dst = _i_dst;
+      var i_src = _i_src;
+      if (i_src === len_src) {
+        return i_dst;
+      }
+      var c = Caml_string.get(src, i_src);
+      if (c === 9) {
+        return loop_spaces(1, i_src + 1 | 0, i_dst);
+      }
+      if (c === 32) {
+        return loop_spaces(1, i_src + 1 | 0, i_dst);
+      }
+      Caml_bytes.set(dst, i_dst, c);
+      _i_dst = i_dst + 1 | 0;
+      _i_src = i_src + 1 | 0;
+      continue ;
+    };
+  };
+  var loop_spaces = function (_spaces, _i_src, i_dst) {
+    while(true) {
+      var i_src = _i_src;
+      var spaces = _spaces;
+      if (i_src === len_src) {
+        return i_dst;
+      }
+      var match = Caml_string.get(src, i_src);
+      if (match === 10 || match === 9) {
+        if (match >= 10) {
+          Caml_bytes.set(dst, i_dst, /* '\n' */10);
+          return loop(i_src + 1 | 0, i_dst + 1 | 0);
+        }
+        
+      } else if (match !== 32) {
+        for(var n = 0; n <= spaces; ++n){
+          Caml_bytes.set(dst, i_dst + n | 0, Caml_string.get(src, (i_src - spaces | 0) + n | 0));
+        }
+        return loop(i_src + 1 | 0, (i_dst + spaces | 0) + 1 | 0);
+      }
+      _i_src = i_src + 1 | 0;
+      _spaces = spaces + 1 | 0;
+      continue ;
+    };
+  };
+  var stop = loop(0, 0);
+  return Bytes.sub_string(dst, 0, stop);
+}
+
+var HookExnWrapper = /* @__PURE__ */Caml_exceptions.create("Misc.HookExnWrapper");
+
+var HookExn = /* @__PURE__ */Caml_exceptions.create("Misc.HookExn");
+
+function raise_direct_hook_exn(e) {
+  throw {
+        RE_EXN_ID: HookExn,
+        _1: e,
+        Error: new Error()
+      };
+}
+
+function fold_hooks(list, hook_info, ast) {
+  return List.fold_left((function (ast, param) {
+                try {
+                  return Curry._2(param[1], hook_info, ast);
+                }
+                catch (raw_e){
+                  var e = Caml_js_exceptions.internalToOCamlException(raw_e);
+                  if (e.RE_EXN_ID === HookExn) {
+                    throw e._1;
+                  }
+                  throw {
+                        RE_EXN_ID: HookExnWrapper,
+                        error: e,
+                        hook_name: param[0],
+                        hook_info: hook_info,
+                        Error: new Error()
+                      };
+                }
+              }), ast, List.sort(Caml_obj.caml_compare, list));
+}
+
+function MakeHooks(M) {
+  var hooks = {
+    contents: /* [] */0
+  };
+  var add_hook = function (name, f) {
+    hooks.contents = {
+      hd: [
+        name,
+        f
+      ],
+      tl: hooks.contents
+    };
+    
+  };
+  var apply_hooks = function (sourcefile, intf) {
+    return fold_hooks(hooks.contents, sourcefile, intf);
+  };
+  return {
+          add_hook: add_hook,
+          apply_hooks: apply_hooks
+        };
+}
+
+var may = iter;
+
+var may_map = map;
+
+export {
+  Fatal_error ,
+  fatal_error ,
+  fatal_errorf ,
+  try_finally ,
+  protect_refs ,
+  map_end ,
+  map_left_right ,
+  for_all2 ,
+  replicate_list ,
+  list_remove ,
+  split_last ,
+  Stdlib ,
+  may ,
+  may_map ,
+  find_in_path ,
+  find_in_path_rel ,
+  find_in_path_uncap ,
+  remove_file ,
+  expand_directory ,
+  create_hashtable ,
+  copy_file ,
+  copy_file_chunk ,
+  string_of_file ,
+  output_to_file_via_temporary ,
+  log2 ,
+  align ,
+  no_overflow_add ,
+  no_overflow_sub ,
+  no_overflow_mul ,
+  no_overflow_lsl ,
+  Int_literal_converter ,
+  chop_extensions ,
+  search_substring ,
+  replace_substring ,
+  rev_split_words ,
+  get_ref ,
+  fst3 ,
+  snd3 ,
+  thd3 ,
+  fst4 ,
+  snd4 ,
+  thd4 ,
+  for4 ,
+  LongString ,
+  edit_distance ,
+  spellcheck ,
+  did_you_mean ,
+  cut_at ,
+  StringSet ,
+  StringMap ,
+  Color ,
+  normalise_eol ,
+  delete_eol_spaces ,
+  HookExnWrapper ,
+  HookExn ,
+  raise_direct_hook_exn ,
+  fold_hooks ,
+  MakeHooks ,
+  
+}
+/* StringSet Not a pure module */
diff --git a/analysis/examples/larger-project/src/misc.res b/analysis/examples/larger-project/src/misc.res
new file mode 100644
index 0000000000..ff95a24c65
--- /dev/null
+++ b/analysis/examples/larger-project/src/misc.res
@@ -0,0 +1,994 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* Errors */
+
+open P
+
+exception Fatal_error
+
+@raises(Fatal_error)
+let fatal_error = msg => {
+  print_string(">> Fatal error: ")
+  prerr_endline(msg)
+  raise(Fatal_error)
+}
+
+@raises(Fatal_error)
+let fatal_errorf = fmt => Format.kasprintf(fatal_error, fmt)
+
+/* Exceptions */
+
+@raises(genericException)
+let try_finally = (work, cleanup) => {
+  let result = try work() catch {
+  | e =>
+    cleanup()
+    raise(e)
+  }
+  cleanup()
+  result
+}
+
+type rec ref_and_value = R(ref<'a>, 'a): ref_and_value
+
+@raises(genericException)
+let protect_refs = {
+  let set_refs = l => List.iter((R(r, v)) => r := v, l)
+  (refs, f) => {
+    let backup = List.map((R(r, _)) => R(r, r.contents), refs)
+    set_refs(refs)
+    switch f() {
+    | x =>
+      set_refs(backup)
+      x
+    | exception e =>
+      set_refs(backup)
+      raise(e)
+    }
+  }
+}
+
+/* List functions */
+
+let rec map_end = (f, l1, l2) =>
+  switch l1 {
+  | list{} => l2
+  | list{hd, ...tl} => list{f(hd), ...map_end(f, tl, l2)}
+  }
+
+let rec map_left_right = (f, x) =>
+  switch x {
+  | list{} => list{}
+  | list{hd, ...tl} =>
+    let res = f(hd)
+    list{res, ...map_left_right(f, tl)}
+  }
+
+let rec for_all2 = (pred, l1, l2) =>
+  switch (l1, l2) {
+  | (list{}, list{}) => true
+  | (list{hd1, ...tl1}, list{hd2, ...tl2}) => pred(hd1, hd2) && for_all2(pred, tl1, tl2)
+  | (_, _) => false
+  }
+
+let rec replicate_list = (elem, n) =>
+  if n <= 0 {
+    list{}
+  } else {
+    list{elem, ...replicate_list(elem, n - 1)}
+  }
+
+let rec list_remove = (x, y) => assert false
+
+let rec split_last = x =>
+  switch x {
+  | list{} => assert false
+  | list{x} => (list{}, x)
+  | list{hd, ...tl} =>
+    let (lst, last) = split_last(tl)
+    (list{hd, ...lst}, last)
+  }
+
+module Stdlib = {
+  module List = {
+    type t<'a> = list<'a>
+
+    let rec compare = (cmp, l1, l2) =>
+      switch (l1, l2) {
+      | (list{}, list{}) => 0
+      | (list{}, list{_, ..._}) => -1
+      | (list{_, ..._}, list{}) => 1
+      | (list{h1, ...t1}, list{h2, ...t2}) =>
+        let c = cmp(h1, h2)
+        if c != 0 {
+          c
+        } else {
+          compare(cmp, t1, t2)
+        }
+      }
+
+    let rec equal = (eq, l1, l2) =>
+      switch (l1, l2) {
+      | (list{}, list{}) => true
+      | (list{hd1, ...tl1}, list{hd2, ...tl2}) => eq(hd1, hd2) && equal(eq, tl1, tl2)
+      | (_, _) => false
+      }
+
+    @raises(Invalid_argument)
+    let filter_map = (f, l) => {
+      @raises(Invalid_argument)
+      let rec aux = (acc, l) =>
+        switch l {
+        | list{} => List.rev(acc)
+        | list{h, ...t} =>
+          switch f(h) {
+          | None => aux(acc, t)
+          | Some(v) => aux(list{v, ...acc}, t)
+          }
+        }
+
+      aux(list{}, l)
+    }
+
+    @raises(Invalid_argument)
+    let map2_prefix = (f, l1, l2) => {
+      @raises(Invalid_argument)
+      let rec aux = (acc, l1, l2) =>
+        switch (l1, l2) {
+        | (list{}, _) => (List.rev(acc), l2)
+        | (list{_, ..._}, list{}) => raise(Invalid_argument("map2_prefix"))
+        | (list{h1, ...t1}, list{h2, ...t2}) =>
+          let h = f(h1, h2)
+          aux(list{h, ...acc}, t1, t2)
+        }
+
+      aux(list{}, l1, l2)
+    }
+
+    @raises(Invalid_argument)
+    let some_if_all_elements_are_some = l => {
+      @raises(Invalid_argument)
+      let rec aux = (acc, l) =>
+        switch l {
+        | list{} => Some(List.rev(acc))
+        | list{None, ..._} => None
+        | list{Some(h), ...t} => aux(list{h, ...acc}, t)
+        }
+
+      aux(list{}, l)
+    }
+
+    @raises(Invalid_argument)
+    let split_at = (n, l) => {
+      @raises(Invalid_argument)
+      let rec aux = (n, acc, l) =>
+        if n == 0 {
+          (List.rev(acc), l)
+        } else {
+          switch l {
+          | list{} => raise(Invalid_argument("split_at"))
+          | list{t, ...q} => aux(n - 1, list{t, ...acc}, q)
+          }
+        }
+
+      aux(n, list{}, l)
+    }
+  }
+
+  module Option = {
+    type t<'a> = option<'a>
+
+    let equal = (eq, o1, o2) =>
+      switch (o1, o2) {
+      | (None, None) => true
+      | (Some(e1), Some(e2)) => eq(e1, e2)
+      | (_, _) => false
+      }
+
+    let iter = (f, x) =>
+      switch x {
+      | Some(x) => f(x)
+      | None => ()
+      }
+
+    let map = (f, x) =>
+      switch x {
+      | Some(x) => Some(f(x))
+      | None => None
+      }
+
+    let fold = (f, a, b) =>
+      switch a {
+      | None => b
+      | Some(a) => f(a, b)
+      }
+
+    let value_default = (f, ~default, a) =>
+      switch a {
+      | None => default
+      | Some(a) => f(a)
+      }
+  }
+
+  module Array = {
+    @raises(Invalid_argument)
+    let exists2 = (p, a1, a2) => {
+      let n = Array.length(a1)
+      if Array.length(a2) != n {
+        invalid_arg("Misc.Stdlib.Array.exists2")
+      }
+      let rec loop = i =>
+        if i == n {
+          false
+        } else if p(Array.unsafe_get(a1, i), Array.unsafe_get(a2, i)) {
+          true
+        } else {
+          loop(succ(i))
+        }
+      loop(0)
+    }
+  }
+}
+
+let may = Stdlib.Option.iter
+let may_map = Stdlib.Option.map
+
+/* File functions */
+
+@raises(Not_found)
+let find_in_path = (path, name) =>
+  if !Filename.is_implicit(name) {
+    if Sys.file_exists(name) {
+      name
+    } else {
+      raise(Not_found)
+    }
+  } else {
+    @raises(Not_found)
+    let rec try_dir = x =>
+      switch x {
+      | list{} => raise(Not_found)
+      | list{dir, ...rem} =>
+        let fullname = Filename.concat(dir, name)
+        if Sys.file_exists(fullname) {
+          fullname
+        } else {
+          try_dir(rem)
+        }
+      }
+    try_dir(path)
+  }
+
+@raises(Not_found)
+let find_in_path_rel = (path, name) => {
+  let rec simplify = s => {
+    open Filename
+    let base = basename(s)
+    let dir = dirname(s)
+    if dir == s {
+      dir
+    } else if base == current_dir_name {
+      simplify(dir)
+    } else {
+      concat(simplify(dir), base)
+    }
+  }
+
+  @raises(Not_found)
+  let rec try_dir = x =>
+    switch x {
+    | list{} => raise(Not_found)
+    | list{dir, ...rem} =>
+      let fullname = simplify(Filename.concat(dir, name))
+      if Sys.file_exists(fullname) {
+        fullname
+      } else {
+        try_dir(rem)
+      }
+    }
+  try_dir(path)
+}
+
+@raises(Not_found)
+let find_in_path_uncap = (path, name) => {
+  let uname = String.uncapitalize_ascii(name)
+
+  @raises(Not_found)
+  let rec try_dir = x =>
+    switch x {
+    | list{} => raise(Not_found)
+    | list{dir, ...rem} =>
+      let fullname = Filename.concat(dir, name)
+      and ufullname = Filename.concat(dir, uname)
+      if Sys.file_exists(ufullname) {
+        ufullname
+      } else if Sys.file_exists(fullname) {
+        fullname
+      } else {
+        try_dir(rem)
+      }
+    }
+  try_dir(path)
+}
+
+
+let remove_file = filename =>
+  try if Sys.file_exists(filename) {
+    Sys.remove(filename)
+  } catch {
+  | Sys_error(_msg) => ()
+  }
+
+/* Expand a -I option: if it starts with +, make it relative to the standard
+ library directory */
+
+@raises(Invalid_argument)
+let expand_directory = (alt, s) =>
+  if String.length(s) > 0 && String.get(s, 0) == '+' {
+    Filename.concat(alt, String.sub(s, 1, String.length(s) - 1))
+  } else {
+    s
+  }
+
+/* Hashtable functions */
+
+let create_hashtable = (size, init) => {
+  let tbl = Hashtbl.create(size)
+  List.iter(((key, data)) => Hashtbl.add(tbl, key, data), init)
+  tbl
+}
+
+/* File copy */
+
+@raises(Invalid_argument)
+let copy_file = (ic, oc) => {
+  let buff = Bytes.create(0x1000)
+
+  @raises(Invalid_argument)
+  let rec copy = () => {
+    let n = input(ic, buff, 0, 0x1000)
+    if n == 0 {
+      ()
+    } else {
+      output(oc, buff, 0, n)
+      copy()
+    }
+  }
+  copy()
+}
+
+@raises(Invalid_argument)
+let copy_file_chunk = (ic, oc, len) => {
+  let buff = Bytes.create(0x1000)
+
+  @raises([End_of_file, Invalid_argument])
+  let rec copy = n =>
+    if n <= 0 {
+      ()
+    } else {
+      let r = input(ic, buff, 0, min(n, 0x1000))
+      if r == 0 {
+        raise(End_of_file)
+      } else {
+        output(oc, buff, 0, r)
+        copy(n - r)
+      }
+    }
+  copy(len)
+}
+
+@raises(Invalid_argument)
+let string_of_file = ic => {
+  let b = Buffer.create(0x10000)
+  let buff = Bytes.create(0x1000)
+
+  @raises(Invalid_argument)
+  let rec copy = () => {
+    let n = input(ic, buff, 0, 0x1000)
+    if n == 0 {
+      Buffer.contents(b)
+    } else {
+      Buffer.add_subbytes(b, buff, 0, n)
+      copy()
+    }
+  }
+  copy()
+}
+
+@raises([Sys_error, genericException])
+let output_to_file_via_temporary = (~mode=list{Open_text}, filename, fn) => {
+  let (temp_filename, oc) = open_temp_file(
+    ~mode,
+    ~perms=0o666,
+    ~temp_dir=Filename.dirname(filename),
+    Filename.basename(filename),
+    ".tmp",
+  )
+  /* The 0o666 permissions will be modified by the umask.  It's just
+       like what [open_out] and [open_out_bin] do.
+       With temp_dir = dirname filename, we ensure that the returned
+       temp file is in the same directory as filename itself, making
+       it safe to rename temp_filename to filename later.
+       With prefix = basename filename, we are almost certain that
+       the first generated name will be unique.  A fixed prefix
+       would work too but might generate more collisions if many
+       files are being produced simultaneously in the same directory. */
+  switch fn(temp_filename, oc) {
+  | res =>
+    close_out(oc)
+    try {
+      Sys.rename(temp_filename, filename)
+      res
+    } catch {
+    | exn =>
+      remove_file(temp_filename)
+      raise(exn)
+    }
+  | exception exn =>
+    close_out(oc)
+    remove_file(temp_filename)
+    raise(exn)
+  }
+}
+
+/* Integer operations */
+
+let rec log2 = n =>
+  if n <= 1 {
+    0
+  } else {
+    1 + log2(asr(n, 1))
+  }
+
+let align = (n, a) =>
+  if n >= 0 {
+    land(n + a - 1, -a)
+  } else {
+    land(n, -a)
+  }
+
+let no_overflow_add = (a, b) => lor(lxor(a, b), lxor(a, lnot(a + b))) < 0
+
+let no_overflow_sub = (a, b) => lor(lxor(a, lnot(b)), lxor(b, a - b)) < 0
+
+@raises(Division_by_zero)
+let no_overflow_mul = (a, b) => b != 0 && a * b / b == a
+
+let no_overflow_lsl = (a, k) =>
+  0 <= k && (k < Sys.word_size && (asr(min_int, k) <= a && a <= asr(max_int, k)))
+
+module Int_literal_converter = {
+  /* To convert integer literals, allowing max_int + 1 (PR#4210) */
+  @raises(Invalid_argument)
+  let cvt_int_aux = (str, neg, of_string) =>
+    if String.length(str) == 0 || String.get(str, 0) == '-' {
+      of_string(str)
+    } else {
+      neg(of_string("-" ++ str))
+    }
+  @raises([Failure, Invalid_argument])
+  let int = s => cvt_int_aux(s, \"~-", int_of_string)
+  @raises(Invalid_argument)
+  let int32 = s => cvt_int_aux(s, Int32.neg, Int32.of_string)
+  @raises(Invalid_argument)
+  let int64 = s => cvt_int_aux(s, Int64.neg, Int64.of_string)
+  @raises(Invalid_argument)
+  let nativeint = s => cvt_int_aux(s, Nativeint.neg, Nativeint.of_string)
+}
+
+/* String operations */
+
+@raises(Invalid_argument)
+let chop_extensions = file => {
+  let dirname = Filename.dirname(file) and basename = Filename.basename(file)
+  try {
+    let pos = String.index(basename, '.')
+    let basename = String.sub(basename, 0, pos)
+    if Filename.is_implicit(file) && dirname == Filename.current_dir_name {
+      basename
+    } else {
+      Filename.concat(dirname, basename)
+    }
+  } catch {
+  | Not_found => file
+  }
+}
+
+@raises(Invalid_argument)
+let search_substring = (pat, str, start) => {
+  @raises([Invalid_argument, Not_found])
+  let rec search = (i, j) =>
+    if j >= String.length(pat) {
+      i
+    } else if i + j >= String.length(str) {
+      raise(Not_found)
+    } else if String.get(str, i + j) == String.get(pat, j) {
+      search(i, j + 1)
+    } else {
+      search(i + 1, 0)
+    }
+  search(start, 0)
+}
+
+@raises(Invalid_argument)
+let replace_substring = (~before, ~after, str) => {
+  @raises(Invalid_argument)
+  let rec search = (acc, curr) =>
+    switch search_substring(before, str, curr) {
+    | next =>
+      let prefix = String.sub(str, curr, next - curr)
+      search(list{prefix, ...acc}, next + String.length(before))
+    | exception Not_found =>
+      let suffix = String.sub(str, curr, String.length(str) - curr)
+      List.rev(list{suffix, ...acc})
+    }
+  String.concat(after, search(list{}, 0))
+}
+
+@raises(Invalid_argument)
+let rev_split_words = s => {
+  @raises(Invalid_argument)
+  let rec split1 = (res, i) =>
+    if i >= String.length(s) {
+      res
+    } else {
+      switch String.get(s, i) {
+      | ' ' | '\t' | '\r' | '\n' => split1(res, i + 1)
+      | _ => split2(res, i, i + 1)
+      }
+    }
+  @raises(Invalid_argument)
+  and split2 = (res, i, j) =>
+    if j >= String.length(s) {
+      list{String.sub(s, i, j - i), ...res}
+    } else {
+      switch String.get(s, j) {
+      | ' ' | '\t' | '\r' | '\n' => split1(list{String.sub(s, i, j - i), ...res}, j + 1)
+      | _ => split2(res, i, j + 1)
+      }
+    }
+  split1(list{}, 0)
+}
+
+let get_ref = r => {
+  let v = r.contents
+  r := list{}
+  v
+}
+
+let fst3 = ((x, _, _)) => x
+let snd3 = ((_, x, _)) => x
+let thd3 = ((_, _, x)) => x
+
+let fst4 = ((x, _, _, _)) => x
+let snd4 = ((_, x, _, _)) => x
+let thd4 = ((_, _, x, _)) => x
+let for4 = ((_, _, _, x)) => x
+
+module LongString = {
+  type t = array<bytes>
+
+  @raises([Division_by_zero, Invalid_argument])
+  let create = str_size => {
+    let tbl_size = str_size / Sys.max_string_length + 1
+    let tbl = Array.make(tbl_size, Bytes.empty)
+    for i in 0 to tbl_size - 2 {
+      tbl[i] = Bytes.create(Sys.max_string_length)
+    }
+    tbl[tbl_size - 1] = Bytes.create(mod(str_size, Sys.max_string_length))
+    tbl
+  }
+
+  @raises(Invalid_argument)
+  let length = tbl => {
+    let tbl_size = Array.length(tbl)
+    Sys.max_string_length * (tbl_size - 1) + Bytes.length(tbl[tbl_size - 1])
+  }
+
+  @raises([Division_by_zero, Invalid_argument])
+  let get = (tbl, ind) =>
+    Bytes.get(tbl[ind / Sys.max_string_length], mod(ind, Sys.max_string_length))
+
+  @raises([Division_by_zero, Invalid_argument])
+  let set = (tbl, ind, c) =>
+    Bytes.set(tbl[ind / Sys.max_string_length], mod(ind, Sys.max_string_length), c)
+
+  @raises([Division_by_zero, Invalid_argument])
+  let blit = (src, srcoff, dst, dstoff, len) =>
+    for i in 0 to len - 1 {
+      set(dst, dstoff + i, get(src, srcoff + i))
+    }
+
+  @raises([Division_by_zero, Invalid_argument])
+  let output = (oc, tbl, pos, len) =>
+    for i in pos to pos + len - 1 {
+      output_char(oc, get(tbl, i))
+    }
+
+  @raises([Division_by_zero, Invalid_argument])
+  let unsafe_blit_to_bytes = (src, srcoff, dst, dstoff, len) =>
+    for i in 0 to len - 1 {
+      Bytes.unsafe_set(dst, dstoff + i, get(src, srcoff + i))
+    }
+
+  @raises([Division_by_zero, End_of_file, Invalid_argument])
+  let input_bytes = (ic, len) => {
+    let tbl = create(len)
+    Array.iter(str => really_input(ic, str, 0, Bytes.length(str)), tbl)
+    tbl
+  }
+}
+
+@raises(Invalid_argument)
+let edit_distance = (a, b, cutoff) => {
+  let (la, lb) = (String.length(a), String.length(b))
+  let cutoff = /* using max_int for cutoff would cause overflows in (i + cutoff + 1);
+   we bring it back to the (max la lb) worstcase */
+  min(max(la, lb), cutoff)
+  if abs(la - lb) > cutoff {
+    None
+  } else {
+    /* initialize with 'cutoff + 1' so that not-yet-written-to cases have
+       the worst possible cost; this is useful when computing the cost of
+       a case just at the boundary of the cutoff diagonal. */
+    let m = Array.make_matrix(la + 1, lb + 1, cutoff + 1)
+    m[0][0] = 0
+    for i in 1 to la {
+      m[i][0] = i
+    }
+    for j in 1 to lb {
+      m[0][j] = j
+    }
+    for i in 1 to la {
+      for j in max(1, i - cutoff - 1) to min(lb, i + cutoff + 1) {
+        let cost = if String.get(a, i - 1) == String.get(b, j - 1) {
+          0
+        } else {
+          1
+        }
+        let best = /* insert, delete or substitute */
+        min(1 + min(m[i - 1][j], m[i][j - 1]), m[i - 1][j - 1] + cost)
+
+        let best = /* swap two adjacent letters; we use "cost" again in case of
+             a swap between two identical letters; this is slightly
+             redundant as this is a double-substitution case, but it
+             was done this way in most online implementations and
+             imitation has its virtues */
+        if (
+          !(
+            i > 1 &&
+              (j > 1 &&
+              (String.get(a, i - 1) == String.get(b, j - 2) &&
+                String.get(a, i - 2) == String.get(b, j - 1)))
+          )
+        ) {
+          best
+        } else {
+          min(best, m[i - 2][j - 2] + cost)
+        }
+
+        m[i][j] = best
+      }
+    }
+    let result = m[la][lb]
+    if result > cutoff {
+      None
+    } else {
+      Some(result)
+    }
+  }
+}
+
+@raises(Invalid_argument)
+let spellcheck = (env, name) => {
+  let cutoff = switch String.length(name) {
+  | 1 | 2 => 0
+  | 3 | 4 => 1
+  | 5 | 6 => 2
+  | _ => 3
+  }
+
+  @raises(Invalid_argument)
+  let compare = (target, acc, head) =>
+    switch edit_distance(target, head, cutoff) {
+    | None => acc
+    | Some(dist) =>
+      let (best_choice, best_dist) = acc
+      if dist < best_dist {
+        (list{head}, dist)
+      } else if dist == best_dist {
+        (list{head, ...best_choice}, dist)
+      } else {
+        acc
+      }
+    }
+
+  fst(List.fold_left(compare(name), (list{}, max_int), env))
+}
+
+let did_you_mean = (ppf, get_choices) => {
+  /* flush now to get the error report early, in the (unheard of) case
+     where the search in the get_choices function would take a bit of
+     time; in the worst case, the user has seen the error, she can
+     interrupt the process before the spell-checking terminates. */
+  Format.fprintf(ppf, "@?")
+  switch get_choices() {
+  | list{} => ()
+  | choices =>
+    let (rest, last) = split_last(choices)
+    Format.fprintf(
+      ppf,
+      "@\nHint: Did you mean %s%s%s?@?",
+      String.concat(", ", rest),
+      if rest == list{} {
+        ""
+      } else {
+        " or "
+      },
+      last,
+    )
+  }
+}
+
+@raises([Invalid_argument, Not_found])
+let cut_at = (s, c) => {
+  let pos = String.index(s, c)
+  (String.sub(s, 0, pos), String.sub(s, pos + 1, String.length(s) - pos - 1))
+}
+
+module StringSet = Set.Make({
+  type t = string
+  let compare = compare
+})
+module StringMap = Map.Make({
+  type t = string
+  let compare = compare
+})
+
+/* Color handling */
+module Color = {
+  /* use ANSI color codes, see https://en.wikipedia.org/wiki/ANSI_escape_code */
+  type color =
+    | Black
+    | Red
+    | Green
+    | Yellow
+    | Blue
+    | Magenta
+    | Cyan
+    | White
+
+  type style =
+    | FG(color) /* foreground */
+    | BG(color) /* background */
+    | Bold
+    | Reset
+
+  let ansi_of_color = x =>
+    switch x {
+    | Black => "0"
+    | Red => "1"
+    | Green => "2"
+    | Yellow => "3"
+    | Blue => "4"
+    | Magenta => "5"
+    | Cyan => "6"
+    | White => "7"
+    }
+
+  let code_of_style = x =>
+    switch x {
+    | FG(c) => "3" ++ ansi_of_color(c)
+    | BG(c) => "4" ++ ansi_of_color(c)
+    | Bold => "1"
+    | Reset => "0"
+    }
+
+  let ansi_of_style_l = l => {
+    let s = switch l {
+    | list{} => code_of_style(Reset)
+    | list{s} => code_of_style(s)
+    | _ => String.concat(";", List.map(code_of_style, l))
+    }
+
+    "\x1b[" ++ (s ++ "m")
+  }
+
+  type styles = {
+    error: list<style>,
+    warning: list<style>,
+    loc: list<style>,
+  }
+
+  let default_styles = {
+    warning: list{Bold, FG(Magenta)},
+    error: list{Bold, FG(Red)},
+    loc: list{Bold},
+  }
+
+  let cur_styles = ref(default_styles)
+  let get_styles = () => cur_styles.contents
+  let set_styles = s => cur_styles := s
+
+  /* map a tag to a style, if the tag is known.
+   @raise Not_found otherwise */
+  @raises(Not_found)
+  let style_of_tag = s =>
+    switch s {
+    | "error" => cur_styles.contents.error
+    | "warning" => cur_styles.contents.warning
+    | "loc" => cur_styles.contents.loc
+    | _ => raise(Not_found)
+    }
+
+  let color_enabled = ref(true)
+
+  /* either prints the tag of [s] or delegates to [or_else] */
+  let mark_open_tag = (~or_else, s) =>
+    try {
+      let style = style_of_tag(s)
+      if color_enabled.contents {
+        ansi_of_style_l(style)
+      } else {
+        ""
+      }
+    } catch {
+    | Not_found => or_else(s)
+    }
+
+  let mark_close_tag = (~or_else, s) =>
+    try {
+      let _ = style_of_tag(s)
+      if color_enabled.contents {
+        ansi_of_style_l(list{Reset})
+      } else {
+        ""
+      }
+    } catch {
+    | Not_found => or_else(s)
+    }
+
+  /* add color handling to formatter [ppf] */
+  let set_color_tag_handling = ppf => {
+    assert false
+  }
+
+  external isatty: out_channel => bool = "caml_sys_isatty"
+
+  /* reasonable heuristic on whether colors should be enabled */
+  let should_enable_color = () => {
+    let term = try Sys.getenv("TERM") catch {
+    | Not_found => ""
+    }
+    term != "dumb" && (term != "" && isatty(stderr))
+  }
+
+  type setting = Auto | Always | Never
+
+  let setup = {
+    let first = ref(true) /* initialize only once */
+    let formatter_l = list{Format.std_formatter, Format.err_formatter, Format.str_formatter}
+
+    o => {
+      if first.contents {
+        first := false
+        Format.set_mark_tags(true)
+        List.iter(set_color_tag_handling, formatter_l)
+        color_enabled :=
+          switch o {
+          | Some(Always) => true
+          | Some(Auto) => should_enable_color()
+          | Some(Never) => false
+          | None => should_enable_color()
+          }
+      }
+      ()
+    }
+  }
+}
+
+@raises(Invalid_argument)
+let normalise_eol = s => {
+  let b = Buffer.create(80)
+  for i in 0 to String.length(s) - 1 {
+    if String.get(s, i) != '\r' {
+      Buffer.add_char(b, String.get(s, i))
+    }
+  }
+  Buffer.contents(b)
+}
+
+@raises(Invalid_argument)
+let delete_eol_spaces = src => {
+  let len_src = String.length(src)
+  let dst = Bytes.create(len_src)
+
+  @raises(Invalid_argument)
+  let rec loop = (i_src, i_dst) =>
+    if i_src == len_src {
+      i_dst
+    } else {
+      switch String.get(src, i_src) {
+      | ' ' | '\t' => loop_spaces(1, i_src + 1, i_dst)
+      | c =>
+        Bytes.set(dst, i_dst, c)
+        loop(i_src + 1, i_dst + 1)
+      }
+    }
+  @raises(Invalid_argument)
+  and loop_spaces = (spaces, i_src, i_dst) =>
+    if i_src == len_src {
+      i_dst
+    } else {
+      switch String.get(src, i_src) {
+      | ' ' | '\t' => loop_spaces(spaces + 1, i_src + 1, i_dst)
+      | '\n' =>
+        Bytes.set(dst, i_dst, '\n')
+        loop(i_src + 1, i_dst + 1)
+      | _ =>
+        for n in 0 to spaces {
+          Bytes.set(dst, i_dst + n, String.get(src, i_src - spaces + n))
+        }
+        loop(i_src + 1, i_dst + spaces + 1)
+      }
+    }
+
+  let stop = loop(0, 0)
+  Bytes.sub_string(dst, 0, stop)
+}
+
+type hook_info = {sourcefile: string}
+
+exception HookExnWrapper({error: exn, hook_name: string, hook_info: hook_info})
+
+exception HookExn(exn)
+
+@raises(HookExn)
+let raise_direct_hook_exn = e => raise(HookExn(e))
+
+@raises([HookExnWrapper, genericException])
+let fold_hooks = (list, hook_info, ast) =>
+  List.fold_left(
+    (ast, (hook_name, f)) =>
+      try f(hook_info, ast) catch {
+      | HookExn(e) => raise(e)
+      | error => raise(HookExnWrapper({error: error, hook_name: hook_name, hook_info: hook_info}))
+      },
+    /* when explicit reraise with backtrace will be available,
+     it should be used here */
+    ast,
+    List.sort(compare, list),
+  )
+
+module type HookSig = {
+  type t
+
+  let add_hook: (string, (hook_info, t) => t) => unit
+  let apply_hooks: (hook_info, t) => t
+}
+
+module MakeHooks = (
+  M: {
+    type t
+  },
+): (HookSig with type t = M.t) => {
+  type t = M.t
+
+  let hooks = ref(list{})
+  let add_hook = (name, f) => hooks := list{(name, f), ...hooks.contents}
+  @raises([HookExnWrapper, genericException])
+  let apply_hooks = (sourcefile, intf) => fold_hooks(hooks.contents, sourcefile, intf)
+}
diff --git a/analysis/examples/larger-project/src/nativeint.js b/analysis/examples/larger-project/src/nativeint.js
new file mode 100644
index 0000000000..d5b185e33c
--- /dev/null
+++ b/analysis/examples/larger-project/src/nativeint.js
@@ -0,0 +1,36 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as Caml_format from "rescript/lib/es6/caml_format.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function to_string(n) {
+  return Caml_format.caml_nativeint_format("%d", n);
+}
+
+function of_string_opt(s) {
+  try {
+    return Caml_option.some(Caml_format.caml_nativeint_of_string(s));
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Failure") {
+      return ;
+    }
+    throw exn;
+  }
+}
+
+var compare = Caml_obj.caml_compare;
+
+var equal = Caml_obj.caml_equal;
+
+export {
+  to_string ,
+  of_string_opt ,
+  compare ,
+  equal ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/nativeint.res b/analysis/examples/larger-project/src/nativeint.res
new file mode 100644
index 0000000000..d6f4375871
--- /dev/null
+++ b/analysis/examples/larger-project/src/nativeint.res
@@ -0,0 +1,54 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* Module [Nativeint]: processor-native integers */
+
+type nativeint
+
+external neg: nativeint => nativeint = "%nativeint_neg"
+external add: (nativeint, nativeint) => nativeint = "%nativeint_add"
+external sub: (nativeint, nativeint) => nativeint = "%nativeint_sub"
+external mul: (nativeint, nativeint) => nativeint = "%nativeint_mul"
+external div: (nativeint, nativeint) => nativeint = "%nativeint_div"
+external rem: (nativeint, nativeint) => nativeint = "%nativeint_mod"
+external logand: (nativeint, nativeint) => nativeint = "%nativeint_and"
+external logor: (nativeint, nativeint) => nativeint = "%nativeint_or"
+external logxor: (nativeint, nativeint) => nativeint = "%nativeint_xor"
+external shift_left: (nativeint, int) => nativeint = "%nativeint_lsl"
+external shift_right: (nativeint, int) => nativeint = "%nativeint_asr"
+external shift_right_logical: (nativeint, int) => nativeint = "%nativeint_lsr"
+external of_int: int => nativeint = "%nativeint_of_int"
+external to_int: nativeint => int = "%nativeint_to_int"
+external of_float: float => nativeint = "caml_nativeint_of_float"
+external to_float: nativeint => float = "caml_nativeint_to_float"
+external of_int32: int32 => nativeint = "%nativeint_of_int32"
+external to_int32: nativeint => int32 = "%nativeint_to_int32"
+
+external format: (string, nativeint) => string = "caml_nativeint_format"
+let to_string = n => format("%d", n)
+
+external of_string: string => nativeint = "caml_nativeint_of_string"
+
+let of_string_opt = s =>
+  /* TODO: expose a non-raising primitive directly. */
+  try Some(of_string(s)) catch {
+  | Failure(_) => None
+  }
+
+type t = nativeint
+
+let compare = (x: t, y: t) => compare(x, y)
+let equal = (x: t, y: t) => compare(x, y) == 0
+
diff --git a/analysis/examples/larger-project/src/numbers.js b/analysis/examples/larger-project/src/numbers.js
new file mode 100644
index 0000000000..e99c78e181
--- /dev/null
+++ b/analysis/examples/larger-project/src/numbers.js
@@ -0,0 +1,57 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Int64 from "rescript/lib/es6/int64.js";
+import * as Caml_int64 from "rescript/lib/es6/caml_int64.js";
+
+var Int_base = {};
+
+var Int = {};
+
+function of_int_exn(i) {
+  return i;
+}
+
+function to_int(i) {
+  return i;
+}
+
+var Int8 = {
+  zero: 0,
+  one: 1,
+  of_int_exn: of_int_exn,
+  to_int: to_int
+};
+
+function of_int_exn$1(i) {
+  return i;
+}
+
+var lower_int64 = Caml_int64.neg(Caml_int64.lsl_(Int64.one, 15));
+
+var upper_int64 = Caml_int64.sub(Caml_int64.lsl_(Int64.one, 15), Int64.one);
+
+var of_int64_exn = Caml_int64.to_int32;
+
+function to_int$1(t) {
+  return t;
+}
+
+var Int16 = {
+  of_int_exn: of_int_exn$1,
+  lower_int64: lower_int64,
+  upper_int64: upper_int64,
+  of_int64_exn: of_int64_exn,
+  to_int: to_int$1
+};
+
+var Float = {};
+
+export {
+  Int_base ,
+  Int ,
+  Int8 ,
+  Int16 ,
+  Float ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/numbers.res b/analysis/examples/larger-project/src/numbers.res
new file mode 100644
index 0000000000..19f13504a5
--- /dev/null
+++ b/analysis/examples/larger-project/src/numbers.res
@@ -0,0 +1,48 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Pierre Chambart, OCamlPro */
+/* Mark Shinwell and Leo White, Jane Street Europe */
+/*  */
+/* Copyright 2013--2016 OCamlPro SAS */
+/* Copyright 2014--2016 Jane Street Group LLC */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+module Int_base = {}
+
+module Int = {}
+
+module Int8 = {
+  type t = int
+
+  let zero = 0
+  let one = 1
+
+  let of_int_exn = i => i
+
+  let to_int = i => i
+}
+
+module Int16 = {
+  type t = int
+
+  let of_int_exn = i => i
+
+  let lower_int64 = Int64.neg(Int64.shift_left(Int64.one, 15))
+  let upper_int64 = Int64.sub(Int64.shift_left(Int64.one, 15), Int64.one)
+
+  let of_int64_exn = i => Int64.to_int(i)
+
+  let to_int = t => t
+}
+
+module Float = {
+  type t = float
+}
+
diff --git a/analysis/examples/larger-project/src/parsetree.js b/analysis/examples/larger-project/src/parsetree.js
new file mode 100644
index 0000000000..d856702bfe
--- /dev/null
+++ b/analysis/examples/larger-project/src/parsetree.js
@@ -0,0 +1,2 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
diff --git a/analysis/examples/larger-project/src/parsetree.res b/analysis/examples/larger-project/src/parsetree.res
new file mode 100644
index 0000000000..6fde4a6749
--- /dev/null
+++ b/analysis/examples/larger-project/src/parsetree.res
@@ -0,0 +1,852 @@
+@@ocaml.text(
+  /* ************************************************************************ */
+  /*  */
+  /* OCaml */
+  /*  */
+  /* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+  /*  */
+  /* Copyright 1996 Institut National de Recherche en Informatique et */
+  /* en Automatique. */
+  /*  */
+  /* All rights reserved.  This file is distributed under the terms of */
+  /* the GNU Lesser General Public License version 2.1, with the */
+  /* special exception on linking described in the file LICENSE. */
+  /*  */
+  /* ************************************************************************ */
+
+  " Abstract syntax tree produced by parsing "
+)
+
+open Asttypes
+
+type constant =
+  | Pconst_integer(string, option<char>)
+  /* 3 3l 3L 3n
+
+     Suffixes [g-z][G-Z] are accepted by the parser.
+     Suffixes except 'l', 'L' and 'n' are rejected by the typechecker
+ */
+  | Pconst_char(char)
+  /* 'c' */
+  | Pconst_string(string, option<string>)
+  /* "constant"
+     {delim|other constant|delim}
+ */
+  | Pconst_float(string, option<char>)
+@@ocaml.text(
+  /* 3.4 2e5 1.4e-4
+
+     Suffixes [g-z][G-Z] are accepted by the parser.
+     Suffixes are rejected by the typechecker.
+ */
+
+  " {1 Extension points} "
+)
+
+type rec attribute = (loc<string>, payload)
+/* [@id ARG]
+          [@@id ARG]
+
+          Metadata containers passed around within the AST.
+          The compiler ignores unknown attributes.
+ */
+
+and extension = (loc<string>, payload)
+/* [%id ARG]
+         [%%id ARG]
+
+         Sub-language placeholder -- rejected by the typechecker.
+ */
+
+and attributes = list<attribute>
+
+and payload =
+  | PStr(structure)
+  | PSig(signature) /* : SIG */
+  | PTyp(core_type) /* : T */
+  | PPat(pattern, option<expression>) /* ? P  or  ? P when E */
+
+/* Type expressions */
+
+@ocaml.text(" {1 Core language} ")
+and core_type = {
+  ptyp_desc: core_type_desc,
+  ptyp_loc: Location.t,
+  ptyp_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and core_type_desc =
+  | Ptyp_any
+  /* _ */
+  | Ptyp_var(string)
+  /* 'a */
+  | Ptyp_arrow(arg_label, core_type, core_type)
+  /* T1 -> T2       Simple
+           ~l:T1 -> T2    Labelled
+           ?l:T1 -> T2    Optional
+ */
+  | Ptyp_tuple(list<core_type>)
+  /* T1 * ... * Tn
+
+           Invariant: n >= 2
+ */
+  | Ptyp_constr(loc<Longident.t>, list<core_type>)
+  /* tconstr
+           T tconstr
+           (T1, ..., Tn) tconstr
+ */
+  | Ptyp_object(list<object_field>, closed_flag)
+  /* < l1:T1; ...; ln:Tn >     (flag = Closed)
+           < l1:T1; ...; ln:Tn; .. > (flag = Open)
+ */
+  | Ptyp_class(loc<Longident.t>, list<core_type>)
+  /* #tconstr
+           T #tconstr
+           (T1, ..., Tn) #tconstr
+ */
+  | Ptyp_alias(core_type, string)
+  /* T as 'a */
+  | Ptyp_variant(list<row_field>, closed_flag, option<list<label>>)
+  /* [ `A|`B ]         (flag = Closed; labels = None)
+           [> `A|`B ]        (flag = Open;   labels = None)
+           [< `A|`B ]        (flag = Closed; labels = Some [])
+           [< `A|`B > `X `Y ](flag = Closed; labels = Some ["X";"Y"])
+ */
+  | Ptyp_poly(list<loc<string>>, core_type)
+  /* 'a1 ... 'an. T
+
+           Can only appear in the following context:
+
+           - As the core_type of a Ppat_constraint node corresponding
+             to a constraint on a let-binding: let x : 'a1 ... 'an. T
+             = e ...
+
+           - Under Cfk_virtual for methods (not values).
+
+           - As the core_type of a Pctf_method node.
+
+           - As the core_type of a Pexp_poly node.
+
+           - As the pld_type field of a label_declaration.
+
+           - As a core_type of a Ptyp_object node.
+ */
+
+  | Ptyp_package(package_type)
+  /* (module S) */
+  | Ptyp_extension(extension)
+/* [%id] */
+
+and package_type = (loc<Longident.t>, list<(loc<Longident.t>, core_type)>)
+/*
+        (module S)
+        (module S with type t1 = T1 and ... and tn = Tn)
+ */
+
+and row_field =
+  | Rtag(loc<label>, attributes, bool, list<core_type>)
+  /* [`A]                   ( true,  [] )
+           [`A of T]              ( false, [T] )
+           [`A of T1 & .. & Tn]   ( false, [T1;...Tn] )
+           [`A of & T1 & .. & Tn] ( true,  [T1;...Tn] )
+
+          - The 2nd field is true if the tag contains a
+            constant (empty) constructor.
+          - '&' occurs when several types are used for the same constructor
+            (see 4.2 in the manual)
+
+          - TODO: switch to a record representation, and keep location
+ */
+  | Rinherit(core_type)
+/* [ T ] */
+
+and object_field =
+  | Otag(loc<label>, attributes, core_type)
+  | Oinherit(core_type)
+
+/* Patterns */
+
+and pattern = {
+  ppat_desc: pattern_desc,
+  ppat_loc: Location.t,
+  ppat_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and pattern_desc =
+  | Ppat_any
+  /* _ */
+  | Ppat_var(loc<string>)
+  /* x */
+  | Ppat_alias(pattern, loc<string>)
+  /* P as 'a */
+  | Ppat_constant(constant)
+  /* 1, 'a', "true", 1.0, 1l, 1L, 1n */
+  | Ppat_interval(constant, constant)
+  /* 'a'..'z'
+
+           Other forms of interval are recognized by the parser
+           but rejected by the type-checker. */
+  | Ppat_tuple(list<pattern>)
+  /* (P1, ..., Pn)
+
+           Invariant: n >= 2
+ */
+  | Ppat_construct(loc<Longident.t>, option<pattern>)
+  /* C                None
+           C P              Some P
+           C (P1, ..., Pn)  Some (Ppat_tuple [P1; ...; Pn])
+ */
+  | Ppat_variant(label, option<pattern>)
+  /* `A             (None)
+           `A P           (Some P)
+ */
+  | Ppat_record(list<(loc<Longident.t>, pattern)>, closed_flag)
+  /* { l1=P1; ...; ln=Pn }     (flag = Closed)
+           { l1=P1; ...; ln=Pn; _}   (flag = Open)
+
+           Invariant: n > 0
+ */
+  | Ppat_array(list<pattern>)
+  /* [| P1; ...; Pn |] */
+  | Ppat_or(pattern, pattern)
+  /* P1 | P2 */
+  | Ppat_constraint(pattern, core_type)
+  /* (P : T) */
+  | Ppat_type(loc<Longident.t>)
+  /* #tconst */
+  | Ppat_lazy(pattern)
+  /* lazy P */
+  | Ppat_unpack(loc<string>)
+  /* (module P)
+           Note: (module P : S) is represented as
+           Ppat_constraint(Ppat_unpack, Ptyp_package)
+ */
+  | Ppat_exception(pattern)
+  /* exception P */
+  | Ppat_extension(extension)
+  /* [%id] */
+  | Ppat_open(loc<Longident.t>, pattern)
+/* M.(P) */
+
+/* Value expressions */
+
+and expression = {
+  pexp_desc: expression_desc,
+  pexp_loc: Location.t,
+  pexp_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and expression_desc =
+  | Pexp_ident(loc<Longident.t>)
+  /* x
+           M.x
+ */
+  | Pexp_constant(constant)
+  /* 1, 'a', "true", 1.0, 1l, 1L, 1n */
+  | Pexp_let(rec_flag, list<value_binding>, expression)
+  /* let P1 = E1 and ... and Pn = EN in E       (flag = Nonrecursive)
+           let rec P1 = E1 and ... and Pn = EN in E   (flag = Recursive)
+ */
+  | Pexp_function(list<case>)
+  /* function P1 -> E1 | ... | Pn -> En */
+  | Pexp_fun(arg_label, option<expression>, pattern, expression)
+  /* fun P -> E1                          (Simple, None)
+           fun ~l:P -> E1                       (Labelled l, None)
+           fun ?l:P -> E1                       (Optional l, None)
+           fun ?l:(P = E0) -> E1                (Optional l, Some E0)
+
+           Notes:
+           - If E0 is provided, only Optional is allowed.
+           - "fun P1 P2 .. Pn -> E1" is represented as nested Pexp_fun.
+           - "let f P = E" is represented using Pexp_fun.
+ */
+  | Pexp_apply(expression, list<(arg_label, expression)>)
+  /* E0 ~l1:E1 ... ~ln:En
+           li can be empty (non labeled argument) or start with '?'
+           (optional argument).
+
+           Invariant: n > 0
+ */
+  | Pexp_match(expression, list<case>)
+  /* match E0 with P1 -> E1 | ... | Pn -> En */
+  | Pexp_try(expression, list<case>)
+  /* try E0 with P1 -> E1 | ... | Pn -> En */
+  | Pexp_tuple(list<expression>)
+  /* (E1, ..., En)
+
+           Invariant: n >= 2
+ */
+  | Pexp_construct(loc<Longident.t>, option<expression>)
+  /* C                None
+           C E              Some E
+           C (E1, ..., En)  Some (Pexp_tuple[E1;...;En])
+ */
+  | Pexp_variant(label, option<expression>)
+  /* `A             (None)
+           `A E           (Some E)
+ */
+  | Pexp_record(list<(loc<Longident.t>, expression)>, option<expression>)
+  /* { l1=P1; ...; ln=Pn }     (None)
+           { E0 with l1=P1; ...; ln=Pn }   (Some E0)
+
+           Invariant: n > 0
+ */
+  | Pexp_field(expression, loc<Longident.t>)
+  /* E.l */
+  | Pexp_setfield(expression, loc<Longident.t>, expression)
+  /* E1.l <- E2 */
+  | Pexp_array(list<expression>)
+  /* [| E1; ...; En |] */
+  | Pexp_ifthenelse(expression, expression, option<expression>)
+  /* if E1 then E2 else E3 */
+  | Pexp_sequence(expression, expression)
+  /* E1; E2 */
+  | Pexp_while(expression, expression)
+  /* while E1 do E2 done */
+  | Pexp_for(pattern, expression, expression, direction_flag, expression)
+  /* for i = E1 to E2 do E3 done      (flag = Upto)
+           for i = E1 downto E2 do E3 done  (flag = Downto)
+ */
+  | Pexp_constraint(expression, core_type)
+  /* (E : T) */
+  | Pexp_coerce(expression, option<core_type>, core_type)
+  /* (E :> T)        (None, T)
+           (E : T0 :> T)   (Some T0, T)
+ */
+  | Pexp_send(expression, loc<label>)
+  /* E # m */
+  | Pexp_new(loc<Longident.t>)
+  /* new M.c */
+  | Pexp_setinstvar(loc<label>, expression)
+  /* x <- 2 */
+  | Pexp_override(list<(loc<label>, expression)>)
+  /* {< x1 = E1; ...; Xn = En >} */
+  | Pexp_letmodule(loc<string>, module_expr, expression)
+  /* let module M = ME in E */
+  | Pexp_letexception(extension_constructor, expression)
+  /* let exception C in E */
+  | Pexp_assert(expression)
+  /* assert E
+           Note: "assert false" is treated in a special way by the
+           type-checker. */
+  | Pexp_lazy(expression)
+  /* lazy E */
+  | Pexp_poly(expression, option<core_type>)
+  /* Used for method bodies.
+
+           Can only be used as the expression under Cfk_concrete
+           for methods (not values). */
+  | Pexp_object(class_structure)
+  /* object ... end */
+  | Pexp_newtype(loc<string>, expression)
+  /* fun (type t) -> E */
+  | Pexp_pack(module_expr)
+  /* (module ME)
+
+           (module ME : S) is represented as
+           Pexp_constraint(Pexp_pack, Ptyp_package S) */
+  | Pexp_open(override_flag, loc<Longident.t>, expression)
+  /* M.(E)
+           let open M in E
+           let! open M in E */
+  | Pexp_extension(extension)
+  /* [%id] */
+  | Pexp_unreachable
+/* . */
+
+and case = {
+  /* (P -> E) or (P when E0 -> E) */
+
+  pc_lhs: pattern,
+  pc_guard: option<expression>,
+  pc_rhs: expression,
+}
+
+/* Value descriptions */
+
+and value_description = {
+  pval_name: loc<string>,
+  pval_type: core_type,
+  pval_prim: list<string>,
+  pval_attributes: attributes /* ... [@@id1] [@@id2] */,
+  pval_loc: Location.t,
+}
+
+/*
+  val x: T                            (prim = [])
+  external x: T = "s1" ... "sn"       (prim = ["s1";..."sn"])
+*/
+
+/* Type declarations */
+
+and type_declaration = {
+  ptype_name: loc<string>,
+  ptype_params: list<(core_type, variance)>,
+  /* ('a1,...'an) t; None represents  _ */
+  ptype_cstrs: list<(core_type, core_type, Location.t)>,
+  /* ... constraint T1=T1'  ... constraint Tn=Tn' */
+  ptype_kind: type_kind,
+  ptype_private: private_flag /* = private ... */,
+  ptype_manifest: option<core_type> /* = T */,
+  ptype_attributes: attributes /* ... [@@id1] [@@id2] */,
+  ptype_loc: Location.t,
+}
+
+/*
+  type t                     (abstract, no manifest)
+  type t = T0                (abstract, manifest=T0)
+  type t = C of T | ...      (variant,  no manifest)
+  type t = T0 = C of T | ... (variant,  manifest=T0)
+  type t = {l: T; ...}       (record,   no manifest)
+  type t = T0 = {l : T; ...} (record,   manifest=T0)
+  type t = ..                (open,     no manifest)
+*/
+
+and type_kind =
+  | Ptype_abstract
+  | Ptype_variant(list<constructor_declaration>)
+  /* Invariant: non-empty list */
+  | Ptype_record(list<label_declaration>)
+  /* Invariant: non-empty list */
+  | Ptype_open
+
+and label_declaration = {
+  pld_name: loc<string>,
+  pld_mutable: mutable_flag,
+  pld_type: core_type,
+  pld_loc: Location.t,
+  pld_attributes: attributes /* l : T [@id1] [@id2] */,
+}
+
+/* { ...; l: T; ... }            (mutable=Immutable)
+    { ...; mutable l: T; ... }    (mutable=Mutable)
+
+    Note: T can be a Ptyp_poly.
+*/
+
+and constructor_declaration = {
+  pcd_name: loc<string>,
+  pcd_args: constructor_arguments,
+  pcd_res: option<core_type>,
+  pcd_loc: Location.t,
+  pcd_attributes: attributes /* C of ... [@id1] [@id2] */,
+}
+
+and constructor_arguments =
+  | Pcstr_tuple(list<core_type>)
+  | Pcstr_record(list<label_declaration>)
+
+/*
+  | C of T1 * ... * Tn     (res = None,    args = Pcstr_tuple [])
+  | C: T0                  (res = Some T0, args = [])
+  | C: T1 * ... * Tn -> T0 (res = Some T0, args = Pcstr_tuple)
+  | C of {...}             (res = None,    args = Pcstr_record)
+  | C: {...} -> T0         (res = Some T0, args = Pcstr_record)
+  | C of {...} as t        (res = None,    args = Pcstr_record)
+*/
+
+and type_extension = {
+  ptyext_path: loc<Longident.t>,
+  ptyext_params: list<(core_type, variance)>,
+  ptyext_constructors: list<extension_constructor>,
+  ptyext_private: private_flag,
+  ptyext_attributes: attributes /* ... [@@id1] [@@id2] */,
+}
+/*
+  type t += ...
+*/
+
+and extension_constructor = {
+  pext_name: loc<string>,
+  pext_kind: extension_constructor_kind,
+  pext_loc: Location.t,
+  pext_attributes: attributes /* C of ... [@id1] [@id2] */,
+}
+
+and extension_constructor_kind =
+  | Pext_decl(constructor_arguments, option<core_type>)
+  /*
+         | C of T1 * ... * Tn     ([T1; ...; Tn], None)
+         | C: T0                  ([], Some T0)
+         | C: T1 * ... * Tn -> T0 ([T1; ...; Tn], Some T0)
+ */
+  | Pext_rebind(loc<Longident.t>)
+/*
+         | C = D
+ */
+
+/* Type expressions for the class language */
+
+@ocaml.text(" {1 Class language} ")
+and class_type = {
+  pcty_desc: class_type_desc,
+  pcty_loc: Location.t,
+  pcty_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and class_type_desc =
+  | Pcty_constr(loc<Longident.t>, list<core_type>)
+  /* c
+   ['a1, ..., 'an] c */
+  | Pcty_signature(class_signature)
+  /* object ... end */
+  | Pcty_arrow(arg_label, core_type, class_type)
+  /* T -> CT       Simple
+           ~l:T -> CT    Labelled l
+           ?l:T -> CT    Optional l
+ */
+  | Pcty_extension(extension)
+  /* [%id] */
+  | Pcty_open(override_flag, loc<Longident.t>, class_type)
+/* let open M in CT */
+
+and class_signature = {
+  pcsig_self: core_type,
+  pcsig_fields: list<class_type_field>,
+}
+/* object('selfpat) ... end
+   object ... end             (self = Ptyp_any)
+ */
+
+and class_type_field = {
+  pctf_desc: class_type_field_desc,
+  pctf_loc: Location.t,
+  pctf_attributes: attributes /* ... [@@id1] [@@id2] */,
+}
+
+and class_type_field_desc =
+  | Pctf_inherit(class_type)
+  /* inherit CT */
+  | Pctf_val((loc<label>, mutable_flag, virtual_flag, core_type))
+  /* val x: T */
+  | Pctf_method((loc<label>, private_flag, virtual_flag, core_type))
+  /* method x: T
+
+           Note: T can be a Ptyp_poly.
+ */
+  | Pctf_constraint((core_type, core_type))
+  /* constraint T1 = T2 */
+  | Pctf_attribute(attribute)
+  /* [@@@id] */
+  | Pctf_extension(extension)
+/* [%%id] */
+
+and class_infos<'a> = {
+  pci_virt: virtual_flag,
+  pci_params: list<(core_type, variance)>,
+  pci_name: loc<string>,
+  pci_expr: 'a,
+  pci_loc: Location.t,
+  pci_attributes: attributes /* ... [@@id1] [@@id2] */,
+}
+/* class c = ...
+   class ['a1,...,'an] c = ...
+   class virtual c = ...
+
+   Also used for "class type" declaration.
+*/
+
+and class_description = class_infos<class_type>
+
+and class_type_declaration = class_infos<class_type>
+
+/* Value expressions for the class language */
+
+and class_expr = {
+  pcl_desc: class_expr_desc,
+  pcl_loc: Location.t,
+  pcl_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and class_expr_desc =
+  | Pcl_constr(loc<Longident.t>, list<core_type>)
+  /* c
+   ['a1, ..., 'an] c */
+  | Pcl_structure(class_structure)
+  /* object ... end */
+  | Pcl_fun(arg_label, option<expression>, pattern, class_expr)
+  /* fun P -> CE                          (Simple, None)
+           fun ~l:P -> CE                       (Labelled l, None)
+           fun ?l:P -> CE                       (Optional l, None)
+           fun ?l:(P = E0) -> CE                (Optional l, Some E0)
+ */
+  | Pcl_apply(class_expr, list<(arg_label, expression)>)
+  /* CE ~l1:E1 ... ~ln:En
+           li can be empty (non labeled argument) or start with '?'
+           (optional argument).
+
+           Invariant: n > 0
+ */
+  | Pcl_let(rec_flag, list<value_binding>, class_expr)
+  /* let P1 = E1 and ... and Pn = EN in CE      (flag = Nonrecursive)
+           let rec P1 = E1 and ... and Pn = EN in CE  (flag = Recursive)
+ */
+  | Pcl_constraint(class_expr, class_type)
+  /* (CE : CT) */
+  | Pcl_extension(extension)
+  /* [%id] */
+  | Pcl_open(override_flag, loc<Longident.t>, class_expr)
+/* let open M in CE */
+
+and class_structure = {
+  pcstr_self: pattern,
+  pcstr_fields: list<class_field>,
+}
+/* object(selfpat) ... end
+   object ... end           (self = Ppat_any)
+ */
+
+and class_field = {
+  pcf_desc: class_field_desc,
+  pcf_loc: Location.t,
+  pcf_attributes: attributes /* ... [@@id1] [@@id2] */,
+}
+
+and class_field_desc =
+  | Pcf_inherit(override_flag, class_expr, option<loc<string>>)
+  /* inherit CE
+           inherit CE as x
+           inherit! CE
+           inherit! CE as x
+ */
+  | Pcf_val((loc<label>, mutable_flag, class_field_kind))
+  /* val x = E
+           val virtual x: T
+ */
+  | Pcf_method((loc<label>, private_flag, class_field_kind))
+  /* method x = E            (E can be a Pexp_poly)
+           method virtual x: T     (T can be a Ptyp_poly)
+ */
+  | Pcf_constraint((core_type, core_type))
+  /* constraint T1 = T2 */
+  | Pcf_initializer(expression)
+  /* initializer E */
+  | Pcf_attribute(attribute)
+  /* [@@@id] */
+  | Pcf_extension(extension)
+/* [%%id] */
+
+and class_field_kind =
+  | Cfk_virtual(core_type)
+  | Cfk_concrete(override_flag, expression)
+
+and class_declaration = class_infos<class_expr>
+
+/* Type expressions for the module language */
+
+@ocaml.text(" {1 Module language} ")
+and module_type = {
+  pmty_desc: module_type_desc,
+  pmty_loc: Location.t,
+  pmty_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and module_type_desc =
+  | Pmty_ident(loc<Longident.t>)
+  /* S */
+  | Pmty_signature(signature)
+  /* sig ... end */
+  | Pmty_functor(loc<string>, option<module_type>, module_type)
+  /* functor(X : MT1) -> MT2 */
+  | Pmty_with(module_type, list<with_constraint>)
+  /* MT with ... */
+  | Pmty_typeof(module_expr)
+  /* module type of ME */
+  | Pmty_extension(extension)
+  /* [%id] */
+  | Pmty_alias(loc<Longident.t>)
+/* (module M) */
+
+and signature = list<signature_item>
+
+and signature_item = {
+  psig_desc: signature_item_desc,
+  psig_loc: Location.t,
+}
+
+and signature_item_desc =
+  | Psig_value(value_description)
+  /*
+          val x: T
+          external x: T = "s1" ... "sn"
+ */
+  | Psig_type(rec_flag, list<type_declaration>)
+  /* type t1 = ... and ... and tn = ... */
+  | Psig_typext(type_extension)
+  /* type t1 += ... */
+  | Psig_exception(extension_constructor)
+  /* exception C of T */
+  | Psig_module(module_declaration)
+  /* module X : MT */
+  | Psig_recmodule(list<module_declaration>)
+  /* module rec X1 : MT1 and ... and Xn : MTn */
+  | Psig_modtype(module_type_declaration)
+  /* module type S = MT
+   module type S */
+  | Psig_open(open_description)
+  /* open X */
+  | Psig_include(include_description)
+  /* include MT */
+  | Psig_class(list<class_description>)
+  /* class c1 : ... and ... and cn : ... */
+  | Psig_class_type(list<class_type_declaration>)
+  /* class type ct1 = ... and ... and ctn = ... */
+  | Psig_attribute(attribute)
+  /* [@@@id] */
+  | Psig_extension(extension, attributes)
+/* [%%id] */
+
+and module_declaration = {
+  pmd_name: loc<string>,
+  pmd_type: module_type,
+  pmd_attributes: attributes /* ... [@@id1] [@@id2] */,
+  pmd_loc: Location.t,
+}
+/* S : MT */
+
+and module_type_declaration = {
+  pmtd_name: loc<string>,
+  pmtd_type: option<module_type>,
+  pmtd_attributes: attributes /* ... [@@id1] [@@id2] */,
+  pmtd_loc: Location.t,
+}
+/* S = MT
+   S       (abstract module type declaration, pmtd_type = None)
+*/
+
+and open_description = {
+  popen_lid: loc<Longident.t>,
+  popen_override: override_flag,
+  popen_loc: Location.t,
+  popen_attributes: attributes,
+}
+/* open! X - popen_override = Override (silences the 'used identifier
+                              shadowing' warning)
+   open  X - popen_override = Fresh
+ */
+
+and include_infos<'a> = {
+  pincl_mod: 'a,
+  pincl_loc: Location.t,
+  pincl_attributes: attributes,
+}
+
+and include_description = include_infos<module_type>
+/* include MT */
+
+and include_declaration = include_infos<module_expr>
+/* include ME */
+
+and with_constraint =
+  | Pwith_type(loc<Longident.t>, type_declaration)
+  /* with type X.t = ...
+
+           Note: the last component of the longident must match
+           the name of the type_declaration. */
+  | Pwith_module(loc<Longident.t>, loc<Longident.t>)
+  /* with module X.Y = Z */
+  | Pwith_typesubst(loc<Longident.t>, type_declaration)
+  /* with type X.t := ..., same format as [Pwith_type] */
+  | Pwith_modsubst(loc<Longident.t>, loc<Longident.t>)
+/* with module X.Y := Z */
+
+/* Value expressions for the module language */
+
+and module_expr = {
+  pmod_desc: module_expr_desc,
+  pmod_loc: Location.t,
+  pmod_attributes: attributes /* ... [@id1] [@id2] */,
+}
+
+and module_expr_desc =
+  | Pmod_ident(loc<Longident.t>)
+  /* X */
+  | Pmod_structure(structure)
+  /* struct ... end */
+  | Pmod_functor(loc<string>, option<module_type>, module_expr)
+  /* functor(X : MT1) -> ME */
+  | Pmod_apply(module_expr, module_expr)
+  /* ME1(ME2) */
+  | Pmod_constraint(module_expr, module_type)
+  /* (ME : MT) */
+  | Pmod_unpack(expression)
+  /* (val E) */
+  | Pmod_extension(extension)
+/* [%id] */
+
+and structure = list<structure_item>
+
+and structure_item = {
+  pstr_desc: structure_item_desc,
+  pstr_loc: Location.t,
+}
+
+and structure_item_desc =
+  | Pstr_eval(expression, attributes)
+  /* E */
+  | Pstr_value(rec_flag, list<value_binding>)
+  /* let P1 = E1 and ... and Pn = EN       (flag = Nonrecursive)
+           let rec P1 = E1 and ... and Pn = EN   (flag = Recursive)
+ */
+  | Pstr_primitive(value_description)
+  /* val x: T
+   external x: T = "s1" ... "sn" */
+  | Pstr_type(rec_flag, list<type_declaration>)
+  /* type t1 = ... and ... and tn = ... */
+  | Pstr_typext(type_extension)
+  /* type t1 += ... */
+  | Pstr_exception(extension_constructor)
+  /* exception C of T
+   exception C = M.X */
+  | Pstr_module(module_binding)
+  /* module X = ME */
+  | Pstr_recmodule(list<module_binding>)
+  /* module rec X1 = ME1 and ... and Xn = MEn */
+  | Pstr_modtype(module_type_declaration)
+  /* module type S = MT */
+  | Pstr_open(open_description)
+  /* open X */
+  | Pstr_class(list<class_declaration>)
+  /* class c1 = ... and ... and cn = ... */
+  | Pstr_class_type(list<class_type_declaration>)
+  /* class type ct1 = ... and ... and ctn = ... */
+  | Pstr_include(include_declaration)
+  /* include ME */
+  | Pstr_attribute(attribute)
+  /* [@@@id] */
+  | Pstr_extension(extension, attributes)
+/* [%%id] */
+
+and value_binding = {
+  pvb_pat: pattern,
+  pvb_expr: expression,
+  pvb_attributes: attributes,
+  pvb_loc: Location.t,
+}
+
+and module_binding = {
+  pmb_name: loc<string>,
+  pmb_expr: module_expr,
+  pmb_attributes: attributes,
+  pmb_loc: Location.t,
+}
+@@ocaml.text(
+  /* X = ME */
+
+  " {1 Toplevel} "
+)
+
+/* Toplevel phrases */
+
+type rec toplevel_phrase =
+  | Ptop_def(structure)
+  | Ptop_dir(string, directive_argument)
+/* #use, #load ... */
+
+and directive_argument =
+  | Pdir_none
+  | Pdir_string(string)
+  | Pdir_int(string, option<char>)
+  | Pdir_ident(Longident.t)
+  | Pdir_bool(bool)
+
diff --git a/analysis/examples/larger-project/src/printf.js b/analysis/examples/larger-project/src/printf.js
new file mode 100644
index 0000000000..f635884c8a
--- /dev/null
+++ b/analysis/examples/larger-project/src/printf.js
@@ -0,0 +1,59 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function printf(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "printf.res",
+          1,
+          18
+        ],
+        Error: new Error()
+      };
+}
+
+function fprintf(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "printf.res",
+          3,
+          19
+        ],
+        Error: new Error()
+      };
+}
+
+function sprintf(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "printf.res",
+          5,
+          19
+        ],
+        Error: new Error()
+      };
+}
+
+function eprintf(param) {
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "printf.res",
+          7,
+          19
+        ],
+        Error: new Error()
+      };
+}
+
+export {
+  printf ,
+  fprintf ,
+  sprintf ,
+  eprintf ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/printf.res b/analysis/examples/larger-project/src/printf.res
new file mode 100644
index 0000000000..0f60b19b65
--- /dev/null
+++ b/analysis/examples/larger-project/src/printf.res
@@ -0,0 +1,7 @@
+let printf = _ => assert false
+
+let fprintf = _ => assert false
+
+let sprintf = _ => assert false
+
+let eprintf = _ => assert false
diff --git a/analysis/examples/larger-project/src/res_comment.js b/analysis/examples/larger-project/src/res_comment.js
new file mode 100644
index 0000000000..08689b1ce2
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_comment.js
@@ -0,0 +1,110 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Format from "./format.js";
+import * as Lexing from "rescript/lib/es6/lexing.js";
+import * as $$String from "rescript/lib/es6/string.js";
+
+function styleToString(s) {
+  if (s) {
+    return "MultiLine";
+  } else {
+    return "SingleLine";
+  }
+}
+
+function loc(t) {
+  return t.loc;
+}
+
+function txt(t) {
+  return t.txt;
+}
+
+function prevTokEndPos(t) {
+  return t.prevTokEndPos;
+}
+
+function setPrevTokEndPos(t, pos) {
+  t.prevTokEndPos = pos;
+  
+}
+
+function isSingleLineComment(t) {
+  var match = t.style;
+  if (match) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function toString(t) {
+  return Curry._4(Format.sprintf("(txt: %s\nstyle: %s\nlines: %d-%d)"), t.txt, t.style ? "MultiLine" : "SingleLine", t.loc.loc_start.pos_lnum, t.loc.loc_end.pos_lnum);
+}
+
+function makeSingleLineComment(loc, txt) {
+  return {
+          txt: txt,
+          style: /* SingleLine */0,
+          loc: loc,
+          prevTokEndPos: Lexing.dummy_pos
+        };
+}
+
+function makeMultiLineComment(loc, txt) {
+  return {
+          txt: txt,
+          style: /* MultiLine */1,
+          loc: loc,
+          prevTokEndPos: Lexing.dummy_pos
+        };
+}
+
+function fromOcamlComment(loc, txt, prevTokEndPos) {
+  return {
+          txt: txt,
+          style: /* MultiLine */1,
+          loc: loc,
+          prevTokEndPos: prevTokEndPos
+        };
+}
+
+function trimSpaces(s) {
+  var len = s.length;
+  if (len === 0) {
+    return s;
+  }
+  if (!(s[0] === " " || s[len - 1 | 0] === " ")) {
+    return s;
+  }
+  var i = 0;
+  while(i < len && s[i] === " ") {
+    i = i + 1 | 0;
+  };
+  var j = len - 1 | 0;
+  while(j >= i && s[j] === " ") {
+    j = j - 1 | 0;
+  };
+  if (j >= i) {
+    return $$String.sub(s, i, (j - i | 0) + 1 | 0);
+  } else {
+    return "";
+  }
+}
+
+export {
+  styleToString ,
+  loc ,
+  txt ,
+  prevTokEndPos ,
+  setPrevTokEndPos ,
+  isSingleLineComment ,
+  toString ,
+  makeSingleLineComment ,
+  makeMultiLineComment ,
+  fromOcamlComment ,
+  trimSpaces ,
+  
+}
+/* Format Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_comment.res b/analysis/examples/larger-project/src/res_comment.res
new file mode 100644
index 0000000000..fe92ec53c3
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_comment.res
@@ -0,0 +1,81 @@
+type style =
+  | SingleLine
+  | MultiLine
+
+let styleToString = s =>
+  switch s {
+  | SingleLine => "SingleLine"
+  | MultiLine => "MultiLine"
+  }
+
+type t = {
+  txt: string,
+  style: style,
+  loc: Location.t,
+  mutable prevTokEndPos: Lexing.position,
+}
+
+let loc = t => t.loc
+let txt = t => t.txt
+let prevTokEndPos = t => t.prevTokEndPos
+
+let setPrevTokEndPos = (t, pos) => t.prevTokEndPos = pos
+
+let isSingleLineComment = t =>
+  switch t.style {
+  | SingleLine => true
+  | MultiLine => false
+  }
+
+let toString = t =>
+  Format.sprintf(
+    "(txt: %s\nstyle: %s\nlines: %d-%d)",
+    t.txt,
+    styleToString(t.style),
+    t.loc.loc_start.pos_lnum,
+    t.loc.loc_end.pos_lnum,
+  )
+
+let makeSingleLineComment = (~loc, txt) => {
+  txt: txt,
+  loc: loc,
+  style: SingleLine,
+  prevTokEndPos: Lexing.dummy_pos,
+}
+
+let makeMultiLineComment = (~loc, txt) => {
+  txt: txt,
+  loc: loc,
+  style: MultiLine,
+  prevTokEndPos: Lexing.dummy_pos,
+}
+
+let fromOcamlComment = (~loc, ~txt, ~prevTokEndPos) => {
+  txt: txt,
+  loc: loc,
+  style: MultiLine,
+  prevTokEndPos: prevTokEndPos,
+}
+
+let trimSpaces = s => {
+  let len = String.length(s)
+  if len == 0 {
+    s
+  } else if String.unsafe_get(s, 0) == ' ' || String.unsafe_get(s, len - 1) == ' ' {
+    let i = ref(0)
+    while i.contents < len && String.unsafe_get(s, i.contents) == ' ' {
+      incr(i)
+    }
+    let j = ref(len - 1)
+    while j.contents >= i.contents && String.unsafe_get(s, j.contents) == ' ' {
+      decr(j)
+    }
+    if j.contents >= i.contents {
+      (@doesNotRaise String.sub)(s, i.contents, j.contents - i.contents + 1)
+    } else {
+      ""
+    }
+  } else {
+    s
+  }
+}
diff --git a/analysis/examples/larger-project/src/res_comments_table.js b/analysis/examples/larger-project/src/res_comments_table.js
new file mode 100644
index 0000000000..227c724abc
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_comments_table.js
@@ -0,0 +1,3077 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Hashtbl from "rescript/lib/es6/hashtbl.js";
+import * as Res_doc from "./res_doc.js";
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as $$Location from "./location.js";
+import * as Ast_helper from "./ast_helper.js";
+import * as Res_comment from "./res_comment.js";
+import * as Res_parsetree_viewer from "./res_parsetree_viewer.js";
+
+function make(param) {
+  return {
+          leading: Hashtbl.create(undefined, 100),
+          inside: Hashtbl.create(undefined, 100),
+          trailing: Hashtbl.create(undefined, 100)
+        };
+}
+
+function copy(tbl) {
+  return {
+          leading: Hashtbl.copy(tbl.leading),
+          inside: Hashtbl.copy(tbl.inside),
+          trailing: Hashtbl.copy(tbl.trailing)
+        };
+}
+
+var empty = make(undefined);
+
+function log(t) {
+  var leadingStuff = Hashtbl.fold((function (k, v, acc) {
+          var loc = Res_doc.concat({
+                hd: Res_doc.lbracket,
+                tl: {
+                  hd: Res_doc.text(String(k.loc_start.pos_lnum)),
+                  tl: {
+                    hd: Res_doc.text(":"),
+                    tl: {
+                      hd: Res_doc.text(String(k.loc_start.pos_cnum - k.loc_start.pos_bol | 0)),
+                      tl: {
+                        hd: Res_doc.text("-"),
+                        tl: {
+                          hd: Res_doc.text(String(k.loc_end.pos_lnum)),
+                          tl: {
+                            hd: Res_doc.text(":"),
+                            tl: {
+                              hd: Res_doc.text(String(k.loc_end.pos_cnum - k.loc_end.pos_bol | 0)),
+                              tl: {
+                                hd: Res_doc.rbracket,
+                                tl: /* [] */0
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              });
+          var doc = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: loc,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.comma, List.map((function (c) {
+                                              return Res_doc.text(Res_comment.txt(c));
+                                            }), v)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+          return {
+                  hd: doc,
+                  tl: acc
+                };
+        }), t.leading, /* [] */0);
+  var trailingStuff = Hashtbl.fold((function (k, v, acc) {
+          var loc = Res_doc.concat({
+                hd: Res_doc.lbracket,
+                tl: {
+                  hd: Res_doc.text(String(k.loc_start.pos_lnum)),
+                  tl: {
+                    hd: Res_doc.text(":"),
+                    tl: {
+                      hd: Res_doc.text(String(k.loc_start.pos_cnum - k.loc_start.pos_bol | 0)),
+                      tl: {
+                        hd: Res_doc.text("-"),
+                        tl: {
+                          hd: Res_doc.text(String(k.loc_end.pos_lnum)),
+                          tl: {
+                            hd: Res_doc.text(":"),
+                            tl: {
+                              hd: Res_doc.text(String(k.loc_end.pos_cnum - k.loc_end.pos_bol | 0)),
+                              tl: {
+                                hd: Res_doc.rbracket,
+                                tl: /* [] */0
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              });
+          var doc = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: loc,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.comma,
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (c) {
+                                              return Res_doc.text(Res_comment.txt(c));
+                                            }), v)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+          return {
+                  hd: doc,
+                  tl: acc
+                };
+        }), t.trailing, /* [] */0);
+  console.log(Res_doc.toString(80, Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: Res_doc.text("leading comments:"),
+                    tl: {
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat(leadingStuff)),
+                        tl: {
+                          hd: Res_doc.line,
+                          tl: {
+                            hd: Res_doc.line,
+                            tl: {
+                              hd: Res_doc.text("trailing comments:"),
+                              tl: {
+                                hd: Res_doc.indent(Res_doc.concat(trailingStuff)),
+                                tl: {
+                                  hd: Res_doc.line,
+                                  tl: {
+                                    hd: Res_doc.line,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }))));
+  
+}
+
+function attach(tbl, loc, comments) {
+  if (comments) {
+    return Hashtbl.replace(tbl, loc, comments);
+  }
+  
+}
+
+function partitionByLoc(comments, loc) {
+  var _param = [
+    /* [] */0,
+    /* [] */0,
+    /* [] */0
+  ];
+  var _comments = comments;
+  while(true) {
+    var param = _param;
+    var comments$1 = _comments;
+    var trailing = param[2];
+    var inside = param[1];
+    var leading = param[0];
+    if (!comments$1) {
+      return [
+              List.rev(leading),
+              List.rev(inside),
+              List.rev(trailing)
+            ];
+    }
+    var rest = comments$1.tl;
+    var comment = comments$1.hd;
+    var cmtLoc = Res_comment.loc(comment);
+    if (cmtLoc.loc_end.pos_cnum <= loc.loc_start.pos_cnum) {
+      _comments = rest;
+      _param = [
+        {
+          hd: comment,
+          tl: leading
+        },
+        inside,
+        trailing
+      ];
+      continue ;
+    }
+    if (cmtLoc.loc_start.pos_cnum >= loc.loc_end.pos_cnum) {
+      _comments = rest;
+      _param = [
+        leading,
+        inside,
+        {
+          hd: comment,
+          tl: trailing
+        }
+      ];
+      continue ;
+    }
+    _comments = rest;
+    _param = [
+      leading,
+      {
+        hd: comment,
+        tl: inside
+      },
+      trailing
+    ];
+    continue ;
+  };
+}
+
+function partitionLeadingTrailing(comments, loc) {
+  var _param = [
+    /* [] */0,
+    /* [] */0
+  ];
+  var _comments = comments;
+  while(true) {
+    var param = _param;
+    var comments$1 = _comments;
+    var trailing = param[1];
+    var leading = param[0];
+    if (!comments$1) {
+      return [
+              List.rev(leading),
+              List.rev(trailing)
+            ];
+    }
+    var rest = comments$1.tl;
+    var comment = comments$1.hd;
+    var cmtLoc = Res_comment.loc(comment);
+    if (cmtLoc.loc_end.pos_cnum <= loc.loc_start.pos_cnum) {
+      _comments = rest;
+      _param = [
+        {
+          hd: comment,
+          tl: leading
+        },
+        trailing
+      ];
+      continue ;
+    }
+    _comments = rest;
+    _param = [
+      leading,
+      {
+        hd: comment,
+        tl: trailing
+      }
+    ];
+    continue ;
+  };
+}
+
+function partitionByOnSameLine(loc, comments) {
+  var _param = [
+    /* [] */0,
+    /* [] */0
+  ];
+  var _comments = comments;
+  while(true) {
+    var param = _param;
+    var comments$1 = _comments;
+    var onOtherLine = param[1];
+    var onSameLine = param[0];
+    if (!comments$1) {
+      return [
+              List.rev(onSameLine),
+              List.rev(onOtherLine)
+            ];
+    }
+    var rest = comments$1.tl;
+    var comment = comments$1.hd;
+    var cmtLoc = Res_comment.loc(comment);
+    if (cmtLoc.loc_start.pos_lnum === loc.loc_end.pos_lnum) {
+      _comments = rest;
+      _param = [
+        {
+          hd: comment,
+          tl: onSameLine
+        },
+        onOtherLine
+      ];
+      continue ;
+    }
+    _comments = rest;
+    _param = [
+      onSameLine,
+      {
+        hd: comment,
+        tl: onOtherLine
+      }
+    ];
+    continue ;
+  };
+}
+
+function partitionAdjacentTrailing(loc1, comments) {
+  var _prevEndPos = loc1.loc_end;
+  var _afterLoc1 = /* [] */0;
+  var _comments = comments;
+  while(true) {
+    var comments$1 = _comments;
+    var afterLoc1 = _afterLoc1;
+    var prevEndPos = _prevEndPos;
+    if (!comments$1) {
+      return [
+              List.rev(afterLoc1),
+              /* [] */0
+            ];
+    }
+    var comment = comments$1.hd;
+    var cmtPrevEndPos = Res_comment.prevTokEndPos(comment);
+    if (prevEndPos.pos_cnum !== cmtPrevEndPos.pos_cnum) {
+      return [
+              List.rev(afterLoc1),
+              comments$1
+            ];
+    }
+    var commentEnd = Res_comment.loc(comment).loc_end;
+    _comments = comments$1.tl;
+    _afterLoc1 = {
+      hd: comment,
+      tl: afterLoc1
+    };
+    _prevEndPos = commentEnd;
+    continue ;
+  };
+}
+
+function collectListPatterns(_acc, _pattern) {
+  while(true) {
+    var pattern = _pattern;
+    var acc = _acc;
+    var match = pattern.ppat_desc;
+    if (typeof match === "number") {
+      return List.rev({
+                  hd: pattern,
+                  tl: acc
+                });
+    }
+    if (match.TAG !== /* Ppat_construct */5) {
+      return List.rev({
+                  hd: pattern,
+                  tl: acc
+                });
+    }
+    var match$1 = match._0.txt;
+    switch (match$1.TAG | 0) {
+      case /* Lident */0 :
+          switch (match$1._0) {
+            case "::" :
+                var match$2 = match._1;
+                if (match$2 === undefined) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                var match$3 = match$2.ppat_desc;
+                if (typeof match$3 === "number") {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                if (match$3.TAG !== /* Ppat_tuple */4) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                var match$4 = match$3._0;
+                if (!match$4) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                var match$5 = match$4.tl;
+                if (!match$5) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                if (match$5.tl) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                }
+                _pattern = match$5.hd;
+                _acc = {
+                  hd: match$4.hd,
+                  tl: acc
+                };
+                continue ;
+            case "[]" :
+                if (match._1 !== undefined) {
+                  return List.rev({
+                              hd: pattern,
+                              tl: acc
+                            });
+                } else {
+                  return List.rev(acc);
+                }
+            default:
+              return List.rev({
+                          hd: pattern,
+                          tl: acc
+                        });
+          }
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return List.rev({
+                      hd: pattern,
+                      tl: acc
+                    });
+      
+    }
+  };
+}
+
+function collectListExprs(_acc, _expr) {
+  while(true) {
+    var expr = _expr;
+    var acc = _acc;
+    var match = expr.pexp_desc;
+    if (typeof match === "number") {
+      return List.rev({
+                  hd: expr,
+                  tl: acc
+                });
+    }
+    if (match.TAG !== /* Pexp_construct */9) {
+      return List.rev({
+                  hd: expr,
+                  tl: acc
+                });
+    }
+    var match$1 = match._0.txt;
+    switch (match$1.TAG | 0) {
+      case /* Lident */0 :
+          switch (match$1._0) {
+            case "::" :
+                var match$2 = match._1;
+                if (match$2 === undefined) {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                var match$3 = match$2.pexp_desc;
+                if (typeof match$3 === "number") {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                if (match$3.TAG !== /* Pexp_tuple */8) {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                var match$4 = match$3._0;
+                if (!match$4) {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                var match$5 = match$4.tl;
+                if (!match$5) {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                if (match$5.tl) {
+                  return List.rev({
+                              hd: expr,
+                              tl: acc
+                            });
+                }
+                _expr = match$5.hd;
+                _acc = {
+                  hd: match$4.hd,
+                  tl: acc
+                };
+                continue ;
+            case "[]" :
+                return List.rev(acc);
+            default:
+              return List.rev({
+                          hd: expr,
+                          tl: acc
+                        });
+          }
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return List.rev({
+                      hd: expr,
+                      tl: acc
+                    });
+      
+    }
+  };
+}
+
+function arrowType(ct) {
+  var $$process = function (attrsBefore, _acc, _typ) {
+    while(true) {
+      var typ = _typ;
+      var acc = _acc;
+      var match = typ.ptyp_desc;
+      if (typeof match === "number") {
+        return [
+                attrsBefore,
+                List.rev(acc),
+                typ
+              ];
+      }
+      if (match.TAG !== /* Ptyp_arrow */1) {
+        return [
+                attrsBefore,
+                List.rev(acc),
+                typ
+              ];
+      }
+      var lbl = match._0;
+      if (typeof lbl === "number") {
+        var attrs = typ.ptyp_attributes;
+        var typ2 = match._2;
+        var typ1 = match._1;
+        if (attrs) {
+          if (attrs.hd[0].txt === "bs" && !attrs.tl) {
+            var arg = [
+              attrs,
+              lbl,
+              typ1
+            ];
+            _typ = typ2;
+            _acc = {
+              hd: arg,
+              tl: acc
+            };
+            continue ;
+          }
+          
+        } else {
+          var arg$1 = [
+            /* [] */0,
+            lbl,
+            typ1
+          ];
+          _typ = typ2;
+          _acc = {
+            hd: arg$1,
+            tl: acc
+          };
+          continue ;
+        }
+        var args = List.rev(acc);
+        return [
+                attrsBefore,
+                args,
+                typ
+              ];
+      }
+      var arg_0 = typ.ptyp_attributes;
+      var arg_2 = match._1;
+      var arg$2 = [
+        arg_0,
+        lbl,
+        arg_2
+      ];
+      _typ = match._2;
+      _acc = {
+        hd: arg$2,
+        tl: acc
+      };
+      continue ;
+    };
+  };
+  var match = ct.ptyp_desc;
+  if (typeof match === "number" || !(match.TAG === /* Ptyp_arrow */1 && typeof match._0 === "number")) {
+    return $$process(/* [] */0, /* [] */0, ct);
+  } else {
+    return $$process(ct.ptyp_attributes, /* [] */0, {
+                ptyp_desc: ct.ptyp_desc,
+                ptyp_loc: ct.ptyp_loc,
+                ptyp_attributes: /* [] */0
+              });
+  }
+}
+
+function modExprApply(modExpr) {
+  var _acc = /* [] */0;
+  var _modExpr = modExpr;
+  while(true) {
+    var modExpr$1 = _modExpr;
+    var acc = _acc;
+    var match = modExpr$1.pmod_desc;
+    if (match.TAG !== /* Pmod_apply */3) {
+      return {
+              hd: modExpr$1,
+              tl: acc
+            };
+    }
+    _modExpr = match._0;
+    _acc = {
+      hd: match._1,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function modExprFunctor(modExpr) {
+  var _acc = /* [] */0;
+  var _modExpr = modExpr;
+  while(true) {
+    var modExpr$1 = _modExpr;
+    var acc = _acc;
+    var match = modExpr$1.pmod_desc;
+    if (match.TAG !== /* Pmod_functor */2) {
+      return [
+              List.rev(acc),
+              modExpr$1
+            ];
+    }
+    var param_0 = modExpr$1.pmod_attributes;
+    var param_1 = match._0;
+    var param_2 = match._1;
+    var param = [
+      param_0,
+      param_1,
+      param_2
+    ];
+    _modExpr = match._2;
+    _acc = {
+      hd: param,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function functorType(modtype) {
+  var _acc = /* [] */0;
+  var _modtype = modtype;
+  while(true) {
+    var modtype$1 = _modtype;
+    var acc = _acc;
+    var match = modtype$1.pmty_desc;
+    if (match.TAG !== /* Pmty_functor */2) {
+      return [
+              List.rev(acc),
+              modtype$1
+            ];
+    }
+    var arg_0 = modtype$1.pmty_attributes;
+    var arg_1 = match._0;
+    var arg_2 = match._1;
+    var arg = [
+      arg_0,
+      arg_1,
+      arg_2
+    ];
+    _modtype = match._2;
+    _acc = {
+      hd: arg,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function funExpr(expr) {
+  var collectNewTypes = function (_acc, _returnExpr) {
+    while(true) {
+      var returnExpr = _returnExpr;
+      var acc = _acc;
+      var match = returnExpr.pexp_desc;
+      if (typeof match !== "number" && match.TAG === /* Pexp_newtype */31 && !returnExpr.pexp_attributes) {
+        _returnExpr = match._1;
+        _acc = {
+          hd: match._0,
+          tl: acc
+        };
+        continue ;
+      }
+      var match$1 = List.rev(acc);
+      var loc;
+      if (acc && match$1) {
+        var endLoc = match$1.hd;
+        var init = endLoc.loc;
+        loc = {
+          loc_start: init.loc_start,
+          loc_end: endLoc.loc.loc_end,
+          loc_ghost: init.loc_ghost
+        };
+      } else {
+        loc = $$Location.none;
+      }
+      var txt = List.fold_right((function (curr, acc) {
+              return acc + (" " + curr.txt);
+            }), acc, "type");
+      return [
+              $$Location.mkloc(txt, loc),
+              returnExpr
+            ];
+    };
+  };
+  var collect = function (attrsBefore, _acc, _expr) {
+    while(true) {
+      var expr = _expr;
+      var acc = _acc;
+      var match = expr.pexp_desc;
+      if (typeof match !== "number") {
+        switch (match.TAG | 0) {
+          case /* Pexp_fun */4 :
+              var lbl = match._0;
+              var exit = 0;
+              var attrs = expr.pexp_attributes;
+              var returnExpr = match._3;
+              var pattern = match._2;
+              var defaultExpr = match._1;
+              if (attrs) {
+                if (attrs.hd[0].txt === "bs" && !attrs.tl) {
+                  var parameter = [
+                    attrs,
+                    lbl,
+                    defaultExpr,
+                    pattern
+                  ];
+                  _expr = returnExpr;
+                  _acc = {
+                    hd: parameter,
+                    tl: acc
+                  };
+                  continue ;
+                }
+                exit = 2;
+              } else {
+                var parameter$1 = [
+                  /* [] */0,
+                  lbl,
+                  defaultExpr,
+                  pattern
+                ];
+                _expr = returnExpr;
+                _acc = {
+                  hd: parameter$1,
+                  tl: acc
+                };
+                continue ;
+              }
+              if (exit === 2 && typeof lbl !== "number") {
+                var parameter_0 = expr.pexp_attributes;
+                var parameter_2 = match._1;
+                var parameter_3 = match._2;
+                var parameter$2 = [
+                  parameter_0,
+                  lbl,
+                  parameter_2,
+                  parameter_3
+                ];
+                _expr = match._3;
+                _acc = {
+                  hd: parameter$2,
+                  tl: acc
+                };
+                continue ;
+              }
+              break;
+          case /* Pexp_newtype */31 :
+              var stringLoc = match._0;
+              var match$1 = collectNewTypes({
+                    hd: stringLoc,
+                    tl: /* [] */0
+                  }, match._1);
+              var parameter_0$1 = expr.pexp_attributes;
+              var parameter_3$1 = Ast_helper.Pat.$$var(stringLoc.loc, undefined, match$1[0]);
+              var parameter$3 = [
+                parameter_0$1,
+                /* Nolabel */0,
+                undefined,
+                parameter_3$1
+              ];
+              _expr = match$1[1];
+              _acc = {
+                hd: parameter$3,
+                tl: acc
+              };
+              continue ;
+          default:
+            
+        }
+      }
+      return [
+              attrsBefore,
+              List.rev(acc),
+              expr
+            ];
+    };
+  };
+  var match = expr.pexp_desc;
+  if (typeof match === "number" || !(match.TAG === /* Pexp_fun */4 && typeof match._0 === "number")) {
+    return collect(/* [] */0, /* [] */0, expr);
+  } else {
+    return collect(expr.pexp_attributes, /* [] */0, {
+                pexp_desc: expr.pexp_desc,
+                pexp_loc: expr.pexp_loc,
+                pexp_attributes: /* [] */0
+              });
+  }
+}
+
+function isBlockExpr(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  switch (match.TAG | 0) {
+    case /* Pexp_apply */5 :
+    case /* Pexp_field */12 :
+    case /* Pexp_setfield */13 :
+    case /* Pexp_constraint */19 :
+        break;
+    case /* Pexp_let */2 :
+    case /* Pexp_sequence */16 :
+    case /* Pexp_letmodule */25 :
+    case /* Pexp_letexception */26 :
+    case /* Pexp_open */33 :
+        return true;
+    default:
+      return false;
+  }
+  if (isBlockExpr(match._0)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isIfThenElseExpr(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number" || match.TAG !== /* Pexp_ifthenelse */15) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function walkStructure(s, t, comments) {
+  if (comments === /* [] */0) {
+    return ;
+  } else if (s) {
+    return walkList(undefined, (function (n) {
+                  return n.pstr_loc;
+                }), walkStructureItem, s, t, comments);
+  } else {
+    return attach(t.inside, $$Location.none, comments);
+  }
+}
+
+function walkStructureItem(si, t, comments) {
+  var valueDescription = si.pstr_desc;
+  if (comments === /* [] */0) {
+    return ;
+  }
+  switch (valueDescription.TAG | 0) {
+    case /* Pstr_eval */0 :
+        return walkExpr(valueDescription._0, t, comments);
+    case /* Pstr_value */1 :
+        return walkValueBindings(valueDescription._1, t, comments);
+    case /* Pstr_primitive */2 :
+        return walkValueDescription(valueDescription._0, t, comments);
+    case /* Pstr_type */3 :
+        return walkTypeDeclarations(valueDescription._1, t, comments);
+    case /* Pstr_typext */4 :
+        return walkTypeExtension(valueDescription._0, t, comments);
+    case /* Pstr_exception */5 :
+        return walkExtConstr(valueDescription._0, t, comments);
+    case /* Pstr_module */6 :
+        return walkModuleBinding(valueDescription._0, t, comments);
+    case /* Pstr_recmodule */7 :
+        return walkList(undefined, (function (mb) {
+                      return mb.pmb_loc;
+                    }), walkModuleBinding, valueDescription._0, t, comments);
+    case /* Pstr_modtype */8 :
+        return walkModuleTypeDeclaration(valueDescription._0, t, comments);
+    case /* Pstr_open */9 :
+        return walkOpenDescription(valueDescription._0, t, comments);
+    case /* Pstr_class */10 :
+    case /* Pstr_class_type */11 :
+        return ;
+    case /* Pstr_include */12 :
+        return walkIncludeDeclaration(valueDescription._0, t, comments);
+    case /* Pstr_attribute */13 :
+        return walkAttribute(valueDescription._0, t, comments);
+    case /* Pstr_extension */14 :
+        return walkExtension(valueDescription._0, t, comments);
+    
+  }
+}
+
+function walkValueDescription(vd, t, comments) {
+  var match = partitionLeadingTrailing(comments, vd.pval_name.loc);
+  attach(t.leading, vd.pval_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(vd.pval_name.loc, match[1]);
+  attach(t.trailing, vd.pval_name.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], vd.pval_type.ptyp_loc);
+  attach(t.leading, vd.pval_type.ptyp_loc, match$2[0]);
+  walkTypExpr(vd.pval_type, t, match$2[1]);
+  return attach(t.trailing, vd.pval_type.ptyp_loc, match$2[2]);
+}
+
+function walkTypeExtension(te, t, comments) {
+  var match = partitionLeadingTrailing(comments, te.ptyext_path.loc);
+  attach(t.leading, te.ptyext_path.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(te.ptyext_path.loc, match[1]);
+  var rest = match$1[1];
+  attach(t.trailing, te.ptyext_path.loc, match$1[0]);
+  var typeParams = te.ptyext_params;
+  var rest$1 = typeParams ? visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+            return param[0].ptyp_loc;
+          }), walkTypeParam, typeParams, t, rest) : rest;
+  return walkList(undefined, (function (n) {
+                return n.pext_loc;
+              }), walkExtConstr, te.ptyext_constructors, t, rest$1);
+}
+
+function walkIncludeDeclaration(inclDecl, t, comments) {
+  var match = partitionByLoc(comments, inclDecl.pincl_mod.pmod_loc);
+  attach(t.leading, inclDecl.pincl_mod.pmod_loc, match[0]);
+  walkModExpr(inclDecl.pincl_mod, t, match[1]);
+  return attach(t.trailing, inclDecl.pincl_mod.pmod_loc, match[2]);
+}
+
+function walkModuleTypeDeclaration(mtd, t, comments) {
+  var match = partitionLeadingTrailing(comments, mtd.pmtd_name.loc);
+  var trailing = match[1];
+  attach(t.leading, mtd.pmtd_name.loc, match[0]);
+  var modType = mtd.pmtd_type;
+  if (modType === undefined) {
+    return attach(t.trailing, mtd.pmtd_name.loc, trailing);
+  }
+  var match$1 = partitionAdjacentTrailing(mtd.pmtd_name.loc, trailing);
+  attach(t.trailing, mtd.pmtd_name.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], modType.pmty_loc);
+  attach(t.leading, modType.pmty_loc, match$2[0]);
+  walkModType(modType, t, match$2[1]);
+  return attach(t.trailing, modType.pmty_loc, match$2[2]);
+}
+
+function walkModuleBinding(mb, t, comments) {
+  var match = partitionLeadingTrailing(comments, mb.pmb_name.loc);
+  attach(t.leading, mb.pmb_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(mb.pmb_name.loc, match[1]);
+  attach(t.trailing, mb.pmb_name.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], mb.pmb_expr.pmod_loc);
+  var inside = match$2[1];
+  var leading = match$2[0];
+  var match$3 = mb.pmb_expr.pmod_desc;
+  if (match$3.TAG === /* Pmod_constraint */4) {
+    walkModExpr(mb.pmb_expr, t, List.concat({
+              hd: leading,
+              tl: {
+                hd: inside,
+                tl: /* [] */0
+              }
+            }));
+  } else {
+    attach(t.leading, mb.pmb_expr.pmod_loc, leading);
+    walkModExpr(mb.pmb_expr, t, inside);
+  }
+  return attach(t.trailing, mb.pmb_expr.pmod_loc, match$2[2]);
+}
+
+function walkSignature(signature, t, comments) {
+  if (comments === /* [] */0) {
+    return ;
+  } else if (signature) {
+    return walkList(undefined, (function (n) {
+                  return n.psig_loc;
+                }), walkSignatureItem, signature, t, comments);
+  } else {
+    return attach(t.inside, $$Location.none, comments);
+  }
+}
+
+function walkSignatureItem(si, t, comments) {
+  var valueDescription = si.psig_desc;
+  if (comments === /* [] */0) {
+    return ;
+  }
+  switch (valueDescription.TAG | 0) {
+    case /* Psig_value */0 :
+        return walkValueDescription(valueDescription._0, t, comments);
+    case /* Psig_type */1 :
+        return walkTypeDeclarations(valueDescription._1, t, comments);
+    case /* Psig_typext */2 :
+        return walkTypeExtension(valueDescription._0, t, comments);
+    case /* Psig_exception */3 :
+        return walkExtConstr(valueDescription._0, t, comments);
+    case /* Psig_module */4 :
+        return walkModuleDeclaration(valueDescription._0, t, comments);
+    case /* Psig_recmodule */5 :
+        return walkList(undefined, (function (n) {
+                      return n.pmd_loc;
+                    }), walkModuleDeclaration, valueDescription._0, t, comments);
+    case /* Psig_modtype */6 :
+        return walkModuleTypeDeclaration(valueDescription._0, t, comments);
+    case /* Psig_open */7 :
+        return walkOpenDescription(valueDescription._0, t, comments);
+    case /* Psig_include */8 :
+        return walkIncludeDescription(valueDescription._0, t, comments);
+    case /* Psig_class */9 :
+    case /* Psig_class_type */10 :
+        return ;
+    case /* Psig_attribute */11 :
+        return walkAttribute(valueDescription._0, t, comments);
+    case /* Psig_extension */12 :
+        return walkExtension(valueDescription._0, t, comments);
+    
+  }
+}
+
+function walkIncludeDescription(id, t, comments) {
+  var match = partitionByLoc(comments, id.pincl_mod.pmty_loc);
+  attach(t.leading, id.pincl_mod.pmty_loc, match[0]);
+  walkModType(id.pincl_mod, t, match[1]);
+  return attach(t.trailing, id.pincl_mod.pmty_loc, match[2]);
+}
+
+function walkModuleDeclaration(md, t, comments) {
+  var match = partitionLeadingTrailing(comments, md.pmd_name.loc);
+  attach(t.leading, md.pmd_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(md.pmd_name.loc, match[1]);
+  attach(t.trailing, md.pmd_name.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], md.pmd_type.pmty_loc);
+  attach(t.leading, md.pmd_type.pmty_loc, match$2[0]);
+  walkModType(md.pmd_type, t, match$2[1]);
+  return attach(t.trailing, md.pmd_type.pmty_loc, match$2[2]);
+}
+
+function walkList(_prevLoc, getLoc, walkNode, _l, t, _comments) {
+  while(true) {
+    var comments = _comments;
+    var l = _l;
+    var prevLoc = _prevLoc;
+    if (comments === /* [] */0) {
+      return ;
+    }
+    if (!l) {
+      if (prevLoc !== undefined) {
+        return attach(t.trailing, prevLoc, comments);
+      } else {
+        return ;
+      }
+    }
+    var node = l.hd;
+    var currLoc = Curry._1(getLoc, node);
+    var match = partitionByLoc(comments, currLoc);
+    var leading = match[0];
+    if (prevLoc !== undefined) {
+      if (prevLoc.loc_end.pos_lnum === currLoc.loc_start.pos_lnum) {
+        var match$1 = partitionAdjacentTrailing(prevLoc, leading);
+        attach(t.trailing, prevLoc, match$1[0]);
+        attach(t.leading, currLoc, match$1[1]);
+      } else {
+        var match$2 = partitionByOnSameLine(prevLoc, leading);
+        attach(t.trailing, prevLoc, match$2[0]);
+        var match$3 = partitionByLoc(match$2[1], currLoc);
+        attach(t.leading, currLoc, match$3[0]);
+      }
+    } else {
+      attach(t.leading, currLoc, leading);
+    }
+    Curry._3(walkNode, node, t, match[1]);
+    _comments = match[2];
+    _l = l.tl;
+    _prevLoc = currLoc;
+    continue ;
+  };
+}
+
+function visitListButContinueWithRemainingComments(_prevLoc, newlineDelimited, getLoc, walkNode, _l, t, _comments) {
+  while(true) {
+    var comments = _comments;
+    var l = _l;
+    var prevLoc = _prevLoc;
+    if (comments === /* [] */0) {
+      return /* [] */0;
+    }
+    if (l) {
+      var node = l.hd;
+      var currLoc = Curry._1(getLoc, node);
+      var match = partitionByLoc(comments, currLoc);
+      var leading = match[0];
+      if (prevLoc !== undefined) {
+        if (prevLoc.loc_end.pos_lnum === currLoc.loc_start.pos_lnum) {
+          var match$1 = partitionAdjacentTrailing(prevLoc, leading);
+          attach(t.trailing, prevLoc, match$1[0]);
+          attach(t.leading, currLoc, match$1[1]);
+        } else {
+          var match$2 = partitionByOnSameLine(prevLoc, leading);
+          attach(t.trailing, prevLoc, match$2[0]);
+          var match$3 = partitionByLoc(match$2[1], currLoc);
+          attach(t.leading, currLoc, match$3[0]);
+        }
+      } else {
+        attach(t.leading, currLoc, leading);
+      }
+      Curry._3(walkNode, node, t, match[1]);
+      _comments = match[2];
+      _l = l.tl;
+      _prevLoc = currLoc;
+      continue ;
+    }
+    if (prevLoc === undefined) {
+      return comments;
+    }
+    var match$4 = newlineDelimited ? partitionByOnSameLine(prevLoc, comments) : partitionAdjacentTrailing(prevLoc, comments);
+    attach(t.trailing, prevLoc, match$4[0]);
+    return match$4[1];
+  };
+}
+
+function walkValueBindings(vbs, t, comments) {
+  return walkList(undefined, (function (n) {
+                return n.pvb_loc;
+              }), walkValueBinding, vbs, t, comments);
+}
+
+function walkOpenDescription(openDescription, t, comments) {
+  var loc = openDescription.popen_lid.loc;
+  var match = partitionLeadingTrailing(comments, loc);
+  attach(t.leading, loc, match[0]);
+  return attach(t.trailing, loc, match[1]);
+}
+
+function walkTypeDeclarations(typeDeclarations, t, comments) {
+  return walkList(undefined, (function (n) {
+                return n.ptype_loc;
+              }), walkTypeDeclaration, typeDeclarations, t, comments);
+}
+
+function walkTypeParam(param, t, comments) {
+  return walkTypExpr(param[0], t, comments);
+}
+
+function walkTypeDeclaration(td, t, comments) {
+  var match = partitionLeadingTrailing(comments, td.ptype_name.loc);
+  attach(t.leading, td.ptype_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(td.ptype_name.loc, match[1]);
+  var rest = match$1[1];
+  attach(t.trailing, td.ptype_name.loc, match$1[0]);
+  var typeParams = td.ptype_params;
+  var rest$1 = typeParams ? visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+            return param[0].ptyp_loc;
+          }), walkTypeParam, typeParams, t, rest) : rest;
+  var typexpr = td.ptype_manifest;
+  var rest$2;
+  if (typexpr !== undefined) {
+    var match$2 = partitionByLoc(rest$1, typexpr.ptyp_loc);
+    attach(t.leading, typexpr.ptyp_loc, match$2[0]);
+    walkTypExpr(typexpr, t, match$2[1]);
+    var match$3 = partitionAdjacentTrailing(typexpr.ptyp_loc, match$2[2]);
+    attach(t.trailing, typexpr.ptyp_loc, match$3[0]);
+    rest$2 = match$3[1];
+  } else {
+    rest$2 = rest$1;
+  }
+  var labelDeclarations = td.ptype_kind;
+  var rest$3;
+  if (typeof labelDeclarations === "number") {
+    rest$3 = rest$2;
+  } else if (labelDeclarations.TAG === /* Ptype_variant */0) {
+    rest$3 = walkConstructorDeclarations(labelDeclarations._0, t, rest$2);
+  } else {
+    walkList(undefined, (function (ld) {
+            return ld.pld_loc;
+          }), walkLabelDeclaration, labelDeclarations._0, t, rest$2);
+    rest$3 = /* [] */0;
+  }
+  return attach(t.trailing, td.ptype_loc, rest$3);
+}
+
+function walkLabelDeclarations(lds, t, comments) {
+  return visitListButContinueWithRemainingComments(undefined, false, (function (ld) {
+                return ld.pld_loc;
+              }), walkLabelDeclaration, lds, t, comments);
+}
+
+function walkLabelDeclaration(ld, t, comments) {
+  var match = partitionLeadingTrailing(comments, ld.pld_name.loc);
+  attach(t.leading, ld.pld_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(ld.pld_name.loc, match[1]);
+  attach(t.trailing, ld.pld_name.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], ld.pld_type.ptyp_loc);
+  attach(t.leading, ld.pld_type.ptyp_loc, match$2[0]);
+  walkTypExpr(ld.pld_type, t, match$2[1]);
+  return attach(t.trailing, ld.pld_type.ptyp_loc, match$2[2]);
+}
+
+function walkConstructorDeclarations(cds, t, comments) {
+  return visitListButContinueWithRemainingComments(undefined, false, (function (cd) {
+                return cd.pcd_loc;
+              }), walkConstructorDeclaration, cds, t, comments);
+}
+
+function walkConstructorDeclaration(cd, t, comments) {
+  var match = partitionLeadingTrailing(comments, cd.pcd_name.loc);
+  attach(t.leading, cd.pcd_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(cd.pcd_name.loc, match[1]);
+  attach(t.trailing, cd.pcd_name.loc, match$1[0]);
+  var rest = walkConstructorArguments(cd.pcd_args, t, match$1[1]);
+  var typexpr = cd.pcd_res;
+  var rest$1;
+  if (typexpr !== undefined) {
+    var match$2 = partitionByLoc(rest, typexpr.ptyp_loc);
+    attach(t.leading, typexpr.ptyp_loc, match$2[0]);
+    walkTypExpr(typexpr, t, match$2[1]);
+    var match$3 = partitionAdjacentTrailing(typexpr.ptyp_loc, match$2[2]);
+    attach(t.trailing, typexpr.ptyp_loc, match$3[0]);
+    rest$1 = match$3[1];
+  } else {
+    rest$1 = rest;
+  }
+  return attach(t.trailing, cd.pcd_loc, rest$1);
+}
+
+function walkConstructorArguments(args, t, comments) {
+  if (args.TAG === /* Pcstr_tuple */0) {
+    return visitListButContinueWithRemainingComments(undefined, false, (function (n) {
+                  return n.ptyp_loc;
+                }), walkTypExpr, args._0, t, comments);
+  } else {
+    return walkLabelDeclarations(args._0, t, comments);
+  }
+}
+
+function walkValueBinding(vb, t, comments) {
+  var match = vb.pvb_pat;
+  var match$1 = vb.pvb_expr;
+  var match$2 = match.ppat_desc;
+  var vb$1;
+  if (typeof match$2 === "number" || match$2.TAG !== /* Ppat_constraint */10) {
+    vb$1 = vb;
+  } else {
+    var typ = match$2._1;
+    var match$3 = typ.ptyp_desc;
+    if (typeof match$3 === "number" || match$3.TAG !== /* Ptyp_poly */8) {
+      vb$1 = vb;
+    } else {
+      var pat = match$2._0;
+      if (match$3._0) {
+        var match$4 = match$1.pexp_desc;
+        var t$1 = match$3._1;
+        if (typeof match$4 === "number") {
+          vb$1 = vb;
+        } else {
+          switch (match$4.TAG | 0) {
+            case /* Pexp_fun */4 :
+                var init = vb.pvb_pat;
+                var init$1 = pat.ppat_loc;
+                vb$1 = {
+                  pvb_pat: {
+                    ppat_desc: init.ppat_desc,
+                    ppat_loc: {
+                      loc_start: init$1.loc_start,
+                      loc_end: t$1.ptyp_loc.loc_end,
+                      loc_ghost: init$1.loc_ghost
+                    },
+                    ppat_attributes: init.ppat_attributes
+                  },
+                  pvb_expr: vb.pvb_expr,
+                  pvb_attributes: vb.pvb_attributes,
+                  pvb_loc: vb.pvb_loc
+                };
+                break;
+            case /* Pexp_newtype */31 :
+                var match$5 = match$4._1.pexp_desc;
+                if (typeof match$5 === "number" || match$5.TAG !== /* Pexp_constraint */19) {
+                  vb$1 = vb;
+                } else {
+                  var init$2 = match.ppat_loc;
+                  vb$1 = {
+                    pvb_pat: {
+                      ppat_desc: {
+                        TAG: /* Ppat_constraint */10,
+                        _0: pat,
+                        _1: typ
+                      },
+                      ppat_loc: {
+                        loc_start: init$2.loc_start,
+                        loc_end: t$1.ptyp_loc.loc_end,
+                        loc_ghost: init$2.loc_ghost
+                      },
+                      ppat_attributes: match.ppat_attributes
+                    },
+                    pvb_expr: match$5._0,
+                    pvb_attributes: vb.pvb_attributes,
+                    pvb_loc: vb.pvb_loc
+                  };
+                }
+                break;
+            default:
+              vb$1 = vb;
+          }
+        }
+      } else {
+        var match$6 = match$1.pexp_desc;
+        if (typeof match$6 === "number" || match$6.TAG !== /* Pexp_constraint */19) {
+          vb$1 = vb;
+        } else {
+          var t$2 = match$3._1;
+          var init$3 = pat.ppat_loc;
+          vb$1 = {
+            pvb_pat: Ast_helper.Pat.constraint_({
+                  loc_start: init$3.loc_start,
+                  loc_end: t$2.ptyp_loc.loc_end,
+                  loc_ghost: init$3.loc_ghost
+                }, undefined, pat, t$2),
+            pvb_expr: match$6._0,
+            pvb_attributes: vb.pvb_attributes,
+            pvb_loc: vb.pvb_loc
+          };
+        }
+      }
+    }
+  }
+  var patternLoc = vb$1.pvb_pat.ppat_loc;
+  var exprLoc = vb$1.pvb_expr.pexp_loc;
+  var expr = vb$1.pvb_expr;
+  var match$7 = partitionByLoc(comments, patternLoc);
+  attach(t.leading, patternLoc, match$7[0]);
+  walkPattern(vb$1.pvb_pat, t, match$7[1]);
+  var match$8 = partitionAdjacentTrailing(patternLoc, match$7[2]);
+  attach(t.trailing, patternLoc, match$8[0]);
+  var match$9 = partitionByLoc(match$8[1], exprLoc);
+  var afterExpr = match$9[2];
+  var insideExpr = match$9[1];
+  var beforeExpr = match$9[0];
+  if (isBlockExpr(expr)) {
+    return walkExpr(expr, t, List.concat({
+                    hd: beforeExpr,
+                    tl: {
+                      hd: insideExpr,
+                      tl: {
+                        hd: afterExpr,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+  } else {
+    attach(t.leading, exprLoc, beforeExpr);
+    walkExpr(expr, t, insideExpr);
+    return attach(t.trailing, exprLoc, afterExpr);
+  }
+}
+
+function walkExpr(_expr, t, _comments) {
+  while(true) {
+    var comments = _comments;
+    var expr = _expr;
+    var longident = expr.pexp_desc;
+    var exit = 0;
+    var exprs;
+    var expr$1;
+    var cases;
+    if (comments === /* [] */0) {
+      return ;
+    }
+    var exit$1 = 0;
+    if (typeof longident === "number") {
+      return ;
+    }
+    switch (longident.TAG | 0) {
+      case /* Pexp_ident */0 :
+          var longident$1 = longident._0;
+          var match = partitionLeadingTrailing(comments, longident$1.loc);
+          attach(t.leading, longident$1.loc, match[0]);
+          return attach(t.trailing, longident$1.loc, match[1]);
+      case /* Pexp_constant */1 :
+          var match$1 = partitionLeadingTrailing(comments, expr.pexp_loc);
+          attach(t.leading, expr.pexp_loc, match$1[0]);
+          return attach(t.trailing, expr.pexp_loc, match$1[1]);
+      case /* Pexp_let */2 :
+          var expr2 = longident._2;
+          var match$2 = expr2.pexp_desc;
+          var valueBindings = longident._1;
+          var exit$2 = 0;
+          if (typeof match$2 === "number" || match$2.TAG !== /* Pexp_construct */9) {
+            exit$2 = 6;
+          } else {
+            var match$3 = match$2._0.txt;
+            switch (match$3.TAG | 0) {
+              case /* Lident */0 :
+                  if (match$3._0 === "()") {
+                    if (match$2._1 === undefined) {
+                      return walkValueBindings(valueBindings, t, comments);
+                    }
+                    exit$2 = 6;
+                  } else {
+                    exit$2 = 6;
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  exit$2 = 6;
+                  break;
+              
+            }
+          }
+          if (exit$2 === 6) {
+            var comments$1 = visitListButContinueWithRemainingComments(undefined, true, (function (n) {
+                    if (n.pvb_pat.ppat_loc.loc_ghost) {
+                      return n.pvb_expr.pexp_loc;
+                    } else {
+                      return n.pvb_loc;
+                    }
+                  }), walkValueBinding, valueBindings, t, comments);
+            if (isBlockExpr(expr2)) {
+              _comments = comments$1;
+              _expr = expr2;
+              continue ;
+            }
+            var match$4 = partitionByLoc(comments$1, expr2.pexp_loc);
+            attach(t.leading, expr2.pexp_loc, match$4[0]);
+            walkExpr(expr2, t, match$4[1]);
+            return attach(t.trailing, expr2.pexp_loc, match$4[2]);
+          }
+          break;
+      case /* Pexp_apply */5 :
+          var callExpr = longident._0;
+          var match$5 = callExpr.pexp_desc;
+          var exit$3 = 0;
+          if (typeof match$5 === "number" || match$5.TAG !== /* Pexp_ident */0) {
+            exit$3 = 6;
+          } else {
+            var match$6 = match$5._0.txt;
+            switch (match$6.TAG | 0) {
+              case /* Lident */0 :
+                  var exit$4 = 0;
+                  switch (match$6._0) {
+                    case "!=" :
+                    case "!==" :
+                    case "&&" :
+                    case "*" :
+                    case "**" :
+                    case "*." :
+                    case "+" :
+                    case "++" :
+                    case "+." :
+                    case "-" :
+                    case "-." :
+                    case "/" :
+                    case "/." :
+                    case ":=" :
+                    case "<" :
+                    case "<=" :
+                    case "<>" :
+                    case "=" :
+                    case "==" :
+                    case ">" :
+                    case ">=" :
+                    case "^" :
+                    case "|." :
+                    case "|>" :
+                    case "||" :
+                        exit$4 = 8;
+                        break;
+                    case "!" :
+                    case "not" :
+                    case "~+" :
+                    case "~+." :
+                    case "~-" :
+                    case "~-." :
+                        exit$4 = 7;
+                        break;
+                    default:
+                      exit$3 = 6;
+                  }
+                  switch (exit$4) {
+                    case 7 :
+                        var match$7 = longident._1;
+                        if (match$7) {
+                          var match$8 = match$7.hd;
+                          if (typeof match$8[0] === "number" && !match$7.tl) {
+                            var argExpr = match$8[1];
+                            var match$9 = partitionByLoc(comments, argExpr.pexp_loc);
+                            attach(t.leading, argExpr.pexp_loc, match$9[0]);
+                            walkExpr(argExpr, t, match$9[1]);
+                            return attach(t.trailing, argExpr.pexp_loc, match$9[2]);
+                          }
+                          exit$3 = 6;
+                        } else {
+                          exit$3 = 6;
+                        }
+                        break;
+                    case 8 :
+                        var match$10 = longident._1;
+                        if (match$10) {
+                          var match$11 = match$10.hd;
+                          if (typeof match$11[0] === "number") {
+                            var match$12 = match$10.tl;
+                            if (match$12) {
+                              var match$13 = match$12.hd;
+                              if (typeof match$13[0] === "number" && !match$12.tl) {
+                                var operand2 = match$13[1];
+                                var operand1 = match$11[1];
+                                var match$14 = partitionByLoc(comments, operand1.pexp_loc);
+                                attach(t.leading, operand1.pexp_loc, match$14[0]);
+                                walkExpr(operand1, t, match$14[1]);
+                                var match$15 = partitionAdjacentTrailing(operand1.pexp_loc, match$14[2]);
+                                attach(t.trailing, operand1.pexp_loc, match$15[0]);
+                                var match$16 = partitionByLoc(match$15[1], operand2.pexp_loc);
+                                attach(t.leading, operand2.pexp_loc, match$16[0]);
+                                walkExpr(operand2, t, match$16[1]);
+                                return attach(t.trailing, operand2.pexp_loc, match$16[2]);
+                              }
+                              exit$3 = 6;
+                            } else {
+                              exit$3 = 6;
+                            }
+                          } else {
+                            exit$3 = 6;
+                          }
+                        } else {
+                          exit$3 = 6;
+                        }
+                        break;
+                    
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  exit$3 = 6;
+                  break;
+              
+            }
+          }
+          if (exit$3 === 6) {
+            var match$17 = partitionByLoc(comments, callExpr.pexp_loc);
+            var after = match$17[2];
+            var inside = match$17[1];
+            var before = match$17[0];
+            var after$1;
+            if (isBlockExpr(callExpr)) {
+              var match$18 = partitionAdjacentTrailing(callExpr.pexp_loc, after);
+              walkExpr(callExpr, t, List.concat({
+                        hd: before,
+                        tl: {
+                          hd: inside,
+                          tl: {
+                            hd: match$18[0],
+                            tl: /* [] */0
+                          }
+                        }
+                      }));
+              after$1 = match$18[1];
+            } else {
+              attach(t.leading, callExpr.pexp_loc, before);
+              walkExpr(callExpr, t, inside);
+              after$1 = after;
+            }
+            var match$19 = partitionAdjacentTrailing(callExpr.pexp_loc, after$1);
+            attach(t.trailing, callExpr.pexp_loc, match$19[0]);
+            return walkList(undefined, (function (param) {
+                          var expr = param[1];
+                          var match = expr.pexp_attributes;
+                          if (!match) {
+                            return expr.pexp_loc;
+                          }
+                          var match$1 = match.hd[0];
+                          if (match$1.txt !== "ns.namedArgLoc") {
+                            return expr.pexp_loc;
+                          }
+                          var loc = match$1.loc;
+                          return {
+                                  loc_start: loc.loc_start,
+                                  loc_end: expr.pexp_loc.loc_end,
+                                  loc_ghost: loc.loc_ghost
+                                };
+                        }), walkExprArgument, longident._1, t, match$19[1]);
+          }
+          break;
+      case /* Pexp_match */6 :
+          var cases$1 = longident._1;
+          var expr1 = longident._0;
+          if (cases$1) {
+            var match$20 = cases$1.tl;
+            if (match$20 && !match$20.tl) {
+              var elseBranch = match$20.hd;
+              var $$case = cases$1.hd;
+              if (Res_parsetree_viewer.hasIfLetAttribute(expr.pexp_attributes)) {
+                var match$21 = partitionByLoc(comments, $$case.pc_lhs.ppat_loc);
+                attach(t.leading, $$case.pc_lhs.ppat_loc, match$21[0]);
+                walkPattern($$case.pc_lhs, t, match$21[1]);
+                var match$22 = partitionAdjacentTrailing($$case.pc_lhs.ppat_loc, match$21[2]);
+                attach(t.trailing, $$case.pc_lhs.ppat_loc, match$22[0]);
+                var match$23 = partitionByLoc(match$22[1], expr1.pexp_loc);
+                attach(t.leading, expr1.pexp_loc, match$23[0]);
+                walkExpr(expr1, t, match$23[1]);
+                var match$24 = partitionAdjacentTrailing(expr1.pexp_loc, match$23[2]);
+                attach(t.trailing, expr1.pexp_loc, match$24[0]);
+                var match$25 = partitionByLoc(match$24[1], $$case.pc_rhs.pexp_loc);
+                var after$2 = match$25[2];
+                var inside$1 = match$25[1];
+                var before$1 = match$25[0];
+                var after$3;
+                if (isBlockExpr($$case.pc_rhs)) {
+                  var match$26 = partitionAdjacentTrailing($$case.pc_rhs.pexp_loc, after$2);
+                  walkExpr($$case.pc_rhs, t, List.concat({
+                            hd: before$1,
+                            tl: {
+                              hd: inside$1,
+                              tl: {
+                                hd: match$26[0],
+                                tl: /* [] */0
+                              }
+                            }
+                          }));
+                  after$3 = match$26[1];
+                } else {
+                  attach(t.leading, $$case.pc_rhs.pexp_loc, before$1);
+                  walkExpr($$case.pc_rhs, t, inside$1);
+                  after$3 = after$2;
+                }
+                var match$27 = partitionAdjacentTrailing($$case.pc_rhs.pexp_loc, after$3);
+                attach(t.trailing, $$case.pc_rhs.pexp_loc, match$27[0]);
+                var match$28 = partitionByLoc(match$27[1], elseBranch.pc_rhs.pexp_loc);
+                var after$4 = match$28[2];
+                var inside$2 = match$28[1];
+                var before$2 = match$28[0];
+                var after$5;
+                if (isBlockExpr(elseBranch.pc_rhs)) {
+                  var match$29 = partitionAdjacentTrailing(elseBranch.pc_rhs.pexp_loc, after$4);
+                  walkExpr(elseBranch.pc_rhs, t, List.concat({
+                            hd: before$2,
+                            tl: {
+                              hd: inside$2,
+                              tl: {
+                                hd: match$29[0],
+                                tl: /* [] */0
+                              }
+                            }
+                          }));
+                  after$5 = match$29[1];
+                } else {
+                  attach(t.leading, elseBranch.pc_rhs.pexp_loc, before$2);
+                  walkExpr(elseBranch.pc_rhs, t, inside$2);
+                  after$5 = after$4;
+                }
+                return attach(t.trailing, elseBranch.pc_rhs.pexp_loc, after$5);
+              }
+              expr$1 = expr1;
+              cases = cases$1;
+              exit = 3;
+            } else {
+              expr$1 = expr1;
+              cases = cases$1;
+              exit = 3;
+            }
+          } else {
+            expr$1 = expr1;
+            cases = cases$1;
+            exit = 3;
+          }
+          break;
+      case /* Pexp_try */7 :
+          expr$1 = longident._0;
+          cases = longident._1;
+          exit = 3;
+          break;
+      case /* Pexp_tuple */8 :
+          var exprs$1 = longident._0;
+          if (exprs$1) {
+            exprs = exprs$1;
+            exit = 2;
+          } else {
+            exit = 1;
+          }
+          break;
+      case /* Pexp_construct */9 :
+          var longident$2 = longident._0;
+          var match$30 = longident$2.txt;
+          var exit$5 = 0;
+          switch (match$30.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$30._0) {
+                  case "::" :
+                      return walkList(undefined, (function (n) {
+                                    return n.pexp_loc;
+                                  }), walkExpr, collectListExprs(/* [] */0, expr), t, comments);
+                  case "[]" :
+                      exit = 1;
+                      break;
+                  default:
+                    exit$5 = 6;
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                exit$5 = 6;
+                break;
+            
+          }
+          if (exit$5 === 6) {
+            var args = longident._1;
+            var match$31 = partitionLeadingTrailing(comments, longident$2.loc);
+            var trailing = match$31[1];
+            attach(t.leading, longident$2.loc, match$31[0]);
+            if (args === undefined) {
+              return attach(t.trailing, longident$2.loc, trailing);
+            }
+            var match$32 = partitionAdjacentTrailing(longident$2.loc, trailing);
+            attach(t.trailing, longident$2.loc, match$32[0]);
+            _comments = match$32[1];
+            _expr = args;
+            continue ;
+          }
+          break;
+      case /* Pexp_variant */10 :
+          var expr$2 = longident._1;
+          if (expr$2 === undefined) {
+            return ;
+          }
+          _expr = expr$2;
+          continue ;
+      case /* Pexp_record */11 :
+          var spreadExpr = longident._1;
+          var comments$2;
+          if (spreadExpr !== undefined) {
+            var match$33 = partitionByLoc(comments, spreadExpr.pexp_loc);
+            attach(t.leading, spreadExpr.pexp_loc, match$33[0]);
+            walkExpr(spreadExpr, t, match$33[1]);
+            var match$34 = partitionAdjacentTrailing(spreadExpr.pexp_loc, match$33[2]);
+            attach(t.trailing, spreadExpr.pexp_loc, match$34[0]);
+            comments$2 = match$34[1];
+          } else {
+            comments$2 = comments;
+          }
+          return walkList(undefined, (function (param) {
+                        var init = param[0].loc;
+                        return {
+                                loc_start: init.loc_start,
+                                loc_end: param[1].pexp_loc.loc_end,
+                                loc_ghost: init.loc_ghost
+                              };
+                      }), walkExprRecordRow, longident._0, t, comments$2);
+      case /* Pexp_field */12 :
+          var longident$3 = longident._1;
+          var expr$3 = longident._0;
+          var match$35 = partitionByLoc(comments, expr$3.pexp_loc);
+          var trailing$1 = match$35[2];
+          var inside$3 = match$35[1];
+          var leading = match$35[0];
+          var trailing$2;
+          if (isBlockExpr(expr$3)) {
+            var match$36 = partitionAdjacentTrailing(expr$3.pexp_loc, trailing$1);
+            walkExpr(expr$3, t, List.concat({
+                      hd: leading,
+                      tl: {
+                        hd: inside$3,
+                        tl: {
+                          hd: match$36[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            trailing$2 = match$36[1];
+          } else {
+            attach(t.leading, expr$3.pexp_loc, leading);
+            walkExpr(expr$3, t, inside$3);
+            trailing$2 = trailing$1;
+          }
+          var match$37 = partitionAdjacentTrailing(expr$3.pexp_loc, trailing$2);
+          attach(t.trailing, expr$3.pexp_loc, match$37[0]);
+          var match$38 = partitionLeadingTrailing(match$37[1], longident$3.loc);
+          attach(t.leading, longident$3.loc, match$38[0]);
+          return attach(t.trailing, longident$3.loc, match$38[1]);
+      case /* Pexp_setfield */13 :
+          var expr2$1 = longident._2;
+          var longident$4 = longident._1;
+          var expr1$1 = longident._0;
+          var match$39 = partitionByLoc(comments, expr1$1.pexp_loc);
+          var trailing$3 = match$39[2];
+          var inside$4 = match$39[1];
+          var leading$1 = match$39[0];
+          var rest;
+          if (isBlockExpr(expr1$1)) {
+            var match$40 = partitionAdjacentTrailing(expr1$1.pexp_loc, trailing$3);
+            walkExpr(expr1$1, t, List.concat({
+                      hd: leading$1,
+                      tl: {
+                        hd: inside$4,
+                        tl: {
+                          hd: match$40[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            rest = match$40[1];
+          } else {
+            var match$41 = partitionAdjacentTrailing(expr1$1.pexp_loc, trailing$3);
+            attach(t.leading, expr1$1.pexp_loc, leading$1);
+            walkExpr(expr1$1, t, inside$4);
+            attach(t.trailing, expr1$1.pexp_loc, match$41[0]);
+            rest = match$41[1];
+          }
+          var match$42 = partitionLeadingTrailing(rest, longident$4.loc);
+          attach(t.leading, longident$4.loc, match$42[0]);
+          var match$43 = partitionAdjacentTrailing(longident$4.loc, match$42[1]);
+          var rest$1 = match$43[1];
+          attach(t.trailing, longident$4.loc, match$43[0]);
+          if (isBlockExpr(expr2$1)) {
+            _comments = rest$1;
+            _expr = expr2$1;
+            continue ;
+          }
+          var match$44 = partitionByLoc(rest$1, expr2$1.pexp_loc);
+          attach(t.leading, expr2$1.pexp_loc, match$44[0]);
+          walkExpr(expr2$1, t, match$44[1]);
+          return attach(t.trailing, expr2$1.pexp_loc, match$44[2]);
+      case /* Pexp_array */14 :
+          var exprs$2 = longident._0;
+          if (exprs$2) {
+            exprs = exprs$2;
+            exit = 2;
+          } else {
+            exit = 1;
+          }
+          break;
+      case /* Pexp_ifthenelse */15 :
+          var elseExpr = longident._2;
+          var thenExpr = longident._1;
+          var ifExpr = longident._0;
+          var match$45 = partitionByLoc(comments, ifExpr.pexp_loc);
+          var trailing$4 = match$45[2];
+          var inside$5 = match$45[1];
+          var leading$2 = match$45[0];
+          var comments$3;
+          if (isBlockExpr(ifExpr)) {
+            var match$46 = partitionAdjacentTrailing(ifExpr.pexp_loc, trailing$4);
+            walkExpr(ifExpr, t, List.concat({
+                      hd: leading$2,
+                      tl: {
+                        hd: inside$5,
+                        tl: {
+                          hd: match$46[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            comments$3 = match$46[1];
+          } else {
+            attach(t.leading, ifExpr.pexp_loc, leading$2);
+            walkExpr(ifExpr, t, inside$5);
+            var match$47 = partitionAdjacentTrailing(ifExpr.pexp_loc, trailing$4);
+            attach(t.trailing, ifExpr.pexp_loc, match$47[0]);
+            comments$3 = match$47[1];
+          }
+          var match$48 = partitionByLoc(comments$3, thenExpr.pexp_loc);
+          var trailing$5 = match$48[2];
+          var inside$6 = match$48[1];
+          var leading$3 = match$48[0];
+          var comments$4;
+          if (isBlockExpr(thenExpr)) {
+            var match$49 = partitionAdjacentTrailing(thenExpr.pexp_loc, trailing$5);
+            walkExpr(thenExpr, t, List.concat({
+                      hd: leading$3,
+                      tl: {
+                        hd: inside$6,
+                        tl: {
+                          hd: match$49[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            comments$4 = match$49[1];
+          } else {
+            attach(t.leading, thenExpr.pexp_loc, leading$3);
+            walkExpr(thenExpr, t, inside$6);
+            var match$50 = partitionAdjacentTrailing(thenExpr.pexp_loc, trailing$5);
+            attach(t.trailing, thenExpr.pexp_loc, match$50[0]);
+            comments$4 = match$50[1];
+          }
+          if (elseExpr === undefined) {
+            return ;
+          }
+          if (isBlockExpr(elseExpr) || isIfThenElseExpr(elseExpr)) {
+            _comments = comments$4;
+            _expr = elseExpr;
+            continue ;
+          }
+          var match$51 = partitionByLoc(comments$4, elseExpr.pexp_loc);
+          attach(t.leading, elseExpr.pexp_loc, match$51[0]);
+          walkExpr(elseExpr, t, match$51[1]);
+          return attach(t.trailing, elseExpr.pexp_loc, match$51[2]);
+      case /* Pexp_sequence */16 :
+          var expr2$2 = longident._1;
+          var expr1$2 = longident._0;
+          var match$52 = partitionByLoc(comments, expr1$2.pexp_loc);
+          var trailing$6 = match$52[2];
+          var inside$7 = match$52[1];
+          var leading$4 = match$52[0];
+          var comments$5;
+          if (isBlockExpr(expr1$2)) {
+            var match$53 = partitionByOnSameLine(expr1$2.pexp_loc, trailing$6);
+            walkExpr(expr1$2, t, List.concat({
+                      hd: leading$4,
+                      tl: {
+                        hd: inside$7,
+                        tl: {
+                          hd: match$53[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            comments$5 = match$53[1];
+          } else {
+            attach(t.leading, expr1$2.pexp_loc, leading$4);
+            walkExpr(expr1$2, t, inside$7);
+            var match$54 = partitionByOnSameLine(expr1$2.pexp_loc, trailing$6);
+            attach(t.trailing, expr1$2.pexp_loc, match$54[0]);
+            comments$5 = match$54[1];
+          }
+          if (isBlockExpr(expr2$2)) {
+            _comments = comments$5;
+            _expr = expr2$2;
+            continue ;
+          }
+          var match$55 = partitionByLoc(comments$5, expr2$2.pexp_loc);
+          attach(t.leading, expr2$2.pexp_loc, match$55[0]);
+          walkExpr(expr2$2, t, match$55[1]);
+          return attach(t.trailing, expr2$2.pexp_loc, match$55[2]);
+      case /* Pexp_while */17 :
+          var expr2$3 = longident._1;
+          var expr1$3 = longident._0;
+          var match$56 = partitionByLoc(comments, expr1$3.pexp_loc);
+          var trailing$7 = match$56[2];
+          var inside$8 = match$56[1];
+          var leading$5 = match$56[0];
+          var rest$2;
+          if (isBlockExpr(expr1$3)) {
+            var match$57 = partitionAdjacentTrailing(expr1$3.pexp_loc, trailing$7);
+            walkExpr(expr1$3, t, List.concat({
+                      hd: leading$5,
+                      tl: {
+                        hd: inside$8,
+                        tl: {
+                          hd: match$57[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            rest$2 = match$57[1];
+          } else {
+            attach(t.leading, expr1$3.pexp_loc, leading$5);
+            walkExpr(expr1$3, t, inside$8);
+            var match$58 = partitionAdjacentTrailing(expr1$3.pexp_loc, trailing$7);
+            attach(t.trailing, expr1$3.pexp_loc, match$58[0]);
+            rest$2 = match$58[1];
+          }
+          if (isBlockExpr(expr2$3)) {
+            _comments = rest$2;
+            _expr = expr2$3;
+            continue ;
+          }
+          var match$59 = partitionByLoc(rest$2, expr2$3.pexp_loc);
+          attach(t.leading, expr2$3.pexp_loc, match$59[0]);
+          walkExpr(expr2$3, t, match$59[1]);
+          return attach(t.trailing, expr2$3.pexp_loc, match$59[2]);
+      case /* Pexp_for */18 :
+          var expr3 = longident._4;
+          var expr2$4 = longident._2;
+          var expr1$4 = longident._1;
+          var pat = longident._0;
+          var match$60 = partitionByLoc(comments, pat.ppat_loc);
+          attach(t.leading, pat.ppat_loc, match$60[0]);
+          walkPattern(pat, t, match$60[1]);
+          var match$61 = partitionAdjacentTrailing(pat.ppat_loc, match$60[2]);
+          attach(t.trailing, pat.ppat_loc, match$61[0]);
+          var match$62 = partitionByLoc(match$61[1], expr1$4.pexp_loc);
+          attach(t.leading, expr1$4.pexp_loc, match$62[0]);
+          walkExpr(expr1$4, t, match$62[1]);
+          var match$63 = partitionAdjacentTrailing(expr1$4.pexp_loc, match$62[2]);
+          attach(t.trailing, expr1$4.pexp_loc, match$63[0]);
+          var match$64 = partitionByLoc(match$63[1], expr2$4.pexp_loc);
+          attach(t.leading, expr2$4.pexp_loc, match$64[0]);
+          walkExpr(expr2$4, t, match$64[1]);
+          var match$65 = partitionAdjacentTrailing(expr2$4.pexp_loc, match$64[2]);
+          var rest$3 = match$65[1];
+          attach(t.trailing, expr2$4.pexp_loc, match$65[0]);
+          if (isBlockExpr(expr3)) {
+            _comments = rest$3;
+            _expr = expr3;
+            continue ;
+          }
+          var match$66 = partitionByLoc(rest$3, expr3.pexp_loc);
+          attach(t.leading, expr3.pexp_loc, match$66[0]);
+          walkExpr(expr3, t, match$66[1]);
+          return attach(t.trailing, expr3.pexp_loc, match$66[2]);
+      case /* Pexp_constraint */19 :
+          var typexpr = longident._1;
+          var expr$4 = longident._0;
+          var match$67 = partitionByLoc(comments, expr$4.pexp_loc);
+          attach(t.leading, expr$4.pexp_loc, match$67[0]);
+          walkExpr(expr$4, t, match$67[1]);
+          var match$68 = partitionAdjacentTrailing(expr$4.pexp_loc, match$67[2]);
+          attach(t.trailing, expr$4.pexp_loc, match$68[0]);
+          var match$69 = partitionByLoc(match$68[1], typexpr.ptyp_loc);
+          attach(t.leading, typexpr.ptyp_loc, match$69[0]);
+          walkTypExpr(typexpr, t, match$69[1]);
+          return attach(t.trailing, typexpr.ptyp_loc, match$69[2]);
+      case /* Pexp_coerce */20 :
+          var typexpr$1 = longident._2;
+          var optTypexpr = longident._1;
+          var expr$5 = longident._0;
+          var match$70 = partitionByLoc(comments, expr$5.pexp_loc);
+          attach(t.leading, expr$5.pexp_loc, match$70[0]);
+          walkExpr(expr$5, t, match$70[1]);
+          var match$71 = partitionAdjacentTrailing(expr$5.pexp_loc, match$70[2]);
+          attach(t.trailing, expr$5.pexp_loc, match$71[0]);
+          var rest$4;
+          if (optTypexpr !== undefined) {
+            var match$72 = partitionByLoc(comments, optTypexpr.ptyp_loc);
+            attach(t.leading, optTypexpr.ptyp_loc, match$72[0]);
+            walkTypExpr(optTypexpr, t, match$72[1]);
+            var match$73 = partitionAdjacentTrailing(optTypexpr.ptyp_loc, match$72[2]);
+            attach(t.trailing, optTypexpr.ptyp_loc, match$73[0]);
+            rest$4 = match$73[1];
+          } else {
+            rest$4 = match$71[1];
+          }
+          var match$74 = partitionByLoc(rest$4, typexpr$1.ptyp_loc);
+          attach(t.leading, typexpr$1.ptyp_loc, match$74[0]);
+          walkTypExpr(typexpr$1, t, match$74[1]);
+          return attach(t.trailing, typexpr$1.ptyp_loc, match$74[2]);
+      case /* Pexp_letmodule */25 :
+          var expr2$5 = longident._2;
+          var modExpr = longident._1;
+          var stringLoc = longident._0;
+          var match$75 = partitionLeadingTrailing(comments, expr.pexp_loc);
+          var init = expr.pexp_loc;
+          attach(t.leading, {
+                loc_start: init.loc_start,
+                loc_end: modExpr.pmod_loc.loc_end,
+                loc_ghost: init.loc_ghost
+              }, match$75[0]);
+          var match$76 = partitionLeadingTrailing(match$75[1], stringLoc.loc);
+          attach(t.leading, stringLoc.loc, match$76[0]);
+          var match$77 = partitionAdjacentTrailing(stringLoc.loc, match$76[1]);
+          attach(t.trailing, stringLoc.loc, match$77[0]);
+          var match$78 = partitionByLoc(match$77[1], modExpr.pmod_loc);
+          attach(t.leading, modExpr.pmod_loc, match$78[0]);
+          walkModExpr(modExpr, t, match$78[1]);
+          var match$79 = partitionByOnSameLine(modExpr.pmod_loc, match$78[2]);
+          var rest$5 = match$79[1];
+          attach(t.trailing, modExpr.pmod_loc, match$79[0]);
+          if (isBlockExpr(expr2$5)) {
+            _comments = rest$5;
+            _expr = expr2$5;
+            continue ;
+          }
+          var match$80 = partitionByLoc(rest$5, expr2$5.pexp_loc);
+          attach(t.leading, expr2$5.pexp_loc, match$80[0]);
+          walkExpr(expr2$5, t, match$80[1]);
+          return attach(t.trailing, expr2$5.pexp_loc, match$80[2]);
+      case /* Pexp_letexception */26 :
+          var expr2$6 = longident._1;
+          var extensionConstructor = longident._0;
+          var match$81 = partitionLeadingTrailing(comments, expr.pexp_loc);
+          var init$1 = expr.pexp_loc;
+          attach(t.leading, {
+                loc_start: init$1.loc_start,
+                loc_end: extensionConstructor.pext_loc.loc_end,
+                loc_ghost: init$1.loc_ghost
+              }, match$81[0]);
+          var match$82 = partitionByLoc(match$81[1], extensionConstructor.pext_loc);
+          attach(t.leading, extensionConstructor.pext_loc, match$82[0]);
+          walkExtConstr(extensionConstructor, t, match$82[1]);
+          var match$83 = partitionByOnSameLine(extensionConstructor.pext_loc, match$82[2]);
+          var rest$6 = match$83[1];
+          attach(t.trailing, extensionConstructor.pext_loc, match$83[0]);
+          if (isBlockExpr(expr2$6)) {
+            _comments = rest$6;
+            _expr = expr2$6;
+            continue ;
+          }
+          var match$84 = partitionByLoc(rest$6, expr2$6.pexp_loc);
+          attach(t.leading, expr2$6.pexp_loc, match$84[0]);
+          walkExpr(expr2$6, t, match$84[1]);
+          return attach(t.trailing, expr2$6.pexp_loc, match$84[2]);
+      case /* Pexp_assert */27 :
+      case /* Pexp_lazy */28 :
+          exit$1 = 5;
+          break;
+      case /* Pexp_fun */4 :
+      case /* Pexp_newtype */31 :
+          exit = 4;
+          break;
+      case /* Pexp_pack */32 :
+          var modExpr$1 = longident._0;
+          var match$85 = partitionByLoc(comments, modExpr$1.pmod_loc);
+          attach(t.leading, modExpr$1.pmod_loc, match$85[0]);
+          walkModExpr(modExpr$1, t, match$85[1]);
+          return attach(t.trailing, modExpr$1.pmod_loc, match$85[2]);
+      case /* Pexp_open */33 :
+          var expr2$7 = longident._2;
+          var longident$5 = longident._1;
+          var match$86 = partitionLeadingTrailing(comments, expr.pexp_loc);
+          var init$2 = expr.pexp_loc;
+          attach(t.leading, {
+                loc_start: init$2.loc_start,
+                loc_end: longident$5.loc.loc_end,
+                loc_ghost: init$2.loc_ghost
+              }, match$86[0]);
+          var match$87 = partitionLeadingTrailing(match$86[1], longident$5.loc);
+          attach(t.leading, longident$5.loc, match$87[0]);
+          var match$88 = partitionByOnSameLine(longident$5.loc, match$87[1]);
+          var rest$7 = match$88[1];
+          attach(t.trailing, longident$5.loc, match$88[0]);
+          if (isBlockExpr(expr2$7)) {
+            _comments = rest$7;
+            _expr = expr2$7;
+            continue ;
+          }
+          var match$89 = partitionByLoc(rest$7, expr2$7.pexp_loc);
+          attach(t.leading, expr2$7.pexp_loc, match$89[0]);
+          walkExpr(expr2$7, t, match$89[1]);
+          return attach(t.trailing, expr2$7.pexp_loc, match$89[2]);
+      case /* Pexp_extension */34 :
+          var extension = longident._0;
+          var exit$6 = 0;
+          switch (extension[0].txt) {
+            case "bs.obj" :
+            case "obj" :
+                exit$6 = 6;
+                break;
+            default:
+              return walkExtension(extension, t, comments);
+          }
+          if (exit$6 === 6) {
+            var match$90 = extension[1];
+            if (match$90.TAG !== /* PStr */0) {
+              return walkExtension(extension, t, comments);
+            }
+            var match$91 = match$90._0;
+            if (!match$91) {
+              return walkExtension(extension, t, comments);
+            }
+            var match$92 = match$91.hd.pstr_desc;
+            if (match$92.TAG !== /* Pstr_eval */0) {
+              return walkExtension(extension, t, comments);
+            }
+            var match$93 = match$92._0.pexp_desc;
+            if (typeof match$93 === "number" || !(match$93.TAG === /* Pexp_record */11 && !(match$92._1 || match$91.tl))) {
+              return walkExtension(extension, t, comments);
+            } else {
+              return walkList(undefined, (function (param) {
+                            var init = param[0].loc;
+                            return {
+                                    loc_start: init.loc_start,
+                                    loc_end: param[1].pexp_loc.loc_end,
+                                    loc_ghost: init.loc_ghost
+                                  };
+                          }), walkExprRecordRow, match$93._0, t, comments);
+            }
+          }
+          break;
+      default:
+        return ;
+    }
+    if (exit$1 === 5) {
+      var expr$6 = longident._0;
+      if (isBlockExpr(expr$6)) {
+        _expr = expr$6;
+        continue ;
+      }
+      var match$94 = partitionByLoc(comments, expr$6.pexp_loc);
+      attach(t.leading, expr$6.pexp_loc, match$94[0]);
+      walkExpr(expr$6, t, match$94[1]);
+      return attach(t.trailing, expr$6.pexp_loc, match$94[2]);
+    }
+    switch (exit) {
+      case 1 :
+          return attach(t.inside, expr.pexp_loc, comments);
+      case 2 :
+          return walkList(undefined, (function (n) {
+                        return n.pexp_loc;
+                      }), walkExpr, exprs, t, comments);
+      case 3 :
+          var match$95 = partitionByLoc(comments, expr$1.pexp_loc);
+          var after$6 = match$95[2];
+          var inside$9 = match$95[1];
+          var before$3 = match$95[0];
+          var after$7;
+          if (isBlockExpr(expr$1)) {
+            var match$96 = partitionAdjacentTrailing(expr$1.pexp_loc, after$6);
+            walkExpr(expr$1, t, List.concat({
+                      hd: before$3,
+                      tl: {
+                        hd: inside$9,
+                        tl: {
+                          hd: match$96[0],
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+            after$7 = match$96[1];
+          } else {
+            attach(t.leading, expr$1.pexp_loc, before$3);
+            walkExpr(expr$1, t, inside$9);
+            after$7 = after$6;
+          }
+          var match$97 = partitionAdjacentTrailing(expr$1.pexp_loc, after$7);
+          attach(t.trailing, expr$1.pexp_loc, match$97[0]);
+          return walkList(undefined, (function (n) {
+                        var init = n.pc_lhs.ppat_loc;
+                        return {
+                                loc_start: init.loc_start,
+                                loc_end: n.pc_rhs.pexp_loc.loc_end,
+                                loc_ghost: init.loc_ghost
+                              };
+                      }), walkCase, cases, t, match$97[1]);
+      case 4 :
+          var match$98 = funExpr(expr);
+          var returnExpr = match$98[2];
+          var comments$6 = visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+                  var pattern = param[3];
+                  var exprOpt = param[2];
+                  var match = pattern.ppat_attributes;
+                  var startPos;
+                  if (match) {
+                    var match$1 = match.hd[0];
+                    startPos = match$1.txt === "ns.namedArgLoc" ? match$1.loc.loc_start : pattern.ppat_loc.loc_start;
+                  } else {
+                    startPos = pattern.ppat_loc.loc_start;
+                  }
+                  if (exprOpt !== undefined) {
+                    var init = pattern.ppat_loc;
+                    return {
+                            loc_start: startPos,
+                            loc_end: exprOpt.pexp_loc.loc_end,
+                            loc_ghost: init.loc_ghost
+                          };
+                  }
+                  var init$1 = pattern.ppat_loc;
+                  return {
+                          loc_start: startPos,
+                          loc_end: init$1.loc_end,
+                          loc_ghost: init$1.loc_ghost
+                        };
+                }), walkExprPararameter, match$98[1], t, comments);
+          var match$99 = returnExpr.pexp_desc;
+          var exit$7 = 0;
+          if (typeof match$99 === "number" || match$99.TAG !== /* Pexp_constraint */19) {
+            exit$7 = 5;
+          } else {
+            var typ = match$99._1;
+            var expr$7 = match$99._0;
+            if (expr$7.pexp_loc.loc_start.pos_cnum >= typ.ptyp_loc.loc_end.pos_cnum) {
+              var match$100 = partitionByLoc(comments$6, typ.ptyp_loc);
+              attach(t.leading, typ.ptyp_loc, match$100[0]);
+              walkTypExpr(typ, t, match$100[1]);
+              var match$101 = partitionAdjacentTrailing(typ.ptyp_loc, match$100[2]);
+              var comments$7 = match$101[1];
+              attach(t.trailing, typ.ptyp_loc, match$101[0]);
+              if (isBlockExpr(expr$7)) {
+                _comments = comments$7;
+                _expr = expr$7;
+                continue ;
+              }
+              var match$102 = partitionByLoc(comments$7, expr$7.pexp_loc);
+              attach(t.leading, expr$7.pexp_loc, match$102[0]);
+              walkExpr(expr$7, t, match$102[1]);
+              return attach(t.trailing, expr$7.pexp_loc, match$102[2]);
+            }
+            exit$7 = 5;
+          }
+          if (exit$7 === 5) {
+            if (isBlockExpr(returnExpr)) {
+              _comments = comments$6;
+              _expr = returnExpr;
+              continue ;
+            }
+            var match$103 = partitionByLoc(comments$6, returnExpr.pexp_loc);
+            attach(t.leading, returnExpr.pexp_loc, match$103[0]);
+            walkExpr(returnExpr, t, match$103[1]);
+            return attach(t.trailing, returnExpr.pexp_loc, match$103[2]);
+          }
+          break;
+      
+    }
+  };
+}
+
+function walkExprPararameter(param, t, comments) {
+  var pattern = param[3];
+  var exprOpt = param[2];
+  var match = partitionByLoc(comments, pattern.ppat_loc);
+  var trailing = match[2];
+  attach(t.leading, pattern.ppat_loc, match[0]);
+  walkPattern(pattern, t, match[1]);
+  if (exprOpt === undefined) {
+    return attach(t.trailing, pattern.ppat_loc, trailing);
+  }
+  var match$1 = partitionAdjacentTrailing(pattern.ppat_loc, trailing);
+  var rest = match$1[1];
+  attach(t.trailing, pattern.ppat_loc, trailing);
+  if (isBlockExpr(exprOpt)) {
+    return walkExpr(exprOpt, t, rest);
+  }
+  var match$2 = partitionByLoc(rest, exprOpt.pexp_loc);
+  attach(t.leading, exprOpt.pexp_loc, match$2[0]);
+  walkExpr(exprOpt, t, match$2[1]);
+  return attach(t.trailing, exprOpt.pexp_loc, match$2[2]);
+}
+
+function walkExprArgument(param, t, comments) {
+  var expr = param[1];
+  var match = expr.pexp_attributes;
+  if (match) {
+    var match$1 = match.hd[0];
+    if (match$1.txt === "ns.namedArgLoc") {
+      var loc = match$1.loc;
+      var match$2 = partitionLeadingTrailing(comments, loc);
+      attach(t.leading, loc, match$2[0]);
+      var match$3 = partitionAdjacentTrailing(loc, match$2[1]);
+      attach(t.trailing, loc, match$3[0]);
+      var match$4 = partitionByLoc(match$3[1], expr.pexp_loc);
+      attach(t.leading, expr.pexp_loc, match$4[0]);
+      walkExpr(expr, t, match$4[1]);
+      return attach(t.trailing, expr.pexp_loc, match$4[2]);
+    }
+    
+  }
+  var match$5 = partitionByLoc(comments, expr.pexp_loc);
+  attach(t.leading, expr.pexp_loc, match$5[0]);
+  walkExpr(expr, t, match$5[1]);
+  return attach(t.trailing, expr.pexp_loc, match$5[2]);
+}
+
+function walkCase($$case, t, comments) {
+  var match = partitionByLoc(comments, $$case.pc_lhs.ppat_loc);
+  walkPattern($$case.pc_lhs, t, List.concat({
+            hd: match[0],
+            tl: {
+              hd: match[1],
+              tl: /* [] */0
+            }
+          }));
+  var match$1 = partitionAdjacentTrailing($$case.pc_lhs.ppat_loc, match[2]);
+  var rest = match$1[1];
+  attach(t.trailing, $$case.pc_lhs.ppat_loc, match$1[0]);
+  var expr = $$case.pc_guard;
+  var comments$1;
+  if (expr !== undefined) {
+    var match$2 = partitionByLoc(rest, expr.pexp_loc);
+    var inside = match$2[1];
+    var before = match$2[0];
+    var match$3 = partitionAdjacentTrailing(expr.pexp_loc, match$2[2]);
+    var afterExpr = match$3[0];
+    if (isBlockExpr(expr)) {
+      walkExpr(expr, t, List.concat({
+                hd: before,
+                tl: {
+                  hd: inside,
+                  tl: {
+                    hd: afterExpr,
+                    tl: /* [] */0
+                  }
+                }
+              }));
+    } else {
+      attach(t.leading, expr.pexp_loc, before);
+      walkExpr(expr, t, inside);
+      attach(t.trailing, expr.pexp_loc, afterExpr);
+    }
+    comments$1 = match$3[1];
+  } else {
+    comments$1 = rest;
+  }
+  if (isBlockExpr($$case.pc_rhs)) {
+    return walkExpr($$case.pc_rhs, t, comments$1);
+  }
+  var match$4 = partitionByLoc(comments$1, $$case.pc_rhs.pexp_loc);
+  attach(t.leading, $$case.pc_rhs.pexp_loc, match$4[0]);
+  walkExpr($$case.pc_rhs, t, match$4[1]);
+  return attach(t.trailing, $$case.pc_rhs.pexp_loc, match$4[2]);
+}
+
+function walkExprRecordRow(param, t, comments) {
+  var expr = param[1];
+  var longident = param[0];
+  var match = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(longident.loc, match[1]);
+  attach(t.trailing, longident.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], expr.pexp_loc);
+  attach(t.leading, expr.pexp_loc, match$2[0]);
+  walkExpr(expr, t, match$2[1]);
+  return attach(t.trailing, expr.pexp_loc, match$2[2]);
+}
+
+function walkExtConstr(extConstr, t, comments) {
+  var match = partitionLeadingTrailing(comments, extConstr.pext_name.loc);
+  attach(t.leading, extConstr.pext_name.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(extConstr.pext_name.loc, match[1]);
+  attach(t.trailing, extConstr.pext_name.loc, match$1[0]);
+  return walkExtensionConstructorKind(extConstr.pext_kind, t, match$1[1]);
+}
+
+function walkExtensionConstructorKind(kind, t, comments) {
+  if (kind.TAG === /* Pext_decl */0) {
+    var maybeTypExpr = kind._1;
+    var rest = walkConstructorArguments(kind._0, t, comments);
+    if (maybeTypExpr === undefined) {
+      return ;
+    }
+    var match = partitionByLoc(rest, maybeTypExpr.ptyp_loc);
+    attach(t.leading, maybeTypExpr.ptyp_loc, match[0]);
+    walkTypExpr(maybeTypExpr, t, match[1]);
+    return attach(t.trailing, maybeTypExpr.ptyp_loc, match[2]);
+  }
+  var longident = kind._0;
+  var match$1 = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match$1[0]);
+  return attach(t.trailing, longident.loc, match$1[1]);
+}
+
+function walkModExpr(modExpr, t, comments) {
+  var longident = modExpr.pmod_desc;
+  switch (longident.TAG | 0) {
+    case /* Pmod_ident */0 :
+        var longident$1 = longident._0;
+        var match = partitionLeadingTrailing(comments, longident$1.loc);
+        attach(t.leading, longident$1.loc, match[0]);
+        return attach(t.trailing, longident$1.loc, match[1]);
+    case /* Pmod_structure */1 :
+        var structure = longident._0;
+        if (structure) {
+          return walkStructure(structure, t, comments);
+        } else {
+          return attach(t.inside, modExpr.pmod_loc, comments);
+        }
+    case /* Pmod_functor */2 :
+        var match$1 = modExprFunctor(modExpr);
+        var returnModExpr = match$1[1];
+        var comments$1 = visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+                var modTypeOption = param[2];
+                var lbl = param[1];
+                if (modTypeOption === undefined) {
+                  return lbl.loc;
+                }
+                var init = lbl.loc;
+                return {
+                        loc_start: init.loc_start,
+                        loc_end: modTypeOption.pmty_loc.loc_end,
+                        loc_ghost: init.loc_ghost
+                      };
+              }), walkModExprParameter, match$1[0], t, comments);
+        var match$2 = returnModExpr.pmod_desc;
+        if (match$2.TAG === /* Pmod_constraint */4) {
+          var modType = match$2._1;
+          var modExpr$1 = match$2._0;
+          if (modType.pmty_loc.loc_end.pos_cnum <= modExpr$1.pmod_loc.loc_start.pos_cnum) {
+            var match$3 = partitionByLoc(comments$1, modType.pmty_loc);
+            attach(t.leading, modType.pmty_loc, match$3[0]);
+            walkModType(modType, t, match$3[1]);
+            var match$4 = partitionAdjacentTrailing(modType.pmty_loc, match$3[2]);
+            attach(t.trailing, modType.pmty_loc, match$4[0]);
+            var match$5 = partitionByLoc(match$4[1], modExpr$1.pmod_loc);
+            attach(t.leading, modExpr$1.pmod_loc, match$5[0]);
+            walkModExpr(modExpr$1, t, match$5[1]);
+            return attach(t.trailing, modExpr$1.pmod_loc, match$5[2]);
+          }
+          
+        }
+        var match$6 = partitionByLoc(comments$1, returnModExpr.pmod_loc);
+        attach(t.leading, returnModExpr.pmod_loc, match$6[0]);
+        walkModExpr(returnModExpr, t, match$6[1]);
+        return attach(t.trailing, returnModExpr.pmod_loc, match$6[2]);
+    case /* Pmod_apply */3 :
+        var modExprs = modExprApply(modExpr);
+        return walkList(undefined, (function (n) {
+                      return n.pmod_loc;
+                    }), walkModExpr, modExprs, t, comments);
+    case /* Pmod_constraint */4 :
+        var modtype = longident._1;
+        var modexpr = longident._0;
+        if (Caml_obj.caml_greaterequal(modtype.pmty_loc.loc_start, modexpr.pmod_loc.loc_end)) {
+          var match$7 = partitionByLoc(comments, modexpr.pmod_loc);
+          attach(t.leading, modexpr.pmod_loc, match$7[0]);
+          walkModExpr(modexpr, t, match$7[1]);
+          var match$8 = partitionAdjacentTrailing(modexpr.pmod_loc, match$7[2]);
+          attach(t.trailing, modexpr.pmod_loc, match$8[0]);
+          var match$9 = partitionByLoc(match$8[1], modtype.pmty_loc);
+          attach(t.leading, modtype.pmty_loc, match$9[0]);
+          walkModType(modtype, t, match$9[1]);
+          return attach(t.trailing, modtype.pmty_loc, match$9[2]);
+        }
+        var match$10 = partitionByLoc(comments, modtype.pmty_loc);
+        attach(t.leading, modtype.pmty_loc, match$10[0]);
+        walkModType(modtype, t, match$10[1]);
+        var match$11 = partitionAdjacentTrailing(modtype.pmty_loc, match$10[2]);
+        attach(t.trailing, modtype.pmty_loc, match$11[0]);
+        var match$12 = partitionByLoc(match$11[1], modexpr.pmod_loc);
+        attach(t.leading, modexpr.pmod_loc, match$12[0]);
+        walkModExpr(modexpr, t, match$12[1]);
+        return attach(t.trailing, modexpr.pmod_loc, match$12[2]);
+    case /* Pmod_unpack */5 :
+        var expr = longident._0;
+        var match$13 = partitionByLoc(comments, expr.pexp_loc);
+        attach(t.leading, expr.pexp_loc, match$13[0]);
+        walkExpr(expr, t, match$13[1]);
+        return attach(t.trailing, expr.pexp_loc, match$13[2]);
+    case /* Pmod_extension */6 :
+        return walkExtension(longident._0, t, comments);
+    
+  }
+}
+
+function walkModExprParameter(parameter, t, comments) {
+  var modTypeOption = parameter[2];
+  var lbl = parameter[1];
+  var match = partitionLeadingTrailing(comments, lbl.loc);
+  var trailing = match[1];
+  attach(t.leading, lbl.loc, match[0]);
+  if (modTypeOption === undefined) {
+    return attach(t.trailing, lbl.loc, trailing);
+  }
+  var match$1 = partitionAdjacentTrailing(lbl.loc, trailing);
+  attach(t.trailing, lbl.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], modTypeOption.pmty_loc);
+  attach(t.leading, modTypeOption.pmty_loc, match$2[0]);
+  walkModType(modTypeOption, t, match$2[1]);
+  return attach(t.trailing, modTypeOption.pmty_loc, match$2[2]);
+}
+
+function walkModType(modType, t, comments) {
+  var signature = modType.pmty_desc;
+  switch (signature.TAG | 0) {
+    case /* Pmty_signature */1 :
+        var signature$1 = signature._0;
+        if (signature$1) {
+          return walkSignature(signature$1, t, comments);
+        } else {
+          return attach(t.inside, modType.pmty_loc, comments);
+        }
+    case /* Pmty_functor */2 :
+        var match = functorType(modType);
+        var returnModType = match[1];
+        var comments$1 = visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+                var modTypeOption = param[2];
+                var lbl = param[1];
+                if (modTypeOption === undefined) {
+                  return lbl.loc;
+                }
+                if (lbl.txt === "_") {
+                  return modTypeOption.pmty_loc;
+                }
+                var init = lbl.loc;
+                return {
+                        loc_start: init.loc_start,
+                        loc_end: modTypeOption.pmty_loc.loc_end,
+                        loc_ghost: init.loc_ghost
+                      };
+              }), walkModTypeParameter, match[0], t, comments);
+        var match$1 = partitionByLoc(comments$1, returnModType.pmty_loc);
+        attach(t.leading, returnModType.pmty_loc, match$1[0]);
+        walkModType(returnModType, t, match$1[1]);
+        return attach(t.trailing, returnModType.pmty_loc, match$1[2]);
+    case /* Pmty_with */3 :
+        var modType$1 = signature._0;
+        var match$2 = partitionByLoc(comments, modType$1.pmty_loc);
+        attach(t.leading, modType$1.pmty_loc, match$2[0]);
+        walkModType(modType$1, t, match$2[1]);
+        return attach(t.trailing, modType$1.pmty_loc, match$2[2]);
+    case /* Pmty_typeof */4 :
+        var modExpr = signature._0;
+        var match$3 = partitionByLoc(comments, modExpr.pmod_loc);
+        attach(t.leading, modExpr.pmod_loc, match$3[0]);
+        walkModExpr(modExpr, t, match$3[1]);
+        return attach(t.trailing, modExpr.pmod_loc, match$3[2]);
+    case /* Pmty_extension */5 :
+        return walkExtension(signature._0, t, comments);
+    case /* Pmty_ident */0 :
+    case /* Pmty_alias */6 :
+        break;
+    
+  }
+  var longident = signature._0;
+  var match$4 = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match$4[0]);
+  return attach(t.trailing, longident.loc, match$4[1]);
+}
+
+function walkModTypeParameter(param, t, comments) {
+  var modTypeOption = param[2];
+  var lbl = param[1];
+  var match = partitionLeadingTrailing(comments, lbl.loc);
+  var trailing = match[1];
+  attach(t.leading, lbl.loc, match[0]);
+  if (modTypeOption === undefined) {
+    return attach(t.trailing, lbl.loc, trailing);
+  }
+  var match$1 = partitionAdjacentTrailing(lbl.loc, trailing);
+  attach(t.trailing, lbl.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], modTypeOption.pmty_loc);
+  attach(t.leading, modTypeOption.pmty_loc, match$2[0]);
+  walkModType(modTypeOption, t, match$2[1]);
+  return attach(t.trailing, modTypeOption.pmty_loc, match$2[2]);
+}
+
+function walkPattern(_pat, t, comments) {
+  while(true) {
+    var pat = _pat;
+    var patterns = pat.ppat_desc;
+    if (comments === /* [] */0) {
+      return ;
+    }
+    var exit = 0;
+    if (typeof patterns === "number") {
+      return ;
+    }
+    switch (patterns.TAG | 0) {
+      case /* Ppat_alias */1 :
+          var alias = patterns._1;
+          var pat$1 = patterns._0;
+          var match = partitionByLoc(comments, pat$1.ppat_loc);
+          var leading = match[0];
+          attach(t.leading, pat$1.ppat_loc, leading);
+          walkPattern(pat$1, t, match[1]);
+          var match$1 = partitionAdjacentTrailing(pat$1.ppat_loc, match[2]);
+          attach(t.leading, pat$1.ppat_loc, leading);
+          attach(t.trailing, pat$1.ppat_loc, match$1[0]);
+          var match$2 = partitionLeadingTrailing(match$1[1], alias.loc);
+          attach(t.leading, alias.loc, match$2[0]);
+          return attach(t.trailing, alias.loc, match$2[1]);
+      case /* Ppat_tuple */4 :
+          var patterns$1 = patterns._0;
+          if (patterns$1) {
+            return walkList(undefined, (function (n) {
+                          return n.ppat_loc;
+                        }), walkPattern, patterns$1, t, comments);
+          }
+          break;
+      case /* Ppat_construct */5 :
+          var constr = patterns._0;
+          var match$3 = constr.txt;
+          var exit$1 = 0;
+          switch (match$3.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$3._0) {
+                  case "::" :
+                      return walkList(undefined, (function (n) {
+                                    return n.ppat_loc;
+                                  }), walkPattern, collectListPatterns(/* [] */0, pat), t, comments);
+                  case "()" :
+                  case "[]" :
+                      break;
+                  default:
+                    exit$1 = 3;
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                exit$1 = 3;
+                break;
+            
+          }
+          if (exit$1 === 3) {
+            var pat$2 = patterns._1;
+            if (pat$2 !== undefined) {
+              var match$4 = partitionLeadingTrailing(comments, constr.loc);
+              attach(t.leading, constr.loc, match$4[0]);
+              var match$5 = partitionAdjacentTrailing(constr.loc, match$4[1]);
+              attach(t.trailing, constr.loc, match$5[0]);
+              var match$6 = partitionByLoc(match$5[1], pat$2.ppat_loc);
+              attach(t.leading, pat$2.ppat_loc, match$6[0]);
+              walkPattern(pat$2, t, match$6[1]);
+              return attach(t.trailing, pat$2.ppat_loc, match$6[2]);
+            }
+            var match$7 = partitionLeadingTrailing(comments, constr.loc);
+            attach(t.leading, constr.loc, match$7[0]);
+            return attach(t.trailing, constr.loc, match$7[1]);
+          }
+          break;
+      case /* Ppat_variant */6 :
+          var pat$3 = patterns._1;
+          if (pat$3 === undefined) {
+            return ;
+          }
+          _pat = pat$3;
+          continue ;
+      case /* Ppat_record */7 :
+          return walkList(undefined, (function (param) {
+                        var init = param[0].loc;
+                        return {
+                                loc_start: init.loc_start,
+                                loc_end: param[1].ppat_loc.loc_end,
+                                loc_ghost: init.loc_ghost
+                              };
+                      }), walkPatternRecordRow, patterns._0, t, comments);
+      case /* Ppat_array */8 :
+          var patterns$2 = patterns._0;
+          if (patterns$2) {
+            return walkList(undefined, (function (n) {
+                          return n.ppat_loc;
+                        }), walkPattern, patterns$2, t, comments);
+          }
+          break;
+      case /* Ppat_or */9 :
+          return walkList(undefined, (function (pattern) {
+                        return pattern.ppat_loc;
+                      }), (function (pattern) {
+                        return function (param, param$1) {
+                          return walkPattern(pattern, param, param$1);
+                        };
+                      }), Res_parsetree_viewer.collectOrPatternChain(pat), t, comments);
+      case /* Ppat_constraint */10 :
+          var typ = patterns._1;
+          var pattern = patterns._0;
+          var match$8 = partitionByLoc(comments, pattern.ppat_loc);
+          attach(t.leading, pattern.ppat_loc, match$8[0]);
+          walkPattern(pattern, t, match$8[1]);
+          var match$9 = partitionAdjacentTrailing(pattern.ppat_loc, match$8[2]);
+          attach(t.trailing, pattern.ppat_loc, match$9[0]);
+          var match$10 = partitionByLoc(match$9[1], typ.ptyp_loc);
+          attach(t.leading, typ.ptyp_loc, match$10[0]);
+          walkTypExpr(typ, t, match$10[1]);
+          return attach(t.trailing, typ.ptyp_loc, match$10[2]);
+      case /* Ppat_type */11 :
+          return ;
+      case /* Ppat_unpack */13 :
+          var stringLoc = patterns._0;
+          var match$11 = partitionLeadingTrailing(comments, stringLoc.loc);
+          attach(t.leading, stringLoc.loc, match$11[0]);
+          return attach(t.trailing, stringLoc.loc, match$11[1]);
+      case /* Ppat_lazy */12 :
+      case /* Ppat_exception */14 :
+          exit = 2;
+          break;
+      case /* Ppat_extension */15 :
+          return walkExtension(patterns._0, t, comments);
+      default:
+        return ;
+    }
+    if (exit === 2) {
+      var pattern$1 = patterns._0;
+      var match$12 = partitionByLoc(comments, pattern$1.ppat_loc);
+      attach(t.leading, pattern$1.ppat_loc, match$12[0]);
+      walkPattern(pattern$1, t, match$12[1]);
+      return attach(t.trailing, pattern$1.ppat_loc, match$12[2]);
+    }
+    return attach(t.inside, pat.ppat_loc, comments);
+  };
+}
+
+function walkPatternRecordRow(row, t, comments) {
+  var longident = row[0];
+  var ident = longident.txt;
+  switch (ident.TAG | 0) {
+    case /* Lident */0 :
+        var match = row[1].ppat_desc;
+        if (typeof match !== "number" && match.TAG === /* Ppat_var */0 && ident._0 === match._0.txt) {
+          var longidentLoc = longident.loc;
+          var match$1 = partitionLeadingTrailing(comments, longidentLoc);
+          attach(t.leading, longidentLoc, match$1[0]);
+          return attach(t.trailing, longidentLoc, match$1[1]);
+        }
+        break;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        break;
+    
+  }
+  var pattern = row[1];
+  var match$2 = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match$2[0]);
+  var match$3 = partitionAdjacentTrailing(longident.loc, match$2[1]);
+  attach(t.trailing, longident.loc, match$3[0]);
+  var match$4 = partitionByLoc(match$3[1], pattern.ppat_loc);
+  attach(t.leading, pattern.ppat_loc, match$4[0]);
+  walkPattern(pattern, t, match$4[1]);
+  return attach(t.trailing, pattern.ppat_loc, match$4[2]);
+}
+
+function walkTypExpr(typ, t, comments) {
+  var typexprs = typ.ptyp_desc;
+  if (comments === /* [] */0) {
+    return ;
+  }
+  if (typeof typexprs === "number") {
+    return ;
+  }
+  switch (typexprs.TAG | 0) {
+    case /* Ptyp_arrow */1 :
+        var match = arrowType(typ);
+        var typexpr = match[2];
+        var comments$1 = walkTypeParameters(match[1], t, comments);
+        var match$1 = partitionByLoc(comments$1, typexpr.ptyp_loc);
+        attach(t.leading, typexpr.ptyp_loc, match$1[0]);
+        walkTypExpr(typexpr, t, match$1[1]);
+        return attach(t.trailing, typexpr.ptyp_loc, match$1[2]);
+    case /* Ptyp_tuple */2 :
+        return walkList(undefined, (function (n) {
+                      return n.ptyp_loc;
+                    }), walkTypExpr, typexprs._0, t, comments);
+    case /* Ptyp_constr */3 :
+        var longident = typexprs._0;
+        var match$2 = partitionLeadingTrailing(comments, longident.loc);
+        var match$3 = partitionAdjacentTrailing(longident.loc, comments);
+        attach(t.leading, longident.loc, match$2[0]);
+        attach(t.trailing, longident.loc, match$3[0]);
+        return walkList(undefined, (function (n) {
+                      return n.ptyp_loc;
+                    }), walkTypExpr, typexprs._1, t, match$3[1]);
+    case /* Ptyp_object */4 :
+        return walkTypObjectFields(typexprs._0, t, comments);
+    case /* Ptyp_alias */6 :
+        var typexpr$1 = typexprs._0;
+        var match$4 = partitionByLoc(comments, typexpr$1.ptyp_loc);
+        attach(t.leading, typexpr$1.ptyp_loc, match$4[0]);
+        walkTypExpr(typexpr$1, t, match$4[1]);
+        return attach(t.trailing, typexpr$1.ptyp_loc, match$4[2]);
+    case /* Ptyp_poly */8 :
+        var typexpr$2 = typexprs._1;
+        var comments$2 = visitListButContinueWithRemainingComments(undefined, false, (function (n) {
+                return n.loc;
+              }), (function (longident, t, comments) {
+                var match = partitionLeadingTrailing(comments, longident.loc);
+                attach(t.leading, longident.loc, match[0]);
+                return attach(t.trailing, longident.loc, match[1]);
+              }), typexprs._0, t, comments);
+        var match$5 = partitionByLoc(comments$2, typexpr$2.ptyp_loc);
+        attach(t.leading, typexpr$2.ptyp_loc, match$5[0]);
+        walkTypExpr(typexpr$2, t, match$5[1]);
+        return attach(t.trailing, typexpr$2.ptyp_loc, match$5[2]);
+    case /* Ptyp_package */9 :
+        return walkPackageType(typexprs._0, t, comments);
+    case /* Ptyp_extension */10 :
+        return walkExtension(typexprs._0, t, comments);
+    default:
+      return ;
+  }
+}
+
+function walkTypObjectFields(fields, t, comments) {
+  return walkList(undefined, (function (field) {
+                if (field.TAG !== /* Otag */0) {
+                  return $$Location.none;
+                }
+                var init = field._0.loc;
+                return {
+                        loc_start: init.loc_start,
+                        loc_end: field._2.ptyp_loc.loc_end,
+                        loc_ghost: init.loc_ghost
+                      };
+              }), walkTypObjectField, fields, t, comments);
+}
+
+function walkTypObjectField(field, t, comments) {
+  if (field.TAG !== /* Otag */0) {
+    return ;
+  }
+  var typexpr = field._2;
+  var lbl = field._0;
+  var match = partitionLeadingTrailing(comments, lbl.loc);
+  attach(t.leading, lbl.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(lbl.loc, match[1]);
+  attach(t.trailing, lbl.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], typexpr.ptyp_loc);
+  attach(t.leading, typexpr.ptyp_loc, match$2[0]);
+  walkTypExpr(typexpr, t, match$2[1]);
+  return attach(t.trailing, typexpr.ptyp_loc, match$2[2]);
+}
+
+function walkTypeParameters(typeParameters, t, comments) {
+  return visitListButContinueWithRemainingComments(undefined, false, (function (param) {
+                var typexpr = param[2];
+                var match = typexpr.ptyp_attributes;
+                if (!match) {
+                  return typexpr.ptyp_loc;
+                }
+                var match$1 = match.hd[0];
+                if (match$1.txt !== "ns.namedArgLoc") {
+                  return typexpr.ptyp_loc;
+                }
+                var loc = match$1.loc;
+                return {
+                        loc_start: loc.loc_start,
+                        loc_end: typexpr.ptyp_loc.loc_end,
+                        loc_ghost: loc.loc_ghost
+                      };
+              }), walkTypeParameter, typeParameters, t, comments);
+}
+
+function walkTypeParameter(param, t, comments) {
+  var typexpr = param[2];
+  var match = partitionByLoc(comments, typexpr.ptyp_loc);
+  attach(t.leading, typexpr.ptyp_loc, match[0]);
+  walkTypExpr(typexpr, t, match[1]);
+  return attach(t.trailing, typexpr.ptyp_loc, match[2]);
+}
+
+function walkPackageType(packageType, t, comments) {
+  var longident = packageType[0];
+  var match = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(longident.loc, match[1]);
+  attach(t.trailing, longident.loc, match$1[0]);
+  return walkPackageConstraints(packageType[1], t, match$1[1]);
+}
+
+function walkPackageConstraints(packageConstraints, t, comments) {
+  return walkList(undefined, (function (param) {
+                var init = param[0].loc;
+                return {
+                        loc_start: init.loc_start,
+                        loc_end: param[1].ptyp_loc.loc_end,
+                        loc_ghost: init.loc_ghost
+                      };
+              }), walkPackageConstraint, packageConstraints, t, comments);
+}
+
+function walkPackageConstraint(packageConstraint, t, comments) {
+  var typexpr = packageConstraint[1];
+  var longident = packageConstraint[0];
+  var match = partitionLeadingTrailing(comments, longident.loc);
+  attach(t.leading, longident.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(longident.loc, match[1]);
+  attach(t.trailing, longident.loc, match$1[0]);
+  var match$2 = partitionByLoc(match$1[1], typexpr.ptyp_loc);
+  attach(t.leading, typexpr.ptyp_loc, match$2[0]);
+  walkTypExpr(typexpr, t, match$2[1]);
+  return attach(t.trailing, typexpr.ptyp_loc, match$2[2]);
+}
+
+function walkExtension(extension, t, comments) {
+  var id = extension[0];
+  var match = partitionLeadingTrailing(comments, id.loc);
+  attach(t.leading, id.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(id.loc, match[1]);
+  attach(t.trailing, id.loc, match$1[0]);
+  return walkPayload(extension[1], t, match$1[1]);
+}
+
+function walkAttribute(param, t, comments) {
+  var id = param[0];
+  var match = partitionLeadingTrailing(comments, id.loc);
+  attach(t.leading, id.loc, match[0]);
+  var match$1 = partitionAdjacentTrailing(id.loc, match[1]);
+  attach(t.trailing, id.loc, match$1[0]);
+  return walkPayload(param[1], t, match$1[1]);
+}
+
+function walkPayload(payload, t, comments) {
+  if (payload.TAG === /* PStr */0) {
+    return walkStructure(payload._0, t, comments);
+  }
+  
+}
+
+var $$Comment;
+
+var Doc;
+
+export {
+  $$Comment ,
+  Doc ,
+  make ,
+  copy ,
+  empty ,
+  log ,
+  attach ,
+  partitionByLoc ,
+  partitionLeadingTrailing ,
+  partitionByOnSameLine ,
+  partitionAdjacentTrailing ,
+  collectListPatterns ,
+  collectListExprs ,
+  arrowType ,
+  modExprApply ,
+  modExprFunctor ,
+  functorType ,
+  funExpr ,
+  isBlockExpr ,
+  isIfThenElseExpr ,
+  walkStructure ,
+  walkStructureItem ,
+  walkValueDescription ,
+  walkTypeExtension ,
+  walkIncludeDeclaration ,
+  walkModuleTypeDeclaration ,
+  walkModuleBinding ,
+  walkSignature ,
+  walkSignatureItem ,
+  walkIncludeDescription ,
+  walkModuleDeclaration ,
+  walkList ,
+  visitListButContinueWithRemainingComments ,
+  walkValueBindings ,
+  walkOpenDescription ,
+  walkTypeDeclarations ,
+  walkTypeParam ,
+  walkTypeDeclaration ,
+  walkLabelDeclarations ,
+  walkLabelDeclaration ,
+  walkConstructorDeclarations ,
+  walkConstructorDeclaration ,
+  walkConstructorArguments ,
+  walkValueBinding ,
+  walkExpr ,
+  walkExprPararameter ,
+  walkExprArgument ,
+  walkCase ,
+  walkExprRecordRow ,
+  walkExtConstr ,
+  walkExtensionConstructorKind ,
+  walkModExpr ,
+  walkModExprParameter ,
+  walkModType ,
+  walkModTypeParameter ,
+  walkPattern ,
+  walkPatternRecordRow ,
+  walkTypExpr ,
+  walkTypObjectFields ,
+  walkTypObjectField ,
+  walkTypeParameters ,
+  walkTypeParameter ,
+  walkPackageType ,
+  walkPackageConstraints ,
+  walkPackageConstraint ,
+  walkExtension ,
+  walkAttribute ,
+  walkPayload ,
+  
+}
+/* empty Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_comments_table.res b/analysis/examples/larger-project/src/res_comments_table.res
new file mode 100644
index 0000000000..f2616345b7
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_comments_table.res
@@ -0,0 +1,1848 @@
+module Comment = Res_comment
+module Doc = Res_doc
+
+type t = {
+  leading: Hashtbl.t<Location.t, list<Comment.t>>,
+  inside: Hashtbl.t<Location.t, list<Comment.t>>,
+  trailing: Hashtbl.t<Location.t, list<Comment.t>>,
+}
+
+let make = () => {
+  leading: Hashtbl.create(100),
+  inside: Hashtbl.create(100),
+  trailing: Hashtbl.create(100),
+}
+
+let copy = tbl => {
+  leading: Hashtbl.copy(tbl.leading),
+  inside: Hashtbl.copy(tbl.inside),
+  trailing: Hashtbl.copy(tbl.trailing),
+}
+
+let empty = make()
+
+@live
+let log = t => {
+  open Location
+  let leadingStuff = Hashtbl.fold((k: Location.t, v: list<Comment.t>, acc) => {
+    let loc = Doc.concat(list{
+      Doc.lbracket,
+      Doc.text(string_of_int(k.loc_start.pos_lnum)),
+      Doc.text(":"),
+      Doc.text(string_of_int(k.loc_start.pos_cnum - k.loc_start.pos_bol)),
+      Doc.text("-"),
+      Doc.text(string_of_int(k.loc_end.pos_lnum)),
+      Doc.text(":"),
+      Doc.text(string_of_int(k.loc_end.pos_cnum - k.loc_end.pos_bol)),
+      Doc.rbracket,
+    })
+    let doc = Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        loc,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.line,
+            Doc.join(~sep=Doc.comma, List.map(c => Doc.text(Comment.txt(c)), v)),
+          }),
+        ),
+        Doc.line,
+      }),
+    )
+    list{doc, ...acc}
+  }, t.leading, list{})
+
+  let trailingStuff = Hashtbl.fold((k: Location.t, v: list<Comment.t>, acc) => {
+    let loc = Doc.concat(list{
+      Doc.lbracket,
+      Doc.text(string_of_int(k.loc_start.pos_lnum)),
+      Doc.text(":"),
+      Doc.text(string_of_int(k.loc_start.pos_cnum - k.loc_start.pos_bol)),
+      Doc.text("-"),
+      Doc.text(string_of_int(k.loc_end.pos_lnum)),
+      Doc.text(":"),
+      Doc.text(string_of_int(k.loc_end.pos_cnum - k.loc_end.pos_bol)),
+      Doc.rbracket,
+    })
+    let doc = Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        loc,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.line,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(c => Doc.text(Comment.txt(c)), v),
+            ),
+          }),
+        ),
+        Doc.line,
+      }),
+    )
+    list{doc, ...acc}
+  }, t.trailing, list{})
+
+  Doc.breakableGroup(
+    ~forceBreak=true,
+    Doc.concat(list{
+      Doc.text("leading comments:"),
+      Doc.line,
+      Doc.indent(Doc.concat(leadingStuff)),
+      Doc.line,
+      Doc.line,
+      Doc.text("trailing comments:"),
+      Doc.indent(Doc.concat(trailingStuff)),
+      Doc.line,
+      Doc.line,
+    }),
+  )
+  |> Doc.toString(~width=80)
+  |> print_endline
+}
+
+let attach = (tbl, loc, comments) =>
+  switch comments {
+  | list{} => ()
+  | comments => Hashtbl.replace(tbl, loc, comments)
+  }
+
+let partitionByLoc = (comments, loc) => {
+  let rec loop = ((leading, inside, trailing), comments) => {
+    open Location
+    switch comments {
+    | list{comment, ...rest} =>
+      let cmtLoc = Comment.loc(comment)
+      if cmtLoc.loc_end.pos_cnum <= loc.loc_start.pos_cnum {
+        loop((list{comment, ...leading}, inside, trailing), rest)
+      } else if cmtLoc.loc_start.pos_cnum >= loc.loc_end.pos_cnum {
+        loop((leading, inside, list{comment, ...trailing}), rest)
+      } else {
+        loop((leading, list{comment, ...inside}, trailing), rest)
+      }
+    | list{} => (List.rev(leading), List.rev(inside), List.rev(trailing))
+    }
+  }
+
+  loop((list{}, list{}, list{}), comments)
+}
+
+let partitionLeadingTrailing = (comments, loc) => {
+  let rec loop = ((leading, trailing), comments) => {
+    open Location
+    switch comments {
+    | list{comment, ...rest} =>
+      let cmtLoc = Comment.loc(comment)
+      if cmtLoc.loc_end.pos_cnum <= loc.loc_start.pos_cnum {
+        loop((list{comment, ...leading}, trailing), rest)
+      } else {
+        loop((leading, list{comment, ...trailing}), rest)
+      }
+    | list{} => (List.rev(leading), List.rev(trailing))
+    }
+  }
+
+  loop((list{}, list{}), comments)
+}
+
+let partitionByOnSameLine = (loc, comments) => {
+  let rec loop = ((onSameLine, onOtherLine), comments) => {
+    open Location
+    switch comments {
+    | list{} => (List.rev(onSameLine), List.rev(onOtherLine))
+    | list{comment, ...rest} =>
+      let cmtLoc = Comment.loc(comment)
+      if cmtLoc.loc_start.pos_lnum === loc.loc_end.pos_lnum {
+        loop((list{comment, ...onSameLine}, onOtherLine), rest)
+      } else {
+        loop((onSameLine, list{comment, ...onOtherLine}), rest)
+      }
+    }
+  }
+
+  loop((list{}, list{}), comments)
+}
+
+let partitionAdjacentTrailing = (loc1, comments) => {
+  open Location
+  open Lexing
+  let rec loop = (~prevEndPos, afterLoc1, comments) =>
+    switch comments {
+    | list{} => (List.rev(afterLoc1), list{})
+    | list{comment, ...rest} as comments =>
+      let cmtPrevEndPos = Comment.prevTokEndPos(comment)
+      if prevEndPos.Lexing.pos_cnum === cmtPrevEndPos.pos_cnum {
+        let commentEnd = Comment.loc(comment).loc_end
+        loop(~prevEndPos=commentEnd, list{comment, ...afterLoc1}, rest)
+      } else {
+        (List.rev(afterLoc1), comments)
+      }
+    }
+
+  loop(~prevEndPos=loc1.loc_end, list{}, comments)
+}
+
+let rec collectListPatterns = (acc, pattern) => {
+  open Parsetree
+  switch pattern.ppat_desc {
+  | Ppat_construct({txt: Longident.Lident("::")}, Some({ppat_desc: Ppat_tuple(list{pat, rest})})) =>
+    collectListPatterns(list{pat, ...acc}, rest)
+  | Ppat_construct({txt: Longident.Lident("[]")}, None) => List.rev(acc)
+  | _ => List.rev(list{pattern, ...acc})
+  }
+}
+
+let rec collectListExprs = (acc, expr) => {
+  open Parsetree
+  switch expr.pexp_desc {
+  | Pexp_construct(
+      {txt: Longident.Lident("::")},
+      Some({pexp_desc: Pexp_tuple(list{expr, rest})}),
+    ) =>
+    collectListExprs(list{expr, ...acc}, rest)
+  | Pexp_construct({txt: Longident.Lident("[]")}, _) => List.rev(acc)
+  | _ => List.rev(list{expr, ...acc})
+  }
+}
+
+/* TODO: use ParsetreeViewer */
+let arrowType = ct => {
+  open Parsetree
+  let rec process = (attrsBefore, acc, typ) =>
+    switch typ {
+    | {ptyp_desc: Ptyp_arrow(Nolabel as lbl, typ1, typ2), ptyp_attributes: list{}} =>
+      let arg = (list{}, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | {
+        ptyp_desc: Ptyp_arrow(Nolabel as lbl, typ1, typ2),
+        ptyp_attributes: list{({txt: "bs"}, _)} as attrs,
+      } =>
+      let arg = (attrs, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | {ptyp_desc: Ptyp_arrow(Nolabel, _typ1, _typ2), ptyp_attributes: _attrs} as returnType =>
+      let args = List.rev(acc)
+      (attrsBefore, args, returnType)
+    | {
+        ptyp_desc: Ptyp_arrow((Labelled(_) | Optional(_)) as lbl, typ1, typ2),
+        ptyp_attributes: attrs,
+      } =>
+      let arg = (attrs, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | typ => (attrsBefore, List.rev(acc), typ)
+    }
+
+  switch ct {
+  | {ptyp_desc: Ptyp_arrow(Nolabel, _typ1, _typ2), ptyp_attributes: attrs} as typ =>
+    process(attrs, list{}, {...typ, ptyp_attributes: list{}})
+  | typ => process(list{}, list{}, typ)
+  }
+}
+
+/* TODO: avoiding the dependency on ParsetreeViewer here, is this a good idea? */
+let modExprApply = modExpr => {
+  let rec loop = (acc, modExpr) =>
+    switch modExpr {
+    | {Parsetree.pmod_desc: Pmod_apply(next, arg)} => loop(list{arg, ...acc}, next)
+    | _ => list{modExpr, ...acc}
+    }
+
+  loop(list{}, modExpr)
+}
+
+/* TODO: avoiding the dependency on ParsetreeViewer here, is this a good idea? */
+let modExprFunctor = modExpr => {
+  let rec loop = (acc, modExpr) =>
+    switch modExpr {
+    | {Parsetree.pmod_desc: Pmod_functor(lbl, modType, returnModExpr), pmod_attributes: attrs} =>
+      let param = (attrs, lbl, modType)
+      loop(list{param, ...acc}, returnModExpr)
+    | returnModExpr => (List.rev(acc), returnModExpr)
+    }
+
+  loop(list{}, modExpr)
+}
+
+let functorType = modtype => {
+  let rec process = (acc, modtype) =>
+    switch modtype {
+    | {Parsetree.pmty_desc: Pmty_functor(lbl, argType, returnType), pmty_attributes: attrs} =>
+      let arg = (attrs, lbl, argType)
+      process(list{arg, ...acc}, returnType)
+    | modType => (List.rev(acc), modType)
+    }
+
+  process(list{}, modtype)
+}
+
+let funExpr = expr => {
+  open Parsetree
+  /* Turns (type t, type u, type z) into "type t u z" */
+  let rec collectNewTypes = (acc, returnExpr) =>
+    switch returnExpr {
+    | {pexp_desc: Pexp_newtype(stringLoc, returnExpr), pexp_attributes: list{}} =>
+      collectNewTypes(list{stringLoc, ...acc}, returnExpr)
+    | returnExpr =>
+      let loc = switch (acc, List.rev(acc)) {
+      | (list{_startLoc, ..._}, list{endLoc, ..._}) => {...endLoc.loc, loc_end: endLoc.loc.loc_end}
+      | _ => Location.none
+      }
+
+      let txt = List.fold_right((curr, acc) => acc ++ (" " ++ curr.Location.txt), acc, "type")
+      (Location.mkloc(txt, loc), returnExpr)
+    }
+
+  /* For simplicity reason Pexp_newtype gets converted to a Nolabel parameter,
+   * otherwise this function would need to return a variant:
+   * | NormalParamater(...)
+   * | NewType(...)
+   * This complicates printing with an extra variant/boxing/allocation for a code-path
+   * that is not often used. Lets just keep it simple for now */
+  let rec collect = (attrsBefore, acc, expr) =>
+    switch expr {
+    | {pexp_desc: Pexp_fun(lbl, defaultExpr, pattern, returnExpr), pexp_attributes: list{}} =>
+      let parameter = (list{}, lbl, defaultExpr, pattern)
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | {pexp_desc: Pexp_newtype(stringLoc, rest), pexp_attributes: attrs} =>
+      let (var, returnExpr) = collectNewTypes(list{stringLoc}, rest)
+      let parameter = (attrs, Asttypes.Nolabel, None, Ast_helper.Pat.var(~loc=stringLoc.loc, var))
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | {
+        pexp_desc: Pexp_fun(lbl, defaultExpr, pattern, returnExpr),
+        pexp_attributes: list{({txt: "bs"}, _)} as attrs,
+      } =>
+      let parameter = (attrs, lbl, defaultExpr, pattern)
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | {
+        pexp_desc: Pexp_fun((Labelled(_) | Optional(_)) as lbl, defaultExpr, pattern, returnExpr),
+        pexp_attributes: attrs,
+      } =>
+      let parameter = (attrs, lbl, defaultExpr, pattern)
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | expr => (attrsBefore, List.rev(acc), expr)
+    }
+
+  switch expr {
+  | {
+      pexp_desc: Pexp_fun(Nolabel, _defaultExpr, _pattern, _returnExpr),
+      pexp_attributes: attrs,
+    } as expr =>
+    collect(attrs, list{}, {...expr, pexp_attributes: list{}})
+  | expr => collect(list{}, list{}, expr)
+  }
+}
+
+let rec isBlockExpr = expr => {
+  open Parsetree
+  switch expr.pexp_desc {
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_let(_)
+  | Pexp_open(_)
+  | Pexp_sequence(_) => true
+  | Pexp_apply(callExpr, _) if isBlockExpr(callExpr) => true
+  | Pexp_constraint(expr, _) if isBlockExpr(expr) => true
+  | Pexp_field(expr, _) if isBlockExpr(expr) => true
+  | Pexp_setfield(expr, _, _) if isBlockExpr(expr) => true
+  | _ => false
+  }
+}
+
+let isIfThenElseExpr = expr => {
+  open Parsetree
+  switch expr.pexp_desc {
+  | Pexp_ifthenelse(_) => true
+  | _ => false
+  }
+}
+
+let rec walkStructure = (s, t, comments) =>
+  switch s {
+  | _ if comments == list{} => ()
+  | list{} => attach(t.inside, Location.none, comments)
+  | s => walkList(~getLoc=n => n.Parsetree.pstr_loc, ~walkNode=walkStructureItem, s, t, comments)
+  }
+
+and walkStructureItem = (si, t, comments) =>
+  switch si.Parsetree.pstr_desc {
+  | _ if comments == list{} => ()
+  | Pstr_primitive(valueDescription) => walkValueDescription(valueDescription, t, comments)
+  | Pstr_open(openDescription) => walkOpenDescription(openDescription, t, comments)
+  | Pstr_value(_, valueBindings) => walkValueBindings(valueBindings, t, comments)
+  | Pstr_type(_, typeDeclarations) => walkTypeDeclarations(typeDeclarations, t, comments)
+  | Pstr_eval(expr, _) => walkExpr(expr, t, comments)
+  | Pstr_module(moduleBinding) => walkModuleBinding(moduleBinding, t, comments)
+  | Pstr_recmodule(moduleBindings) =>
+    walkList(
+      ~getLoc=mb => mb.Parsetree.pmb_loc,
+      ~walkNode=walkModuleBinding,
+      moduleBindings,
+      t,
+      comments,
+    )
+  | Pstr_modtype(modTypDecl) => walkModuleTypeDeclaration(modTypDecl, t, comments)
+  | Pstr_attribute(attribute) => walkAttribute(attribute, t, comments)
+  | Pstr_extension(extension, _) => walkExtension(extension, t, comments)
+  | Pstr_include(includeDeclaration) => walkIncludeDeclaration(includeDeclaration, t, comments)
+  | Pstr_exception(extensionConstructor) => walkExtConstr(extensionConstructor, t, comments)
+  | Pstr_typext(typeExtension) => walkTypeExtension(typeExtension, t, comments)
+  | Pstr_class_type(_) | Pstr_class(_) => ()
+  }
+
+and walkValueDescription = (vd, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, vd.pval_name.loc)
+  attach(t.leading, vd.pval_name.loc, leading)
+  let (afterName, rest) = partitionAdjacentTrailing(vd.pval_name.loc, trailing)
+  attach(t.trailing, vd.pval_name.loc, afterName)
+  let (before, inside, after) = partitionByLoc(rest, vd.pval_type.ptyp_loc)
+
+  attach(t.leading, vd.pval_type.ptyp_loc, before)
+  walkTypExpr(vd.pval_type, t, inside)
+  attach(t.trailing, vd.pval_type.ptyp_loc, after)
+}
+
+and walkTypeExtension = (te, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, te.ptyext_path.loc)
+  attach(t.leading, te.ptyext_path.loc, leading)
+  let (afterPath, rest) = partitionAdjacentTrailing(te.ptyext_path.loc, trailing)
+  attach(t.trailing, te.ptyext_path.loc, afterPath)
+
+  /* type params */
+  let rest = switch te.ptyext_params {
+  | list{} => rest
+  | typeParams =>
+    visitListButContinueWithRemainingComments(
+      ~getLoc=((typexpr, _variance)) => typexpr.Parsetree.ptyp_loc,
+      ~walkNode=walkTypeParam,
+      ~newlineDelimited=false,
+      typeParams,
+      t,
+      rest,
+    )
+  }
+
+  walkList(
+    ~getLoc=n => n.Parsetree.pext_loc,
+    ~walkNode=walkExtConstr,
+    te.ptyext_constructors,
+    t,
+    rest,
+  )
+}
+
+and walkIncludeDeclaration = (inclDecl, t, comments) => {
+  let (before, inside, after) = partitionByLoc(comments, inclDecl.pincl_mod.pmod_loc)
+  attach(t.leading, inclDecl.pincl_mod.pmod_loc, before)
+  walkModExpr(inclDecl.pincl_mod, t, inside)
+  attach(t.trailing, inclDecl.pincl_mod.pmod_loc, after)
+}
+
+and walkModuleTypeDeclaration = (mtd, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, mtd.pmtd_name.loc)
+  attach(t.leading, mtd.pmtd_name.loc, leading)
+  switch mtd.pmtd_type {
+  | None => attach(t.trailing, mtd.pmtd_name.loc, trailing)
+  | Some(modType) =>
+    let (afterName, rest) = partitionAdjacentTrailing(mtd.pmtd_name.loc, trailing)
+    attach(t.trailing, mtd.pmtd_name.loc, afterName)
+    let (before, inside, after) = partitionByLoc(rest, modType.pmty_loc)
+    attach(t.leading, modType.pmty_loc, before)
+    walkModType(modType, t, inside)
+    attach(t.trailing, modType.pmty_loc, after)
+  }
+}
+
+and walkModuleBinding = (mb, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, mb.pmb_name.loc)
+  attach(t.leading, mb.pmb_name.loc, leading)
+  let (afterName, rest) = partitionAdjacentTrailing(mb.pmb_name.loc, trailing)
+  attach(t.trailing, mb.pmb_name.loc, afterName)
+  let (leading, inside, trailing) = partitionByLoc(rest, mb.pmb_expr.pmod_loc)
+  switch mb.pmb_expr.pmod_desc {
+  | Pmod_constraint(_) => walkModExpr(mb.pmb_expr, t, List.concat(list{leading, inside}))
+  | _ =>
+    attach(t.leading, mb.pmb_expr.pmod_loc, leading)
+    walkModExpr(mb.pmb_expr, t, inside)
+  }
+  attach(t.trailing, mb.pmb_expr.pmod_loc, trailing)
+}
+
+and walkSignature = (signature, t, comments) =>
+  switch signature {
+  | _ if comments == list{} => ()
+  | list{} => attach(t.inside, Location.none, comments)
+  | _s =>
+    walkList(~getLoc=n => n.Parsetree.psig_loc, ~walkNode=walkSignatureItem, signature, t, comments)
+  }
+
+and walkSignatureItem = (si, t, comments) =>
+  switch si.psig_desc {
+  | _ if comments == list{} => ()
+  | Psig_value(valueDescription) => walkValueDescription(valueDescription, t, comments)
+  | Psig_type(_, typeDeclarations) => walkTypeDeclarations(typeDeclarations, t, comments)
+  | Psig_typext(typeExtension) => walkTypeExtension(typeExtension, t, comments)
+  | Psig_exception(extensionConstructor) => walkExtConstr(extensionConstructor, t, comments)
+  | Psig_module(moduleDeclaration) => walkModuleDeclaration(moduleDeclaration, t, comments)
+  | Psig_recmodule(moduleDeclarations) =>
+    walkList(
+      ~getLoc=n => n.Parsetree.pmd_loc,
+      ~walkNode=walkModuleDeclaration,
+      moduleDeclarations,
+      t,
+      comments,
+    )
+  | Psig_modtype(moduleTypeDeclaration) =>
+    walkModuleTypeDeclaration(moduleTypeDeclaration, t, comments)
+  | Psig_open(openDescription) => walkOpenDescription(openDescription, t, comments)
+  | Psig_include(includeDescription) => walkIncludeDescription(includeDescription, t, comments)
+  | Psig_attribute(attribute) => walkAttribute(attribute, t, comments)
+  | Psig_extension(extension, _) => walkExtension(extension, t, comments)
+  | Psig_class(_) | Psig_class_type(_) => ()
+  }
+
+and walkIncludeDescription = (id, t, comments) => {
+  let (before, inside, after) = partitionByLoc(comments, id.pincl_mod.pmty_loc)
+  attach(t.leading, id.pincl_mod.pmty_loc, before)
+  walkModType(id.pincl_mod, t, inside)
+  attach(t.trailing, id.pincl_mod.pmty_loc, after)
+}
+
+and walkModuleDeclaration = (md, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, md.pmd_name.loc)
+  attach(t.leading, md.pmd_name.loc, leading)
+  let (afterName, rest) = partitionAdjacentTrailing(md.pmd_name.loc, trailing)
+  attach(t.trailing, md.pmd_name.loc, afterName)
+  let (leading, inside, trailing) = partitionByLoc(rest, md.pmd_type.pmty_loc)
+  attach(t.leading, md.pmd_type.pmty_loc, leading)
+  walkModType(md.pmd_type, t, inside)
+  attach(t.trailing, md.pmd_type.pmty_loc, trailing)
+}
+
+and walkList: 'node. (
+  ~prevLoc: Location.t=?,
+  ~getLoc: 'node => Location.t,
+  ~walkNode: ('node, t, list<Comment.t>) => unit,
+  list<'node>,
+  t,
+  list<Comment.t>,
+) => unit = (~prevLoc=?, ~getLoc, ~walkNode, l, t, comments) => {
+  open Location
+  switch l {
+  | _ if comments == list{} => ()
+  | list{} =>
+    switch prevLoc {
+    | Some(loc) => attach(t.trailing, loc, comments)
+    | None => ()
+    }
+  | list{node, ...rest} =>
+    let currLoc = getLoc(node)
+    let (leading, inside, trailing) = partitionByLoc(comments, currLoc)
+    switch prevLoc {
+    | None =>
+      /* first node, all leading comments attach here */
+      attach(t.leading, currLoc, leading)
+    | Some(prevLoc) =>
+      /* Same line */
+      if prevLoc.loc_end.pos_lnum === currLoc.loc_start.pos_lnum {
+        let (afterPrev, beforeCurr) = partitionAdjacentTrailing(prevLoc, leading)
+        let () = attach(t.trailing, prevLoc, afterPrev)
+        attach(t.leading, currLoc, beforeCurr)
+      } else {
+        let (onSameLineAsPrev, afterPrev) = partitionByOnSameLine(prevLoc, leading)
+        let () = attach(t.trailing, prevLoc, onSameLineAsPrev)
+        let (leading, _inside, _trailing) = partitionByLoc(afterPrev, currLoc)
+        attach(t.leading, currLoc, leading)
+      }
+    }
+    walkNode(node, t, inside)
+    walkList(~prevLoc=currLoc, ~getLoc, ~walkNode, rest, t, trailing)
+  }
+}
+
+/* The parsetree doesn't always contain location info about the opening or
+ * closing token of a "list-of-things". This routine visits the whole list,
+ * but returns any remaining comments that likely fall after the whole list. */
+and visitListButContinueWithRemainingComments: 'node. (
+  ~prevLoc: Location.t=?,
+  ~newlineDelimited: bool,
+  ~getLoc: 'node => Location.t,
+  ~walkNode: ('node, t, list<Comment.t>) => unit,
+  list<'node>,
+  t,
+  list<Comment.t>,
+) => list<Comment.t> = (~prevLoc=?, ~newlineDelimited, ~getLoc, ~walkNode, l, t, comments) => {
+  open Location
+  switch l {
+  | _ if comments == list{} => list{}
+  | list{} =>
+    switch prevLoc {
+    | Some(loc) =>
+      let (afterPrev, rest) = if newlineDelimited {
+        partitionByOnSameLine(loc, comments)
+      } else {
+        partitionAdjacentTrailing(loc, comments)
+      }
+
+      attach(t.trailing, loc, afterPrev)
+      rest
+    | None => comments
+    }
+  | list{node, ...rest} =>
+    let currLoc = getLoc(node)
+    let (leading, inside, trailing) = partitionByLoc(comments, currLoc)
+    let () = switch prevLoc {
+    | None =>
+      /* first node, all leading comments attach here */
+      attach(t.leading, currLoc, leading)
+      ()
+    | Some(prevLoc) =>
+      /* Same line */
+      if prevLoc.loc_end.pos_lnum === currLoc.loc_start.pos_lnum {
+        let (afterPrev, beforeCurr) = partitionAdjacentTrailing(prevLoc, leading)
+        let () = attach(t.trailing, prevLoc, afterPrev)
+        let () = attach(t.leading, currLoc, beforeCurr)
+      } else {
+        let (onSameLineAsPrev, afterPrev) = partitionByOnSameLine(prevLoc, leading)
+        let () = attach(t.trailing, prevLoc, onSameLineAsPrev)
+        let (leading, _inside, _trailing) = partitionByLoc(afterPrev, currLoc)
+        let () = attach(t.leading, currLoc, leading)
+      }
+    }
+
+    walkNode(node, t, inside)
+    visitListButContinueWithRemainingComments(
+      ~prevLoc=currLoc,
+      ~getLoc,
+      ~walkNode,
+      ~newlineDelimited,
+      rest,
+      t,
+      trailing,
+    )
+  }
+}
+
+and walkValueBindings = (vbs, t, comments) =>
+  walkList(~getLoc=n => n.Parsetree.pvb_loc, ~walkNode=walkValueBinding, vbs, t, comments)
+
+and walkOpenDescription = (openDescription, t, comments) => {
+  let loc = openDescription.popen_lid.loc
+  let (leading, trailing) = partitionLeadingTrailing(comments, loc)
+  attach(t.leading, loc, leading)
+  attach(t.trailing, loc, trailing)
+}
+
+and walkTypeDeclarations = (typeDeclarations, t, comments) =>
+  walkList(
+    ~getLoc=n => n.Parsetree.ptype_loc,
+    ~walkNode=walkTypeDeclaration,
+    typeDeclarations,
+    t,
+    comments,
+  )
+
+and walkTypeParam = ((typexpr, _variance), t, comments) => walkTypExpr(typexpr, t, comments)
+
+and walkTypeDeclaration = (td, t, comments) => {
+  let (beforeName, rest) = partitionLeadingTrailing(comments, td.ptype_name.loc)
+  attach(t.leading, td.ptype_name.loc, beforeName)
+
+  let (afterName, rest) = partitionAdjacentTrailing(td.ptype_name.loc, rest)
+  attach(t.trailing, td.ptype_name.loc, afterName)
+
+  /* type params */
+  let rest = switch td.ptype_params {
+  | list{} => rest
+  | typeParams =>
+    visitListButContinueWithRemainingComments(
+      ~getLoc=((typexpr, _variance)) => typexpr.Parsetree.ptyp_loc,
+      ~walkNode=walkTypeParam,
+      ~newlineDelimited=false,
+      typeParams,
+      t,
+      rest,
+    )
+  }
+
+  /* manifest:  = typexpr */
+  let rest = switch td.ptype_manifest {
+  | Some(typexpr) =>
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    let (afterTyp, rest) = partitionAdjacentTrailing(typexpr.Parsetree.ptyp_loc, afterTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+    rest
+  | None => rest
+  }
+
+  let rest = switch td.ptype_kind {
+  | Ptype_abstract | Ptype_open => rest
+  | Ptype_record(labelDeclarations) =>
+    let () = walkList(
+      ~getLoc=ld => ld.Parsetree.pld_loc,
+      ~walkNode=walkLabelDeclaration,
+      labelDeclarations,
+      t,
+      rest,
+    )
+
+    list{}
+  | Ptype_variant(constructorDeclarations) =>
+    walkConstructorDeclarations(constructorDeclarations, t, rest)
+  }
+
+  attach(t.trailing, td.ptype_loc, rest)
+}
+
+and walkLabelDeclarations = (lds, t, comments) =>
+  visitListButContinueWithRemainingComments(
+    ~getLoc=ld => ld.Parsetree.pld_loc,
+    ~walkNode=walkLabelDeclaration,
+    ~newlineDelimited=false,
+    lds,
+    t,
+    comments,
+  )
+
+and walkLabelDeclaration = (ld, t, comments) => {
+  let (beforeName, rest) = partitionLeadingTrailing(comments, ld.pld_name.loc)
+  attach(t.leading, ld.pld_name.loc, beforeName)
+  let (afterName, rest) = partitionAdjacentTrailing(ld.pld_name.loc, rest)
+  attach(t.trailing, ld.pld_name.loc, afterName)
+  let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, ld.pld_type.ptyp_loc)
+  attach(t.leading, ld.pld_type.ptyp_loc, beforeTyp)
+  walkTypExpr(ld.pld_type, t, insideTyp)
+  attach(t.trailing, ld.pld_type.ptyp_loc, afterTyp)
+}
+
+and walkConstructorDeclarations = (cds, t, comments) =>
+  visitListButContinueWithRemainingComments(
+    ~getLoc=cd => cd.Parsetree.pcd_loc,
+    ~walkNode=walkConstructorDeclaration,
+    ~newlineDelimited=false,
+    cds,
+    t,
+    comments,
+  )
+
+and walkConstructorDeclaration = (cd, t, comments) => {
+  let (beforeName, rest) = partitionLeadingTrailing(comments, cd.pcd_name.loc)
+  attach(t.leading, cd.pcd_name.loc, beforeName)
+  let (afterName, rest) = partitionAdjacentTrailing(cd.pcd_name.loc, rest)
+  attach(t.trailing, cd.pcd_name.loc, afterName)
+  let rest = walkConstructorArguments(cd.pcd_args, t, rest)
+
+  let rest = switch cd.pcd_res {
+  | Some(typexpr) =>
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    let (afterTyp, rest) = partitionAdjacentTrailing(typexpr.Parsetree.ptyp_loc, afterTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+    rest
+  | None => rest
+  }
+
+  attach(t.trailing, cd.pcd_loc, rest)
+}
+
+and walkConstructorArguments = (args, t, comments) =>
+  switch args {
+  | Pcstr_tuple(typexprs) =>
+    visitListButContinueWithRemainingComments(
+      ~getLoc=n => n.Parsetree.ptyp_loc,
+      ~walkNode=walkTypExpr,
+      ~newlineDelimited=false,
+      typexprs,
+      t,
+      comments,
+    )
+  | Pcstr_record(labelDeclarations) => walkLabelDeclarations(labelDeclarations, t, comments)
+  }
+
+and walkValueBinding = (vb, t, comments) => {
+  open Location
+
+  let vb = {
+    open Parsetree
+    switch (vb.pvb_pat, vb.pvb_expr) {
+    | (
+        {ppat_desc: Ppat_constraint(pat, {ptyp_desc: Ptyp_poly(list{}, t)})},
+        {pexp_desc: Pexp_constraint(expr, _typ)},
+      ) => {
+        ...vb,
+        pvb_pat: Ast_helper.Pat.constraint_(
+          ~loc={...pat.ppat_loc, loc_end: t.Parsetree.ptyp_loc.loc_end},
+          pat,
+          t,
+        ),
+        pvb_expr: expr,
+      }
+    | (
+        {ppat_desc: Ppat_constraint(pat, {ptyp_desc: Ptyp_poly(list{_, ..._}, t)})},
+        {pexp_desc: Pexp_fun(_)},
+      ) => {
+        ...vb,
+        pvb_pat: {
+          ...vb.pvb_pat,
+          ppat_loc: {...pat.ppat_loc, loc_end: t.ptyp_loc.loc_end},
+        },
+      }
+
+    | (
+        {
+          ppat_desc: Ppat_constraint(pat, {ptyp_desc: Ptyp_poly(list{_, ..._}, t)} as typ),
+        } as constrainedPattern,
+        {pexp_desc: Pexp_newtype(_, {pexp_desc: Pexp_constraint(expr, _)})},
+      ) => /*
+       * The location of the Ptyp_poly on the pattern is the whole thing.
+       * let x:
+       *   type t. (int, int) => int =
+       *   (a, b) => {
+       *     // comment
+       *     a + b
+       *   }
+       */
+      {
+        ...vb,
+        pvb_pat: {
+          ...constrainedPattern,
+          ppat_desc: Ppat_constraint(pat, typ),
+          ppat_loc: {...constrainedPattern.ppat_loc, loc_end: t.ptyp_loc.loc_end},
+        },
+        pvb_expr: expr,
+      }
+    | _ => vb
+    }
+  }
+
+  let patternLoc = vb.Parsetree.pvb_pat.ppat_loc
+  let exprLoc = vb.Parsetree.pvb_expr.pexp_loc
+  let expr = vb.pvb_expr
+
+  let (leading, inside, trailing) = partitionByLoc(comments, patternLoc)
+
+  /* everything before start of pattern can only be leading on the pattern:
+   *   let |* before *| a = 1 */
+  attach(t.leading, patternLoc, leading)
+  walkPattern(vb.Parsetree.pvb_pat, t, inside)
+  let (afterPat, surroundingExpr) = partitionAdjacentTrailing(patternLoc, trailing)
+
+  attach(t.trailing, patternLoc, afterPat)
+  let (beforeExpr, insideExpr, afterExpr) = partitionByLoc(surroundingExpr, exprLoc)
+  if isBlockExpr(expr) {
+    walkExpr(expr, t, List.concat(list{beforeExpr, insideExpr, afterExpr}))
+  } else {
+    attach(t.leading, exprLoc, beforeExpr)
+    walkExpr(expr, t, insideExpr)
+    attach(t.trailing, exprLoc, afterExpr)
+  }
+}
+
+and walkExpr = (expr, t, comments) => {
+  open Location
+  switch expr.Parsetree.pexp_desc {
+  | _ if comments == list{} => ()
+  | Pexp_constant(_) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, leading)
+    attach(t.trailing, expr.pexp_loc, trailing)
+  | Pexp_ident(longident) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    attach(t.trailing, longident.loc, trailing)
+  | Pexp_let(
+      _recFlag,
+      valueBindings,
+      {pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, None)},
+    ) =>
+    walkValueBindings(valueBindings, t, comments)
+  | Pexp_let(_recFlag, valueBindings, expr2) =>
+    let comments = visitListButContinueWithRemainingComments(~getLoc=n =>
+      if n.Parsetree.pvb_pat.ppat_loc.loc_ghost {
+        n.pvb_expr.pexp_loc
+      } else {
+        n.Parsetree.pvb_loc
+      }
+    , ~walkNode=walkValueBinding, ~newlineDelimited=true, valueBindings, t, comments)
+
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, comments)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(comments, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_sequence(expr1, expr2) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr1.pexp_loc)
+    let comments = if isBlockExpr(expr1) {
+      let (afterExpr, comments) = partitionByOnSameLine(expr1.pexp_loc, trailing)
+      walkExpr(expr1, t, List.concat(list{leading, inside, afterExpr}))
+      comments
+    } else {
+      attach(t.leading, expr1.pexp_loc, leading)
+      walkExpr(expr1, t, inside)
+      let (afterExpr, comments) = partitionByOnSameLine(expr1.pexp_loc, trailing)
+      attach(t.trailing, expr1.pexp_loc, afterExpr)
+      comments
+    }
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, comments)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(comments, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_open(_override, longident, expr2) =>
+    let (leading, comments) = partitionLeadingTrailing(comments, expr.pexp_loc)
+    attach(t.leading, {...expr.pexp_loc, loc_end: longident.loc.loc_end}, leading)
+    let (leading, trailing) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    let (afterLongident, rest) = partitionByOnSameLine(longident.loc, trailing)
+    attach(t.trailing, longident.loc, afterLongident)
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_extension(
+      {txt: "bs.obj" | "obj"},
+      PStr(list{{pstr_desc: Pstr_eval({pexp_desc: Pexp_record(rows, _)}, list{})}}),
+    ) =>
+    walkList(~getLoc=((longident, expr): (Asttypes.loc<Longident.t>, Parsetree.expression)) => {
+      ...longident.loc,
+      loc_end: expr.pexp_loc.loc_end,
+    }, ~walkNode=walkExprRecordRow, rows, t, comments)
+  | Pexp_extension(extension) => walkExtension(extension, t, comments)
+  | Pexp_letexception(extensionConstructor, expr2) =>
+    let (leading, comments) = partitionLeadingTrailing(comments, expr.pexp_loc)
+    attach(t.leading, {...expr.pexp_loc, loc_end: extensionConstructor.pext_loc.loc_end}, leading)
+    let (leading, inside, trailing) = partitionByLoc(comments, extensionConstructor.pext_loc)
+    attach(t.leading, extensionConstructor.pext_loc, leading)
+    walkExtConstr(extensionConstructor, t, inside)
+    let (afterExtConstr, rest) = partitionByOnSameLine(extensionConstructor.pext_loc, trailing)
+    attach(t.trailing, extensionConstructor.pext_loc, afterExtConstr)
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_letmodule(stringLoc, modExpr, expr2) =>
+    let (leading, comments) = partitionLeadingTrailing(comments, expr.pexp_loc)
+    attach(t.leading, {...expr.pexp_loc, loc_end: modExpr.pmod_loc.loc_end}, leading)
+    let (leading, trailing) = partitionLeadingTrailing(comments, stringLoc.loc)
+    attach(t.leading, stringLoc.loc, leading)
+    let (afterString, rest) = partitionAdjacentTrailing(stringLoc.loc, trailing)
+    attach(t.trailing, stringLoc.loc, afterString)
+    let (beforeModExpr, insideModExpr, afterModExpr) = partitionByLoc(rest, modExpr.pmod_loc)
+    attach(t.leading, modExpr.pmod_loc, beforeModExpr)
+    walkModExpr(modExpr, t, insideModExpr)
+    let (afterModExpr, rest) = partitionByOnSameLine(modExpr.pmod_loc, afterModExpr)
+    attach(t.trailing, modExpr.pmod_loc, afterModExpr)
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_assert(expr)
+  | Pexp_lazy(expr) =>
+    if isBlockExpr(expr) {
+      walkExpr(expr, t, comments)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+      attach(t.leading, expr.pexp_loc, leading)
+      walkExpr(expr, t, inside)
+      attach(t.trailing, expr.pexp_loc, trailing)
+    }
+  | Pexp_coerce(expr, optTypexpr, typexpr) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, leading)
+    walkExpr(expr, t, inside)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, trailing)
+    attach(t.trailing, expr.pexp_loc, afterExpr)
+    let rest = switch optTypexpr {
+    | Some(typexpr) =>
+      let (leading, inside, trailing) = partitionByLoc(comments, typexpr.ptyp_loc)
+      attach(t.leading, typexpr.ptyp_loc, leading)
+      walkTypExpr(typexpr, t, inside)
+      let (afterTyp, rest) = partitionAdjacentTrailing(typexpr.ptyp_loc, trailing)
+      attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+      rest
+    | None => rest
+    }
+
+    let (leading, inside, trailing) = partitionByLoc(rest, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, leading)
+    walkTypExpr(typexpr, t, inside)
+    attach(t.trailing, typexpr.ptyp_loc, trailing)
+  | Pexp_constraint(expr, typexpr) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, leading)
+    walkExpr(expr, t, inside)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, trailing)
+    attach(t.trailing, expr.pexp_loc, afterExpr)
+    let (leading, inside, trailing) = partitionByLoc(rest, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, leading)
+    walkTypExpr(typexpr, t, inside)
+    attach(t.trailing, typexpr.ptyp_loc, trailing)
+  | Pexp_tuple(list{})
+  | Pexp_array(list{})
+  | Pexp_construct({txt: Longident.Lident("[]")}, _) =>
+    attach(t.inside, expr.pexp_loc, comments)
+  | Pexp_construct({txt: Longident.Lident("::")}, _) =>
+    walkList(
+      ~getLoc=n => n.Parsetree.pexp_loc,
+      ~walkNode=walkExpr,
+      collectListExprs(list{}, expr),
+      t,
+      comments,
+    )
+  | Pexp_construct(longident, args) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    switch args {
+    | Some(expr) =>
+      let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, trailing)
+      attach(t.trailing, longident.loc, afterLongident)
+      walkExpr(expr, t, rest)
+    | None => attach(t.trailing, longident.loc, trailing)
+    }
+  | Pexp_variant(_label, None) => ()
+  | Pexp_variant(_label, Some(expr)) => walkExpr(expr, t, comments)
+  | Pexp_array(exprs) | Pexp_tuple(exprs) =>
+    walkList(~getLoc=n => n.Parsetree.pexp_loc, ~walkNode=walkExpr, exprs, t, comments)
+  | Pexp_record(rows, spreadExpr) =>
+    let comments = switch spreadExpr {
+    | None => comments
+    | Some(expr) =>
+      let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+      attach(t.leading, expr.pexp_loc, leading)
+      walkExpr(expr, t, inside)
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, trailing)
+      attach(t.trailing, expr.pexp_loc, afterExpr)
+      rest
+    }
+
+    walkList(~getLoc=((longident, expr): (Asttypes.loc<Longident.t>, Parsetree.expression)) => {
+      ...longident.loc,
+      loc_end: expr.pexp_loc.loc_end,
+    }, ~walkNode=walkExprRecordRow, rows, t, comments)
+  | Pexp_field(expr, longident) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+    let trailing = if isBlockExpr(expr) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, trailing)
+      walkExpr(expr, t, List.concat(list{leading, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, expr.pexp_loc, leading)
+      walkExpr(expr, t, inside)
+      trailing
+    }
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, trailing)
+    attach(t.trailing, expr.pexp_loc, afterExpr)
+    let (leading, trailing) = partitionLeadingTrailing(rest, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    attach(t.trailing, longident.loc, trailing)
+  | Pexp_setfield(expr1, longident, expr2) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr1.pexp_loc)
+    let rest = if isBlockExpr(expr1) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, trailing)
+      walkExpr(expr1, t, List.concat(list{leading, inside, afterExpr}))
+      rest
+    } else {
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, trailing)
+      attach(t.leading, expr1.pexp_loc, leading)
+      walkExpr(expr1, t, inside)
+      attach(t.trailing, expr1.pexp_loc, afterExpr)
+      rest
+    }
+    let (beforeLongident, afterLongident) = partitionLeadingTrailing(rest, longident.loc)
+    attach(t.leading, longident.loc, beforeLongident)
+    let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, afterLongident)
+    attach(t.trailing, longident.loc, afterLongident)
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_ifthenelse(ifExpr, thenExpr, elseExpr) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, ifExpr.pexp_loc)
+    let comments = if isBlockExpr(ifExpr) {
+      let (afterExpr, comments) = partitionAdjacentTrailing(ifExpr.pexp_loc, trailing)
+      walkExpr(ifExpr, t, List.concat(list{leading, inside, afterExpr}))
+      comments
+    } else {
+      attach(t.leading, ifExpr.pexp_loc, leading)
+      walkExpr(ifExpr, t, inside)
+      let (afterExpr, comments) = partitionAdjacentTrailing(ifExpr.pexp_loc, trailing)
+      attach(t.trailing, ifExpr.pexp_loc, afterExpr)
+      comments
+    }
+    let (leading, inside, trailing) = partitionByLoc(comments, thenExpr.pexp_loc)
+    let comments = if isBlockExpr(thenExpr) {
+      let (afterExpr, trailing) = partitionAdjacentTrailing(thenExpr.pexp_loc, trailing)
+      walkExpr(thenExpr, t, List.concat(list{leading, inside, afterExpr}))
+      trailing
+    } else {
+      attach(t.leading, thenExpr.pexp_loc, leading)
+      walkExpr(thenExpr, t, inside)
+      let (afterExpr, comments) = partitionAdjacentTrailing(thenExpr.pexp_loc, trailing)
+      attach(t.trailing, thenExpr.pexp_loc, afterExpr)
+      comments
+    }
+    switch elseExpr {
+    | None => ()
+    | Some(expr) =>
+      if isBlockExpr(expr) || isIfThenElseExpr(expr) {
+        walkExpr(expr, t, comments)
+      } else {
+        let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+        attach(t.leading, expr.pexp_loc, leading)
+        walkExpr(expr, t, inside)
+        attach(t.trailing, expr.pexp_loc, trailing)
+      }
+    }
+  | Pexp_while(expr1, expr2) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, expr1.pexp_loc)
+    let rest = if isBlockExpr(expr1) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, trailing)
+      walkExpr(expr1, t, List.concat(list{leading, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, expr1.pexp_loc, leading)
+      walkExpr(expr1, t, inside)
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, trailing)
+      attach(t.trailing, expr1.pexp_loc, afterExpr)
+      rest
+    }
+    if isBlockExpr(expr2) {
+      walkExpr(expr2, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+      attach(t.leading, expr2.pexp_loc, leading)
+      walkExpr(expr2, t, inside)
+      attach(t.trailing, expr2.pexp_loc, trailing)
+    }
+  | Pexp_for(pat, expr1, expr2, _, expr3) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, pat.ppat_loc)
+    attach(t.leading, pat.ppat_loc, leading)
+    walkPattern(pat, t, inside)
+    let (afterPat, rest) = partitionAdjacentTrailing(pat.ppat_loc, trailing)
+    attach(t.trailing, pat.ppat_loc, afterPat)
+    let (leading, inside, trailing) = partitionByLoc(rest, expr1.pexp_loc)
+    attach(t.leading, expr1.pexp_loc, leading)
+    walkExpr(expr1, t, inside)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, trailing)
+    attach(t.trailing, expr1.pexp_loc, afterExpr)
+    let (leading, inside, trailing) = partitionByLoc(rest, expr2.pexp_loc)
+    attach(t.leading, expr2.pexp_loc, leading)
+    walkExpr(expr2, t, inside)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr2.pexp_loc, trailing)
+    attach(t.trailing, expr2.pexp_loc, afterExpr)
+    if isBlockExpr(expr3) {
+      walkExpr(expr3, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr3.pexp_loc)
+      attach(t.leading, expr3.pexp_loc, leading)
+      walkExpr(expr3, t, inside)
+      attach(t.trailing, expr3.pexp_loc, trailing)
+    }
+  | Pexp_pack(modExpr) =>
+    let (before, inside, after) = partitionByLoc(comments, modExpr.pmod_loc)
+    attach(t.leading, modExpr.pmod_loc, before)
+    walkModExpr(modExpr, t, inside)
+    attach(t.trailing, modExpr.pmod_loc, after)
+  | Pexp_match(expr1, list{case, elseBranch})
+    if Res_parsetree_viewer.hasIfLetAttribute(expr.pexp_attributes) =>
+    let (before, inside, after) = partitionByLoc(comments, case.pc_lhs.ppat_loc)
+    attach(t.leading, case.pc_lhs.ppat_loc, before)
+    walkPattern(case.pc_lhs, t, inside)
+    let (afterPat, rest) = partitionAdjacentTrailing(case.pc_lhs.ppat_loc, after)
+    attach(t.trailing, case.pc_lhs.ppat_loc, afterPat)
+    let (before, inside, after) = partitionByLoc(rest, expr1.pexp_loc)
+    attach(t.leading, expr1.pexp_loc, before)
+    walkExpr(expr1, t, inside)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr1.pexp_loc, after)
+    attach(t.trailing, expr1.pexp_loc, afterExpr)
+    let (before, inside, after) = partitionByLoc(rest, case.pc_rhs.pexp_loc)
+    let after = if isBlockExpr(case.pc_rhs) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(case.pc_rhs.pexp_loc, after)
+      walkExpr(case.pc_rhs, t, List.concat(list{before, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, case.pc_rhs.pexp_loc, before)
+      walkExpr(case.pc_rhs, t, inside)
+      after
+    }
+    let (afterExpr, rest) = partitionAdjacentTrailing(case.pc_rhs.pexp_loc, after)
+    attach(t.trailing, case.pc_rhs.pexp_loc, afterExpr)
+    let (before, inside, after) = partitionByLoc(rest, elseBranch.pc_rhs.pexp_loc)
+    let after = if isBlockExpr(elseBranch.pc_rhs) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(elseBranch.pc_rhs.pexp_loc, after)
+      walkExpr(elseBranch.pc_rhs, t, List.concat(list{before, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, elseBranch.pc_rhs.pexp_loc, before)
+      walkExpr(elseBranch.pc_rhs, t, inside)
+      after
+    }
+    attach(t.trailing, elseBranch.pc_rhs.pexp_loc, after)
+
+  | Pexp_match(expr, cases) | Pexp_try(expr, cases) =>
+    let (before, inside, after) = partitionByLoc(comments, expr.pexp_loc)
+    let after = if isBlockExpr(expr) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, after)
+      walkExpr(expr, t, List.concat(list{before, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, expr.pexp_loc, before)
+      walkExpr(expr, t, inside)
+      after
+    }
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, after)
+    attach(t.trailing, expr.pexp_loc, afterExpr)
+    walkList(~getLoc=n => {
+      ...n.Parsetree.pc_lhs.ppat_loc,
+      loc_end: n.pc_rhs.pexp_loc.loc_end,
+    }, ~walkNode=walkCase, cases, t, rest)
+  /* unary expression: todo use parsetreeviewer */
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident("~+" | "~+." | "~-" | "~-." | "not" | "!")})},
+      list{(Nolabel, argExpr)},
+    ) =>
+    let (before, inside, after) = partitionByLoc(comments, argExpr.pexp_loc)
+    attach(t.leading, argExpr.pexp_loc, before)
+    walkExpr(argExpr, t, inside)
+    attach(t.trailing, argExpr.pexp_loc, after)
+  /* binary expression */
+  | Pexp_apply(
+      {
+        pexp_desc: Pexp_ident({
+          txt: Longident.Lident(
+            ":="
+            | "||"
+            | "&&"
+            | "="
+            | "=="
+            | "<"
+            | ">"
+            | "!="
+            | "!=="
+            | "<="
+            | ">="
+            | "|>"
+            | "+"
+            | "+."
+            | "-"
+            | "-."
+            | "++"
+            | "^"
+            | "*"
+            | "*."
+            | "/"
+            | "/."
+            | "**"
+            | "|."
+            | "<>",
+          ),
+        }),
+      },
+      list{(Nolabel, operand1), (Nolabel, operand2)},
+    ) =>
+    let (before, inside, after) = partitionByLoc(comments, operand1.pexp_loc)
+    attach(t.leading, operand1.pexp_loc, before)
+    walkExpr(operand1, t, inside)
+    let (afterOperand1, rest) = partitionAdjacentTrailing(operand1.pexp_loc, after)
+    attach(t.trailing, operand1.pexp_loc, afterOperand1)
+    let (before, inside, after) = partitionByLoc(rest, operand2.pexp_loc)
+    attach(t.leading, operand2.pexp_loc, before)
+    walkExpr(operand2, t, inside) /* (List.concat [inside; after]); */
+    attach(t.trailing, operand2.pexp_loc, after)
+  | Pexp_apply(callExpr, arguments) =>
+    let (before, inside, after) = partitionByLoc(comments, callExpr.pexp_loc)
+    let after = if isBlockExpr(callExpr) {
+      let (afterExpr, rest) = partitionAdjacentTrailing(callExpr.pexp_loc, after)
+      walkExpr(callExpr, t, List.concat(list{before, inside, afterExpr}))
+      rest
+    } else {
+      attach(t.leading, callExpr.pexp_loc, before)
+      walkExpr(callExpr, t, inside)
+      after
+    }
+    let (afterExpr, rest) = partitionAdjacentTrailing(callExpr.pexp_loc, after)
+    attach(t.trailing, callExpr.pexp_loc, afterExpr)
+    walkList(~getLoc=((_argLabel, expr)) =>
+      switch expr.Parsetree.pexp_attributes {
+      | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._attrs} => {
+          ...loc,
+          loc_end: expr.pexp_loc.loc_end,
+        }
+      | _ => expr.pexp_loc
+      }
+    , ~walkNode=walkExprArgument, arguments, t, rest)
+  | Pexp_fun(_, _, _, _) | Pexp_newtype(_) =>
+    let (_, parameters, returnExpr) = funExpr(expr)
+    let comments = visitListButContinueWithRemainingComments(
+      ~newlineDelimited=false,
+      ~walkNode=walkExprPararameter,
+      ~getLoc=((_attrs, _argLbl, exprOpt, pattern)) => {
+        open Parsetree
+        let startPos = switch pattern.ppat_attributes {
+        | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._attrs} => loc.loc_start
+        | _ => pattern.ppat_loc.loc_start
+        }
+
+        switch exprOpt {
+        | None => {...pattern.ppat_loc, loc_start: startPos}
+        | Some(expr) => {
+            ...pattern.ppat_loc,
+            loc_start: startPos,
+            loc_end: expr.pexp_loc.loc_end,
+          }
+        }
+      },
+      parameters,
+      t,
+      comments,
+    )
+
+    switch returnExpr.pexp_desc {
+    | Pexp_constraint(expr, typ)
+      if expr.pexp_loc.loc_start.pos_cnum >= typ.ptyp_loc.loc_end.pos_cnum =>
+      let (leading, inside, trailing) = partitionByLoc(comments, typ.ptyp_loc)
+      attach(t.leading, typ.ptyp_loc, leading)
+      walkTypExpr(typ, t, inside)
+      let (afterTyp, comments) = partitionAdjacentTrailing(typ.ptyp_loc, trailing)
+      attach(t.trailing, typ.ptyp_loc, afterTyp)
+      if isBlockExpr(expr) {
+        walkExpr(expr, t, comments)
+      } else {
+        let (leading, inside, trailing) = partitionByLoc(comments, expr.pexp_loc)
+        attach(t.leading, expr.pexp_loc, leading)
+        walkExpr(expr, t, inside)
+        attach(t.trailing, expr.pexp_loc, trailing)
+      }
+    | _ =>
+      if isBlockExpr(returnExpr) {
+        walkExpr(returnExpr, t, comments)
+      } else {
+        let (leading, inside, trailing) = partitionByLoc(comments, returnExpr.pexp_loc)
+        attach(t.leading, returnExpr.pexp_loc, leading)
+        walkExpr(returnExpr, t, inside)
+        attach(t.trailing, returnExpr.pexp_loc, trailing)
+      }
+    }
+  | _ => ()
+  }
+}
+
+and walkExprPararameter = ((_attrs, _argLbl, exprOpt, pattern), t, comments) => {
+  let (leading, inside, trailing) = partitionByLoc(comments, pattern.ppat_loc)
+  attach(t.leading, pattern.ppat_loc, leading)
+  walkPattern(pattern, t, inside)
+  switch exprOpt {
+  | Some(expr) =>
+    let (_afterPat, rest) = partitionAdjacentTrailing(pattern.ppat_loc, trailing)
+    attach(t.trailing, pattern.ppat_loc, trailing)
+    if isBlockExpr(expr) {
+      walkExpr(expr, t, rest)
+    } else {
+      let (leading, inside, trailing) = partitionByLoc(rest, expr.pexp_loc)
+      attach(t.leading, expr.pexp_loc, leading)
+      walkExpr(expr, t, inside)
+      attach(t.trailing, expr.pexp_loc, trailing)
+    }
+  | None => attach(t.trailing, pattern.ppat_loc, trailing)
+  }
+}
+
+and walkExprArgument = ((_argLabel, expr), t, comments) =>
+  switch expr.Parsetree.pexp_attributes {
+  | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._attrs} =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, loc)
+    attach(t.leading, loc, leading)
+    let (afterLabel, rest) = partitionAdjacentTrailing(loc, trailing)
+    attach(t.trailing, loc, afterLabel)
+    let (before, inside, after) = partitionByLoc(rest, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, before)
+    walkExpr(expr, t, inside)
+    attach(t.trailing, expr.pexp_loc, after)
+  | _ =>
+    let (before, inside, after) = partitionByLoc(comments, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, before)
+    walkExpr(expr, t, inside)
+    attach(t.trailing, expr.pexp_loc, after)
+  }
+
+and walkCase = (case, t, comments) => {
+  let (before, inside, after) = partitionByLoc(comments, case.pc_lhs.ppat_loc)
+  /* cases don't have a location on their own, leading comments should go
+   * after the bar on the pattern */
+  walkPattern(case.pc_lhs, t, List.concat(list{before, inside}))
+  let (afterPat, rest) = partitionAdjacentTrailing(case.pc_lhs.ppat_loc, after)
+  attach(t.trailing, case.pc_lhs.ppat_loc, afterPat)
+  let comments = switch case.pc_guard {
+  | Some(expr) =>
+    let (before, inside, after) = partitionByLoc(rest, expr.pexp_loc)
+    let (afterExpr, rest) = partitionAdjacentTrailing(expr.pexp_loc, after)
+    if isBlockExpr(expr) {
+      walkExpr(expr, t, List.concat(list{before, inside, afterExpr}))
+    } else {
+      attach(t.leading, expr.pexp_loc, before)
+      walkExpr(expr, t, inside)
+      attach(t.trailing, expr.pexp_loc, afterExpr)
+    }
+    rest
+  | None => rest
+  }
+
+  if isBlockExpr(case.pc_rhs) {
+    walkExpr(case.pc_rhs, t, comments)
+  } else {
+    let (before, inside, after) = partitionByLoc(comments, case.pc_rhs.pexp_loc)
+    attach(t.leading, case.pc_rhs.pexp_loc, before)
+    walkExpr(case.pc_rhs, t, inside)
+    attach(t.trailing, case.pc_rhs.pexp_loc, after)
+  }
+}
+
+and walkExprRecordRow = ((longident, expr), t, comments) => {
+  let (beforeLongident, afterLongident) = partitionLeadingTrailing(comments, longident.loc)
+
+  attach(t.leading, longident.loc, beforeLongident)
+  let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, afterLongident)
+  attach(t.trailing, longident.loc, afterLongident)
+  let (leading, inside, trailing) = partitionByLoc(rest, expr.pexp_loc)
+  attach(t.leading, expr.pexp_loc, leading)
+  walkExpr(expr, t, inside)
+  attach(t.trailing, expr.pexp_loc, trailing)
+}
+
+and walkExtConstr = (extConstr, t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, extConstr.pext_name.loc)
+  attach(t.leading, extConstr.pext_name.loc, leading)
+  let (afterName, rest) = partitionAdjacentTrailing(extConstr.pext_name.loc, trailing)
+  attach(t.trailing, extConstr.pext_name.loc, afterName)
+  walkExtensionConstructorKind(extConstr.pext_kind, t, rest)
+}
+
+and walkExtensionConstructorKind = (kind, t, comments) =>
+  switch kind {
+  | Pext_rebind(longident) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    attach(t.trailing, longident.loc, trailing)
+  | Pext_decl(constructorArguments, maybeTypExpr) =>
+    let rest = walkConstructorArguments(constructorArguments, t, comments)
+    switch maybeTypExpr {
+    | None => ()
+    | Some(typexpr) =>
+      let (before, inside, after) = partitionByLoc(rest, typexpr.ptyp_loc)
+      attach(t.leading, typexpr.ptyp_loc, before)
+      walkTypExpr(typexpr, t, inside)
+      attach(t.trailing, typexpr.ptyp_loc, after)
+    }
+  }
+
+and walkModExpr = (modExpr, t, comments) =>
+  switch modExpr.pmod_desc {
+  | Pmod_ident(longident) =>
+    let (before, after) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, before)
+    attach(t.trailing, longident.loc, after)
+  | Pmod_structure(list{}) => attach(t.inside, modExpr.pmod_loc, comments)
+  | Pmod_structure(structure) => walkStructure(structure, t, comments)
+  | Pmod_extension(extension) => walkExtension(extension, t, comments)
+  | Pmod_unpack(expr) =>
+    let (before, inside, after) = partitionByLoc(comments, expr.pexp_loc)
+    attach(t.leading, expr.pexp_loc, before)
+    walkExpr(expr, t, inside)
+    attach(t.trailing, expr.pexp_loc, after)
+  | Pmod_constraint(modexpr, modtype) =>
+    if modtype.pmty_loc.loc_start >= modexpr.pmod_loc.loc_end {
+      let (before, inside, after) = partitionByLoc(comments, modexpr.pmod_loc)
+      attach(t.leading, modexpr.pmod_loc, before)
+      walkModExpr(modexpr, t, inside)
+      let (after, rest) = partitionAdjacentTrailing(modexpr.pmod_loc, after)
+      attach(t.trailing, modexpr.pmod_loc, after)
+      let (before, inside, after) = partitionByLoc(rest, modtype.pmty_loc)
+      attach(t.leading, modtype.pmty_loc, before)
+      walkModType(modtype, t, inside)
+      attach(t.trailing, modtype.pmty_loc, after)
+    } else {
+      let (before, inside, after) = partitionByLoc(comments, modtype.pmty_loc)
+      attach(t.leading, modtype.pmty_loc, before)
+      walkModType(modtype, t, inside)
+      let (after, rest) = partitionAdjacentTrailing(modtype.pmty_loc, after)
+      attach(t.trailing, modtype.pmty_loc, after)
+      let (before, inside, after) = partitionByLoc(rest, modexpr.pmod_loc)
+      attach(t.leading, modexpr.pmod_loc, before)
+      walkModExpr(modexpr, t, inside)
+      attach(t.trailing, modexpr.pmod_loc, after)
+    }
+  | Pmod_apply(_callModExpr, _argModExpr) =>
+    let modExprs = modExprApply(modExpr)
+    walkList(~getLoc=n => n.Parsetree.pmod_loc, ~walkNode=walkModExpr, modExprs, t, comments)
+  | Pmod_functor(_) =>
+    let (parameters, returnModExpr) = modExprFunctor(modExpr)
+    let comments = visitListButContinueWithRemainingComments(~getLoc=((_, lbl, modTypeOption)) =>
+      switch modTypeOption {
+      | None => lbl.Asttypes.loc
+      | Some(modType) => {...lbl.loc, loc_end: modType.Parsetree.pmty_loc.loc_end}
+      }
+    , ~walkNode=walkModExprParameter, ~newlineDelimited=false, parameters, t, comments)
+
+    switch returnModExpr.pmod_desc {
+    | Pmod_constraint(modExpr, modType)
+      if modType.pmty_loc.loc_end.pos_cnum <= modExpr.pmod_loc.loc_start.pos_cnum =>
+      let (before, inside, after) = partitionByLoc(comments, modType.pmty_loc)
+      attach(t.leading, modType.pmty_loc, before)
+      walkModType(modType, t, inside)
+      let (after, rest) = partitionAdjacentTrailing(modType.pmty_loc, after)
+      attach(t.trailing, modType.pmty_loc, after)
+      let (before, inside, after) = partitionByLoc(rest, modExpr.pmod_loc)
+      attach(t.leading, modExpr.pmod_loc, before)
+      walkModExpr(modExpr, t, inside)
+      attach(t.trailing, modExpr.pmod_loc, after)
+    | _ =>
+      let (before, inside, after) = partitionByLoc(comments, returnModExpr.pmod_loc)
+      attach(t.leading, returnModExpr.pmod_loc, before)
+      walkModExpr(returnModExpr, t, inside)
+      attach(t.trailing, returnModExpr.pmod_loc, after)
+    }
+  }
+
+and walkModExprParameter = (parameter, t, comments) => {
+  let (_attrs, lbl, modTypeOption) = parameter
+  let (leading, trailing) = partitionLeadingTrailing(comments, lbl.loc)
+  attach(t.leading, lbl.loc, leading)
+  switch modTypeOption {
+  | None => attach(t.trailing, lbl.loc, trailing)
+  | Some(modType) =>
+    let (afterLbl, rest) = partitionAdjacentTrailing(lbl.loc, trailing)
+    attach(t.trailing, lbl.loc, afterLbl)
+    let (before, inside, after) = partitionByLoc(rest, modType.pmty_loc)
+    attach(t.leading, modType.pmty_loc, before)
+    walkModType(modType, t, inside)
+    attach(t.trailing, modType.pmty_loc, after)
+  }
+}
+
+and walkModType = (modType, t, comments) =>
+  switch modType.pmty_desc {
+  | Pmty_ident(longident) | Pmty_alias(longident) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, longident.loc)
+    attach(t.leading, longident.loc, leading)
+    attach(t.trailing, longident.loc, trailing)
+  | Pmty_signature(list{}) => attach(t.inside, modType.pmty_loc, comments)
+  | Pmty_signature(signature) => walkSignature(signature, t, comments)
+  | Pmty_extension(extension) => walkExtension(extension, t, comments)
+  | Pmty_typeof(modExpr) =>
+    let (before, inside, after) = partitionByLoc(comments, modExpr.pmod_loc)
+    attach(t.leading, modExpr.pmod_loc, before)
+    walkModExpr(modExpr, t, inside)
+    attach(t.trailing, modExpr.pmod_loc, after)
+  | Pmty_with(modType, _withConstraints) =>
+    let (before, inside, after) = partitionByLoc(comments, modType.pmty_loc)
+    attach(t.leading, modType.pmty_loc, before)
+    walkModType(modType, t, inside)
+    attach(t.trailing, modType.pmty_loc, after)
+  /* TODO: withConstraints */
+  | Pmty_functor(_) =>
+    let (parameters, returnModType) = functorType(modType)
+    let comments = visitListButContinueWithRemainingComments(~getLoc=((_, lbl, modTypeOption)) =>
+      switch modTypeOption {
+      | None => lbl.Asttypes.loc
+      | Some(modType) =>
+        if lbl.txt == "_" {
+          modType.Parsetree.pmty_loc
+        } else {
+          {...lbl.loc, loc_end: modType.Parsetree.pmty_loc.loc_end}
+        }
+      }
+    , ~walkNode=walkModTypeParameter, ~newlineDelimited=false, parameters, t, comments)
+
+    let (before, inside, after) = partitionByLoc(comments, returnModType.pmty_loc)
+    attach(t.leading, returnModType.pmty_loc, before)
+    walkModType(returnModType, t, inside)
+    attach(t.trailing, returnModType.pmty_loc, after)
+  }
+
+and walkModTypeParameter = ((_, lbl, modTypeOption), t, comments) => {
+  let (leading, trailing) = partitionLeadingTrailing(comments, lbl.loc)
+  attach(t.leading, lbl.loc, leading)
+  switch modTypeOption {
+  | None => attach(t.trailing, lbl.loc, trailing)
+  | Some(modType) =>
+    let (afterLbl, rest) = partitionAdjacentTrailing(lbl.loc, trailing)
+    attach(t.trailing, lbl.loc, afterLbl)
+    let (before, inside, after) = partitionByLoc(rest, modType.pmty_loc)
+    attach(t.leading, modType.pmty_loc, before)
+    walkModType(modType, t, inside)
+    attach(t.trailing, modType.pmty_loc, after)
+  }
+}
+
+and walkPattern = (pat, t, comments) => {
+  open Location
+  switch pat.Parsetree.ppat_desc {
+  | _ if comments == list{} => ()
+  | Ppat_alias(pat, alias) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, pat.ppat_loc)
+    attach(t.leading, pat.ppat_loc, leading)
+    walkPattern(pat, t, inside)
+    let (afterPat, rest) = partitionAdjacentTrailing(pat.ppat_loc, trailing)
+    attach(t.leading, pat.ppat_loc, leading)
+    attach(t.trailing, pat.ppat_loc, afterPat)
+    let (beforeAlias, afterAlias) = partitionLeadingTrailing(rest, alias.loc)
+    attach(t.leading, alias.loc, beforeAlias)
+    attach(t.trailing, alias.loc, afterAlias)
+  | Ppat_tuple(list{})
+  | Ppat_array(list{})
+  | Ppat_construct({txt: Longident.Lident("()")}, _)
+  | Ppat_construct({txt: Longident.Lident("[]")}, _) =>
+    attach(t.inside, pat.ppat_loc, comments)
+  | Ppat_array(patterns) =>
+    walkList(~getLoc=n => n.Parsetree.ppat_loc, ~walkNode=walkPattern, patterns, t, comments)
+  | Ppat_tuple(patterns) =>
+    walkList(~getLoc=n => n.Parsetree.ppat_loc, ~walkNode=walkPattern, patterns, t, comments)
+  | Ppat_construct({txt: Longident.Lident("::")}, _) =>
+    walkList(
+      ~getLoc=n => n.Parsetree.ppat_loc,
+      ~walkNode=walkPattern,
+      collectListPatterns(list{}, pat),
+      t,
+      comments,
+    )
+  | Ppat_construct(constr, None) =>
+    let (beforeConstr, afterConstr) = partitionLeadingTrailing(comments, constr.loc)
+
+    attach(t.leading, constr.loc, beforeConstr)
+    attach(t.trailing, constr.loc, afterConstr)
+  | Ppat_construct(constr, Some(pat)) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, constr.loc)
+    attach(t.leading, constr.loc, leading)
+    let (afterConstructor, rest) = partitionAdjacentTrailing(constr.loc, trailing)
+
+    attach(t.trailing, constr.loc, afterConstructor)
+    let (leading, inside, trailing) = partitionByLoc(rest, pat.ppat_loc)
+    attach(t.leading, pat.ppat_loc, leading)
+    walkPattern(pat, t, inside)
+    attach(t.trailing, pat.ppat_loc, trailing)
+  | Ppat_variant(_label, None) => ()
+  | Ppat_variant(_label, Some(pat)) => walkPattern(pat, t, comments)
+  | Ppat_type(_) => ()
+  | Ppat_record(recordRows, _) =>
+    walkList(~getLoc=((longidentLoc, pattern): (Asttypes.loc<Longident.t>, Parsetree.pattern)) => {
+      ...longidentLoc.loc,
+      loc_end: pattern.Parsetree.ppat_loc.loc_end,
+    }, ~walkNode=walkPatternRecordRow, recordRows, t, comments)
+  | Ppat_or(_) =>
+    walkList(
+      ~getLoc=pattern => pattern.Parsetree.ppat_loc,
+      ~walkNode=pattern => walkPattern(pattern),
+      Res_parsetree_viewer.collectOrPatternChain(pat),
+      t,
+      comments,
+    )
+  | Ppat_constraint(pattern, typ) =>
+    let (beforePattern, insidePattern, afterPattern) = partitionByLoc(comments, pattern.ppat_loc)
+
+    attach(t.leading, pattern.ppat_loc, beforePattern)
+    walkPattern(pattern, t, insidePattern)
+    let (afterPattern, rest) = partitionAdjacentTrailing(pattern.ppat_loc, afterPattern)
+
+    attach(t.trailing, pattern.ppat_loc, afterPattern)
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, typ.ptyp_loc)
+
+    attach(t.leading, typ.ptyp_loc, beforeTyp)
+    walkTypExpr(typ, t, insideTyp)
+    attach(t.trailing, typ.ptyp_loc, afterTyp)
+  | Ppat_lazy(pattern) | Ppat_exception(pattern) =>
+    let (leading, inside, trailing) = partitionByLoc(comments, pattern.ppat_loc)
+    attach(t.leading, pattern.ppat_loc, leading)
+    walkPattern(pattern, t, inside)
+    attach(t.trailing, pattern.ppat_loc, trailing)
+  | Ppat_unpack(stringLoc) =>
+    let (leading, trailing) = partitionLeadingTrailing(comments, stringLoc.loc)
+    attach(t.leading, stringLoc.loc, leading)
+    attach(t.trailing, stringLoc.loc, trailing)
+  | Ppat_extension(extension) => walkExtension(extension, t, comments)
+  | _ => ()
+  }
+}
+
+/* name: firstName */
+and walkPatternRecordRow = (row, t, comments) =>
+  switch row {
+  /* punned {x} */
+  | (
+      {Location.txt: Longident.Lident(ident), loc: longidentLoc},
+      {Parsetree.ppat_desc: Ppat_var({txt, _})},
+    ) if ident == txt =>
+    let (beforeLbl, afterLbl) = partitionLeadingTrailing(comments, longidentLoc)
+
+    attach(t.leading, longidentLoc, beforeLbl)
+    attach(t.trailing, longidentLoc, afterLbl)
+  | (longident, pattern) =>
+    let (beforeLbl, afterLbl) = partitionLeadingTrailing(comments, longident.loc)
+
+    attach(t.leading, longident.loc, beforeLbl)
+    let (afterLbl, rest) = partitionAdjacentTrailing(longident.loc, afterLbl)
+    attach(t.trailing, longident.loc, afterLbl)
+    let (leading, inside, trailing) = partitionByLoc(rest, pattern.ppat_loc)
+    attach(t.leading, pattern.ppat_loc, leading)
+    walkPattern(pattern, t, inside)
+    attach(t.trailing, pattern.ppat_loc, trailing)
+  }
+
+and walkTypExpr = (typ, t, comments) =>
+  switch typ.Parsetree.ptyp_desc {
+  | _ if comments == list{} => ()
+  | Ptyp_tuple(typexprs) =>
+    walkList(~getLoc=n => n.Parsetree.ptyp_loc, ~walkNode=walkTypExpr, typexprs, t, comments)
+  | Ptyp_extension(extension) => walkExtension(extension, t, comments)
+  | Ptyp_package(packageType) => walkPackageType(packageType, t, comments)
+  | Ptyp_alias(typexpr, _alias) =>
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(comments, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+  | Ptyp_poly(strings, typexpr) =>
+    let comments = visitListButContinueWithRemainingComments(
+      ~getLoc=n => n.Asttypes.loc,
+      ~walkNode=(longident, t, comments) => {
+        let (beforeLongident, afterLongident) = partitionLeadingTrailing(comments, longident.loc)
+        attach(t.leading, longident.loc, beforeLongident)
+        attach(t.trailing, longident.loc, afterLongident)
+      },
+      ~newlineDelimited=false,
+      strings,
+      t,
+      comments,
+    )
+
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(comments, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+  | Ptyp_constr(longident, typexprs) =>
+    let (beforeLongident, _afterLongident) = partitionLeadingTrailing(comments, longident.loc)
+    let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, comments)
+    attach(t.leading, longident.loc, beforeLongident)
+    attach(t.trailing, longident.loc, afterLongident)
+    walkList(~getLoc=n => n.Parsetree.ptyp_loc, ~walkNode=walkTypExpr, typexprs, t, rest)
+  | Ptyp_arrow(_) =>
+    let (_, parameters, typexpr) = arrowType(typ)
+    let comments = walkTypeParameters(parameters, t, comments)
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(comments, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+  | Ptyp_object(fields, _) => walkTypObjectFields(fields, t, comments)
+  | _ => ()
+  }
+
+and walkTypObjectFields = (fields, t, comments) => walkList(~getLoc=field =>
+    switch field {
+    | Parsetree.Otag(lbl, _, typ) => {...lbl.loc, loc_end: typ.ptyp_loc.loc_end}
+    | _ => Location.none
+    }
+  , ~walkNode=walkTypObjectField, fields, t, comments)
+
+and walkTypObjectField = (field, t, comments) =>
+  switch field {
+  | Otag(lbl, _, typexpr) =>
+    let (beforeLbl, afterLbl) = partitionLeadingTrailing(comments, lbl.loc)
+    attach(t.leading, lbl.loc, beforeLbl)
+    let (afterLbl, rest) = partitionAdjacentTrailing(lbl.loc, afterLbl)
+    attach(t.trailing, lbl.loc, afterLbl)
+    let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, typexpr.ptyp_loc)
+    attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+    walkTypExpr(typexpr, t, insideTyp)
+    attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+  | _ => ()
+  }
+
+and walkTypeParameters = (typeParameters, t, comments) =>
+  visitListButContinueWithRemainingComments(~getLoc=((_, _, typexpr)) =>
+    switch typexpr.Parsetree.ptyp_attributes {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._attrs} => {
+        ...loc,
+        loc_end: typexpr.ptyp_loc.loc_end,
+      }
+    | _ => typexpr.ptyp_loc
+    }
+  , ~walkNode=walkTypeParameter, ~newlineDelimited=false, typeParameters, t, comments)
+
+and walkTypeParameter = ((_attrs, _lbl, typexpr), t, comments) => {
+  let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(comments, typexpr.ptyp_loc)
+  attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+  walkTypExpr(typexpr, t, insideTyp)
+  attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+}
+
+and walkPackageType = (packageType, t, comments) => {
+  let (longident, packageConstraints) = packageType
+  let (beforeLongident, afterLongident) = partitionLeadingTrailing(comments, longident.loc)
+  attach(t.leading, longident.loc, beforeLongident)
+  let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, afterLongident)
+  attach(t.trailing, longident.loc, afterLongident)
+  walkPackageConstraints(packageConstraints, t, rest)
+}
+
+and walkPackageConstraints = (packageConstraints, t, comments) =>
+  walkList(~getLoc=((longident, typexpr)) => {
+    ...longident.Asttypes.loc,
+    loc_end: typexpr.Parsetree.ptyp_loc.loc_end,
+  }, ~walkNode=walkPackageConstraint, packageConstraints, t, comments)
+
+and walkPackageConstraint = (packageConstraint, t, comments) => {
+  let (longident, typexpr) = packageConstraint
+  let (beforeLongident, afterLongident) = partitionLeadingTrailing(comments, longident.loc)
+  attach(t.leading, longident.loc, beforeLongident)
+  let (afterLongident, rest) = partitionAdjacentTrailing(longident.loc, afterLongident)
+  attach(t.trailing, longident.loc, afterLongident)
+  let (beforeTyp, insideTyp, afterTyp) = partitionByLoc(rest, typexpr.ptyp_loc)
+  attach(t.leading, typexpr.ptyp_loc, beforeTyp)
+  walkTypExpr(typexpr, t, insideTyp)
+  attach(t.trailing, typexpr.ptyp_loc, afterTyp)
+}
+
+and walkExtension = (extension, t, comments) => {
+  let (id, payload) = extension
+  let (beforeId, afterId) = partitionLeadingTrailing(comments, id.loc)
+  attach(t.leading, id.loc, beforeId)
+  let (afterId, rest) = partitionAdjacentTrailing(id.loc, afterId)
+  attach(t.trailing, id.loc, afterId)
+  walkPayload(payload, t, rest)
+}
+
+and walkAttribute = ((id, payload), t, comments) => {
+  let (beforeId, afterId) = partitionLeadingTrailing(comments, id.loc)
+  attach(t.leading, id.loc, beforeId)
+  let (afterId, rest) = partitionAdjacentTrailing(id.loc, afterId)
+  attach(t.trailing, id.loc, afterId)
+  walkPayload(payload, t, rest)
+}
+
+and walkPayload = (payload, t, comments) =>
+  switch payload {
+  | PStr(s) => walkStructure(s, t, comments)
+  | _ => ()
+  }
+
diff --git a/analysis/examples/larger-project/src/res_core.js b/analysis/examples/larger-project/src/res_core.js
new file mode 100644
index 0000000000..6a15e442d4
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_core.js
@@ -0,0 +1,11194 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Buffer from "rescript/lib/es6/buffer.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Res_doc from "./res_doc.js";
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as $$Location from "./location.js";
+import * as Res_utf8 from "./res_utf8.js";
+import * as Longident from "./longident.js";
+import * as Res_token from "./res_token.js";
+import * as Ast_helper from "./ast_helper.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Res_js_ffi from "./res_js_ffi.js";
+import * as Res_parser from "./res_parser.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Res_grammar from "./res_grammar.js";
+import * as Res_printer from "./res_printer.js";
+import * as Res_scanner from "./res_scanner.js";
+import * as Res_diagnostics from "./res_diagnostics.js";
+import * as Res_comments_table from "./res_comments_table.js";
+
+function mkLoc(startLoc, endLoc) {
+  return {
+          loc_start: startLoc,
+          loc_end: endLoc,
+          loc_ghost: false
+        };
+}
+
+function defaultExpr(param) {
+  var id = $$Location.mknoloc("rescript.exprhole");
+  return Ast_helper.Exp.mk(undefined, undefined, {
+              TAG: /* Pexp_extension */34,
+              _0: [
+                id,
+                {
+                  TAG: /* PStr */0,
+                  _0: /* [] */0
+                }
+              ]
+            });
+}
+
+function defaultType(param) {
+  var id = $$Location.mknoloc("rescript.typehole");
+  return Ast_helper.Typ.extension(undefined, undefined, [
+              id,
+              {
+                TAG: /* PStr */0,
+                _0: /* [] */0
+              }
+            ]);
+}
+
+function defaultPattern(param) {
+  var id = $$Location.mknoloc("rescript.patternhole");
+  return Ast_helper.Pat.extension(undefined, undefined, [
+              id,
+              {
+                TAG: /* PStr */0,
+                _0: /* [] */0
+              }
+            ]);
+}
+
+function defaultModuleExpr(param) {
+  return Ast_helper.Mod.structure(undefined, undefined, /* [] */0);
+}
+
+function defaultModuleType(param) {
+  return Ast_helper.Mty.signature(undefined, undefined, /* [] */0);
+}
+
+var id = $$Location.mknoloc("rescript.sigitemhole");
+
+var defaultSignatureItem = Ast_helper.Sig.extension(undefined, undefined, [
+      id,
+      {
+        TAG: /* PStr */0,
+        _0: /* [] */0
+      }
+    ]);
+
+function recoverEqualGreater(p) {
+  Res_parser.expect(undefined, /* EqualGreater */57, p);
+  var match = p.token;
+  if (match === 58) {
+    return Res_parser.next(undefined, p);
+  }
+  
+}
+
+function shouldAbortListParse(p) {
+  var _breadcrumbs = p.breadcrumbs;
+  while(true) {
+    var breadcrumbs = _breadcrumbs;
+    if (!breadcrumbs) {
+      return false;
+    }
+    if (Res_grammar.isPartOfList(breadcrumbs.hd[0], p.token)) {
+      return true;
+    }
+    _breadcrumbs = breadcrumbs.tl;
+    continue ;
+  };
+}
+
+var Recover = {
+  defaultExpr: defaultExpr,
+  defaultType: defaultType,
+  defaultPattern: defaultPattern,
+  defaultModuleExpr: defaultModuleExpr,
+  defaultModuleType: defaultModuleType,
+  defaultSignatureItem: defaultSignatureItem,
+  recoverEqualGreater: recoverEqualGreater,
+  shouldAbortListParse: shouldAbortListParse
+};
+
+var listPatternSpread = "List pattern matches only supports one `...` spread, at the end.\nExplanation: a list spread at the tail is efficient, but a spread in the middle would create new list[s]; out of performance concern, our pattern matching currently guarantees to never create new intermediate data.";
+
+var recordPatternSpread = "Record's `...` spread is not supported in pattern matches.\nExplanation: you can't collect a subset of a record's field into its own record, since a record needs an explicit declaration and that subset wouldn't have one.\nSolution: you need to pull out each field you want explicitly.";
+
+var arrayPatternSpread = "Array's `...` spread is not supported in pattern matches.\nExplanation: such spread would create a subarray; out of performance concern, our pattern matching currently guarantees to never create new intermediate data.\nSolution: if it's to validate the first few elements, use a `when` clause + Array size check + `get` checks on the current pattern. If it's to obtain a subarray, use `Array.sub` or `Belt.Array.slice`.";
+
+var arrayExprSpread = "Arrays can't use the `...` spread currently. Please use `concat` or other Array helpers.";
+
+var recordExprSpread = "Records can only have one `...` spread, at the beginning.\nExplanation: since records have a known, fixed shape, a spread like `{a, ...b}` wouldn't make sense, as `b` would override every field of `a` anyway.";
+
+var listExprSpread = "Lists can only have one `...` spread, and at the end.\nExplanation: lists are singly-linked list, where a node contains a value and points to the next node. `list[a, ...bc]` efficiently creates a new item and links `bc` as its next nodes. `[...bc, a]` would be expensive, as it'd need to traverse `bc` and prepend each item to `a` one by one. We therefore disallow such syntax sugar.\nSolution: directly use `concat`.";
+
+var variantIdent = "A polymorphic variant (e.g. #id) must start with an alphabetical letter or be a number (e.g. #742)";
+
+function experimentalIfLet(expr) {
+  var switchExpr_pexp_desc = expr.pexp_desc;
+  var switchExpr_pexp_loc = expr.pexp_loc;
+  var switchExpr = {
+    pexp_desc: switchExpr_pexp_desc,
+    pexp_loc: switchExpr_pexp_loc,
+    pexp_attributes: /* [] */0
+  };
+  return Res_doc.toString(80, Res_doc.concat({
+                  hd: Res_doc.text("If-let is currently highly experimental."),
+                  tl: {
+                    hd: Res_doc.line,
+                    tl: {
+                      hd: Res_doc.text("Use a regular `switch` with pattern matching instead:"),
+                      tl: {
+                        hd: Res_doc.concat({
+                              hd: Res_doc.hardLine,
+                              tl: {
+                                hd: Res_doc.hardLine,
+                                tl: {
+                                  hd: Res_printer.printExpression(switchExpr, Res_comments_table.empty),
+                                  tl: /* [] */0
+                                }
+                              }
+                            }),
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+var typeParam = "A type param consists of a singlequote followed by a name like `'a` or `'A`";
+
+var typeVar = "A type variable consists of a singlequote followed by a name like `'a` or `'A`";
+
+function attributeWithoutNode(attr) {
+  var attrName = attr[0].txt;
+  return "Did you forget to attach `" + (attrName + ("` to an item?\n  Standalone attributes start with `@@` like: `@@" + (attrName + "`")));
+}
+
+function typeDeclarationNameLongident(longident) {
+  return "A type declaration's name cannot contain a module access. Did you mean `" + (Longident.last(longident) + "`?");
+}
+
+var tupleSingleElement = "A tuple needs at least two elements";
+
+function missingTildeLabeledParameter(name) {
+  if (name === "") {
+    return "A labeled parameter starts with a `~`.";
+  } else {
+    return "A labeled parameter starts with a `~`. Did you mean: `~" + (name + "`?");
+  }
+}
+
+var stringInterpolationInPattern = "String interpolation is not supported in pattern matching.";
+
+var spreadInRecordDeclaration = "A record type declaration doesn't support the ... spread. Only an object (with quoted field names) does.";
+
+function objectQuotedFieldName(name) {
+  return "An object type declaration needs quoted field names. Did you mean \"" + (name + "\"?");
+}
+
+var forbiddenInlineRecordDeclaration = "An inline record type declaration is only allowed in a variant constructor's declaration";
+
+var sameTypeSpread = "You're using a ... spread without extra fields. This is the same type.";
+
+function polyVarIntWithSuffix(number) {
+  return "A numeric polymorphic variant cannot be followed by a letter. Did you mean `#" + (number + "`?");
+}
+
+var ErrorMessages = {
+  listPatternSpread: listPatternSpread,
+  recordPatternSpread: recordPatternSpread,
+  arrayPatternSpread: arrayPatternSpread,
+  arrayExprSpread: arrayExprSpread,
+  recordExprSpread: recordExprSpread,
+  listExprSpread: listExprSpread,
+  variantIdent: variantIdent,
+  experimentalIfLet: experimentalIfLet,
+  typeParam: typeParam,
+  typeVar: typeVar,
+  attributeWithoutNode: attributeWithoutNode,
+  typeDeclarationNameLongident: typeDeclarationNameLongident,
+  tupleSingleElement: tupleSingleElement,
+  missingTildeLabeledParameter: missingTildeLabeledParameter,
+  stringInterpolationInPattern: stringInterpolationInPattern,
+  spreadInRecordDeclaration: spreadInRecordDeclaration,
+  objectQuotedFieldName: objectQuotedFieldName,
+  forbiddenInlineRecordDeclaration: forbiddenInlineRecordDeclaration,
+  sameTypeSpread: sameTypeSpread,
+  polyVarIntWithSuffix: polyVarIntWithSuffix
+};
+
+var jsxAttr_0 = $$Location.mknoloc("JSX");
+
+var jsxAttr_1 = {
+  TAG: /* PStr */0,
+  _0: /* [] */0
+};
+
+var jsxAttr = [
+  jsxAttr_0,
+  jsxAttr_1
+];
+
+var uncurryAttr_0 = $$Location.mknoloc("bs");
+
+var uncurryAttr_1 = {
+  TAG: /* PStr */0,
+  _0: /* [] */0
+};
+
+var uncurryAttr = [
+  uncurryAttr_0,
+  uncurryAttr_1
+];
+
+var ternaryAttr_0 = $$Location.mknoloc("ns.ternary");
+
+var ternaryAttr_1 = {
+  TAG: /* PStr */0,
+  _0: /* [] */0
+};
+
+var ternaryAttr = [
+  ternaryAttr_0,
+  ternaryAttr_1
+];
+
+var ifLetAttr_0 = $$Location.mknoloc("ns.iflet");
+
+var ifLetAttr_1 = {
+  TAG: /* PStr */0,
+  _0: /* [] */0
+};
+
+var ifLetAttr = [
+  ifLetAttr_0,
+  ifLetAttr_1
+];
+
+var suppressFragileMatchWarningAttr_0 = $$Location.mknoloc("warning");
+
+var suppressFragileMatchWarningAttr_1 = {
+  TAG: /* PStr */0,
+  _0: {
+    hd: Ast_helper.Str.$$eval(undefined, undefined, Ast_helper.Exp.constant(undefined, undefined, {
+              TAG: /* Pconst_string */2,
+              _0: "-4",
+              _1: undefined
+            })),
+    tl: /* [] */0
+  }
+};
+
+var suppressFragileMatchWarningAttr = [
+  suppressFragileMatchWarningAttr_0,
+  suppressFragileMatchWarningAttr_1
+];
+
+function makeBracesAttr(loc) {
+  return [
+          $$Location.mkloc("ns.braces", loc),
+          {
+            TAG: /* PStr */0,
+            _0: /* [] */0
+          }
+        ];
+}
+
+var templateLiteralAttr_0 = $$Location.mknoloc("res.template");
+
+var templateLiteralAttr_1 = {
+  TAG: /* PStr */0,
+  _0: /* [] */0
+};
+
+var templateLiteralAttr = [
+  templateLiteralAttr_0,
+  templateLiteralAttr_1
+];
+
+function getClosingToken(x) {
+  if (typeof x === "number") {
+    if (x === 42) {
+      return /* GreaterThan */41;
+    }
+    if (x >= 23) {
+      if (x === 79) {
+        return /* Rbrace */23;
+      }
+      
+    } else if (x >= 18) {
+      switch (x) {
+        case /* Lparen */18 :
+            return /* Rparen */19;
+        case /* Lbracket */20 :
+            return /* Rbracket */21;
+        case /* Rparen */19 :
+        case /* Rbracket */21 :
+            break;
+        case /* Lbrace */22 :
+            return /* Rbrace */23;
+        
+      }
+    }
+    
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_core.res",
+          207,
+          9
+        ],
+        Error: new Error()
+      };
+}
+
+function goToClosing(closingToken, state) {
+  while(true) {
+    var match = state.token;
+    var exit = 0;
+    if (typeof match === "number") {
+      if (match >= 43) {
+        exit = match !== 79 ? 1 : 2;
+      } else if (match >= 18) {
+        switch (match) {
+          case /* Rparen */19 :
+              if (closingToken === 19) {
+                Res_parser.next(undefined, state);
+                return ;
+              } else {
+                return ;
+              }
+          case /* Rbracket */21 :
+              if (closingToken === 21) {
+                Res_parser.next(undefined, state);
+                return ;
+              } else {
+                return ;
+              }
+          case /* Rbrace */23 :
+              if (closingToken === 23) {
+                Res_parser.next(undefined, state);
+                return ;
+              } else {
+                return ;
+              }
+          case /* Eof */26 :
+              return ;
+          case /* Colon */24 :
+          case /* Comma */25 :
+          case /* Exception */27 :
+          case /* Backslash */28 :
+          case /* Forwardslash */29 :
+          case /* ForwardslashDot */30 :
+          case /* Asterisk */31 :
+          case /* AsteriskDot */32 :
+          case /* Exponentiation */33 :
+          case /* Minus */34 :
+          case /* MinusDot */35 :
+          case /* Plus */36 :
+          case /* PlusDot */37 :
+          case /* PlusPlus */38 :
+          case /* PlusEqual */39 :
+          case /* ColonGreaterThan */40 :
+              exit = 1;
+              break;
+          case /* GreaterThan */41 :
+              if (closingToken === 41) {
+                Res_parser.next(undefined, state);
+                return ;
+              }
+              exit = 1;
+              break;
+          case /* Lparen */18 :
+          case /* Lbracket */20 :
+          case /* Lbrace */22 :
+          case /* LessThan */42 :
+              exit = 2;
+              break;
+          
+        }
+      } else {
+        exit = 1;
+      }
+    } else {
+      exit = 1;
+    }
+    switch (exit) {
+      case 1 :
+          Res_parser.next(undefined, state);
+          continue ;
+      case 2 :
+          Res_parser.next(undefined, state);
+          goToClosing(getClosingToken(match), state);
+          continue ;
+      
+    }
+  };
+}
+
+function isEs6ArrowExpression(inTernary, p) {
+  return Res_parser.lookahead(p, (function (state) {
+                var match = state.token;
+                if (typeof match === "number") {
+                  switch (match) {
+                    case /* Underscore */12 :
+                        break;
+                    case /* Lparen */18 :
+                        var prevEndPos = state.prevEndPos;
+                        Res_parser.next(undefined, state);
+                        var match$1 = state.token;
+                        var exit = 0;
+                        if (typeof match$1 === "number") {
+                          if (match$1 >= 20) {
+                            if (match$1 === 48) {
+                              return true;
+                            }
+                            if (match$1 === 80) {
+                              return false;
+                            }
+                            exit = 2;
+                          } else {
+                            if (match$1 === 4) {
+                              return true;
+                            }
+                            if (match$1 >= 19) {
+                              Res_parser.next(undefined, state);
+                              var match$2 = state.token;
+                              if (typeof match$2 !== "number") {
+                                return false;
+                              }
+                              if (match$2 !== 24) {
+                                return match$2 === 57;
+                              }
+                              if (inTernary) {
+                                return false;
+                              }
+                              Res_parser.next(undefined, state);
+                              var match$3 = state.token;
+                              if (typeof match$3 === "number") {
+                                return true;
+                              }
+                              if (match$3.TAG !== /* Lident */4) {
+                                return true;
+                              }
+                              Res_parser.next(undefined, state);
+                              var match$4 = state.token;
+                              if (match$4 === 42) {
+                                Res_parser.next(undefined, state);
+                                goToClosing(/* GreaterThan */41, state);
+                              }
+                              var match$5 = state.token;
+                              return match$5 === 57;
+                            } else {
+                              exit = 2;
+                            }
+                          }
+                        } else {
+                          exit = 2;
+                        }
+                        if (exit === 2) {
+                          goToClosing(/* Rparen */19, state);
+                          var match$6 = state.token;
+                          var exit$1 = 0;
+                          if (typeof match$6 === "number") {
+                            if (match$6 === 19) {
+                              return false;
+                            }
+                            if (match$6 !== 24) {
+                              if (match$6 === 57) {
+                                return true;
+                              }
+                              exit$1 = 3;
+                            } else {
+                              if (!inTernary) {
+                                return true;
+                              }
+                              exit$1 = 3;
+                            }
+                          } else {
+                            exit$1 = 3;
+                          }
+                          if (exit$1 === 3) {
+                            Res_parser.nextUnsafe(state);
+                            var match$7 = state.token;
+                            if (match$7 === 57) {
+                              return state.startPos.pos_lnum === prevEndPos.pos_lnum;
+                            } else {
+                              return false;
+                            }
+                          }
+                          
+                        }
+                        break;
+                    default:
+                      return false;
+                  }
+                } else if (match.TAG !== /* Lident */4) {
+                  return false;
+                }
+                Res_parser.next(undefined, state);
+                var match$8 = state.token;
+                return match$8 === 57;
+              }));
+}
+
+function isEs6ArrowFunctor(p) {
+  return Res_parser.lookahead(p, (function (state) {
+                var match = state.token;
+                if (match !== 18) {
+                  return false;
+                }
+                Res_parser.next(undefined, state);
+                var match$1 = state.token;
+                if (match$1 === 19) {
+                  Res_parser.next(undefined, state);
+                  var match$2 = state.token;
+                  if (typeof match$2 === "number") {
+                    if (match$2 !== 24) {
+                      return match$2 === 57;
+                    } else {
+                      return true;
+                    }
+                  } else {
+                    return false;
+                  }
+                }
+                goToClosing(/* Rparen */19, state);
+                var match$3 = state.token;
+                if (typeof match$3 !== "number") {
+                  return false;
+                }
+                if (match$3 >= 25) {
+                  return match$3 === 57;
+                }
+                if (match$3 < 22) {
+                  return false;
+                }
+                switch (match$3) {
+                  case /* Rbrace */23 :
+                      return false;
+                  case /* Lbrace */22 :
+                  case /* Colon */24 :
+                      return true;
+                  
+                }
+              }));
+}
+
+function isEs6ArrowType(p) {
+  return Res_parser.lookahead(p, (function (state) {
+                var match = state.token;
+                if (typeof match !== "number") {
+                  return false;
+                }
+                if (match !== 18) {
+                  return match === 48;
+                }
+                Res_parser.next(undefined, state);
+                var match$1 = state.token;
+                if (typeof match$1 === "number") {
+                  if (match$1 > 47 || match$1 < 5) {
+                    if (!(match$1 > 48 || match$1 < 4)) {
+                      return true;
+                    }
+                    
+                  } else if (match$1 === 19) {
+                    Res_parser.next(undefined, state);
+                    var match$2 = state.token;
+                    return match$2 === 57;
+                  }
+                  
+                }
+                goToClosing(/* Rparen */19, state);
+                var match$3 = state.token;
+                return match$3 === 57;
+              }));
+}
+
+function buildLongident(words) {
+  var match = List.rev(words);
+  if (match) {
+    return List.fold_left((function (p, s) {
+                  return {
+                          TAG: /* Ldot */1,
+                          _0: p,
+                          _1: s
+                        };
+                }), {
+                TAG: /* Lident */0,
+                _0: match.hd
+              }, match.tl);
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_core.res",
+          363,
+          14
+        ],
+        Error: new Error()
+      };
+}
+
+function makeInfixOperator(p, token, startPos, endPos) {
+  var stringifiedToken = token === /* MinusGreater */58 ? "|." : (
+      token === /* PlusPlus */38 ? "^" : (
+          token === /* BangEqual */70 ? "<>" : (
+              token === /* BangEqualEqual */71 ? "!=" : (
+                  token === /* Equal */14 ? (Res_parser.err(startPos, endPos, p, Res_diagnostics.message("Did you mean `==` here?")), "=") : (
+                      token === /* EqualEqual */15 ? "=" : (
+                          token === /* EqualEqualEqual */16 ? "==" : Res_token.toString(token)
+                        )
+                    )
+                )
+            )
+        )
+    );
+  var loc = {
+    loc_start: startPos,
+    loc_end: endPos,
+    loc_ghost: false
+  };
+  var operator = $$Location.mkloc({
+        TAG: /* Lident */0,
+        _0: stringifiedToken
+      }, loc);
+  return Ast_helper.Exp.ident(loc, undefined, operator);
+}
+
+function negateString(s) {
+  if (s.length !== 0 && Caml_string.get(s, 0) === /* '-' */45) {
+    return $$String.sub(s, 1, s.length - 1 | 0);
+  } else {
+    return "-" + s;
+  }
+}
+
+function makeUnaryExpr(startPos, tokenEnd, token, operand) {
+  var match = operand.pexp_desc;
+  var exit = 0;
+  if (typeof token === "number") {
+    if (token >= 34) {
+      if (token >= 36) {
+        if (token < 38 && typeof match !== "number" && match.TAG === /* Pexp_constant */1) {
+          switch (match._0.TAG | 0) {
+            case /* Pconst_char */1 :
+            case /* Pconst_string */2 :
+                break;
+            case /* Pconst_integer */0 :
+            case /* Pconst_float */3 :
+                return operand;
+            
+          }
+        }
+        
+      } else if (token >= 35 || typeof match === "number" || match.TAG !== /* Pexp_constant */1) {
+        exit = 2;
+      } else {
+        var match$1 = match._0;
+        if (match$1.TAG === /* Pconst_integer */0) {
+          return {
+                  pexp_desc: {
+                    TAG: /* Pexp_constant */1,
+                    _0: {
+                      TAG: /* Pconst_integer */0,
+                      _0: negateString(match$1._0),
+                      _1: match$1._1
+                    }
+                  },
+                  pexp_loc: operand.pexp_loc,
+                  pexp_attributes: operand.pexp_attributes
+                };
+        }
+        exit = 2;
+      }
+    } else if (token === 7) {
+      var tokenLoc = {
+        loc_start: startPos,
+        loc_end: tokenEnd,
+        loc_ghost: false
+      };
+      return Ast_helper.Exp.apply({
+                  loc_start: startPos,
+                  loc_end: operand.pexp_loc.loc_end,
+                  loc_ghost: false
+                }, undefined, Ast_helper.Exp.ident(tokenLoc, undefined, $$Location.mkloc({
+                          TAG: /* Lident */0,
+                          _0: "not"
+                        }, tokenLoc)), {
+                  hd: [
+                    /* Nolabel */0,
+                    operand
+                  ],
+                  tl: /* [] */0
+                });
+    }
+    
+  }
+  if (exit === 2 && typeof match !== "number" && match.TAG === /* Pexp_constant */1) {
+    var match$2 = match._0;
+    if (match$2.TAG === /* Pconst_float */3) {
+      return {
+              pexp_desc: {
+                TAG: /* Pexp_constant */1,
+                _0: {
+                  TAG: /* Pconst_float */3,
+                  _0: negateString(match$2._0),
+                  _1: match$2._1
+                }
+              },
+              pexp_loc: operand.pexp_loc,
+              pexp_attributes: operand.pexp_attributes
+            };
+    }
+    
+  }
+  if (typeof token !== "number") {
+    return operand;
+  }
+  if (token > 37 || token < 34) {
+    return operand;
+  }
+  var tokenLoc$1 = {
+    loc_start: startPos,
+    loc_end: tokenEnd,
+    loc_ghost: false
+  };
+  var operator = "~" + Res_token.toString(token);
+  return Ast_helper.Exp.apply({
+              loc_start: startPos,
+              loc_end: operand.pexp_loc.loc_end,
+              loc_ghost: false
+            }, undefined, Ast_helper.Exp.ident(tokenLoc$1, undefined, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: operator
+                    }, tokenLoc$1)), {
+              hd: [
+                /* Nolabel */0,
+                operand
+              ],
+              tl: /* [] */0
+            });
+}
+
+function makeListExpression(loc, seq, extOpt) {
+  var handleSeq = function (x) {
+    if (x) {
+      var e1 = x.hd;
+      var exp_el = handleSeq(x.tl);
+      var loc_loc_start = e1.pexp_loc.loc_start;
+      var loc_loc_end = exp_el.pexp_loc.loc_end;
+      var loc$1 = {
+        loc_start: loc_loc_start,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      var arg = Ast_helper.Exp.tuple(loc$1, undefined, {
+            hd: e1,
+            tl: {
+              hd: exp_el,
+              tl: /* [] */0
+            }
+          });
+      return Ast_helper.Exp.construct(loc$1, undefined, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: "::"
+                    }, loc$1), arg);
+    }
+    if (extOpt !== undefined) {
+      return extOpt;
+    }
+    var loc_loc_start$1 = loc.loc_start;
+    var loc_loc_end$1 = loc.loc_end;
+    var loc$2 = {
+      loc_start: loc_loc_start$1,
+      loc_end: loc_loc_end$1,
+      loc_ghost: true
+    };
+    var nil = $$Location.mkloc({
+          TAG: /* Lident */0,
+          _0: "[]"
+        }, loc$2);
+    return Ast_helper.Exp.construct(loc$2, undefined, nil, undefined);
+  };
+  var expr = handleSeq(seq);
+  return {
+          pexp_desc: expr.pexp_desc,
+          pexp_loc: loc,
+          pexp_attributes: expr.pexp_attributes
+        };
+}
+
+function makeListPattern(loc, seq, ext_opt) {
+  var handle_seq = function (x) {
+    if (x) {
+      var p1 = x.hd;
+      var pat_pl = handle_seq(x.tl);
+      var loc_loc_start = p1.ppat_loc.loc_start;
+      var loc_loc_end = pat_pl.ppat_loc.loc_end;
+      var loc$1 = {
+        loc_start: loc_loc_start,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      var arg = Ast_helper.Pat.mk(loc$1, undefined, {
+            TAG: /* Ppat_tuple */4,
+            _0: {
+              hd: p1,
+              tl: {
+                hd: pat_pl,
+                tl: /* [] */0
+              }
+            }
+          });
+      return Ast_helper.Pat.mk(loc$1, undefined, {
+                  TAG: /* Ppat_construct */5,
+                  _0: $$Location.mkloc({
+                        TAG: /* Lident */0,
+                        _0: "::"
+                      }, loc$1),
+                  _1: arg
+                });
+    }
+    if (ext_opt !== undefined) {
+      return ext_opt;
+    }
+    var loc_loc_start$1 = loc.loc_start;
+    var loc_loc_end$1 = loc.loc_end;
+    var loc$2 = {
+      loc_start: loc_loc_start$1,
+      loc_end: loc_loc_end$1,
+      loc_ghost: true
+    };
+    var nil_txt = {
+      TAG: /* Lident */0,
+      _0: "[]"
+    };
+    var nil = {
+      txt: nil_txt,
+      loc: loc$2
+    };
+    return Ast_helper.Pat.construct(loc$2, undefined, nil, undefined);
+  };
+  return handle_seq(seq);
+}
+
+function lidentOfPath(longident) {
+  var match = List.rev(Longident.flatten(longident));
+  if (match) {
+    return match.hd;
+  } else {
+    return "";
+  }
+}
+
+function makeNewtypes(attrs, loc, newtypes, exp) {
+  var expr = List.fold_right((function (newtype, exp) {
+          return Ast_helper.Exp.mk(loc, undefined, {
+                      TAG: /* Pexp_newtype */31,
+                      _0: newtype,
+                      _1: exp
+                    });
+        }), newtypes, exp);
+  return {
+          pexp_desc: expr.pexp_desc,
+          pexp_loc: expr.pexp_loc,
+          pexp_attributes: attrs
+        };
+}
+
+function wrapTypeAnnotation(loc, newtypes, core_type, body) {
+  var exp = makeNewtypes(/* [] */0, loc, newtypes, Ast_helper.Exp.constraint_(loc, undefined, body, core_type));
+  var typ = Ast_helper.Typ.poly(loc, undefined, newtypes, Ast_helper.Typ.varify_constructors(newtypes, core_type));
+  return [
+          exp,
+          typ
+        ];
+}
+
+function processUnderscoreApplication(args) {
+  var exp_question = {
+    contents: undefined
+  };
+  var hidden_var = "__x";
+  var check_arg = function (arg) {
+    var exp = arg[1];
+    var id = exp.pexp_desc;
+    if (typeof id === "number") {
+      return arg;
+    }
+    if (id.TAG !== /* Pexp_ident */0) {
+      return arg;
+    }
+    var id$1 = id._0;
+    var match = id$1.txt;
+    switch (match.TAG | 0) {
+      case /* Lident */0 :
+          if (match._0 !== "_") {
+            return arg;
+          }
+          var new_id = $$Location.mkloc({
+                TAG: /* Lident */0,
+                _0: hidden_var
+              }, id$1.loc);
+          var new_exp = Ast_helper.Exp.mk(exp.pexp_loc, undefined, {
+                TAG: /* Pexp_ident */0,
+                _0: new_id
+              });
+          exp_question.contents = new_exp;
+          return [
+                  arg[0],
+                  new_exp
+                ];
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return arg;
+      
+    }
+  };
+  var args$1 = List.map(check_arg, args);
+  var wrap = function (exp_apply) {
+    var match = exp_question.contents;
+    if (match === undefined) {
+      return exp_apply;
+    }
+    var loc = match.pexp_loc;
+    var pattern = Ast_helper.Pat.mk(loc, undefined, {
+          TAG: /* Ppat_var */0,
+          _0: $$Location.mkloc(hidden_var, loc)
+        });
+    return Ast_helper.Exp.mk(loc, undefined, {
+                TAG: /* Pexp_fun */4,
+                _0: /* Nolabel */0,
+                _1: undefined,
+                _2: pattern,
+                _3: exp_apply
+              });
+  };
+  return [
+          args$1,
+          wrap
+        ];
+}
+
+function hexValue(ch) {
+  if (ch >= 65) {
+    if (ch >= 97) {
+      if (ch >= 103) {
+        return 16;
+      } else {
+        return (ch - /* 'a' */97 | 0) + 10 | 0;
+      }
+    } else if (ch >= 71) {
+      return 16;
+    } else {
+      return ((ch + 32 | 0) - /* 'a' */97 | 0) + 10 | 0;
+    }
+  } else if (ch > 57 || ch < 48) {
+    return 16;
+  } else {
+    return ch - 48 | 0;
+  }
+}
+
+function removeModuleNameFromPunnedFieldValue(exp) {
+  var pathIdent = exp.pexp_desc;
+  if (typeof pathIdent === "number") {
+    return exp;
+  }
+  if (pathIdent.TAG !== /* Pexp_ident */0) {
+    return exp;
+  }
+  var pathIdent$1 = pathIdent._0;
+  return {
+          pexp_desc: {
+            TAG: /* Pexp_ident */0,
+            _0: {
+              txt: {
+                TAG: /* Lident */0,
+                _0: Longident.last(pathIdent$1.txt)
+              },
+              loc: pathIdent$1.loc
+            }
+          },
+          pexp_loc: exp.pexp_loc,
+          pexp_attributes: exp.pexp_attributes
+        };
+}
+
+function parseStringLiteral(s) {
+  var len = s.length;
+  var b = $$Buffer.create(s.length);
+  var parse = function (_state, _i, _d) {
+    while(true) {
+      var d = _d;
+      var i = _i;
+      var state = _state;
+      if (i === len) {
+        return state > 6 || state < 2;
+      }
+      var c = s.charCodeAt(i);
+      switch (state) {
+        case /* Start */0 :
+            if (c !== 92) {
+              $$Buffer.add_char(b, c);
+              _i = i + 1 | 0;
+              _state = /* Start */0;
+              continue ;
+            }
+            _i = i + 1 | 0;
+            _state = /* Backslash */1;
+            continue ;
+        case /* Backslash */1 :
+            var exit = 0;
+            if (c >= 32) {
+              if (c < 58) {
+                if (c >= 40) {
+                  if (c >= 48) {
+                    _d = 0;
+                    _state = /* DecimalEscape */3;
+                    continue ;
+                  }
+                  exit = 1;
+                } else {
+                  switch (c) {
+                    case 33 :
+                    case 35 :
+                    case 36 :
+                    case 37 :
+                    case 38 :
+                        exit = 1;
+                        break;
+                    case 32 :
+                    case 34 :
+                    case 39 :
+                        exit = 2;
+                        break;
+                    
+                  }
+                }
+              } else {
+                switch (c) {
+                  case 92 :
+                      exit = 2;
+                      break;
+                  case 98 :
+                      $$Buffer.add_char(b, /* '\b' */8);
+                      _i = i + 1 | 0;
+                      _state = /* Start */0;
+                      continue ;
+                  case 110 :
+                      $$Buffer.add_char(b, /* '\n' */10);
+                      _i = i + 1 | 0;
+                      _state = /* Start */0;
+                      continue ;
+                  case 111 :
+                      _d = 0;
+                      _i = i + 1 | 0;
+                      _state = /* OctalEscape */4;
+                      continue ;
+                  case 114 :
+                      $$Buffer.add_char(b, /* '\r' */13);
+                      _i = i + 1 | 0;
+                      _state = /* Start */0;
+                      continue ;
+                  case 116 :
+                      $$Buffer.add_char(b, /* '\t' */9);
+                      _i = i + 1 | 0;
+                      _state = /* Start */0;
+                      continue ;
+                  case 117 :
+                      _d = 0;
+                      _i = i + 1 | 0;
+                      _state = /* UnicodeEscapeStart */7;
+                      continue ;
+                  case 93 :
+                  case 94 :
+                  case 95 :
+                  case 96 :
+                  case 97 :
+                  case 99 :
+                  case 100 :
+                  case 101 :
+                  case 102 :
+                  case 103 :
+                  case 104 :
+                  case 105 :
+                  case 106 :
+                  case 107 :
+                  case 108 :
+                  case 109 :
+                  case 112 :
+                  case 113 :
+                  case 115 :
+                  case 118 :
+                  case 119 :
+                      exit = 1;
+                      break;
+                  case 120 :
+                      _d = 0;
+                      _i = i + 1 | 0;
+                      _state = /* HexEscape */2;
+                      continue ;
+                  default:
+                    exit = 1;
+                }
+              }
+            } else if (c !== 10) {
+              if (c !== 13) {
+                exit = 1;
+              } else {
+                _i = i + 1 | 0;
+                _state = /* EscapedLineBreak */8;
+                continue ;
+              }
+            } else {
+              _i = i + 1 | 0;
+              _state = /* EscapedLineBreak */8;
+              continue ;
+            }
+            switch (exit) {
+              case 1 :
+                  $$Buffer.add_char(b, /* '\\' */92);
+                  $$Buffer.add_char(b, c);
+                  _i = i + 1 | 0;
+                  _state = /* Start */0;
+                  continue ;
+              case 2 :
+                  $$Buffer.add_char(b, c);
+                  _i = i + 1 | 0;
+                  _state = /* Start */0;
+                  continue ;
+              
+            }
+            break;
+        case /* HexEscape */2 :
+            if (d === 1) {
+              var c0 = s.charCodeAt(i - 1 | 0);
+              var c1 = s.charCodeAt(i);
+              var c$1 = (hexValue(c0) << 4) + hexValue(c1) | 0;
+              if (c$1 < 0 || c$1 > 255) {
+                return false;
+              }
+              $$Buffer.add_char(b, c$1);
+              _d = 0;
+              _i = i + 1 | 0;
+              _state = /* Start */0;
+              continue ;
+            }
+            _d = d + 1 | 0;
+            _i = i + 1 | 0;
+            _state = /* HexEscape */2;
+            continue ;
+        case /* DecimalEscape */3 :
+            if (d === 2) {
+              var c0$1 = s.charCodeAt(i - 2 | 0);
+              var c1$1 = s.charCodeAt(i - 1 | 0);
+              var c2 = s.charCodeAt(i);
+              var c$2 = (Math.imul(100, c0$1 - 48 | 0) + Math.imul(10, c1$1 - 48 | 0) | 0) + (c2 - 48 | 0) | 0;
+              if (c$2 < 0 || c$2 > 255) {
+                return false;
+              }
+              $$Buffer.add_char(b, c$2);
+              _d = 0;
+              _i = i + 1 | 0;
+              _state = /* Start */0;
+              continue ;
+            }
+            _d = d + 1 | 0;
+            _i = i + 1 | 0;
+            _state = /* DecimalEscape */3;
+            continue ;
+        case /* OctalEscape */4 :
+            if (d === 2) {
+              var c0$2 = s.charCodeAt(i - 2 | 0);
+              var c1$2 = s.charCodeAt(i - 1 | 0);
+              var c2$1 = s.charCodeAt(i);
+              var c$3 = (((c0$2 - 48 | 0) << 6) + ((c1$2 - 48 | 0) << 3) | 0) + (c2$1 - 48 | 0) | 0;
+              if (c$3 < 0 || c$3 > 255) {
+                return false;
+              }
+              $$Buffer.add_char(b, c$3);
+              _d = 0;
+              _i = i + 1 | 0;
+              _state = /* Start */0;
+              continue ;
+            }
+            _d = d + 1 | 0;
+            _i = i + 1 | 0;
+            _state = /* OctalEscape */4;
+            continue ;
+        case /* UnicodeEscape */5 :
+            if (d === 3) {
+              var c0$3 = s.charCodeAt(i - 3 | 0);
+              var c1$3 = s.charCodeAt(i - 2 | 0);
+              var c2$2 = s.charCodeAt(i - 1 | 0);
+              var c3 = s.charCodeAt(i);
+              var c$4 = (((hexValue(c0$3) << 12) + (hexValue(c1$3) << 8) | 0) + (hexValue(c2$2) << 4) | 0) + hexValue(c3) | 0;
+              if (!Res_utf8.isValidCodePoint(c$4)) {
+                return false;
+              }
+              var codePoint = Res_utf8.encodeCodePoint(c$4);
+              $$Buffer.add_string(b, codePoint);
+              _d = 0;
+              _i = i + 1 | 0;
+              _state = /* Start */0;
+              continue ;
+            }
+            _d = d + 1 | 0;
+            _i = i + 1 | 0;
+            _state = /* UnicodeEscape */5;
+            continue ;
+        case /* UnicodeCodePointEscape */6 :
+            if (c >= 71) {
+              if (c >= 103) {
+                if (c !== 125) {
+                  return false;
+                }
+                var x = 0;
+                for(var remaining = d; remaining >= 1; --remaining){
+                  var ix = i - remaining | 0;
+                  x = (x << 4) + hexValue(s.charCodeAt(ix)) | 0;
+                }
+                var c$5 = x;
+                if (!Res_utf8.isValidCodePoint(c$5)) {
+                  return false;
+                }
+                var codePoint$1 = Res_utf8.encodeCodePoint(x);
+                $$Buffer.add_string(b, codePoint$1);
+                _d = 0;
+                _i = i + 1 | 0;
+                _state = /* Start */0;
+                continue ;
+              }
+              if (c < 97) {
+                return false;
+              }
+              
+            } else if (c >= 58) {
+              if (c < 65) {
+                return false;
+              }
+              
+            } else if (c < 48) {
+              return false;
+            }
+            _d = d + 1 | 0;
+            _i = i + 1 | 0;
+            _state = /* UnicodeCodePointEscape */6;
+            continue ;
+        case /* UnicodeEscapeStart */7 :
+            if (c !== 123) {
+              _d = 1;
+              _i = i + 1 | 0;
+              _state = /* UnicodeEscape */5;
+              continue ;
+            }
+            _d = 0;
+            _i = i + 1 | 0;
+            _state = /* UnicodeCodePointEscape */6;
+            continue ;
+        case /* EscapedLineBreak */8 :
+            if (c !== 9) {
+              if (c !== 32) {
+                $$Buffer.add_char(b, c);
+                _i = i + 1 | 0;
+                _state = /* Start */0;
+                continue ;
+              }
+              _i = i + 1 | 0;
+              _state = /* EscapedLineBreak */8;
+              continue ;
+            }
+            _i = i + 1 | 0;
+            _state = /* EscapedLineBreak */8;
+            continue ;
+        
+      }
+    };
+  };
+  if (parse(/* Start */0, 0, 0)) {
+    return $$Buffer.contents(b);
+  } else {
+    return s;
+  }
+}
+
+function parseLident(p) {
+  while(true) {
+    var recoverLident = function (p) {
+      if (Res_token.isKeyword(p.token) && p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.lident(p.token));
+        Res_parser.next(undefined, p);
+        return ;
+      }
+      var loop = function (p) {
+        while(true) {
+          if (shouldAbortListParse(p)) {
+            return ;
+          }
+          Res_parser.next(undefined, p);
+          continue ;
+        };
+      };
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.lident(p.token));
+      Res_parser.next(undefined, p);
+      loop(p);
+      var match = p.token;
+      if (typeof match === "number" || match.TAG !== /* Lident */4) {
+        return ;
+      } else {
+        return Caml_option.some(undefined);
+      }
+    };
+    var startPos = p.startPos;
+    var ident = p.token;
+    if (typeof ident !== "number" && ident.TAG === /* Lident */4) {
+      Res_parser.next(undefined, p);
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: startPos,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      return [
+              ident._0,
+              loc
+            ];
+    }
+    var match = recoverLident(p);
+    if (match === undefined) {
+      return [
+              "_",
+              {
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              }
+            ];
+    }
+    continue ;
+  };
+}
+
+function parseIdent(msg, startPos, p) {
+  var token = p.token;
+  var exit = 0;
+  if (typeof token === "number") {
+    exit = 2;
+  } else {
+    switch (token.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 1;
+          break;
+      default:
+        exit = 2;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        Res_parser.next(undefined, p);
+        var loc_loc_end = p.prevEndPos;
+        var loc = {
+          loc_start: startPos,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        return [
+                token._0,
+                loc
+              ];
+    case 2 :
+        if (Res_token.isKeyword(token) && p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+          var tokenTxt = Res_token.toString(token);
+          var msg$1 = "`" + (tokenTxt + ("` is a reserved keyword. Keywords need to be escaped: \\\"" + (tokenTxt + "\"")));
+          Res_parser.err(startPos, undefined, p, Res_diagnostics.message(msg$1));
+          Res_parser.next(undefined, p);
+          return [
+                  tokenTxt,
+                  {
+                    loc_start: startPos,
+                    loc_end: p.prevEndPos,
+                    loc_ghost: false
+                  }
+                ];
+        }
+        Res_parser.err(startPos, undefined, p, Res_diagnostics.message(msg));
+        Res_parser.next(undefined, p);
+        return [
+                "",
+                {
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                }
+              ];
+    
+  }
+}
+
+function parseHashIdent(startPos, p) {
+  Res_parser.expect(undefined, /* Hash */44, p);
+  var text = p.token;
+  if (typeof text === "number") {
+    return parseIdent(variantIdent, startPos, p);
+  }
+  switch (text.TAG | 0) {
+    case /* Int */1 :
+        var i = text.i;
+        if (text.suffix !== undefined) {
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.message(polyVarIntWithSuffix(i)));
+        }
+        Res_parser.next(undefined, p);
+        return [
+                i,
+                {
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                }
+              ];
+    case /* String */3 :
+        var text$1 = text._0;
+        var text$2 = p.mode === /* ParseForTypeChecker */0 ? parseStringLiteral(text$1) : text$1;
+        Res_parser.next(undefined, p);
+        return [
+                text$2,
+                {
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                }
+              ];
+    default:
+      return parseIdent(variantIdent, startPos, p);
+  }
+}
+
+function parseValuePath(p) {
+  var startPos = p.startPos;
+  var aux = function (p, _path) {
+    while(true) {
+      var path = _path;
+      var ident = p.token;
+      if (typeof ident !== "number") {
+        switch (ident.TAG | 0) {
+          case /* Lident */4 :
+              return {
+                      TAG: /* Ldot */1,
+                      _0: path,
+                      _1: ident._0
+                    };
+          case /* Uident */5 :
+              Res_parser.next(undefined, p);
+              if (p.token === /* Dot */4) {
+                Res_parser.expect(undefined, /* Dot */4, p);
+                _path = {
+                  TAG: /* Ldot */1,
+                  _0: path,
+                  _1: ident._0
+                };
+                continue ;
+              }
+              Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+              return path;
+          default:
+            
+        }
+      }
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident, p.breadcrumbs));
+      return {
+              TAG: /* Ldot */1,
+              _0: path,
+              _1: "_"
+            };
+    };
+  };
+  var ident = p.token;
+  var ident$1;
+  var exit = 0;
+  if (typeof ident === "number") {
+    exit = 1;
+  } else {
+    switch (ident.TAG | 0) {
+      case /* Lident */4 :
+          ident$1 = {
+            TAG: /* Lident */0,
+            _0: ident._0
+          };
+          break;
+      case /* Uident */5 :
+          var ident$2 = ident._0;
+          Res_parser.next(undefined, p);
+          if (p.token === /* Dot */4) {
+            Res_parser.expect(undefined, /* Dot */4, p);
+            ident$1 = aux(p, {
+                  TAG: /* Lident */0,
+                  _0: ident$2
+                });
+          } else {
+            Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+            ident$1 = {
+              TAG: /* Lident */0,
+              _0: ident$2
+            };
+          }
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident, p.breadcrumbs));
+    ident$1 = {
+      TAG: /* Lident */0,
+      _0: "_"
+    };
+  }
+  Res_parser.next(undefined, p);
+  return $$Location.mkloc(ident$1, {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            });
+}
+
+function parseValuePathAfterDot(p) {
+  var startPos = p.startPos;
+  var token = p.token;
+  if (typeof token !== "number") {
+    switch (token.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return parseValuePath(p);
+      default:
+        
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+  return $$Location.mkloc({
+              TAG: /* Lident */0,
+              _0: "_"
+            }, {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            });
+}
+
+function parseValuePathTail(p, startPos, ident) {
+  var _path = ident;
+  while(true) {
+    var path = _path;
+    var ident$1 = p.token;
+    if (typeof ident$1 !== "number") {
+      switch (ident$1.TAG | 0) {
+        case /* Lident */4 :
+            Res_parser.next(undefined, p);
+            return $$Location.mkloc({
+                        TAG: /* Ldot */1,
+                        _0: path,
+                        _1: ident$1._0
+                      }, {
+                        loc_start: startPos,
+                        loc_end: p.prevEndPos,
+                        loc_ghost: false
+                      });
+        case /* Uident */5 :
+            Res_parser.next(undefined, p);
+            Res_parser.expect(undefined, /* Dot */4, p);
+            _path = {
+              TAG: /* Ldot */1,
+              _0: path,
+              _1: ident$1._0
+            };
+            continue ;
+        default:
+          
+      }
+    }
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident$1, p.breadcrumbs));
+    return $$Location.mkloc({
+                TAG: /* Ldot */1,
+                _0: path,
+                _1: "_"
+              }, {
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              });
+  };
+}
+
+function parseModuleLongIdentTail(lowercase, p, startPos, ident) {
+  var _acc = ident;
+  while(true) {
+    var acc = _acc;
+    var ident$1 = p.token;
+    if (typeof ident$1 !== "number") {
+      switch (ident$1.TAG | 0) {
+        case /* Lident */4 :
+            if (lowercase) {
+              Res_parser.next(undefined, p);
+              var lident_1 = ident$1._0;
+              var lident = {
+                TAG: /* Ldot */1,
+                _0: acc,
+                _1: lident_1
+              };
+              return $$Location.mkloc(lident, {
+                          loc_start: startPos,
+                          loc_end: p.prevEndPos,
+                          loc_ghost: false
+                        });
+            }
+            break;
+        case /* Uident */5 :
+            Res_parser.next(undefined, p);
+            var endPos = p.prevEndPos;
+            var lident_1$1 = ident$1._0;
+            var lident$1 = {
+              TAG: /* Ldot */1,
+              _0: acc,
+              _1: lident_1$1
+            };
+            var match = p.token;
+            if (match !== 4) {
+              return $$Location.mkloc(lident$1, {
+                          loc_start: startPos,
+                          loc_end: endPos,
+                          loc_ghost: false
+                        });
+            }
+            Res_parser.next(undefined, p);
+            _acc = lident$1;
+            continue ;
+        default:
+          
+      }
+    }
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident$1));
+    return $$Location.mkloc({
+                TAG: /* Ldot */1,
+                _0: acc,
+                _1: "_"
+              }, {
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              });
+  };
+}
+
+function parseModuleLongIdent(lowercase, p) {
+  var startPos = p.startPos;
+  var ident = p.token;
+  if (typeof ident !== "number") {
+    switch (ident.TAG | 0) {
+      case /* Lident */4 :
+          if (lowercase) {
+            var loc_loc_end = p.endPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var lident = {
+              TAG: /* Lident */0,
+              _0: ident._0
+            };
+            Res_parser.next(undefined, p);
+            return $$Location.mkloc(lident, loc);
+          }
+          break;
+      case /* Uident */5 :
+          var lident$1 = {
+            TAG: /* Lident */0,
+            _0: ident._0
+          };
+          var endPos = p.endPos;
+          Res_parser.next(undefined, p);
+          var match = p.token;
+          if (match === 4) {
+            Res_parser.next(undefined, p);
+            return parseModuleLongIdentTail(lowercase, p, startPos, lident$1);
+          } else {
+            return $$Location.mkloc(lident$1, {
+                        loc_start: startPos,
+                        loc_end: endPos,
+                        loc_ghost: false
+                      });
+          }
+      default:
+        
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+  return $$Location.mkloc({
+              TAG: /* Lident */0,
+              _0: "_"
+            }, {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            });
+}
+
+function parseIdentPath(p) {
+  var match = p.token;
+  if (typeof match === "number") {
+    return {
+            TAG: /* Lident */0,
+            _0: "_"
+          };
+  }
+  switch (match.TAG | 0) {
+    case /* Lident */4 :
+    case /* Uident */5 :
+        break;
+    default:
+      return {
+              TAG: /* Lident */0,
+              _0: "_"
+            };
+  }
+  var ident = match._0;
+  Res_parser.next(undefined, p);
+  var match$1 = p.token;
+  if (match$1 === 4) {
+    Res_parser.next(undefined, p);
+    var _acc = {
+      TAG: /* Lident */0,
+      _0: ident
+    };
+    while(true) {
+      var acc = _acc;
+      var _t = p.token;
+      if (typeof _t === "number") {
+        return acc;
+      }
+      switch (_t.TAG | 0) {
+        case /* Lident */4 :
+        case /* Uident */5 :
+            break;
+        default:
+          return acc;
+      }
+      Res_parser.next(undefined, p);
+      var lident_1 = _t._0;
+      var lident = {
+        TAG: /* Ldot */1,
+        _0: acc,
+        _1: lident_1
+      };
+      var match$2 = p.token;
+      if (match$2 !== 4) {
+        return lident;
+      }
+      Res_parser.next(undefined, p);
+      _acc = lident;
+      continue ;
+    };
+  } else {
+    return {
+            TAG: /* Lident */0,
+            _0: ident
+          };
+  }
+}
+
+function verifyJsxOpeningClosingName(p, nameExpr) {
+  var lident = p.token;
+  var closing;
+  if (typeof lident === "number") {
+    closing = {
+      TAG: /* Lident */0,
+      _0: ""
+    };
+  } else {
+    switch (lident.TAG | 0) {
+      case /* Lident */4 :
+          Res_parser.next(undefined, p);
+          closing = {
+            TAG: /* Lident */0,
+            _0: lident._0
+          };
+          break;
+      case /* Uident */5 :
+          closing = parseModuleLongIdent(true, p).txt;
+          break;
+      default:
+        closing = {
+          TAG: /* Lident */0,
+          _0: ""
+        };
+    }
+  }
+  var openingIdent = nameExpr.pexp_desc;
+  if (typeof openingIdent === "number") {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_core.res",
+            975,
+            9
+          ],
+          Error: new Error()
+        };
+  }
+  if (openingIdent.TAG === /* Pexp_ident */0) {
+    var withoutCreateElement = List.filter(function (s) {
+            return s !== "createElement";
+          })(Longident.flatten(openingIdent._0.txt));
+    var li = Longident.unflatten(withoutCreateElement);
+    var opening = li !== undefined ? li : ({
+          TAG: /* Lident */0,
+          _0: ""
+        });
+    return Caml_obj.caml_equal(opening, closing);
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_core.res",
+          975,
+          9
+        ],
+        Error: new Error()
+      };
+}
+
+function string_of_pexp_ident(nameExpr) {
+  var openingIdent = nameExpr.pexp_desc;
+  if (typeof openingIdent === "number" || openingIdent.TAG !== /* Pexp_ident */0) {
+    return "";
+  } else {
+    return $$String.concat(".", List.filter(function (s) {
+                      return s !== "createElement";
+                    })(Longident.flatten(openingIdent._0.txt)));
+  }
+}
+
+function parseOpenDescription(attrs, p) {
+  Res_parser.leaveBreadcrumb(p, /* OpenDescription */0);
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Open */0, p);
+  var override = Res_parser.optional(p, /* Bang */7) ? /* Override */0 : /* Fresh */1;
+  var modident = parseModuleLongIdent(false, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  Res_parser.eatBreadcrumb(p);
+  return Ast_helper.Opn.mk(loc, attrs, undefined, override, modident);
+}
+
+function parseTemplateStringLiteral(s) {
+  var len = s.length;
+  var b = $$Buffer.create(len);
+  var loop = function (_i) {
+    while(true) {
+      var i = _i;
+      if (i >= len) {
+        return ;
+      }
+      var c = s.charCodeAt(i);
+      if (c !== 92) {
+        $$Buffer.add_char(b, c);
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if ((i + 1 | 0) >= len) {
+        return $$Buffer.add_char(b, c);
+      }
+      var nextChar = s.charCodeAt(i + 1 | 0);
+      var exit = 0;
+      if (nextChar >= 36) {
+        exit = nextChar > 95 || nextChar < 37 ? (
+            nextChar >= 97 ? 2 : 1
+          ) : (
+            nextChar !== 92 ? 2 : 1
+          );
+      } else if (nextChar !== 10) {
+        if (nextChar !== 13) {
+          exit = 2;
+        } else {
+          _i = i + 2 | 0;
+          continue ;
+        }
+      } else {
+        _i = i + 2 | 0;
+        continue ;
+      }
+      switch (exit) {
+        case 1 :
+            $$Buffer.add_char(b, nextChar);
+            _i = i + 2 | 0;
+            continue ;
+        case 2 :
+            $$Buffer.add_char(b, /* '\\' */92);
+            $$Buffer.add_char(b, nextChar);
+            _i = i + 2 | 0;
+            continue ;
+        
+      }
+    };
+  };
+  loop(0);
+  return $$Buffer.contents(b);
+}
+
+function parseConstant(p) {
+  var match = p.token;
+  var isNegative;
+  if (typeof match === "number") {
+    switch (match) {
+      case /* Minus */34 :
+          Res_parser.next(undefined, p);
+          isNegative = true;
+          break;
+      case /* MinusDot */35 :
+          isNegative = false;
+          break;
+      case /* Plus */36 :
+          Res_parser.next(undefined, p);
+          isNegative = false;
+          break;
+      default:
+        isNegative = false;
+    }
+  } else {
+    isNegative = false;
+  }
+  var s = p.token;
+  var constant;
+  var exit = 0;
+  if (typeof s === "number") {
+    exit = 1;
+  } else {
+    switch (s.TAG | 0) {
+      case /* Codepoint */0 :
+          constant = p.mode === /* ParseForTypeChecker */0 ? ({
+                TAG: /* Pconst_char */1,
+                _0: s.c
+              }) : ({
+                TAG: /* Pconst_string */2,
+                _0: s.original,
+                _1: "INTERNAL_RES_CHAR_CONTENTS"
+              });
+          break;
+      case /* Int */1 :
+          var i = s.i;
+          var intTxt = isNegative ? "-" + i : i;
+          constant = {
+            TAG: /* Pconst_integer */0,
+            _0: intTxt,
+            _1: s.suffix
+          };
+          break;
+      case /* Float */2 :
+          var f = s.f;
+          var floatTxt = isNegative ? "-" + f : f;
+          constant = {
+            TAG: /* Pconst_float */3,
+            _0: floatTxt,
+            _1: s.suffix
+          };
+          break;
+      case /* String */3 :
+          var s$1 = s._0;
+          constant = p.mode === /* ParseForTypeChecker */0 ? ({
+                TAG: /* Pconst_string */2,
+                _0: s$1,
+                _1: "js"
+              }) : ({
+                TAG: /* Pconst_string */2,
+                _0: s$1,
+                _1: undefined
+              });
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(s, p.breadcrumbs));
+    constant = {
+      TAG: /* Pconst_string */2,
+      _0: "",
+      _1: undefined
+    };
+  }
+  Res_parser.next(undefined, p);
+  return constant;
+}
+
+function parseTemplateConstant(prefix, p) {
+  var startPos = p.startPos;
+  Res_parser.nextTemplateLiteralToken(p);
+  var txt = p.token;
+  if (typeof txt !== "number" && txt.TAG === /* TemplateTail */7) {
+    var txt$1 = txt._0;
+    Res_parser.next(undefined, p);
+    var txt$2 = p.mode === /* ParseForTypeChecker */0 ? parseTemplateStringLiteral(txt$1) : txt$1;
+    return {
+            TAG: /* Pconst_string */2,
+            _0: txt$2,
+            _1: prefix
+          };
+  }
+  var skipTokens = function (_param) {
+    while(true) {
+      Res_parser.next(undefined, p);
+      var match = p.token;
+      if (match === 80) {
+        Res_parser.next(undefined, p);
+        return ;
+      }
+      _param = undefined;
+      continue ;
+    };
+  };
+  skipTokens(undefined);
+  Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(stringInterpolationInPattern));
+  return {
+          TAG: /* Pconst_string */2,
+          _0: "",
+          _1: undefined
+        };
+}
+
+function parseCommaDelimitedRegion(p, grammar, closing, f) {
+  Res_parser.leaveBreadcrumb(p, grammar);
+  var loop = function (_nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var node = Curry._1(f, p);
+      if (node !== undefined) {
+        var node$1 = Caml_option.valFromOption(node);
+        var token = p.token;
+        if (token === 25) {
+          Res_parser.next(undefined, p);
+          _nodes = {
+            hd: node$1,
+            tl: nodes
+          };
+          continue ;
+        }
+        if (Caml_obj.caml_equal(token, closing) || token === /* Eof */26) {
+          return List.rev({
+                      hd: node$1,
+                      tl: nodes
+                    });
+        }
+        if (Res_grammar.isListElement(grammar, p.token)) {
+          Res_parser.expect(undefined, /* Comma */25, p);
+          _nodes = {
+            hd: node$1,
+            tl: nodes
+          };
+          continue ;
+        }
+        if (!(p.token === /* Eof */26 || Caml_obj.caml_equal(p.token, closing) || shouldAbortListParse(p))) {
+          Res_parser.expect(undefined, /* Comma */25, p);
+        }
+        if (p.token === /* Semicolon */8) {
+          Res_parser.next(undefined, p);
+        }
+        _nodes = {
+          hd: node$1,
+          tl: nodes
+        };
+        continue ;
+      }
+      if (p.token === /* Eof */26 || Caml_obj.caml_equal(p.token, closing) || shouldAbortListParse(p)) {
+        return List.rev(nodes);
+      }
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+      Res_parser.next(undefined, p);
+      continue ;
+    };
+  };
+  var nodes = loop(/* [] */0);
+  Res_parser.eatBreadcrumb(p);
+  return nodes;
+}
+
+function parseCommaDelimitedReversedList(p, grammar, closing, f) {
+  Res_parser.leaveBreadcrumb(p, grammar);
+  var loop = function (_nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var node = Curry._1(f, p);
+      if (node !== undefined) {
+        var node$1 = Caml_option.valFromOption(node);
+        var token = p.token;
+        if (token === 25) {
+          Res_parser.next(undefined, p);
+          _nodes = {
+            hd: node$1,
+            tl: nodes
+          };
+          continue ;
+        }
+        if (Caml_obj.caml_equal(token, closing) || token === /* Eof */26) {
+          return {
+                  hd: node$1,
+                  tl: nodes
+                };
+        }
+        if (Res_grammar.isListElement(grammar, p.token)) {
+          Res_parser.expect(undefined, /* Comma */25, p);
+          _nodes = {
+            hd: node$1,
+            tl: nodes
+          };
+          continue ;
+        }
+        if (!(p.token === /* Eof */26 || Caml_obj.caml_equal(p.token, closing) || shouldAbortListParse(p))) {
+          Res_parser.expect(undefined, /* Comma */25, p);
+        }
+        if (p.token === /* Semicolon */8) {
+          Res_parser.next(undefined, p);
+        }
+        _nodes = {
+          hd: node$1,
+          tl: nodes
+        };
+        continue ;
+      }
+      if (p.token === /* Eof */26 || Caml_obj.caml_equal(p.token, closing) || shouldAbortListParse(p)) {
+        return nodes;
+      }
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+      Res_parser.next(undefined, p);
+      continue ;
+    };
+  };
+  var nodes = loop(/* [] */0);
+  Res_parser.eatBreadcrumb(p);
+  return nodes;
+}
+
+function parseDelimitedRegion(p, grammar, closing, f) {
+  Res_parser.leaveBreadcrumb(p, grammar);
+  var loop = function (_nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var node = Curry._1(f, p);
+      if (node !== undefined) {
+        _nodes = {
+          hd: Caml_option.valFromOption(node),
+          tl: nodes
+        };
+        continue ;
+      }
+      if (p.token === /* Eof */26 || Caml_obj.caml_equal(p.token, closing) || shouldAbortListParse(p)) {
+        return List.rev(nodes);
+      }
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+      Res_parser.next(undefined, p);
+      continue ;
+    };
+  };
+  var nodes = loop(/* [] */0);
+  Res_parser.eatBreadcrumb(p);
+  return nodes;
+}
+
+function parseRegion(p, grammar, f) {
+  Res_parser.leaveBreadcrumb(p, grammar);
+  var loop = function (_nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var node = Curry._1(f, p);
+      if (node !== undefined) {
+        _nodes = {
+          hd: Caml_option.valFromOption(node),
+          tl: nodes
+        };
+        continue ;
+      }
+      if (p.token === /* Eof */26 || shouldAbortListParse(p)) {
+        return List.rev(nodes);
+      }
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(p.token, p.breadcrumbs));
+      Res_parser.next(undefined, p);
+      continue ;
+    };
+  };
+  var nodes = loop(/* [] */0);
+  Res_parser.eatBreadcrumb(p);
+  return nodes;
+}
+
+function parsePattern(aliasOpt, or_Opt, p) {
+  var alias = aliasOpt !== undefined ? aliasOpt : true;
+  var or_ = or_Opt !== undefined ? or_Opt : true;
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var token = p.token;
+  var pat;
+  var exit = 0;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* True */1 :
+      case /* False */2 :
+          exit = 2;
+          break;
+      case /* Underscore */12 :
+          var endPos = p.endPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: endPos,
+            loc_ghost: false
+          };
+          Res_parser.next(undefined, p);
+          pat = Ast_helper.Pat.any(loc, attrs, undefined);
+          break;
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var match = p.token;
+          if (match === 19) {
+            Res_parser.next(undefined, p);
+            var loc_loc_end = p.prevEndPos;
+            var loc$1 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var lid = $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: "()"
+                }, loc$1);
+            pat = Ast_helper.Pat.construct(loc$1, undefined, lid, undefined);
+          } else {
+            var pat$1 = parseConstrainedPattern(p);
+            var match$1 = p.token;
+            if (match$1 === 25) {
+              Res_parser.next(undefined, p);
+              pat = parseTuplePattern(attrs, pat$1, startPos, p);
+            } else {
+              Res_parser.expect(undefined, /* Rparen */19, p);
+              var loc_loc_end$1 = p.prevEndPos;
+              var loc$2 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$1,
+                loc_ghost: false
+              };
+              pat = {
+                ppat_desc: pat$1.ppat_desc,
+                ppat_loc: loc$2,
+                ppat_attributes: pat$1.ppat_attributes
+              };
+            }
+          }
+          break;
+      case /* Lbracket */20 :
+          pat = parseArrayPattern(attrs, p);
+          break;
+      case /* Lbrace */22 :
+          pat = parseRecordPattern(attrs, p);
+          break;
+      case /* Exception */27 :
+          Res_parser.next(undefined, p);
+          var pat$2 = parsePattern(false, false, p);
+          var loc_loc_end$2 = p.prevEndPos;
+          var loc$3 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$2,
+            loc_ghost: false
+          };
+          pat = Ast_helper.Pat.exception_(loc$3, attrs, pat$2);
+          break;
+      case /* Minus */34 :
+      case /* Plus */36 :
+          exit = 3;
+          break;
+      case /* Hash */44 :
+          Res_parser.next(undefined, p);
+          if (p.token === /* DotDotDot */6) {
+            Res_parser.next(undefined, p);
+            var ident = parseValuePath(p);
+            var loc_loc_end$3 = ident.loc.loc_end;
+            var loc$4 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$3,
+              loc_ghost: false
+            };
+            pat = Ast_helper.Pat.type_(loc$4, attrs, ident);
+          } else {
+            var text = p.token;
+            var match$2;
+            if (typeof text === "number") {
+              match$2 = parseIdent(variantIdent, startPos, p);
+            } else {
+              switch (text.TAG | 0) {
+                case /* Int */1 :
+                    var i = text.i;
+                    if (text.suffix !== undefined) {
+                      Res_parser.err(undefined, undefined, p, Res_diagnostics.message(polyVarIntWithSuffix(i)));
+                    }
+                    Res_parser.next(undefined, p);
+                    match$2 = [
+                      i,
+                      {
+                        loc_start: startPos,
+                        loc_end: p.prevEndPos,
+                        loc_ghost: false
+                      }
+                    ];
+                    break;
+                case /* String */3 :
+                    var text$1 = text._0;
+                    var text$2 = p.mode === /* ParseForTypeChecker */0 ? parseStringLiteral(text$1) : text$1;
+                    Res_parser.next(undefined, p);
+                    match$2 = [
+                      text$2,
+                      {
+                        loc_start: startPos,
+                        loc_end: p.prevEndPos,
+                        loc_ghost: false
+                      }
+                    ];
+                    break;
+                default:
+                  match$2 = parseIdent(variantIdent, startPos, p);
+              }
+            }
+            var ident$1 = match$2[0];
+            var match$3 = p.token;
+            pat = match$3 === 18 ? parseVariantPatternArgs(p, ident$1, startPos, attrs) : Ast_helper.Pat.variant(match$2[1], attrs, ident$1, undefined);
+          }
+          break;
+      case /* Lazy */47 :
+          Res_parser.next(undefined, p);
+          var pat$3 = parsePattern(false, false, p);
+          var loc_loc_end$4 = p.prevEndPos;
+          var loc$5 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$4,
+            loc_ghost: false
+          };
+          pat = Ast_helper.Pat.lazy_(loc$5, attrs, pat$3);
+          break;
+      case /* Module */65 :
+          pat = parseModulePattern(attrs, p);
+          break;
+      case /* Percent */77 :
+          var extension = parseExtension(undefined, p);
+          var loc_loc_end$5 = p.prevEndPos;
+          var loc$6 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$5,
+            loc_ghost: false
+          };
+          pat = Ast_helper.Pat.extension(loc$6, attrs, extension);
+          break;
+      case /* List */79 :
+          Res_parser.next(undefined, p);
+          pat = parseListPattern(startPos, attrs, p);
+          break;
+      case /* Backtick */80 :
+          var constant = parseTemplateConstant("js", p);
+          pat = Ast_helper.Pat.constant({
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              }, {
+                hd: templateLiteralAttr,
+                tl: /* [] */0
+              }, constant);
+          break;
+      default:
+        exit = 1;
+    }
+  } else {
+    switch (token.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+          exit = 3;
+          break;
+      case /* Lident */4 :
+          var ident$2 = token._0;
+          var endPos$1 = p.endPos;
+          var loc$7 = {
+            loc_start: startPos,
+            loc_end: endPos$1,
+            loc_ghost: false
+          };
+          Res_parser.next(undefined, p);
+          var match$4 = p.token;
+          if (match$4 === 80) {
+            var constant$1 = parseTemplateConstant(ident$2, p);
+            pat = Ast_helper.Pat.constant({
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                }, undefined, constant$1);
+          } else {
+            pat = Ast_helper.Pat.$$var(loc$7, attrs, $$Location.mkloc(ident$2, loc$7));
+          }
+          break;
+      case /* Uident */5 :
+          var constr = parseModuleLongIdent(false, p);
+          var match$5 = p.token;
+          pat = match$5 === 18 ? parseConstructorPatternArgs(p, constr, startPos, attrs) : Ast_helper.Pat.construct(constr.loc, attrs, constr, undefined);
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+        var match$6 = skipTokensAndMaybeRetry(p, Res_grammar.isAtomicPatternStart);
+        pat = match$6 !== undefined ? parsePattern(undefined, undefined, p) : defaultPattern(undefined);
+        break;
+    case 2 :
+        var endPos$2 = p.endPos;
+        Res_parser.next(undefined, p);
+        var loc$8 = {
+          loc_start: startPos,
+          loc_end: endPos$2,
+          loc_ghost: false
+        };
+        pat = Ast_helper.Pat.construct(loc$8, undefined, $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: Res_token.toString(token)
+                }, loc$8), undefined);
+        break;
+    case 3 :
+        var c = parseConstant(p);
+        var match$7 = p.token;
+        if (match$7 === 5) {
+          Res_parser.next(undefined, p);
+          var c2 = parseConstant(p);
+          pat = Ast_helper.Pat.interval({
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              }, undefined, c, c2);
+        } else {
+          pat = Ast_helper.Pat.constant({
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              }, undefined, c);
+        }
+        break;
+    
+  }
+  var pat$4 = alias ? parseAliasPattern(attrs, pat, p) : pat;
+  if (or_) {
+    return parseOrPattern(pat$4, p);
+  } else {
+    return pat$4;
+  }
+}
+
+function parseAttribute(p) {
+  var match = p.token;
+  if (match !== 75) {
+    return ;
+  }
+  var startPos = p.startPos;
+  Res_parser.next(undefined, p);
+  var attrId = parseAttributeId(startPos, p);
+  var payload = parsePayload(p);
+  return [
+          attrId,
+          payload
+        ];
+}
+
+function parseModuleType(es6ArrowOpt, with_Opt, p) {
+  var es6Arrow = es6ArrowOpt !== undefined ? es6ArrowOpt : true;
+  var with_ = with_Opt !== undefined ? with_Opt : true;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var modty;
+  if (es6Arrow && isEs6ArrowFunctor(p)) {
+    modty = parseFunctorModuleType(p);
+  } else {
+    var modty$1 = parseAtomicModuleType(p);
+    var match = p.token;
+    if (match === 57 && es6Arrow === true) {
+      Res_parser.next(undefined, p);
+      var rhs = parseModuleType(undefined, false, p);
+      var str = $$Location.mknoloc("_");
+      var loc_loc_start = modty$1.pmty_loc.loc_start;
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: loc_loc_start,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      modty = Ast_helper.Mty.functor_(loc, undefined, str, modty$1, rhs);
+    } else {
+      modty = modty$1;
+    }
+  }
+  var moduleType_pmty_desc = modty.pmty_desc;
+  var moduleType_pmty_loc = modty.pmty_loc;
+  var moduleType_pmty_attributes = List.concat({
+        hd: modty.pmty_attributes,
+        tl: {
+          hd: attrs,
+          tl: /* [] */0
+        }
+      });
+  var moduleType = {
+    pmty_desc: moduleType_pmty_desc,
+    pmty_loc: moduleType_pmty_loc,
+    pmty_attributes: moduleType_pmty_attributes
+  };
+  if (with_) {
+    return parseWithConstraints(moduleType, p);
+  } else {
+    return moduleType;
+  }
+}
+
+function parseModuleBindings(attrs, startPos, p) {
+  var first = parseModuleBinding(attrs, startPos, p);
+  var _acc = {
+    hd: first,
+    tl: /* [] */0
+  };
+  while(true) {
+    var acc = _acc;
+    var startPos$1 = p.startPos;
+    var attrs$1 = parseAttributesAndBinding(p);
+    var match = p.token;
+    if (match !== 10) {
+      return List.rev(acc);
+    }
+    Res_parser.next(undefined, p);
+    Res_parser.optional(p, /* Module */65);
+    var modBinding = parseModuleBinding(attrs$1, startPos$1, p);
+    _acc = {
+      hd: modBinding,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function parseModuleBinding(attrs, startPos, p) {
+  var ident = p.token;
+  var name;
+  var exit = 0;
+  if (typeof ident === "number" || ident.TAG !== /* Uident */5) {
+    exit = 1;
+  } else {
+    var startPos$1 = p.startPos;
+    Res_parser.next(undefined, p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos$1,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    name = $$Location.mkloc(ident._0, loc);
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+    name = $$Location.mknoloc("_");
+  }
+  var body = parseModuleBindingBody(p);
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Mb.mk(loc$1, attrs, undefined, undefined, name, body);
+}
+
+function parseConstrainedPattern(p) {
+  var pat = parsePattern(undefined, undefined, p);
+  var match = p.token;
+  if (match !== 24) {
+    return pat;
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = pat.ppat_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.constraint_(loc, undefined, pat, typ);
+}
+
+function parseLidentList(p) {
+  var _ls = /* [] */0;
+  while(true) {
+    var ls = _ls;
+    var lident = p.token;
+    if (typeof lident === "number") {
+      return List.rev(ls);
+    }
+    if (lident.TAG !== /* Lident */4) {
+      return List.rev(ls);
+    }
+    var loc_loc_start = p.startPos;
+    var loc_loc_end = p.endPos;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    _ls = {
+      hd: $$Location.mkloc(lident._0, loc),
+      tl: ls
+    };
+    continue ;
+  };
+}
+
+function parseTypExpr(attrs, es6ArrowOpt, aliasOpt, p) {
+  var es6Arrow = es6ArrowOpt !== undefined ? es6ArrowOpt : true;
+  var alias = aliasOpt !== undefined ? aliasOpt : true;
+  var startPos = p.startPos;
+  var attrs$1 = attrs !== undefined ? attrs : parseRegion(p, /* Attribute */50, parseAttribute);
+  var typ;
+  if (es6Arrow && isEs6ArrowType(p)) {
+    typ = parseEs6ArrowType(attrs$1, p);
+  } else {
+    var typ$1 = parseAtomicTypExpr(attrs$1, p);
+    typ = parseArrowTypeRest(es6Arrow, startPos, typ$1, p);
+  }
+  if (alias) {
+    return parseTypeAlias(p, typ);
+  } else {
+    return typ;
+  }
+}
+
+function parseConstrainedOrCoercedExpr(p) {
+  var expr = parseExpr(undefined, p);
+  var match = p.token;
+  if (typeof match !== "number") {
+    return expr;
+  }
+  if (match !== 24) {
+    if (match !== 40) {
+      return expr;
+    } else {
+      return parseCoercedExpr(expr, p);
+    }
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = expr.pexp_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var expr$1 = Ast_helper.Exp.constraint_(loc, undefined, expr, typ);
+  var match$1 = p.token;
+  if (match$1 === 40) {
+    return parseCoercedExpr(expr$1, p);
+  } else {
+    return expr$1;
+  }
+}
+
+function parseNonSpreadPattern(msg, p) {
+  var match = p.token;
+  if (match === 6) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message(msg));
+    Res_parser.next(undefined, p);
+  }
+  var token = p.token;
+  if (!Res_grammar.isPatternStart(token)) {
+    return ;
+  }
+  var pat = parsePattern(undefined, undefined, p);
+  var match$1 = p.token;
+  if (match$1 !== 24) {
+    return pat;
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = pat.ppat_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.constraint_(loc, undefined, pat, typ);
+}
+
+function parseTypeAlias(p, typ) {
+  var match = p.token;
+  if (match !== 3) {
+    return typ;
+  }
+  Res_parser.next(undefined, p);
+  Res_parser.expect(undefined, /* SingleQuote */13, p);
+  var match$1 = parseLident(p);
+  return Ast_helper.Typ.alias({
+              loc_start: typ.ptyp_loc.loc_start,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, undefined, typ, match$1[0]);
+}
+
+function parseFieldDeclarationRegion(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var mut = Res_parser.optional(p, /* Mutable */62) ? /* Mutable */1 : /* Immutable */0;
+  var match = p.token;
+  if (typeof match === "number") {
+    return ;
+  }
+  if (match.TAG !== /* Lident */4) {
+    return ;
+  }
+  var match$1 = parseLident(p);
+  var name = $$Location.mkloc(match$1[0], match$1[1]);
+  var match$2 = p.token;
+  var typ = match$2 === 24 ? (Res_parser.next(undefined, p), parsePolyTypeExpr(p)) : Ast_helper.Typ.constr(name.loc, undefined, {
+          txt: {
+            TAG: /* Lident */0,
+            _0: name.txt
+          },
+          loc: name.loc
+        }, /* [] */0);
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Type.field(loc, attrs, undefined, mut, name, typ);
+}
+
+function parseFieldDeclaration(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var mut = Res_parser.optional(p, /* Mutable */62) ? /* Mutable */1 : /* Immutable */0;
+  var match = parseLident(p);
+  var name = $$Location.mkloc(match[0], match[1]);
+  var match$1 = p.token;
+  var typ = match$1 === 24 ? (Res_parser.next(undefined, p), parsePolyTypeExpr(p)) : Ast_helper.Typ.constr(name.loc, undefined, {
+          txt: {
+            TAG: /* Lident */0,
+            _0: name.txt
+          },
+          loc: name.loc
+        }, /* [] */0);
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Type.field(loc, attrs, undefined, mut, name, typ);
+}
+
+function parseArrowTypeRest(es6Arrow, startPos, typ, p) {
+  var token = p.token;
+  if (typeof token !== "number") {
+    return typ;
+  }
+  if (!(token === 58 || token === 57)) {
+    return typ;
+  }
+  if (es6Arrow !== true) {
+    return typ;
+  }
+  if (token === /* MinusGreater */58) {
+    Res_parser.expect(undefined, /* EqualGreater */57, p);
+  }
+  Res_parser.next(undefined, p);
+  var returnType = parseTypExpr(undefined, undefined, false, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Typ.arrow(loc, undefined, /* Nolabel */0, typ, returnType);
+}
+
+function parseStringFieldDeclaration(p) {
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var name = p.token;
+  if (typeof name === "number") {
+    if (name !== /* DotDotDot */6) {
+      return ;
+    }
+    Res_parser.next(undefined, p);
+    var typ = parseTypExpr(undefined, undefined, undefined, p);
+    return {
+            TAG: /* Oinherit */1,
+            _0: typ
+          };
+  } else {
+    switch (name.TAG | 0) {
+      case /* String */3 :
+          var nameStartPos = p.startPos;
+          var nameEndPos = p.endPos;
+          Res_parser.next(undefined, p);
+          var fieldName = $$Location.mkloc(name._0, {
+                loc_start: nameStartPos,
+                loc_end: nameEndPos,
+                loc_ghost: false
+              });
+          Res_parser.expect(/* TypeExpression */20, /* Colon */24, p);
+          var typ$1 = parsePolyTypeExpr(p);
+          return {
+                  TAG: /* Otag */0,
+                  _0: fieldName,
+                  _1: attrs,
+                  _2: typ$1
+                };
+      case /* Lident */4 :
+          var name$1 = name._0;
+          var nameLoc_loc_start = p.startPos;
+          var nameLoc_loc_end = p.endPos;
+          var nameLoc = {
+            loc_start: nameLoc_loc_start,
+            loc_end: nameLoc_loc_end,
+            loc_ghost: false
+          };
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.message(objectQuotedFieldName(name$1)));
+          Res_parser.next(undefined, p);
+          var fieldName$1 = $$Location.mkloc(name$1, nameLoc);
+          Res_parser.expect(/* TypeExpression */20, /* Colon */24, p);
+          var typ$2 = parsePolyTypeExpr(p);
+          return {
+                  TAG: /* Otag */0,
+                  _0: fieldName$1,
+                  _1: attrs,
+                  _2: typ$2
+                };
+      default:
+        return ;
+    }
+  }
+}
+
+function parseTagSpec(p) {
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (match === 44) {
+    return parsePolymorphicVariantTypeSpecHash(attrs, false, p);
+  }
+  var typ = parseTypExpr(attrs, undefined, undefined, p);
+  return {
+          TAG: /* Rinherit */1,
+          _0: typ
+        };
+}
+
+function parseTagSpecFulls(p) {
+  var match = p.token;
+  if (typeof match !== "number") {
+    return /* [] */0;
+  }
+  if (!(match > 41 || match < 21)) {
+    return /* [] */0;
+  }
+  if (match !== 17) {
+    return /* [] */0;
+  }
+  Res_parser.next(undefined, p);
+  var rowField = parseTagSpecFull(p);
+  return {
+          hd: rowField,
+          tl: parseTagSpecFulls(p)
+        };
+}
+
+function parseTagSpecFull(p) {
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (match === 44) {
+    return parsePolymorphicVariantTypeSpecHash(attrs, true, p);
+  }
+  var typ = parseTypExpr(attrs, undefined, undefined, p);
+  return {
+          TAG: /* Rinherit */1,
+          _0: typ
+        };
+}
+
+function parseTagSpecFirst(p) {
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match !== 17) {
+      if (match === 44) {
+        return {
+                hd: parsePolymorphicVariantTypeSpecHash(attrs, false, p),
+                tl: /* [] */0
+              };
+      }
+      
+    } else {
+      Res_parser.next(undefined, p);
+      return {
+              hd: parseTagSpec(p),
+              tl: /* [] */0
+            };
+    }
+  }
+  var typ = parseTypExpr(attrs, undefined, undefined, p);
+  var match$1 = p.token;
+  if (match$1 === 21) {
+    return {
+            hd: {
+              TAG: /* Rinherit */1,
+              _0: typ
+            },
+            tl: /* [] */0
+          };
+  } else {
+    Res_parser.expect(undefined, /* Bar */17, p);
+    return {
+            hd: {
+              TAG: /* Rinherit */1,
+              _0: typ
+            },
+            tl: {
+              hd: parseTagSpec(p),
+              tl: /* [] */0
+            }
+          };
+  }
+}
+
+function parseTagSpecs(p) {
+  var match = p.token;
+  if (match !== 17) {
+    return /* [] */0;
+  }
+  Res_parser.next(undefined, p);
+  var rowField = parseTagSpec(p);
+  return {
+          hd: rowField,
+          tl: parseTagSpecs(p)
+        };
+}
+
+function parseTagNames(p) {
+  if (p.token === /* GreaterThan */41) {
+    Res_parser.next(undefined, p);
+    return parseRegion(p, /* TagNames */57, parseTagName);
+  } else {
+    return /* [] */0;
+  }
+}
+
+function parseRecordRow(p) {
+  var match = p.token;
+  if (match === 6) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message(recordExprSpread));
+    Res_parser.next(undefined, p);
+  }
+  var match$1 = p.token;
+  if (typeof match$1 === "number") {
+    return ;
+  }
+  switch (match$1.TAG | 0) {
+    case /* Lident */4 :
+    case /* Uident */5 :
+        break;
+    default:
+      return ;
+  }
+  var startToken = p.token;
+  var field = parseValuePath(p);
+  var match$2 = p.token;
+  if (match$2 === 24) {
+    Res_parser.next(undefined, p);
+    var fieldExpr = parseExpr(undefined, p);
+    return [
+            field,
+            fieldExpr
+          ];
+  }
+  var value = Ast_helper.Exp.ident(field.loc, undefined, field);
+  var value$1;
+  value$1 = typeof startToken === "number" || startToken.TAG !== /* Uident */5 ? value : removeModuleNameFromPunnedFieldValue(value);
+  return [
+          field,
+          value$1
+        ];
+}
+
+function parsePolyTypeExpr(p) {
+  var startPos = p.startPos;
+  var match = p.token;
+  if (match !== 13) {
+    return parseTypExpr(undefined, undefined, undefined, p);
+  }
+  var vars = parseTypeVarList(p);
+  if (vars) {
+    var _v1 = vars.hd;
+    if (vars.tl) {
+      Res_parser.expect(undefined, /* Dot */4, p);
+      var typ = parseTypExpr(undefined, undefined, undefined, p);
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: startPos,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      return Ast_helper.Typ.poly(loc, undefined, vars, typ);
+    }
+    var match$1 = p.token;
+    if (typeof match$1 === "number") {
+      if (match$1 !== 4) {
+        if (match$1 === 57) {
+          Res_parser.next(undefined, p);
+          var typ$1 = Ast_helper.Typ.$$var(_v1.loc, undefined, _v1.txt);
+          var returnType = parseTypExpr(undefined, undefined, false, p);
+          var loc_loc_start = typ$1.ptyp_loc.loc_start;
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$1 = {
+            loc_start: loc_loc_start,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          return Ast_helper.Typ.arrow(loc$1, undefined, /* Nolabel */0, typ$1, returnType);
+        }
+        
+      } else {
+        Res_parser.next(undefined, p);
+        var typ$2 = parseTypExpr(undefined, undefined, undefined, p);
+        var loc_loc_end$2 = p.prevEndPos;
+        var loc$2 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$2,
+          loc_ghost: false
+        };
+        return Ast_helper.Typ.poly(loc$2, undefined, vars, typ$2);
+      }
+    }
+    return Ast_helper.Typ.$$var(_v1.loc, undefined, _v1.txt);
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_core.res",
+          4299,
+          11
+        ],
+        Error: new Error()
+      };
+}
+
+function overParseConstrainedOrCoercedOrArrowExpression(p, expr) {
+  var match = p.token;
+  if (typeof match !== "number") {
+    return expr;
+  }
+  if (match !== 24) {
+    if (match !== 40) {
+      return expr;
+    } else {
+      return parseCoercedExpr(expr, p);
+    }
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, false, undefined, p);
+  var match$1 = p.token;
+  if (match$1 === 57) {
+    Res_parser.next(undefined, p);
+    var body = parseExpr(undefined, p);
+    var longident = expr.pexp_desc;
+    var pat;
+    var exit = 0;
+    if (typeof longident === "number" || longident.TAG !== /* Pexp_ident */0) {
+      exit = 1;
+    } else {
+      var longident$1 = longident._0;
+      pat = Ast_helper.Pat.$$var(expr.pexp_loc, undefined, $$Location.mkloc($$String.concat(".", Longident.flatten(longident$1.txt)), longident$1.loc));
+    }
+    if (exit === 1) {
+      pat = Ast_helper.Pat.$$var(expr.pexp_loc, undefined, $$Location.mkloc("pattern", expr.pexp_loc));
+    }
+    var arrow1 = Ast_helper.Exp.fun_({
+          loc_start: expr.pexp_loc.loc_start,
+          loc_end: body.pexp_loc.loc_end,
+          loc_ghost: false
+        }, undefined, /* Nolabel */0, undefined, pat, Ast_helper.Exp.constraint_(undefined, undefined, body, typ));
+    var arrow2 = Ast_helper.Exp.fun_({
+          loc_start: expr.pexp_loc.loc_start,
+          loc_end: body.pexp_loc.loc_end,
+          loc_ghost: false
+        }, undefined, /* Nolabel */0, undefined, Ast_helper.Pat.constraint_(undefined, undefined, pat, typ), body);
+    var msg = Res_doc.toString(80, Res_doc.breakableGroup(true, Res_doc.concat({
+                  hd: Res_doc.text("Did you mean to annotate the parameter type or the return type?"),
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.line,
+                              tl: {
+                                hd: Res_doc.text("1) "),
+                                tl: {
+                                  hd: Res_printer.printExpression(arrow1, Res_comments_table.empty),
+                                  tl: {
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: Res_doc.text("2) "),
+                                      tl: {
+                                        hd: Res_printer.printExpression(arrow2, Res_comments_table.empty),
+                                        tl: /* [] */0
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                })));
+    Res_parser.err(expr.pexp_loc.loc_start, body.pexp_loc.loc_end, p, Res_diagnostics.message(msg));
+    return arrow1;
+  }
+  var loc_loc_start = expr.pexp_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var expr$1 = Ast_helper.Exp.constraint_(loc, undefined, expr, typ);
+  Res_parser.err(expr$1.pexp_loc.loc_start, typ.ptyp_loc.loc_end, p, Res_diagnostics.message(Res_doc.toString(80, Res_doc.breakableGroup(true, Res_doc.concat({
+                        hd: Res_doc.text("Expressions with type constraints need to be wrapped in parens:"),
+                        tl: {
+                          hd: Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: Res_printer.addParens(Res_printer.printExpression(expr$1, Res_comments_table.empty)),
+                                      tl: /* [] */0
+                                    }
+                                  })),
+                          tl: /* [] */0
+                        }
+                      })))));
+  return expr$1;
+}
+
+function parseExpr(contextOpt, p) {
+  var context = contextOpt !== undefined ? contextOpt : /* OrdinaryExpr */0;
+  var expr = parseOperandExpr(context, p);
+  var expr$1 = parseBinaryExpr(context, expr, p, 1);
+  return parseTernaryExpr(expr$1, p);
+}
+
+function parsePayload(p) {
+  var match = p.token;
+  if (match !== 18) {
+    return {
+            TAG: /* PStr */0,
+            _0: /* [] */0
+          };
+  }
+  if (p.startPos.pos_cnum !== p.prevEndPos.pos_cnum) {
+    return {
+            TAG: /* PStr */0,
+            _0: /* [] */0
+          };
+  }
+  Res_parser.leaveBreadcrumb(p, /* AttributePayload */56);
+  Res_parser.next(undefined, p);
+  var match$1 = p.token;
+  if (typeof match$1 === "number") {
+    if (match$1 !== 24) {
+      if (match$1 === 49) {
+        Res_parser.next(undefined, p);
+        var pattern = parsePattern(undefined, undefined, p);
+        var match$2 = p.token;
+        var expr;
+        var exit = 0;
+        if (typeof match$2 === "number" && !(match$2 !== 50 && match$2 !== 56)) {
+          exit = 2;
+        } else {
+          expr = undefined;
+        }
+        if (exit === 2) {
+          Res_parser.next(undefined, p);
+          expr = parseExpr(undefined, p);
+        }
+        Res_parser.expect(undefined, /* Rparen */19, p);
+        Res_parser.eatBreadcrumb(p);
+        return {
+                TAG: /* PPat */3,
+                _0: pattern,
+                _1: expr
+              };
+      }
+      
+    } else {
+      Res_parser.next(undefined, p);
+      var payload = Res_grammar.isSignatureItemStart(p.token) ? ({
+            TAG: /* PSig */1,
+            _0: parseDelimitedRegion(p, /* Signature */46, /* Rparen */19, parseSignatureItemRegion)
+          }) : ({
+            TAG: /* PTyp */2,
+            _0: parseTypExpr(undefined, undefined, undefined, p)
+          });
+      Res_parser.expect(undefined, /* Rparen */19, p);
+      Res_parser.eatBreadcrumb(p);
+      return payload;
+    }
+  }
+  var items = parseDelimitedRegion(p, /* Structure */48, /* Rparen */19, parseStructureItemRegion);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  Res_parser.eatBreadcrumb(p);
+  return {
+          TAG: /* PStr */0,
+          _0: items
+        };
+}
+
+function parseAttributeId(startPos, p) {
+  var loop = function (p, _acc) {
+    while(true) {
+      var acc = _acc;
+      var token = p.token;
+      var exit = 0;
+      if (typeof token === "number") {
+        exit = 2;
+      } else {
+        switch (token.TAG | 0) {
+          case /* Lident */4 :
+          case /* Uident */5 :
+              exit = 1;
+              break;
+          default:
+            exit = 2;
+        }
+      }
+      switch (exit) {
+        case 1 :
+            Res_parser.next(undefined, p);
+            var id = acc + token._0;
+            var match = p.token;
+            if (match !== 4) {
+              return id;
+            }
+            Res_parser.next(undefined, p);
+            _acc = id + ".";
+            continue ;
+        case 2 :
+            if (Res_token.isKeyword(token)) {
+              Res_parser.next(undefined, p);
+              var id$1 = acc + Res_token.toString(token);
+              var match$1 = p.token;
+              if (match$1 !== 4) {
+                return id$1;
+              }
+              Res_parser.next(undefined, p);
+              _acc = id$1 + ".";
+              continue ;
+            }
+            Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+            return acc;
+        
+      }
+    };
+  };
+  var id = loop(p, "");
+  var endPos = p.prevEndPos;
+  return $$Location.mkloc(id, {
+              loc_start: startPos,
+              loc_end: endPos,
+              loc_ghost: false
+            });
+}
+
+function parseConstrDeclArgs(p) {
+  var match = p.token;
+  var constrArgs;
+  if (match === 18) {
+    Res_parser.next(undefined, p);
+    var match$1 = p.token;
+    if (match$1 === 22) {
+      var lbrace = p.startPos;
+      Res_parser.next(undefined, p);
+      var startPos = p.startPos;
+      var match$2 = p.token;
+      var exit = 0;
+      if (typeof match$2 === "number") {
+        if (match$2 >= 6) {
+          if (match$2 >= 7) {
+            exit = 1;
+          } else {
+            var dotdotdotStart = p.startPos;
+            var dotdotdotEnd = p.endPos;
+            Res_parser.next(undefined, p);
+            var typ = parseTypExpr(undefined, undefined, undefined, p);
+            var match$3 = p.token;
+            if (match$3 === 23) {
+              Res_parser.err(dotdotdotStart, dotdotdotEnd, p, Res_diagnostics.message(sameTypeSpread));
+              Res_parser.next(undefined, p);
+            } else {
+              Res_parser.expect(undefined, /* Comma */25, p);
+            }
+            var match$4 = p.token;
+            if (typeof match$4 !== "number" && match$4.TAG === /* Lident */4) {
+              Res_parser.err(dotdotdotStart, dotdotdotEnd, p, Res_diagnostics.message(spreadInRecordDeclaration));
+            }
+            var fields_0 = {
+              TAG: /* Oinherit */1,
+              _0: typ
+            };
+            var fields_1 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+            var fields = {
+              hd: fields_0,
+              tl: fields_1
+            };
+            Res_parser.expect(undefined, /* Rbrace */23, p);
+            var loc_loc_end = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var typ$1 = parseTypeAlias(p, Ast_helper.Typ.object_(loc, undefined, fields, /* Closed */0));
+            var typ$2 = parseArrowTypeRest(true, startPos, typ$1, p);
+            Res_parser.optional(p, /* Comma */25);
+            var moreArgs = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+            Res_parser.expect(undefined, /* Rparen */19, p);
+            constrArgs = {
+              TAG: /* Pcstr_tuple */0,
+              _0: {
+                hd: typ$2,
+                tl: moreArgs
+              }
+            };
+          }
+        } else if (match$2 >= 4) {
+          var match$5 = p.token;
+          var closedFlag = typeof match$5 === "number" ? (
+              match$5 !== 4 ? (
+                  match$5 !== 5 ? /* Closed */0 : (Res_parser.next(undefined, p), /* Open */1)
+                ) : (Res_parser.next(undefined, p), /* Closed */0)
+            ) : /* Closed */0;
+          var fields$1 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$1 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          var typ$3 = Ast_helper.Typ.object_(loc$1, /* [] */0, fields$1, closedFlag);
+          Res_parser.optional(p, /* Comma */25);
+          var moreArgs$1 = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          constrArgs = {
+            TAG: /* Pcstr_tuple */0,
+            _0: {
+              hd: typ$3,
+              tl: moreArgs$1
+            }
+          };
+        } else {
+          exit = 1;
+        }
+      } else {
+        exit = 1;
+      }
+      if (exit === 1) {
+        var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+        var match$6 = p.token;
+        var exit$1 = 0;
+        if (typeof match$6 === "number" || match$6.TAG !== /* String */3) {
+          exit$1 = 2;
+        } else {
+          var fields$2;
+          if (attrs) {
+            Res_parser.leaveBreadcrumb(p, /* StringFieldDeclarations */37);
+            var field = parseStringFieldDeclaration(p);
+            var field$1;
+            if (field !== undefined) {
+              field$1 = field;
+            } else {
+              throw {
+                    RE_EXN_ID: "Assert_failure",
+                    _1: [
+                      "res_core.res",
+                      5016,
+                      24
+                    ],
+                    Error: new Error()
+                  };
+            }
+            var match$7 = p.token;
+            if (typeof match$7 === "number") {
+              if (match$7 >= 24) {
+                if (match$7 >= 27) {
+                  Res_parser.expect(undefined, /* Comma */25, p);
+                } else {
+                  switch (match$7) {
+                    case /* Colon */24 :
+                        Res_parser.expect(undefined, /* Comma */25, p);
+                        break;
+                    case /* Comma */25 :
+                        Res_parser.next(undefined, p);
+                        break;
+                    case /* Eof */26 :
+                        break;
+                    
+                  }
+                }
+              } else if (match$7 >= 23) {
+                
+              } else {
+                Res_parser.expect(undefined, /* Comma */25, p);
+              }
+            } else {
+              Res_parser.expect(undefined, /* Comma */25, p);
+            }
+            Res_parser.eatBreadcrumb(p);
+            var first;
+            first = field$1.TAG === /* Otag */0 ? ({
+                  TAG: /* Otag */0,
+                  _0: field$1._0,
+                  _1: attrs,
+                  _2: field$1._2
+                }) : ({
+                  TAG: /* Oinherit */1,
+                  _0: field$1._0
+                });
+            fields$2 = {
+              hd: first,
+              tl: parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration)
+            };
+          } else {
+            fields$2 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+          }
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          var loc_loc_end$2 = p.prevEndPos;
+          var loc$2 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$2,
+            loc_ghost: false
+          };
+          var typ$4 = parseTypeAlias(p, Ast_helper.Typ.object_(loc$2, /* [] */0, fields$2, /* Closed */0));
+          var typ$5 = parseArrowTypeRest(true, startPos, typ$4, p);
+          Res_parser.optional(p, /* Comma */25);
+          var moreArgs$2 = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          constrArgs = {
+            TAG: /* Pcstr_tuple */0,
+            _0: {
+              hd: typ$5,
+              tl: moreArgs$2
+            }
+          };
+        }
+        if (exit$1 === 2) {
+          var fields$3;
+          if (attrs) {
+            var field$2 = parseFieldDeclaration(p);
+            Res_parser.expect(undefined, /* Comma */25, p);
+            var first_pld_name = field$2.pld_name;
+            var first_pld_mutable = field$2.pld_mutable;
+            var first_pld_type = field$2.pld_type;
+            var first_pld_loc = field$2.pld_loc;
+            var first$1 = {
+              pld_name: first_pld_name,
+              pld_mutable: first_pld_mutable,
+              pld_type: first_pld_type,
+              pld_loc: first_pld_loc,
+              pld_attributes: attrs
+            };
+            fields$3 = {
+              hd: first$1,
+              tl: parseCommaDelimitedRegion(p, /* FieldDeclarations */38, /* Rbrace */23, parseFieldDeclarationRegion)
+            };
+          } else {
+            fields$3 = parseCommaDelimitedRegion(p, /* FieldDeclarations */38, /* Rbrace */23, parseFieldDeclarationRegion);
+          }
+          if (fields$3) {
+            
+          } else {
+            Res_parser.err(lbrace, undefined, p, Res_diagnostics.message("An inline record declaration needs at least one field"));
+          }
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          Res_parser.optional(p, /* Comma */25);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          constrArgs = {
+            TAG: /* Pcstr_record */1,
+            _0: fields$3
+          };
+        }
+        
+      }
+      
+    } else {
+      var args = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+      Res_parser.expect(undefined, /* Rparen */19, p);
+      constrArgs = {
+        TAG: /* Pcstr_tuple */0,
+        _0: args
+      };
+    }
+  } else {
+    constrArgs = {
+      TAG: /* Pcstr_tuple */0,
+      _0: /* [] */0
+    };
+  }
+  var match$8 = p.token;
+  var res = match$8 === 24 ? (Res_parser.next(undefined, p), parseTypExpr(undefined, undefined, undefined, p)) : undefined;
+  return [
+          constrArgs,
+          res
+        ];
+}
+
+function parsePackageConstraint(p) {
+  var match = p.token;
+  if (match !== 10) {
+    return ;
+  }
+  Res_parser.next(undefined, p);
+  Res_parser.expect(undefined, /* Typ */60, p);
+  var typeConstr = parseValuePath(p);
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  return [
+          typeConstr,
+          typ
+        ];
+}
+
+function parseConstrDef(parseAttrs, p) {
+  var attrs = parseAttrs ? parseRegion(p, /* Attribute */50, parseAttribute) : /* [] */0;
+  var name = p.token;
+  var name$1;
+  var exit = 0;
+  if (typeof name === "number" || name.TAG !== /* Uident */5) {
+    exit = 1;
+  } else {
+    var loc_loc_start = p.startPos;
+    var loc_loc_end = p.endPos;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    name$1 = $$Location.mkloc(name._0, loc);
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(name));
+    name$1 = $$Location.mknoloc("_");
+  }
+  var match = p.token;
+  var kind;
+  if (typeof match === "number") {
+    switch (match) {
+      case /* Equal */14 :
+          Res_parser.next(undefined, p);
+          var longident = parseModuleLongIdent(false, p);
+          kind = {
+            TAG: /* Pext_rebind */1,
+            _0: longident
+          };
+          break;
+      case /* Lparen */18 :
+          var match$1 = parseConstrDeclArgs(p);
+          kind = {
+            TAG: /* Pext_decl */0,
+            _0: match$1[0],
+            _1: match$1[1]
+          };
+          break;
+      case /* EqualEqual */15 :
+      case /* EqualEqualEqual */16 :
+      case /* Bar */17 :
+      case /* Rparen */19 :
+      case /* Lbracket */20 :
+      case /* Rbracket */21 :
+      case /* Lbrace */22 :
+      case /* Rbrace */23 :
+          kind = {
+            TAG: /* Pext_decl */0,
+            _0: {
+              TAG: /* Pcstr_tuple */0,
+              _0: /* [] */0
+            },
+            _1: undefined
+          };
+          break;
+      case /* Colon */24 :
+          Res_parser.next(undefined, p);
+          var typ = parseTypExpr(undefined, undefined, undefined, p);
+          kind = {
+            TAG: /* Pext_decl */0,
+            _0: {
+              TAG: /* Pcstr_tuple */0,
+              _0: /* [] */0
+            },
+            _1: typ
+          };
+          break;
+      default:
+        kind = {
+          TAG: /* Pext_decl */0,
+          _0: {
+            TAG: /* Pcstr_tuple */0,
+            _0: /* [] */0
+          },
+          _1: undefined
+        };
+    }
+  } else {
+    kind = {
+      TAG: /* Pext_decl */0,
+      _0: {
+        TAG: /* Pcstr_tuple */0,
+        _0: /* [] */0
+      },
+      _1: undefined
+    };
+  }
+  return [
+          attrs,
+          name$1,
+          kind
+        ];
+}
+
+function parseTypeVarList(p) {
+  var _vars = /* [] */0;
+  while(true) {
+    var vars = _vars;
+    var match = p.token;
+    if (match !== 13) {
+      return List.rev(vars);
+    }
+    Res_parser.next(undefined, p);
+    var match$1 = parseLident(p);
+    var $$var = $$Location.mkloc(match$1[0], match$1[1]);
+    _vars = {
+      hd: $$var,
+      tl: vars
+    };
+    continue ;
+  };
+}
+
+function parseModuleExpr(p) {
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var modExpr = isEs6ArrowFunctor(p) ? parseFunctorModuleExpr(p) : parsePrimaryModExpr(p);
+  return {
+          pmod_desc: modExpr.pmod_desc,
+          pmod_loc: modExpr.pmod_loc,
+          pmod_attributes: List.concat({
+                hd: modExpr.pmod_attributes,
+                tl: {
+                  hd: attrs,
+                  tl: /* [] */0
+                }
+              })
+        };
+}
+
+function parseTypeConstructorDeclaration(startPos, p) {
+  Res_parser.leaveBreadcrumb(p, /* ConstructorDeclaration */35);
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var uident = p.token;
+  if (typeof uident !== "number" && uident.TAG === /* Uident */5) {
+    var uidentLoc_loc_start = p.startPos;
+    var uidentLoc_loc_end = p.endPos;
+    var uidentLoc = {
+      loc_start: uidentLoc_loc_start,
+      loc_end: uidentLoc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    var match = parseConstrDeclArgs(p);
+    Res_parser.eatBreadcrumb(p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return Ast_helper.Type.constructor(loc, attrs, undefined, match[0], match[1], $$Location.mkloc(uident._0, uidentLoc));
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(uident));
+  return Ast_helper.Type.constructor(undefined, undefined, undefined, undefined, undefined, $$Location.mknoloc("_"));
+}
+
+function parseConstrainedPatternRegion(p) {
+  var token = p.token;
+  if (Res_grammar.isPatternStart(token)) {
+    return parseConstrainedPattern(p);
+  }
+  
+}
+
+function parseExprBlock(first, p) {
+  Res_parser.leaveBreadcrumb(p, /* ExprBlock */10);
+  var item = first !== undefined ? first : parseExprBlockItem(p);
+  parseNewlineOrSemicolonExprBlock(p);
+  var blockExpr;
+  if (Res_grammar.isBlockExprStart(p.token)) {
+    var next = parseExprBlockItem(p);
+    var init = item.pexp_loc;
+    var loc_loc_start = init.loc_start;
+    var loc_loc_end = next.pexp_loc.loc_end;
+    var loc_loc_ghost = init.loc_ghost;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: loc_loc_ghost
+    };
+    blockExpr = Ast_helper.Exp.sequence(loc, undefined, item, next);
+  } else {
+    blockExpr = item;
+  }
+  Res_parser.eatBreadcrumb(p);
+  return overParseConstrainedOrCoercedOrArrowExpression(p, blockExpr);
+}
+
+function parsePatternRegion(p) {
+  var token = p.token;
+  if (token === 6) {
+    Res_parser.next(undefined, p);
+    return [
+            true,
+            parseConstrainedPattern(p)
+          ];
+  } else if (Res_grammar.isPatternStart(token)) {
+    return [
+            false,
+            parseConstrainedPattern(p)
+          ];
+  } else {
+    return ;
+  }
+}
+
+function parseSpreadExprRegion(p) {
+  var token = p.token;
+  if (token !== 6) {
+    if (Res_grammar.isExprStart(token)) {
+      return [
+              false,
+              parseConstrainedOrCoercedExpr(p)
+            ];
+    } else {
+      return ;
+    }
+  }
+  Res_parser.next(undefined, p);
+  var expr = parseConstrainedOrCoercedExpr(p);
+  return [
+          true,
+          expr
+        ];
+}
+
+function parsePackageType(startPos, attrs, p) {
+  var modTypePath = parseModuleLongIdent(true, p);
+  var match = p.token;
+  if (typeof match !== "number" && match.TAG === /* Lident */4 && match._0 === "with") {
+    Res_parser.next(undefined, p);
+    var constraints = parsePackageConstraints(p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return Ast_helper.Typ.$$package(loc, attrs, modTypePath, constraints);
+  }
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Typ.$$package(loc$1, attrs, modTypePath, /* [] */0);
+}
+
+function parseConstrainedModExpr(p) {
+  var modExpr = parseModuleExpr(p);
+  var match = p.token;
+  if (match !== 24) {
+    return modExpr;
+  }
+  Res_parser.next(undefined, p);
+  var modType = parseModuleType(undefined, undefined, p);
+  var loc_loc_start = modExpr.pmod_loc.loc_start;
+  var loc_loc_end = modType.pmty_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Mod.constraint_(loc, undefined, modExpr, modType);
+}
+
+function parsePolymorphicVariantTypeArgs(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var args = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  if (args) {
+    var typ = args.hd;
+    var tmp = typ.ptyp_desc;
+    if (typeof tmp === "number") {
+      if (!args.tl) {
+        return typ;
+      }
+      
+    } else if (tmp.TAG === /* Ptyp_tuple */2) {
+      if (!args.tl) {
+        if (p.mode === /* ParseForTypeChecker */0) {
+          return typ;
+        } else {
+          return Ast_helper.Typ.tuple(loc, /* [] */0, args);
+        }
+      }
+      
+    } else if (!args.tl) {
+      return typ;
+    }
+    
+  }
+  return Ast_helper.Typ.tuple(loc, /* [] */0, args);
+}
+
+function parsePolymorphicVariantTypeSpecHash(attrs, full, p) {
+  var startPos = p.startPos;
+  var match = parseHashIdent(startPos, p);
+  var loop = function (p) {
+    var match = p.token;
+    if (match !== 69) {
+      return /* [] */0;
+    }
+    if (!full) {
+      return /* [] */0;
+    }
+    Res_parser.next(undefined, p);
+    var rowField = parsePolymorphicVariantTypeArgs(p);
+    return {
+            hd: rowField,
+            tl: loop(p)
+          };
+  };
+  var match$1 = p.token;
+  var match$2 = typeof match$1 === "number" ? (
+      match$1 !== 18 ? (
+          match$1 !== 69 || !full ? [
+              /* [] */0,
+              true
+            ] : (Res_parser.next(undefined, p), [
+                {
+                  hd: parsePolymorphicVariantTypeArgs(p),
+                  tl: /* [] */0
+                },
+                true
+              ])
+        ) : [
+          {
+            hd: parsePolymorphicVariantTypeArgs(p),
+            tl: /* [] */0
+          },
+          false
+        ]
+    ) : [
+      /* [] */0,
+      true
+    ];
+  var tuples = Pervasives.$at(match$2[0], loop(p));
+  return {
+          TAG: /* Rtag */0,
+          _0: $$Location.mkloc(match[0], match[1]),
+          _1: attrs,
+          _2: match$2[1],
+          _3: tuples
+        };
+}
+
+function parseTypExprRegion(p) {
+  if (Res_grammar.isTypExprStart(p.token)) {
+    return parseTypExpr(undefined, undefined, undefined, p);
+  }
+  
+}
+
+function parseModuleBindingBody(p) {
+  var match = p.token;
+  var returnModType = match === 24 ? (Res_parser.next(undefined, p), parseModuleType(undefined, undefined, p)) : undefined;
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var modExpr = parseModuleExpr(p);
+  if (returnModType !== undefined) {
+    return Ast_helper.Mod.constraint_({
+                loc_start: returnModType.pmty_loc.loc_start,
+                loc_end: modExpr.pmod_loc.loc_end,
+                loc_ghost: false
+              }, undefined, modExpr, returnModType);
+  } else {
+    return modExpr;
+  }
+}
+
+function parsePrimaryExpr(operand, noCallOpt, p) {
+  var noCall = noCallOpt !== undefined ? noCallOpt : false;
+  var startPos = operand.pexp_loc.loc_start;
+  var _expr = operand;
+  while(true) {
+    var expr = _expr;
+    var match = p.token;
+    if (typeof match !== "number") {
+      return expr;
+    }
+    if (match !== 4) {
+      if (match >= 21) {
+        if (match !== 80) {
+          return expr;
+        }
+        if (!(noCall === false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum)) {
+          return expr;
+        }
+        var match$1 = expr.pexp_desc;
+        if (typeof match$1 !== "number" && match$1.TAG === /* Pexp_ident */0) {
+          var ident = match$1._0.txt;
+          switch (ident.TAG | 0) {
+            case /* Lident */0 :
+                return parseTemplateExpr(ident._0, p);
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                break;
+            
+          }
+        }
+        Res_parser.err(expr.pexp_loc.loc_start, expr.pexp_loc.loc_end, p, Res_diagnostics.message("Tagged template literals are currently restricted to names like: json`null`."));
+        return parseTemplateExpr(undefined, p);
+      }
+      if (match < 18) {
+        return expr;
+      }
+      switch (match) {
+        case /* Lparen */18 :
+            if (!(noCall === false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum)) {
+              return expr;
+            }
+            _expr = parseCallExpr(p, expr);
+            continue ;
+        case /* Rparen */19 :
+            return expr;
+        case /* Lbracket */20 :
+            if (noCall === false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+              return parseBracketAccess(p, expr, startPos);
+            } else {
+              return expr;
+            }
+        
+      }
+    } else {
+      Res_parser.next(undefined, p);
+      var lident = parseValuePathAfterDot(p);
+      var match$2 = p.token;
+      if (match$2 === 14 && noCall === false) {
+        Res_parser.leaveBreadcrumb(p, /* ExprSetField */9);
+        Res_parser.next(undefined, p);
+        var targetExpr = parseExpr(undefined, p);
+        var loc_loc_end = p.prevEndPos;
+        var loc = {
+          loc_start: startPos,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        var setfield = Ast_helper.Exp.setfield(loc, undefined, expr, lident, targetExpr);
+        Res_parser.eatBreadcrumb(p);
+        return setfield;
+      }
+      var endPos = p.prevEndPos;
+      var loc$1 = {
+        loc_start: startPos,
+        loc_end: endPos,
+        loc_ghost: false
+      };
+      _expr = Ast_helper.Exp.field(loc$1, undefined, expr, lident);
+      continue ;
+    }
+  };
+}
+
+function parseExceptionDef(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Exception */27, p);
+  var match = parseConstrDef(false, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Te.constructor(loc, attrs, undefined, undefined, match[1], match[2]);
+}
+
+function parseNewlineOrSemicolonExprBlock(p) {
+  var token = p.token;
+  if (token === 8) {
+    return Res_parser.next(undefined, p);
+  } else if (Res_grammar.isBlockExprStart(token) && p.prevEndPos.pos_lnum >= p.startPos.pos_lnum) {
+    return Res_parser.err(p.prevEndPos, p.endPos, p, Res_diagnostics.message("consecutive expressions on a line must be separated by ';' or a newline"));
+  } else {
+    return ;
+  }
+}
+
+function parseLetBindings(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.optional(p, /* Let */9);
+  var recFlag = Res_parser.optional(p, /* Rec */11) ? /* Recursive */1 : /* Nonrecursive */0;
+  var first = parseLetBindingBody(startPos, attrs, p);
+  var loop = function (p, _bindings) {
+    while(true) {
+      var bindings = _bindings;
+      var startPos = p.startPos;
+      var attrs = parseAttributesAndBinding(p);
+      var match = p.token;
+      if (match !== 10) {
+        return List.rev(bindings);
+      }
+      Res_parser.next(undefined, p);
+      var match$1 = p.token;
+      var attrs$1;
+      if (typeof match$1 === "number" && match$1 >= 84) {
+        var exportLoc_loc_start = p.startPos;
+        var exportLoc_loc_end = p.endPos;
+        var exportLoc = {
+          loc_start: exportLoc_loc_start,
+          loc_end: exportLoc_loc_end,
+          loc_ghost: false
+        };
+        Res_parser.next(undefined, p);
+        var genTypeAttr_0 = $$Location.mkloc("genType", exportLoc);
+        var genTypeAttr_1 = {
+          TAG: /* PStr */0,
+          _0: /* [] */0
+        };
+        var genTypeAttr = [
+          genTypeAttr_0,
+          genTypeAttr_1
+        ];
+        attrs$1 = {
+          hd: genTypeAttr,
+          tl: attrs
+        };
+      } else {
+        attrs$1 = attrs;
+      }
+      Res_parser.optional(p, /* Let */9);
+      var letBinding = parseLetBindingBody(startPos, attrs$1, p);
+      _bindings = {
+        hd: letBinding,
+        tl: bindings
+      };
+      continue ;
+    };
+  };
+  return [
+          recFlag,
+          loop(p, {
+                hd: first,
+                tl: /* [] */0
+              })
+        ];
+}
+
+function parseTernaryExpr(leftOperand, p) {
+  var match = p.token;
+  if (match !== 49) {
+    return leftOperand;
+  }
+  Res_parser.leaveBreadcrumb(p, /* Ternary */2);
+  Res_parser.next(undefined, p);
+  var trueBranch = parseExpr(/* TernaryTrueBranchExpr */1, p);
+  Res_parser.expect(undefined, /* Colon */24, p);
+  var falseBranch = parseExpr(undefined, p);
+  Res_parser.eatBreadcrumb(p);
+  var init = leftOperand.pexp_loc;
+  var loc_loc_start = leftOperand.pexp_loc.loc_start;
+  var loc_loc_end = falseBranch.pexp_loc.loc_end;
+  var loc_loc_ghost = init.loc_ghost;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: loc_loc_ghost
+  };
+  return Ast_helper.Exp.ifthenelse(loc, {
+              hd: ternaryAttr,
+              tl: /* [] */0
+            }, leftOperand, trueBranch, falseBranch);
+}
+
+function parseFirstClassModuleExpr(startPos, p) {
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var modExpr = parseModuleExpr(p);
+  var modEndLoc = p.prevEndPos;
+  var match = p.token;
+  if (match === 24) {
+    var colonStart = p.startPos;
+    Res_parser.next(undefined, p);
+    var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+    var packageType = parsePackageType(colonStart, attrs, p);
+    Res_parser.expect(undefined, /* Rparen */19, p);
+    var loc = {
+      loc_start: startPos,
+      loc_end: modEndLoc,
+      loc_ghost: false
+    };
+    var firstClassModule = Ast_helper.Exp.pack(loc, undefined, modExpr);
+    var loc_loc_end = p.prevEndPos;
+    var loc$1 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return Ast_helper.Exp.constraint_(loc$1, undefined, firstClassModule, packageType);
+  }
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$2 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.pack(loc$2, undefined, modExpr);
+}
+
+function parseBinaryExpr(contextOpt, a, p, prec) {
+  var context = contextOpt !== undefined ? contextOpt : /* OrdinaryExpr */0;
+  var a$1 = a !== undefined ? a : parseOperandExpr(context, p);
+  var _a = a$1;
+  while(true) {
+    var a$2 = _a;
+    var token = p.token;
+    var tokenPrec;
+    var exit = 0;
+    if (typeof token === "number") {
+      if (token >= 36) {
+        if (token !== 42) {
+          tokenPrec = Res_token.precedence(token);
+        } else {
+          exit = 1;
+        }
+      } else if (token >= 34) {
+        exit = 1;
+      } else {
+        tokenPrec = Res_token.precedence(token);
+      }
+    } else {
+      tokenPrec = Res_token.precedence(token);
+    }
+    if (exit === 1) {
+      tokenPrec = !Res_scanner.isBinaryOp(p.scanner.src, p.startPos.pos_cnum, p.endPos.pos_cnum) && p.startPos.pos_lnum > p.prevEndPos.pos_lnum ? -1 : Res_token.precedence(token);
+    }
+    if (tokenPrec < prec) {
+      return a$2;
+    }
+    Res_parser.leaveBreadcrumb(p, /* ExprBinaryAfterOp */{
+          _0: token
+        });
+    var startPos = p.startPos;
+    Res_parser.next(undefined, p);
+    var endPos = p.prevEndPos;
+    var b = parseBinaryExpr(context, undefined, p, tokenPrec + 1 | 0);
+    var loc_loc_start = a$2.pexp_loc.loc_start;
+    var loc_loc_end = b.pexp_loc.loc_end;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    var expr = Ast_helper.Exp.apply(loc, undefined, makeInfixOperator(p, token, startPos, endPos), {
+          hd: [
+            /* Nolabel */0,
+            a$2
+          ],
+          tl: {
+            hd: [
+              /* Nolabel */0,
+              b
+            ],
+            tl: /* [] */0
+          }
+        });
+    Res_parser.eatBreadcrumb(p);
+    _a = expr;
+    continue ;
+  };
+}
+
+function parseOperandExpr(context, p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  var expr;
+  var exit = 0;
+  if (typeof match === "number") {
+    if (match >= 56) {
+      if (match !== 82) {
+        exit = 1;
+      } else {
+        expr = parseTryExpression(p);
+      }
+    } else if (match >= 46) {
+      switch (match) {
+        case /* Assert */46 :
+            Res_parser.next(undefined, p);
+            var expr$1 = parseUnaryExpr(p);
+            var loc_loc_end = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            expr = Ast_helper.Exp.assert_(loc, undefined, expr$1);
+            break;
+        case /* Lazy */47 :
+            Res_parser.next(undefined, p);
+            var expr$2 = parseUnaryExpr(p);
+            var loc_loc_end$1 = p.prevEndPos;
+            var loc$1 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$1,
+              loc_ghost: false
+            };
+            expr = Ast_helper.Exp.lazy_(loc$1, undefined, expr$2);
+            break;
+        case /* If */50 :
+            expr = parseIfOrIfLetExpression(p);
+            break;
+        case /* For */52 :
+            expr = parseForExpression(p);
+            break;
+        case /* Tilde */48 :
+        case /* Question */49 :
+        case /* Else */51 :
+        case /* In */53 :
+            exit = 1;
+            break;
+        case /* While */54 :
+            expr = parseWhileExpression(p);
+            break;
+        case /* Switch */55 :
+            expr = parseSwitchExpression(p);
+            break;
+        
+      }
+    } else {
+      exit = 1;
+    }
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    expr = context !== /* WhenExpr */2 && isEs6ArrowExpression(context === /* TernaryTrueBranchExpr */1, p) ? parseEs6ArrowExpression(context, undefined, p) : parseUnaryExpr(p);
+  }
+  return {
+          pexp_desc: expr.pexp_desc,
+          pexp_loc: expr.pexp_loc,
+          pexp_attributes: List.concat({
+                hd: expr.pexp_attributes,
+                tl: {
+                  hd: attrs,
+                  tl: /* [] */0
+                }
+              })
+        };
+}
+
+function parseStandaloneAttribute(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* AtAt */76, p);
+  var attrId = parseAttributeId(startPos, p);
+  var payload = parsePayload(p);
+  return [
+          attrId,
+          payload
+        ];
+}
+
+function parseJsExport(attrs, p) {
+  var exportStart = p.startPos;
+  Res_parser.expect(undefined, /* Export */84, p);
+  var exportLoc_loc_end = p.prevEndPos;
+  var exportLoc = {
+    loc_start: exportStart,
+    loc_end: exportLoc_loc_end,
+    loc_ghost: false
+  };
+  var genTypeAttr_0 = $$Location.mkloc("genType", exportLoc);
+  var genTypeAttr_1 = {
+    TAG: /* PStr */0,
+    _0: /* [] */0
+  };
+  var genTypeAttr = [
+    genTypeAttr_0,
+    genTypeAttr_1
+  ];
+  var attrs$1 = {
+    hd: genTypeAttr,
+    tl: attrs
+  };
+  var match = p.token;
+  if (match === 60) {
+    var ext = parseTypeDefinitionOrExtension(attrs$1, p);
+    if (ext.TAG === /* TypeDef */0) {
+      return Ast_helper.Str.type_(undefined, ext.recFlag, ext.types);
+    } else {
+      return Ast_helper.Str.type_extension(undefined, ext._0);
+    }
+  }
+  var match$1 = parseLetBindings(attrs$1, p);
+  return Ast_helper.Str.value(undefined, match$1[0], match$1[1]);
+}
+
+function parseModuleOrModuleTypeImplOrPackExpr(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Module */65, p);
+  var match = p.token;
+  if (typeof match !== "number") {
+    return parseMaybeRecModuleBinding(attrs, startPos, p);
+  }
+  if (match !== 18) {
+    if (match !== 60) {
+      return parseMaybeRecModuleBinding(attrs, startPos, p);
+    } else {
+      return parseModuleTypeImpl(attrs, startPos, p);
+    }
+  }
+  var expr = parseFirstClassModuleExpr(startPos, p);
+  var a = parsePrimaryExpr(expr, undefined, p);
+  var expr$1 = parseBinaryExpr(undefined, a, p, 1);
+  var expr$2 = parseTernaryExpr(expr$1, p);
+  return Ast_helper.Str.$$eval(undefined, attrs, expr$2);
+}
+
+function parseExternalDef(attrs, startPos, p) {
+  Res_parser.leaveBreadcrumb(p, /* External */21);
+  Res_parser.expect(undefined, /* External */59, p);
+  var match = parseLident(p);
+  var name = $$Location.mkloc(match[0], match[1]);
+  Res_parser.expect(/* TypeExpression */20, /* Colon */24, p);
+  var typExpr = parseTypExpr(undefined, undefined, undefined, p);
+  var equalStart = p.startPos;
+  var equalEnd = p.endPos;
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var s = p.token;
+  var prim;
+  var exit = 0;
+  if (typeof s === "number" || s.TAG !== /* String */3) {
+    exit = 1;
+  } else {
+    Res_parser.next(undefined, p);
+    prim = {
+      hd: s._0,
+      tl: /* [] */0
+    };
+  }
+  if (exit === 1) {
+    Res_parser.err(equalStart, equalEnd, p, Res_diagnostics.message("An external requires the name of the JS value you're referring to, like \"" + (name.txt + "\".")));
+    prim = /* [] */0;
+  }
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var vb = Ast_helper.Val.mk(loc, attrs, undefined, prim, name, typExpr);
+  Res_parser.eatBreadcrumb(p);
+  return vb;
+}
+
+function parseNewlineOrSemicolonStructure(p) {
+  var token = p.token;
+  if (token === 8) {
+    return Res_parser.next(undefined, p);
+  } else if (Res_grammar.isStructureItemStart(token) && p.prevEndPos.pos_lnum >= p.startPos.pos_lnum) {
+    return Res_parser.err(p.prevEndPos, p.endPos, p, Res_diagnostics.message("consecutive statements on a line must be separated by ';' or a newline"));
+  } else {
+    return ;
+  }
+}
+
+function parseTypeDefinitionOrExtension(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Typ */60, p);
+  var match = p.token;
+  var recFlag;
+  if (typeof match === "number") {
+    if (match === /* Rec */11) {
+      Res_parser.next(undefined, p);
+      recFlag = /* Recursive */1;
+    } else {
+      recFlag = /* Nonrecursive */0;
+    }
+  } else if (match.TAG === /* Lident */4 && match._0 === "nonrec") {
+    Res_parser.next(undefined, p);
+    recFlag = /* Nonrecursive */0;
+  } else {
+    recFlag = /* Nonrecursive */0;
+  }
+  var name = parseValuePath(p);
+  var params = parseTypeParams(name, p);
+  var match$1 = p.token;
+  if (match$1 === 39) {
+    return {
+            TAG: /* TypeExt */1,
+            _0: parseTypeExtension(params, attrs, name, p)
+          };
+  }
+  var longident = name.txt;
+  var exit = 0;
+  switch (longident.TAG | 0) {
+    case /* Lident */0 :
+        break;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        exit = 1;
+        break;
+    
+  }
+  if (exit === 1) {
+    Res_parser.err(name.loc.loc_start, name.loc.loc_end, p, Res_diagnostics.message(typeDeclarationNameLongident(longident)));
+  }
+  var typeDefs = parseTypeDefinitions(attrs, name, params, startPos, p);
+  return {
+          TAG: /* TypeDef */0,
+          recFlag: recFlag,
+          types: typeDefs
+        };
+}
+
+function parseExtension(moduleLanguageOpt, p) {
+  var moduleLanguage = moduleLanguageOpt !== undefined ? moduleLanguageOpt : false;
+  var startPos = p.startPos;
+  if (moduleLanguage) {
+    Res_parser.expect(undefined, /* PercentPercent */78, p);
+  } else {
+    Res_parser.expect(undefined, /* Percent */77, p);
+  }
+  var attrId = parseAttributeId(startPos, p);
+  var payload = parsePayload(p);
+  return [
+          attrId,
+          payload
+        ];
+}
+
+function parseIncludeStatement(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Include */64, p);
+  var modExpr = parseModuleExpr(p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Incl.mk(loc, attrs, undefined, modExpr);
+}
+
+function parseJsImport(startPos, attrs, p) {
+  Res_parser.expect(undefined, /* Import */83, p);
+  var match = p.token;
+  var importSpec;
+  var exit = 0;
+  if (typeof match === "number") {
+    if (match === /* At */75) {
+      exit = 1;
+    } else {
+      importSpec = {
+        TAG: /* Spec */1,
+        _0: parseJsFfiDeclarations(p)
+      };
+    }
+  } else if (match.TAG === /* Lident */4) {
+    exit = 1;
+  } else {
+    importSpec = {
+      TAG: /* Spec */1,
+      _0: parseJsFfiDeclarations(p)
+    };
+  }
+  if (exit === 1) {
+    var decl = parseJsFfiDeclaration(p);
+    var decl$1;
+    if (decl !== undefined) {
+      decl$1 = decl;
+    } else {
+      throw {
+            RE_EXN_ID: "Assert_failure",
+            _1: [
+              "res_core.res",
+              6159,
+              14
+            ],
+            Error: new Error()
+          };
+    }
+    importSpec = {
+      TAG: /* Default */0,
+      _0: decl$1
+    };
+  }
+  var scope = parseJsFfiScope(p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Res_js_ffi.importDescr(attrs, scope, importSpec, loc);
+}
+
+function parseRecordPatternField(p) {
+  var label = parseValuePath(p);
+  var match = p.token;
+  var pattern = match === 24 ? (Res_parser.next(undefined, p), parsePattern(undefined, undefined, p)) : Ast_helper.Pat.$$var(label.loc, undefined, $$Location.mkloc(Longident.last(label.txt), label.loc));
+  return [
+          label,
+          pattern
+        ];
+}
+
+function parseTypeParam(p) {
+  var match = p.token;
+  var variance;
+  if (typeof match === "number") {
+    switch (match) {
+      case /* Minus */34 :
+          Res_parser.next(undefined, p);
+          variance = /* Contravariant */1;
+          break;
+      case /* MinusDot */35 :
+          variance = /* Invariant */2;
+          break;
+      case /* Plus */36 :
+          Res_parser.next(undefined, p);
+          variance = /* Covariant */0;
+          break;
+      default:
+        variance = /* Invariant */2;
+    }
+  } else {
+    variance = /* Invariant */2;
+  }
+  var token = p.token;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* Underscore */12 :
+          var loc_loc_start = p.startPos;
+          var loc_loc_end = p.endPos;
+          var loc = {
+            loc_start: loc_loc_start,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          Res_parser.next(undefined, p);
+          return [
+                  Ast_helper.Typ.any(loc, undefined, undefined),
+                  variance
+                ];
+      case /* SingleQuote */13 :
+          Res_parser.next(undefined, p);
+          var match$1 = parseIdent(typeParam, p.startPos, p);
+          return [
+                  Ast_helper.Typ.$$var(match$1[1], undefined, match$1[0]),
+                  variance
+                ];
+      default:
+        return ;
+    }
+  } else {
+    switch (token.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          break;
+      default:
+        return ;
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.message("Type params start with a singlequote: '" + Res_token.toString(token)));
+  var match$2 = parseIdent(typeParam, p.startPos, p);
+  return [
+          Ast_helper.Typ.$$var(match$2[1], undefined, match$2[0]),
+          variance
+        ];
+}
+
+function parseTypeConstructorArgs(constrName, p) {
+  var opening = p.token;
+  var openingStartPos = p.startPos;
+  if (typeof opening !== "number") {
+    return /* [] */0;
+  }
+  if (opening !== 18 && opening !== 42) {
+    return /* [] */0;
+  }
+  Res_scanner.setDiamondMode(p.scanner);
+  Res_parser.next(undefined, p);
+  var typeArgs = parseCommaDelimitedRegion(p, /* TypExprList */39, /* GreaterThan */41, parseTypeConstructorArgRegion);
+  var match = p.token;
+  if (match === 19 && opening === /* Lparen */18) {
+    var typ = Ast_helper.Typ.constr(undefined, undefined, constrName, typeArgs);
+    var msg = Res_doc.toString(80, Res_doc.breakableGroup(true, Res_doc.concat({
+                  hd: Res_doc.text("Type parameters require angle brackets:"),
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.line,
+                              tl: {
+                                hd: Res_printer.printTypExpr(typ, Res_comments_table.empty),
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                })));
+    Res_parser.err(openingStartPos, undefined, p, Res_diagnostics.message(msg));
+    Res_parser.next(undefined, p);
+  } else {
+    Res_parser.expect(undefined, /* GreaterThan */41, p);
+  }
+  Res_scanner.popMode(p.scanner, /* Diamond */1);
+  return typeArgs;
+}
+
+function parseTypeRepresentation(p) {
+  Res_parser.leaveBreadcrumb(p, /* TypeRepresentation */33);
+  var privateFlag = Res_parser.optional(p, /* Private */61) ? /* Private */0 : /* Public */1;
+  var token = p.token;
+  var kind;
+  var exit = 0;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* DotDot */5 :
+          Res_parser.next(undefined, p);
+          kind = /* Ptype_open */1;
+          break;
+      case /* Bar */17 :
+          kind = {
+            TAG: /* Ptype_variant */0,
+            _0: parseTypeConstructorDeclarations(undefined, p)
+          };
+          break;
+      case /* Lbrace */22 :
+          kind = {
+            TAG: /* Ptype_record */1,
+            _0: parseRecordDeclaration(p)
+          };
+          break;
+      default:
+        exit = 1;
+    }
+  } else if (token.TAG === /* Uident */5) {
+    kind = {
+      TAG: /* Ptype_variant */0,
+      _0: parseTypeConstructorDeclarations(undefined, p)
+    };
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+    kind = {
+      TAG: /* Ptype_variant */0,
+      _0: /* [] */0
+    };
+  }
+  Res_parser.eatBreadcrumb(p);
+  return [
+          privateFlag,
+          kind
+        ];
+}
+
+function parseTypeConstructorDeclarations(first, p) {
+  var firstConstrDecl;
+  if (first !== undefined) {
+    firstConstrDecl = first;
+  } else {
+    var startPos = p.startPos;
+    Res_parser.optional(p, /* Bar */17);
+    firstConstrDecl = parseTypeConstructorDeclaration(startPos, p);
+  }
+  return {
+          hd: firstConstrDecl,
+          tl: parseRegion(p, /* ConstructorDeclaration */35, parseTypeConstructorDeclarationWithBar)
+        };
+}
+
+function parseParameterList(p) {
+  var parameters = parseCommaDelimitedRegion(p, /* ParameterList */36, /* Rparen */19, parseParameter);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  return parameters;
+}
+
+function parseTypeConstraint(p) {
+  var startPos = p.startPos;
+  var match = p.token;
+  if (match !== 63) {
+    return ;
+  }
+  Res_parser.next(undefined, p);
+  Res_parser.expect(undefined, /* SingleQuote */13, p);
+  var ident = p.token;
+  if (typeof ident !== "number" && ident.TAG === /* Lident */4) {
+    var identLoc_loc_end = p.endPos;
+    var identLoc = {
+      loc_start: startPos,
+      loc_end: identLoc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    Res_parser.expect(undefined, /* Equal */14, p);
+    var typ = parseTypExpr(undefined, undefined, undefined, p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return [
+            Ast_helper.Typ.$$var(identLoc, undefined, ident._0),
+            typ,
+            loc
+          ];
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.lident(ident));
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return [
+          Ast_helper.Typ.any(undefined, undefined, undefined),
+          parseTypExpr(undefined, undefined, undefined, p),
+          loc$1
+        ];
+}
+
+function parseAttributesAndBinding(p) {
+  var err = p.scanner.err;
+  var ch = p.scanner.ch;
+  var offset = p.scanner.offset;
+  var lineOffset = p.scanner.lineOffset;
+  var lnum = p.scanner.lnum;
+  var mode = p.scanner.mode;
+  var token = p.token;
+  var startPos = p.startPos;
+  var endPos = p.endPos;
+  var prevEndPos = p.prevEndPos;
+  var breadcrumbs = p.breadcrumbs;
+  var errors = p.errors;
+  var diagnostics = p.diagnostics;
+  var comments = p.comments;
+  var match = p.token;
+  if (match !== 75) {
+    return /* [] */0;
+  }
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match$1 = p.token;
+  if (match$1 === 10) {
+    return attrs;
+  } else {
+    p.scanner.err = err;
+    p.scanner.ch = ch;
+    p.scanner.offset = offset;
+    p.scanner.lineOffset = lineOffset;
+    p.scanner.lnum = lnum;
+    p.scanner.mode = mode;
+    p.token = token;
+    p.startPos = startPos;
+    p.endPos = endPos;
+    p.prevEndPos = prevEndPos;
+    p.breadcrumbs = breadcrumbs;
+    p.errors = errors;
+    p.diagnostics = diagnostics;
+    p.comments = comments;
+    return /* [] */0;
+  }
+}
+
+function parseTypeDef(attrs, startPos, p) {
+  Res_parser.leaveBreadcrumb(p, /* TypeDef */28);
+  Res_parser.leaveBreadcrumb(p, /* TypeConstrName */29);
+  var match = parseLident(p);
+  var loc = match[1];
+  var name = match[0];
+  var typeConstrName = $$Location.mkloc(name, loc);
+  Res_parser.eatBreadcrumb(p);
+  var constrName = $$Location.mkloc({
+        TAG: /* Lident */0,
+        _0: name
+      }, loc);
+  var params = parseTypeParams(constrName, p);
+  var match$1 = parseTypeEquationAndRepresentation(p);
+  var cstrs = parseRegion(p, /* TypeConstraint */51, parseTypeConstraint);
+  var loc_loc_end = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var typeDef = Ast_helper.Type.mk(loc$1, attrs, undefined, undefined, params, cstrs, match$1[2], match$1[1], match$1[0], typeConstrName);
+  Res_parser.eatBreadcrumb(p);
+  return typeDef;
+}
+
+function parseTypeEquationAndRepresentation(p) {
+  var token = p.token;
+  var exit = 0;
+  if (typeof token !== "number") {
+    return [
+            undefined,
+            /* Public */1,
+            /* Ptype_abstract */0
+          ];
+  }
+  if (token !== 14) {
+    if (token !== 17) {
+      return [
+              undefined,
+              /* Public */1,
+              /* Ptype_abstract */0
+            ];
+    }
+    exit = 1;
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    if (token === /* Bar */17) {
+      Res_parser.expect(undefined, /* Equal */14, p);
+    }
+    Res_parser.next(undefined, p);
+    var match = p.token;
+    var exit$1 = 0;
+    if (typeof match === "number") {
+      switch (match) {
+        case /* DotDot */5 :
+        case /* Bar */17 :
+            exit$1 = 3;
+            break;
+        case /* Lbrace */22 :
+            return parseRecordOrObjectDecl(p);
+        case /* Private */61 :
+            return parsePrivateEqOrRepr(p);
+        default:
+          exit$1 = 2;
+      }
+    } else {
+      if (match.TAG === /* Uident */5) {
+        return parseTypeEquationOrConstrDecl(p);
+      }
+      exit$1 = 2;
+    }
+    switch (exit$1) {
+      case 2 :
+          var manifest = parseTypExpr(undefined, undefined, undefined, p);
+          var match$1 = p.token;
+          if (match$1 !== 14) {
+            return [
+                    manifest,
+                    /* Public */1,
+                    /* Ptype_abstract */0
+                  ];
+          }
+          Res_parser.next(undefined, p);
+          var match$2 = parseTypeRepresentation(p);
+          return [
+                  manifest,
+                  match$2[0],
+                  match$2[1]
+                ];
+      case 3 :
+          var match$3 = parseTypeRepresentation(p);
+          return [
+                  undefined,
+                  match$3[0],
+                  match$3[1]
+                ];
+      
+    }
+  }
+  
+}
+
+function parseIfLetExpr(startPos, p) {
+  var pattern = parsePattern(undefined, undefined, p);
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var conditionExpr = parseIfCondition(p);
+  var thenExpr = parseThenBranch(p);
+  var match = p.token;
+  var elseExpr;
+  Res_parser.endRegion(p);
+  if (match === 51) {
+    Res_parser.leaveBreadcrumb(p, /* ElseBranch */19);
+    Res_parser.next(undefined, p);
+    Res_parser.beginRegion(p);
+    var match$1 = p.token;
+    var elseExpr$1 = match$1 === 50 ? parseIfOrIfLetExpression(p) : parseElseBranch(p);
+    Res_parser.eatBreadcrumb(p);
+    Res_parser.endRegion(p);
+    elseExpr = elseExpr$1;
+  } else {
+    var startPos$1 = p.startPos;
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos$1,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    elseExpr = Ast_helper.Exp.construct(loc, undefined, $$Location.mkloc({
+              TAG: /* Lident */0,
+              _0: "()"
+            }, loc), undefined);
+  }
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.match_(loc$1, {
+              hd: ifLetAttr,
+              tl: {
+                hd: suppressFragileMatchWarningAttr,
+                tl: /* [] */0
+              }
+            }, conditionExpr, {
+              hd: Ast_helper.Exp.$$case(pattern, undefined, thenExpr),
+              tl: {
+                hd: Ast_helper.Exp.$$case(Ast_helper.Pat.any(undefined, undefined, undefined), undefined, elseExpr),
+                tl: /* [] */0
+              }
+            });
+}
+
+function parseIfExpr(startPos, p) {
+  var conditionExpr = parseIfCondition(p);
+  var thenExpr = parseThenBranch(p);
+  var match = p.token;
+  var elseExpr;
+  Res_parser.endRegion(p);
+  if (match === 51) {
+    Res_parser.leaveBreadcrumb(p, /* ElseBranch */19);
+    Res_parser.next(undefined, p);
+    Res_parser.beginRegion(p);
+    var match$1 = p.token;
+    var elseExpr$1 = match$1 === 50 ? parseIfOrIfLetExpression(p) : parseElseBranch(p);
+    Res_parser.eatBreadcrumb(p);
+    Res_parser.endRegion(p);
+    elseExpr = elseExpr$1;
+  } else {
+    elseExpr = undefined;
+  }
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.ifthenelse(loc, undefined, conditionExpr, thenExpr, elseExpr);
+}
+
+function parseRecordOrObjectDecl(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var match = p.token;
+  var exit = 0;
+  if (typeof match === "number") {
+    if (match >= 6) {
+      if (match >= 7) {
+        exit = 1;
+      } else {
+        var dotdotdotStart = p.startPos;
+        var dotdotdotEnd = p.endPos;
+        Res_parser.next(undefined, p);
+        var typ = parseTypExpr(undefined, undefined, undefined, p);
+        var match$1 = p.token;
+        if (match$1 === 23) {
+          Res_parser.err(dotdotdotStart, dotdotdotEnd, p, Res_diagnostics.message(sameTypeSpread));
+          Res_parser.next(undefined, p);
+        } else {
+          Res_parser.expect(undefined, /* Comma */25, p);
+        }
+        var match$2 = p.token;
+        if (typeof match$2 !== "number" && match$2.TAG === /* Lident */4) {
+          Res_parser.err(dotdotdotStart, dotdotdotEnd, p, Res_diagnostics.message(spreadInRecordDeclaration));
+        }
+        var fields_0 = {
+          TAG: /* Oinherit */1,
+          _0: typ
+        };
+        var fields_1 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+        var fields = {
+          hd: fields_0,
+          tl: fields_1
+        };
+        Res_parser.expect(undefined, /* Rbrace */23, p);
+        var loc_loc_end = p.prevEndPos;
+        var loc = {
+          loc_start: startPos,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        var typ$1 = parseTypeAlias(p, Ast_helper.Typ.object_(loc, undefined, fields, /* Closed */0));
+        var typ$2 = parseArrowTypeRest(true, startPos, typ$1, p);
+        return [
+                typ$2,
+                /* Public */1,
+                /* Ptype_abstract */0
+              ];
+      }
+    } else {
+      if (match >= 4) {
+        var match$3 = p.token;
+        var closedFlag = typeof match$3 === "number" ? (
+            match$3 !== 4 ? (
+                match$3 !== 5 ? /* Closed */0 : (Res_parser.next(undefined, p), /* Open */1)
+              ) : (Res_parser.next(undefined, p), /* Closed */0)
+          ) : /* Closed */0;
+        var fields$1 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+        Res_parser.expect(undefined, /* Rbrace */23, p);
+        var loc_loc_end$1 = p.prevEndPos;
+        var loc$1 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$1,
+          loc_ghost: false
+        };
+        var typ$3 = parseTypeAlias(p, Ast_helper.Typ.object_(loc$1, /* [] */0, fields$1, closedFlag));
+        var typ$4 = parseArrowTypeRest(true, startPos, typ$3, p);
+        return [
+                typ$4,
+                /* Public */1,
+                /* Ptype_abstract */0
+              ];
+      }
+      exit = 1;
+    }
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+    var match$4 = p.token;
+    var exit$1 = 0;
+    if (typeof match$4 === "number") {
+      exit$1 = 2;
+    } else {
+      if (match$4.TAG === /* String */3) {
+        var fields$2;
+        if (attrs) {
+          Res_parser.leaveBreadcrumb(p, /* StringFieldDeclarations */37);
+          var field = parseStringFieldDeclaration(p);
+          var field$1;
+          if (field !== undefined) {
+            field$1 = field;
+          } else {
+            throw {
+                  RE_EXN_ID: "Assert_failure",
+                  _1: [
+                    "res_core.res",
+                    5486,
+                    20
+                  ],
+                  Error: new Error()
+                };
+          }
+          var match$5 = p.token;
+          if (typeof match$5 === "number") {
+            if (match$5 >= 24) {
+              if (match$5 >= 27) {
+                Res_parser.expect(undefined, /* Comma */25, p);
+              } else {
+                switch (match$5) {
+                  case /* Colon */24 :
+                      Res_parser.expect(undefined, /* Comma */25, p);
+                      break;
+                  case /* Comma */25 :
+                      Res_parser.next(undefined, p);
+                      break;
+                  case /* Eof */26 :
+                      break;
+                  
+                }
+              }
+            } else if (match$5 >= 23) {
+              
+            } else {
+              Res_parser.expect(undefined, /* Comma */25, p);
+            }
+          } else {
+            Res_parser.expect(undefined, /* Comma */25, p);
+          }
+          Res_parser.eatBreadcrumb(p);
+          var first;
+          first = field$1.TAG === /* Otag */0 ? ({
+                TAG: /* Otag */0,
+                _0: field$1._0,
+                _1: attrs,
+                _2: field$1._2
+              }) : ({
+                TAG: /* Oinherit */1,
+                _0: field$1._0
+              });
+          fields$2 = {
+            hd: first,
+            tl: parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration)
+          };
+        } else {
+          fields$2 = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+        }
+        Res_parser.expect(undefined, /* Rbrace */23, p);
+        var loc_loc_end$2 = p.prevEndPos;
+        var loc$2 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$2,
+          loc_ghost: false
+        };
+        var typ$5 = parseTypeAlias(p, Ast_helper.Typ.object_(loc$2, /* [] */0, fields$2, /* Closed */0));
+        var typ$6 = parseArrowTypeRest(true, startPos, typ$5, p);
+        return [
+                typ$6,
+                /* Public */1,
+                /* Ptype_abstract */0
+              ];
+      }
+      exit$1 = 2;
+    }
+    if (exit$1 === 2) {
+      Res_parser.leaveBreadcrumb(p, /* RecordDecl */34);
+      var fields$3;
+      if (attrs) {
+        var field$2 = parseFieldDeclaration(p);
+        Res_parser.optional(p, /* Comma */25);
+        var init = field$2.pld_loc;
+        var first_pld_name = field$2.pld_name;
+        var first_pld_mutable = field$2.pld_mutable;
+        var first_pld_type = field$2.pld_type;
+        var first_pld_loc = {
+          loc_start: attrs.hd[0].loc.loc_start,
+          loc_end: init.loc_end,
+          loc_ghost: init.loc_ghost
+        };
+        var first$1 = {
+          pld_name: first_pld_name,
+          pld_mutable: first_pld_mutable,
+          pld_type: first_pld_type,
+          pld_loc: first_pld_loc,
+          pld_attributes: attrs
+        };
+        fields$3 = {
+          hd: first$1,
+          tl: parseCommaDelimitedRegion(p, /* FieldDeclarations */38, /* Rbrace */23, parseFieldDeclarationRegion)
+        };
+      } else {
+        fields$3 = parseCommaDelimitedRegion(p, /* FieldDeclarations */38, /* Rbrace */23, parseFieldDeclarationRegion);
+      }
+      if (fields$3) {
+        
+      } else {
+        Res_parser.err(startPos, undefined, p, Res_diagnostics.message("A record needs at least one field"));
+      }
+      Res_parser.expect(undefined, /* Rbrace */23, p);
+      Res_parser.eatBreadcrumb(p);
+      return [
+              undefined,
+              /* Public */1,
+              {
+                TAG: /* Ptype_record */1,
+                _0: fields$3
+              }
+            ];
+    }
+    
+  }
+  
+}
+
+function parseTypeEquationOrConstrDecl(p) {
+  var uidentStartPos = p.startPos;
+  var uident = p.token;
+  if (typeof uident !== "number" && uident.TAG === /* Uident */5) {
+    var uident$1 = uident._0;
+    Res_parser.next(undefined, p);
+    var match = p.token;
+    if (match === 4) {
+      Res_parser.next(undefined, p);
+      var typeConstr = parseValuePathTail(p, uidentStartPos, {
+            TAG: /* Lident */0,
+            _0: uident$1
+          });
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: uidentStartPos,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      var typ = parseTypeAlias(p, Ast_helper.Typ.constr(loc, undefined, typeConstr, parseTypeConstructorArgs(typeConstr, p)));
+      var match$1 = p.token;
+      if (typeof match$1 !== "number") {
+        return [
+                typ,
+                /* Public */1,
+                /* Ptype_abstract */0
+              ];
+      }
+      if (match$1 !== 14) {
+        if (match$1 !== 57) {
+          return [
+                  typ,
+                  /* Public */1,
+                  /* Ptype_abstract */0
+                ];
+        }
+        Res_parser.next(undefined, p);
+        var returnType = parseTypExpr(undefined, undefined, false, p);
+        var loc_loc_end$1 = p.prevEndPos;
+        var loc$1 = {
+          loc_start: uidentStartPos,
+          loc_end: loc_loc_end$1,
+          loc_ghost: false
+        };
+        var arrowType = Ast_helper.Typ.arrow(loc$1, undefined, /* Nolabel */0, typ, returnType);
+        var typ$1 = parseTypeAlias(p, arrowType);
+        return [
+                typ$1,
+                /* Public */1,
+                /* Ptype_abstract */0
+              ];
+      }
+      Res_parser.next(undefined, p);
+      var match$2 = parseTypeRepresentation(p);
+      return [
+              typ,
+              match$2[0],
+              match$2[1]
+            ];
+    }
+    var uidentEndPos = p.prevEndPos;
+    var match$3 = parseConstrDeclArgs(p);
+    var uidentLoc = {
+      loc_start: uidentStartPos,
+      loc_end: uidentEndPos,
+      loc_ghost: false
+    };
+    var first = Ast_helper.Type.constructor({
+          loc_start: uidentStartPos,
+          loc_end: p.prevEndPos,
+          loc_ghost: false
+        }, undefined, undefined, match$3[0], match$3[1], $$Location.mkloc(uident$1, uidentLoc));
+    return [
+            undefined,
+            /* Public */1,
+            {
+              TAG: /* Ptype_variant */0,
+              _0: parseTypeConstructorDeclarations(first, p)
+            }
+          ];
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(uident));
+  return [
+          undefined,
+          /* Public */1,
+          /* Ptype_abstract */0
+        ];
+}
+
+function parseJsFfiDeclaration(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (typeof match === "number") {
+    return ;
+  }
+  if (match.TAG !== /* Lident */4) {
+    return ;
+  }
+  var match$1 = parseLident(p);
+  var ident = match$1[0];
+  var match$2 = p.token;
+  var alias = match$2 === 3 ? (Res_parser.next(undefined, p), parseLident(p)[0]) : ident;
+  Res_parser.expect(undefined, /* Colon */24, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Res_js_ffi.decl(attrs, loc, ident, alias, typ);
+}
+
+function parseWithConstraint(p) {
+  var token = p.token;
+  if (typeof token === "number") {
+    if (token !== 60) {
+      if (token === 65) {
+        Res_parser.next(undefined, p);
+        var modulePath = parseModuleLongIdent(false, p);
+        var token$1 = p.token;
+        var exit = 0;
+        if (typeof token$1 === "number") {
+          if (token$1 !== 14) {
+            if (token$1 !== 74) {
+              exit = 2;
+            } else {
+              Res_parser.next(undefined, p);
+              var lident = parseModuleLongIdent(false, p);
+              return {
+                      TAG: /* Pwith_modsubst */3,
+                      _0: modulePath,
+                      _1: lident
+                    };
+            }
+          } else {
+            Res_parser.next(undefined, p);
+            var lident$1 = parseModuleLongIdent(false, p);
+            return {
+                    TAG: /* Pwith_module */1,
+                    _0: modulePath,
+                    _1: lident$1
+                  };
+          }
+        } else {
+          exit = 2;
+        }
+        if (exit === 2) {
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token$1, p.breadcrumbs));
+          var lident$2 = parseModuleLongIdent(false, p);
+          return {
+                  TAG: /* Pwith_modsubst */3,
+                  _0: modulePath,
+                  _1: lident$2
+                };
+        }
+        
+      }
+      
+    } else {
+      Res_parser.next(undefined, p);
+      var typeConstr = parseValuePath(p);
+      var params = parseTypeParams(typeConstr, p);
+      var token$2 = p.token;
+      var exit$1 = 0;
+      if (typeof token$2 === "number") {
+        if (token$2 !== 14) {
+          if (token$2 !== 74) {
+            exit$1 = 2;
+          } else {
+            Res_parser.next(undefined, p);
+            var typExpr = parseTypExpr(undefined, undefined, undefined, p);
+            return {
+                    TAG: /* Pwith_typesubst */2,
+                    _0: typeConstr,
+                    _1: Ast_helper.Type.mk(typeConstr.loc, undefined, undefined, undefined, params, undefined, undefined, undefined, typExpr, $$Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc))
+                  };
+          }
+        } else {
+          Res_parser.next(undefined, p);
+          var typExpr$1 = parseTypExpr(undefined, undefined, undefined, p);
+          var typeConstraints = parseRegion(p, /* TypeConstraint */51, parseTypeConstraint);
+          return {
+                  TAG: /* Pwith_type */0,
+                  _0: typeConstr,
+                  _1: Ast_helper.Type.mk(typeConstr.loc, undefined, undefined, undefined, params, typeConstraints, undefined, undefined, typExpr$1, $$Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc))
+                };
+        }
+      } else {
+        exit$1 = 2;
+      }
+      if (exit$1 === 2) {
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token$2, p.breadcrumbs));
+        var typExpr$2 = parseTypExpr(undefined, undefined, undefined, p);
+        var typeConstraints$1 = parseRegion(p, /* TypeConstraint */51, parseTypeConstraint);
+        return {
+                TAG: /* Pwith_type */0,
+                _0: typeConstr,
+                _1: Ast_helper.Type.mk(typeConstr.loc, undefined, undefined, undefined, params, typeConstraints$1, undefined, undefined, typExpr$2, $$Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc))
+              };
+      }
+      
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+  return {
+          TAG: /* Pwith_type */0,
+          _0: $$Location.mknoloc({
+                TAG: /* Lident */0,
+                _0: ""
+              }),
+          _1: Ast_helper.Type.mk(undefined, undefined, undefined, undefined, /* [] */0, /* [] */0, undefined, undefined, defaultType(undefined), $$Location.mknoloc(""))
+        };
+}
+
+function parseTypeParameter(p) {
+  if (!(p.token === /* Tilde */48 || p.token === /* Dot */4 || Res_grammar.isTypExprStart(p.token))) {
+    return ;
+  }
+  var startPos = p.startPos;
+  var uncurried = Res_parser.optional(p, /* Dot */4);
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match === /* Tilde */48) {
+      Res_parser.next(undefined, p);
+      var match$1 = parseLident(p);
+      var name = match$1[0];
+      var lblLocAttr_0 = $$Location.mkloc("ns.namedArgLoc", match$1[1]);
+      var lblLocAttr_1 = {
+        TAG: /* PStr */0,
+        _0: /* [] */0
+      };
+      var lblLocAttr = [
+        lblLocAttr_0,
+        lblLocAttr_1
+      ];
+      Res_parser.expect(/* TypeExpression */20, /* Colon */24, p);
+      var typ = parseTypExpr(undefined, undefined, undefined, p);
+      var typ_ptyp_desc = typ.ptyp_desc;
+      var typ_ptyp_loc = typ.ptyp_loc;
+      var typ_ptyp_attributes = {
+        hd: lblLocAttr,
+        tl: typ.ptyp_attributes
+      };
+      var typ$1 = {
+        ptyp_desc: typ_ptyp_desc,
+        ptyp_loc: typ_ptyp_loc,
+        ptyp_attributes: typ_ptyp_attributes
+      };
+      var match$2 = p.token;
+      if (match$2 === 14) {
+        Res_parser.next(undefined, p);
+        Res_parser.expect(undefined, /* Question */49, p);
+        return [
+                uncurried,
+                attrs,
+                {
+                  TAG: /* Optional */1,
+                  _0: name
+                },
+                typ$1,
+                startPos
+              ];
+      } else {
+        return [
+                uncurried,
+                attrs,
+                {
+                  TAG: /* Labelled */0,
+                  _0: name
+                },
+                typ$1,
+                startPos
+              ];
+      }
+    }
+    
+  } else if (match.TAG === /* Lident */4) {
+    var match$3 = parseLident(p);
+    var loc = match$3[1];
+    var name$1 = match$3[0];
+    var match$4 = p.token;
+    if (match$4 === 24) {
+      var error = Res_diagnostics.message(missingTildeLabeledParameter(name$1));
+      Res_parser.err(loc.loc_start, loc.loc_end, p, error);
+      Res_parser.next(undefined, p);
+      var typ$2 = parseTypExpr(undefined, undefined, undefined, p);
+      var match$5 = p.token;
+      if (match$5 === 14) {
+        Res_parser.next(undefined, p);
+        Res_parser.expect(undefined, /* Question */49, p);
+        return [
+                uncurried,
+                attrs,
+                {
+                  TAG: /* Optional */1,
+                  _0: name$1
+                },
+                typ$2,
+                startPos
+              ];
+      } else {
+        return [
+                uncurried,
+                attrs,
+                {
+                  TAG: /* Labelled */0,
+                  _0: name$1
+                },
+                typ$2,
+                startPos
+              ];
+      }
+    }
+    var constr = $$Location.mkloc({
+          TAG: /* Lident */0,
+          _0: name$1
+        }, loc);
+    var args = parseTypeConstructorArgs(constr, p);
+    var typ$3 = Ast_helper.Typ.constr({
+          loc_start: startPos,
+          loc_end: p.prevEndPos,
+          loc_ghost: false
+        }, attrs, constr, args);
+    var typ$4 = parseArrowTypeRest(true, startPos, typ$3, p);
+    var typ$5 = parseTypeAlias(p, typ$4);
+    return [
+            uncurried,
+            /* [] */0,
+            /* Nolabel */0,
+            typ$5,
+            startPos
+          ];
+  }
+  var typ$6 = parseTypExpr(undefined, undefined, undefined, p);
+  var typWithAttributes_ptyp_desc = typ$6.ptyp_desc;
+  var typWithAttributes_ptyp_loc = typ$6.ptyp_loc;
+  var typWithAttributes_ptyp_attributes = List.concat({
+        hd: attrs,
+        tl: {
+          hd: typ$6.ptyp_attributes,
+          tl: /* [] */0
+        }
+      });
+  var typWithAttributes = {
+    ptyp_desc: typWithAttributes_ptyp_desc,
+    ptyp_loc: typWithAttributes_ptyp_loc,
+    ptyp_attributes: typWithAttributes_ptyp_attributes
+  };
+  return [
+          uncurried,
+          /* [] */0,
+          /* Nolabel */0,
+          typWithAttributes,
+          startPos
+        ];
+}
+
+function parseRecordDeclaration(p) {
+  Res_parser.leaveBreadcrumb(p, /* RecordDecl */34);
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var rows = parseCommaDelimitedRegion(p, /* RecordDecl */34, /* Rbrace */23, parseFieldDeclarationRegion);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  Res_parser.eatBreadcrumb(p);
+  return rows;
+}
+
+function parseStructureItemRegion(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var token = p.token;
+  if (typeof token === "number") {
+    if (token >= 10) {
+      if (token !== 27) {
+        if (token >= 59) {
+          switch (token) {
+            case /* External */59 :
+                var externalDef = parseExternalDef(attrs, startPos, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end = p.prevEndPos;
+                var loc = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end,
+                  loc_ghost: false
+                };
+                return Ast_helper.Str.primitive(loc, externalDef);
+            case /* Typ */60 :
+                Res_parser.beginRegion(p);
+                var ext = parseTypeDefinitionOrExtension(attrs, p);
+                if (ext.TAG === /* TypeDef */0) {
+                  parseNewlineOrSemicolonStructure(p);
+                  var loc_loc_end$1 = p.prevEndPos;
+                  var loc$1 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$1,
+                    loc_ghost: false
+                  };
+                  Res_parser.endRegion(p);
+                  return Ast_helper.Str.type_(loc$1, ext.recFlag, ext.types);
+                }
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$2 = p.prevEndPos;
+                var loc$2 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$2,
+                  loc_ghost: false
+                };
+                Res_parser.endRegion(p);
+                return Ast_helper.Str.type_extension(loc$2, ext._0);
+            case /* Include */64 :
+                var includeStatement = parseIncludeStatement(attrs, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$3 = p.prevEndPos;
+                var loc$3 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$3,
+                  loc_ghost: false
+                };
+                return Ast_helper.Str.include_(loc$3, includeStatement);
+            case /* Module */65 :
+                Res_parser.beginRegion(p);
+                var structureItem = parseModuleOrModuleTypeImplOrPackExpr(attrs, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$4 = p.prevEndPos;
+                var loc$4 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$4,
+                  loc_ghost: false
+                };
+                Res_parser.endRegion(p);
+                return {
+                        pstr_desc: structureItem.pstr_desc,
+                        pstr_loc: loc$4
+                      };
+            case /* AtAt */76 :
+                var attr = parseStandaloneAttribute(p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$5 = p.prevEndPos;
+                var loc$5 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$5,
+                  loc_ghost: false
+                };
+                return Ast_helper.Str.attribute(loc$5, attr);
+            case /* PercentPercent */78 :
+                var extension = parseExtension(true, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$6 = p.prevEndPos;
+                var loc$6 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$6,
+                  loc_ghost: false
+                };
+                return Ast_helper.Str.extension(loc$6, attrs, extension);
+            case /* Private */61 :
+            case /* Mutable */62 :
+            case /* Constraint */63 :
+            case /* Of */66 :
+            case /* Land */67 :
+            case /* Lor */68 :
+            case /* Band */69 :
+            case /* BangEqual */70 :
+            case /* BangEqualEqual */71 :
+            case /* LessEqual */72 :
+            case /* GreaterEqual */73 :
+            case /* ColonEqual */74 :
+            case /* At */75 :
+            case /* Percent */77 :
+            case /* List */79 :
+            case /* Backtick */80 :
+            case /* BarGreater */81 :
+            case /* Try */82 :
+                break;
+            case /* Import */83 :
+                var importDescr = parseJsImport(startPos, attrs, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$7 = p.prevEndPos;
+                var loc$7 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$7,
+                  loc_ghost: false
+                };
+                var structureItem$1 = Res_js_ffi.toParsetree(importDescr);
+                return {
+                        pstr_desc: structureItem$1.pstr_desc,
+                        pstr_loc: loc$7
+                      };
+            case /* Export */84 :
+                var structureItem$2 = parseJsExport(attrs, p);
+                parseNewlineOrSemicolonStructure(p);
+                var loc_loc_end$8 = p.prevEndPos;
+                var loc$8 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$8,
+                  loc_ghost: false
+                };
+                return {
+                        pstr_desc: structureItem$2.pstr_desc,
+                        pstr_loc: loc$8
+                      };
+            
+          }
+        }
+        
+      } else {
+        var exceptionDef = parseExceptionDef(attrs, p);
+        parseNewlineOrSemicolonStructure(p);
+        var loc_loc_end$9 = p.prevEndPos;
+        var loc$9 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$9,
+          loc_ghost: false
+        };
+        return Ast_helper.Str.exception_(loc$9, exceptionDef);
+      }
+    } else if (token !== 0) {
+      if (token >= 9) {
+        var match = parseLetBindings(attrs, p);
+        parseNewlineOrSemicolonStructure(p);
+        var loc_loc_end$10 = p.prevEndPos;
+        var loc$10 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$10,
+          loc_ghost: false
+        };
+        return Ast_helper.Str.value(loc$10, match[0], match[1]);
+      }
+      
+    } else {
+      var openDescription = parseOpenDescription(attrs, p);
+      parseNewlineOrSemicolonStructure(p);
+      var loc_loc_end$11 = p.prevEndPos;
+      var loc$11 = {
+        loc_start: startPos,
+        loc_end: loc_loc_end$11,
+        loc_ghost: false
+      };
+      return Ast_helper.Str.open_(loc$11, openDescription);
+    }
+  }
+  if (Res_grammar.isExprStart(token)) {
+    var prevEndPos = p.endPos;
+    var exp = parseExpr(undefined, p);
+    parseNewlineOrSemicolonStructure(p);
+    var loc_loc_end$12 = p.prevEndPos;
+    var loc$12 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end$12,
+      loc_ghost: false
+    };
+    return Res_parser.checkProgress(prevEndPos, Ast_helper.Str.$$eval(loc$12, attrs, exp), p);
+  }
+  if (!attrs) {
+    return ;
+  }
+  var attr$1 = attrs.hd;
+  var attrLoc = attr$1[0].loc;
+  Res_parser.err(attrLoc.loc_start, attrLoc.loc_end, p, Res_diagnostics.message(attributeWithoutNode(attr$1)));
+  var expr = parseExpr(undefined, p);
+  return Ast_helper.Str.$$eval({
+              loc_start: p.startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, attrs, expr);
+}
+
+function parseSignatureItemRegion(p) {
+  while(true) {
+    var startPos = p.startPos;
+    var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+    var match = p.token;
+    if (typeof match === "number") {
+      if (match >= 10) {
+        if (match !== 27) {
+          if (match >= 59) {
+            switch (match) {
+              case /* External */59 :
+                  var externalDef = parseExternalDef(attrs, startPos, p);
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end = p.prevEndPos;
+                  var loc = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end,
+                    loc_ghost: false
+                  };
+                  return Ast_helper.Sig.value(loc, externalDef);
+              case /* Typ */60 :
+                  Res_parser.beginRegion(p);
+                  var ext = parseTypeDefinitionOrExtension(attrs, p);
+                  if (ext.TAG === /* TypeDef */0) {
+                    parseNewlineOrSemicolonSignature(p);
+                    var loc_loc_end$1 = p.prevEndPos;
+                    var loc$1 = {
+                      loc_start: startPos,
+                      loc_end: loc_loc_end$1,
+                      loc_ghost: false
+                    };
+                    Res_parser.endRegion(p);
+                    return Ast_helper.Sig.type_(loc$1, ext.recFlag, ext.types);
+                  }
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end$2 = p.prevEndPos;
+                  var loc$2 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$2,
+                    loc_ghost: false
+                  };
+                  Res_parser.endRegion(p);
+                  return Ast_helper.Sig.type_extension(loc$2, ext._0);
+              case /* Include */64 :
+                  Res_parser.next(undefined, p);
+                  var moduleType = parseModuleType(undefined, undefined, p);
+                  var includeDescription = Ast_helper.Incl.mk({
+                        loc_start: startPos,
+                        loc_end: p.prevEndPos,
+                        loc_ghost: false
+                      }, attrs, undefined, moduleType);
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end$3 = p.prevEndPos;
+                  var loc$3 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$3,
+                    loc_ghost: false
+                  };
+                  return Ast_helper.Sig.include_(loc$3, includeDescription);
+              case /* Module */65 :
+                  Res_parser.beginRegion(p);
+                  Res_parser.next(undefined, p);
+                  var _t = p.token;
+                  var exit = 0;
+                  if (typeof _t === "number") {
+                    switch (_t) {
+                      case /* Rec */11 :
+                          var recModule = parseRecModuleSpec(attrs, startPos, p);
+                          parseNewlineOrSemicolonSignature(p);
+                          var loc_loc_end$4 = p.prevEndPos;
+                          var loc$4 = {
+                            loc_start: startPos,
+                            loc_end: loc_loc_end$4,
+                            loc_ghost: false
+                          };
+                          Res_parser.endRegion(p);
+                          return Ast_helper.Sig.rec_module(loc$4, recModule);
+                      case /* Typ */60 :
+                          var modTypeDecl = parseModuleTypeDeclaration(attrs, startPos, p);
+                          Res_parser.endRegion(p);
+                          return modTypeDecl;
+                      default:
+                        exit = 2;
+                    }
+                  } else {
+                    if (_t.TAG === /* Uident */5) {
+                      var modDecl = parseModuleDeclarationOrAlias(attrs, p);
+                      parseNewlineOrSemicolonSignature(p);
+                      var loc_loc_end$5 = p.prevEndPos;
+                      var loc$5 = {
+                        loc_start: startPos,
+                        loc_end: loc_loc_end$5,
+                        loc_ghost: false
+                      };
+                      Res_parser.endRegion(p);
+                      return Ast_helper.Sig.module_(loc$5, modDecl);
+                    }
+                    exit = 2;
+                  }
+                  if (exit === 2) {
+                    var modDecl$1 = parseModuleDeclarationOrAlias(attrs, p);
+                    parseNewlineOrSemicolonSignature(p);
+                    var loc_loc_end$6 = p.prevEndPos;
+                    var loc$6 = {
+                      loc_start: startPos,
+                      loc_end: loc_loc_end$6,
+                      loc_ghost: false
+                    };
+                    Res_parser.endRegion(p);
+                    return Ast_helper.Sig.module_(loc$6, modDecl$1);
+                  }
+                  break;
+              case /* AtAt */76 :
+                  var attr = parseStandaloneAttribute(p);
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end$7 = p.prevEndPos;
+                  var loc$7 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$7,
+                    loc_ghost: false
+                  };
+                  return Ast_helper.Sig.attribute(loc$7, attr);
+              case /* PercentPercent */78 :
+                  var extension = parseExtension(true, p);
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end$8 = p.prevEndPos;
+                  var loc$8 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$8,
+                    loc_ghost: false
+                  };
+                  return Ast_helper.Sig.extension(loc$8, attrs, extension);
+              case /* Private */61 :
+              case /* Mutable */62 :
+              case /* Constraint */63 :
+              case /* Of */66 :
+              case /* Land */67 :
+              case /* Lor */68 :
+              case /* Band */69 :
+              case /* BangEqual */70 :
+              case /* BangEqualEqual */71 :
+              case /* LessEqual */72 :
+              case /* GreaterEqual */73 :
+              case /* ColonEqual */74 :
+              case /* At */75 :
+              case /* Percent */77 :
+              case /* List */79 :
+              case /* Backtick */80 :
+              case /* BarGreater */81 :
+              case /* Try */82 :
+                  break;
+              case /* Import */83 :
+                  Res_parser.next(undefined, p);
+                  continue ;
+              case /* Export */84 :
+                  var signatureItem = parseSignJsExport(attrs, p);
+                  parseNewlineOrSemicolonSignature(p);
+                  var loc_loc_end$9 = p.prevEndPos;
+                  var loc$9 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$9,
+                    loc_ghost: false
+                  };
+                  return {
+                          psig_desc: signatureItem.psig_desc,
+                          psig_loc: loc$9
+                        };
+              
+            }
+          }
+          
+        } else {
+          var exceptionDef = parseExceptionDef(attrs, p);
+          parseNewlineOrSemicolonSignature(p);
+          var loc_loc_end$10 = p.prevEndPos;
+          var loc$10 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$10,
+            loc_ghost: false
+          };
+          return Ast_helper.Sig.exception_(loc$10, exceptionDef);
+        }
+      } else if (match !== 0) {
+        if (match >= 9) {
+          Res_parser.beginRegion(p);
+          var valueDesc = parseSignLetDesc(attrs, p);
+          parseNewlineOrSemicolonSignature(p);
+          var loc_loc_end$11 = p.prevEndPos;
+          var loc$11 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$11,
+            loc_ghost: false
+          };
+          Res_parser.endRegion(p);
+          return Ast_helper.Sig.value(loc$11, valueDesc);
+        }
+        
+      } else {
+        var openDescription = parseOpenDescription(attrs, p);
+        parseNewlineOrSemicolonSignature(p);
+        var loc_loc_end$12 = p.prevEndPos;
+        var loc$12 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$12,
+          loc_ghost: false
+        };
+        return Ast_helper.Sig.open_(loc$12, openDescription);
+      }
+    }
+    if (!attrs) {
+      return ;
+    }
+    var attr$1 = attrs.hd;
+    var attrLoc = attr$1[0].loc;
+    Res_parser.err(attrLoc.loc_start, attrLoc.loc_end, p, Res_diagnostics.message(attributeWithoutNode(attr$1)));
+    return defaultSignatureItem;
+  };
+}
+
+function parseNonSpreadExp(msg, p) {
+  var match = p.token;
+  if (match === 6) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message(msg));
+    Res_parser.next(undefined, p);
+  }
+  var token = p.token;
+  if (!Res_grammar.isExprStart(token)) {
+    return ;
+  }
+  var expr = parseExpr(undefined, p);
+  var match$1 = p.token;
+  if (match$1 !== 24) {
+    return expr;
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = expr.pexp_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.constraint_(loc, undefined, expr, typ);
+}
+
+function parseTypeParameters(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var match = p.token;
+  if (match === 19) {
+    Res_parser.next(undefined, p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    var unitConstr = $$Location.mkloc({
+          TAG: /* Lident */0,
+          _0: "unit"
+        }, loc);
+    var typ = Ast_helper.Typ.constr(undefined, undefined, unitConstr, /* [] */0);
+    return {
+            hd: [
+              false,
+              /* [] */0,
+              /* Nolabel */0,
+              typ,
+              startPos
+            ],
+            tl: /* [] */0
+          };
+  }
+  var params = parseCommaDelimitedRegion(p, /* TypeParameters */42, /* Rparen */19, parseTypeParameter);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  return params;
+}
+
+function parsePatternGuard(p) {
+  var match = p.token;
+  if (typeof match !== "number") {
+    return ;
+  }
+  if (match !== 50 && match !== 56) {
+    return ;
+  }
+  Res_parser.next(undefined, p);
+  return parseExpr(/* WhenExpr */2, p);
+}
+
+function parseRecModuleDeclaration(attrs, startPos, p) {
+  var modName = p.token;
+  var name;
+  var exit = 0;
+  if (typeof modName === "number" || modName.TAG !== /* Uident */5) {
+    exit = 1;
+  } else {
+    var loc_loc_start = p.startPos;
+    var loc_loc_end = p.endPos;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    name = $$Location.mkloc(modName._0, loc);
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(modName));
+    name = $$Location.mknoloc("_");
+  }
+  Res_parser.expect(undefined, /* Colon */24, p);
+  var modType = parseModuleType(undefined, undefined, p);
+  return Ast_helper.Md.mk({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, attrs, undefined, undefined, name, modType);
+}
+
+function parseEs6ArrowExpression(context, parameters, p) {
+  var startPos = p.startPos;
+  Res_parser.leaveBreadcrumb(p, /* Es6ArrowExpr */3);
+  var parameters$1 = parameters !== undefined ? parameters : parseParameters(p);
+  var match = p.token;
+  var returnType = match === 24 ? (Res_parser.next(undefined, p), parseTypExpr(undefined, false, undefined, p)) : undefined;
+  Res_parser.expect(undefined, /* EqualGreater */57, p);
+  var expr = parseExpr(context, p);
+  var body = returnType !== undefined ? Ast_helper.Exp.constraint_({
+          loc_start: expr.pexp_loc.loc_start,
+          loc_end: returnType.ptyp_loc.loc_end,
+          loc_ghost: false
+        }, undefined, expr, returnType) : expr;
+  Res_parser.eatBreadcrumb(p);
+  var endPos = p.prevEndPos;
+  var arrowExpr = List.fold_right((function (parameter, expr) {
+          if (parameter.TAG === /* TermParameter */0) {
+            var attrs = parameter.attrs;
+            var attrs$1 = parameter.uncurried ? ({
+                  hd: uncurryAttr,
+                  tl: attrs
+                }) : attrs;
+            return Ast_helper.Exp.fun_({
+                        loc_start: parameter.pos,
+                        loc_end: endPos,
+                        loc_ghost: false
+                      }, attrs$1, parameter.label, parameter.expr, parameter.pat, expr);
+          }
+          var attrs$2 = parameter.attrs;
+          var attrs$3 = parameter.uncurried ? ({
+                hd: uncurryAttr,
+                tl: attrs$2
+              }) : attrs$2;
+          return makeNewtypes(attrs$3, {
+                      loc_start: parameter.pos,
+                      loc_end: endPos,
+                      loc_ghost: false
+                    }, parameter.locs, expr);
+        }), parameters$1, body);
+  var init = arrowExpr.pexp_loc;
+  return {
+          pexp_desc: arrowExpr.pexp_desc,
+          pexp_loc: {
+            loc_start: startPos,
+            loc_end: init.loc_end,
+            loc_ghost: init.loc_ghost
+          },
+          pexp_attributes: arrowExpr.pexp_attributes
+        };
+}
+
+function parseRecordExprWithStringKeys(startPos, firstRow, p) {
+  var rows_1 = parseCommaDelimitedRegion(p, /* RecordRowsStringKey */44, /* Rbrace */23, parseRecordRowWithStringKey);
+  var rows = {
+    hd: firstRow,
+    tl: rows_1
+  };
+  var loc_loc_end = p.endPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var recordStrExpr = Ast_helper.Str.$$eval(loc, undefined, Ast_helper.Exp.record(loc, undefined, rows, undefined));
+  return Ast_helper.Exp.extension(loc, undefined, [
+              $$Location.mkloc("obj", loc),
+              {
+                TAG: /* PStr */0,
+                _0: {
+                  hd: recordStrExpr,
+                  tl: /* [] */0
+                }
+              }
+            ]);
+}
+
+function parseRecordExpr(startPos, spreadOpt, rows, p) {
+  var spread = spreadOpt !== undefined ? Caml_option.valFromOption(spreadOpt) : undefined;
+  var exprs = parseCommaDelimitedRegion(p, /* RecordRows */43, /* Rbrace */23, parseRecordRow);
+  var rows$1 = List.concat({
+        hd: rows,
+        tl: {
+          hd: exprs,
+          tl: /* [] */0
+        }
+      });
+  if (rows$1) {
+    
+  } else {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message("Record spread needs at least one field that's updated"));
+  }
+  var loc_loc_end = p.endPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.record(loc, undefined, rows$1, spread);
+}
+
+function parseValueOrConstructor(p) {
+  var startPos = p.startPos;
+  var _acc = /* [] */0;
+  while(true) {
+    var acc = _acc;
+    var ident = p.token;
+    if (typeof ident !== "number") {
+      switch (ident.TAG | 0) {
+        case /* Lident */4 :
+            Res_parser.next(undefined, p);
+            var loc_loc_end = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var lident = buildLongident({
+                  hd: ident._0,
+                  tl: acc
+                });
+            return Ast_helper.Exp.ident(loc, undefined, $$Location.mkloc(lident, loc));
+        case /* Uident */5 :
+            var ident$1 = ident._0;
+            var endPosLident = p.endPos;
+            Res_parser.next(undefined, p);
+            var match = p.token;
+            var exit = 0;
+            if (typeof match === "number") {
+              if (match !== 4) {
+                if (match !== 18) {
+                  exit = 2;
+                } else {
+                  if (p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+                    var lparen = p.startPos;
+                    var args = parseConstructorArgs(p);
+                    var rparen = p.prevEndPos;
+                    var lident$1 = buildLongident({
+                          hd: ident$1,
+                          tl: acc
+                        });
+                    var tail;
+                    var exit$1 = 0;
+                    if (args) {
+                      var arg = args.hd;
+                      var tmp = arg.pexp_desc;
+                      if (typeof tmp === "number") {
+                        if (args.tl) {
+                          exit$1 = 3;
+                        } else {
+                          tail = arg;
+                        }
+                      } else if (tmp.TAG === /* Pexp_tuple */8) {
+                        if (args.tl) {
+                          exit$1 = 3;
+                        } else {
+                          var loc$1 = {
+                            loc_start: lparen,
+                            loc_end: rparen,
+                            loc_ghost: false
+                          };
+                          tail = p.mode === /* ParseForTypeChecker */0 ? arg : Ast_helper.Exp.tuple(loc$1, undefined, args);
+                        }
+                      } else if (args.tl) {
+                        exit$1 = 3;
+                      } else {
+                        tail = arg;
+                      }
+                    } else {
+                      tail = undefined;
+                    }
+                    if (exit$1 === 3) {
+                      var loc$2 = {
+                        loc_start: lparen,
+                        loc_end: rparen,
+                        loc_ghost: false
+                      };
+                      tail = Ast_helper.Exp.tuple(loc$2, undefined, args);
+                    }
+                    var loc_loc_end$1 = p.prevEndPos;
+                    var loc$3 = {
+                      loc_start: startPos,
+                      loc_end: loc_loc_end$1,
+                      loc_ghost: false
+                    };
+                    var identLoc = {
+                      loc_start: startPos,
+                      loc_end: endPosLident,
+                      loc_ghost: false
+                    };
+                    return Ast_helper.Exp.construct(loc$3, undefined, $$Location.mkloc(lident$1, identLoc), tail);
+                  }
+                  exit = 2;
+                }
+              } else {
+                Res_parser.next(undefined, p);
+                _acc = {
+                  hd: ident$1,
+                  tl: acc
+                };
+                continue ;
+              }
+            } else {
+              exit = 2;
+            }
+            if (exit === 2) {
+              var loc_loc_end$2 = p.prevEndPos;
+              var loc$4 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$2,
+                loc_ghost: false
+              };
+              var lident$2 = buildLongident({
+                    hd: ident$1,
+                    tl: acc
+                  });
+              return Ast_helper.Exp.construct(loc$4, undefined, $$Location.mkloc(lident$2, loc$4), undefined);
+            }
+            break;
+        default:
+          
+      }
+    }
+    if (acc === /* [] */0) {
+      Res_parser.next(undefined, p);
+      Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident, p.breadcrumbs));
+      return defaultExpr(undefined);
+    }
+    var loc_loc_end$3 = p.prevEndPos;
+    var loc$5 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end$3,
+      loc_ghost: false
+    };
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident, p.breadcrumbs));
+    var lident$3 = buildLongident({
+          hd: "_",
+          tl: acc
+        });
+    return Ast_helper.Exp.ident(loc$5, undefined, $$Location.mkloc(lident$3, loc$5));
+  };
+}
+
+function parseAtomicExpr(p) {
+  Res_parser.leaveBreadcrumb(p, /* ExprOperand */7);
+  var startPos = p.startPos;
+  var token = p.token;
+  var expr;
+  var exit = 0;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* True */1 :
+      case /* False */2 :
+          exit = 2;
+          break;
+      case /* Underscore */12 :
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.lident(token));
+          Res_parser.next(undefined, p);
+          expr = defaultExpr(undefined);
+          break;
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var _t = p.token;
+          if (_t === 19) {
+            Res_parser.next(undefined, p);
+            var loc_loc_end = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            expr = Ast_helper.Exp.construct(loc, undefined, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: "()"
+                    }, loc), undefined);
+          } else {
+            var expr$1 = parseConstrainedOrCoercedExpr(p);
+            var match = p.token;
+            if (match === 25) {
+              Res_parser.next(undefined, p);
+              expr = parseTupleExpr(expr$1, startPos, p);
+            } else {
+              Res_parser.expect(undefined, /* Rparen */19, p);
+              expr = expr$1;
+            }
+          }
+          break;
+      case /* Lbracket */20 :
+          expr = parseArrayExp(p);
+          break;
+      case /* Lbrace */22 :
+          expr = parseBracedOrRecordExpr(p);
+          break;
+      case /* LessThan */42 :
+          expr = parseJsx(p);
+          break;
+      case /* Hash */44 :
+          expr = parsePolyVariantExpr(p);
+          break;
+      case /* Module */65 :
+          Res_parser.next(undefined, p);
+          expr = parseFirstClassModuleExpr(startPos, p);
+          break;
+      case /* Percent */77 :
+          var extension = parseExtension(undefined, p);
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$1 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          expr = Ast_helper.Exp.extension(loc$1, undefined, extension);
+          break;
+      case /* List */79 :
+          Res_parser.next(undefined, p);
+          expr = parseListExpr(startPos, p);
+          break;
+      case /* Backtick */80 :
+          var expr$2 = parseTemplateExpr(undefined, p);
+          expr = {
+            pexp_desc: expr$2.pexp_desc,
+            pexp_loc: {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            },
+            pexp_attributes: expr$2.pexp_attributes
+          };
+          break;
+      default:
+        exit = 1;
+    }
+  } else {
+    switch (token.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+          exit = 3;
+          break;
+      case /* Lident */4 :
+      case /* Uident */5 :
+          expr = parseValueOrConstructor(p);
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        var errPos = p.prevEndPos;
+        Res_parser.err(errPos, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+        var match$1 = skipTokensAndMaybeRetry(p, Res_grammar.isAtomicExprStart);
+        expr = match$1 !== undefined ? parseAtomicExpr(p) : defaultExpr(undefined);
+        break;
+    case 2 :
+        Res_parser.next(undefined, p);
+        var loc_loc_end$2 = p.prevEndPos;
+        var loc$2 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$2,
+          loc_ghost: false
+        };
+        expr = Ast_helper.Exp.construct(loc$2, undefined, $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: Res_token.toString(token)
+                }, loc$2), undefined);
+        break;
+    case 3 :
+        var c = parseConstant(p);
+        var loc_loc_end$3 = p.prevEndPos;
+        var loc$3 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$3,
+          loc_ghost: false
+        };
+        expr = Ast_helper.Exp.constant(loc$3, undefined, c);
+        break;
+    
+  }
+  Res_parser.eatBreadcrumb(p);
+  return expr;
+}
+
+function parseRecordPatternItem(p) {
+  var match = p.token;
+  if (typeof match === "number") {
+    switch (match) {
+      case /* DotDotDot */6 :
+          Res_parser.next(undefined, p);
+          return [
+                  true,
+                  /* PatField */{
+                    _0: parseRecordPatternField(p)
+                  }
+                ];
+      case /* Underscore */12 :
+          Res_parser.next(undefined, p);
+          return [
+                  false,
+                  /* PatUnderscore */0
+                ];
+      default:
+        return ;
+    }
+  } else {
+    switch (match.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return [
+                  false,
+                  /* PatField */{
+                    _0: parseRecordPatternField(p)
+                  }
+                ];
+      default:
+        return ;
+    }
+  }
+}
+
+function parsePackageConstraints(p) {
+  Res_parser.expect(undefined, /* Typ */60, p);
+  var typeConstr = parseValuePath(p);
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var first = [
+    typeConstr,
+    typ
+  ];
+  var rest = parseRegion(p, /* PackageConstraint */32, parsePackageConstraint);
+  return {
+          hd: first,
+          tl: rest
+        };
+}
+
+function parseConstrainedExprRegion(p) {
+  var token = p.token;
+  if (!Res_grammar.isExprStart(token)) {
+    return ;
+  }
+  var expr = parseExpr(undefined, p);
+  var match = p.token;
+  if (match !== 24) {
+    return expr;
+  }
+  Res_parser.next(undefined, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = expr.pexp_loc.loc_start;
+  var loc_loc_end = typ.ptyp_loc.loc_end;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.constraint_(loc, undefined, expr, typ);
+}
+
+function parseTagName(p) {
+  var match = p.token;
+  if (match !== 44) {
+    return ;
+  }
+  var match$1 = parseHashIdent(p.startPos, p);
+  return match$1[0];
+}
+
+function parseFunctorModuleType(p) {
+  var startPos = p.startPos;
+  var args = parseFunctorArgs(p);
+  Res_parser.expect(undefined, /* EqualGreater */57, p);
+  var rhs = parseModuleType(undefined, undefined, p);
+  var endPos = p.prevEndPos;
+  var modType = List.fold_right((function (param, acc) {
+          return Ast_helper.Mty.functor_({
+                      loc_start: param[3],
+                      loc_end: endPos,
+                      loc_ghost: false
+                    }, param[0], param[1], param[2], acc);
+        }), args, rhs);
+  return {
+          pmty_desc: modType.pmty_desc,
+          pmty_loc: {
+            loc_start: startPos,
+            loc_end: endPos,
+            loc_ghost: false
+          },
+          pmty_attributes: modType.pmty_attributes
+        };
+}
+
+function parseWithConstraints(moduleType, p) {
+  var match = p.token;
+  if (typeof match === "number") {
+    return moduleType;
+  }
+  if (match.TAG !== /* Lident */4) {
+    return moduleType;
+  }
+  if (match._0 !== "with") {
+    return moduleType;
+  }
+  Res_parser.next(undefined, p);
+  var first = parseWithConstraint(p);
+  var loop = function (p, _acc) {
+    while(true) {
+      var acc = _acc;
+      var match = p.token;
+      if (match !== 10) {
+        return List.rev(acc);
+      }
+      Res_parser.next(undefined, p);
+      _acc = {
+        hd: parseWithConstraint(p),
+        tl: acc
+      };
+      continue ;
+    };
+  };
+  var constraints = loop(p, {
+        hd: first,
+        tl: /* [] */0
+      });
+  var loc_loc_start = moduleType.pmty_loc.loc_start;
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Mty.with_(loc, undefined, moduleType, constraints);
+}
+
+function parseAtomicModuleType(p) {
+  var startPos = p.startPos;
+  var token = p.token;
+  var moduleType;
+  var exit = 0;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var mty = parseModuleType(undefined, undefined, p);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          moduleType = {
+            pmty_desc: mty.pmty_desc,
+            pmty_loc: {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            },
+            pmty_attributes: mty.pmty_attributes
+          };
+          break;
+      case /* Lbrace */22 :
+          Res_parser.next(undefined, p);
+          var spec = parseDelimitedRegion(p, /* Signature */46, /* Rbrace */23, parseSignatureItemRegion);
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          var loc_loc_end = p.prevEndPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          moduleType = Ast_helper.Mty.signature(loc, undefined, spec);
+          break;
+      case /* Module */65 :
+          moduleType = parseModuleTypeOf(p);
+          break;
+      case /* Percent */77 :
+          var extension = parseExtension(undefined, p);
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$1 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          moduleType = Ast_helper.Mty.extension(loc$1, undefined, extension);
+          break;
+      default:
+        exit = 1;
+    }
+  } else {
+    switch (token.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 2;
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+        moduleType = defaultModuleType(undefined);
+        break;
+    case 2 :
+        var moduleLongIdent = parseModuleLongIdent(true, p);
+        moduleType = Ast_helper.Mty.ident(moduleLongIdent.loc, undefined, moduleLongIdent);
+        break;
+    
+  }
+  var moduleTypeLoc_loc_end = p.prevEndPos;
+  var moduleTypeLoc = {
+    loc_start: startPos,
+    loc_end: moduleTypeLoc_loc_end,
+    loc_ghost: false
+  };
+  return {
+          pmty_desc: moduleType.pmty_desc,
+          pmty_loc: moduleTypeLoc,
+          pmty_attributes: moduleType.pmty_attributes
+        };
+}
+
+function parseFunctorArgs(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var args = parseCommaDelimitedRegion(p, /* FunctorArgs */40, /* Rparen */19, parseFunctorArg);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  if (args) {
+    return args;
+  } else {
+    return {
+            hd: [
+              /* [] */0,
+              $$Location.mkloc("*", {
+                    loc_start: startPos,
+                    loc_end: p.prevEndPos,
+                    loc_ghost: false
+                  }),
+              undefined,
+              startPos
+            ],
+            tl: /* [] */0
+          };
+  }
+}
+
+function parseFunctorArg(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var ident = p.token;
+  if (typeof ident === "number") {
+    switch (ident) {
+      case /* Underscore */12 :
+          Res_parser.next(undefined, p);
+          var argName = $$Location.mkloc("_", {
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              });
+          Res_parser.expect(undefined, /* Colon */24, p);
+          var moduleType = parseModuleType(undefined, undefined, p);
+          return [
+                  attrs,
+                  argName,
+                  moduleType,
+                  startPos
+                ];
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          var argName$1 = $$Location.mkloc("*", {
+                loc_start: startPos,
+                loc_end: p.prevEndPos,
+                loc_ghost: false
+              });
+          return [
+                  attrs,
+                  argName$1,
+                  undefined,
+                  startPos
+                ];
+      default:
+        return ;
+    }
+  } else {
+    if (ident.TAG !== /* Uident */5) {
+      return ;
+    }
+    var ident$1 = ident._0;
+    Res_parser.next(undefined, p);
+    var uidentEndPos = p.prevEndPos;
+    var match = p.token;
+    if (typeof match === "number") {
+      if (match !== 4) {
+        if (match === 24) {
+          Res_parser.next(undefined, p);
+          var moduleType$1 = parseModuleType(undefined, undefined, p);
+          var loc = {
+            loc_start: startPos,
+            loc_end: uidentEndPos,
+            loc_ghost: false
+          };
+          var argName$2 = $$Location.mkloc(ident$1, loc);
+          return [
+                  attrs,
+                  argName$2,
+                  moduleType$1,
+                  startPos
+                ];
+        }
+        
+      } else {
+        Res_parser.next(undefined, p);
+        var moduleLongIdent = parseModuleLongIdentTail(false, p, startPos, {
+              TAG: /* Lident */0,
+              _0: ident$1
+            });
+        var moduleType$2 = Ast_helper.Mty.ident(moduleLongIdent.loc, undefined, moduleLongIdent);
+        var argName$3 = $$Location.mknoloc("_");
+        return [
+                attrs,
+                argName$3,
+                moduleType$2,
+                startPos
+              ];
+      }
+    }
+    var loc$1 = {
+      loc_start: startPos,
+      loc_end: uidentEndPos,
+      loc_ghost: false
+    };
+    var modIdent = $$Location.mkloc({
+          TAG: /* Lident */0,
+          _0: ident$1
+        }, loc$1);
+    var moduleType$3 = Ast_helper.Mty.ident(loc$1, undefined, modIdent);
+    var argName$4 = $$Location.mknoloc("_");
+    return [
+            attrs,
+            argName$4,
+            moduleType$3,
+            startPos
+          ];
+  }
+}
+
+function parsePrivateEqOrRepr(p) {
+  Res_parser.expect(undefined, /* Private */61, p);
+  var t = p.token;
+  var exit = 0;
+  if (typeof t === "number") {
+    switch (t) {
+      case /* DotDot */5 :
+      case /* Bar */17 :
+          exit = 2;
+          break;
+      case /* Lbrace */22 :
+          var match = parseRecordOrObjectDecl(p);
+          return [
+                  match[0],
+                  /* Private */0,
+                  match[2]
+                ];
+      default:
+        exit = 1;
+    }
+  } else {
+    if (t.TAG === /* Uident */5) {
+      var match$1 = parseTypeEquationOrConstrDecl(p);
+      return [
+              match$1[0],
+              /* Private */0,
+              match$1[2]
+            ];
+    }
+    exit = 1;
+  }
+  switch (exit) {
+    case 1 :
+        if (Res_grammar.isTypExprStart(t)) {
+          return [
+                  parseTypExpr(undefined, undefined, undefined, p),
+                  /* Private */0,
+                  /* Ptype_abstract */0
+                ];
+        }
+        var match$2 = parseTypeRepresentation(p);
+        return [
+                undefined,
+                /* Private */0,
+                match$2[1]
+              ];
+    case 2 :
+        var match$3 = parseTypeRepresentation(p);
+        return [
+                undefined,
+                /* Private */0,
+                match$3[1]
+              ];
+    
+  }
+}
+
+function parseAtomicModuleExpr(p) {
+  var startPos = p.startPos;
+  var _ident = p.token;
+  if (typeof _ident === "number") {
+    switch (_ident) {
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var match = p.token;
+          var modExpr = match === 19 ? Ast_helper.Mod.structure({
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                }, undefined, /* [] */0) : parseConstrainedModExpr(p);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          return modExpr;
+      case /* Lbrace */22 :
+          Res_parser.next(undefined, p);
+          var structure = Ast_helper.Mod.structure(undefined, undefined, parseDelimitedRegion(p, /* Structure */48, /* Rbrace */23, parseStructureItemRegion));
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          var endPos = p.prevEndPos;
+          return {
+                  pmod_desc: structure.pmod_desc,
+                  pmod_loc: {
+                    loc_start: startPos,
+                    loc_end: endPos,
+                    loc_ghost: false
+                  },
+                  pmod_attributes: structure.pmod_attributes
+                };
+      case /* Percent */77 :
+          var extension = parseExtension(undefined, p);
+          var loc_loc_end = p.prevEndPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          return Ast_helper.Mod.extension(loc, undefined, extension);
+      default:
+        
+    }
+  } else {
+    switch (_ident.TAG | 0) {
+      case /* Lident */4 :
+          if (_ident._0 === "unpack") {
+            Res_parser.next(undefined, p);
+            Res_parser.expect(undefined, /* Lparen */18, p);
+            var expr = parseExpr(undefined, p);
+            var match$1 = p.token;
+            if (match$1 === 24) {
+              var colonStart = p.startPos;
+              Res_parser.next(undefined, p);
+              var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+              var packageType = parsePackageType(colonStart, attrs, p);
+              Res_parser.expect(undefined, /* Rparen */19, p);
+              var loc_loc_end$1 = p.prevEndPos;
+              var loc$1 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$1,
+                loc_ghost: false
+              };
+              var constraintExpr = Ast_helper.Exp.constraint_(loc$1, undefined, expr, packageType);
+              return Ast_helper.Mod.unpack(loc$1, undefined, constraintExpr);
+            }
+            Res_parser.expect(undefined, /* Rparen */19, p);
+            var loc_loc_end$2 = p.prevEndPos;
+            var loc$2 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$2,
+              loc_ghost: false
+            };
+            return Ast_helper.Mod.unpack(loc$2, undefined, expr);
+          }
+          break;
+      case /* Uident */5 :
+          var longident = parseModuleLongIdent(false, p);
+          return Ast_helper.Mod.ident(longident.loc, undefined, longident);
+      default:
+        
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(_ident, p.breadcrumbs));
+  return defaultModuleExpr(undefined);
+}
+
+function parseModuleApplication(p, modExpr) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var args = parseCommaDelimitedRegion(p, /* ModExprList */41, /* Rparen */19, parseConstrainedModExprRegion);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var args$1;
+  if (args) {
+    args$1 = args;
+  } else {
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    args$1 = {
+      hd: Ast_helper.Mod.structure(loc, undefined, /* [] */0),
+      tl: /* [] */0
+    };
+  }
+  return List.fold_left((function (modExpr, arg) {
+                return Ast_helper.Mod.apply({
+                            loc_start: modExpr.pmod_loc.loc_start,
+                            loc_end: arg.pmod_loc.loc_end,
+                            loc_ghost: false
+                          }, undefined, modExpr, arg);
+              }), modExpr, args$1);
+}
+
+function parseTemplateExpr(prefixOpt, p) {
+  var prefix = prefixOpt !== undefined ? prefixOpt : "js";
+  var op = $$Location.mknoloc({
+        TAG: /* Lident */0,
+        _0: "^"
+      });
+  var hiddenOperator = Ast_helper.Exp.ident(undefined, undefined, op);
+  var startPos = p.startPos;
+  Res_parser.nextTemplateLiteralToken(p);
+  var txt = p.token;
+  if (typeof txt !== "number") {
+    switch (txt.TAG | 0) {
+      case /* TemplateTail */7 :
+          var txt$1 = txt._0;
+          Res_parser.next(undefined, p);
+          var txt$2 = p.mode === /* ParseForTypeChecker */0 ? parseTemplateStringLiteral(txt$1) : txt$1;
+          return Ast_helper.Exp.constant({
+                      loc_start: startPos,
+                      loc_end: p.prevEndPos,
+                      loc_ghost: false
+                    }, {
+                      hd: templateLiteralAttr,
+                      tl: /* [] */0
+                    }, {
+                      TAG: /* Pconst_string */2,
+                      _0: txt$2,
+                      _1: prefix
+                    });
+      case /* TemplatePart */8 :
+          var txt$3 = txt._0;
+          Res_parser.next(undefined, p);
+          var constantLoc_loc_end = p.prevEndPos;
+          var constantLoc = {
+            loc_start: startPos,
+            loc_end: constantLoc_loc_end,
+            loc_ghost: false
+          };
+          var expr = parseExprBlock(undefined, p);
+          var fullLoc_loc_end = p.prevEndPos;
+          var fullLoc = {
+            loc_start: startPos,
+            loc_end: fullLoc_loc_end,
+            loc_ghost: false
+          };
+          var txt$4 = p.mode === /* ParseForTypeChecker */0 ? parseTemplateStringLiteral(txt$3) : txt$3;
+          var str = Ast_helper.Exp.constant(constantLoc, {
+                hd: templateLiteralAttr,
+                tl: /* [] */0
+              }, {
+                TAG: /* Pconst_string */2,
+                _0: txt$4,
+                _1: prefix
+              });
+          var _acc = Ast_helper.Exp.apply(fullLoc, {
+                hd: templateLiteralAttr,
+                tl: /* [] */0
+              }, hiddenOperator, {
+                hd: [
+                  /* Nolabel */0,
+                  str
+                ],
+                tl: {
+                  hd: [
+                    /* Nolabel */0,
+                    expr
+                  ],
+                  tl: /* [] */0
+                }
+              });
+          while(true) {
+            var acc = _acc;
+            var startPos$1 = p.startPos;
+            Res_parser.nextTemplateLiteralToken(p);
+            var txt$5 = p.token;
+            if (typeof txt$5 !== "number") {
+              switch (txt$5.TAG | 0) {
+                case /* TemplateTail */7 :
+                    var txt$6 = txt$5._0;
+                    Res_parser.next(undefined, p);
+                    var loc_loc_end = p.prevEndPos;
+                    var loc = {
+                      loc_start: startPos$1,
+                      loc_end: loc_loc_end,
+                      loc_ghost: false
+                    };
+                    var txt$7 = p.mode === /* ParseForTypeChecker */0 ? parseTemplateStringLiteral(txt$6) : txt$6;
+                    var str$1 = Ast_helper.Exp.constant(loc, {
+                          hd: templateLiteralAttr,
+                          tl: /* [] */0
+                        }, {
+                          TAG: /* Pconst_string */2,
+                          _0: txt$7,
+                          _1: prefix
+                        });
+                    return Ast_helper.Exp.apply(loc, {
+                                hd: templateLiteralAttr,
+                                tl: /* [] */0
+                              }, hiddenOperator, {
+                                hd: [
+                                  /* Nolabel */0,
+                                  acc
+                                ],
+                                tl: {
+                                  hd: [
+                                    /* Nolabel */0,
+                                    str$1
+                                  ],
+                                  tl: /* [] */0
+                                }
+                              });
+                case /* TemplatePart */8 :
+                    var txt$8 = txt$5._0;
+                    Res_parser.next(undefined, p);
+                    var loc_loc_end$1 = p.prevEndPos;
+                    var loc$1 = {
+                      loc_start: startPos$1,
+                      loc_end: loc_loc_end$1,
+                      loc_ghost: false
+                    };
+                    var expr$1 = parseExprBlock(undefined, p);
+                    var fullLoc_loc_end$1 = p.prevEndPos;
+                    var fullLoc$1 = {
+                      loc_start: startPos$1,
+                      loc_end: fullLoc_loc_end$1,
+                      loc_ghost: false
+                    };
+                    var txt$9 = p.mode === /* ParseForTypeChecker */0 ? parseTemplateStringLiteral(txt$8) : txt$8;
+                    var str$2 = Ast_helper.Exp.constant(loc$1, {
+                          hd: templateLiteralAttr,
+                          tl: /* [] */0
+                        }, {
+                          TAG: /* Pconst_string */2,
+                          _0: txt$9,
+                          _1: prefix
+                        });
+                    var a = Ast_helper.Exp.apply(fullLoc$1, {
+                          hd: templateLiteralAttr,
+                          tl: /* [] */0
+                        }, hiddenOperator, {
+                          hd: [
+                            /* Nolabel */0,
+                            acc
+                          ],
+                          tl: {
+                            hd: [
+                              /* Nolabel */0,
+                              str$2
+                            ],
+                            tl: /* [] */0
+                          }
+                        });
+                    _acc = Ast_helper.Exp.apply(fullLoc$1, undefined, hiddenOperator, {
+                          hd: [
+                            /* Nolabel */0,
+                            a
+                          ],
+                          tl: {
+                            hd: [
+                              /* Nolabel */0,
+                              expr$1
+                            ],
+                            tl: /* [] */0
+                          }
+                        });
+                    continue ;
+                default:
+                  
+              }
+            }
+            Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(txt$5, p.breadcrumbs));
+            return Ast_helper.Exp.constant(undefined, undefined, {
+                        TAG: /* Pconst_string */2,
+                        _0: "",
+                        _1: undefined
+                      });
+          };
+      default:
+        
+    }
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(txt, p.breadcrumbs));
+  return Ast_helper.Exp.constant(undefined, undefined, {
+              TAG: /* Pconst_string */2,
+              _0: "",
+              _1: undefined
+            });
+}
+
+function parseCallExpr(p, funExpr) {
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var startPos = p.startPos;
+  Res_parser.leaveBreadcrumb(p, /* ExprCall */11);
+  var args = parseCommaDelimitedRegion(p, /* ArgumentList */45, /* Rparen */19, parseArgument);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var args$1;
+  if (args) {
+    var match = args.hd;
+    if (match[0] && typeof match[1] === "number") {
+      var expr = match[2];
+      var match$1 = expr.pexp_desc;
+      if (typeof match$1 === "number" || match$1.TAG !== /* Pexp_construct */9) {
+        args$1 = args;
+      } else {
+        var match$2 = match$1._0.txt;
+        switch (match$2.TAG | 0) {
+          case /* Lident */0 :
+              args$1 = match$2._0 === "()" && !(match$1._1 !== undefined || expr.pexp_attributes || args.tl || !(!expr.pexp_loc.loc_ghost && p.mode === /* ParseForTypeChecker */0)) ? ({
+                    hd: [
+                      true,
+                      /* Nolabel */0,
+                      Ast_helper.Exp.let_(undefined, undefined, /* Nonrecursive */0, {
+                            hd: Ast_helper.Vb.mk(undefined, undefined, undefined, undefined, Ast_helper.Pat.$$var(undefined, undefined, $$Location.mknoloc("__res_unit")), expr),
+                            tl: /* [] */0
+                          }, Ast_helper.Exp.ident(undefined, undefined, $$Location.mknoloc({
+                                    TAG: /* Lident */0,
+                                    _0: "__res_unit"
+                                  })))
+                    ],
+                    tl: /* [] */0
+                  }) : args;
+              break;
+          case /* Ldot */1 :
+          case /* Lapply */2 :
+              args$1 = args;
+              break;
+          
+        }
+      }
+    } else {
+      args$1 = args;
+    }
+  } else {
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    args$1 = {
+      hd: [
+        false,
+        /* Nolabel */0,
+        Ast_helper.Exp.construct(loc, undefined, $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: "()"
+                }, loc), undefined)
+      ],
+      tl: /* [] */0
+    };
+  }
+  var init = funExpr.pexp_loc;
+  var loc_loc_start = init.loc_start;
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc_loc_ghost = init.loc_ghost;
+  var loc$1 = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end$1,
+    loc_ghost: loc_loc_ghost
+  };
+  var args$2;
+  if (args$1) {
+    var match$3 = args$1.hd;
+    var group = function (param, param$1) {
+      var grp = param[0];
+      var grp$1 = grp[1];
+      var _u = grp[0];
+      var expr = param$1[2];
+      var lbl = param$1[1];
+      var acc = param[1];
+      if (param$1[0] === true) {
+        return [
+                [
+                  true,
+                  {
+                    hd: [
+                      lbl,
+                      expr
+                    ],
+                    tl: /* [] */0
+                  }
+                ],
+                {
+                  hd: [
+                    _u,
+                    List.rev(grp$1)
+                  ],
+                  tl: acc
+                }
+              ];
+      } else {
+        return [
+                [
+                  _u,
+                  {
+                    hd: [
+                      lbl,
+                      expr
+                    ],
+                    tl: grp$1
+                  }
+                ],
+                acc
+              ];
+      }
+    };
+    var match$4 = List.fold_left(group, [
+          [
+            match$3[0],
+            {
+              hd: [
+                match$3[1],
+                match$3[2]
+              ],
+              tl: /* [] */0
+            }
+          ],
+          /* [] */0
+        ], args$1.tl);
+    var match$5 = match$4[0];
+    args$2 = List.rev({
+          hd: [
+            match$5[0],
+            List.rev(match$5[1])
+          ],
+          tl: match$4[1]
+        });
+  } else {
+    args$2 = /* [] */0;
+  }
+  var apply = List.fold_left((function (callBody, group) {
+          var match = processUnderscoreApplication(group[1]);
+          var args = match[0];
+          var tmp;
+          if (group[0]) {
+            var attrs = {
+              hd: uncurryAttr,
+              tl: /* [] */0
+            };
+            tmp = Ast_helper.Exp.apply(loc$1, attrs, callBody, args);
+          } else {
+            tmp = Ast_helper.Exp.apply(loc$1, undefined, callBody, args);
+          }
+          return Curry._1(match[1], tmp);
+        }), funExpr, args$2);
+  Res_parser.eatBreadcrumb(p);
+  return apply;
+}
+
+function parseBracketAccess(p, expr, startPos) {
+  Res_parser.leaveBreadcrumb(p, /* ExprArrayAccess */13);
+  var lbracket = p.startPos;
+  Res_parser.next(undefined, p);
+  var stringStart = p.startPos;
+  var s = p.token;
+  if (typeof s !== "number" && s.TAG === /* String */3) {
+    var s$1 = s._0;
+    var s$2 = p.mode === /* ParseForTypeChecker */0 ? parseStringLiteral(s$1) : s$1;
+    Res_parser.next(undefined, p);
+    var stringEnd = p.prevEndPos;
+    Res_parser.expect(undefined, /* Rbracket */21, p);
+    Res_parser.eatBreadcrumb(p);
+    var rbracket = p.prevEndPos;
+    var identLoc = {
+      loc_start: stringStart,
+      loc_end: stringEnd,
+      loc_ghost: false
+    };
+    var loc = {
+      loc_start: startPos,
+      loc_end: rbracket,
+      loc_ghost: false
+    };
+    var e = Ast_helper.Exp.send(loc, undefined, expr, $$Location.mkloc(s$2, identLoc));
+    var e$1 = parsePrimaryExpr(e, undefined, p);
+    var equalStart = p.startPos;
+    var match = p.token;
+    if (match !== 14) {
+      return e$1;
+    }
+    Res_parser.next(undefined, p);
+    var equalEnd = p.prevEndPos;
+    var rhsExpr = parseExpr(undefined, p);
+    var loc_loc_end = rhsExpr.pexp_loc.loc_end;
+    var loc$1 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    var operatorLoc = {
+      loc_start: equalStart,
+      loc_end: equalEnd,
+      loc_ghost: false
+    };
+    return Ast_helper.Exp.apply(loc$1, undefined, Ast_helper.Exp.ident(operatorLoc, undefined, $$Location.mkloc({
+                        TAG: /* Lident */0,
+                        _0: "#="
+                      }, operatorLoc)), {
+                hd: [
+                  /* Nolabel */0,
+                  e$1
+                ],
+                tl: {
+                  hd: [
+                    /* Nolabel */0,
+                    rhsExpr
+                  ],
+                  tl: /* [] */0
+                }
+              });
+  }
+  var accessExpr = parseConstrainedOrCoercedExpr(p);
+  Res_parser.expect(undefined, /* Rbracket */21, p);
+  Res_parser.eatBreadcrumb(p);
+  var rbracket$1 = p.prevEndPos;
+  var arrayLoc = {
+    loc_start: lbracket,
+    loc_end: rbracket$1,
+    loc_ghost: false
+  };
+  var match$1 = p.token;
+  if (match$1 === 14) {
+    Res_parser.leaveBreadcrumb(p, /* ExprArrayMutation */14);
+    Res_parser.next(undefined, p);
+    var rhsExpr$1 = parseExpr(undefined, p);
+    var arraySet = $$Location.mkloc({
+          TAG: /* Ldot */1,
+          _0: {
+            TAG: /* Lident */0,
+            _0: "Array"
+          },
+          _1: "set"
+        }, arrayLoc);
+    var endPos = p.prevEndPos;
+    var arraySet$1 = Ast_helper.Exp.apply({
+          loc_start: startPos,
+          loc_end: endPos,
+          loc_ghost: false
+        }, undefined, Ast_helper.Exp.ident(arrayLoc, undefined, arraySet), {
+          hd: [
+            /* Nolabel */0,
+            expr
+          ],
+          tl: {
+            hd: [
+              /* Nolabel */0,
+              accessExpr
+            ],
+            tl: {
+              hd: [
+                /* Nolabel */0,
+                rhsExpr$1
+              ],
+              tl: /* [] */0
+            }
+          }
+        });
+    Res_parser.eatBreadcrumb(p);
+    return arraySet$1;
+  }
+  var endPos$1 = p.prevEndPos;
+  var e$2 = Ast_helper.Exp.apply({
+        loc_start: startPos,
+        loc_end: endPos$1,
+        loc_ghost: false
+      }, undefined, Ast_helper.Exp.ident(arrayLoc, undefined, $$Location.mkloc({
+                TAG: /* Ldot */1,
+                _0: {
+                  TAG: /* Lident */0,
+                  _0: "Array"
+                },
+                _1: "get"
+              }, arrayLoc)), {
+        hd: [
+          /* Nolabel */0,
+          expr
+        ],
+        tl: {
+          hd: [
+            /* Nolabel */0,
+            accessExpr
+          ],
+          tl: /* [] */0
+        }
+      });
+  return parsePrimaryExpr(e$2, undefined, p);
+}
+
+function parseRecordOrObjectType(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var match = p.token;
+  var closedFlag = typeof match === "number" ? (
+      match !== 4 ? (
+          match !== 5 ? /* Closed */0 : (Res_parser.next(undefined, p), /* Open */1)
+        ) : (Res_parser.next(undefined, p), /* Closed */0)
+    ) : /* Closed */0;
+  var match$1 = p.token;
+  if (typeof match$1 !== "number" && match$1.TAG === /* Lident */4) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message(forbiddenInlineRecordDeclaration));
+  }
+  var startFirstField = p.startPos;
+  var fields = parseCommaDelimitedRegion(p, /* StringFieldDeclarations */37, /* Rbrace */23, parseStringFieldDeclaration);
+  if (fields) {
+    var match$2 = fields.hd;
+    if (match$2.TAG !== /* Otag */0) {
+      if (fields.tl) {
+        
+      } else {
+        Res_parser.err(startFirstField, match$2._0.ptyp_loc.loc_end, p, Res_diagnostics.message(sameTypeSpread));
+      }
+    }
+    
+  }
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Typ.object_(loc, attrs, fields, closedFlag);
+}
+
+function parseAtomicTypExpr(attrs, p) {
+  Res_parser.leaveBreadcrumb(p, /* AtomicTypExpr */52);
+  var startPos = p.startPos;
+  var token = p.token;
+  var typ;
+  var exit = 0;
+  if (typeof token === "number") {
+    switch (token) {
+      case /* Underscore */12 :
+          var endPos = p.endPos;
+          Res_parser.next(undefined, p);
+          typ = Ast_helper.Typ.any({
+                loc_start: startPos,
+                loc_end: endPos,
+                loc_ghost: false
+              }, attrs, undefined);
+          break;
+      case /* SingleQuote */13 :
+          Res_parser.next(undefined, p);
+          var match = parseIdent(typeVar, p.startPos, p);
+          typ = Ast_helper.Typ.$$var(match[1], attrs, match[0]);
+          break;
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var match$1 = p.token;
+          if (match$1 === 19) {
+            Res_parser.next(undefined, p);
+            var loc_loc_end = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var unitConstr = $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: "unit"
+                }, loc);
+            typ = Ast_helper.Typ.constr(undefined, attrs, unitConstr, /* [] */0);
+          } else {
+            var t = parseTypExpr(undefined, undefined, undefined, p);
+            var match$2 = p.token;
+            if (match$2 === 25) {
+              Res_parser.next(undefined, p);
+              typ = parseTupleType(attrs, t, startPos, p);
+            } else {
+              Res_parser.expect(undefined, /* Rparen */19, p);
+              typ = {
+                ptyp_desc: t.ptyp_desc,
+                ptyp_loc: {
+                  loc_start: startPos,
+                  loc_end: p.prevEndPos,
+                  loc_ghost: false
+                },
+                ptyp_attributes: List.concat({
+                      hd: attrs,
+                      tl: {
+                        hd: t.ptyp_attributes,
+                        tl: /* [] */0
+                      }
+                    })
+              };
+            }
+          }
+          break;
+      case /* Lbracket */20 :
+          typ = parsePolymorphicVariantType(attrs, p);
+          break;
+      case /* Lbrace */22 :
+          typ = parseRecordOrObjectType(attrs, p);
+          break;
+      case /* Module */65 :
+          Res_parser.next(undefined, p);
+          Res_parser.expect(undefined, /* Lparen */18, p);
+          var packageType = parsePackageType(startPos, attrs, p);
+          Res_parser.expect(undefined, /* Rparen */19, p);
+          typ = {
+            ptyp_desc: packageType.ptyp_desc,
+            ptyp_loc: {
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            },
+            ptyp_attributes: packageType.ptyp_attributes
+          };
+          break;
+      case /* Percent */77 :
+          var extension = parseExtension(undefined, p);
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$1 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          typ = Ast_helper.Typ.extension(loc$1, attrs, extension);
+          break;
+      default:
+        exit = 1;
+    }
+  } else {
+    switch (token.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 2;
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+        var match$3 = skipTokensAndMaybeRetry(p, Res_grammar.isAtomicTypExprStart);
+        if (match$3 !== undefined) {
+          typ = parseAtomicTypExpr(attrs, p);
+        } else {
+          Res_parser.err(p.prevEndPos, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+          typ = defaultType(undefined);
+        }
+        break;
+    case 2 :
+        var constr = parseValuePath(p);
+        var args = parseTypeConstructorArgs(constr, p);
+        typ = Ast_helper.Typ.constr({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, attrs, constr, args);
+        break;
+    
+  }
+  Res_parser.eatBreadcrumb(p);
+  return typ;
+}
+
+function skipTokensAndMaybeRetry(p, isStartOfGrammar) {
+  if (Res_token.isKeyword(p.token) && p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+    Res_parser.next(undefined, p);
+    return ;
+  }
+  if (shouldAbortListParse(p)) {
+    if (Curry._1(isStartOfGrammar, p.token)) {
+      Res_parser.next(undefined, p);
+      return Caml_option.some(undefined);
+    } else {
+      return ;
+    }
+  }
+  Res_parser.next(undefined, p);
+  var loop = function (p) {
+    while(true) {
+      if (shouldAbortListParse(p)) {
+        return ;
+      }
+      Res_parser.next(undefined, p);
+      continue ;
+    };
+  };
+  loop(p);
+  if (Curry._1(isStartOfGrammar, p.token)) {
+    return Caml_option.some(undefined);
+  }
+  
+}
+
+function parsePolymorphicVariantType(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbracket */20, p);
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match !== 41) {
+      if (match === 42) {
+        Res_parser.next(undefined, p);
+        Res_parser.optional(p, /* Bar */17);
+        var rowField = parseTagSpecFull(p);
+        var rowFields = parseTagSpecFulls(p);
+        var tagNames = parseTagNames(p);
+        var loc_loc_end = p.prevEndPos;
+        var loc = {
+          loc_start: startPos,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        var variant = Ast_helper.Typ.variant(loc, attrs, {
+              hd: rowField,
+              tl: rowFields
+            }, /* Closed */0, tagNames);
+        Res_parser.expect(undefined, /* Rbracket */21, p);
+        return variant;
+      }
+      
+    } else {
+      Res_parser.next(undefined, p);
+      var match$1 = p.token;
+      var rowFields$1;
+      var exit = 0;
+      if (typeof match$1 === "number") {
+        if (match$1 !== 17) {
+          if (match$1 !== 21) {
+            exit = 2;
+          } else {
+            rowFields$1 = /* [] */0;
+          }
+        } else {
+          rowFields$1 = parseTagSpecs(p);
+        }
+      } else {
+        exit = 2;
+      }
+      if (exit === 2) {
+        var rowField$1 = parseTagSpec(p);
+        rowFields$1 = {
+          hd: rowField$1,
+          tl: parseTagSpecs(p)
+        };
+      }
+      var loc_loc_end$1 = p.prevEndPos;
+      var loc$1 = {
+        loc_start: startPos,
+        loc_end: loc_loc_end$1,
+        loc_ghost: false
+      };
+      var variant$1 = Ast_helper.Typ.variant(loc$1, attrs, rowFields$1, /* Open */1, undefined);
+      Res_parser.expect(undefined, /* Rbracket */21, p);
+      return variant$1;
+    }
+  }
+  var rowFields1 = parseTagSpecFirst(p);
+  var rowFields2 = parseTagSpecs(p);
+  var loc_loc_end$2 = p.prevEndPos;
+  var loc$2 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$2,
+    loc_ghost: false
+  };
+  var variant$2 = Ast_helper.Typ.variant(loc$2, attrs, Pervasives.$at(rowFields1, rowFields2), /* Closed */0, undefined);
+  Res_parser.expect(undefined, /* Rbracket */21, p);
+  return variant$2;
+}
+
+function parseTupleType(attrs, first, startPos, p) {
+  var typexprs_1 = parseCommaDelimitedRegion(p, /* TypExprList */39, /* Rparen */19, parseTypExprRegion);
+  var typexprs = {
+    hd: first,
+    tl: typexprs_1
+  };
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  if (typexprs_1) {
+    
+  } else {
+    Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(tupleSingleElement));
+  }
+  var tupleLoc_loc_end = p.prevEndPos;
+  var tupleLoc = {
+    loc_start: startPos,
+    loc_end: tupleLoc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Typ.tuple(tupleLoc, attrs, typexprs);
+}
+
+function parseTypeParams(parent, p) {
+  var opening = p.token;
+  if (typeof opening !== "number") {
+    return /* [] */0;
+  }
+  if (opening !== 18 && opening !== 42) {
+    return /* [] */0;
+  }
+  if (p.startPos.pos_lnum !== p.prevEndPos.pos_lnum) {
+    return /* [] */0;
+  }
+  Res_scanner.setDiamondMode(p.scanner);
+  var openingStartPos = p.startPos;
+  Res_parser.leaveBreadcrumb(p, /* TypeParams */30);
+  Res_parser.next(undefined, p);
+  var params = parseCommaDelimitedRegion(p, /* TypeParams */30, /* GreaterThan */41, parseTypeParam);
+  var match = p.token;
+  if (match === 19 && opening === /* Lparen */18) {
+    var msg = Res_doc.toString(80, Res_doc.breakableGroup(true, Res_doc.concat({
+                  hd: Res_doc.text("Type parameters require angle brackets:"),
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.line,
+                              tl: {
+                                hd: Res_doc.concat({
+                                      hd: Res_printer.printLongident(parent.txt),
+                                      tl: {
+                                        hd: Res_printer.printTypeParams(params, Res_comments_table.empty),
+                                        tl: /* [] */0
+                                      }
+                                    }),
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                })));
+    Res_parser.err(openingStartPos, undefined, p, Res_diagnostics.message(msg));
+    Res_parser.next(undefined, p);
+  } else {
+    Res_parser.expect(undefined, /* GreaterThan */41, p);
+  }
+  Res_scanner.popMode(p.scanner, /* Diamond */1);
+  Res_parser.eatBreadcrumb(p);
+  return params;
+}
+
+function parseConstructorArgs(p) {
+  var lparen = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var args = parseCommaDelimitedRegion(p, /* ExprList */12, /* Rparen */19, parseConstrainedExprRegion);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  if (args) {
+    return args;
+  }
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: lparen,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return {
+          hd: Ast_helper.Exp.construct(loc, undefined, $$Location.mkloc({
+                    TAG: /* Lident */0,
+                    _0: "()"
+                  }, loc), undefined),
+          tl: /* [] */0
+        };
+}
+
+function parseParameters(p) {
+  var startPos = p.startPos;
+  var ident = p.token;
+  if (typeof ident === "number") {
+    switch (ident) {
+      case /* Underscore */12 :
+          Res_parser.next(undefined, p);
+          var loc_loc_end = p.prevEndPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          return {
+                  hd: {
+                    TAG: /* TermParameter */0,
+                    uncurried: false,
+                    attrs: /* [] */0,
+                    label: /* Nolabel */0,
+                    expr: undefined,
+                    pat: Ast_helper.Pat.any(loc, undefined, undefined),
+                    pos: startPos
+                  },
+                  tl: /* [] */0
+                };
+      case /* Lparen */18 :
+          Res_parser.next(undefined, p);
+          var match = p.token;
+          if (typeof match !== "number") {
+            return parseParameterList(p);
+          }
+          if (match !== 4) {
+            if (match !== 19) {
+              return parseParameterList(p);
+            }
+            Res_parser.next(undefined, p);
+            var loc_loc_end$1 = p.prevEndPos;
+            var loc$1 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$1,
+              loc_ghost: false
+            };
+            var unitPattern = Ast_helper.Pat.construct(loc$1, undefined, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: "()"
+                    }, loc$1), undefined);
+            return {
+                    hd: {
+                      TAG: /* TermParameter */0,
+                      uncurried: false,
+                      attrs: /* [] */0,
+                      label: /* Nolabel */0,
+                      expr: undefined,
+                      pat: unitPattern,
+                      pos: startPos
+                    },
+                    tl: /* [] */0
+                  };
+          }
+          Res_parser.next(undefined, p);
+          var match$1 = p.token;
+          if (match$1 === 19) {
+            Res_parser.next(undefined, p);
+            var loc_loc_end$2 = p.prevEndPos;
+            var loc$2 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$2,
+              loc_ghost: false
+            };
+            var unitPattern$1 = Ast_helper.Pat.construct(loc$2, undefined, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: "()"
+                    }, loc$2), undefined);
+            return {
+                    hd: {
+                      TAG: /* TermParameter */0,
+                      uncurried: true,
+                      attrs: /* [] */0,
+                      label: /* Nolabel */0,
+                      expr: undefined,
+                      pat: unitPattern$1,
+                      pos: startPos
+                    },
+                    tl: /* [] */0
+                  };
+          }
+          var parameters = parseParameterList(p);
+          if (!parameters) {
+            return parameters;
+          }
+          var match$2 = parameters.hd;
+          if (match$2.TAG === /* TermParameter */0) {
+            return {
+                    hd: {
+                      TAG: /* TermParameter */0,
+                      uncurried: true,
+                      attrs: match$2.attrs,
+                      label: match$2.label,
+                      expr: match$2.expr,
+                      pat: match$2.pat,
+                      pos: match$2.pos
+                    },
+                    tl: parameters.tl
+                  };
+          } else {
+            return parameters;
+          }
+      default:
+        
+    }
+  } else if (ident.TAG === /* Lident */4) {
+    Res_parser.next(undefined, p);
+    var loc_loc_end$3 = p.prevEndPos;
+    var loc$3 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end$3,
+      loc_ghost: false
+    };
+    return {
+            hd: {
+              TAG: /* TermParameter */0,
+              uncurried: false,
+              attrs: /* [] */0,
+              label: /* Nolabel */0,
+              expr: undefined,
+              pat: Ast_helper.Pat.$$var(loc$3, undefined, $$Location.mkloc(ident._0, loc$3)),
+              pos: startPos
+            },
+            tl: /* [] */0
+          };
+  }
+  Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(ident, p.breadcrumbs));
+  return /* [] */0;
+}
+
+function parseSignLetDesc(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.optional(p, /* Let */9);
+  var match = parseLident(p);
+  var name = $$Location.mkloc(match[0], match[1]);
+  Res_parser.expect(undefined, /* Colon */24, p);
+  var typExpr = parsePolyTypeExpr(p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Val.mk(loc, attrs, undefined, undefined, name, typExpr);
+}
+
+function parseCoercedExpr(expr, p) {
+  Res_parser.expect(undefined, /* ColonGreaterThan */40, p);
+  var typ = parseTypExpr(undefined, undefined, undefined, p);
+  var loc_loc_start = expr.pexp_loc.loc_start;
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.coerce(loc, undefined, expr, undefined, typ);
+}
+
+function parseMaybeRecModuleBinding(attrs, startPos, p) {
+  var match = p.token;
+  if (match === 11) {
+    Res_parser.next(undefined, p);
+    return Ast_helper.Str.rec_module(undefined, parseModuleBindings(attrs, startPos, p));
+  } else {
+    return Ast_helper.Str.module_(undefined, parseModuleBinding(attrs, p.startPos, p));
+  }
+}
+
+function parseModuleTypeImpl(attrs, startPos, p) {
+  Res_parser.expect(undefined, /* Typ */60, p);
+  var nameStart = p.startPos;
+  var ident = p.token;
+  var name;
+  var exit = 0;
+  if (typeof ident === "number") {
+    exit = 2;
+  } else {
+    switch (ident.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 1;
+          break;
+      default:
+        exit = 2;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        Res_parser.next(undefined, p);
+        var loc_loc_end = p.prevEndPos;
+        var loc = {
+          loc_start: nameStart,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        name = $$Location.mkloc(ident._0, loc);
+        break;
+    case 2 :
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+        name = $$Location.mknoloc("_");
+        break;
+    
+  }
+  Res_parser.expect(undefined, /* Equal */14, p);
+  var moduleType = parseModuleType(undefined, undefined, p);
+  var moduleTypeDeclaration = Ast_helper.Mtd.mk({
+        loc_start: nameStart,
+        loc_end: p.prevEndPos,
+        loc_ghost: false
+      }, attrs, undefined, undefined, moduleType, name);
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Str.modtype(loc$1, moduleTypeDeclaration);
+}
+
+function parseIfCondition(p) {
+  Res_parser.leaveBreadcrumb(p, /* IfCondition */17);
+  var conditionExpr = parseExpr(/* WhenExpr */2, p);
+  Res_parser.eatBreadcrumb(p);
+  return conditionExpr;
+}
+
+function parseIfOrIfLetExpression(p) {
+  Res_parser.beginRegion(p);
+  Res_parser.leaveBreadcrumb(p, /* ExprIf */15);
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* If */50, p);
+  var match = p.token;
+  var expr;
+  if (match === 9) {
+    Res_parser.next(undefined, p);
+    var ifLetExpr = parseIfLetExpr(startPos, p);
+    Res_parser.err(ifLetExpr.pexp_loc.loc_start, ifLetExpr.pexp_loc.loc_end, p, Res_diagnostics.message(experimentalIfLet(ifLetExpr)));
+    expr = ifLetExpr;
+  } else {
+    expr = parseIfExpr(startPos, p);
+  }
+  Res_parser.eatBreadcrumb(p);
+  return expr;
+}
+
+function parseElseBranch(p) {
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var blockExpr = parseExprBlock(undefined, p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  return blockExpr;
+}
+
+function parseThenBranch(p) {
+  Res_parser.leaveBreadcrumb(p, /* IfBranch */18);
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var thenExpr = parseExprBlock(undefined, p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  Res_parser.eatBreadcrumb(p);
+  return thenExpr;
+}
+
+function parseRecordRowWithStringKey(p) {
+  var s = p.token;
+  if (typeof s === "number") {
+    return ;
+  }
+  if (s.TAG !== /* String */3) {
+    return ;
+  }
+  var loc_loc_start = p.startPos;
+  var loc_loc_end = p.endPos;
+  var loc = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  Res_parser.next(undefined, p);
+  var field = $$Location.mkloc({
+        TAG: /* Lident */0,
+        _0: s._0
+      }, loc);
+  var match = p.token;
+  if (match !== 24) {
+    return [
+            field,
+            Ast_helper.Exp.ident(field.loc, undefined, field)
+          ];
+  }
+  Res_parser.next(undefined, p);
+  var fieldExpr = parseExpr(undefined, p);
+  return [
+          field,
+          fieldExpr
+        ];
+}
+
+function parseModuleTypeOf(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Module */65, p);
+  Res_parser.expect(undefined, /* Typ */60, p);
+  Res_parser.expect(undefined, /* Of */66, p);
+  var moduleExpr = parseModuleExpr(p);
+  return Ast_helper.Mty.typeof_({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, undefined, moduleExpr);
+}
+
+function parseParameter(p) {
+  if (!(p.token === /* Typ */60 || p.token === /* Tilde */48 || p.token === /* Dot */4 || Res_grammar.isPatternStart(p.token))) {
+    return ;
+  }
+  var startPos = p.startPos;
+  var uncurried = Res_parser.optional(p, /* Dot */4);
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  if (p.token === /* Typ */60) {
+    Res_parser.next(undefined, p);
+    var lidents = parseLidentList(p);
+    return {
+            TAG: /* TypeParameter */1,
+            uncurried: uncurried,
+            attrs: attrs,
+            locs: lidents,
+            pos: startPos
+          };
+  }
+  var match = p.token;
+  var match$1;
+  if (match === 48) {
+    Res_parser.next(undefined, p);
+    var match$2 = parseLident(p);
+    var lblName = match$2[0];
+    var propLocAttr_0 = $$Location.mkloc("ns.namedArgLoc", match$2[1]);
+    var propLocAttr_1 = {
+      TAG: /* PStr */0,
+      _0: /* [] */0
+    };
+    var propLocAttr = [
+      propLocAttr_0,
+      propLocAttr_1
+    ];
+    var t = p.token;
+    var exit = 0;
+    if (typeof t === "number" && t < 26) {
+      switch (t) {
+        case /* As */3 :
+            Res_parser.next(undefined, p);
+            var pat = parseConstrainedPattern(p);
+            var pat_ppat_desc = pat.ppat_desc;
+            var pat_ppat_loc = pat.ppat_loc;
+            var pat_ppat_attributes = {
+              hd: propLocAttr,
+              tl: pat.ppat_attributes
+            };
+            var pat$1 = {
+              ppat_desc: pat_ppat_desc,
+              ppat_loc: pat_ppat_loc,
+              ppat_attributes: pat_ppat_attributes
+            };
+            match$1 = [
+              attrs,
+              {
+                TAG: /* Labelled */0,
+                _0: lblName
+              },
+              pat$1
+            ];
+            break;
+        case /* Open */0 :
+        case /* True */1 :
+        case /* False */2 :
+        case /* Dot */4 :
+        case /* DotDot */5 :
+        case /* DotDotDot */6 :
+        case /* Bang */7 :
+        case /* Semicolon */8 :
+        case /* Let */9 :
+        case /* And */10 :
+        case /* Rec */11 :
+        case /* Underscore */12 :
+        case /* SingleQuote */13 :
+        case /* EqualEqual */15 :
+        case /* EqualEqualEqual */16 :
+        case /* Bar */17 :
+        case /* Lparen */18 :
+        case /* Lbracket */20 :
+        case /* Rbracket */21 :
+        case /* Lbrace */22 :
+        case /* Rbrace */23 :
+            exit = 1;
+            break;
+        case /* Colon */24 :
+            var lblEnd = p.prevEndPos;
+            Res_parser.next(undefined, p);
+            var typ = parseTypExpr(undefined, undefined, undefined, p);
+            var loc = {
+              loc_start: startPos,
+              loc_end: lblEnd,
+              loc_ghost: false
+            };
+            var pat$2 = Ast_helper.Pat.$$var(loc, undefined, $$Location.mkloc(lblName, loc));
+            var loc_loc_end = p.prevEndPos;
+            var loc$1 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end,
+              loc_ghost: false
+            };
+            var pat$3 = Ast_helper.Pat.constraint_(loc$1, {
+                  hd: propLocAttr,
+                  tl: /* [] */0
+                }, pat$2, typ);
+            match$1 = [
+              attrs,
+              {
+                TAG: /* Labelled */0,
+                _0: lblName
+              },
+              pat$3
+            ];
+            break;
+        case /* Equal */14 :
+        case /* Rparen */19 :
+        case /* Comma */25 :
+            exit = 2;
+            break;
+        
+      }
+    } else {
+      exit = 1;
+    }
+    switch (exit) {
+      case 1 :
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(t, p.breadcrumbs));
+          var loc_loc_end$1 = p.prevEndPos;
+          var loc$2 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          match$1 = [
+            attrs,
+            {
+              TAG: /* Labelled */0,
+              _0: lblName
+            },
+            Ast_helper.Pat.$$var(loc$2, undefined, $$Location.mkloc(lblName, loc$2))
+          ];
+          break;
+      case 2 :
+          var loc_loc_end$2 = p.prevEndPos;
+          var loc$3 = {
+            loc_start: startPos,
+            loc_end: loc_loc_end$2,
+            loc_ghost: false
+          };
+          match$1 = [
+            attrs,
+            {
+              TAG: /* Labelled */0,
+              _0: lblName
+            },
+            Ast_helper.Pat.$$var(loc$3, {
+                  hd: propLocAttr,
+                  tl: /* [] */0
+                }, $$Location.mkloc(lblName, loc$3))
+          ];
+          break;
+      
+    }
+  } else {
+    var pattern = parseConstrainedPattern(p);
+    var attrs$1 = List.concat({
+          hd: attrs,
+          tl: {
+            hd: pattern.ppat_attributes,
+            tl: /* [] */0
+          }
+        });
+    match$1 = [
+      /* [] */0,
+      /* Nolabel */0,
+      {
+        ppat_desc: pattern.ppat_desc,
+        ppat_loc: pattern.ppat_loc,
+        ppat_attributes: attrs$1
+      }
+    ];
+  }
+  var pat$4 = match$1[2];
+  var lbl = match$1[1];
+  var attrs$2 = match$1[0];
+  var match$3 = p.token;
+  if (match$3 !== 14) {
+    return {
+            TAG: /* TermParameter */0,
+            uncurried: uncurried,
+            attrs: attrs$2,
+            label: lbl,
+            expr: undefined,
+            pat: pat$4,
+            pos: startPos
+          };
+  }
+  Res_parser.next(undefined, p);
+  var lbl$1;
+  if (typeof lbl === "number") {
+    var $$var = pat$4.ppat_desc;
+    var lblName$1;
+    lblName$1 = typeof $$var === "number" || $$var.TAG !== /* Ppat_var */0 ? "" : $$var._0.txt;
+    Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(missingTildeLabeledParameter(lblName$1)));
+    lbl$1 = {
+      TAG: /* Optional */1,
+      _0: lblName$1
+    };
+  } else {
+    lbl$1 = lbl.TAG === /* Labelled */0 ? ({
+          TAG: /* Optional */1,
+          _0: lbl._0
+        }) : lbl;
+  }
+  var match$4 = p.token;
+  if (match$4 === 49) {
+    Res_parser.next(undefined, p);
+    return {
+            TAG: /* TermParameter */0,
+            uncurried: uncurried,
+            attrs: attrs$2,
+            label: lbl$1,
+            expr: undefined,
+            pat: pat$4,
+            pos: startPos
+          };
+  }
+  var expr = parseConstrainedOrCoercedExpr(p);
+  return {
+          TAG: /* TermParameter */0,
+          uncurried: uncurried,
+          attrs: attrs$2,
+          label: lbl$1,
+          expr: expr,
+          pat: pat$4,
+          pos: startPos
+        };
+}
+
+function parseExprBlockItem(p) {
+  var startPos = p.startPos;
+  var attrs = parseRegion(p, /* Attribute */50, parseAttribute);
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match >= 27) {
+      if (match !== 65) {
+        if (match < 28) {
+          var extensionConstructor = parseExceptionDef(attrs, p);
+          parseNewlineOrSemicolonExprBlock(p);
+          var blockExpr = parseExprBlock(undefined, p);
+          var loc_loc_end = p.prevEndPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          return Ast_helper.Exp.letexception(loc, undefined, extensionConstructor, blockExpr);
+        }
+        
+      } else {
+        Res_parser.next(undefined, p);
+        var match$1 = p.token;
+        if (match$1 === 18) {
+          var expr = parseFirstClassModuleExpr(startPos, p);
+          var a = parsePrimaryExpr(expr, undefined, p);
+          var expr$1 = parseBinaryExpr(undefined, a, p, 1);
+          return parseTernaryExpr(expr$1, p);
+        }
+        var ident = p.token;
+        var name;
+        var exit = 0;
+        if (typeof ident === "number" || ident.TAG !== /* Uident */5) {
+          exit = 2;
+        } else {
+          var loc_loc_start = p.startPos;
+          var loc_loc_end$1 = p.endPos;
+          var loc$1 = {
+            loc_start: loc_loc_start,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          Res_parser.next(undefined, p);
+          name = $$Location.mkloc(ident._0, loc$1);
+        }
+        if (exit === 2) {
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+          name = $$Location.mknoloc("_");
+        }
+        var body = parseModuleBindingBody(p);
+        parseNewlineOrSemicolonExprBlock(p);
+        var expr$2 = parseExprBlock(undefined, p);
+        var loc_loc_end$2 = p.prevEndPos;
+        var loc$2 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$2,
+          loc_ghost: false
+        };
+        return Ast_helper.Exp.letmodule(loc$2, undefined, name, body, expr$2);
+      }
+    } else if (match !== 9) {
+      if (match === 0) {
+        var od = parseOpenDescription(attrs, p);
+        parseNewlineOrSemicolonExprBlock(p);
+        var blockExpr$1 = parseExprBlock(undefined, p);
+        var loc_loc_end$3 = p.prevEndPos;
+        var loc$3 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$3,
+          loc_ghost: false
+        };
+        return Ast_helper.Exp.open_(loc$3, undefined, od.popen_override, od.popen_lid, blockExpr$1);
+      }
+      
+    } else {
+      var match$2 = parseLetBindings(attrs, p);
+      parseNewlineOrSemicolonExprBlock(p);
+      var next;
+      if (Res_grammar.isBlockExprStart(p.token)) {
+        next = parseExprBlock(undefined, p);
+      } else {
+        var loc_loc_start$1 = p.startPos;
+        var loc_loc_end$4 = p.endPos;
+        var loc$4 = {
+          loc_start: loc_loc_start$1,
+          loc_end: loc_loc_end$4,
+          loc_ghost: false
+        };
+        next = Ast_helper.Exp.construct(loc$4, undefined, $$Location.mkloc({
+                  TAG: /* Lident */0,
+                  _0: "()"
+                }, loc$4), undefined);
+      }
+      var loc_loc_end$5 = p.prevEndPos;
+      var loc$5 = {
+        loc_start: startPos,
+        loc_end: loc_loc_end$5,
+        loc_ghost: false
+      };
+      return Ast_helper.Exp.let_(loc$5, undefined, match$2[0], match$2[1], next);
+    }
+  }
+  var expr$3 = parseExpr(undefined, p);
+  var e1_pexp_desc = expr$3.pexp_desc;
+  var e1_pexp_loc = expr$3.pexp_loc;
+  var e1_pexp_attributes = List.concat({
+        hd: attrs,
+        tl: {
+          hd: expr$3.pexp_attributes,
+          tl: /* [] */0
+        }
+      });
+  var e1 = {
+    pexp_desc: e1_pexp_desc,
+    pexp_loc: e1_pexp_loc,
+    pexp_attributes: e1_pexp_attributes
+  };
+  parseNewlineOrSemicolonExprBlock(p);
+  if (!Res_grammar.isBlockExprStart(p.token)) {
+    return e1;
+  }
+  var e2 = parseExprBlock(undefined, p);
+  var init = e1_pexp_loc;
+  var loc_loc_start$2 = init.loc_start;
+  var loc_loc_end$6 = e2.pexp_loc.loc_end;
+  var loc_loc_ghost = init.loc_ghost;
+  var loc$6 = {
+    loc_start: loc_loc_start$2,
+    loc_end: loc_loc_end$6,
+    loc_ghost: loc_loc_ghost
+  };
+  return Ast_helper.Exp.sequence(loc$6, undefined, e1, e2);
+}
+
+function parseJsxName(p) {
+  var ident = p.token;
+  var longident;
+  var exit = 0;
+  if (typeof ident === "number") {
+    exit = 1;
+  } else {
+    switch (ident.TAG | 0) {
+      case /* Lident */4 :
+          var identStart = p.startPos;
+          var identEnd = p.endPos;
+          Res_parser.next(undefined, p);
+          var loc = {
+            loc_start: identStart,
+            loc_end: identEnd,
+            loc_ghost: false
+          };
+          longident = $$Location.mkloc({
+                TAG: /* Lident */0,
+                _0: ident._0
+              }, loc);
+          break;
+      case /* Uident */5 :
+          var longident$1 = parseModuleLongIdent(true, p);
+          longident = $$Location.mkloc({
+                TAG: /* Ldot */1,
+                _0: longident$1.txt,
+                _1: "createElement"
+              }, longident$1.loc);
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.message("A jsx name must be a lowercase or uppercase name, like: div in <div /> or Navbar in <Navbar />"));
+    longident = $$Location.mknoloc({
+          TAG: /* Lident */0,
+          _0: "_"
+        });
+  }
+  return Ast_helper.Exp.ident(longident.loc, undefined, longident);
+}
+
+function parseJsxOpeningOrSelfClosingElement(startPos, p) {
+  var jsxStartPos = p.startPos;
+  var name = parseJsxName(p);
+  var jsxProps = parseRegion(p, /* JsxAttribute */5, parseJsxProp);
+  var token = p.token;
+  var children;
+  var exit = 0;
+  if (typeof token === "number") {
+    if (token !== 29) {
+      if (token !== 41) {
+        exit = 1;
+      } else {
+        var childrenStartPos = p.startPos;
+        Res_scanner.setJsxMode(p.scanner);
+        Res_parser.next(undefined, p);
+        var match = parseJsxChildren(p);
+        var children$1 = match[1];
+        var spread = match[0];
+        var childrenEndPos = p.startPos;
+        var token$1 = p.token;
+        var exit$1 = 0;
+        if (typeof token$1 === "number") {
+          if (token$1 !== 42) {
+            if (token$1 !== 43) {
+              exit$1 = 2;
+            } else {
+              Res_parser.next(undefined, p);
+            }
+          } else {
+            Res_parser.next(undefined, p);
+            Res_parser.expect(undefined, /* Forwardslash */29, p);
+          }
+        } else {
+          exit$1 = 2;
+        }
+        if (exit$1 === 2) {
+          if (Res_grammar.isStructureItemStart(token$1)) {
+            
+          } else {
+            Res_parser.expect(undefined, /* LessThanSlash */43, p);
+          }
+        }
+        var token$2 = p.token;
+        var exit$2 = 0;
+        var exit$3 = 0;
+        if (typeof token$2 === "number") {
+          exit$2 = 2;
+        } else {
+          switch (token$2.TAG | 0) {
+            case /* Lident */4 :
+            case /* Uident */5 :
+                exit$3 = 3;
+                break;
+            default:
+              exit$2 = 2;
+          }
+        }
+        if (exit$3 === 3) {
+          if (verifyJsxOpeningClosingName(p, name)) {
+            Res_parser.expect(undefined, /* GreaterThan */41, p);
+            var loc = {
+              loc_start: childrenStartPos,
+              loc_end: childrenEndPos,
+              loc_ghost: false
+            };
+            children = spread && children$1 ? children$1.hd : makeListExpression(loc, children$1, undefined);
+          } else {
+            exit$2 = 2;
+          }
+        }
+        if (exit$2 === 2) {
+          if (Res_grammar.isStructureItemStart(token$2)) {
+            var closing = "</" + (string_of_pexp_ident(name) + ">");
+            var msg = Res_diagnostics.message("Missing " + closing);
+            Res_parser.err(startPos, p.prevEndPos, p, msg);
+          } else {
+            var opening = "</" + (string_of_pexp_ident(name) + ">");
+            var msg$1 = "Closing jsx name should be the same as the opening name. Did you mean " + (opening + " ?");
+            Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(msg$1));
+            Res_parser.expect(undefined, /* GreaterThan */41, p);
+          }
+          var loc$1 = {
+            loc_start: childrenStartPos,
+            loc_end: childrenEndPos,
+            loc_ghost: false
+          };
+          children = spread && children$1 ? children$1.hd : makeListExpression(loc$1, children$1, undefined);
+        }
+        
+      }
+    } else {
+      var childrenStartPos$1 = p.startPos;
+      Res_parser.next(undefined, p);
+      var childrenEndPos$1 = p.startPos;
+      Res_parser.expect(undefined, /* GreaterThan */41, p);
+      var loc$2 = {
+        loc_start: childrenStartPos$1,
+        loc_end: childrenEndPos$1,
+        loc_ghost: false
+      };
+      children = makeListExpression(loc$2, /* [] */0, undefined);
+    }
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+    children = makeListExpression($$Location.none, /* [] */0, undefined);
+  }
+  var jsxEndPos = p.prevEndPos;
+  var loc$3 = {
+    loc_start: jsxStartPos,
+    loc_end: jsxEndPos,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.apply(loc$3, undefined, name, List.concat({
+                  hd: jsxProps,
+                  tl: {
+                    hd: {
+                      hd: [
+                        {
+                          TAG: /* Labelled */0,
+                          _0: "children"
+                        },
+                        children
+                      ],
+                      tl: {
+                        hd: [
+                          /* Nolabel */0,
+                          Ast_helper.Exp.construct(undefined, undefined, $$Location.mknoloc({
+                                    TAG: /* Lident */0,
+                                    _0: "()"
+                                  }), undefined)
+                        ],
+                        tl: /* [] */0
+                      }
+                    },
+                    tl: /* [] */0
+                  }
+                }));
+}
+
+function parseJsxFragment(p) {
+  var childrenStartPos = p.startPos;
+  Res_scanner.setJsxMode(p.scanner);
+  Res_parser.expect(undefined, /* GreaterThan */41, p);
+  var match = parseJsxChildren(p);
+  var childrenEndPos = p.startPos;
+  Res_parser.expect(undefined, /* LessThanSlash */43, p);
+  Res_parser.expect(undefined, /* GreaterThan */41, p);
+  var loc = {
+    loc_start: childrenStartPos,
+    loc_end: childrenEndPos,
+    loc_ghost: false
+  };
+  return makeListExpression(loc, match[1], undefined);
+}
+
+function parseTypeConstructorDeclarationWithBar(p) {
+  var match = p.token;
+  if (match !== 17) {
+    return ;
+  }
+  var startPos = p.startPos;
+  Res_parser.next(undefined, p);
+  return parseTypeConstructorDeclaration(startPos, p);
+}
+
+function parsePatternMatching(p) {
+  var cases = parseDelimitedRegion(p, /* PatternMatching */22, /* Rbrace */23, parsePatternMatchCase);
+  if (cases) {
+    
+  } else {
+    Res_parser.err(p.prevEndPos, undefined, p, Res_diagnostics.message("Pattern matching needs at least one case"));
+  }
+  return cases;
+}
+
+function parseJsxProp(p) {
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match !== /* Question */49) {
+      return ;
+    }
+    
+  } else if (match.TAG !== /* Lident */4) {
+    return ;
+  }
+  var optional = Res_parser.optional(p, /* Question */49);
+  var match$1 = parseLident(p);
+  var loc = match$1[1];
+  var name = match$1[0];
+  var propLocAttr_0 = $$Location.mkloc("ns.namedArgLoc", loc);
+  var propLocAttr_1 = {
+    TAG: /* PStr */0,
+    _0: /* [] */0
+  };
+  var propLocAttr = [
+    propLocAttr_0,
+    propLocAttr_1
+  ];
+  if (optional) {
+    return [
+            {
+              TAG: /* Optional */1,
+              _0: name
+            },
+            Ast_helper.Exp.ident(loc, {
+                  hd: propLocAttr,
+                  tl: /* [] */0
+                }, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: name
+                    }, loc))
+          ];
+  }
+  var match$2 = p.token;
+  if (match$2 === 14) {
+    Res_parser.next(undefined, p);
+    var optional$1 = Res_parser.optional(p, /* Question */49);
+    var e = parsePrimaryExpr(parseAtomicExpr(p), undefined, p);
+    var attrExpr_pexp_desc = e.pexp_desc;
+    var attrExpr_pexp_loc = e.pexp_loc;
+    var attrExpr_pexp_attributes = {
+      hd: propLocAttr,
+      tl: e.pexp_attributes
+    };
+    var attrExpr = {
+      pexp_desc: attrExpr_pexp_desc,
+      pexp_loc: attrExpr_pexp_loc,
+      pexp_attributes: attrExpr_pexp_attributes
+    };
+    var label = optional$1 ? ({
+          TAG: /* Optional */1,
+          _0: name
+        }) : ({
+          TAG: /* Labelled */0,
+          _0: name
+        });
+    return [
+            label,
+            attrExpr
+          ];
+  }
+  var attrExpr$1 = Ast_helper.Exp.ident(loc, {
+        hd: propLocAttr,
+        tl: /* [] */0
+      }, $$Location.mkloc({
+            TAG: /* Lident */0,
+            _0: name
+          }, loc));
+  var label$1 = optional ? ({
+        TAG: /* Optional */1,
+        _0: name
+      }) : ({
+        TAG: /* Labelled */0,
+        _0: name
+      });
+  return [
+          label$1,
+          attrExpr$1
+        ];
+}
+
+function parseTypeConstructorArgRegion(p) {
+  while(true) {
+    if (Res_grammar.isTypExprStart(p.token)) {
+      return parseTypExpr(undefined, undefined, undefined, p);
+    }
+    if (p.token !== /* LessThan */42) {
+      return ;
+    }
+    Res_parser.next(undefined, p);
+    continue ;
+  };
+}
+
+function parseModuleTypeDeclaration(attrs, startPos, p) {
+  Res_parser.expect(undefined, /* Typ */60, p);
+  var ident = p.token;
+  var moduleName;
+  var exit = 0;
+  if (typeof ident === "number") {
+    exit = 2;
+  } else {
+    switch (ident.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 1;
+          break;
+      default:
+        exit = 2;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        var loc_loc_start = p.startPos;
+        var loc_loc_end = p.endPos;
+        var loc = {
+          loc_start: loc_loc_start,
+          loc_end: loc_loc_end,
+          loc_ghost: false
+        };
+        Res_parser.next(undefined, p);
+        moduleName = $$Location.mkloc(ident._0, loc);
+        break;
+    case 2 :
+        Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+        moduleName = $$Location.mknoloc("_");
+        break;
+    
+  }
+  var match = p.token;
+  var typ = match === 14 ? (Res_parser.next(undefined, p), parseModuleType(undefined, undefined, p)) : undefined;
+  var moduleDecl = Ast_helper.Mtd.mk(undefined, attrs, undefined, undefined, typ, moduleName);
+  return Ast_helper.Sig.modtype({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, moduleDecl);
+}
+
+function parseNewlineOrSemicolonSignature(p) {
+  var token = p.token;
+  if (token === 8) {
+    return Res_parser.next(undefined, p);
+  } else if (Res_grammar.isSignatureItemStart(token) && p.prevEndPos.pos_lnum >= p.startPos.pos_lnum) {
+    return Res_parser.err(p.prevEndPos, p.endPos, p, Res_diagnostics.message("consecutive specifications on a line must be separated by ';' or a newline"));
+  } else {
+    return ;
+  }
+}
+
+function parseModuleDeclarationOrAlias(attrs, p) {
+  var startPos = p.startPos;
+  var ident = p.token;
+  var moduleName;
+  var exit = 0;
+  if (typeof ident === "number" || ident.TAG !== /* Uident */5) {
+    exit = 1;
+  } else {
+    var loc_loc_start = p.startPos;
+    var loc_loc_end = p.endPos;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    moduleName = $$Location.mkloc(ident._0, loc);
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.uident(ident));
+    moduleName = $$Location.mknoloc("_");
+  }
+  var token = p.token;
+  var body;
+  var exit$1 = 0;
+  if (typeof token === "number") {
+    if (token !== 14) {
+      if (token !== 24) {
+        exit$1 = 1;
+      } else {
+        Res_parser.next(undefined, p);
+        body = parseModuleType(undefined, undefined, p);
+      }
+    } else {
+      Res_parser.next(undefined, p);
+      var lident = parseModuleLongIdent(false, p);
+      body = Ast_helper.Mty.alias(undefined, undefined, lident);
+    }
+  } else {
+    exit$1 = 1;
+  }
+  if (exit$1 === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+    body = defaultModuleType(undefined);
+  }
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Md.mk(loc$1, attrs, undefined, undefined, moduleName, body);
+}
+
+function parseSignJsExport(attrs, p) {
+  var exportStart = p.startPos;
+  Res_parser.expect(undefined, /* Export */84, p);
+  var exportLoc_loc_end = p.prevEndPos;
+  var exportLoc = {
+    loc_start: exportStart,
+    loc_end: exportLoc_loc_end,
+    loc_ghost: false
+  };
+  var genTypeAttr_0 = $$Location.mkloc("genType", exportLoc);
+  var genTypeAttr_1 = {
+    TAG: /* PStr */0,
+    _0: /* [] */0
+  };
+  var genTypeAttr = [
+    genTypeAttr_0,
+    genTypeAttr_1
+  ];
+  var attrs$1 = {
+    hd: genTypeAttr,
+    tl: attrs
+  };
+  var match = p.token;
+  if (match === 60) {
+    var ext = parseTypeDefinitionOrExtension(attrs$1, p);
+    if (ext.TAG === /* TypeDef */0) {
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: exportStart,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      return Ast_helper.Sig.type_(loc, ext.recFlag, ext.types);
+    }
+    var loc_loc_end$1 = p.prevEndPos;
+    var loc$1 = {
+      loc_start: exportStart,
+      loc_end: loc_loc_end$1,
+      loc_ghost: false
+    };
+    return Ast_helper.Sig.type_extension(loc$1, ext._0);
+  }
+  var valueDesc = parseSignLetDesc(attrs$1, p);
+  var loc_loc_end$2 = p.prevEndPos;
+  var loc$2 = {
+    loc_start: exportStart,
+    loc_end: loc_loc_end$2,
+    loc_ghost: false
+  };
+  return Ast_helper.Sig.value(loc$2, valueDesc);
+}
+
+function parseRecModuleSpec(attrs, startPos, p) {
+  Res_parser.expect(undefined, /* Rec */11, p);
+  var first = parseRecModuleDeclaration(attrs, startPos, p);
+  var _spec = {
+    hd: first,
+    tl: /* [] */0
+  };
+  while(true) {
+    var spec = _spec;
+    var startPos$1 = p.startPos;
+    var attrs$1 = parseAttributesAndBinding(p);
+    var match = p.token;
+    if (match !== 10) {
+      return List.rev(spec);
+    }
+    Res_parser.expect(undefined, /* And */10, p);
+    var decl = parseRecModuleDeclaration(attrs$1, startPos$1, p);
+    _spec = {
+      hd: decl,
+      tl: spec
+    };
+    continue ;
+  };
+}
+
+function parseEs6ArrowType(attrs, p) {
+  var startPos = p.startPos;
+  var match = p.token;
+  if (match === 48) {
+    Res_parser.next(undefined, p);
+    var match$1 = parseLident(p);
+    var name = match$1[0];
+    var lblLocAttr_0 = $$Location.mkloc("ns.namedArgLoc", match$1[1]);
+    var lblLocAttr_1 = {
+      TAG: /* PStr */0,
+      _0: /* [] */0
+    };
+    var lblLocAttr = [
+      lblLocAttr_0,
+      lblLocAttr_1
+    ];
+    Res_parser.expect(/* TypeExpression */20, /* Colon */24, p);
+    var typ = parseTypExpr(undefined, false, false, p);
+    var typ_ptyp_desc = typ.ptyp_desc;
+    var typ_ptyp_loc = typ.ptyp_loc;
+    var typ_ptyp_attributes = {
+      hd: lblLocAttr,
+      tl: typ.ptyp_attributes
+    };
+    var typ$1 = {
+      ptyp_desc: typ_ptyp_desc,
+      ptyp_loc: typ_ptyp_loc,
+      ptyp_attributes: typ_ptyp_attributes
+    };
+    var match$2 = p.token;
+    var arg = match$2 === 14 ? (Res_parser.next(undefined, p), Res_parser.expect(undefined, /* Question */49, p), {
+          TAG: /* Optional */1,
+          _0: name
+        }) : ({
+          TAG: /* Labelled */0,
+          _0: name
+        });
+    Res_parser.expect(undefined, /* EqualGreater */57, p);
+    var returnType = parseTypExpr(undefined, undefined, false, p);
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return Ast_helper.Typ.arrow(loc, attrs, arg, typ$1, returnType);
+  }
+  var parameters = parseTypeParameters(p);
+  Res_parser.expect(undefined, /* EqualGreater */57, p);
+  var returnType$1 = parseTypExpr(undefined, undefined, false, p);
+  var endPos = p.prevEndPos;
+  var typ$2 = List.fold_right((function (param, t) {
+          var attrs = param[1];
+          var attrs$1 = param[0] ? ({
+                hd: uncurryAttr,
+                tl: attrs
+              }) : attrs;
+          return Ast_helper.Typ.arrow({
+                      loc_start: param[4],
+                      loc_end: endPos,
+                      loc_ghost: false
+                    }, attrs$1, param[2], param[3], t);
+        }), parameters, returnType$1);
+  return {
+          ptyp_desc: typ$2.ptyp_desc,
+          ptyp_loc: {
+            loc_start: startPos,
+            loc_end: p.prevEndPos,
+            loc_ghost: false
+          },
+          ptyp_attributes: List.concat({
+                hd: typ$2.ptyp_attributes,
+                tl: {
+                  hd: attrs,
+                  tl: /* [] */0
+                }
+              })
+        };
+}
+
+function parseJsxChildren(p) {
+  var loop = function (p, _children) {
+    while(true) {
+      var children = _children;
+      var token = p.token;
+      if (typeof token === "number") {
+        if (token === 43 || token === 42) {
+          if (token >= 43) {
+            Res_scanner.popMode(p.scanner, /* Jsx */0);
+            return List.rev(children);
+          }
+          var token$1 = Res_scanner.reconsiderLessThan(p.scanner);
+          if (token$1 === /* LessThan */42) {
+            var child = parsePrimaryExpr(parseAtomicExpr(p), true, p);
+            _children = {
+              hd: child,
+              tl: children
+            };
+            continue ;
+          }
+          p.token = token$1;
+          Res_scanner.popMode(p.scanner, /* Jsx */0);
+          return List.rev(children);
+        }
+        if (token === 26) {
+          Res_scanner.popMode(p.scanner, /* Jsx */0);
+          return List.rev(children);
+        }
+        
+      }
+      if (Res_grammar.isJsxChildStart(token)) {
+        Res_scanner.popMode(p.scanner, /* Jsx */0);
+        var child$1 = parsePrimaryExpr(parseAtomicExpr(p), true, p);
+        _children = {
+          hd: child$1,
+          tl: children
+        };
+        continue ;
+      }
+      Res_scanner.popMode(p.scanner, /* Jsx */0);
+      return List.rev(children);
+    };
+  };
+  var match = p.token;
+  if (match === 6) {
+    Res_parser.next(undefined, p);
+    return [
+            true,
+            {
+              hd: parsePrimaryExpr(parseAtomicExpr(p), true, p),
+              tl: /* [] */0
+            }
+          ];
+  } else {
+    return [
+            false,
+            loop(p, /* [] */0)
+          ];
+  }
+}
+
+function parseConstrainedModExprRegion(p) {
+  if (Res_grammar.isModExprStart(p.token)) {
+    return parseConstrainedModExpr(p);
+  }
+  
+}
+
+function parsePolyVariantExpr(p) {
+  var startPos = p.startPos;
+  var match = parseHashIdent(startPos, p);
+  var ident = match[0];
+  var match$1 = p.token;
+  if (match$1 === 18 && p.prevEndPos.pos_lnum === p.startPos.pos_lnum) {
+    var lparen = p.startPos;
+    var args = parseConstructorArgs(p);
+    var rparen = p.prevEndPos;
+    var loc_paren = {
+      loc_start: lparen,
+      loc_end: rparen,
+      loc_ghost: false
+    };
+    var tail;
+    var exit = 0;
+    if (args) {
+      var expr = args.hd;
+      var tmp = expr.pexp_desc;
+      if (typeof tmp === "number") {
+        if (args.tl) {
+          exit = 2;
+        } else {
+          tail = expr;
+        }
+      } else if (tmp.TAG === /* Pexp_tuple */8) {
+        if (args.tl) {
+          exit = 2;
+        } else {
+          tail = p.mode === /* ParseForTypeChecker */0 ? expr : Ast_helper.Exp.tuple(loc_paren, undefined, args);
+        }
+      } else if (args.tl) {
+        exit = 2;
+      } else {
+        tail = expr;
+      }
+    } else {
+      tail = undefined;
+    }
+    if (exit === 2) {
+      tail = Ast_helper.Exp.tuple(loc_paren, undefined, args);
+    }
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: startPos,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    return Ast_helper.Exp.variant(loc, undefined, ident, tail);
+  }
+  var loc_loc_end$1 = p.prevEndPos;
+  var loc$1 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$1,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.variant(loc$1, undefined, ident, undefined);
+}
+
+function parseJsx(p) {
+  Res_parser.leaveBreadcrumb(p, /* Jsx */4);
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* LessThan */42, p);
+  var match = p.token;
+  var jsxExpr;
+  if (typeof match === "number") {
+    jsxExpr = match === /* GreaterThan */41 ? parseJsxFragment(p) : parseJsxName(p);
+  } else {
+    switch (match.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          jsxExpr = parseJsxOpeningOrSelfClosingElement(startPos, p);
+          break;
+      default:
+        jsxExpr = parseJsxName(p);
+    }
+  }
+  Res_parser.eatBreadcrumb(p);
+  return {
+          pexp_desc: jsxExpr.pexp_desc,
+          pexp_loc: jsxExpr.pexp_loc,
+          pexp_attributes: {
+            hd: jsxAttr,
+            tl: /* [] */0
+          }
+        };
+}
+
+function parseListExpr(startPos, p) {
+  var listExprs = parseCommaDelimitedReversedList(p, /* ListExpr */53, /* Rbrace */23, parseSpreadExprRegion);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  if (listExprs) {
+    var match = listExprs.hd;
+    if (match[0]) {
+      var exprs = List.rev(List.map((function (prim) {
+                  return prim[1];
+                }), listExprs.tl));
+      return makeListExpression(loc, exprs, match[1]);
+    }
+    
+  }
+  var exprs$1 = List.rev(List.map((function (param) {
+              if (param[0]) {
+                Res_parser.err(undefined, undefined, p, Res_diagnostics.message(listExprSpread));
+              }
+              return param[1];
+            }), listExprs));
+  return makeListExpression(loc, exprs$1, undefined);
+}
+
+function parseArrayExp(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbracket */20, p);
+  var exprs = parseCommaDelimitedRegion(p, /* ExprList */12, /* Rbracket */21, (function (param) {
+          return parseNonSpreadExp(arrayExprSpread, param);
+        }));
+  Res_parser.expect(undefined, /* Rbracket */21, p);
+  return Ast_helper.Exp.array({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, undefined, exprs);
+}
+
+function parseTupleExpr(first, startPos, p) {
+  var exprs_1 = parseCommaDelimitedRegion(p, /* ExprList */12, /* Rparen */19, parseConstrainedExprRegion);
+  var exprs = {
+    hd: first,
+    tl: exprs_1
+  };
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  if (exprs_1) {
+    
+  } else {
+    Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(tupleSingleElement));
+  }
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.tuple(loc, undefined, exprs);
+}
+
+function parseBracedOrRecordExpr(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var s = p.token;
+  var exit = 0;
+  if (typeof s === "number") {
+    switch (s) {
+      case /* DotDotDot */6 :
+          Res_parser.next(undefined, p);
+          var spreadExpr = parseConstrainedOrCoercedExpr(p);
+          Res_parser.expect(undefined, /* Comma */25, p);
+          var expr = parseRecordExpr(startPos, Caml_option.some(spreadExpr), /* [] */0, p);
+          Res_parser.expect(undefined, /* Rbrace */23, p);
+          return expr;
+      case /* Rbrace */23 :
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(/* Rbrace */23, p.breadcrumbs));
+          Res_parser.next(undefined, p);
+          var loc_loc_end = p.prevEndPos;
+          var loc = {
+            loc_start: startPos,
+            loc_end: loc_loc_end,
+            loc_ghost: false
+          };
+          var braces = makeBracesAttr(loc);
+          return Ast_helper.Exp.construct(loc, {
+                      hd: braces,
+                      tl: /* [] */0
+                    }, $$Location.mkloc({
+                          TAG: /* Lident */0,
+                          _0: "()"
+                        }, loc), undefined);
+      default:
+        exit = 1;
+    }
+  } else {
+    switch (s.TAG | 0) {
+      case /* String */3 :
+          var s$1 = s._0;
+          var s$2 = p.mode === /* ParseForTypeChecker */0 ? parseStringLiteral(s$1) : s$1;
+          var loc_loc_start = p.startPos;
+          var loc_loc_end$1 = p.endPos;
+          var loc$1 = {
+            loc_start: loc_loc_start,
+            loc_end: loc_loc_end$1,
+            loc_ghost: false
+          };
+          Res_parser.next(undefined, p);
+          var field = $$Location.mkloc({
+                TAG: /* Lident */0,
+                _0: s$2
+              }, loc$1);
+          var match = p.token;
+          if (match === 24) {
+            Res_parser.next(undefined, p);
+            var fieldExpr = parseExpr(undefined, p);
+            Res_parser.optional(p, /* Comma */25);
+            var expr$1 = parseRecordExprWithStringKeys(startPos, [
+                  field,
+                  fieldExpr
+                ], p);
+            Res_parser.expect(undefined, /* Rbrace */23, p);
+            return expr$1;
+          }
+          var tag = p.mode === /* ParseForTypeChecker */0 ? "js" : undefined;
+          var constant = Ast_helper.Exp.constant(field.loc, undefined, {
+                TAG: /* Pconst_string */2,
+                _0: s$2,
+                _1: tag
+              });
+          var a = parsePrimaryExpr(constant, undefined, p);
+          var e = parseBinaryExpr(undefined, a, p, 1);
+          var e$1 = parseTernaryExpr(e, p);
+          var match$1 = p.token;
+          var exit$1 = 0;
+          if (typeof match$1 === "number") {
+            if (match$1 !== 8) {
+              if (match$1 !== 23) {
+                exit$1 = 3;
+              } else {
+                Res_parser.next(undefined, p);
+                var loc_loc_end$2 = p.prevEndPos;
+                var loc$2 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$2,
+                  loc_ghost: false
+                };
+                var braces$1 = makeBracesAttr(loc$2);
+                return {
+                        pexp_desc: e$1.pexp_desc,
+                        pexp_loc: e$1.pexp_loc,
+                        pexp_attributes: {
+                          hd: braces$1,
+                          tl: e$1.pexp_attributes
+                        }
+                      };
+              }
+            } else {
+              var expr$2 = parseExprBlock(e$1, p);
+              Res_parser.expect(undefined, /* Rbrace */23, p);
+              var loc_loc_end$3 = p.prevEndPos;
+              var loc$3 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$3,
+                loc_ghost: false
+              };
+              var braces$2 = makeBracesAttr(loc$3);
+              return {
+                      pexp_desc: expr$2.pexp_desc,
+                      pexp_loc: expr$2.pexp_loc,
+                      pexp_attributes: {
+                        hd: braces$2,
+                        tl: expr$2.pexp_attributes
+                      }
+                    };
+            }
+          } else {
+            exit$1 = 3;
+          }
+          if (exit$1 === 3) {
+            var expr$3 = parseExprBlock(e$1, p);
+            Res_parser.expect(undefined, /* Rbrace */23, p);
+            var loc_loc_end$4 = p.prevEndPos;
+            var loc$4 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$4,
+              loc_ghost: false
+            };
+            var braces$3 = makeBracesAttr(loc$4);
+            return {
+                    pexp_desc: expr$3.pexp_desc,
+                    pexp_loc: expr$3.pexp_loc,
+                    pexp_attributes: {
+                      hd: braces$3,
+                      tl: expr$3.pexp_attributes
+                    }
+                  };
+          }
+          break;
+      case /* Lident */4 :
+      case /* Uident */5 :
+          exit = 2;
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        var expr$4 = parseExprBlock(undefined, p);
+        Res_parser.expect(undefined, /* Rbrace */23, p);
+        var loc_loc_end$5 = p.prevEndPos;
+        var loc$5 = {
+          loc_start: startPos,
+          loc_end: loc_loc_end$5,
+          loc_ghost: false
+        };
+        var braces$4 = makeBracesAttr(loc$5);
+        return {
+                pexp_desc: expr$4.pexp_desc,
+                pexp_loc: expr$4.pexp_loc,
+                pexp_attributes: {
+                  hd: braces$4,
+                  tl: expr$4.pexp_attributes
+                }
+              };
+    case 2 :
+        var startToken = p.token;
+        var valueOrConstructor = parseValueOrConstructor(p);
+        var pathIdent = valueOrConstructor.pexp_desc;
+        var exit$2 = 0;
+        if (typeof pathIdent === "number" || pathIdent.TAG !== /* Pexp_ident */0) {
+          exit$2 = 3;
+        } else {
+          var pathIdent$1 = pathIdent._0;
+          var identEndPos = p.prevEndPos;
+          var match$2 = p.token;
+          var exit$3 = 0;
+          if (typeof match$2 === "number") {
+            switch (match$2) {
+              case /* Semicolon */8 :
+                  var expr$5 = parseExprBlock(Ast_helper.Exp.ident(undefined, undefined, pathIdent$1), p);
+                  Res_parser.expect(undefined, /* Rbrace */23, p);
+                  var loc_loc_end$6 = p.prevEndPos;
+                  var loc$6 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$6,
+                    loc_ghost: false
+                  };
+                  var braces$5 = makeBracesAttr(loc$6);
+                  return {
+                          pexp_desc: expr$5.pexp_desc,
+                          pexp_loc: expr$5.pexp_loc,
+                          pexp_attributes: {
+                            hd: braces$5,
+                            tl: expr$5.pexp_attributes
+                          }
+                        };
+              case /* Rbrace */23 :
+                  Res_parser.next(undefined, p);
+                  var expr$6 = Ast_helper.Exp.ident(pathIdent$1.loc, undefined, pathIdent$1);
+                  var loc_loc_end$7 = p.prevEndPos;
+                  var loc$7 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$7,
+                    loc_ghost: false
+                  };
+                  var braces$6 = makeBracesAttr(loc$7);
+                  return {
+                          pexp_desc: expr$6.pexp_desc,
+                          pexp_loc: expr$6.pexp_loc,
+                          pexp_attributes: {
+                            hd: braces$6,
+                            tl: expr$6.pexp_attributes
+                          }
+                        };
+              case /* Colon */24 :
+                  Res_parser.next(undefined, p);
+                  var fieldExpr$1 = parseExpr(undefined, p);
+                  var match$3 = p.token;
+                  if (match$3 === 23) {
+                    Res_parser.next(undefined, p);
+                    var loc_loc_end$8 = p.prevEndPos;
+                    var loc$8 = {
+                      loc_start: startPos,
+                      loc_end: loc_loc_end$8,
+                      loc_ghost: false
+                    };
+                    return Ast_helper.Exp.record(loc$8, undefined, {
+                                hd: [
+                                  pathIdent$1,
+                                  fieldExpr$1
+                                ],
+                                tl: /* [] */0
+                              }, undefined);
+                  }
+                  Res_parser.expect(undefined, /* Comma */25, p);
+                  var expr$7 = parseRecordExpr(startPos, undefined, {
+                        hd: [
+                          pathIdent$1,
+                          fieldExpr$1
+                        ],
+                        tl: /* [] */0
+                      }, p);
+                  Res_parser.expect(undefined, /* Rbrace */23, p);
+                  return expr$7;
+              case /* Comma */25 :
+                  Res_parser.next(undefined, p);
+                  var valueOrConstructor$1;
+                  valueOrConstructor$1 = typeof startToken === "number" || startToken.TAG !== /* Uident */5 ? valueOrConstructor : removeModuleNameFromPunnedFieldValue(valueOrConstructor);
+                  var expr$8 = parseRecordExpr(startPos, undefined, {
+                        hd: [
+                          pathIdent$1,
+                          valueOrConstructor$1
+                        ],
+                        tl: /* [] */0
+                      }, p);
+                  Res_parser.expect(undefined, /* Rbrace */23, p);
+                  return expr$8;
+              case /* EqualGreater */57 :
+                  var loc$9 = {
+                    loc_start: startPos,
+                    loc_end: identEndPos,
+                    loc_ghost: false
+                  };
+                  var ident = $$Location.mkloc(Longident.last(pathIdent$1.txt), loc$9);
+                  var a$1 = parseEs6ArrowExpression(undefined, {
+                        hd: {
+                          TAG: /* TermParameter */0,
+                          uncurried: false,
+                          attrs: /* [] */0,
+                          label: /* Nolabel */0,
+                          expr: undefined,
+                          pat: Ast_helper.Pat.$$var(undefined, undefined, ident),
+                          pos: startPos
+                        },
+                        tl: /* [] */0
+                      }, p);
+                  var e$2 = parseBinaryExpr(undefined, a$1, p, 1);
+                  var e$3 = parseTernaryExpr(e$2, p);
+                  var match$4 = p.token;
+                  var exit$4 = 0;
+                  if (typeof match$4 === "number") {
+                    if (match$4 !== 8) {
+                      if (match$4 !== 23) {
+                        exit$4 = 5;
+                      } else {
+                        Res_parser.next(undefined, p);
+                        var loc_loc_end$9 = p.prevEndPos;
+                        var loc$10 = {
+                          loc_start: startPos,
+                          loc_end: loc_loc_end$9,
+                          loc_ghost: false
+                        };
+                        var braces$7 = makeBracesAttr(loc$10);
+                        return {
+                                pexp_desc: e$3.pexp_desc,
+                                pexp_loc: e$3.pexp_loc,
+                                pexp_attributes: {
+                                  hd: braces$7,
+                                  tl: e$3.pexp_attributes
+                                }
+                              };
+                      }
+                    } else {
+                      var expr$9 = parseExprBlock(e$3, p);
+                      Res_parser.expect(undefined, /* Rbrace */23, p);
+                      var loc_loc_end$10 = p.prevEndPos;
+                      var loc$11 = {
+                        loc_start: startPos,
+                        loc_end: loc_loc_end$10,
+                        loc_ghost: false
+                      };
+                      var braces$8 = makeBracesAttr(loc$11);
+                      return {
+                              pexp_desc: expr$9.pexp_desc,
+                              pexp_loc: expr$9.pexp_loc,
+                              pexp_attributes: {
+                                hd: braces$8,
+                                tl: expr$9.pexp_attributes
+                              }
+                            };
+                    }
+                  } else {
+                    exit$4 = 5;
+                  }
+                  if (exit$4 === 5) {
+                    var expr$10 = parseExprBlock(e$3, p);
+                    Res_parser.expect(undefined, /* Rbrace */23, p);
+                    var loc_loc_end$11 = p.prevEndPos;
+                    var loc$12 = {
+                      loc_start: startPos,
+                      loc_end: loc_loc_end$11,
+                      loc_ghost: false
+                    };
+                    var braces$9 = makeBracesAttr(loc$12);
+                    return {
+                            pexp_desc: expr$10.pexp_desc,
+                            pexp_loc: expr$10.pexp_loc,
+                            pexp_attributes: {
+                              hd: braces$9,
+                              tl: expr$10.pexp_attributes
+                            }
+                          };
+                  }
+                  break;
+              default:
+                exit$3 = 4;
+            }
+          } else {
+            if (match$2.TAG === /* Lident */4) {
+              if (p.prevEndPos.pos_lnum < p.startPos.pos_lnum) {
+                Res_parser.expect(undefined, /* Comma */25, p);
+                var expr$11 = parseRecordExpr(startPos, undefined, {
+                      hd: [
+                        pathIdent$1,
+                        valueOrConstructor
+                      ],
+                      tl: /* [] */0
+                    }, p);
+                Res_parser.expect(undefined, /* Rbrace */23, p);
+                return expr$11;
+              }
+              Res_parser.expect(undefined, /* Colon */24, p);
+              var expr$12 = parseRecordExpr(startPos, undefined, {
+                    hd: [
+                      pathIdent$1,
+                      valueOrConstructor
+                    ],
+                    tl: /* [] */0
+                  }, p);
+              Res_parser.expect(undefined, /* Rbrace */23, p);
+              return expr$12;
+            }
+            exit$3 = 4;
+          }
+          if (exit$3 === 4) {
+            Res_parser.leaveBreadcrumb(p, /* ExprBlock */10);
+            var a$2 = parsePrimaryExpr(Ast_helper.Exp.ident(pathIdent$1.loc, undefined, pathIdent$1), undefined, p);
+            var e$4 = parseBinaryExpr(undefined, a$2, p, 1);
+            var e$5 = parseTernaryExpr(e$4, p);
+            Res_parser.eatBreadcrumb(p);
+            var match$5 = p.token;
+            var exit$5 = 0;
+            if (typeof match$5 === "number") {
+              if (match$5 !== 8) {
+                if (match$5 !== 23) {
+                  exit$5 = 5;
+                } else {
+                  Res_parser.next(undefined, p);
+                  var loc_loc_end$12 = p.prevEndPos;
+                  var loc$13 = {
+                    loc_start: startPos,
+                    loc_end: loc_loc_end$12,
+                    loc_ghost: false
+                  };
+                  var braces$10 = makeBracesAttr(loc$13);
+                  return {
+                          pexp_desc: e$5.pexp_desc,
+                          pexp_loc: e$5.pexp_loc,
+                          pexp_attributes: {
+                            hd: braces$10,
+                            tl: e$5.pexp_attributes
+                          }
+                        };
+                }
+              } else {
+                var expr$13 = parseExprBlock(e$5, p);
+                Res_parser.expect(undefined, /* Rbrace */23, p);
+                var loc_loc_end$13 = p.prevEndPos;
+                var loc$14 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$13,
+                  loc_ghost: false
+                };
+                var braces$11 = makeBracesAttr(loc$14);
+                return {
+                        pexp_desc: expr$13.pexp_desc,
+                        pexp_loc: expr$13.pexp_loc,
+                        pexp_attributes: {
+                          hd: braces$11,
+                          tl: expr$13.pexp_attributes
+                        }
+                      };
+              }
+            } else {
+              exit$5 = 5;
+            }
+            if (exit$5 === 5) {
+              var expr$14 = parseExprBlock(e$5, p);
+              Res_parser.expect(undefined, /* Rbrace */23, p);
+              var loc_loc_end$14 = p.prevEndPos;
+              var loc$15 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$14,
+                loc_ghost: false
+              };
+              var braces$12 = makeBracesAttr(loc$15);
+              return {
+                      pexp_desc: expr$14.pexp_desc,
+                      pexp_loc: expr$14.pexp_loc,
+                      pexp_attributes: {
+                        hd: braces$12,
+                        tl: expr$14.pexp_attributes
+                      }
+                    };
+            }
+            
+          }
+          
+        }
+        if (exit$2 === 3) {
+          Res_parser.leaveBreadcrumb(p, /* ExprBlock */10);
+          var a$3 = parsePrimaryExpr(valueOrConstructor, undefined, p);
+          var e$6 = parseBinaryExpr(undefined, a$3, p, 1);
+          var e$7 = parseTernaryExpr(e$6, p);
+          Res_parser.eatBreadcrumb(p);
+          var match$6 = p.token;
+          var exit$6 = 0;
+          if (typeof match$6 === "number") {
+            if (match$6 !== 8) {
+              if (match$6 !== 23) {
+                exit$6 = 4;
+              } else {
+                Res_parser.next(undefined, p);
+                var loc_loc_end$15 = p.prevEndPos;
+                var loc$16 = {
+                  loc_start: startPos,
+                  loc_end: loc_loc_end$15,
+                  loc_ghost: false
+                };
+                var braces$13 = makeBracesAttr(loc$16);
+                return {
+                        pexp_desc: e$7.pexp_desc,
+                        pexp_loc: e$7.pexp_loc,
+                        pexp_attributes: {
+                          hd: braces$13,
+                          tl: e$7.pexp_attributes
+                        }
+                      };
+              }
+            } else {
+              var expr$15 = parseExprBlock(e$7, p);
+              Res_parser.expect(undefined, /* Rbrace */23, p);
+              var loc_loc_end$16 = p.prevEndPos;
+              var loc$17 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end$16,
+                loc_ghost: false
+              };
+              var braces$14 = makeBracesAttr(loc$17);
+              return {
+                      pexp_desc: expr$15.pexp_desc,
+                      pexp_loc: expr$15.pexp_loc,
+                      pexp_attributes: {
+                        hd: braces$14,
+                        tl: expr$15.pexp_attributes
+                      }
+                    };
+            }
+          } else {
+            exit$6 = 4;
+          }
+          if (exit$6 === 4) {
+            var expr$16 = parseExprBlock(e$7, p);
+            Res_parser.expect(undefined, /* Rbrace */23, p);
+            var loc_loc_end$17 = p.prevEndPos;
+            var loc$18 = {
+              loc_start: startPos,
+              loc_end: loc_loc_end$17,
+              loc_ghost: false
+            };
+            var braces$15 = makeBracesAttr(loc$18);
+            return {
+                    pexp_desc: expr$16.pexp_desc,
+                    pexp_loc: expr$16.pexp_loc,
+                    pexp_attributes: {
+                      hd: braces$15,
+                      tl: expr$16.pexp_attributes
+                    }
+                  };
+          }
+          
+        }
+        break;
+    
+  }
+}
+
+function parsePatternMatchCase(p) {
+  Res_parser.beginRegion(p);
+  Res_parser.leaveBreadcrumb(p, /* PatternMatchCase */23);
+  var match = p.token;
+  if (match === 17) {
+    Res_parser.next(undefined, p);
+    Res_parser.leaveBreadcrumb(p, /* Pattern */55);
+    var lhs = parsePattern(undefined, undefined, p);
+    Res_parser.eatBreadcrumb(p);
+    var guard = parsePatternGuard(p);
+    var match$1 = p.token;
+    if (match$1 === 57) {
+      Res_parser.next(undefined, p);
+    } else {
+      recoverEqualGreater(p);
+    }
+    var rhs = parseExprBlock(undefined, p);
+    Res_parser.endRegion(p);
+    Res_parser.eatBreadcrumb(p);
+    return Ast_helper.Exp.$$case(lhs, guard, rhs);
+  }
+  Res_parser.endRegion(p);
+  Res_parser.eatBreadcrumb(p);
+  
+}
+
+function parseAliasPattern(attrs, pattern, p) {
+  var match = p.token;
+  if (match !== 3) {
+    return pattern;
+  }
+  Res_parser.next(undefined, p);
+  var match$1 = parseLident(p);
+  var name = $$Location.mkloc(match$1[0], match$1[1]);
+  var init = pattern.ppat_loc;
+  return Ast_helper.Pat.alias({
+              loc_start: init.loc_start,
+              loc_end: p.prevEndPos,
+              loc_ghost: init.loc_ghost
+            }, attrs, pattern, name);
+}
+
+function parseTuplePattern(attrs, first, startPos, p) {
+  var patterns_1 = parseCommaDelimitedRegion(p, /* PatternList */25, /* Rparen */19, parseConstrainedPatternRegion);
+  var patterns = {
+    hd: first,
+    tl: patterns_1
+  };
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  if (patterns_1) {
+    
+  } else {
+    Res_parser.err(startPos, p.prevEndPos, p, Res_diagnostics.message(tupleSingleElement));
+  }
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.tuple(loc, attrs, patterns);
+}
+
+function parseForRest(hasOpeningParen, pattern, startPos, p) {
+  Res_parser.expect(undefined, /* In */53, p);
+  var e1 = parseExpr(undefined, p);
+  var token = p.token;
+  var direction;
+  var exit = 0;
+  if (typeof token === "number" || token.TAG !== /* Lident */4) {
+    exit = 1;
+  } else {
+    switch (token._0) {
+      case "downto" :
+          direction = /* Downto */1;
+          break;
+      case "to" :
+          direction = /* Upto */0;
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    Res_parser.err(undefined, undefined, p, Res_diagnostics.unexpected(token, p.breadcrumbs));
+    direction = /* Upto */0;
+  }
+  Res_parser.next(undefined, p);
+  var e2 = parseExpr(/* WhenExpr */2, p);
+  if (hasOpeningParen) {
+    Res_parser.expect(undefined, /* Rparen */19, p);
+  }
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var bodyExpr = parseExprBlock(undefined, p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.for_(loc, undefined, pattern, e1, e2, direction, bodyExpr);
+}
+
+function parseTypeDefinitions(attrs, name, params, startPos, p) {
+  var match = parseTypeEquationAndRepresentation(p);
+  var cstrs = parseRegion(p, /* TypeConstraint */51, parseTypeConstraint);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var typeDef = Ast_helper.Type.mk(loc, attrs, undefined, undefined, params, cstrs, match[2], match[1], match[0], {
+        txt: lidentOfPath(name.txt),
+        loc: name.loc
+      });
+  var _defs = {
+    hd: typeDef,
+    tl: /* [] */0
+  };
+  while(true) {
+    var defs = _defs;
+    var startPos$1 = p.startPos;
+    var attrs$1 = parseAttributesAndBinding(p);
+    var match$1 = p.token;
+    if (match$1 !== 10) {
+      return List.rev(defs);
+    }
+    Res_parser.next(undefined, p);
+    var match$2 = p.token;
+    var attrs$2;
+    if (typeof match$2 === "number" && match$2 >= 84) {
+      var exportLoc_loc_start = p.startPos;
+      var exportLoc_loc_end = p.endPos;
+      var exportLoc = {
+        loc_start: exportLoc_loc_start,
+        loc_end: exportLoc_loc_end,
+        loc_ghost: false
+      };
+      Res_parser.next(undefined, p);
+      var genTypeAttr_0 = $$Location.mkloc("genType", exportLoc);
+      var genTypeAttr_1 = {
+        TAG: /* PStr */0,
+        _0: /* [] */0
+      };
+      var genTypeAttr = [
+        genTypeAttr_0,
+        genTypeAttr_1
+      ];
+      attrs$2 = {
+        hd: genTypeAttr,
+        tl: attrs$1
+      };
+    } else {
+      attrs$2 = attrs$1;
+    }
+    var typeDef$1 = parseTypeDef(attrs$2, startPos$1, p);
+    _defs = {
+      hd: typeDef$1,
+      tl: defs
+    };
+    continue ;
+  };
+}
+
+function parseTypeExtension(params, attrs, name, p) {
+  Res_parser.expect(undefined, /* PlusEqual */39, p);
+  var priv = Res_parser.optional(p, /* Private */61) ? /* Private */0 : /* Public */1;
+  var constrStart = p.startPos;
+  Res_parser.optional(p, /* Bar */17);
+  var match = p.token;
+  var match$1 = match === 17 ? (Res_parser.next(undefined, p), parseConstrDef(true, p)) : parseConstrDef(true, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: constrStart,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var first = Ast_helper.Te.constructor(loc, match$1[0], undefined, undefined, match$1[1], match$1[2]);
+  var loop = function (p, _cs) {
+    while(true) {
+      var cs = _cs;
+      var match = p.token;
+      if (match !== 17) {
+        return List.rev(cs);
+      }
+      var startPos = p.startPos;
+      Res_parser.next(undefined, p);
+      var match$1 = parseConstrDef(true, p);
+      var extConstr = Ast_helper.Te.constructor({
+            loc_start: startPos,
+            loc_end: p.prevEndPos,
+            loc_ghost: false
+          }, match$1[0], undefined, undefined, match$1[1], match$1[2]);
+      _cs = {
+        hd: extConstr,
+        tl: cs
+      };
+      continue ;
+    };
+  };
+  var constructors = loop(p, {
+        hd: first,
+        tl: /* [] */0
+      });
+  return Ast_helper.Te.mk(attrs, undefined, params, priv, name, constructors);
+}
+
+function parseArgument2(p, uncurried) {
+  var match = p.token;
+  if (typeof match === "number") {
+    if (match !== 12) {
+      if (match === 48) {
+        Res_parser.next(undefined, p);
+        var ident = p.token;
+        var exit = 0;
+        if (typeof ident === "number") {
+          exit = 2;
+        } else {
+          if (ident.TAG === /* Lident */4) {
+            var ident$1 = ident._0;
+            var startPos = p.startPos;
+            Res_parser.next(undefined, p);
+            var endPos = p.prevEndPos;
+            var loc = {
+              loc_start: startPos,
+              loc_end: endPos,
+              loc_ghost: false
+            };
+            var propLocAttr_0 = $$Location.mkloc("ns.namedArgLoc", loc);
+            var propLocAttr_1 = {
+              TAG: /* PStr */0,
+              _0: /* [] */0
+            };
+            var propLocAttr = [
+              propLocAttr_0,
+              propLocAttr_1
+            ];
+            var identExpr = Ast_helper.Exp.ident(loc, {
+                  hd: propLocAttr,
+                  tl: /* [] */0
+                }, $$Location.mkloc({
+                      TAG: /* Lident */0,
+                      _0: ident$1
+                    }, loc));
+            var match$1 = p.token;
+            if (typeof match$1 !== "number") {
+              return [
+                      uncurried,
+                      {
+                        TAG: /* Labelled */0,
+                        _0: ident$1
+                      },
+                      identExpr
+                    ];
+            }
+            if (match$1 !== 14) {
+              if (match$1 !== 24) {
+                if (match$1 !== 49) {
+                  return [
+                          uncurried,
+                          {
+                            TAG: /* Labelled */0,
+                            _0: ident$1
+                          },
+                          identExpr
+                        ];
+                } else {
+                  Res_parser.next(undefined, p);
+                  return [
+                          uncurried,
+                          {
+                            TAG: /* Optional */1,
+                            _0: ident$1
+                          },
+                          identExpr
+                        ];
+                }
+              }
+              Res_parser.next(undefined, p);
+              var typ = parseTypExpr(undefined, undefined, undefined, p);
+              var loc_loc_end = p.prevEndPos;
+              var loc$1 = {
+                loc_start: startPos,
+                loc_end: loc_loc_end,
+                loc_ghost: false
+              };
+              var expr = Ast_helper.Exp.constraint_(loc$1, {
+                    hd: propLocAttr,
+                    tl: /* [] */0
+                  }, identExpr, typ);
+              return [
+                      uncurried,
+                      {
+                        TAG: /* Labelled */0,
+                        _0: ident$1
+                      },
+                      expr
+                    ];
+            }
+            Res_parser.next(undefined, p);
+            var match$2 = p.token;
+            var label = match$2 === 49 ? (Res_parser.next(undefined, p), {
+                  TAG: /* Optional */1,
+                  _0: ident$1
+                }) : ({
+                  TAG: /* Labelled */0,
+                  _0: ident$1
+                });
+            var match$3 = p.token;
+            var expr$1;
+            var exit$1 = 0;
+            if (match$3 === 12 && !isEs6ArrowExpression(false, p)) {
+              var loc_loc_start = p.startPos;
+              var loc_loc_end$1 = p.endPos;
+              var loc$2 = {
+                loc_start: loc_loc_start,
+                loc_end: loc_loc_end$1,
+                loc_ghost: false
+              };
+              Res_parser.next(undefined, p);
+              expr$1 = Ast_helper.Exp.ident(loc$2, undefined, $$Location.mkloc({
+                        TAG: /* Lident */0,
+                        _0: "_"
+                      }, loc$2));
+            } else {
+              exit$1 = 3;
+            }
+            if (exit$1 === 3) {
+              var expr$2 = parseConstrainedOrCoercedExpr(p);
+              expr$1 = {
+                pexp_desc: expr$2.pexp_desc,
+                pexp_loc: expr$2.pexp_loc,
+                pexp_attributes: {
+                  hd: propLocAttr,
+                  tl: expr$2.pexp_attributes
+                }
+              };
+            }
+            return [
+                    uncurried,
+                    label,
+                    expr$1
+                  ];
+          }
+          exit = 2;
+        }
+        if (exit === 2) {
+          Res_parser.err(undefined, undefined, p, Res_diagnostics.lident(ident));
+          return [
+                  uncurried,
+                  /* Nolabel */0,
+                  defaultExpr(undefined)
+                ];
+        }
+        
+      }
+      
+    } else if (!isEs6ArrowExpression(false, p)) {
+      var loc_loc_start$1 = p.startPos;
+      var loc_loc_end$2 = p.endPos;
+      var loc$3 = {
+        loc_start: loc_loc_start$1,
+        loc_end: loc_loc_end$2,
+        loc_ghost: false
+      };
+      Res_parser.next(undefined, p);
+      var exp = Ast_helper.Exp.ident(loc$3, undefined, $$Location.mkloc({
+                TAG: /* Lident */0,
+                _0: "_"
+              }, loc$3));
+      return [
+              uncurried,
+              /* Nolabel */0,
+              exp
+            ];
+    }
+    
+  }
+  return [
+          uncurried,
+          /* Nolabel */0,
+          parseConstrainedOrCoercedExpr(p)
+        ];
+}
+
+function parseLetBindingBody(startPos, attrs, p) {
+  Res_parser.beginRegion(p);
+  Res_parser.leaveBreadcrumb(p, /* LetBinding */24);
+  Res_parser.leaveBreadcrumb(p, /* Pattern */55);
+  var pat = parsePattern(undefined, undefined, p);
+  Res_parser.eatBreadcrumb(p);
+  var match = p.token;
+  var match$1;
+  if (match === 24) {
+    Res_parser.next(undefined, p);
+    var match$2 = p.token;
+    if (match$2 === 60) {
+      Res_parser.next(undefined, p);
+      var newtypes = parseLidentList(p);
+      Res_parser.expect(undefined, /* Dot */4, p);
+      var typ = parseTypExpr(undefined, undefined, undefined, p);
+      Res_parser.expect(undefined, /* Equal */14, p);
+      var expr = parseExpr(undefined, p);
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: startPos,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      var match$3 = wrapTypeAnnotation(loc, newtypes, typ, expr);
+      var pat$1 = Ast_helper.Pat.constraint_(loc, undefined, pat, match$3[1]);
+      match$1 = [
+        pat$1,
+        match$3[0]
+      ];
+    } else {
+      var polyType = parsePolyTypeExpr(p);
+      var init = pat.ppat_loc;
+      var loc_loc_start = init.loc_start;
+      var loc_loc_end$1 = polyType.ptyp_loc.loc_end;
+      var loc_loc_ghost = init.loc_ghost;
+      var loc$1 = {
+        loc_start: loc_loc_start,
+        loc_end: loc_loc_end$1,
+        loc_ghost: loc_loc_ghost
+      };
+      var pat$2 = Ast_helper.Pat.constraint_(loc$1, undefined, pat, polyType);
+      Res_parser.expect(undefined, /* Equal */14, p);
+      var exp = parseExpr(undefined, p);
+      var exp$1 = overParseConstrainedOrCoercedOrArrowExpression(p, exp);
+      match$1 = [
+        pat$2,
+        exp$1
+      ];
+    }
+  } else {
+    Res_parser.expect(undefined, /* Equal */14, p);
+    var exp$2 = overParseConstrainedOrCoercedOrArrowExpression(p, parseExpr(undefined, p));
+    match$1 = [
+      pat,
+      exp$2
+    ];
+  }
+  var loc_loc_end$2 = p.prevEndPos;
+  var loc$2 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$2,
+    loc_ghost: false
+  };
+  var vb = Ast_helper.Vb.mk(loc$2, attrs, undefined, undefined, match$1[0], match$1[1]);
+  Res_parser.eatBreadcrumb(p);
+  Res_parser.endRegion(p);
+  return vb;
+}
+
+function parseArgument(p) {
+  if (!(p.token === /* Tilde */48 || p.token === /* Dot */4 || p.token === /* Underscore */12 || Res_grammar.isExprStart(p.token))) {
+    return ;
+  }
+  var match = p.token;
+  if (match !== 4) {
+    return parseArgument2(p, false);
+  }
+  Res_parser.next(undefined, p);
+  var match$1 = p.token;
+  if (match$1 !== 19) {
+    return parseArgument2(p, true);
+  }
+  var unitExpr = Ast_helper.Exp.construct(undefined, undefined, $$Location.mknoloc({
+            TAG: /* Lident */0,
+            _0: "()"
+          }), undefined);
+  return [
+          true,
+          /* Nolabel */0,
+          unitExpr
+        ];
+}
+
+function parsePrimaryModExpr(p) {
+  var startPos = p.startPos;
+  var modExpr = parseAtomicModuleExpr(p);
+  var loop = function (p, _modExpr) {
+    while(true) {
+      var modExpr = _modExpr;
+      var match = p.token;
+      if (match !== 18) {
+        return modExpr;
+      }
+      if (p.prevEndPos.pos_lnum !== p.startPos.pos_lnum) {
+        return modExpr;
+      }
+      _modExpr = parseModuleApplication(p, modExpr);
+      continue ;
+    };
+  };
+  var modExpr$1 = loop(p, modExpr);
+  return {
+          pmod_desc: modExpr$1.pmod_desc,
+          pmod_loc: {
+            loc_start: startPos,
+            loc_end: p.prevEndPos,
+            loc_ghost: false
+          },
+          pmod_attributes: modExpr$1.pmod_attributes
+        };
+}
+
+function parseFunctorModuleExpr(p) {
+  var startPos = p.startPos;
+  var args = parseFunctorArgs(p);
+  var match = p.token;
+  var returnType = match === 24 ? (Res_parser.next(undefined, p), parseModuleType(false, undefined, p)) : undefined;
+  Res_parser.expect(undefined, /* EqualGreater */57, p);
+  var modExpr = parseModuleExpr(p);
+  var rhsModuleExpr = returnType !== undefined ? Ast_helper.Mod.constraint_({
+          loc_start: modExpr.pmod_loc.loc_start,
+          loc_end: returnType.pmty_loc.loc_end,
+          loc_ghost: false
+        }, undefined, modExpr, returnType) : modExpr;
+  var endPos = p.prevEndPos;
+  var modExpr$1 = List.fold_right((function (param, acc) {
+          return Ast_helper.Mod.functor_({
+                      loc_start: param[3],
+                      loc_end: endPos,
+                      loc_ghost: false
+                    }, param[0], param[1], param[2], acc);
+        }), args, rhsModuleExpr);
+  return {
+          pmod_desc: modExpr$1.pmod_desc,
+          pmod_loc: {
+            loc_start: startPos,
+            loc_end: endPos,
+            loc_ghost: false
+          },
+          pmod_attributes: modExpr$1.pmod_attributes
+        };
+}
+
+function parseUnaryExpr(p) {
+  var startPos = p.startPos;
+  var token = p.token;
+  var exit = 0;
+  exit = typeof token === "number" ? (
+      token >= 34 ? (
+          token >= 38 ? 1 : 2
+        ) : (
+          token !== 7 ? 1 : 2
+        )
+    ) : 1;
+  switch (exit) {
+    case 1 :
+        return parsePrimaryExpr(parseAtomicExpr(p), undefined, p);
+    case 2 :
+        Res_parser.leaveBreadcrumb(p, /* ExprUnary */8);
+        var tokenEnd = p.endPos;
+        Res_parser.next(undefined, p);
+        var operand = parseUnaryExpr(p);
+        var unaryExpr = makeUnaryExpr(startPos, tokenEnd, token, operand);
+        Res_parser.eatBreadcrumb(p);
+        return unaryExpr;
+    
+  }
+}
+
+function parseSwitchExpression(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Switch */55, p);
+  var switchExpr = parseExpr(/* WhenExpr */2, p);
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var cases = parsePatternMatching(p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.match_(loc, undefined, switchExpr, cases);
+}
+
+function parseTryExpression(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Try */82, p);
+  var expr = parseExpr(/* WhenExpr */2, p);
+  Res_parser.expect(undefined, Res_token.$$catch, p);
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var cases = parsePatternMatching(p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.try_(loc, undefined, expr, cases);
+}
+
+function parseWhileExpression(p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* While */54, p);
+  var expr1 = parseExpr(/* WhenExpr */2, p);
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var expr2 = parseExprBlock(undefined, p);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Exp.while_(loc, undefined, expr1, expr2);
+}
+
+function parseForExpression(p) {
+  var startPos = p.startPos;
+  Res_parser.leaveBreadcrumb(p, /* ExprFor */16);
+  Res_parser.expect(undefined, /* For */52, p);
+  Res_parser.beginRegion(p);
+  var match = p.token;
+  var forExpr;
+  if (match === 18) {
+    var lparen = p.startPos;
+    Res_parser.next(undefined, p);
+    var match$1 = p.token;
+    if (match$1 === 19) {
+      Res_parser.next(undefined, p);
+      var loc_loc_end = p.prevEndPos;
+      var loc = {
+        loc_start: lparen,
+        loc_end: loc_loc_end,
+        loc_ghost: false
+      };
+      var lid = $$Location.mkloc({
+            TAG: /* Lident */0,
+            _0: "()"
+          }, loc);
+      var unitPattern = Ast_helper.Pat.construct(undefined, undefined, lid, undefined);
+      forExpr = parseForRest(false, parseAliasPattern(/* [] */0, unitPattern, p), startPos, p);
+    } else {
+      Res_parser.leaveBreadcrumb(p, /* Pattern */55);
+      var pat = parsePattern(undefined, undefined, p);
+      Res_parser.eatBreadcrumb(p);
+      var match$2 = p.token;
+      if (match$2 === 25) {
+        Res_parser.next(undefined, p);
+        var tuplePattern = parseTuplePattern(/* [] */0, pat, lparen, p);
+        var pattern = parseAliasPattern(/* [] */0, tuplePattern, p);
+        forExpr = parseForRest(false, pattern, startPos, p);
+      } else {
+        forExpr = parseForRest(true, pat, startPos, p);
+      }
+    }
+  } else {
+    Res_parser.leaveBreadcrumb(p, /* Pattern */55);
+    var pat$1 = parsePattern(undefined, undefined, p);
+    Res_parser.eatBreadcrumb(p);
+    forExpr = parseForRest(false, pat$1, startPos, p);
+  }
+  Res_parser.eatBreadcrumb(p);
+  Res_parser.endRegion(p);
+  return forExpr;
+}
+
+function parseConstructorPatternArgs(p, constr, startPos, attrs) {
+  var lparen = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var args = parseCommaDelimitedRegion(p, /* PatternList */25, /* Rparen */19, parseConstrainedPatternRegion);
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var args$1;
+  var exit = 0;
+  if (args) {
+    var pat = args.hd;
+    var tmp = pat.ppat_desc;
+    if (typeof tmp === "number") {
+      if (args.tl) {
+        exit = 1;
+      } else {
+        args$1 = pat;
+      }
+    } else if (tmp.TAG === /* Ppat_tuple */4) {
+      if (args.tl) {
+        exit = 1;
+      } else {
+        args$1 = p.mode === /* ParseForTypeChecker */0 ? pat : Ast_helper.Pat.tuple({
+                loc_start: lparen,
+                loc_end: p.endPos,
+                loc_ghost: false
+              }, undefined, args);
+      }
+    } else if (args.tl) {
+      exit = 1;
+    } else {
+      args$1 = pat;
+    }
+  } else {
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: lparen,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    args$1 = Ast_helper.Pat.construct(loc, undefined, $$Location.mkloc({
+              TAG: /* Lident */0,
+              _0: "()"
+            }, loc), undefined);
+  }
+  if (exit === 1) {
+    args$1 = Ast_helper.Pat.tuple({
+          loc_start: lparen,
+          loc_end: p.endPos,
+          loc_ghost: false
+        }, undefined, args);
+  }
+  return Ast_helper.Pat.construct({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, attrs, constr, args$1);
+}
+
+function parseRecordPattern(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var rawFields = parseCommaDelimitedReversedList(p, /* PatternRecord */27, /* Rbrace */23, parseRecordPatternItem);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var match = rawFields && !rawFields.hd[1] ? [
+      rawFields.tl,
+      /* Open */1
+    ] : [
+      rawFields,
+      /* Closed */0
+    ];
+  var match$1 = List.fold_left((function (param, curr) {
+          var field = curr[1];
+          var flag = param[1];
+          var fields = param[0];
+          if (!field) {
+            return [
+                    fields,
+                    flag
+                  ];
+          }
+          var field$1 = field._0;
+          if (curr[0]) {
+            Res_parser.err(field$1[1].ppat_loc.loc_start, undefined, p, Res_diagnostics.message(recordPatternSpread));
+          }
+          return [
+                  {
+                    hd: field$1,
+                    tl: fields
+                  },
+                  flag
+                ];
+        }), [
+        /* [] */0,
+        match[1]
+      ], match[0]);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.record(loc, attrs, match$1[0], match$1[1]);
+}
+
+function parseModulePattern(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Module */65, p);
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var uident = p.token;
+  var uident$1;
+  if (typeof uident === "number" || uident.TAG !== /* Uident */5) {
+    uident$1 = $$Location.mknoloc("_");
+  } else {
+    var loc_loc_start = p.startPos;
+    var loc_loc_end = p.endPos;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    Res_parser.next(undefined, p);
+    uident$1 = $$Location.mkloc(uident._0, loc);
+  }
+  var match = p.token;
+  if (match === 24) {
+    var colonStart = p.startPos;
+    Res_parser.next(undefined, p);
+    var packageTypAttrs = parseRegion(p, /* Attribute */50, parseAttribute);
+    var packageType = parsePackageType(colonStart, packageTypAttrs, p);
+    Res_parser.expect(undefined, /* Rparen */19, p);
+    var loc_loc_end$1 = p.prevEndPos;
+    var loc$1 = {
+      loc_start: startPos,
+      loc_end: loc_loc_end$1,
+      loc_ghost: false
+    };
+    var unpack = Ast_helper.Pat.unpack(uident$1.loc, undefined, uident$1);
+    return Ast_helper.Pat.constraint_(loc$1, attrs, unpack, packageType);
+  }
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  var loc_loc_end$2 = p.prevEndPos;
+  var loc$2 = {
+    loc_start: startPos,
+    loc_end: loc_loc_end$2,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.unpack(loc$2, attrs, uident$1);
+}
+
+function parseListPattern(startPos, attrs, p) {
+  var listPatterns = parseCommaDelimitedReversedList(p, /* PatternOcamlList */26, /* Rbrace */23, parsePatternRegion);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  var filterSpread = function (param) {
+    var pattern = param[1];
+    if (param[0]) {
+      Res_parser.err(pattern.ppat_loc.loc_start, undefined, p, Res_diagnostics.message(listPatternSpread));
+      return pattern;
+    } else {
+      return pattern;
+    }
+  };
+  if (listPatterns) {
+    var match = listPatterns.hd;
+    if (match[0]) {
+      var patterns = List.rev(List.map(filterSpread, listPatterns.tl));
+      var pat = makeListPattern(loc, patterns, match[1]);
+      return {
+              ppat_desc: pat.ppat_desc,
+              ppat_loc: loc,
+              ppat_attributes: attrs
+            };
+    }
+    
+  }
+  var patterns$1 = List.rev(List.map(filterSpread, listPatterns));
+  var pat$1 = makeListPattern(loc, patterns$1, undefined);
+  return {
+          ppat_desc: pat$1.ppat_desc,
+          ppat_loc: loc,
+          ppat_attributes: attrs
+        };
+}
+
+function parseVariantPatternArgs(p, ident, startPos, attrs) {
+  var lparen = p.startPos;
+  Res_parser.expect(undefined, /* Lparen */18, p);
+  var patterns = parseCommaDelimitedRegion(p, /* PatternList */25, /* Rparen */19, parseConstrainedPatternRegion);
+  var args;
+  var exit = 0;
+  if (patterns) {
+    var pat = patterns.hd;
+    var tmp = pat.ppat_desc;
+    if (typeof tmp === "number") {
+      if (patterns.tl) {
+        exit = 1;
+      } else {
+        args = pat;
+      }
+    } else if (tmp.TAG === /* Ppat_tuple */4) {
+      if (patterns.tl) {
+        exit = 1;
+      } else {
+        args = p.mode === /* ParseForTypeChecker */0 ? pat : Ast_helper.Pat.tuple({
+                loc_start: lparen,
+                loc_end: p.endPos,
+                loc_ghost: false
+              }, undefined, patterns);
+      }
+    } else if (patterns.tl) {
+      exit = 1;
+    } else {
+      args = pat;
+    }
+  } else {
+    var loc_loc_end = p.prevEndPos;
+    var loc = {
+      loc_start: lparen,
+      loc_end: loc_loc_end,
+      loc_ghost: false
+    };
+    args = Ast_helper.Pat.construct(loc, undefined, $$Location.mkloc({
+              TAG: /* Lident */0,
+              _0: "()"
+            }, loc), undefined);
+  }
+  if (exit === 1) {
+    args = Ast_helper.Pat.tuple({
+          loc_start: lparen,
+          loc_end: p.endPos,
+          loc_ghost: false
+        }, undefined, patterns);
+  }
+  Res_parser.expect(undefined, /* Rparen */19, p);
+  return Ast_helper.Pat.variant({
+              loc_start: startPos,
+              loc_end: p.prevEndPos,
+              loc_ghost: false
+            }, attrs, ident, args);
+}
+
+function parseOrPattern(pattern1, p) {
+  var _pattern1 = pattern1;
+  while(true) {
+    var pattern1$1 = _pattern1;
+    var match = p.token;
+    if (match !== 17) {
+      return pattern1$1;
+    }
+    Res_parser.next(undefined, p);
+    var pattern2 = parsePattern(undefined, false, p);
+    var init = pattern1$1.ppat_loc;
+    var loc_loc_start = init.loc_start;
+    var loc_loc_end = pattern2.ppat_loc.loc_end;
+    var loc_loc_ghost = init.loc_ghost;
+    var loc = {
+      loc_start: loc_loc_start,
+      loc_end: loc_loc_end,
+      loc_ghost: loc_loc_ghost
+    };
+    _pattern1 = Ast_helper.Pat.or_(loc, undefined, pattern1$1, pattern2);
+    continue ;
+  };
+}
+
+function parseArrayPattern(attrs, p) {
+  var startPos = p.startPos;
+  Res_parser.expect(undefined, /* Lbracket */20, p);
+  var patterns = parseCommaDelimitedRegion(p, /* PatternList */25, /* Rbracket */21, (function (param) {
+          return parseNonSpreadPattern(arrayPatternSpread, param);
+        }));
+  Res_parser.expect(undefined, /* Rbracket */21, p);
+  var loc_loc_end = p.prevEndPos;
+  var loc = {
+    loc_start: startPos,
+    loc_end: loc_loc_end,
+    loc_ghost: false
+  };
+  return Ast_helper.Pat.array(loc, attrs, patterns);
+}
+
+function parseJsFfiDeclarations(p) {
+  Res_parser.expect(undefined, /* Lbrace */22, p);
+  var decls = parseCommaDelimitedRegion(p, /* JsFfiImport */54, /* Rbrace */23, parseJsFfiDeclaration);
+  Res_parser.expect(undefined, /* Rbrace */23, p);
+  return decls;
+}
+
+function parseJsFfiScope(p) {
+  var match = p.token;
+  if (typeof match === "number") {
+    return /* Global */0;
+  }
+  if (match.TAG !== /* Lident */4) {
+    return /* Global */0;
+  }
+  if (match._0 !== "from") {
+    return /* Global */0;
+  }
+  Res_parser.next(undefined, p);
+  var s = p.token;
+  if (typeof s === "number") {
+    return /* Global */0;
+  }
+  switch (s.TAG | 0) {
+    case /* String */3 :
+        Res_parser.next(undefined, p);
+        return {
+                TAG: /* Module */0,
+                _0: s._0
+              };
+    case /* Lident */4 :
+    case /* Uident */5 :
+        break;
+    default:
+      return /* Global */0;
+  }
+  var value = parseIdentPath(p);
+  return {
+          TAG: /* Scope */1,
+          _0: value
+        };
+}
+
+function parseJsxProps(p) {
+  return parseRegion(p, /* JsxAttribute */5, parseJsxProp);
+}
+
+function parseTypeConstraints(p) {
+  return parseRegion(p, /* TypeConstraint */51, parseTypeConstraint);
+}
+
+function parseAttributes(p) {
+  return parseRegion(p, /* Attribute */50, parseAttribute);
+}
+
+function parseSpecification(p) {
+  return parseRegion(p, /* Specification */47, parseSignatureItemRegion);
+}
+
+function parseImplementation(p) {
+  return parseRegion(p, /* Implementation */49, parseStructureItemRegion);
+}
+
+var Doc;
+
+var Grammar;
+
+var Token;
+
+var Diagnostics;
+
+var CommentTable;
+
+var ResPrinter;
+
+var Scanner;
+
+var JsFfi;
+
+var Parser;
+
+export {
+  Doc ,
+  Grammar ,
+  Token ,
+  Diagnostics ,
+  CommentTable ,
+  ResPrinter ,
+  Scanner ,
+  JsFfi ,
+  Parser ,
+  mkLoc ,
+  Recover ,
+  ErrorMessages ,
+  jsxAttr ,
+  uncurryAttr ,
+  ternaryAttr ,
+  ifLetAttr ,
+  suppressFragileMatchWarningAttr ,
+  makeBracesAttr ,
+  templateLiteralAttr ,
+  getClosingToken ,
+  goToClosing ,
+  isEs6ArrowExpression ,
+  isEs6ArrowFunctor ,
+  isEs6ArrowType ,
+  buildLongident ,
+  makeInfixOperator ,
+  negateString ,
+  makeUnaryExpr ,
+  makeListExpression ,
+  makeListPattern ,
+  lidentOfPath ,
+  makeNewtypes ,
+  wrapTypeAnnotation ,
+  processUnderscoreApplication ,
+  hexValue ,
+  removeModuleNameFromPunnedFieldValue ,
+  parseStringLiteral ,
+  parseLident ,
+  parseIdent ,
+  parseHashIdent ,
+  parseValuePath ,
+  parseValuePathAfterDot ,
+  parseValuePathTail ,
+  parseModuleLongIdentTail ,
+  parseModuleLongIdent ,
+  parseIdentPath ,
+  verifyJsxOpeningClosingName ,
+  string_of_pexp_ident ,
+  parseOpenDescription ,
+  parseTemplateStringLiteral ,
+  parseConstant ,
+  parseTemplateConstant ,
+  parseCommaDelimitedRegion ,
+  parseCommaDelimitedReversedList ,
+  parseDelimitedRegion ,
+  parseRegion ,
+  parsePattern ,
+  skipTokensAndMaybeRetry ,
+  parseAliasPattern ,
+  parseOrPattern ,
+  parseNonSpreadPattern ,
+  parseConstrainedPattern ,
+  parseConstrainedPatternRegion ,
+  parseRecordPatternField ,
+  parseRecordPatternItem ,
+  parseRecordPattern ,
+  parseTuplePattern ,
+  parsePatternRegion ,
+  parseModulePattern ,
+  parseListPattern ,
+  parseArrayPattern ,
+  parseConstructorPatternArgs ,
+  parseVariantPatternArgs ,
+  parseExpr ,
+  parseTernaryExpr ,
+  parseEs6ArrowExpression ,
+  parseParameter ,
+  parseParameterList ,
+  parseParameters ,
+  parseCoercedExpr ,
+  parseConstrainedOrCoercedExpr ,
+  parseConstrainedExprRegion ,
+  parseAtomicExpr ,
+  parseFirstClassModuleExpr ,
+  parseBracketAccess ,
+  parsePrimaryExpr ,
+  parseUnaryExpr ,
+  parseOperandExpr ,
+  parseBinaryExpr ,
+  parseTemplateExpr ,
+  overParseConstrainedOrCoercedOrArrowExpression ,
+  parseLetBindingBody ,
+  parseAttributesAndBinding ,
+  parseLetBindings ,
+  parseJsxName ,
+  parseJsxOpeningOrSelfClosingElement ,
+  parseJsx ,
+  parseJsxFragment ,
+  parseJsxProp ,
+  parseJsxProps ,
+  parseJsxChildren ,
+  parseBracedOrRecordExpr ,
+  parseRecordRowWithStringKey ,
+  parseRecordRow ,
+  parseRecordExprWithStringKeys ,
+  parseRecordExpr ,
+  parseNewlineOrSemicolonExprBlock ,
+  parseExprBlockItem ,
+  parseExprBlock ,
+  parseTryExpression ,
+  parseIfCondition ,
+  parseThenBranch ,
+  parseElseBranch ,
+  parseIfExpr ,
+  parseIfLetExpr ,
+  parseIfOrIfLetExpression ,
+  parseForRest ,
+  parseForExpression ,
+  parseWhileExpression ,
+  parsePatternGuard ,
+  parsePatternMatchCase ,
+  parsePatternMatching ,
+  parseSwitchExpression ,
+  parseArgument ,
+  parseArgument2 ,
+  parseCallExpr ,
+  parseValueOrConstructor ,
+  parsePolyVariantExpr ,
+  parseConstructorArgs ,
+  parseTupleExpr ,
+  parseSpreadExprRegion ,
+  parseListExpr ,
+  parseNonSpreadExp ,
+  parseArrayExp ,
+  parsePolyTypeExpr ,
+  parseTypeVarList ,
+  parseLidentList ,
+  parseAtomicTypExpr ,
+  parsePackageType ,
+  parsePackageConstraints ,
+  parsePackageConstraint ,
+  parseRecordOrObjectType ,
+  parseTypeAlias ,
+  parseTypeParameter ,
+  parseTypeParameters ,
+  parseEs6ArrowType ,
+  parseTypExpr ,
+  parseArrowTypeRest ,
+  parseTypExprRegion ,
+  parseTupleType ,
+  parseTypeConstructorArgRegion ,
+  parseTypeConstructorArgs ,
+  parseStringFieldDeclaration ,
+  parseFieldDeclaration ,
+  parseFieldDeclarationRegion ,
+  parseRecordDeclaration ,
+  parseConstrDeclArgs ,
+  parseTypeConstructorDeclarationWithBar ,
+  parseTypeConstructorDeclaration ,
+  parseTypeConstructorDeclarations ,
+  parseTypeRepresentation ,
+  parseTypeParam ,
+  parseTypeParams ,
+  parseTypeConstraint ,
+  parseTypeConstraints ,
+  parseTypeEquationOrConstrDecl ,
+  parseRecordOrObjectDecl ,
+  parsePrivateEqOrRepr ,
+  parsePolymorphicVariantType ,
+  parseTagName ,
+  parseTagNames ,
+  parseTagSpecFulls ,
+  parseTagSpecFull ,
+  parseTagSpecs ,
+  parseTagSpec ,
+  parseTagSpecFirst ,
+  parsePolymorphicVariantTypeSpecHash ,
+  parsePolymorphicVariantTypeArgs ,
+  parseTypeEquationAndRepresentation ,
+  parseTypeDef ,
+  parseTypeExtension ,
+  parseTypeDefinitions ,
+  parseTypeDefinitionOrExtension ,
+  parseExternalDef ,
+  parseConstrDef ,
+  parseExceptionDef ,
+  parseNewlineOrSemicolonStructure ,
+  parseStructureItemRegion ,
+  parseJsImport ,
+  parseJsExport ,
+  parseSignJsExport ,
+  parseJsFfiScope ,
+  parseJsFfiDeclarations ,
+  parseJsFfiDeclaration ,
+  parseIncludeStatement ,
+  parseAtomicModuleExpr ,
+  parsePrimaryModExpr ,
+  parseFunctorArg ,
+  parseFunctorArgs ,
+  parseFunctorModuleExpr ,
+  parseModuleExpr ,
+  parseConstrainedModExpr ,
+  parseConstrainedModExprRegion ,
+  parseModuleApplication ,
+  parseModuleOrModuleTypeImplOrPackExpr ,
+  parseModuleTypeImpl ,
+  parseMaybeRecModuleBinding ,
+  parseModuleBinding ,
+  parseModuleBindingBody ,
+  parseModuleBindings ,
+  parseAtomicModuleType ,
+  parseFunctorModuleType ,
+  parseModuleType ,
+  parseWithConstraints ,
+  parseWithConstraint ,
+  parseModuleTypeOf ,
+  parseNewlineOrSemicolonSignature ,
+  parseSignatureItemRegion ,
+  parseRecModuleSpec ,
+  parseRecModuleDeclaration ,
+  parseModuleDeclarationOrAlias ,
+  parseModuleTypeDeclaration ,
+  parseSignLetDesc ,
+  parseAttributeId ,
+  parsePayload ,
+  parseAttribute ,
+  parseAttributes ,
+  parseStandaloneAttribute ,
+  parseExtension ,
+  parseSpecification ,
+  parseImplementation ,
+  
+}
+/* id Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_core.res b/analysis/examples/larger-project/src/res_core.res
new file mode 100644
index 0000000000..397711de20
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_core.res
@@ -0,0 +1,7271 @@
+module Doc = Res_doc
+module Grammar = Res_grammar
+module Token = Res_token
+module Diagnostics = Res_diagnostics
+module CommentTable = Res_comments_table
+module ResPrinter = Res_printer
+module Scanner = Res_scanner
+module JsFfi = Res_js_ffi
+module Parser = Res_parser
+
+let mkLoc = (startLoc, endLoc) => {
+  open Location
+  {
+    loc_start: startLoc,
+    loc_end: endLoc,
+    loc_ghost: false,
+  }
+}
+
+module Recover = {
+  let defaultExpr = () => {
+    let id = Location.mknoloc("rescript.exprhole")
+    Ast_helper.Exp.mk(Pexp_extension(id, PStr(list{})))
+  }
+
+  let defaultType = () => {
+    let id = Location.mknoloc("rescript.typehole")
+    Ast_helper.Typ.extension((id, PStr(list{})))
+  }
+
+  let defaultPattern = () => {
+    let id = Location.mknoloc("rescript.patternhole")
+    Ast_helper.Pat.extension((id, PStr(list{})))
+  }
+
+  let defaultModuleExpr = () => Ast_helper.Mod.structure(list{})
+  let defaultModuleType = () => Ast_helper.Mty.signature(list{})
+
+  let defaultSignatureItem = {
+    let id = Location.mknoloc("rescript.sigitemhole")
+    Ast_helper.Sig.extension((id, PStr(list{})))
+  }
+
+  let recoverEqualGreater = p => {
+    Parser.expect(EqualGreater, p)
+    switch p.Parser.token {
+    | MinusGreater => Parser.next(p)
+    | _ => ()
+    }
+  }
+
+  let shouldAbortListParse = p => {
+    let rec check = breadcrumbs =>
+      switch breadcrumbs {
+      | list{} => false
+      | list{(grammar, _), ...rest} =>
+        if Grammar.isPartOfList(grammar, p.Parser.token) {
+          true
+        } else {
+          check(rest)
+        }
+      }
+
+    check(p.breadcrumbs)
+  }
+}
+
+module ErrorMessages = {
+  let listPatternSpread = "List pattern matches only supports one `...` spread, at the end.\n\
+Explanation: a list spread at the tail is efficient, but a spread in the middle would create new list[s]; out of performance concern, our pattern matching currently guarantees to never create new intermediate data."
+
+  @live
+  let recordPatternSpread = "Record's `...` spread is not supported in pattern matches.\n\
+Explanation: you can't collect a subset of a record's field into its own record, since a record needs an explicit declaration and that subset wouldn't have one.\n\
+Solution: you need to pull out each field you want explicitly."
+
+  /* let recordPatternUnderscore = "Record patterns only support one `_`, at the end." */
+
+  let arrayPatternSpread = "Array's `...` spread is not supported in pattern matches.\n\
+Explanation: such spread would create a subarray; out of performance concern, our pattern matching currently guarantees to never create new intermediate data.\n\
+Solution: if it's to validate the first few elements, use a `when` clause + Array size check + `get` checks on the current pattern. If it's to obtain a subarray, use `Array.sub` or `Belt.Array.slice`."
+
+  let arrayExprSpread = "Arrays can't use the `...` spread currently. Please use `concat` or other Array helpers."
+
+  let recordExprSpread = "Records can only have one `...` spread, at the beginning.\n\
+Explanation: since records have a known, fixed shape, a spread like `{a, ...b}` wouldn't make sense, as `b` would override every field of `a` anyway."
+
+  let listExprSpread = "Lists can only have one `...` spread, and at the end.\n\
+Explanation: lists are singly-linked list, where a node contains a value and points to the next node. `list[a, ...bc]` efficiently creates a new item and links `bc` as its next nodes. `[...bc, a]` would be expensive, as it'd need to traverse `bc` and prepend each item to `a` one by one. We therefore disallow such syntax sugar.\n\
+Solution: directly use `concat`."
+
+  let variantIdent = "A polymorphic variant (e.g. #id) must start with an alphabetical letter or be a number (e.g. #742)"
+
+  let experimentalIfLet = expr => {
+    let switchExpr = {...expr, Parsetree.pexp_attributes: list{}}
+    Doc.concat(list{
+      Doc.text("If-let is currently highly experimental."),
+      Doc.line,
+      Doc.text("Use a regular `switch` with pattern matching instead:"),
+      Doc.concat(list{
+        Doc.hardLine,
+        Doc.hardLine,
+        ResPrinter.printExpression(switchExpr, CommentTable.empty),
+      }),
+    }) |> Doc.toString(~width=80)
+  }
+
+  let typeParam = "A type param consists of a singlequote followed by a name like `'a` or `'A`"
+  let typeVar = "A type variable consists of a singlequote followed by a name like `'a` or `'A`"
+
+  let attributeWithoutNode = (attr: Parsetree.attribute) => {
+    let ({Asttypes.txt: attrName}, _) = attr
+    "Did you forget to attach `" ++
+    (attrName ++
+    ("` to an item?\n  Standalone attributes start with `@@` like: `@@" ++ (attrName ++ "`")))
+  }
+
+  let typeDeclarationNameLongident = longident =>
+    "A type declaration's name cannot contain a module access. Did you mean `" ++
+    (Longident.last(longident) ++
+    "`?")
+
+  let tupleSingleElement = "A tuple needs at least two elements"
+
+  let missingTildeLabeledParameter = name =>
+    if name == "" {
+      "A labeled parameter starts with a `~`."
+    } else {
+      "A labeled parameter starts with a `~`. Did you mean: `~" ++ (name ++ "`?")
+    }
+
+  let stringInterpolationInPattern = "String interpolation is not supported in pattern matching."
+
+  let spreadInRecordDeclaration = "A record type declaration doesn't support the ... spread. Only an object (with quoted field names) does."
+
+  let objectQuotedFieldName = name =>
+    "An object type declaration needs quoted field names. Did you mean \"" ++ (name ++ "\"?")
+
+  let forbiddenInlineRecordDeclaration = "An inline record type declaration is only allowed in a variant constructor's declaration"
+
+  let sameTypeSpread = "You're using a ... spread without extra fields. This is the same type."
+
+  let polyVarIntWithSuffix = number =>
+    "A numeric polymorphic variant cannot be followed by a letter. Did you mean `#" ++
+    (number ++
+    "`?")
+}
+
+let jsxAttr = (Location.mknoloc("JSX"), Parsetree.PStr(list{}))
+let uncurryAttr = (Location.mknoloc("bs"), Parsetree.PStr(list{}))
+let ternaryAttr = (Location.mknoloc("ns.ternary"), Parsetree.PStr(list{}))
+let ifLetAttr = (Location.mknoloc("ns.iflet"), Parsetree.PStr(list{}))
+let suppressFragileMatchWarningAttr = (
+  Location.mknoloc("warning"),
+  Parsetree.PStr(list{Ast_helper.Str.eval(Ast_helper.Exp.constant(Pconst_string("-4", None)))}),
+)
+let makeBracesAttr = loc => (Location.mkloc("ns.braces", loc), Parsetree.PStr(list{}))
+let templateLiteralAttr = (Location.mknoloc("res.template"), Parsetree.PStr(list{}))
+
+type stringLiteralState =
+  | Start
+  | Backslash
+  | HexEscape
+  | DecimalEscape
+  | OctalEscape
+  | UnicodeEscape
+  | UnicodeCodePointEscape
+  | UnicodeEscapeStart
+  | EscapedLineBreak
+
+type typDefOrExt =
+  | TypeDef({recFlag: Asttypes.rec_flag, types: list<Parsetree.type_declaration>})
+  | TypeExt(Parsetree.type_extension)
+
+type labelledParameter =
+  | TermParameter({
+      uncurried: bool,
+      attrs: Parsetree.attributes,
+      label: Asttypes.arg_label,
+      expr: option<Parsetree.expression>,
+      pat: Parsetree.pattern,
+      pos: Lexing.position,
+    })
+  | TypeParameter({
+      uncurried: bool,
+      attrs: Parsetree.attributes,
+      locs: list<Location.loc<string>>,
+      pos: Lexing.position,
+    })
+
+type recordPatternItem =
+  | PatUnderscore
+  | PatField((Ast_helper.lid, Parsetree.pattern))
+
+type context =
+  | OrdinaryExpr
+  | TernaryTrueBranchExpr
+  | WhenExpr
+
+let getClosingToken = x =>
+  switch x {
+  | Token.Lparen => Token.Rparen
+  | Lbrace => Rbrace
+  | Lbracket => Rbracket
+  | List => Rbrace
+  | LessThan => GreaterThan
+  | _ => assert false
+  }
+
+let rec goToClosing = (closingToken, state) =>
+  switch (state.Parser.token, closingToken) {
+  | (Rparen, Token.Rparen) | (Rbrace, Rbrace) | (Rbracket, Rbracket) | (GreaterThan, GreaterThan) =>
+    Parser.next(state)
+    ()
+  | ((Token.Lbracket | Lparen | Lbrace | List | LessThan) as t, _) =>
+    Parser.next(state)
+    goToClosing(getClosingToken(t), state)
+    goToClosing(closingToken, state)
+  | (Rparen | Token.Rbrace | Rbracket | Eof, _) => () /* TODO: how do report errors here? */
+  | _ =>
+    Parser.next(state)
+    goToClosing(closingToken, state)
+  }
+
+/* Madness */
+let isEs6ArrowExpression = (~inTernary, p) =>
+  Parser.lookahead(p, state =>
+    switch state.Parser.token {
+    | Lident(_) | Underscore =>
+      Parser.next(state)
+      switch state.Parser.token {
+      /* Don't think that this valid
+       * Imagine: let x = (a: int)
+       * This is a parenthesized expression with a type constraint, wait for
+       * the arrow */
+      /* | Colon when not inTernary -> true */
+      | EqualGreater => true
+      | _ => false
+      }
+    | Lparen =>
+      let prevEndPos = state.prevEndPos
+      Parser.next(state)
+      switch state.token {
+      /* arrived at `()` here */
+      | Rparen =>
+        Parser.next(state)
+        switch state.Parser.token {
+        /* arrived at `() :` here */
+        | Colon if !inTernary =>
+          Parser.next(state)
+          switch state.Parser.token {
+          /* arrived at `() :typ` here */
+          | Lident(_) =>
+            Parser.next(state)
+            switch state.Parser.token {
+            /* arrived at `() :typ<` here */
+            | LessThan =>
+              Parser.next(state)
+              goToClosing(GreaterThan, state)
+            | _ => ()
+            }
+            switch state.Parser.token {
+            /* arrived at `() :typ =>` or `() :typ<'a,'b> =>` here */
+            | EqualGreater => true
+            | _ => false
+            }
+          | _ => true
+          }
+        | EqualGreater => true
+        | _ => false
+        }
+      | Dot /* uncurried */ => true
+      | Tilde => true
+      | Backtick => false /* (` always indicates the start of an expr, can't be es6 parameter */
+      | _ =>
+        goToClosing(Rparen, state)
+        switch state.Parser.token {
+        | EqualGreater => true
+        /* | Lbrace TODO: detect missing =>, is this possible? */
+        | Colon if !inTernary => true
+        | Rparen => /* imagine having something as :
+           * switch colour {
+           * | Red
+           *    when l == l'
+           *    || (&Clflags.classic && (l == Nolabel && !is_optional(l'))) => (t1, t2)
+           * We'll arrive at the outer rparen just before the =>.
+           * This is not an es6 arrow.
+           * */
+          false
+        | _ =>
+          Parser.nextUnsafe(state)
+          /* error recovery, peek at the next token,
+           * (elements, providerId] => {
+           *  in the example above, we have an unbalanced ] here
+           */
+          switch state.Parser.token {
+          | EqualGreater if state.startPos.pos_lnum === prevEndPos.pos_lnum => true
+          | _ => false
+          }
+        }
+      }
+    | _ => false
+    }
+  )
+
+let isEs6ArrowFunctor = p =>
+  Parser.lookahead(p, state =>
+    switch state.Parser.token {
+    /* | Uident _ | Underscore -> */
+    /* Parser.next state; */
+    /* begin match state.Parser.token with */
+    /* | EqualGreater -> true */
+    /* | _ -> false */
+    /* end */
+    | Lparen =>
+      Parser.next(state)
+      switch state.token {
+      | Rparen =>
+        Parser.next(state)
+        switch state.token {
+        | Colon | EqualGreater => true
+        | _ => false
+        }
+      | _ =>
+        goToClosing(Rparen, state)
+        switch state.Parser.token {
+        | EqualGreater | Lbrace => true
+        | Colon => true
+        | _ => false
+        }
+      }
+    | _ => false
+    }
+  )
+
+let isEs6ArrowType = p =>
+  Parser.lookahead(p, state =>
+    switch state.Parser.token {
+    | Lparen =>
+      Parser.next(state)
+      switch state.Parser.token {
+      | Rparen =>
+        Parser.next(state)
+        switch state.Parser.token {
+        | EqualGreater => true
+        | _ => false
+        }
+      | Tilde | Dot => true
+      | _ =>
+        goToClosing(Rparen, state)
+        switch state.Parser.token {
+        | EqualGreater => true
+        | _ => false
+        }
+      }
+    | Tilde => true
+    | _ => false
+    }
+  )
+
+let buildLongident = words =>
+  switch List.rev(words) {
+  | list{} => assert false
+  | list{hd, ...tl} => List.fold_left((p, s) => Longident.Ldot(p, s), Lident(hd), tl)
+  }
+
+let makeInfixOperator = (p, token, startPos, endPos) => {
+  let stringifiedToken = if token == Token.MinusGreater {
+    "|."
+  } else if token == Token.PlusPlus {
+    "^"
+  } else if token == Token.BangEqual {
+    "<>"
+  } else if token == Token.BangEqualEqual {
+    "!="
+  } else if token == Token.Equal {
+    /* TODO: could have a totally different meaning like x->fooSet(y) */
+    Parser.err(~startPos, ~endPos, p, Diagnostics.message("Did you mean `==` here?"))
+    "="
+  } else if token == Token.EqualEqual {
+    "="
+  } else if token == Token.EqualEqualEqual {
+    "=="
+  } else {
+    Token.toString(token)
+  }
+
+  let loc = mkLoc(startPos, endPos)
+  let operator = Location.mkloc(Longident.Lident(stringifiedToken), loc)
+
+  Ast_helper.Exp.ident(~loc, operator)
+}
+
+let negateString = s =>
+  if String.length(s) > 0 && @doesNotRaise String.get(s, 0) == '-' {
+    (@doesNotRaise String.sub)(s, 1, String.length(s) - 1)
+  } else {
+    "-" ++ s
+  }
+
+let makeUnaryExpr = (startPos, tokenEnd, token, operand) =>
+  switch (token, operand.Parsetree.pexp_desc) {
+  | (Token.Plus | PlusDot, Pexp_constant(Pconst_integer(_) | Pconst_float(_))) => operand
+  | (Minus, Pexp_constant(Pconst_integer(n, m))) => {
+      ...operand,
+      pexp_desc: Pexp_constant(Pconst_integer(negateString(n), m)),
+    }
+  | (Minus | MinusDot, Pexp_constant(Pconst_float(n, m))) => {
+      ...operand,
+      pexp_desc: Pexp_constant(Pconst_float(negateString(n), m)),
+    }
+  | (Token.Plus | PlusDot | Minus | MinusDot, _) =>
+    let tokenLoc = mkLoc(startPos, tokenEnd)
+    let operator = "~" ++ Token.toString(token)
+    Ast_helper.Exp.apply(
+      ~loc=mkLoc(startPos, operand.Parsetree.pexp_loc.loc_end),
+      Ast_helper.Exp.ident(~loc=tokenLoc, Location.mkloc(Longident.Lident(operator), tokenLoc)),
+      list{(Nolabel, operand)},
+    )
+  | (Token.Bang, _) =>
+    let tokenLoc = mkLoc(startPos, tokenEnd)
+    Ast_helper.Exp.apply(
+      ~loc=mkLoc(startPos, operand.Parsetree.pexp_loc.loc_end),
+      Ast_helper.Exp.ident(~loc=tokenLoc, Location.mkloc(Longident.Lident("not"), tokenLoc)),
+      list{(Nolabel, operand)},
+    )
+  | _ => operand
+  }
+
+let makeListExpression = (loc, seq, extOpt) => {
+  let rec handleSeq = x =>
+    switch x {
+    | list{} =>
+      switch extOpt {
+      | Some(ext) => ext
+      | None =>
+        let loc = {...loc, Location.loc_ghost: true}
+        let nil = Location.mkloc(Longident.Lident("[]"), loc)
+        Ast_helper.Exp.construct(~loc, nil, None)
+      }
+    | list{e1, ...el} =>
+      let exp_el = handleSeq(el)
+      let loc = mkLoc(e1.Parsetree.pexp_loc.Location.loc_start, exp_el.pexp_loc.loc_end)
+
+      let arg = Ast_helper.Exp.tuple(~loc, list{e1, exp_el})
+      Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("::"), loc), Some(arg))
+    }
+
+  let expr = handleSeq(seq)
+  {...expr, pexp_loc: loc}
+}
+
+let makeListPattern = (loc, seq, ext_opt) => {
+  let rec handle_seq = x =>
+    switch x {
+    | list{} =>
+      let base_case = switch ext_opt {
+      | Some(ext) => ext
+      | None =>
+        let loc = {...loc, Location.loc_ghost: true}
+        let nil = {Location.txt: Longident.Lident("[]"), loc: loc}
+        Ast_helper.Pat.construct(~loc, nil, None)
+      }
+
+      base_case
+    | list{p1, ...pl} =>
+      let pat_pl = handle_seq(pl)
+      let loc = mkLoc(p1.Parsetree.ppat_loc.loc_start, pat_pl.ppat_loc.loc_end)
+      let arg = Ast_helper.Pat.mk(~loc, Ppat_tuple(list{p1, pat_pl}))
+      Ast_helper.Pat.mk(
+        ~loc,
+        Ppat_construct(Location.mkloc(Longident.Lident("::"), loc), Some(arg)),
+      )
+    }
+
+  handle_seq(seq)
+}
+
+/* TODO: diagnostic reporting */
+let lidentOfPath = longident =>
+  switch Longident.flatten(longident) |> List.rev {
+  | list{} => ""
+  | list{ident, ..._} => ident
+  }
+
+let makeNewtypes = (~attrs, ~loc, newtypes, exp) => {
+  let expr = List.fold_right(
+    (newtype, exp) => Ast_helper.Exp.mk(~loc, Pexp_newtype(newtype, exp)),
+    newtypes,
+    exp,
+  )
+  {...expr, pexp_attributes: attrs}
+}
+
+/* locally abstract types syntax sugar
+ * Transforms
+ *  let f: type t u v. = (foo : list</t, u, v/>) => ...
+ * into
+ *  let f = (type t u v. foo : list</t, u, v/>) => ...
+ */
+let wrapTypeAnnotation = (~loc, newtypes, core_type, body) => {
+  let exp = makeNewtypes(
+    ~attrs=list{},
+    ~loc,
+    newtypes,
+    Ast_helper.Exp.constraint_(~loc, body, core_type),
+  )
+
+  let typ = Ast_helper.Typ.poly(
+    ~loc,
+    newtypes,
+    Ast_helper.Typ.varify_constructors(newtypes, core_type),
+  )
+
+  (exp, typ)
+}
+
+@ocaml.doc("
+  * process the occurrence of _ in the arguments of a function application
+  * replace _ with a new variable, currently __x, in the arguments
+  * return a wrapping function that wraps ((__x) => ...) around an expression
+  * e.g. foo(_, 3) becomes (__x) => foo(__x, 3)
+  ")
+let processUnderscoreApplication = args => {
+  let exp_question = ref(None)
+  let hidden_var = "__x"
+  let check_arg = ((lab, exp) as arg) =>
+    switch exp.Parsetree.pexp_desc {
+    | Pexp_ident({txt: Lident("_")} as id) =>
+      let new_id = Location.mkloc(Longident.Lident(hidden_var), id.loc)
+      let new_exp = Ast_helper.Exp.mk(Pexp_ident(new_id), ~loc=exp.pexp_loc)
+      exp_question := Some(new_exp)
+      (lab, new_exp)
+    | _ => arg
+    }
+
+  let args = List.map(check_arg, args)
+  let wrap = exp_apply =>
+    switch exp_question.contents {
+    | Some({pexp_loc: loc}) =>
+      let pattern = Ast_helper.Pat.mk(Ppat_var(Location.mkloc(hidden_var, loc)), ~loc)
+      Ast_helper.Exp.mk(Pexp_fun(Nolabel, None, pattern, exp_apply), ~loc)
+    | None => exp_apply
+    }
+
+  (args, wrap)
+}
+
+let hexValue = ch =>
+  switch ch {
+  | '0' .. '9' => Char.code(ch) - 48
+  | 'a' .. 'f' => Char.code(ch) - Char.code('a') + 10
+  | 'A' .. 'F' => Char.code(ch) + 32 - Char.code('a') + 10
+  | _ => 16
+  } /* larger than any legal value */
+
+/* Transform A.a into a. For use with punned record fields as in {A.a, b}. */
+let removeModuleNameFromPunnedFieldValue = exp =>
+  switch exp.Parsetree.pexp_desc {
+  | Pexp_ident(pathIdent) => {
+      ...exp,
+      pexp_desc: Pexp_ident({...pathIdent, txt: Lident(Longident.last(pathIdent.txt))}),
+    }
+  | _ => exp
+  }
+
+let parseStringLiteral = s => {
+  let len = String.length(s)
+  let b = Buffer.create(String.length(s))
+
+  let rec parse = (state, i, d) =>
+    if i == len {
+      switch state {
+      | HexEscape | DecimalEscape | OctalEscape | UnicodeEscape | UnicodeCodePointEscape => false
+      | _ => true
+      }
+    } else {
+      let c = String.unsafe_get(s, i)
+      switch state {
+      | Start =>
+        switch c {
+        | '\\' => parse(Backslash, i + 1, d)
+        | c =>
+          Buffer.add_char(b, c)
+          parse(Start, i + 1, d)
+        }
+      | Backslash =>
+        switch c {
+        | 'n' =>
+          Buffer.add_char(b, '\n')
+          parse(Start, i + 1, d)
+        | 'r' =>
+          Buffer.add_char(b, '\r')
+          parse(Start, i + 1, d)
+        | 'b' =>
+          Buffer.add_char(b, '\b')
+          parse(Start, i + 1, d)
+        | 't' =>
+          Buffer.add_char(b, '\t')
+          parse(Start, i + 1, d)
+        | ('\\' | ' ' | '\'' | '"') as c =>
+          Buffer.add_char(b, c)
+          parse(Start, i + 1, d)
+        | 'x' => parse(HexEscape, i + 1, 0)
+        | 'o' => parse(OctalEscape, i + 1, 0)
+        | 'u' => parse(UnicodeEscapeStart, i + 1, 0)
+        | '0' .. '9' => parse(DecimalEscape, i, 0)
+        | '\n' | '\r' => parse(EscapedLineBreak, i + 1, d)
+        | c =>
+          Buffer.add_char(b, '\\')
+          Buffer.add_char(b, c)
+          parse(Start, i + 1, d)
+        }
+      | HexEscape =>
+        if d === 1 {
+          let c0 = String.unsafe_get(s, i - 1)
+          let c1 = String.unsafe_get(s, i)
+          let c = 16 * hexValue(c0) + hexValue(c1)
+          if c < 0 || c > 255 {
+            false
+          } else {
+            Buffer.add_char(b, Char.unsafe_chr(c))
+            parse(Start, i + 1, 0)
+          }
+        } else {
+          parse(HexEscape, i + 1, d + 1)
+        }
+      | DecimalEscape =>
+        if d === 2 {
+          let c0 = String.unsafe_get(s, i - 2)
+          let c1 = String.unsafe_get(s, i - 1)
+          let c2 = String.unsafe_get(s, i)
+          let c = 100 * (Char.code(c0) - 48) + 10 * (Char.code(c1) - 48) + (Char.code(c2) - 48)
+          if c < 0 || c > 255 {
+            false
+          } else {
+            Buffer.add_char(b, Char.unsafe_chr(c))
+            parse(Start, i + 1, 0)
+          }
+        } else {
+          parse(DecimalEscape, i + 1, d + 1)
+        }
+      | OctalEscape =>
+        if d === 2 {
+          let c0 = String.unsafe_get(s, i - 2)
+          let c1 = String.unsafe_get(s, i - 1)
+          let c2 = String.unsafe_get(s, i)
+          let c = 64 * (Char.code(c0) - 48) + 8 * (Char.code(c1) - 48) + (Char.code(c2) - 48)
+          if c < 0 || c > 255 {
+            false
+          } else {
+            Buffer.add_char(b, Char.unsafe_chr(c))
+            parse(Start, i + 1, 0)
+          }
+        } else {
+          parse(OctalEscape, i + 1, d + 1)
+        }
+      | UnicodeEscapeStart =>
+        switch c {
+        | '{' => parse(UnicodeCodePointEscape, i + 1, 0)
+        | _ => parse(UnicodeEscape, i + 1, 1)
+        }
+      | UnicodeEscape =>
+        if d === 3 {
+          let c0 = String.unsafe_get(s, i - 3)
+          let c1 = String.unsafe_get(s, i - 2)
+          let c2 = String.unsafe_get(s, i - 1)
+          let c3 = String.unsafe_get(s, i)
+          let c = 4096 * hexValue(c0) + 256 * hexValue(c1) + 16 * hexValue(c2) + hexValue(c3)
+          if Res_utf8.isValidCodePoint(c) {
+            let codePoint = Res_utf8.encodeCodePoint(c)
+            Buffer.add_string(b, codePoint)
+            parse(Start, i + 1, 0)
+          } else {
+            false
+          }
+        } else {
+          parse(UnicodeEscape, i + 1, d + 1)
+        }
+      | UnicodeCodePointEscape =>
+        switch c {
+        | '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' => parse(UnicodeCodePointEscape, i + 1, d + 1)
+        | '}' =>
+          let x = ref(0)
+          for remaining in d downto 1 {
+            let ix = i - remaining
+            x := x.contents * 16 + hexValue(String.unsafe_get(s, ix))
+          }
+          let c = x.contents
+          if Res_utf8.isValidCodePoint(c) {
+            let codePoint = Res_utf8.encodeCodePoint(x.contents)
+            Buffer.add_string(b, codePoint)
+            parse(Start, i + 1, 0)
+          } else {
+            false
+          }
+        | _ => false
+        }
+      | EscapedLineBreak =>
+        switch c {
+        | ' ' | '\t' => parse(EscapedLineBreak, i + 1, d)
+        | c =>
+          Buffer.add_char(b, c)
+          parse(Start, i + 1, d)
+        }
+      }
+    }
+
+  if parse(Start, 0, 0) {
+    Buffer.contents(b)
+  } else {
+    s
+  }
+}
+
+let rec parseLident = p => {
+  let recoverLident = p =>
+    if Token.isKeyword(p.Parser.token) && p.Parser.prevEndPos.pos_lnum === p.startPos.pos_lnum {
+      Parser.err(p, Diagnostics.lident(p.Parser.token))
+      Parser.next(p)
+      None
+    } else {
+      let rec loop = p =>
+        if !Recover.shouldAbortListParse(p) {
+          Parser.next(p)
+          loop(p)
+        }
+
+      Parser.err(p, Diagnostics.lident(p.Parser.token))
+      Parser.next(p)
+      loop(p)
+      switch p.Parser.token {
+      | Lident(_) => Some()
+      | _ => None
+      }
+    }
+
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Lident(ident) =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    (ident, loc)
+  | _ =>
+    switch recoverLident(p) {
+    | Some() => parseLident(p)
+    | None => ("_", mkLoc(startPos, p.prevEndPos))
+    }
+  }
+}
+
+let parseIdent = (~msg, ~startPos, p) =>
+  switch p.Parser.token {
+  | Lident(ident)
+  | Uident(ident) =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    (ident, loc)
+  | token if Token.isKeyword(token) && p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+    let tokenTxt = Token.toString(token)
+    let msg =
+      "`" ++
+      (tokenTxt ++
+      ("` is a reserved keyword. Keywords need to be escaped: \\\"" ++ (tokenTxt ++ "\"")))
+
+    Parser.err(~startPos, p, Diagnostics.message(msg))
+    Parser.next(p)
+    (tokenTxt, mkLoc(startPos, p.prevEndPos))
+  | _token =>
+    Parser.err(~startPos, p, Diagnostics.message(msg))
+    Parser.next(p)
+    ("", mkLoc(startPos, p.prevEndPos))
+  }
+
+let parseHashIdent = (~startPos, p) => {
+  Parser.expect(Hash, p)
+  switch p.token {
+  | String(text) =>
+    let text = if p.mode == ParseForTypeChecker {
+      parseStringLiteral(text)
+    } else {
+      text
+    }
+    Parser.next(p)
+    (text, mkLoc(startPos, p.prevEndPos))
+  | Int({i, suffix}) =>
+    let () = switch suffix {
+    | Some(_) => Parser.err(p, Diagnostics.message(ErrorMessages.polyVarIntWithSuffix(i)))
+    | None => ()
+    }
+
+    Parser.next(p)
+    (i, mkLoc(startPos, p.prevEndPos))
+  | _ => parseIdent(~startPos, ~msg=ErrorMessages.variantIdent, p)
+  }
+}
+
+/* Ldot (Ldot (Lident "Foo", "Bar"), "baz") */
+let parseValuePath = p => {
+  let startPos = p.Parser.startPos
+  let rec aux = (p, path) =>
+    switch p.Parser.token {
+    | Lident(ident) => Longident.Ldot(path, ident)
+    | Uident(uident) =>
+      Parser.next(p)
+      if p.Parser.token == Dot {
+        Parser.expect(Dot, p)
+        aux(p, Ldot(path, uident))
+      } else {
+        Parser.err(p, Diagnostics.unexpected(p.Parser.token, p.breadcrumbs))
+        path
+      }
+    | token =>
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      Longident.Ldot(path, "_")
+    }
+
+  let ident = switch p.Parser.token {
+  | Lident(ident) => Longident.Lident(ident)
+  | Uident(ident) =>
+    Parser.next(p)
+    if p.Parser.token == Dot {
+      Parser.expect(Dot, p)
+      aux(p, Lident(ident))
+    } else {
+      Parser.err(p, Diagnostics.unexpected(p.Parser.token, p.breadcrumbs))
+      Longident.Lident(ident)
+    }
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Longident.Lident("_")
+  }
+
+  Parser.next(p)
+  Location.mkloc(ident, mkLoc(startPos, p.prevEndPos))
+}
+
+let parseValuePathAfterDot = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Lident(_)
+  | Uident(_) =>
+    parseValuePath(p)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Location.mkloc(Longident.Lident("_"), mkLoc(startPos, p.prevEndPos))
+  }
+}
+
+let parseValuePathTail = (p, startPos, ident) => {
+  let rec loop = (p, path) =>
+    switch p.Parser.token {
+    | Lident(ident) =>
+      Parser.next(p)
+      Location.mkloc(Longident.Ldot(path, ident), mkLoc(startPos, p.prevEndPos))
+    | Uident(ident) =>
+      Parser.next(p)
+      Parser.expect(Dot, p)
+      loop(p, Longident.Ldot(path, ident))
+    | token =>
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      Location.mkloc(Longident.Ldot(path, "_"), mkLoc(startPos, p.prevEndPos))
+    }
+
+  loop(p, ident)
+}
+
+let parseModuleLongIdentTail = (~lowercase, p, startPos, ident) => {
+  let rec loop = (p, acc) =>
+    switch p.Parser.token {
+    | Lident(ident) if lowercase =>
+      Parser.next(p)
+      let lident = Longident.Ldot(acc, ident)
+      Location.mkloc(lident, mkLoc(startPos, p.prevEndPos))
+    | Uident(ident) =>
+      Parser.next(p)
+      let endPos = p.prevEndPos
+      let lident = Longident.Ldot(acc, ident)
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        loop(p, lident)
+      | _ => Location.mkloc(lident, mkLoc(startPos, endPos))
+      }
+    | t =>
+      Parser.err(p, Diagnostics.uident(t))
+      Location.mkloc(Longident.Ldot(acc, "_"), mkLoc(startPos, p.prevEndPos))
+    }
+
+  loop(p, ident)
+}
+
+/* Parses module identifiers:
+     Foo
+     Foo.Bar */
+let parseModuleLongIdent = (~lowercase, p) => {
+  /* Parser.leaveBreadcrumb p Reporting.ModuleLongIdent; */
+  let startPos = p.Parser.startPos
+  let moduleIdent = switch p.Parser.token {
+  | Lident(ident) if lowercase =>
+    let loc = mkLoc(startPos, p.endPos)
+    let lident = Longident.Lident(ident)
+    Parser.next(p)
+    Location.mkloc(lident, loc)
+  | Uident(ident) =>
+    let lident = Longident.Lident(ident)
+    let endPos = p.endPos
+    Parser.next(p)
+    switch p.Parser.token {
+    | Dot =>
+      Parser.next(p)
+      parseModuleLongIdentTail(~lowercase, p, startPos, lident)
+    | _ => Location.mkloc(lident, mkLoc(startPos, endPos))
+    }
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mkloc(Longident.Lident("_"), mkLoc(startPos, p.prevEndPos))
+  }
+
+  /* Parser.eatBreadcrumb p; */
+  moduleIdent
+}
+
+/* `window.location` or `Math` or `Foo.Bar` */
+let parseIdentPath = p => {
+  let rec loop = (p, acc) =>
+    switch p.Parser.token {
+    | Uident(ident) | Lident(ident) =>
+      Parser.next(p)
+      let lident = Longident.Ldot(acc, ident)
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        loop(p, lident)
+      | _ => lident
+      }
+    | _t => acc
+    }
+
+  switch p.Parser.token {
+  | Lident(ident) | Uident(ident) =>
+    Parser.next(p)
+    switch p.Parser.token {
+    | Dot =>
+      Parser.next(p)
+      loop(p, Longident.Lident(ident))
+    | _ => Longident.Lident(ident)
+    }
+  | _ => Longident.Lident("_")
+  }
+}
+
+let verifyJsxOpeningClosingName = (p, nameExpr) => {
+  let closing = switch p.Parser.token {
+  | Lident(lident) =>
+    Parser.next(p)
+    Longident.Lident(lident)
+  | Uident(_) => parseModuleLongIdent(~lowercase=true, p).txt
+  | _ => Longident.Lident("")
+  }
+
+  switch nameExpr.Parsetree.pexp_desc {
+  | Pexp_ident(openingIdent) =>
+    let opening = {
+      let withoutCreateElement =
+        Longident.flatten(openingIdent.txt) |> List.filter(s => s != "createElement")
+
+      switch Longident.unflatten(withoutCreateElement) {
+      | Some(li) => li
+      | None => Longident.Lident("")
+      }
+    }
+
+    opening == closing
+  | _ => assert false
+  }
+}
+
+let string_of_pexp_ident = nameExpr =>
+  switch nameExpr.Parsetree.pexp_desc {
+  | Pexp_ident(openingIdent) =>
+    Longident.flatten(openingIdent.txt)
+    |> List.filter(s => s != "createElement")
+    |> String.concat(".")
+  | _ => ""
+  }
+
+/* open-def ::=
+ *   | open module-path
+ *   | open! module-path */
+let parseOpenDescription = (~attrs, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.OpenDescription)
+  let startPos = p.Parser.startPos
+  Parser.expect(Open, p)
+  let override = if Parser.optional(p, Token.Bang) {
+    Asttypes.Override
+  } else {
+    Asttypes.Fresh
+  }
+
+  let modident = parseModuleLongIdent(~lowercase=false, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Parser.eatBreadcrumb(p)
+  Ast_helper.Opn.mk(~loc, ~attrs, ~override, modident)
+}
+
+let parseTemplateStringLiteral = s => {
+  let len = String.length(s)
+  let b = Buffer.create(len)
+
+  let rec loop = i =>
+    if i < len {
+      let c = String.unsafe_get(s, i)
+      switch c {
+      | '\\' as c =>
+        if i + 1 < len {
+          let nextChar = String.unsafe_get(s, i + 1)
+          switch nextChar {
+          | '\\' as c =>
+            Buffer.add_char(b, c)
+            loop(i + 2)
+          | '$' as c =>
+            Buffer.add_char(b, c)
+            loop(i + 2)
+          | '`' as c =>
+            Buffer.add_char(b, c)
+            loop(i + 2)
+          | '\n' | '\r' =>
+            /* line break */
+            loop(i + 2)
+          | c =>
+            Buffer.add_char(b, '\\')
+            Buffer.add_char(b, c)
+            loop(i + 2)
+          }
+        } else {
+          Buffer.add_char(b, c)
+        }
+
+      | c =>
+        Buffer.add_char(b, c)
+        loop(i + 1)
+      }
+    } else {
+      ()
+    }
+
+  loop(0)
+  Buffer.contents(b)
+}
+
+/* constant	::=	integer-literal */
+/* ∣	 float-literal */
+/* ∣	 string-literal */
+let parseConstant = p => {
+  let isNegative = switch p.Parser.token {
+  | Token.Minus =>
+    Parser.next(p)
+    true
+  | Plus =>
+    Parser.next(p)
+    false
+  | _ => false
+  }
+
+  let constant = switch p.Parser.token {
+  | Int({i, suffix}) =>
+    let intTxt = if isNegative {
+      "-" ++ i
+    } else {
+      i
+    }
+    Parsetree.Pconst_integer(intTxt, suffix)
+  | Float({f, suffix}) =>
+    let floatTxt = if isNegative {
+      "-" ++ f
+    } else {
+      f
+    }
+    Parsetree.Pconst_float(floatTxt, suffix)
+  | String(s) =>
+    if p.mode == ParseForTypeChecker {
+      Pconst_string(s, Some("js"))
+    } else {
+      Pconst_string(s, None)
+    }
+  | Codepoint({c, original}) =>
+    if p.mode == ParseForTypeChecker {
+      Pconst_char(c)
+    } else {
+      /* Pconst_char char does not have enough information for formatting.
+       * When parsing for the printer, we encode the char contents as a string
+       * with a special prefix. */
+      Pconst_string(original, Some("INTERNAL_RES_CHAR_CONTENTS"))
+    }
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Pconst_string("", None)
+  }
+
+  Parser.next(p)
+  constant
+}
+
+let parseTemplateConstant = (~prefix, p: Parser.t) => {
+  /* Arrived at the ` char */
+  let startPos = p.startPos
+  Parser.nextTemplateLiteralToken(p)
+  switch p.token {
+  | TemplateTail(txt) =>
+    Parser.next(p)
+    let txt = if p.mode == ParseForTypeChecker {
+      parseTemplateStringLiteral(txt)
+    } else {
+      txt
+    }
+    Parsetree.Pconst_string(txt, prefix)
+  | _ =>
+    let rec skipTokens = () => {
+      Parser.next(p)
+      switch p.token {
+      | Backtick =>
+        Parser.next(p)
+        ()
+      | _ => skipTokens()
+      }
+    }
+
+    skipTokens()
+    Parser.err(
+      ~startPos,
+      ~endPos=p.prevEndPos,
+      p,
+      Diagnostics.message(ErrorMessages.stringInterpolationInPattern),
+    )
+    Pconst_string("", None)
+  }
+}
+
+let parseCommaDelimitedRegion = (p, ~grammar, ~closing, ~f) => {
+  Parser.leaveBreadcrumb(p, grammar)
+  let rec loop = nodes =>
+    switch f(p) {
+    | Some(node) =>
+      switch p.Parser.token {
+      | Comma =>
+        Parser.next(p)
+        loop(list{node, ...nodes})
+      | token if token == closing || token == Eof => List.rev(list{node, ...nodes})
+      | _ if Grammar.isListElement(grammar, p.token) =>
+        /* missing comma between nodes in the region and the current token
+         * looks like the start of something valid in the current region.
+         * Example:
+         *   type student<'extraInfo> = {
+         *     name: string,
+         *     age: int
+         *     otherInfo: 'extraInfo
+         *   }
+         * There is a missing comma between `int` and `otherInfo`.
+         * `otherInfo` looks like a valid start of the record declaration.
+         * We report the error here and then continue parsing the region.
+         */
+        Parser.expect(Comma, p)
+        loop(list{node, ...nodes})
+      | _ =>
+        if !(p.token == Eof || (p.token == closing || Recover.shouldAbortListParse(p))) {
+          Parser.expect(Comma, p)
+        }
+        if p.token == Semicolon {
+          Parser.next(p)
+        }
+        loop(list{node, ...nodes})
+      }
+    | None =>
+      if p.token == Eof || (p.token == closing || Recover.shouldAbortListParse(p)) {
+        List.rev(nodes)
+      } else {
+        Parser.err(p, Diagnostics.unexpected(p.token, p.breadcrumbs))
+        Parser.next(p)
+        loop(nodes)
+      }
+    }
+
+  let nodes = loop(list{})
+  Parser.eatBreadcrumb(p)
+  nodes
+}
+
+let parseCommaDelimitedReversedList = (p, ~grammar, ~closing, ~f) => {
+  Parser.leaveBreadcrumb(p, grammar)
+  let rec loop = nodes =>
+    switch f(p) {
+    | Some(node) =>
+      switch p.Parser.token {
+      | Comma =>
+        Parser.next(p)
+        loop(list{node, ...nodes})
+      | token if token == closing || token == Eof => list{node, ...nodes}
+      | _ if Grammar.isListElement(grammar, p.token) =>
+        /* missing comma between nodes in the region and the current token
+         * looks like the start of something valid in the current region.
+         * Example:
+         *   type student<'extraInfo> = {
+         *     name: string,
+         *     age: int
+         *     otherInfo: 'extraInfo
+         *   }
+         * There is a missing comma between `int` and `otherInfo`.
+         * `otherInfo` looks like a valid start of the record declaration.
+         * We report the error here and then continue parsing the region.
+         */
+        Parser.expect(Comma, p)
+        loop(list{node, ...nodes})
+      | _ =>
+        if !(p.token == Eof || (p.token == closing || Recover.shouldAbortListParse(p))) {
+          Parser.expect(Comma, p)
+        }
+        if p.token == Semicolon {
+          Parser.next(p)
+        }
+        loop(list{node, ...nodes})
+      }
+    | None =>
+      if p.token == Eof || (p.token == closing || Recover.shouldAbortListParse(p)) {
+        nodes
+      } else {
+        Parser.err(p, Diagnostics.unexpected(p.token, p.breadcrumbs))
+        Parser.next(p)
+        loop(nodes)
+      }
+    }
+
+  let nodes = loop(list{})
+  Parser.eatBreadcrumb(p)
+  nodes
+}
+
+let parseDelimitedRegion = (p, ~grammar, ~closing, ~f) => {
+  Parser.leaveBreadcrumb(p, grammar)
+  let rec loop = nodes =>
+    switch f(p) {
+    | Some(node) => loop(list{node, ...nodes})
+    | None =>
+      if p.Parser.token == Token.Eof || (p.token == closing || Recover.shouldAbortListParse(p)) {
+        List.rev(nodes)
+      } else {
+        Parser.err(p, Diagnostics.unexpected(p.token, p.breadcrumbs))
+        Parser.next(p)
+        loop(nodes)
+      }
+    }
+
+  let nodes = loop(list{})
+  Parser.eatBreadcrumb(p)
+  nodes
+}
+
+let parseRegion = (p, ~grammar, ~f) => {
+  Parser.leaveBreadcrumb(p, grammar)
+  let rec loop = nodes =>
+    switch f(p) {
+    | Some(node) => loop(list{node, ...nodes})
+    | None =>
+      if p.Parser.token == Token.Eof || Recover.shouldAbortListParse(p) {
+        List.rev(nodes)
+      } else {
+        Parser.err(p, Diagnostics.unexpected(p.token, p.breadcrumbs))
+        Parser.next(p)
+        loop(nodes)
+      }
+    }
+
+  let nodes = loop(list{})
+  Parser.eatBreadcrumb(p)
+  nodes
+}
+
+/* let-binding	::=	pattern =  expr */
+/* ∣	 value-name  { parameter }  [: typexpr]  [:> typexpr] =  expr */
+/* ∣	 value-name :  poly-typexpr =  expr */
+
+/* pattern	::=	value-name */
+/* ∣	 _ */
+/* ∣	 constant */
+/* ∣	 pattern as  value-name */
+/* ∣	 ( pattern ) */
+/* ∣	 ( pattern :  typexpr ) */
+/* ∣	 pattern |  pattern */
+/* ∣	 constr  pattern */
+/* ∣	 #variant variant-pattern */
+/* ∣	 #...type */
+/* ∣	 / pattern  { , pattern }+  / */
+/* ∣	 { field  [: typexpr]  [= pattern] { ; field  [: typexpr]  [= pattern] }  [; _ ] [ ; ] } */
+/* ∣	 [ pattern  { ; pattern }  [ ; ] ] */
+/* ∣	 pattern ::  pattern */
+/* ∣	 [| pattern  { ; pattern }  [ ; ] |] */
+/* ∣	 char-literal ..  char-literal */
+/* 	∣	 exception pattern */
+let rec parsePattern = (~alias=true, ~or_=true, p) => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  let pat = switch p.Parser.token {
+  | (True | False) as token =>
+    let endPos = p.endPos
+    Parser.next(p)
+    let loc = mkLoc(startPos, endPos)
+    Ast_helper.Pat.construct(
+      ~loc,
+      Location.mkloc(Longident.Lident(Token.toString(token)), loc),
+      None,
+    )
+  | Int(_) | String(_) | Float(_) | Codepoint(_) | Minus | Plus =>
+    let c = parseConstant(p)
+    switch p.token {
+    | DotDot =>
+      Parser.next(p)
+      let c2 = parseConstant(p)
+      Ast_helper.Pat.interval(~loc=mkLoc(startPos, p.prevEndPos), c, c2)
+    | _ => Ast_helper.Pat.constant(~loc=mkLoc(startPos, p.prevEndPos), c)
+    }
+  | Backtick =>
+    let constant = parseTemplateConstant(~prefix=Some("js"), p)
+    Ast_helper.Pat.constant(
+      ~attrs=list{templateLiteralAttr},
+      ~loc=mkLoc(startPos, p.prevEndPos),
+      constant,
+    )
+  | Lparen =>
+    Parser.next(p)
+    switch p.token {
+    | Rparen =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let lid = Location.mkloc(Longident.Lident("()"), loc)
+      Ast_helper.Pat.construct(~loc, lid, None)
+    | _ =>
+      let pat = parseConstrainedPattern(p)
+      switch p.token {
+      | Comma =>
+        Parser.next(p)
+        parseTuplePattern(~attrs, ~first=pat, ~startPos, p)
+      | _ =>
+        Parser.expect(Rparen, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        {...pat, ppat_loc: loc}
+      }
+    }
+  | Lbracket => parseArrayPattern(~attrs, p)
+  | Lbrace => parseRecordPattern(~attrs, p)
+  | Underscore =>
+    let endPos = p.endPos
+    let loc = mkLoc(startPos, endPos)
+    Parser.next(p)
+    Ast_helper.Pat.any(~loc, ~attrs, ())
+  | Lident(ident) =>
+    let endPos = p.endPos
+    let loc = mkLoc(startPos, endPos)
+    Parser.next(p)
+    switch p.token {
+    | Backtick =>
+      let constant = parseTemplateConstant(~prefix=Some(ident), p)
+      Ast_helper.Pat.constant(~loc=mkLoc(startPos, p.prevEndPos), constant)
+    | _ => Ast_helper.Pat.var(~loc, ~attrs, Location.mkloc(ident, loc))
+    }
+  | Uident(_) =>
+    let constr = parseModuleLongIdent(~lowercase=false, p)
+    switch p.Parser.token {
+    | Lparen => parseConstructorPatternArgs(p, constr, startPos, attrs)
+    | _ => Ast_helper.Pat.construct(~loc=constr.loc, ~attrs, constr, None)
+    }
+  | Hash =>
+    Parser.next(p)
+    if p.Parser.token === DotDotDot {
+      Parser.next(p)
+      let ident = parseValuePath(p)
+      let loc = mkLoc(startPos, ident.loc.loc_end)
+      Ast_helper.Pat.type_(~loc, ~attrs, ident)
+    } else {
+      let (ident, loc) = switch p.token {
+      | String(text) =>
+        let text = if p.mode == ParseForTypeChecker {
+          parseStringLiteral(text)
+        } else {
+          text
+        }
+        Parser.next(p)
+        (text, mkLoc(startPos, p.prevEndPos))
+      | Int({i, suffix}) =>
+        let () = switch suffix {
+        | Some(_) => Parser.err(p, Diagnostics.message(ErrorMessages.polyVarIntWithSuffix(i)))
+        | None => ()
+        }
+
+        Parser.next(p)
+        (i, mkLoc(startPos, p.prevEndPos))
+      | _ => parseIdent(~msg=ErrorMessages.variantIdent, ~startPos, p)
+      }
+
+      switch p.Parser.token {
+      | Lparen => parseVariantPatternArgs(p, ident, startPos, attrs)
+      | _ => Ast_helper.Pat.variant(~loc, ~attrs, ident, None)
+      }
+    }
+  | Exception =>
+    Parser.next(p)
+    let pat = parsePattern(~alias=false, ~or_=false, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Pat.exception_(~loc, ~attrs, pat)
+  | Lazy =>
+    Parser.next(p)
+    let pat = parsePattern(~alias=false, ~or_=false, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Pat.lazy_(~loc, ~attrs, pat)
+  | List =>
+    Parser.next(p)
+    parseListPattern(~startPos, ~attrs, p)
+  | Module => parseModulePattern(~attrs, p)
+  | Percent =>
+    let extension = parseExtension(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Pat.extension(~loc, ~attrs, extension)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    switch skipTokensAndMaybeRetry(p, ~isStartOfGrammar=Grammar.isAtomicPatternStart) {
+    | None => Recover.defaultPattern()
+    | Some() => parsePattern(p)
+    }
+  }
+
+  let pat = if alias {
+    parseAliasPattern(~attrs, pat, p)
+  } else {
+    pat
+  }
+  if or_ {
+    parseOrPattern(pat, p)
+  } else {
+    pat
+  }
+}
+
+and skipTokensAndMaybeRetry = (p, ~isStartOfGrammar) =>
+  if Token.isKeyword(p.Parser.token) && p.Parser.prevEndPos.pos_lnum === p.startPos.pos_lnum {
+    Parser.next(p)
+    None
+  } else if Recover.shouldAbortListParse(p) {
+    if isStartOfGrammar(p.Parser.token) {
+      Parser.next(p)
+      Some()
+    } else {
+      None
+    }
+  } else {
+    Parser.next(p)
+    let rec loop = p =>
+      if !Recover.shouldAbortListParse(p) {
+        Parser.next(p)
+        loop(p)
+      }
+    loop(p)
+    if isStartOfGrammar(p.Parser.token) {
+      Some()
+    } else {
+      None
+    }
+  }
+
+/* alias ::= pattern as lident */
+and parseAliasPattern = (~attrs, pattern, p) =>
+  switch p.Parser.token {
+  | As =>
+    Parser.next(p)
+    let (name, loc) = parseLident(p)
+    let name = Location.mkloc(name, loc)
+    Ast_helper.Pat.alias(~loc={...pattern.ppat_loc, loc_end: p.prevEndPos}, ~attrs, pattern, name)
+  | _ => pattern
+  }
+
+/* or ::= pattern | pattern
+ * precedence: Red | Blue | Green is interpreted as (Red | Blue) | Green */
+and parseOrPattern = (pattern1, p) => {
+  let rec loop = pattern1 =>
+    switch p.Parser.token {
+    | Bar =>
+      Parser.next(p)
+      let pattern2 = parsePattern(~or_=false, p)
+      let loc = {
+        ...pattern1.Parsetree.ppat_loc,
+        loc_end: pattern2.ppat_loc.loc_end,
+      }
+      loop(Ast_helper.Pat.or_(~loc, pattern1, pattern2))
+    | _ => pattern1
+    }
+
+  loop(pattern1)
+}
+
+and parseNonSpreadPattern = (~msg, p) => {
+  let () = switch p.Parser.token {
+  | DotDotDot =>
+    Parser.err(p, Diagnostics.message(msg))
+    Parser.next(p)
+  | _ => ()
+  }
+
+  switch p.Parser.token {
+  | token if Grammar.isPatternStart(token) =>
+    let pat = parsePattern(p)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(pat.ppat_loc.loc_start, typ.Parsetree.ptyp_loc.loc_end)
+      Some(Ast_helper.Pat.constraint_(~loc, pat, typ))
+    | _ => Some(pat)
+    }
+  | _ => None
+  }
+}
+
+and parseConstrainedPattern = p => {
+  let pat = parsePattern(p)
+  switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    let typ = parseTypExpr(p)
+    let loc = mkLoc(pat.ppat_loc.loc_start, typ.Parsetree.ptyp_loc.loc_end)
+    Ast_helper.Pat.constraint_(~loc, pat, typ)
+  | _ => pat
+  }
+}
+
+and parseConstrainedPatternRegion = p =>
+  switch p.Parser.token {
+  | token if Grammar.isPatternStart(token) => Some(parseConstrainedPattern(p))
+  | _ => None
+  }
+
+/* field ::=
+ *   | longident
+ *   | longident : pattern
+ *   | longident as lident
+ *
+ *  row ::=
+ *	 | field ,
+ *	 | field , _
+ *	 | field , _,
+ */
+and parseRecordPatternField = p => {
+  let label = parseValuePath(p)
+  let pattern = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    parsePattern(p)
+  | _ => Ast_helper.Pat.var(~loc=label.loc, Location.mkloc(Longident.last(label.txt), label.loc))
+  }
+
+  (label, pattern)
+}
+
+/* TODO: there are better representations than PatField|Underscore ? */
+and parseRecordPatternItem = p =>
+  switch p.Parser.token {
+  | DotDotDot =>
+    Parser.next(p)
+    Some(true, PatField(parseRecordPatternField(p)))
+  | Uident(_) | Lident(_) => Some(false, PatField(parseRecordPatternField(p)))
+  | Underscore =>
+    Parser.next(p)
+    Some(false, PatUnderscore)
+  | _ => None
+  }
+
+and parseRecordPattern = (~attrs, p) => {
+  let startPos = p.startPos
+  Parser.expect(Lbrace, p)
+  let rawFields = parseCommaDelimitedReversedList(
+    p,
+    ~grammar=PatternRecord,
+    ~closing=Rbrace,
+    ~f=parseRecordPatternItem,
+  )
+
+  Parser.expect(Rbrace, p)
+  let (fields, closedFlag) = {
+    let (rawFields, flag) = switch rawFields {
+    | list{(_hasSpread, PatUnderscore), ...rest} => (rest, Asttypes.Open)
+    | rawFields => (rawFields, Asttypes.Closed)
+    }
+
+    List.fold_left(((fields, flag), curr) => {
+      let (hasSpread, field) = curr
+      switch field {
+      | PatField(field) =>
+        if hasSpread {
+          let (_, pattern) = field
+          Parser.err(
+            ~startPos=pattern.Parsetree.ppat_loc.loc_start,
+            p,
+            Diagnostics.message(ErrorMessages.recordPatternSpread),
+          )
+        }
+        (list{field, ...fields}, flag)
+      | PatUnderscore => (fields, flag)
+      }
+    }, (list{}, flag), rawFields)
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Pat.record(~loc, ~attrs, fields, closedFlag)
+}
+
+and parseTuplePattern = (~attrs, ~first, ~startPos, p) => {
+  let patterns = list{
+    first,
+    ...parseCommaDelimitedRegion(
+      p,
+      ~grammar=Grammar.PatternList,
+      ~closing=Rparen,
+      ~f=parseConstrainedPatternRegion,
+    ),
+  }
+
+  Parser.expect(Rparen, p)
+  let () = switch patterns {
+  | list{_} =>
+    Parser.err(
+      ~startPos,
+      ~endPos=p.prevEndPos,
+      p,
+      Diagnostics.message(ErrorMessages.tupleSingleElement),
+    )
+  | _ => ()
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Pat.tuple(~loc, ~attrs, patterns)
+}
+
+and parsePatternRegion = p =>
+  switch p.Parser.token {
+  | DotDotDot =>
+    Parser.next(p)
+    Some(true, parseConstrainedPattern(p))
+  | token if Grammar.isPatternStart(token) => Some(false, parseConstrainedPattern(p))
+  | _ => None
+  }
+
+and parseModulePattern = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Module, p)
+  Parser.expect(Lparen, p)
+  let uident = switch p.token {
+  | Uident(uident) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(uident, loc)
+  | _ =>
+    /* TODO: error recovery */
+    Location.mknoloc("_")
+  }
+
+  switch p.token {
+  | Colon =>
+    let colonStart = p.Parser.startPos
+    Parser.next(p)
+    let packageTypAttrs = parseAttributes(p)
+    let packageType = parsePackageType(~startPos=colonStart, ~attrs=packageTypAttrs, p)
+    Parser.expect(Rparen, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let unpack = Ast_helper.Pat.unpack(~loc=uident.loc, uident)
+    Ast_helper.Pat.constraint_(~loc, ~attrs, unpack, packageType)
+  | _ =>
+    Parser.expect(Rparen, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Pat.unpack(~loc, ~attrs, uident)
+  }
+}
+
+and parseListPattern = (~startPos, ~attrs, p) => {
+  let listPatterns = parseCommaDelimitedReversedList(
+    p,
+    ~grammar=Grammar.PatternOcamlList,
+    ~closing=Rbrace,
+    ~f=parsePatternRegion,
+  )
+
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  let filterSpread = ((hasSpread, pattern)) =>
+    if hasSpread {
+      Parser.err(
+        ~startPos=pattern.Parsetree.ppat_loc.loc_start,
+        p,
+        Diagnostics.message(ErrorMessages.listPatternSpread),
+      )
+      pattern
+    } else {
+      pattern
+    }
+
+  switch listPatterns {
+  | list{(true, pattern), ...patterns} =>
+    let patterns = patterns |> List.map(filterSpread) |> List.rev
+    let pat = makeListPattern(loc, patterns, Some(pattern))
+    {...pat, ppat_loc: loc, ppat_attributes: attrs}
+  | patterns =>
+    let patterns = patterns |> List.map(filterSpread) |> List.rev
+    let pat = makeListPattern(loc, patterns, None)
+    {...pat, ppat_loc: loc, ppat_attributes: attrs}
+  }
+}
+
+and parseArrayPattern = (~attrs, p) => {
+  let startPos = p.startPos
+  Parser.expect(Lbracket, p)
+  let patterns = parseCommaDelimitedRegion(
+    p,
+    ~grammar=Grammar.PatternList,
+    ~closing=Rbracket,
+    ~f=parseNonSpreadPattern(~msg=ErrorMessages.arrayPatternSpread),
+  )
+
+  Parser.expect(Rbracket, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Pat.array(~loc, ~attrs, patterns)
+}
+
+and parseConstructorPatternArgs = (p, constr, startPos, attrs) => {
+  let lparen = p.startPos
+  Parser.expect(Lparen, p)
+  let args = parseCommaDelimitedRegion(
+    p,
+    ~grammar=Grammar.PatternList,
+    ~closing=Rparen,
+    ~f=parseConstrainedPatternRegion,
+  )
+
+  Parser.expect(Rparen, p)
+  let args = switch args {
+  | list{} =>
+    let loc = mkLoc(lparen, p.prevEndPos)
+    Some(Ast_helper.Pat.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None))
+  | list{{ppat_desc: Ppat_tuple(_)} as pat} as patterns =>
+    if p.mode == ParseForTypeChecker {
+      /* Some(1, 2) for type-checker */
+      Some(pat)
+    } else {
+      /* Some((1, 2)) for printer */
+      Some(Ast_helper.Pat.tuple(~loc=mkLoc(lparen, p.endPos), patterns))
+    }
+  | list{pattern} => Some(pattern)
+  | patterns => Some(Ast_helper.Pat.tuple(~loc=mkLoc(lparen, p.endPos), patterns))
+  }
+
+  Ast_helper.Pat.construct(~loc=mkLoc(startPos, p.prevEndPos), ~attrs, constr, args)
+}
+
+and parseVariantPatternArgs = (p, ident, startPos, attrs) => {
+  let lparen = p.startPos
+  Parser.expect(Lparen, p)
+  let patterns = parseCommaDelimitedRegion(
+    p,
+    ~grammar=Grammar.PatternList,
+    ~closing=Rparen,
+    ~f=parseConstrainedPatternRegion,
+  )
+  let args = switch patterns {
+  | list{} =>
+    let loc = mkLoc(lparen, p.prevEndPos)
+    Some(Ast_helper.Pat.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None))
+  | list{{ppat_desc: Ppat_tuple(_)} as pat} as patterns =>
+    if p.mode == ParseForTypeChecker {
+      /* #ident(1, 2) for type-checker */
+      Some(pat)
+    } else {
+      /* #ident((1, 2)) for printer */
+      Some(Ast_helper.Pat.tuple(~loc=mkLoc(lparen, p.endPos), patterns))
+    }
+  | list{pattern} => Some(pattern)
+  | patterns => Some(Ast_helper.Pat.tuple(~loc=mkLoc(lparen, p.endPos), patterns))
+  }
+
+  Parser.expect(Rparen, p)
+  Ast_helper.Pat.variant(~loc=mkLoc(startPos, p.prevEndPos), ~attrs, ident, args)
+}
+
+and parseExpr = (~context=OrdinaryExpr, p) => {
+  let expr = parseOperandExpr(~context, p)
+  let expr = parseBinaryExpr(~context, ~a=expr, p, 1)
+  parseTernaryExpr(expr, p)
+}
+
+/* expr ? expr : expr */
+and parseTernaryExpr = (leftOperand, p) =>
+  switch p.Parser.token {
+  | Question =>
+    Parser.leaveBreadcrumb(p, Grammar.Ternary)
+    Parser.next(p)
+    let trueBranch = parseExpr(~context=TernaryTrueBranchExpr, p)
+    Parser.expect(Colon, p)
+    let falseBranch = parseExpr(p)
+    Parser.eatBreadcrumb(p)
+    let loc = {
+      ...leftOperand.Parsetree.pexp_loc,
+      loc_start: leftOperand.pexp_loc.loc_start,
+      loc_end: falseBranch.Parsetree.pexp_loc.loc_end,
+    }
+    Ast_helper.Exp.ifthenelse(
+      ~attrs=list{ternaryAttr},
+      ~loc,
+      leftOperand,
+      trueBranch,
+      Some(falseBranch),
+    )
+  | _ => leftOperand
+  }
+
+and parseEs6ArrowExpression = (~context=?, ~parameters=?, p) => {
+  let startPos = p.Parser.startPos
+  Parser.leaveBreadcrumb(p, Grammar.Es6ArrowExpr)
+  let parameters = switch parameters {
+  | Some(params) => params
+  | None => parseParameters(p)
+  }
+
+  let returnType = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    Some(parseTypExpr(~es6Arrow=false, p))
+  | _ => None
+  }
+
+  Parser.expect(EqualGreater, p)
+  let body = {
+    let expr = parseExpr(~context?, p)
+    switch returnType {
+    | Some(typ) =>
+      Ast_helper.Exp.constraint_(
+        ~loc=mkLoc(expr.pexp_loc.loc_start, typ.Parsetree.ptyp_loc.loc_end),
+        expr,
+        typ,
+      )
+    | None => expr
+    }
+  }
+
+  Parser.eatBreadcrumb(p)
+  let endPos = p.prevEndPos
+  let arrowExpr = List.fold_right((parameter, expr) =>
+    switch parameter {
+    | TermParameter({uncurried, attrs, label: lbl, expr: defaultExpr, pat, pos: startPos}) =>
+      let attrs = if uncurried {
+        list{uncurryAttr, ...attrs}
+      } else {
+        attrs
+      }
+      Ast_helper.Exp.fun_(~loc=mkLoc(startPos, endPos), ~attrs, lbl, defaultExpr, pat, expr)
+    | TypeParameter({uncurried, attrs, locs: newtypes, pos: startPos}) =>
+      let attrs = if uncurried {
+        list{uncurryAttr, ...attrs}
+      } else {
+        attrs
+      }
+      makeNewtypes(~attrs, ~loc=mkLoc(startPos, endPos), newtypes, expr)
+    }
+  , parameters, body)
+
+  {...arrowExpr, pexp_loc: {...arrowExpr.pexp_loc, loc_start: startPos}}
+}
+
+/*
+ * uncurried_parameter ::=
+ *   | . parameter
+ *
+ * parameter ::=
+ *   | pattern
+ *   | pattern : type
+ *   | ~ labelName
+ *   | ~ labelName as pattern
+ *   | ~ labelName as pattern : type
+ *   | ~ labelName = expr
+ *   | ~ labelName as pattern = expr
+ *   | ~ labelName as pattern : type = expr
+ *   | ~ labelName = ?
+ *   | ~ labelName as pattern = ?
+ *   | ~ labelName as pattern : type = ?
+ *
+ * labelName ::= lident
+ */
+and parseParameter = p =>
+  if (
+    p.Parser.token == Token.Typ ||
+      (p.token == Tilde ||
+      (p.token == Dot || Grammar.isPatternStart(p.token)))
+  ) {
+    let startPos = p.Parser.startPos
+    let uncurried = Parser.optional(p, Token.Dot)
+    /* two scenarios:
+     *   attrs ~lbl ...
+     *   attrs pattern
+     * Attributes before a labelled arg, indicate that it's on the whole arrow expr
+     * Otherwise it's part of the pattern
+     * */
+    let attrs = parseAttributes(p)
+    if p.Parser.token == Typ {
+      Parser.next(p)
+      let lidents = parseLidentList(p)
+      Some(TypeParameter({uncurried: uncurried, attrs: attrs, locs: lidents, pos: startPos}))
+    } else {
+      let (attrs, lbl, pat) = switch p.Parser.token {
+      | Tilde =>
+        Parser.next(p)
+        let (lblName, loc) = parseLident(p)
+        let propLocAttr = (Location.mkloc("ns.namedArgLoc", loc), Parsetree.PStr(list{}))
+        switch p.Parser.token {
+        | Comma | Equal | Rparen =>
+          let loc = mkLoc(startPos, p.prevEndPos)
+          (
+            attrs,
+            Asttypes.Labelled(lblName),
+            Ast_helper.Pat.var(~attrs=list{propLocAttr}, ~loc, Location.mkloc(lblName, loc)),
+          )
+        | Colon =>
+          let lblEnd = p.prevEndPos
+          Parser.next(p)
+          let typ = parseTypExpr(p)
+          let loc = mkLoc(startPos, lblEnd)
+          let pat = {
+            let pat = Ast_helper.Pat.var(~loc, Location.mkloc(lblName, loc))
+            let loc = mkLoc(startPos, p.prevEndPos)
+            Ast_helper.Pat.constraint_(~attrs=list{propLocAttr}, ~loc, pat, typ)
+          }
+          (attrs, Asttypes.Labelled(lblName), pat)
+        | As =>
+          Parser.next(p)
+          let pat = {
+            let pat = parseConstrainedPattern(p)
+            {...pat, ppat_attributes: list{propLocAttr, ...pat.ppat_attributes}}
+          }
+
+          (attrs, Asttypes.Labelled(lblName), pat)
+        | t =>
+          Parser.err(p, Diagnostics.unexpected(t, p.breadcrumbs))
+          let loc = mkLoc(startPos, p.prevEndPos)
+          (
+            attrs,
+            Asttypes.Labelled(lblName),
+            Ast_helper.Pat.var(~loc, Location.mkloc(lblName, loc)),
+          )
+        }
+      | _ =>
+        let pattern = parseConstrainedPattern(p)
+        let attrs = List.concat(list{attrs, pattern.ppat_attributes})
+        (list{}, Asttypes.Nolabel, {...pattern, ppat_attributes: attrs})
+      }
+
+      switch p.Parser.token {
+      | Equal =>
+        Parser.next(p)
+        let lbl = switch lbl {
+        | Asttypes.Labelled(lblName) => Asttypes.Optional(lblName)
+        | Asttypes.Nolabel =>
+          let lblName = switch pat.ppat_desc {
+          | Ppat_var(var) => var.txt
+          | _ => ""
+          }
+          Parser.err(
+            ~startPos,
+            ~endPos=p.prevEndPos,
+            p,
+            Diagnostics.message(ErrorMessages.missingTildeLabeledParameter(lblName)),
+          )
+          Asttypes.Optional(lblName)
+        | lbl => lbl
+        }
+
+        switch p.Parser.token {
+        | Question =>
+          Parser.next(p)
+          Some(
+            TermParameter({
+              uncurried: uncurried,
+              attrs: attrs,
+              label: lbl,
+              expr: None,
+              pat: pat,
+              pos: startPos,
+            }),
+          )
+        | _ =>
+          let expr = parseConstrainedOrCoercedExpr(p)
+          Some(
+            TermParameter({
+              uncurried: uncurried,
+              attrs: attrs,
+              label: lbl,
+              expr: Some(expr),
+              pat: pat,
+              pos: startPos,
+            }),
+          )
+        }
+      | _ =>
+        Some(
+          TermParameter({
+            uncurried: uncurried,
+            attrs: attrs,
+            label: lbl,
+            expr: None,
+            pat: pat,
+            pos: startPos,
+          }),
+        )
+      }
+    }
+  } else {
+    None
+  }
+
+and parseParameterList = p => {
+  let parameters = parseCommaDelimitedRegion(
+    ~grammar=Grammar.ParameterList,
+    ~f=parseParameter,
+    ~closing=Rparen,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  parameters
+}
+
+/* parameters ::=
+ *   | _
+ *   | lident
+ *   | ()
+ *   | (.)
+ *   | ( parameter {, parameter} [,] )
+ */
+and parseParameters = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Lident(ident) =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.Parser.prevEndPos)
+    list{
+      TermParameter({
+        uncurried: false,
+        attrs: list{},
+        label: Asttypes.Nolabel,
+        expr: None,
+        pat: Ast_helper.Pat.var(~loc, Location.mkloc(ident, loc)),
+        pos: startPos,
+      }),
+    }
+  | Underscore =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.Parser.prevEndPos)
+    list{
+      TermParameter({
+        uncurried: false,
+        attrs: list{},
+        label: Asttypes.Nolabel,
+        expr: None,
+        pat: Ast_helper.Pat.any(~loc, ()),
+        pos: startPos,
+      }),
+    }
+  | Lparen =>
+    Parser.next(p)
+    switch p.Parser.token {
+    | Rparen =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.Parser.prevEndPos)
+      let unitPattern = Ast_helper.Pat.construct(
+        ~loc,
+        Location.mkloc(Longident.Lident("()"), loc),
+        None,
+      )
+
+      list{
+        TermParameter({
+          uncurried: false,
+          attrs: list{},
+          label: Asttypes.Nolabel,
+          expr: None,
+          pat: unitPattern,
+          pos: startPos,
+        }),
+      }
+    | Dot =>
+      Parser.next(p)
+      switch p.token {
+      | Rparen =>
+        Parser.next(p)
+        let loc = mkLoc(startPos, p.Parser.prevEndPos)
+        let unitPattern = Ast_helper.Pat.construct(
+          ~loc,
+          Location.mkloc(Longident.Lident("()"), loc),
+          None,
+        )
+
+        list{
+          TermParameter({
+            uncurried: true,
+            attrs: list{},
+            label: Asttypes.Nolabel,
+            expr: None,
+            pat: unitPattern,
+            pos: startPos,
+          }),
+        }
+      | _ =>
+        switch parseParameterList(p) {
+        | list{
+            TermParameter({attrs, label: lbl, expr: defaultExpr, pat: pattern, pos: startPos}),
+            ...rest,
+          } => list{
+            TermParameter({
+              uncurried: true,
+              attrs: attrs,
+              label: lbl,
+              expr: defaultExpr,
+              pat: pattern,
+              pos: startPos,
+            }),
+            ...rest,
+          }
+        | parameters => parameters
+        }
+      }
+    | _ => parseParameterList(p)
+    }
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    list{}
+  }
+}
+
+and parseCoercedExpr = (~expr: Parsetree.expression, p) => {
+  Parser.expect(ColonGreaterThan, p)
+  let typ = parseTypExpr(p)
+  let loc = mkLoc(expr.pexp_loc.loc_start, p.prevEndPos)
+  Ast_helper.Exp.coerce(~loc, expr, None, typ)
+}
+
+and parseConstrainedOrCoercedExpr = p => {
+  let expr = parseExpr(p)
+  switch p.Parser.token {
+  | ColonGreaterThan => parseCoercedExpr(~expr, p)
+  | Colon =>
+    Parser.next(p)
+    switch p.token {
+    | _ =>
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(expr.pexp_loc.loc_start, typ.ptyp_loc.loc_end)
+      let expr = Ast_helper.Exp.constraint_(~loc, expr, typ)
+      switch p.token {
+      | ColonGreaterThan => parseCoercedExpr(~expr, p)
+      | _ => expr
+      }
+    }
+  | _ => expr
+  }
+}
+
+and parseConstrainedExprRegion = p =>
+  switch p.Parser.token {
+  | token if Grammar.isExprStart(token) =>
+    let expr = parseExpr(p)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(expr.pexp_loc.loc_start, typ.ptyp_loc.loc_end)
+      Some(Ast_helper.Exp.constraint_(~loc, expr, typ))
+    | _ => Some(expr)
+    }
+  | _ => None
+  }
+
+/* Atomic expressions represent unambiguous expressions.
+ * This means that regardless of the context, these expressions
+ * are always interpreted correctly. */
+and parseAtomicExpr = p => {
+  Parser.leaveBreadcrumb(p, Grammar.ExprOperand)
+  let startPos = p.Parser.startPos
+  let expr = switch p.Parser.token {
+  | (True | False) as token =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.construct(
+      ~loc,
+      Location.mkloc(Longident.Lident(Token.toString(token)), loc),
+      None,
+    )
+  | Int(_) | String(_) | Float(_) | Codepoint(_) =>
+    let c = parseConstant(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.constant(~loc, c)
+  | Backtick =>
+    let expr = parseTemplateExpr(p)
+    {...expr, pexp_loc: mkLoc(startPos, p.prevEndPos)}
+  | Uident(_) | Lident(_) => parseValueOrConstructor(p)
+  | Hash => parsePolyVariantExpr(p)
+  | Lparen =>
+    Parser.next(p)
+    switch p.Parser.token {
+    | Rparen =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None)
+    | _t =>
+      let expr = parseConstrainedOrCoercedExpr(p)
+      switch p.token {
+      | Comma =>
+        Parser.next(p)
+        parseTupleExpr(~startPos, ~first=expr, p)
+      | _ =>
+        Parser.expect(Rparen, p)
+        expr
+      /* {expr with pexp_loc = mkLoc startPos p.prevEndPos}
+       * What does this location mean here? It means that when there's
+       * a parenthesized we keep the location here for whitespace interleaving.
+       * Without the closing paren in the location there will always be an extra
+       * line. For now we don't include it, because it does weird things
+       * with for comments. */
+      }
+    }
+  | List =>
+    Parser.next(p)
+    parseListExpr(~startPos, p)
+  | Module =>
+    Parser.next(p)
+    parseFirstClassModuleExpr(~startPos, p)
+  | Lbracket => parseArrayExp(p)
+  | Lbrace => parseBracedOrRecordExpr(p)
+  | LessThan => parseJsx(p)
+  | Percent =>
+    let extension = parseExtension(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.extension(~loc, extension)
+  | Underscore as token =>
+    /* This case is for error recovery. Not sure if it's the correct place */
+    Parser.err(p, Diagnostics.lident(token))
+    Parser.next(p)
+    Recover.defaultExpr()
+  | token =>
+    let errPos = p.prevEndPos
+    Parser.err(~startPos=errPos, p, Diagnostics.unexpected(token, p.breadcrumbs))
+    switch skipTokensAndMaybeRetry(p, ~isStartOfGrammar=Grammar.isAtomicExprStart) {
+    | None => Recover.defaultExpr()
+    | Some() => parseAtomicExpr(p)
+    }
+  }
+
+  Parser.eatBreadcrumb(p)
+  expr
+}
+
+/* module(module-expr)
+ * module(module-expr : package-type) */
+and parseFirstClassModuleExpr = (~startPos, p) => {
+  Parser.expect(Lparen, p)
+
+  let modExpr = parseModuleExpr(p)
+  let modEndLoc = p.prevEndPos
+  switch p.Parser.token {
+  | Colon =>
+    let colonStart = p.Parser.startPos
+    Parser.next(p)
+    let attrs = parseAttributes(p)
+    let packageType = parsePackageType(~startPos=colonStart, ~attrs, p)
+    Parser.expect(Rparen, p)
+    let loc = mkLoc(startPos, modEndLoc)
+    let firstClassModule = Ast_helper.Exp.pack(~loc, modExpr)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.constraint_(~loc, firstClassModule, packageType)
+  | _ =>
+    Parser.expect(Rparen, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.pack(~loc, modExpr)
+  }
+}
+
+and parseBracketAccess = (p, expr, startPos) => {
+  Parser.leaveBreadcrumb(p, Grammar.ExprArrayAccess)
+  let lbracket = p.startPos
+  Parser.next(p)
+  let stringStart = p.startPos
+  switch p.Parser.token {
+  | String(s) =>
+    let s = if p.mode == ParseForTypeChecker {
+      parseStringLiteral(s)
+    } else {
+      s
+    }
+    Parser.next(p)
+    let stringEnd = p.prevEndPos
+    Parser.expect(Rbracket, p)
+    Parser.eatBreadcrumb(p)
+    let rbracket = p.prevEndPos
+    let e = {
+      let identLoc = mkLoc(stringStart, stringEnd)
+      let loc = mkLoc(startPos, rbracket)
+      Ast_helper.Exp.send(~loc, expr, Location.mkloc(s, identLoc))
+    }
+
+    let e = parsePrimaryExpr(~operand=e, p)
+    let equalStart = p.startPos
+    switch p.token {
+    | Equal =>
+      Parser.next(p)
+      let equalEnd = p.prevEndPos
+      let rhsExpr = parseExpr(p)
+      let loc = mkLoc(startPos, rhsExpr.pexp_loc.loc_end)
+      let operatorLoc = mkLoc(equalStart, equalEnd)
+      Ast_helper.Exp.apply(
+        ~loc,
+        Ast_helper.Exp.ident(~loc=operatorLoc, Location.mkloc(Longident.Lident("#="), operatorLoc)),
+        list{(Nolabel, e), (Nolabel, rhsExpr)},
+      )
+    | _ => e
+    }
+  | _ =>
+    let accessExpr = parseConstrainedOrCoercedExpr(p)
+    Parser.expect(Rbracket, p)
+    Parser.eatBreadcrumb(p)
+    let rbracket = p.prevEndPos
+    let arrayLoc = mkLoc(lbracket, rbracket)
+    switch p.token {
+    | Equal =>
+      Parser.leaveBreadcrumb(p, ExprArrayMutation)
+      Parser.next(p)
+      let rhsExpr = parseExpr(p)
+      let arraySet = Location.mkloc(Longident.Ldot(Lident("Array"), "set"), arrayLoc)
+
+      let endPos = p.prevEndPos
+      let arraySet = Ast_helper.Exp.apply(
+        ~loc=mkLoc(startPos, endPos),
+        Ast_helper.Exp.ident(~loc=arrayLoc, arraySet),
+        list{(Nolabel, expr), (Nolabel, accessExpr), (Nolabel, rhsExpr)},
+      )
+
+      Parser.eatBreadcrumb(p)
+      arraySet
+    | _ =>
+      let endPos = p.prevEndPos
+      let e = Ast_helper.Exp.apply(
+        ~loc=mkLoc(startPos, endPos),
+        Ast_helper.Exp.ident(
+          ~loc=arrayLoc,
+          Location.mkloc(Longident.Ldot(Lident("Array"), "get"), arrayLoc),
+        ),
+        list{(Nolabel, expr), (Nolabel, accessExpr)},
+      )
+
+      parsePrimaryExpr(~operand=e, p)
+    }
+  }
+}
+
+/* * A primary expression represents
+ *  - atomic-expr
+ *  - john.age
+ *  - array[0]
+ *  - applyFunctionTo(arg1, arg2)
+ *
+ *  The "operand" represents the expression that is operated on
+ */
+and parsePrimaryExpr = (~operand, ~noCall=false, p) => {
+  let startPos = operand.pexp_loc.loc_start
+  let rec loop = (p, expr) =>
+    switch p.Parser.token {
+    | Dot =>
+      Parser.next(p)
+      let lident = parseValuePathAfterDot(p)
+      switch p.Parser.token {
+      | Equal if noCall == false =>
+        Parser.leaveBreadcrumb(p, Grammar.ExprSetField)
+        Parser.next(p)
+        let targetExpr = parseExpr(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let setfield = Ast_helper.Exp.setfield(~loc, expr, lident, targetExpr)
+        Parser.eatBreadcrumb(p)
+        setfield
+      | _ =>
+        let endPos = p.prevEndPos
+        let loc = mkLoc(startPos, endPos)
+        loop(p, Ast_helper.Exp.field(~loc, expr, lident))
+      }
+    | Lbracket if noCall == false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+      parseBracketAccess(p, expr, startPos)
+    | Lparen if noCall == false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+      loop(p, parseCallExpr(p, expr))
+    | Backtick if noCall == false && p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+      switch expr.pexp_desc {
+      | Pexp_ident({txt: Longident.Lident(ident)}) => parseTemplateExpr(~prefix=ident, p)
+      | _ =>
+        Parser.err(
+          ~startPos=expr.pexp_loc.loc_start,
+          ~endPos=expr.pexp_loc.loc_end,
+          p,
+          Diagnostics.message(
+            "Tagged template literals are currently restricted to names like: json`null`.",
+          ),
+        )
+        parseTemplateExpr(p)
+      }
+    | _ => expr
+    }
+
+  loop(p, operand)
+}
+
+/* a unary expression is an expression with only one operand and
+ * unary operator. Examples:
+ *   -1
+ *   !condition
+ *   -. 1.6
+ */
+and parseUnaryExpr = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | (Minus | MinusDot | Plus | PlusDot | Bang) as token =>
+    Parser.leaveBreadcrumb(p, Grammar.ExprUnary)
+    let tokenEnd = p.endPos
+    Parser.next(p)
+    let operand = parseUnaryExpr(p)
+    let unaryExpr = makeUnaryExpr(startPos, tokenEnd, token, operand)
+    Parser.eatBreadcrumb(p)
+    unaryExpr
+  | _ => parsePrimaryExpr(~operand=parseAtomicExpr(p), p)
+  }
+}
+
+/* Represents an "operand" in a binary expression.
+ * If you have `a + b`, `a` and `b` both represent
+ * the operands of the binary expression with opeartor `+` */
+and parseOperandExpr = (~context, p) => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  let expr = switch p.Parser.token {
+  | Assert =>
+    Parser.next(p)
+    let expr = parseUnaryExpr(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.assert_(~loc, expr)
+  | Lazy =>
+    Parser.next(p)
+    let expr = parseUnaryExpr(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.lazy_(~loc, expr)
+  | Try => parseTryExpression(p)
+  | If => parseIfOrIfLetExpression(p)
+  | For => parseForExpression(p)
+  | While => parseWhileExpression(p)
+  | Switch => parseSwitchExpression(p)
+  | _ =>
+    if (
+      context !== WhenExpr && isEs6ArrowExpression(~inTernary=context == TernaryTrueBranchExpr, p)
+    ) {
+      parseEs6ArrowExpression(~context, p)
+    } else {
+      parseUnaryExpr(p)
+    }
+  }
+
+  /* let endPos = p.Parser.prevEndPos in */
+  {
+    ...expr,
+    pexp_attributes: List.concat(list{expr.Parsetree.pexp_attributes, attrs}),
+    /* pexp_loc = mkLoc startPos endPos */
+  }
+}
+
+/* a binary expression is an expression that combines two expressions with an
+ * operator. Examples:
+ *    a + b
+ *    f(x) |> g(y)
+ */
+and parseBinaryExpr = (~context=OrdinaryExpr, ~a=?, p, prec) => {
+  let a = switch a {
+  | Some(e) => e
+  | None => parseOperandExpr(~context, p)
+  }
+
+  let rec loop = a => {
+    let token = p.Parser.token
+    let tokenPrec = switch token {
+    /* Can the minus be interpreted as a binary operator? Or is it a unary?
+     * let w = {
+     *   x
+     *   -10
+     * }
+     * vs
+     * let w = {
+     *   width
+     *   - gap
+     * }
+     *
+     * First case is unary, second is a binary operator.
+     * See Scanner.isBinaryOp */
+    | Minus | MinusDot | LessThan
+      if !Scanner.isBinaryOp(p.scanner.src, p.startPos.pos_cnum, p.endPos.pos_cnum) &&
+      p.startPos.pos_lnum > p.prevEndPos.pos_lnum => -1
+    | token => Token.precedence(token)
+    }
+
+    if tokenPrec < prec {
+      a
+    } else {
+      Parser.leaveBreadcrumb(p, Grammar.ExprBinaryAfterOp(token))
+      let startPos = p.startPos
+      Parser.next(p)
+      let endPos = p.prevEndPos
+      let b = parseBinaryExpr(~context, p, tokenPrec + 1)
+      let loc = mkLoc(a.Parsetree.pexp_loc.loc_start, b.pexp_loc.loc_end)
+      let expr = Ast_helper.Exp.apply(
+        ~loc,
+        makeInfixOperator(p, token, startPos, endPos),
+        list{(Nolabel, a), (Nolabel, b)},
+      )
+
+      Parser.eatBreadcrumb(p)
+      loop(expr)
+    }
+  }
+
+  loop(a)
+}
+
+/* If we even need this, determines if < might be the start of jsx. Not 100% complete */
+/* and isStartOfJsx p = */
+/* Parser.lookahead p (fun p -> */
+/* match p.Parser.token with */
+/* | LessThan -> */
+/* Parser.next p; */
+/* begin match p.token with */
+/* | GreaterThan (* <> *) -> true */
+/* | Lident _ | Uident _ | List -> */
+/* ignore (parseJsxName p); */
+/* begin match p.token with */
+/* | GreaterThan (* <div> *) -> true */
+/* | Question (*<Component ? *) -> true */
+/* | Lident _ | List -> */
+/* Parser.next p; */
+/* begin match p.token with */
+/* | Equal (* <Component handleClick= *) -> true */
+/* | _ -> false (* TODO *) */
+/* end */
+/* | Forwardslash (* <Component / *)-> */
+/* Parser.next p; */
+/* begin match p.token with */
+/* | GreaterThan (* <Component /> *) -> true */
+/* | _ -> false */
+/* end */
+/* | _ -> */
+/* false */
+/* end */
+/* | _ -> false */
+/* end */
+/* | _ -> false */
+/* ) */
+
+and parseTemplateExpr = (~prefix="js", p) => {
+  let hiddenOperator = {
+    let op = Location.mknoloc(Longident.Lident("^"))
+    Ast_helper.Exp.ident(op)
+  }
+
+  let rec parseParts = acc => {
+    let startPos = p.Parser.startPos
+    Parser.nextTemplateLiteralToken(p)
+    switch p.token {
+    | TemplateTail(txt) =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let txt = if p.mode == ParseForTypeChecker {
+        parseTemplateStringLiteral(txt)
+      } else {
+        txt
+      }
+      let str = Ast_helper.Exp.constant(
+        ~attrs=list{templateLiteralAttr},
+        ~loc,
+        Pconst_string(txt, Some(prefix)),
+      )
+      Ast_helper.Exp.apply(
+        ~attrs=list{templateLiteralAttr},
+        ~loc,
+        hiddenOperator,
+        list{(Nolabel, acc), (Nolabel, str)},
+      )
+    | TemplatePart(txt) =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let expr = parseExprBlock(p)
+      let fullLoc = mkLoc(startPos, p.prevEndPos)
+      let txt = if p.mode == ParseForTypeChecker {
+        parseTemplateStringLiteral(txt)
+      } else {
+        txt
+      }
+      let str = Ast_helper.Exp.constant(
+        ~attrs=list{templateLiteralAttr},
+        ~loc,
+        Pconst_string(txt, Some(prefix)),
+      )
+      let next = {
+        let a = Ast_helper.Exp.apply(
+          ~attrs=list{templateLiteralAttr},
+          ~loc=fullLoc,
+          hiddenOperator,
+          list{(Nolabel, acc), (Nolabel, str)},
+        )
+        Ast_helper.Exp.apply(~loc=fullLoc, hiddenOperator, list{(Nolabel, a), (Nolabel, expr)})
+      }
+
+      parseParts(next)
+    | token =>
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      Ast_helper.Exp.constant(Pconst_string("", None))
+    }
+  }
+
+  let startPos = p.startPos
+  Parser.nextTemplateLiteralToken(p)
+  switch p.token {
+  | TemplateTail(txt) =>
+    Parser.next(p)
+    let txt = if p.mode == ParseForTypeChecker {
+      parseTemplateStringLiteral(txt)
+    } else {
+      txt
+    }
+    Ast_helper.Exp.constant(
+      ~attrs=list{templateLiteralAttr},
+      ~loc=mkLoc(startPos, p.prevEndPos),
+      Pconst_string(txt, Some(prefix)),
+    )
+  | TemplatePart(txt) =>
+    Parser.next(p)
+    let constantLoc = mkLoc(startPos, p.prevEndPos)
+    let expr = parseExprBlock(p)
+    let fullLoc = mkLoc(startPos, p.prevEndPos)
+    let txt = if p.mode == ParseForTypeChecker {
+      parseTemplateStringLiteral(txt)
+    } else {
+      txt
+    }
+    let str = Ast_helper.Exp.constant(
+      ~attrs=list{templateLiteralAttr},
+      ~loc=constantLoc,
+      Pconst_string(txt, Some(prefix)),
+    )
+    let next = Ast_helper.Exp.apply(
+      ~attrs=list{templateLiteralAttr},
+      ~loc=fullLoc,
+      hiddenOperator,
+      list{(Nolabel, str), (Nolabel, expr)},
+    )
+
+    parseParts(next)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Ast_helper.Exp.constant(Pconst_string("", None))
+  }
+}
+
+/* Overparse: let f = a : int => a + 1, is it (a : int) => or (a): int =>
+ * Also overparse constraints:
+ *  let x = {
+ *    let a = 1
+ *    a + pi: int
+ *  }
+ *
+ *  We want to give a nice error message in these cases
+ * */
+and overParseConstrainedOrCoercedOrArrowExpression = (p, expr) =>
+  switch p.Parser.token {
+  | ColonGreaterThan => parseCoercedExpr(~expr, p)
+  | Colon =>
+    Parser.next(p)
+    let typ = parseTypExpr(~es6Arrow=false, p)
+    switch p.Parser.token {
+    | EqualGreater =>
+      Parser.next(p)
+      let body = parseExpr(p)
+      let pat = switch expr.pexp_desc {
+      | Pexp_ident(longident) =>
+        Ast_helper.Pat.var(
+          ~loc=expr.pexp_loc,
+          Location.mkloc(Longident.flatten(longident.txt) |> String.concat("."), longident.loc),
+        )
+      /* TODO: can we convert more expressions to patterns? */
+      | _ => Ast_helper.Pat.var(~loc=expr.pexp_loc, Location.mkloc("pattern", expr.pexp_loc))
+      }
+
+      let arrow1 = Ast_helper.Exp.fun_(
+        ~loc=mkLoc(expr.pexp_loc.loc_start, body.pexp_loc.loc_end),
+        Asttypes.Nolabel,
+        None,
+        pat,
+        Ast_helper.Exp.constraint_(body, typ),
+      )
+
+      let arrow2 = Ast_helper.Exp.fun_(
+        ~loc=mkLoc(expr.pexp_loc.loc_start, body.pexp_loc.loc_end),
+        Asttypes.Nolabel,
+        None,
+        Ast_helper.Pat.constraint_(pat, typ),
+        body,
+      )
+
+      let msg =
+        Doc.breakableGroup(
+          ~forceBreak=true,
+          Doc.concat(list{
+            Doc.text("Did you mean to annotate the parameter type or the return type?"),
+            Doc.indent(
+              Doc.concat(list{
+                Doc.line,
+                Doc.text("1) "),
+                ResPrinter.printExpression(arrow1, CommentTable.empty),
+                Doc.line,
+                Doc.text("2) "),
+                ResPrinter.printExpression(arrow2, CommentTable.empty),
+              }),
+            ),
+          }),
+        ) |> Doc.toString(~width=80)
+
+      Parser.err(
+        ~startPos=expr.pexp_loc.loc_start,
+        ~endPos=body.pexp_loc.loc_end,
+        p,
+        Diagnostics.message(msg),
+      )
+      arrow1
+    | _ =>
+      let loc = mkLoc(expr.pexp_loc.loc_start, typ.ptyp_loc.loc_end)
+      let expr = Ast_helper.Exp.constraint_(~loc, expr, typ)
+      let () = Parser.err(
+        ~startPos=expr.pexp_loc.loc_start,
+        ~endPos=typ.ptyp_loc.loc_end,
+        p,
+        Diagnostics.message(
+          Doc.breakableGroup(
+            ~forceBreak=true,
+            Doc.concat(list{
+              Doc.text("Expressions with type constraints need to be wrapped in parens:"),
+              Doc.indent(
+                Doc.concat(list{
+                  Doc.line,
+                  ResPrinter.addParens(ResPrinter.printExpression(expr, CommentTable.empty)),
+                }),
+              ),
+            }),
+          ) |> Doc.toString(~width=80),
+        ),
+      )
+
+      expr
+    }
+  | _ => expr
+  }
+
+and parseLetBindingBody = (~startPos, ~attrs, p) => {
+  Parser.beginRegion(p)
+  Parser.leaveBreadcrumb(p, Grammar.LetBinding)
+  let (pat, exp) = {
+    Parser.leaveBreadcrumb(p, Grammar.Pattern)
+    let pat = parsePattern(p)
+    Parser.eatBreadcrumb(p)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      switch p.token {
+      | Typ =>
+        /* locally abstract types */
+        Parser.next(p)
+        let newtypes = parseLidentList(p)
+        Parser.expect(Dot, p)
+        let typ = parseTypExpr(p)
+        Parser.expect(Equal, p)
+        let expr = parseExpr(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let (exp, poly) = wrapTypeAnnotation(~loc, newtypes, typ, expr)
+        let pat = Ast_helper.Pat.constraint_(~loc, pat, poly)
+        (pat, exp)
+      | _ =>
+        let polyType = parsePolyTypeExpr(p)
+        let loc = {...pat.ppat_loc, loc_end: polyType.Parsetree.ptyp_loc.loc_end}
+        let pat = Ast_helper.Pat.constraint_(~loc, pat, polyType)
+        Parser.expect(Token.Equal, p)
+        let exp = parseExpr(p)
+        let exp = overParseConstrainedOrCoercedOrArrowExpression(p, exp)
+        (pat, exp)
+      }
+    | _ =>
+      Parser.expect(Token.Equal, p)
+      let exp = overParseConstrainedOrCoercedOrArrowExpression(p, parseExpr(p))
+      (pat, exp)
+    }
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  let vb = Ast_helper.Vb.mk(~loc, ~attrs, pat, exp)
+  Parser.eatBreadcrumb(p)
+  Parser.endRegion(p)
+  vb
+}
+
+/* TODO: find a better way? Is it possible?
+ * let a = 1
+ * @attr
+ * and b = 2
+ *
+ * The problem is that without semi we need a lookahead to determine
+ * if the attr is on the letbinding or the start of a new thing
+ *
+ * let a = 1
+ * @attr
+ * let b = 1
+ *
+ * Here @attr should attach to something "new": `let b = 1`
+ * The parser state is forked, which is quite expensive…
+ */
+and parseAttributesAndBinding = (p: Parser.t) => {
+  let err = p.scanner.err
+  let ch = p.scanner.ch
+  let offset = p.scanner.offset
+  let lineOffset = p.scanner.lineOffset
+  let lnum = p.scanner.lnum
+  let mode = p.scanner.mode
+  let token = p.token
+  let startPos = p.startPos
+  let endPos = p.endPos
+  let prevEndPos = p.prevEndPos
+  let breadcrumbs = p.breadcrumbs
+  let errors = p.errors
+  let diagnostics = p.diagnostics
+  let comments = p.comments
+
+  switch p.Parser.token {
+  | At =>
+    let attrs = parseAttributes(p)
+    switch p.Parser.token {
+    | And => attrs
+    | _ =>
+      p.scanner.err = err
+      p.scanner.ch = ch
+      p.scanner.offset = offset
+      p.scanner.lineOffset = lineOffset
+      p.scanner.lnum = lnum
+      p.scanner.mode = mode
+      p.token = token
+      p.startPos = startPos
+      p.endPos = endPos
+      p.prevEndPos = prevEndPos
+      p.breadcrumbs = breadcrumbs
+      p.errors = errors
+      p.diagnostics = diagnostics
+      p.comments = comments
+      list{}
+    }
+  | _ => list{}
+  }
+}
+
+/* definition	::=	let [rec] let-binding  { and let-binding } */
+and parseLetBindings = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.optional(p, Let) |> ignore
+  let recFlag = if Parser.optional(p, Token.Rec) {
+    Asttypes.Recursive
+  } else {
+    Asttypes.Nonrecursive
+  }
+
+  let first = parseLetBindingBody(~startPos, ~attrs, p)
+
+  let rec loop = (p, bindings) => {
+    let startPos = p.Parser.startPos
+    let attrs = parseAttributesAndBinding(p)
+    switch p.Parser.token {
+    | And =>
+      Parser.next(p)
+      let attrs = switch p.token {
+      | Export =>
+        let exportLoc = mkLoc(p.startPos, p.endPos)
+        Parser.next(p)
+        let genTypeAttr = (Location.mkloc("genType", exportLoc), Parsetree.PStr(list{}))
+        list{genTypeAttr, ...attrs}
+      | _ => attrs
+      }
+
+      ignore(Parser.optional(p, Let)) /* overparse for fault tolerance */
+      let letBinding = parseLetBindingBody(~startPos, ~attrs, p)
+      loop(p, list{letBinding, ...bindings})
+    | _ => List.rev(bindings)
+    }
+  }
+
+  (recFlag, loop(p, list{first}))
+}
+
+/*
+ * div -> div
+ * Foo -> Foo.createElement
+ * Foo.Bar -> Foo.Bar.createElement
+ */
+and parseJsxName = p => {
+  let longident = switch p.Parser.token {
+  | Lident(ident) =>
+    let identStart = p.startPos
+    let identEnd = p.endPos
+    Parser.next(p)
+    let loc = mkLoc(identStart, identEnd)
+    Location.mkloc(Longident.Lident(ident), loc)
+  | Uident(_) =>
+    let longident = parseModuleLongIdent(~lowercase=true, p)
+    Location.mkloc(Longident.Ldot(longident.txt, "createElement"), longident.loc)
+  | _ =>
+    let msg = "A jsx name must be a lowercase or uppercase name, like: div in <div /> or Navbar in <Navbar />"
+
+    Parser.err(p, Diagnostics.message(msg))
+    Location.mknoloc(Longident.Lident("_"))
+  }
+
+  Ast_helper.Exp.ident(~loc=longident.loc, longident)
+}
+
+and parseJsxOpeningOrSelfClosingElement = (~startPos, p) => {
+  let jsxStartPos = p.Parser.startPos
+  let name = parseJsxName(p)
+  let jsxProps = parseJsxProps(p)
+  let children = switch p.Parser.token {
+  | Forwardslash =>
+    /* <foo a=b /> */
+    let childrenStartPos = p.Parser.startPos
+    Parser.next(p)
+    let childrenEndPos = p.Parser.startPos
+    Parser.expect(GreaterThan, p)
+    let loc = mkLoc(childrenStartPos, childrenEndPos)
+    makeListExpression(loc, list{}, None) /* no children */
+  | GreaterThan =>
+    /* <foo a=b> bar </foo> */
+    let childrenStartPos = p.Parser.startPos
+    Scanner.setJsxMode(p.scanner)
+    Parser.next(p)
+    let (spread, children) = parseJsxChildren(p)
+    let childrenEndPos = p.Parser.startPos
+    let () = switch p.token {
+    | LessThanSlash => Parser.next(p)
+    | LessThan =>
+      Parser.next(p)
+      Parser.expect(Forwardslash, p)
+    | token if Grammar.isStructureItemStart(token) => ()
+    | _ => Parser.expect(LessThanSlash, p)
+    }
+
+    switch p.Parser.token {
+    | Lident(_) | Uident(_) if verifyJsxOpeningClosingName(p, name) =>
+      Parser.expect(GreaterThan, p)
+      let loc = mkLoc(childrenStartPos, childrenEndPos)
+      switch (spread, children) {
+      | (true, list{child, ..._}) => child
+      | _ => makeListExpression(loc, children, None)
+      }
+    | token =>
+      let () = if Grammar.isStructureItemStart(token) {
+        let closing = "</" ++ (string_of_pexp_ident(name) ++ ">")
+        let msg = Diagnostics.message("Missing " ++ closing)
+        Parser.err(~startPos, ~endPos=p.prevEndPos, p, msg)
+      } else {
+        let opening = "</" ++ (string_of_pexp_ident(name) ++ ">")
+        let msg =
+          "Closing jsx name should be the same as the opening name. Did you mean " ++
+          (opening ++
+          " ?")
+        Parser.err(~startPos, ~endPos=p.prevEndPos, p, Diagnostics.message(msg))
+        Parser.expect(GreaterThan, p)
+      }
+
+      let loc = mkLoc(childrenStartPos, childrenEndPos)
+      switch (spread, children) {
+      | (true, list{child, ..._}) => child
+      | _ => makeListExpression(loc, children, None)
+      }
+    }
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    makeListExpression(Location.none, list{}, None)
+  }
+
+  let jsxEndPos = p.prevEndPos
+  let loc = mkLoc(jsxStartPos, jsxEndPos)
+  Ast_helper.Exp.apply(
+    ~loc,
+    name,
+    List.concat(list{
+      jsxProps,
+      list{
+        (Asttypes.Labelled("children"), children),
+        (
+          Asttypes.Nolabel,
+          Ast_helper.Exp.construct(Location.mknoloc(Longident.Lident("()")), None),
+        ),
+      },
+    }),
+  )
+}
+
+/*
+ *  jsx ::=
+ *    | <> jsx-children </>
+ *    | <element-name {jsx-prop} />
+ *    | <element-name {jsx-prop}> jsx-children </element-name>
+ *
+ *  jsx-children ::= primary-expr*          * => 0 or more
+ */
+and parseJsx = p => {
+  Parser.leaveBreadcrumb(p, Grammar.Jsx)
+  let startPos = p.Parser.startPos
+  Parser.expect(LessThan, p)
+  let jsxExpr = switch p.Parser.token {
+  | Lident(_) | Uident(_) => parseJsxOpeningOrSelfClosingElement(~startPos, p)
+  | GreaterThan =>
+    /* fragment: <> foo </> */
+    parseJsxFragment(p)
+  | _ => parseJsxName(p)
+  }
+
+  Parser.eatBreadcrumb(p)
+  {...jsxExpr, pexp_attributes: list{jsxAttr}}
+}
+
+/*
+ * jsx-fragment ::=
+ *  | <> </>
+ *  | <> jsx-children </>
+ */
+and parseJsxFragment = p => {
+  let childrenStartPos = p.Parser.startPos
+  Scanner.setJsxMode(p.scanner)
+  Parser.expect(GreaterThan, p)
+  let (_spread, children) = parseJsxChildren(p)
+  let childrenEndPos = p.Parser.startPos
+  Parser.expect(LessThanSlash, p)
+  Parser.expect(GreaterThan, p)
+  let loc = mkLoc(childrenStartPos, childrenEndPos)
+  makeListExpression(loc, children, None)
+}
+
+/*
+ * jsx-prop ::=
+ *   |  lident
+ *   | ?lident
+ *   |  lident =  jsx_expr
+ *   |  lident = ?jsx_expr
+ */
+and parseJsxProp = p =>
+  switch p.Parser.token {
+  | Question | Lident(_) =>
+    let optional = Parser.optional(p, Question)
+    let (name, loc) = parseLident(p)
+    let propLocAttr = (Location.mkloc("ns.namedArgLoc", loc), Parsetree.PStr(list{}))
+    /* optional punning: <foo ?a /> */
+    if optional {
+      Some(
+        Asttypes.Optional(name),
+        Ast_helper.Exp.ident(
+          ~attrs=list{propLocAttr},
+          ~loc,
+          Location.mkloc(Longident.Lident(name), loc),
+        ),
+      )
+    } else {
+      switch p.Parser.token {
+      | Equal =>
+        Parser.next(p)
+        /* no punning */
+        let optional = Parser.optional(p, Question)
+        let attrExpr = {
+          let e = parsePrimaryExpr(~operand=parseAtomicExpr(p), p)
+          {...e, pexp_attributes: list{propLocAttr, ...e.pexp_attributes}}
+        }
+
+        let label = if optional {
+          Asttypes.Optional(name)
+        } else {
+          Asttypes.Labelled(name)
+        }
+
+        Some(label, attrExpr)
+      | _ =>
+        let attrExpr = Ast_helper.Exp.ident(
+          ~loc,
+          ~attrs=list{propLocAttr},
+          Location.mkloc(Longident.Lident(name), loc),
+        )
+        let label = if optional {
+          Asttypes.Optional(name)
+        } else {
+          Asttypes.Labelled(name)
+        }
+
+        Some(label, attrExpr)
+      }
+    }
+  | _ => None
+  }
+
+and parseJsxProps = p => parseRegion(~grammar=Grammar.JsxAttribute, ~f=parseJsxProp, p)
+
+and parseJsxChildren = p => {
+  let rec loop = (p, children) =>
+    switch p.Parser.token {
+    | Token.Eof | LessThanSlash =>
+      Scanner.popMode(p.scanner, Jsx)
+      List.rev(children)
+    | LessThan =>
+      /* Imagine: <div> <Navbar /> <
+       * is `<` the start of a jsx-child? <div …
+       * or is it the start of a closing tag?  </div>
+       * reconsiderLessThan peeks at the next token and
+       * determines the correct token to disambiguate */
+      let token = Scanner.reconsiderLessThan(p.scanner)
+      if token == LessThan {
+        let child = parsePrimaryExpr(~operand=parseAtomicExpr(p), ~noCall=true, p)
+        loop(p, list{child, ...children})
+      } else {
+        /* LessThanSlash */
+        let () = p.token = token
+        let () = Scanner.popMode(p.scanner, Jsx)
+        List.rev(children)
+      }
+    | token if Grammar.isJsxChildStart(token) =>
+      let () = Scanner.popMode(p.scanner, Jsx)
+      let child = parsePrimaryExpr(~operand=parseAtomicExpr(p), ~noCall=true, p)
+      loop(p, list{child, ...children})
+    | _ =>
+      Scanner.popMode(p.scanner, Jsx)
+      List.rev(children)
+    }
+
+  switch p.Parser.token {
+  | DotDotDot =>
+    Parser.next(p)
+    (true, list{parsePrimaryExpr(~operand=parseAtomicExpr(p), ~noCall=true, p)})
+  | _ => (false, loop(p, list{}))
+  }
+}
+
+and parseBracedOrRecordExpr = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lbrace, p)
+  switch p.Parser.token {
+  | Rbrace =>
+    Parser.err(p, Diagnostics.unexpected(Rbrace, p.breadcrumbs))
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let braces = makeBracesAttr(loc)
+    Ast_helper.Exp.construct(
+      ~attrs=list{braces},
+      ~loc,
+      Location.mkloc(Longident.Lident("()"), loc),
+      None,
+    )
+  | DotDotDot =>
+    /* beginning of record spread, parse record */
+    Parser.next(p)
+    let spreadExpr = parseConstrainedOrCoercedExpr(p)
+    Parser.expect(Comma, p)
+    let expr = parseRecordExpr(~startPos, ~spread=Some(spreadExpr), list{}, p)
+    Parser.expect(Rbrace, p)
+    expr
+  | String(s) =>
+    let s = if p.mode == ParseForTypeChecker {
+      parseStringLiteral(s)
+    } else {
+      s
+    }
+    let field = {
+      let loc = mkLoc(p.startPos, p.endPos)
+      Parser.next(p)
+      Location.mkloc(Longident.Lident(s), loc)
+    }
+
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let fieldExpr = parseExpr(p)
+      Parser.optional(p, Comma) |> ignore
+      let expr = parseRecordExprWithStringKeys(~startPos, (field, fieldExpr), p)
+      Parser.expect(Rbrace, p)
+      expr
+    | _ =>
+      let tag = if p.mode == ParseForTypeChecker {
+        Some("js")
+      } else {
+        None
+      }
+      let constant = Ast_helper.Exp.constant(~loc=field.loc, Parsetree.Pconst_string(s, tag))
+      let a = parsePrimaryExpr(~operand=constant, p)
+      let e = parseBinaryExpr(~a, p, 1)
+      let e = parseTernaryExpr(e, p)
+      switch p.Parser.token {
+      | Semicolon =>
+        let expr = parseExprBlock(~first=e, p)
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, Parsetree.pexp_attributes: list{braces, ...expr.Parsetree.pexp_attributes}}
+      | Rbrace =>
+        Parser.next(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...e, pexp_attributes: list{braces, ...e.pexp_attributes}}
+      | _ =>
+        let expr = parseExprBlock(~first=e, p)
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+      }
+    }
+  | Uident(_) | Lident(_) =>
+    let startToken = p.token
+    let valueOrConstructor = parseValueOrConstructor(p)
+    switch valueOrConstructor.pexp_desc {
+    | Pexp_ident(pathIdent) =>
+      let identEndPos = p.prevEndPos
+      switch p.Parser.token {
+      | Comma =>
+        Parser.next(p)
+        let valueOrConstructor = switch startToken {
+        | Uident(_) => removeModuleNameFromPunnedFieldValue(valueOrConstructor)
+        | _ => valueOrConstructor
+        }
+
+        let expr = parseRecordExpr(~startPos, list{(pathIdent, valueOrConstructor)}, p)
+        Parser.expect(Rbrace, p)
+        expr
+      | Colon =>
+        Parser.next(p)
+        let fieldExpr = parseExpr(p)
+        switch p.token {
+        | Rbrace =>
+          Parser.next(p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          Ast_helper.Exp.record(~loc, list{(pathIdent, fieldExpr)}, None)
+        | _ =>
+          Parser.expect(Comma, p)
+          let expr = parseRecordExpr(~startPos, list{(pathIdent, fieldExpr)}, p)
+          Parser.expect(Rbrace, p)
+          expr
+        }
+      /* error case */
+      | Lident(_) =>
+        if p.prevEndPos.pos_lnum < p.startPos.pos_lnum {
+          Parser.expect(Comma, p)
+          let expr = parseRecordExpr(~startPos, list{(pathIdent, valueOrConstructor)}, p)
+          Parser.expect(Rbrace, p)
+          expr
+        } else {
+          Parser.expect(Colon, p)
+          let expr = parseRecordExpr(~startPos, list{(pathIdent, valueOrConstructor)}, p)
+          Parser.expect(Rbrace, p)
+          expr
+        }
+      | Semicolon =>
+        let expr = parseExprBlock(~first=Ast_helper.Exp.ident(pathIdent), p)
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+      | Rbrace =>
+        Parser.next(p)
+        let expr = Ast_helper.Exp.ident(~loc=pathIdent.loc, pathIdent)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+      | EqualGreater =>
+        let loc = mkLoc(startPos, identEndPos)
+        let ident = Location.mkloc(Longident.last(pathIdent.txt), loc)
+        let a = parseEs6ArrowExpression(
+          ~parameters=list{
+            TermParameter({
+              uncurried: false,
+              attrs: list{},
+              label: Asttypes.Nolabel,
+              expr: None,
+              pat: Ast_helper.Pat.var(ident),
+              pos: startPos,
+            }),
+          },
+          p,
+        )
+
+        let e = parseBinaryExpr(~a, p, 1)
+        let e = parseTernaryExpr(e, p)
+        switch p.Parser.token {
+        | Semicolon =>
+          let expr = parseExprBlock(~first=e, p)
+          Parser.expect(Rbrace, p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+        | Rbrace =>
+          Parser.next(p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...e, pexp_attributes: list{braces, ...e.pexp_attributes}}
+        | _ =>
+          let expr = parseExprBlock(~first=e, p)
+          Parser.expect(Rbrace, p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+        }
+      | _ =>
+        Parser.leaveBreadcrumb(p, Grammar.ExprBlock)
+        let a = parsePrimaryExpr(~operand=Ast_helper.Exp.ident(~loc=pathIdent.loc, pathIdent), p)
+        let e = parseBinaryExpr(~a, p, 1)
+        let e = parseTernaryExpr(e, p)
+        Parser.eatBreadcrumb(p)
+        switch p.Parser.token {
+        | Semicolon =>
+          let expr = parseExprBlock(~first=e, p)
+          Parser.expect(Rbrace, p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+        | Rbrace =>
+          Parser.next(p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...e, pexp_attributes: list{braces, ...e.pexp_attributes}}
+        | _ =>
+          let expr = parseExprBlock(~first=e, p)
+          Parser.expect(Rbrace, p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let braces = makeBracesAttr(loc)
+          {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+        }
+      }
+    | _ =>
+      Parser.leaveBreadcrumb(p, Grammar.ExprBlock)
+      let a = parsePrimaryExpr(~operand=valueOrConstructor, p)
+      let e = parseBinaryExpr(~a, p, 1)
+      let e = parseTernaryExpr(e, p)
+      Parser.eatBreadcrumb(p)
+      switch p.Parser.token {
+      | Semicolon =>
+        let expr = parseExprBlock(~first=e, p)
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+      | Rbrace =>
+        Parser.next(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...e, pexp_attributes: list{braces, ...e.pexp_attributes}}
+      | _ =>
+        let expr = parseExprBlock(~first=e, p)
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let braces = makeBracesAttr(loc)
+        {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+      }
+    }
+  | _ =>
+    let expr = parseExprBlock(p)
+    Parser.expect(Rbrace, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let braces = makeBracesAttr(loc)
+    {...expr, pexp_attributes: list{braces, ...expr.pexp_attributes}}
+  }
+}
+
+and parseRecordRowWithStringKey = p =>
+  switch p.Parser.token {
+  | String(s) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    let field = Location.mkloc(Longident.Lident(s), loc)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let fieldExpr = parseExpr(p)
+      Some(field, fieldExpr)
+    | _ => Some(field, Ast_helper.Exp.ident(~loc=field.loc, field))
+    }
+  | _ => None
+  }
+
+and parseRecordRow = p => {
+  let () = switch p.Parser.token {
+  | Token.DotDotDot =>
+    Parser.err(p, Diagnostics.message(ErrorMessages.recordExprSpread))
+    Parser.next(p)
+  | _ => ()
+  }
+
+  switch p.Parser.token {
+  | Lident(_) | Uident(_) =>
+    let startToken = p.token
+    let field = parseValuePath(p)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let fieldExpr = parseExpr(p)
+      Some(field, fieldExpr)
+    | _ =>
+      let value = Ast_helper.Exp.ident(~loc=field.loc, field)
+      let value = switch startToken {
+      | Uident(_) => removeModuleNameFromPunnedFieldValue(value)
+      | _ => value
+      }
+
+      Some(field, value)
+    }
+  | _ => None
+  }
+}
+
+and parseRecordExprWithStringKeys = (~startPos, firstRow, p) => {
+  let rows = list{
+    firstRow,
+    ...parseCommaDelimitedRegion(
+      ~grammar=Grammar.RecordRowsStringKey,
+      ~closing=Rbrace,
+      ~f=parseRecordRowWithStringKey,
+      p,
+    ),
+  }
+  let loc = mkLoc(startPos, p.endPos)
+  let recordStrExpr = Ast_helper.Str.eval(~loc, Ast_helper.Exp.record(~loc, rows, None))
+  Ast_helper.Exp.extension(~loc, (Location.mkloc("obj", loc), Parsetree.PStr(list{recordStrExpr})))
+}
+
+and parseRecordExpr = (~startPos, ~spread=None, rows, p) => {
+  let exprs = parseCommaDelimitedRegion(
+    ~grammar=Grammar.RecordRows,
+    ~closing=Rbrace,
+    ~f=parseRecordRow,
+    p,
+  )
+
+  let rows = List.concat(list{rows, exprs})
+  let () = switch rows {
+  | list{} =>
+    let msg = "Record spread needs at least one field that's updated"
+    Parser.err(p, Diagnostics.message(msg))
+  | _rows => ()
+  }
+
+  let loc = mkLoc(startPos, p.endPos)
+  Ast_helper.Exp.record(~loc, rows, spread)
+}
+
+and parseNewlineOrSemicolonExprBlock = p =>
+  switch p.Parser.token {
+  | Semicolon => Parser.next(p)
+  | token if Grammar.isBlockExprStart(token) =>
+    if p.prevEndPos.pos_lnum < p.startPos.pos_lnum {
+      ()
+    } else {
+      Parser.err(
+        ~startPos=p.prevEndPos,
+        ~endPos=p.endPos,
+        p,
+        Diagnostics.message(
+          "consecutive expressions on a line must be separated by ';' or a newline",
+        ),
+      )
+    }
+  | _ => ()
+  }
+
+and parseExprBlockItem = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Module =>
+    Parser.next(p)
+    switch p.token {
+    | Lparen =>
+      let expr = parseFirstClassModuleExpr(~startPos, p)
+      let a = parsePrimaryExpr(~operand=expr, p)
+      let expr = parseBinaryExpr(~a, p, 1)
+      parseTernaryExpr(expr, p)
+    | _ =>
+      let name = switch p.Parser.token {
+      | Uident(ident) =>
+        let loc = mkLoc(p.startPos, p.endPos)
+        Parser.next(p)
+        Location.mkloc(ident, loc)
+      | t =>
+        Parser.err(p, Diagnostics.uident(t))
+        Location.mknoloc("_")
+      }
+
+      let body = parseModuleBindingBody(p)
+      parseNewlineOrSemicolonExprBlock(p)
+      let expr = parseExprBlock(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Exp.letmodule(~loc, name, body, expr)
+    }
+  | Exception =>
+    let extensionConstructor = parseExceptionDef(~attrs, p)
+    parseNewlineOrSemicolonExprBlock(p)
+    let blockExpr = parseExprBlock(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.letexception(~loc, extensionConstructor, blockExpr)
+  | Open =>
+    let od = parseOpenDescription(~attrs, p)
+    parseNewlineOrSemicolonExprBlock(p)
+    let blockExpr = parseExprBlock(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.open_(~loc, od.popen_override, od.popen_lid, blockExpr)
+  | Let =>
+    let (recFlag, letBindings) = parseLetBindings(~attrs, p)
+    parseNewlineOrSemicolonExprBlock(p)
+    let next = if Grammar.isBlockExprStart(p.Parser.token) {
+      parseExprBlock(p)
+    } else {
+      let loc = mkLoc(p.startPos, p.endPos)
+      Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None)
+    }
+
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.let_(~loc, recFlag, letBindings, next)
+  | _ =>
+    let e1 = {
+      let expr = parseExpr(p)
+      {...expr, pexp_attributes: List.concat(list{attrs, expr.pexp_attributes})}
+    }
+
+    parseNewlineOrSemicolonExprBlock(p)
+    if Grammar.isBlockExprStart(p.Parser.token) {
+      let e2 = parseExprBlock(p)
+      let loc = {...e1.pexp_loc, loc_end: e2.pexp_loc.loc_end}
+      Ast_helper.Exp.sequence(~loc, e1, e2)
+    } else {
+      e1
+    }
+  }
+}
+
+/* blockExpr ::= expr
+ *            |  expr          ;
+ *            |  expr          ; blockExpr
+ *            |  module    ... ; blockExpr
+ *            |  open      ... ; blockExpr
+ *            |  exception ... ; blockExpr
+ *            |  let       ...
+ *            |  let       ... ;
+ *            |  let       ... ; blockExpr
+ *
+ *  note: semi should be made optional
+ *  a block of expression is always
+ */
+and parseExprBlock = (~first=?, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.ExprBlock)
+  let item = switch first {
+  | Some(e) => e
+  | None => parseExprBlockItem(p)
+  }
+
+  parseNewlineOrSemicolonExprBlock(p)
+  let blockExpr = if Grammar.isBlockExprStart(p.Parser.token) {
+    let next = parseExprBlockItem(p)
+    let loc = {...item.pexp_loc, loc_end: next.pexp_loc.loc_end}
+    Ast_helper.Exp.sequence(~loc, item, next)
+  } else {
+    item
+  }
+
+  Parser.eatBreadcrumb(p)
+  overParseConstrainedOrCoercedOrArrowExpression(p, blockExpr)
+}
+
+and parseTryExpression = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Try, p)
+  let expr = parseExpr(~context=WhenExpr, p)
+  Parser.expect(Res_token.catch, p)
+  Parser.expect(Lbrace, p)
+  let cases = parsePatternMatching(p)
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.try_(~loc, expr, cases)
+}
+
+and parseIfCondition = p => {
+  Parser.leaveBreadcrumb(p, Grammar.IfCondition)
+  /* doesn't make sense to try es6 arrow here? */
+  let conditionExpr = parseExpr(~context=WhenExpr, p)
+  Parser.eatBreadcrumb(p)
+  conditionExpr
+}
+
+and parseThenBranch = p => {
+  Parser.leaveBreadcrumb(p, IfBranch)
+  Parser.expect(Lbrace, p)
+  let thenExpr = parseExprBlock(p)
+  Parser.expect(Rbrace, p)
+  Parser.eatBreadcrumb(p)
+  thenExpr
+}
+
+and parseElseBranch = p => {
+  Parser.expect(Lbrace, p)
+  let blockExpr = parseExprBlock(p)
+  Parser.expect(Rbrace, p)
+  blockExpr
+}
+
+and parseIfExpr = (startPos, p) => {
+  let conditionExpr = parseIfCondition(p)
+  let thenExpr = parseThenBranch(p)
+  let elseExpr = switch p.Parser.token {
+  | Else =>
+    Parser.endRegion(p)
+    Parser.leaveBreadcrumb(p, Grammar.ElseBranch)
+    Parser.next(p)
+    Parser.beginRegion(p)
+    let elseExpr = switch p.token {
+    | If => parseIfOrIfLetExpression(p)
+    | _ => parseElseBranch(p)
+    }
+
+    Parser.eatBreadcrumb(p)
+    Parser.endRegion(p)
+    Some(elseExpr)
+  | _ =>
+    Parser.endRegion(p)
+    None
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.ifthenelse(~loc, conditionExpr, thenExpr, elseExpr)
+}
+
+and parseIfLetExpr = (startPos, p) => {
+  let pattern = parsePattern(p)
+  Parser.expect(Equal, p)
+  let conditionExpr = parseIfCondition(p)
+  let thenExpr = parseThenBranch(p)
+  let elseExpr = switch p.Parser.token {
+  | Else =>
+    Parser.endRegion(p)
+    Parser.leaveBreadcrumb(p, Grammar.ElseBranch)
+    Parser.next(p)
+    Parser.beginRegion(p)
+    let elseExpr = switch p.token {
+    | If => parseIfOrIfLetExpression(p)
+    | _ => parseElseBranch(p)
+    }
+
+    Parser.eatBreadcrumb(p)
+    Parser.endRegion(p)
+    elseExpr
+  | _ =>
+    Parser.endRegion(p)
+    let startPos = p.Parser.startPos
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None)
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.match_(
+    ~attrs=list{ifLetAttr, suppressFragileMatchWarningAttr},
+    ~loc,
+    conditionExpr,
+    list{
+      Ast_helper.Exp.case(pattern, thenExpr),
+      Ast_helper.Exp.case(Ast_helper.Pat.any(), elseExpr),
+    },
+  )
+}
+
+and parseIfOrIfLetExpression = p => {
+  Parser.beginRegion(p)
+  Parser.leaveBreadcrumb(p, Grammar.ExprIf)
+  let startPos = p.Parser.startPos
+  Parser.expect(If, p)
+  let expr = switch p.Parser.token {
+  | Let =>
+    Parser.next(p)
+    let ifLetExpr = parseIfLetExpr(startPos, p)
+    Parser.err(
+      ~startPos=ifLetExpr.pexp_loc.loc_start,
+      ~endPos=ifLetExpr.pexp_loc.loc_end,
+      p,
+      Diagnostics.message(ErrorMessages.experimentalIfLet(ifLetExpr)),
+    )
+    ifLetExpr
+  | _ => parseIfExpr(startPos, p)
+  }
+
+  Parser.eatBreadcrumb(p)
+  expr
+}
+
+and parseForRest = (hasOpeningParen, pattern, startPos, p) => {
+  Parser.expect(In, p)
+  let e1 = parseExpr(p)
+  let direction = switch p.Parser.token {
+  | Lident("to") => Asttypes.Upto
+  | Lident("downto") => Asttypes.Downto
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Asttypes.Upto
+  }
+
+  Parser.next(p)
+  let e2 = parseExpr(~context=WhenExpr, p)
+  if hasOpeningParen {
+    Parser.expect(Rparen, p)
+  }
+  Parser.expect(Lbrace, p)
+  let bodyExpr = parseExprBlock(p)
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.for_(~loc, pattern, e1, e2, direction, bodyExpr)
+}
+
+and parseForExpression = p => {
+  let startPos = p.Parser.startPos
+  Parser.leaveBreadcrumb(p, Grammar.ExprFor)
+  Parser.expect(For, p)
+  Parser.beginRegion(p)
+  let forExpr = switch p.token {
+  | Lparen =>
+    let lparen = p.startPos
+    Parser.next(p)
+    switch p.token {
+    | Rparen =>
+      Parser.next(p)
+      let unitPattern = {
+        let loc = mkLoc(lparen, p.prevEndPos)
+        let lid = Location.mkloc(Longident.Lident("()"), loc)
+        Ast_helper.Pat.construct(lid, None)
+      }
+
+      parseForRest(false, parseAliasPattern(~attrs=list{}, unitPattern, p), startPos, p)
+    | _ =>
+      Parser.leaveBreadcrumb(p, Grammar.Pattern)
+      let pat = parsePattern(p)
+      Parser.eatBreadcrumb(p)
+      switch p.token {
+      | Comma =>
+        Parser.next(p)
+        let tuplePattern = parseTuplePattern(~attrs=list{}, ~startPos=lparen, ~first=pat, p)
+
+        let pattern = parseAliasPattern(~attrs=list{}, tuplePattern, p)
+        parseForRest(false, pattern, startPos, p)
+      | _ => parseForRest(true, pat, startPos, p)
+      }
+    }
+  | _ =>
+    Parser.leaveBreadcrumb(p, Grammar.Pattern)
+    let pat = parsePattern(p)
+    Parser.eatBreadcrumb(p)
+    parseForRest(false, pat, startPos, p)
+  }
+
+  Parser.eatBreadcrumb(p)
+  Parser.endRegion(p)
+  forExpr
+}
+
+and parseWhileExpression = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(While, p)
+  let expr1 = parseExpr(~context=WhenExpr, p)
+  Parser.expect(Lbrace, p)
+  let expr2 = parseExprBlock(p)
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.while_(~loc, expr1, expr2)
+}
+
+and parsePatternGuard = p =>
+  switch p.Parser.token {
+  | When | If =>
+    Parser.next(p)
+    Some(parseExpr(~context=WhenExpr, p))
+  | _ => None
+  }
+
+and parsePatternMatchCase = p => {
+  Parser.beginRegion(p)
+  Parser.leaveBreadcrumb(p, Grammar.PatternMatchCase)
+  switch p.Parser.token {
+  | Token.Bar =>
+    Parser.next(p)
+    Parser.leaveBreadcrumb(p, Grammar.Pattern)
+    let lhs = parsePattern(p)
+    Parser.eatBreadcrumb(p)
+    let guard = parsePatternGuard(p)
+    let () = switch p.token {
+    | EqualGreater => Parser.next(p)
+    | _ => Recover.recoverEqualGreater(p)
+    }
+
+    let rhs = parseExprBlock(p)
+    Parser.endRegion(p)
+    Parser.eatBreadcrumb(p)
+    Some(Ast_helper.Exp.case(lhs, ~guard?, rhs))
+  | _ =>
+    Parser.endRegion(p)
+    Parser.eatBreadcrumb(p)
+    None
+  }
+}
+
+and parsePatternMatching = p => {
+  let cases = parseDelimitedRegion(
+    ~grammar=Grammar.PatternMatching,
+    ~closing=Rbrace,
+    ~f=parsePatternMatchCase,
+    p,
+  )
+
+  let () = switch cases {
+  | list{} =>
+    Parser.err(
+      ~startPos=p.prevEndPos,
+      p,
+      Diagnostics.message("Pattern matching needs at least one case"),
+    )
+  | _ => ()
+  }
+
+  cases
+}
+
+and parseSwitchExpression = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Switch, p)
+  let switchExpr = parseExpr(~context=WhenExpr, p)
+  Parser.expect(Lbrace, p)
+  let cases = parsePatternMatching(p)
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.match_(~loc, switchExpr, cases)
+}
+
+/*
+ * argument ::=
+ *   | _                            (* syntax sugar *)
+ *   | expr
+ *   | expr : type
+ *   | ~ label-name
+ *   | ~ label-name
+ *   | ~ label-name ?
+ *   | ~ label-name =   expr
+ *   | ~ label-name =   _           (* syntax sugar *)
+ *   | ~ label-name =   expr : type
+ *   | ~ label-name = ? expr
+ *   | ~ label-name = ? _           (* syntax sugar *)
+ *   | ~ label-name = ? expr : type
+ *
+ *  uncurried_argument ::=
+ *   | . argument
+ */
+and parseArgument = p =>
+  if (
+    p.Parser.token == Token.Tilde ||
+      (p.token == Dot ||
+      (p.token == Underscore || Grammar.isExprStart(p.token)))
+  ) {
+    switch p.Parser.token {
+    | Dot =>
+      let uncurried = true
+      Parser.next(p)
+      switch p.token {
+      /* apply(.) */
+      | Rparen =>
+        let unitExpr = Ast_helper.Exp.construct(Location.mknoloc(Longident.Lident("()")), None)
+
+        Some(uncurried, Asttypes.Nolabel, unitExpr)
+      | _ => parseArgument2(p, ~uncurried)
+      }
+    | _ => parseArgument2(p, ~uncurried=false)
+    }
+  } else {
+    None
+  }
+
+and parseArgument2 = (p, ~uncurried) =>
+  switch p.Parser.token {
+  /* foo(_), do not confuse with foo(_ => x), TODO: performance */
+  | Underscore if !isEs6ArrowExpression(~inTernary=false, p) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    let exp = Ast_helper.Exp.ident(~loc, Location.mkloc(Longident.Lident("_"), loc))
+    Some(uncurried, Asttypes.Nolabel, exp)
+  | Tilde =>
+    Parser.next(p)
+    /* TODO: nesting of pattern matches not intuitive for error recovery */
+    switch p.Parser.token {
+    | Lident(ident) =>
+      let startPos = p.startPos
+      Parser.next(p)
+      let endPos = p.prevEndPos
+      let loc = mkLoc(startPos, endPos)
+      let propLocAttr = (Location.mkloc("ns.namedArgLoc", loc), Parsetree.PStr(list{}))
+      let identExpr = Ast_helper.Exp.ident(
+        ~attrs=list{propLocAttr},
+        ~loc,
+        Location.mkloc(Longident.Lident(ident), loc),
+      )
+      switch p.Parser.token {
+      | Question =>
+        Parser.next(p)
+        Some(uncurried, Asttypes.Optional(ident), identExpr)
+      | Equal =>
+        Parser.next(p)
+        let label = switch p.Parser.token {
+        | Question =>
+          Parser.next(p)
+          Asttypes.Optional(ident)
+        | _ => Labelled(ident)
+        }
+
+        let expr = switch p.Parser.token {
+        | Underscore if !isEs6ArrowExpression(~inTernary=false, p) =>
+          let loc = mkLoc(p.startPos, p.endPos)
+          Parser.next(p)
+          Ast_helper.Exp.ident(~loc, Location.mkloc(Longident.Lident("_"), loc))
+        | _ =>
+          let expr = parseConstrainedOrCoercedExpr(p)
+          {...expr, pexp_attributes: list{propLocAttr, ...expr.pexp_attributes}}
+        }
+
+        Some(uncurried, label, expr)
+      | Colon =>
+        Parser.next(p)
+        let typ = parseTypExpr(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let expr = Ast_helper.Exp.constraint_(~attrs=list{propLocAttr}, ~loc, identExpr, typ)
+        Some(uncurried, Labelled(ident), expr)
+      | _ => Some(uncurried, Labelled(ident), identExpr)
+      }
+    | t =>
+      Parser.err(p, Diagnostics.lident(t))
+      Some(uncurried, Nolabel, Recover.defaultExpr())
+    }
+  | _ => Some(uncurried, Nolabel, parseConstrainedOrCoercedExpr(p))
+  }
+
+and parseCallExpr = (p, funExpr) => {
+  Parser.expect(Lparen, p)
+  let startPos = p.Parser.startPos
+  Parser.leaveBreadcrumb(p, Grammar.ExprCall)
+  let args = parseCommaDelimitedRegion(
+    ~grammar=Grammar.ArgumentList,
+    ~closing=Rparen,
+    ~f=parseArgument,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  let args = switch args {
+  | list{} =>
+    let loc = mkLoc(startPos, p.prevEndPos)
+    /* No args -> unit sugar: `foo()` */
+    list{
+      (
+        false,
+        Asttypes.Nolabel,
+        Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None),
+      ),
+    }
+  | list{(
+      true,
+      Asttypes.Nolabel,
+      {
+        pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, None),
+        pexp_loc: loc,
+        pexp_attributes: list{},
+      } as expr,
+    )}
+    if !loc.loc_ghost &&
+    p.mode == ParseForTypeChecker => /* Since there is no syntax space for arity zero vs arity one,
+     *  we expand
+     *    `fn(. ())` into
+     *    `fn(. {let __res_unit = (); __res_unit})`
+     *  when the parsetree is intended for type checking
+     *
+     *  Note:
+     *    `fn(.)` is treated as zero arity application.
+     *  The invisible unit expression here has loc_ghost === true
+     *
+     *  Related: https://github.com/rescript-lang/syntax/issues/138
+     */
+    list{
+      (
+        true,
+        Asttypes.Nolabel,
+        Ast_helper.Exp.let_(
+          Asttypes.Nonrecursive,
+          list{Ast_helper.Vb.mk(Ast_helper.Pat.var(Location.mknoloc("__res_unit")), expr)},
+          Ast_helper.Exp.ident(Location.mknoloc(Longident.Lident("__res_unit"))),
+        ),
+      ),
+    }
+  | args => args
+  }
+
+  let loc = {...funExpr.pexp_loc, loc_end: p.prevEndPos}
+  let args = switch args {
+  | list{(u, lbl, expr), ...args} =>
+    let group = ((grp, acc), (uncurried, lbl, expr)) => {
+      let (_u, grp) = grp
+      if uncurried === true {
+        ((true, list{(lbl, expr)}), list{(_u, List.rev(grp)), ...acc})
+      } else {
+        ((_u, list{(lbl, expr), ...grp}), acc)
+      }
+    }
+
+    let ((_u, grp), acc) = List.fold_left(group, ((u, list{(lbl, expr)}), list{}), args)
+    List.rev(list{(_u, List.rev(grp)), ...acc})
+  | list{} => list{}
+  }
+
+  let apply = List.fold_left((callBody, group) => {
+    let (uncurried, args) = group
+    let (args, wrap) = processUnderscoreApplication(args)
+    let exp = if uncurried {
+      let attrs = list{uncurryAttr}
+      Ast_helper.Exp.apply(~loc, ~attrs, callBody, args)
+    } else {
+      Ast_helper.Exp.apply(~loc, callBody, args)
+    }
+
+    wrap(exp)
+  }, funExpr, args)
+
+  Parser.eatBreadcrumb(p)
+  apply
+}
+
+and parseValueOrConstructor = p => {
+  let startPos = p.Parser.startPos
+  let rec aux = (p, acc) =>
+    switch p.Parser.token {
+    | Uident(ident) =>
+      let endPosLident = p.endPos
+      Parser.next(p)
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        aux(p, list{ident, ...acc})
+      | Lparen if p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+        let lparen = p.startPos
+        let args = parseConstructorArgs(p)
+        let rparen = p.prevEndPos
+        let lident = buildLongident(list{ident, ...acc})
+        let tail = switch args {
+        | list{} => None
+        | list{{Parsetree.pexp_desc: Pexp_tuple(_)} as arg} as args =>
+          let loc = mkLoc(lparen, rparen)
+          if p.mode == ParseForTypeChecker {
+            /* Some(1, 2) for type-checker */
+            Some(arg)
+          } else {
+            /* Some((1, 2)) for printer */
+            Some(Ast_helper.Exp.tuple(~loc, args))
+          }
+        | list{arg} => Some(arg)
+        | args =>
+          let loc = mkLoc(lparen, rparen)
+          Some(Ast_helper.Exp.tuple(~loc, args))
+        }
+
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let identLoc = mkLoc(startPos, endPosLident)
+        Ast_helper.Exp.construct(~loc, Location.mkloc(lident, identLoc), tail)
+      | _ =>
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let lident = buildLongident(list{ident, ...acc})
+        Ast_helper.Exp.construct(~loc, Location.mkloc(lident, loc), None)
+      }
+    | Lident(ident) =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let lident = buildLongident(list{ident, ...acc})
+      Ast_helper.Exp.ident(~loc, Location.mkloc(lident, loc))
+    | token =>
+      if acc == list{} {
+        Parser.next(p)
+        Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+        Recover.defaultExpr()
+      } else {
+        let loc = mkLoc(startPos, p.prevEndPos)
+        Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+        let lident = buildLongident(list{"_", ...acc})
+        Ast_helper.Exp.ident(~loc, Location.mkloc(lident, loc))
+      }
+    }
+
+  aux(p, list{})
+}
+
+and parsePolyVariantExpr = p => {
+  let startPos = p.startPos
+  let (ident, _loc) = parseHashIdent(~startPos, p)
+  switch p.Parser.token {
+  | Lparen if p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+    let lparen = p.startPos
+    let args = parseConstructorArgs(p)
+    let rparen = p.prevEndPos
+    let loc_paren = mkLoc(lparen, rparen)
+    let tail = switch args {
+    | list{} => None
+    | list{{Parsetree.pexp_desc: Pexp_tuple(_)} as expr} as args =>
+      if p.mode == ParseForTypeChecker {
+        /* #a(1, 2) for type-checker */
+        Some(expr)
+      } else {
+        /* #a((1, 2)) for type-checker */
+        Some(Ast_helper.Exp.tuple(~loc=loc_paren, args))
+      }
+    | list{arg} => Some(arg)
+    | args =>
+      /* #a((1, 2)) for printer */
+      Some(Ast_helper.Exp.tuple(~loc=loc_paren, args))
+    }
+
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.variant(~loc, ident, tail)
+  | _ =>
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Exp.variant(~loc, ident, None)
+  }
+}
+
+and parseConstructorArgs = p => {
+  let lparen = p.Parser.startPos
+  Parser.expect(Lparen, p)
+  let args = parseCommaDelimitedRegion(
+    ~grammar=Grammar.ExprList,
+    ~f=parseConstrainedExprRegion,
+    ~closing=Rparen,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  switch args {
+  | list{} =>
+    let loc = mkLoc(lparen, p.prevEndPos)
+    list{Ast_helper.Exp.construct(~loc, Location.mkloc(Longident.Lident("()"), loc), None)}
+  | args => args
+  }
+}
+
+and parseTupleExpr = (~first, ~startPos, p) => {
+  let exprs = list{
+    first,
+    ...parseCommaDelimitedRegion(
+      p,
+      ~grammar=Grammar.ExprList,
+      ~closing=Rparen,
+      ~f=parseConstrainedExprRegion,
+    ),
+  }
+
+  Parser.expect(Rparen, p)
+  let () = switch exprs {
+  | list{_} =>
+    Parser.err(
+      ~startPos,
+      ~endPos=p.prevEndPos,
+      p,
+      Diagnostics.message(ErrorMessages.tupleSingleElement),
+    )
+  | _ => ()
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Exp.tuple(~loc, exprs)
+}
+
+and parseSpreadExprRegion = p =>
+  switch p.Parser.token {
+  | DotDotDot =>
+    Parser.next(p)
+    let expr = parseConstrainedOrCoercedExpr(p)
+    Some(true, expr)
+  | token if Grammar.isExprStart(token) => Some(false, parseConstrainedOrCoercedExpr(p))
+  | _ => None
+  }
+
+and parseListExpr = (~startPos, p) => {
+  let listExprs = parseCommaDelimitedReversedList(
+    p,
+    ~grammar=Grammar.ListExpr,
+    ~closing=Rbrace,
+    ~f=parseSpreadExprRegion,
+  )
+
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  switch listExprs {
+  | list{(true, expr), ...exprs} =>
+    let exprs = exprs |> List.map(snd) |> List.rev
+    makeListExpression(loc, exprs, Some(expr))
+  | exprs =>
+    let exprs =
+      exprs
+      |> List.map(((spread, expr)) => {
+        if spread {
+          Parser.err(p, Diagnostics.message(ErrorMessages.listExprSpread))
+        }
+        expr
+      })
+      |> List.rev
+
+    makeListExpression(loc, exprs, None)
+  }
+}
+
+/* Overparse ... and give a nice error message */
+and parseNonSpreadExp = (~msg, p) => {
+  let () = switch p.Parser.token {
+  | DotDotDot =>
+    Parser.err(p, Diagnostics.message(msg))
+    Parser.next(p)
+  | _ => ()
+  }
+
+  switch p.Parser.token {
+  | token if Grammar.isExprStart(token) =>
+    let expr = parseExpr(p)
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(expr.pexp_loc.loc_start, typ.ptyp_loc.loc_end)
+      Some(Ast_helper.Exp.constraint_(~loc, expr, typ))
+    | _ => Some(expr)
+    }
+  | _ => None
+  }
+}
+
+and parseArrayExp = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lbracket, p)
+  let exprs = parseCommaDelimitedRegion(
+    p,
+    ~grammar=Grammar.ExprList,
+    ~closing=Rbracket,
+    ~f=parseNonSpreadExp(~msg=ErrorMessages.arrayExprSpread),
+  )
+
+  Parser.expect(Rbracket, p)
+  Ast_helper.Exp.array(~loc=mkLoc(startPos, p.prevEndPos), exprs)
+}
+
+/* TODO: check attributes in the case of poly type vars,
+ * might be context dependend: parseFieldDeclaration (see ocaml) */
+and parsePolyTypeExpr = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | SingleQuote =>
+    let vars = parseTypeVarList(p)
+    switch vars {
+    | list{_v1, _v2, ..._} =>
+      Parser.expect(Dot, p)
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Typ.poly(~loc, vars, typ)
+    | list{var} =>
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        let typ = parseTypExpr(p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        Ast_helper.Typ.poly(~loc, vars, typ)
+      | EqualGreater =>
+        Parser.next(p)
+        let typ = Ast_helper.Typ.var(~loc=var.loc, var.txt)
+        let returnType = parseTypExpr(~alias=false, p)
+        let loc = mkLoc(typ.Parsetree.ptyp_loc.loc_start, p.prevEndPos)
+        Ast_helper.Typ.arrow(~loc, Asttypes.Nolabel, typ, returnType)
+      | _ => Ast_helper.Typ.var(~loc=var.loc, var.txt)
+      }
+    | _ => assert false
+    }
+  | _ => parseTypExpr(p)
+  }
+}
+
+/* 'a 'b 'c */
+and parseTypeVarList = p => {
+  let rec loop = (p, vars) =>
+    switch p.Parser.token {
+    | SingleQuote =>
+      Parser.next(p)
+      let (lident, loc) = parseLident(p)
+      let var = Location.mkloc(lident, loc)
+      loop(p, list{var, ...vars})
+    | _ => List.rev(vars)
+    }
+
+  loop(p, list{})
+}
+
+and parseLidentList = p => {
+  let rec loop = (p, ls) =>
+    switch p.Parser.token {
+    | Lident(lident) =>
+      let loc = mkLoc(p.startPos, p.endPos)
+      Parser.next(p)
+      loop(p, list{Location.mkloc(lident, loc), ...ls})
+    | _ => List.rev(ls)
+    }
+
+  loop(p, list{})
+}
+
+and parseAtomicTypExpr = (~attrs, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.AtomicTypExpr)
+  let startPos = p.Parser.startPos
+  let typ = switch p.Parser.token {
+  | SingleQuote =>
+    Parser.next(p)
+    let (ident, loc) = parseIdent(~msg=ErrorMessages.typeVar, ~startPos=p.startPos, p)
+    Ast_helper.Typ.var(~loc, ~attrs, ident)
+  | Underscore =>
+    let endPos = p.endPos
+    Parser.next(p)
+    Ast_helper.Typ.any(~loc=mkLoc(startPos, endPos), ~attrs, ())
+  | Lparen =>
+    Parser.next(p)
+    switch p.Parser.token {
+    | Rparen =>
+      Parser.next(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let unitConstr = Location.mkloc(Longident.Lident("unit"), loc)
+      Ast_helper.Typ.constr(~attrs, unitConstr, list{})
+    | _ =>
+      let t = parseTypExpr(p)
+      switch p.token {
+      | Comma =>
+        Parser.next(p)
+        parseTupleType(~attrs, ~first=t, ~startPos, p)
+      | _ =>
+        Parser.expect(Rparen, p)
+        {
+          ...t,
+          ptyp_loc: mkLoc(startPos, p.prevEndPos),
+          ptyp_attributes: List.concat(list{attrs, t.ptyp_attributes}),
+        }
+      }
+    }
+  | Lbracket => parsePolymorphicVariantType(~attrs, p)
+  | Uident(_) | Lident(_) =>
+    let constr = parseValuePath(p)
+    let args = parseTypeConstructorArgs(~constrName=constr, p)
+    Ast_helper.Typ.constr(~loc=mkLoc(startPos, p.prevEndPos), ~attrs, constr, args)
+  | Module =>
+    Parser.next(p)
+    Parser.expect(Lparen, p)
+    let packageType = parsePackageType(~startPos, ~attrs, p)
+    Parser.expect(Rparen, p)
+    {...packageType, ptyp_loc: mkLoc(startPos, p.prevEndPos)}
+  | Percent =>
+    let extension = parseExtension(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Typ.extension(~attrs, ~loc, extension)
+  | Lbrace => parseRecordOrObjectType(~attrs, p)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    switch skipTokensAndMaybeRetry(p, ~isStartOfGrammar=Grammar.isAtomicTypExprStart) {
+    | Some() => parseAtomicTypExpr(~attrs, p)
+    | None =>
+      Parser.err(~startPos=p.prevEndPos, p, Diagnostics.unexpected(token, p.breadcrumbs))
+      Recover.defaultType()
+    }
+  }
+
+  Parser.eatBreadcrumb(p)
+  typ
+}
+
+/* package-type	::=
+    | modtype-path
+    ∣ modtype-path with package-constraint  { and package-constraint }
+ */
+and parsePackageType = (~startPos, ~attrs, p) => {
+  let modTypePath = parseModuleLongIdent(~lowercase=true, p)
+  switch p.Parser.token {
+  | Lident("with") =>
+    Parser.next(p)
+    let constraints = parsePackageConstraints(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Typ.package(~loc, ~attrs, modTypePath, constraints)
+  | _ =>
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Typ.package(~loc, ~attrs, modTypePath, list{})
+  }
+}
+
+/* package-constraint  { and package-constraint } */
+and parsePackageConstraints = p => {
+  let first = {
+    Parser.expect(Typ, p)
+    let typeConstr = parseValuePath(p)
+    Parser.expect(Equal, p)
+    let typ = parseTypExpr(p)
+    (typeConstr, typ)
+  }
+
+  let rest = parseRegion(~grammar=Grammar.PackageConstraint, ~f=parsePackageConstraint, p)
+
+  list{first, ...rest}
+}
+
+/* and type typeconstr = typexpr */
+and parsePackageConstraint = p =>
+  switch p.Parser.token {
+  | And =>
+    Parser.next(p)
+    Parser.expect(Typ, p)
+    let typeConstr = parseValuePath(p)
+    Parser.expect(Equal, p)
+    let typ = parseTypExpr(p)
+    Some(typeConstr, typ)
+  | _ => None
+  }
+
+and parseRecordOrObjectType = (~attrs, p) => {
+  /* for inline record in constructor */
+  let startPos = p.Parser.startPos
+  Parser.expect(Lbrace, p)
+  let closedFlag = switch p.token {
+  | DotDot =>
+    Parser.next(p)
+    Asttypes.Open
+  | Dot =>
+    Parser.next(p)
+    Asttypes.Closed
+  | _ => Asttypes.Closed
+  }
+
+  let () = switch p.token {
+  | Lident(_) => Parser.err(p, Diagnostics.message(ErrorMessages.forbiddenInlineRecordDeclaration))
+  | _ => ()
+  }
+
+  let startFirstField = p.startPos
+  let fields = parseCommaDelimitedRegion(
+    ~grammar=Grammar.StringFieldDeclarations,
+    ~closing=Rbrace,
+    ~f=parseStringFieldDeclaration,
+    p,
+  )
+
+  let () = switch fields {
+  | list{Parsetree.Oinherit({ptyp_loc})} =>
+    /* {...x}, spread without extra fields */
+    Parser.err(
+      p,
+      ~startPos=startFirstField,
+      ~endPos=ptyp_loc.loc_end,
+      Diagnostics.message(ErrorMessages.sameTypeSpread),
+    )
+  | _ => ()
+  }
+
+  Parser.expect(Rbrace, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Typ.object_(~loc, ~attrs, fields, closedFlag)
+}
+
+/* TODO: check associativity in combination with attributes */
+and parseTypeAlias = (p, typ) =>
+  switch p.Parser.token {
+  | As =>
+    Parser.next(p)
+    Parser.expect(SingleQuote, p)
+    let (ident, _loc) = parseLident(p)
+    /* TODO: how do we parse attributes here? */
+    Ast_helper.Typ.alias(~loc=mkLoc(typ.Parsetree.ptyp_loc.loc_start, p.prevEndPos), typ, ident)
+  | _ => typ
+  }
+
+/* type_parameter ::=
+ *  | type_expr
+ *  | ~ident: type_expr
+ *  | ~ident: type_expr=?
+ *
+ * note:
+ *  | attrs ~ident: type_expr    -> attrs are on the arrow
+ *  | attrs type_expr            -> attrs are here part of the type_expr
+ *
+ * uncurried_type_parameter ::=
+ *  | . type_parameter
+ */
+and parseTypeParameter = p =>
+  if p.Parser.token == Token.Tilde || (p.token == Dot || Grammar.isTypExprStart(p.token)) {
+    let startPos = p.Parser.startPos
+    let uncurried = Parser.optional(p, Dot)
+    let attrs = parseAttributes(p)
+    switch p.Parser.token {
+    | Tilde =>
+      Parser.next(p)
+      let (name, loc) = parseLident(p)
+      let lblLocAttr = (Location.mkloc("ns.namedArgLoc", loc), Parsetree.PStr(list{}))
+      Parser.expect(~grammar=Grammar.TypeExpression, Colon, p)
+      let typ = {
+        let typ = parseTypExpr(p)
+        {...typ, ptyp_attributes: list{lblLocAttr, ...typ.ptyp_attributes}}
+      }
+
+      switch p.Parser.token {
+      | Equal =>
+        Parser.next(p)
+        Parser.expect(Question, p)
+        Some(uncurried, attrs, Asttypes.Optional(name), typ, startPos)
+      | _ => Some(uncurried, attrs, Asttypes.Labelled(name), typ, startPos)
+      }
+    | Lident(_) =>
+      let (name, loc) = parseLident(p)
+      switch p.token {
+      | Colon =>
+        let () = {
+          let error = Diagnostics.message(ErrorMessages.missingTildeLabeledParameter(name))
+          Parser.err(~startPos=loc.loc_start, ~endPos=loc.loc_end, p, error)
+        }
+
+        Parser.next(p)
+        let typ = parseTypExpr(p)
+        switch p.Parser.token {
+        | Equal =>
+          Parser.next(p)
+          Parser.expect(Question, p)
+          Some(uncurried, attrs, Asttypes.Optional(name), typ, startPos)
+        | _ => Some(uncurried, attrs, Asttypes.Labelled(name), typ, startPos)
+        }
+      | _ =>
+        let constr = Location.mkloc(Longident.Lident(name), loc)
+        let args = parseTypeConstructorArgs(~constrName=constr, p)
+        let typ = Ast_helper.Typ.constr(~loc=mkLoc(startPos, p.prevEndPos), ~attrs, constr, args)
+
+        let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+        let typ = parseTypeAlias(p, typ)
+        Some(uncurried, list{}, Asttypes.Nolabel, typ, startPos)
+      }
+    | _ =>
+      let typ = parseTypExpr(p)
+      let typWithAttributes = {
+        ...typ,
+        ptyp_attributes: List.concat(list{attrs, typ.ptyp_attributes}),
+      }
+      Some(uncurried, list{}, Asttypes.Nolabel, typWithAttributes, startPos)
+    }
+  } else {
+    None
+  }
+
+/* (int, ~x:string, float) */
+and parseTypeParameters = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lparen, p)
+  switch p.Parser.token {
+  | Rparen =>
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let unitConstr = Location.mkloc(Longident.Lident("unit"), loc)
+    let typ = Ast_helper.Typ.constr(unitConstr, list{})
+    list{(false, list{}, Asttypes.Nolabel, typ, startPos)}
+  | _ =>
+    let params = parseCommaDelimitedRegion(
+      ~grammar=Grammar.TypeParameters,
+      ~closing=Rparen,
+      ~f=parseTypeParameter,
+      p,
+    )
+
+    Parser.expect(Rparen, p)
+    params
+  }
+}
+
+and parseEs6ArrowType = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Tilde =>
+    Parser.next(p)
+    let (name, loc) = parseLident(p)
+    let lblLocAttr = (Location.mkloc("ns.namedArgLoc", loc), Parsetree.PStr(list{}))
+    Parser.expect(~grammar=Grammar.TypeExpression, Colon, p)
+    let typ = {
+      let typ = parseTypExpr(~alias=false, ~es6Arrow=false, p)
+      {...typ, ptyp_attributes: list{lblLocAttr, ...typ.ptyp_attributes}}
+    }
+
+    let arg = switch p.Parser.token {
+    | Equal =>
+      Parser.next(p)
+      Parser.expect(Question, p)
+      Asttypes.Optional(name)
+    | _ => Asttypes.Labelled(name)
+    }
+
+    Parser.expect(EqualGreater, p)
+    let returnType = parseTypExpr(~alias=false, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Typ.arrow(~loc, ~attrs, arg, typ, returnType)
+  | _ =>
+    let parameters = parseTypeParameters(p)
+    Parser.expect(EqualGreater, p)
+    let returnType = parseTypExpr(~alias=false, p)
+    let endPos = p.prevEndPos
+    let typ = List.fold_right(((uncurried, attrs, argLbl, typ, startPos), t) => {
+      let attrs = if uncurried {
+        list{uncurryAttr, ...attrs}
+      } else {
+        attrs
+      }
+      Ast_helper.Typ.arrow(~loc=mkLoc(startPos, endPos), ~attrs, argLbl, typ, t)
+    }, parameters, returnType)
+
+    {
+      ...typ,
+      ptyp_attributes: List.concat(list{typ.ptyp_attributes, attrs}),
+      ptyp_loc: mkLoc(startPos, p.prevEndPos),
+    }
+  }
+}
+
+/*
+ * typexpr ::=
+ *  | 'ident
+ *  | _
+ *  | (typexpr)
+ *  | typexpr => typexpr            --> es6 arrow
+ *  | (typexpr, typexpr) => typexpr --> es6 arrow
+ *  | /typexpr, typexpr, typexpr/  --> tuple
+ *  | typeconstr
+ *  | typeconstr<typexpr>
+ *  | typeconstr<typexpr, typexpr,>
+ *  | typexpr as 'ident
+ *  | %attr-id                      --> extension
+ *  | %attr-id(payload)             --> extension
+ *
+ * typeconstr ::=
+ *  | lident
+ *  | uident.lident
+ *  | uident.uident.lident     --> long module path
+ */
+and parseTypExpr = (~attrs=?, ~es6Arrow=true, ~alias=true, p) => {
+  /* Parser.leaveBreadcrumb p Grammar.TypeExpression; */
+  let startPos = p.Parser.startPos
+  let attrs = switch attrs {
+  | Some(attrs) => attrs
+  | None => parseAttributes(p)
+  }
+  let typ = if es6Arrow && isEs6ArrowType(p) {
+    parseEs6ArrowType(~attrs, p)
+  } else {
+    let typ = parseAtomicTypExpr(~attrs, p)
+    parseArrowTypeRest(~es6Arrow, ~startPos, typ, p)
+  }
+
+  let typ = if alias {
+    parseTypeAlias(p, typ)
+  } else {
+    typ
+  }
+
+  /* Parser.eatBreadcrumb p; */
+  typ
+}
+
+and parseArrowTypeRest = (~es6Arrow, ~startPos, typ, p) =>
+  switch p.Parser.token {
+  | (EqualGreater | MinusGreater) as token if es6Arrow === true =>
+    /* error recovery */
+    if token == MinusGreater {
+      Parser.expect(EqualGreater, p)
+    }
+    Parser.next(p)
+    let returnType = parseTypExpr(~alias=false, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Typ.arrow(~loc, Asttypes.Nolabel, typ, returnType)
+  | _ => typ
+  }
+
+and parseTypExprRegion = p =>
+  if Grammar.isTypExprStart(p.Parser.token) {
+    Some(parseTypExpr(p))
+  } else {
+    None
+  }
+
+and parseTupleType = (~attrs, ~first, ~startPos, p) => {
+  let typexprs = list{
+    first,
+    ...parseCommaDelimitedRegion(
+      ~grammar=Grammar.TypExprList,
+      ~closing=Rparen,
+      ~f=parseTypExprRegion,
+      p,
+    ),
+  }
+
+  Parser.expect(Rparen, p)
+  let () = switch typexprs {
+  | list{_} =>
+    Parser.err(
+      ~startPos,
+      ~endPos=p.prevEndPos,
+      p,
+      Diagnostics.message(ErrorMessages.tupleSingleElement),
+    )
+  | _ => ()
+  }
+
+  let tupleLoc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Typ.tuple(~attrs, ~loc=tupleLoc, typexprs)
+}
+
+and parseTypeConstructorArgRegion = p =>
+  if Grammar.isTypExprStart(p.Parser.token) {
+    Some(parseTypExpr(p))
+  } else if p.token == LessThan {
+    Parser.next(p)
+    parseTypeConstructorArgRegion(p)
+  } else {
+    None
+  }
+
+/* Js.Nullable.value<'a> */
+and parseTypeConstructorArgs = (~constrName, p) => {
+  let opening = p.Parser.token
+  let openingStartPos = p.startPos
+  switch opening {
+  | LessThan | Lparen =>
+    Scanner.setDiamondMode(p.scanner)
+    Parser.next(p)
+    let typeArgs = /* TODO: change Grammar.TypExprList to TypArgList!!! Why did I wrote this? */
+    parseCommaDelimitedRegion(
+      ~grammar=Grammar.TypExprList,
+      ~closing=GreaterThan,
+      ~f=parseTypeConstructorArgRegion,
+      p,
+    )
+
+    let () = switch p.token {
+    | Rparen if opening == Token.Lparen =>
+      let typ = Ast_helper.Typ.constr(constrName, typeArgs)
+      let msg =
+        Doc.breakableGroup(
+          ~forceBreak=true,
+          Doc.concat(list{
+            Doc.text("Type parameters require angle brackets:"),
+            Doc.indent(
+              Doc.concat(list{Doc.line, ResPrinter.printTypExpr(typ, CommentTable.empty)}),
+            ),
+          }),
+        ) |> Doc.toString(~width=80)
+
+      Parser.err(~startPos=openingStartPos, p, Diagnostics.message(msg))
+      Parser.next(p)
+    | _ => Parser.expect(GreaterThan, p)
+    }
+
+    Scanner.popMode(p.scanner, Diamond)
+    typeArgs
+  | _ => list{}
+  }
+}
+
+/* string-field-decl ::=
+ *  | string: poly-typexpr
+ *  | attributes string-field-decl */
+and parseStringFieldDeclaration = p => {
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | String(name) =>
+    let nameStartPos = p.startPos
+    let nameEndPos = p.endPos
+    Parser.next(p)
+    let fieldName = Location.mkloc(name, mkLoc(nameStartPos, nameEndPos))
+    Parser.expect(~grammar=Grammar.TypeExpression, Colon, p)
+    let typ = parsePolyTypeExpr(p)
+    Some(Parsetree.Otag(fieldName, attrs, typ))
+  | DotDotDot =>
+    Parser.next(p)
+    let typ = parseTypExpr(p)
+    Some(Parsetree.Oinherit(typ))
+  | Lident(name) =>
+    let nameLoc = mkLoc(p.startPos, p.endPos)
+    Parser.err(p, Diagnostics.message(ErrorMessages.objectQuotedFieldName(name)))
+    Parser.next(p)
+    let fieldName = Location.mkloc(name, nameLoc)
+    Parser.expect(~grammar=Grammar.TypeExpression, Colon, p)
+    let typ = parsePolyTypeExpr(p)
+    Some(Parsetree.Otag(fieldName, attrs, typ))
+  | _token => None
+  }
+}
+
+/* field-decl	::=
+ *  | [mutable] field-name : poly-typexpr
+ *  | attributes field-decl */
+and parseFieldDeclaration = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  let mut = if Parser.optional(p, Token.Mutable) {
+    Asttypes.Mutable
+  } else {
+    Asttypes.Immutable
+  }
+
+  let (lident, loc) = switch p.token {
+  | _ => parseLident(p)
+  }
+
+  let name = Location.mkloc(lident, loc)
+  let typ = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    parsePolyTypeExpr(p)
+  | _ => Ast_helper.Typ.constr(~loc=name.loc, {...name, txt: Lident(name.txt)}, list{})
+  }
+
+  let loc = mkLoc(startPos, typ.ptyp_loc.loc_end)
+  Ast_helper.Type.field(~attrs, ~loc, ~mut, name, typ)
+}
+
+and parseFieldDeclarationRegion = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  let mut = if Parser.optional(p, Token.Mutable) {
+    Asttypes.Mutable
+  } else {
+    Asttypes.Immutable
+  }
+
+  switch p.token {
+  | Lident(_) =>
+    let (lident, loc) = parseLident(p)
+    let name = Location.mkloc(lident, loc)
+    let typ = switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      parsePolyTypeExpr(p)
+    | _ => Ast_helper.Typ.constr(~loc=name.loc, {...name, txt: Lident(name.txt)}, list{})
+    }
+
+    let loc = mkLoc(startPos, typ.ptyp_loc.loc_end)
+    Some(Ast_helper.Type.field(~attrs, ~loc, ~mut, name, typ))
+  | _ => None
+  }
+}
+
+/* record-decl ::=
+ *  | { field-decl }
+ *  | { field-decl, field-decl }
+ *  | { field-decl, field-decl, field-decl, }
+ */
+and parseRecordDeclaration = p => {
+  Parser.leaveBreadcrumb(p, Grammar.RecordDecl)
+  Parser.expect(Lbrace, p)
+  let rows = parseCommaDelimitedRegion(
+    ~grammar=Grammar.RecordDecl,
+    ~closing=Rbrace,
+    ~f=parseFieldDeclarationRegion,
+    p,
+  )
+
+  Parser.expect(Rbrace, p)
+  Parser.eatBreadcrumb(p)
+  rows
+}
+
+/* constr-args ::=
+ *  | (typexpr)
+ *  | (typexpr, typexpr)
+ *  | (typexpr, typexpr, typexpr,)
+ *  | (record-decl)
+ *
+ * TODO: should we overparse inline-records in every position?
+ * Give a good error message afterwards?
+ */
+and parseConstrDeclArgs = p => {
+  let constrArgs = switch p.Parser.token {
+  | Lparen =>
+    Parser.next(p)
+    /* TODO: this could use some cleanup/stratification */
+    switch p.Parser.token {
+    | Lbrace =>
+      let lbrace = p.startPos
+      Parser.next(p)
+      let startPos = p.Parser.startPos
+      switch p.Parser.token {
+      | DotDot | Dot =>
+        let closedFlag = switch p.token {
+        | DotDot =>
+          Parser.next(p)
+          Asttypes.Open
+        | Dot =>
+          Parser.next(p)
+          Asttypes.Closed
+        | _ => Asttypes.Closed
+        }
+
+        let fields = parseCommaDelimitedRegion(
+          ~grammar=Grammar.StringFieldDeclarations,
+          ~closing=Rbrace,
+          ~f=parseStringFieldDeclaration,
+          p,
+        )
+
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let typ = Ast_helper.Typ.object_(~loc, ~attrs=list{}, fields, closedFlag)
+        Parser.optional(p, Comma) |> ignore
+        let moreArgs = parseCommaDelimitedRegion(
+          ~grammar=Grammar.TypExprList,
+          ~closing=Rparen,
+          ~f=parseTypExprRegion,
+          p,
+        )
+
+        Parser.expect(Rparen, p)
+        Parsetree.Pcstr_tuple(list{typ, ...moreArgs})
+      | DotDotDot =>
+        let dotdotdotStart = p.startPos
+        let dotdotdotEnd = p.endPos
+        /* start of object type spreading, e.g. `User({...a, "u": int})` */
+        Parser.next(p)
+        let typ = parseTypExpr(p)
+        let () = switch p.token {
+        | Rbrace =>
+          /* {...x}, spread without extra fields */
+          Parser.err(
+            ~startPos=dotdotdotStart,
+            ~endPos=dotdotdotEnd,
+            p,
+            Diagnostics.message(ErrorMessages.sameTypeSpread),
+          )
+          Parser.next(p)
+        | _ => Parser.expect(Comma, p)
+        }
+
+        let () = switch p.token {
+        | Lident(_) =>
+          Parser.err(
+            ~startPos=dotdotdotStart,
+            ~endPos=dotdotdotEnd,
+            p,
+            Diagnostics.message(ErrorMessages.spreadInRecordDeclaration),
+          )
+        | _ => ()
+        }
+
+        let fields = list{
+          Parsetree.Oinherit(typ),
+          ...parseCommaDelimitedRegion(
+            ~grammar=Grammar.StringFieldDeclarations,
+            ~closing=Rbrace,
+            ~f=parseStringFieldDeclaration,
+            p,
+          ),
+        }
+
+        Parser.expect(Rbrace, p)
+        let loc = mkLoc(startPos, p.prevEndPos)
+        let typ = Ast_helper.Typ.object_(~loc, fields, Asttypes.Closed) |> parseTypeAlias(p)
+
+        let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+        Parser.optional(p, Comma) |> ignore
+        let moreArgs = parseCommaDelimitedRegion(
+          ~grammar=Grammar.TypExprList,
+          ~closing=Rparen,
+          ~f=parseTypExprRegion,
+          p,
+        )
+
+        Parser.expect(Rparen, p)
+        Parsetree.Pcstr_tuple(list{typ, ...moreArgs})
+      | _ =>
+        let attrs = parseAttributes(p)
+        switch p.Parser.token {
+        | String(_) =>
+          let closedFlag = Asttypes.Closed
+          let fields = switch attrs {
+          | list{} =>
+            parseCommaDelimitedRegion(
+              ~grammar=Grammar.StringFieldDeclarations,
+              ~closing=Rbrace,
+              ~f=parseStringFieldDeclaration,
+              p,
+            )
+          | attrs =>
+            let first = {
+              Parser.leaveBreadcrumb(p, Grammar.StringFieldDeclarations)
+              let field = switch parseStringFieldDeclaration(p) {
+              | Some(field) => field
+              | None => assert false
+              }
+
+              /* parse comma after first */
+              let () = switch p.Parser.token {
+              | Rbrace | Eof => ()
+              | Comma => Parser.next(p)
+              | _ => Parser.expect(Comma, p)
+              }
+
+              Parser.eatBreadcrumb(p)
+              switch field {
+              | Parsetree.Otag(label, _, ct) => Parsetree.Otag(label, attrs, ct)
+              | Oinherit(ct) => Oinherit(ct)
+              }
+            }
+
+            list{
+              first,
+              ...parseCommaDelimitedRegion(
+                ~grammar=Grammar.StringFieldDeclarations,
+                ~closing=Rbrace,
+                ~f=parseStringFieldDeclaration,
+                p,
+              ),
+            }
+          }
+          Parser.expect(Rbrace, p)
+          let loc = mkLoc(startPos, p.prevEndPos)
+          let typ =
+            Ast_helper.Typ.object_(~loc, ~attrs=list{}, fields, closedFlag) |> parseTypeAlias(p)
+
+          let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+          Parser.optional(p, Comma) |> ignore
+          let moreArgs = parseCommaDelimitedRegion(
+            ~grammar=Grammar.TypExprList,
+            ~closing=Rparen,
+            ~f=parseTypExprRegion,
+            p,
+          )
+
+          Parser.expect(Rparen, p)
+          Parsetree.Pcstr_tuple(list{typ, ...moreArgs})
+        | _ =>
+          let fields = switch attrs {
+          | list{} =>
+            parseCommaDelimitedRegion(
+              ~grammar=Grammar.FieldDeclarations,
+              ~closing=Rbrace,
+              ~f=parseFieldDeclarationRegion,
+              p,
+            )
+          | attrs =>
+            let first = {
+              let field = parseFieldDeclaration(p)
+              Parser.expect(Comma, p)
+              {...field, Parsetree.pld_attributes: attrs}
+            }
+
+            list{
+              first,
+              ...parseCommaDelimitedRegion(
+                ~grammar=Grammar.FieldDeclarations,
+                ~closing=Rbrace,
+                ~f=parseFieldDeclarationRegion,
+                p,
+              ),
+            }
+          }
+
+          let () = switch fields {
+          | list{} =>
+            Parser.err(
+              ~startPos=lbrace,
+              p,
+              Diagnostics.message("An inline record declaration needs at least one field"),
+            )
+          | _ => ()
+          }
+
+          Parser.expect(Rbrace, p)
+          Parser.optional(p, Comma) |> ignore
+          Parser.expect(Rparen, p)
+          Parsetree.Pcstr_record(fields)
+        }
+      }
+    | _ =>
+      let args = parseCommaDelimitedRegion(
+        ~grammar=Grammar.TypExprList,
+        ~closing=Rparen,
+        ~f=parseTypExprRegion,
+        p,
+      )
+
+      Parser.expect(Rparen, p)
+      Parsetree.Pcstr_tuple(args)
+    }
+  | _ => Pcstr_tuple(list{})
+  }
+
+  let res = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    Some(parseTypExpr(p))
+  | _ => None
+  }
+
+  (constrArgs, res)
+}
+
+/* constr-decl ::=
+ *  | constr-name
+ *  | attrs constr-name
+ *  | constr-name const-args
+ *  | attrs constr-name const-args */
+and parseTypeConstructorDeclarationWithBar = p =>
+  switch p.Parser.token {
+  | Bar =>
+    let startPos = p.Parser.startPos
+    Parser.next(p)
+    Some(parseTypeConstructorDeclaration(~startPos, p))
+  | _ => None
+  }
+
+and parseTypeConstructorDeclaration = (~startPos, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.ConstructorDeclaration)
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Uident(uident) =>
+    let uidentLoc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    let (args, res) = parseConstrDeclArgs(p)
+    Parser.eatBreadcrumb(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Type.constructor(~loc, ~attrs, ~res?, ~args, Location.mkloc(uident, uidentLoc))
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Ast_helper.Type.constructor(Location.mknoloc("_"))
+  }
+}
+
+/* [|] constr-decl  { | constr-decl } */
+and parseTypeConstructorDeclarations = (~first=?, p) => {
+  let firstConstrDecl = switch first {
+  | None =>
+    let startPos = p.Parser.startPos
+    ignore(Parser.optional(p, Token.Bar))
+    parseTypeConstructorDeclaration(~startPos, p)
+  | Some(firstConstrDecl) => firstConstrDecl
+  }
+
+  list{
+    firstConstrDecl,
+    ...parseRegion(
+      ~grammar=Grammar.ConstructorDeclaration,
+      ~f=parseTypeConstructorDeclarationWithBar,
+      p,
+    ),
+  }
+}
+
+/*
+ * type-representation ::=
+ *  ∣	 = [ | ] constr-decl  { | constr-decl }
+ *  ∣	 = private [ | ] constr-decl  { | constr-decl }
+ *  |  = |
+ *  ∣	 = private |
+ *  ∣	 = record-decl
+ *  ∣	 = private record-decl
+ *  |  = ..
+ */
+and parseTypeRepresentation = p => {
+  Parser.leaveBreadcrumb(p, Grammar.TypeRepresentation)
+  /* = consumed */
+  let privateFlag = if Parser.optional(p, Token.Private) {
+    Asttypes.Private
+  } else {
+    Asttypes.Public
+  }
+
+  let kind = switch p.Parser.token {
+  | Bar | Uident(_) => Parsetree.Ptype_variant(parseTypeConstructorDeclarations(p))
+  | Lbrace => Parsetree.Ptype_record(parseRecordDeclaration(p))
+  | DotDot =>
+    Parser.next(p)
+    Ptype_open
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    /* TODO: I have no idea if this is even remotely a good idea */
+    Parsetree.Ptype_variant(list{})
+  }
+
+  Parser.eatBreadcrumb(p)
+  (privateFlag, kind)
+}
+
+/* type-param	::=
+ *  | variance 'lident
+ *  | variance 'uident
+ *  | variance _
+ *
+ * variance ::=
+ *   | +
+ *   | -
+ *   | (* empty *)
+ */
+and parseTypeParam = p => {
+  let variance = switch p.Parser.token {
+  | Plus =>
+    Parser.next(p)
+    Asttypes.Covariant
+  | Minus =>
+    Parser.next(p)
+    Contravariant
+  | _ => Invariant
+  }
+
+  switch p.Parser.token {
+  | SingleQuote =>
+    Parser.next(p)
+    let (ident, loc) = parseIdent(~msg=ErrorMessages.typeParam, ~startPos=p.startPos, p)
+    Some(Ast_helper.Typ.var(~loc, ident), variance)
+  | Underscore =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Some(Ast_helper.Typ.any(~loc, ()), variance)
+  | (Uident(_) | Lident(_)) as token =>
+    Parser.err(
+      p,
+      Diagnostics.message("Type params start with a singlequote: '" ++ Token.toString(token)),
+    )
+    let (ident, loc) = parseIdent(~msg=ErrorMessages.typeParam, ~startPos=p.startPos, p)
+    Some(Ast_helper.Typ.var(~loc, ident), variance)
+  | _token => None
+  }
+}
+
+/* type-params	::=
+ *  | <type-param>
+ *  ∣	<type-param, type-param>
+ *  ∣	<type-param, type-param, type-param>
+ *  ∣	<type-param, type-param, type-param,>
+ *
+ *  TODO: when we have pretty-printer show an error
+ *  with the actual code corrected. */
+and parseTypeParams = (~parent, p) => {
+  let opening = p.Parser.token
+  switch opening {
+  | LessThan | Lparen if p.startPos.pos_lnum === p.prevEndPos.pos_lnum =>
+    Scanner.setDiamondMode(p.scanner)
+    let openingStartPos = p.startPos
+    Parser.leaveBreadcrumb(p, Grammar.TypeParams)
+    Parser.next(p)
+    let params = parseCommaDelimitedRegion(
+      ~grammar=Grammar.TypeParams,
+      ~closing=GreaterThan,
+      ~f=parseTypeParam,
+      p,
+    )
+
+    let () = switch p.token {
+    | Rparen if opening == Token.Lparen =>
+      let msg =
+        Doc.breakableGroup(
+          ~forceBreak=true,
+          Doc.concat(list{
+            Doc.text("Type parameters require angle brackets:"),
+            Doc.indent(
+              Doc.concat(list{
+                Doc.line,
+                Doc.concat(list{
+                  ResPrinter.printLongident(parent.Location.txt),
+                  ResPrinter.printTypeParams(params, CommentTable.empty),
+                }),
+              }),
+            ),
+          }),
+        ) |> Doc.toString(~width=80)
+
+      Parser.err(~startPos=openingStartPos, p, Diagnostics.message(msg))
+      Parser.next(p)
+    | _ => Parser.expect(GreaterThan, p)
+    }
+
+    Scanner.popMode(p.scanner, Diamond)
+    Parser.eatBreadcrumb(p)
+    params
+  | _ => list{}
+  }
+}
+
+/* type-constraint	::=	constraint ' ident =  typexpr */
+and parseTypeConstraint = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Token.Constraint =>
+    Parser.next(p)
+    Parser.expect(SingleQuote, p)
+    switch p.Parser.token {
+    | Lident(ident) =>
+      let identLoc = mkLoc(startPos, p.endPos)
+      Parser.next(p)
+      Parser.expect(Equal, p)
+      let typ = parseTypExpr(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Some(Ast_helper.Typ.var(~loc=identLoc, ident), typ, loc)
+    | t =>
+      Parser.err(p, Diagnostics.lident(t))
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Some(Ast_helper.Typ.any(), parseTypExpr(p), loc)
+    }
+  | _ => None
+  }
+}
+
+/* type-constraints ::=
+ *  | (* empty *)
+ *  | type-constraint
+ *  | type-constraint type-constraint
+ *  | type-constraint type-constraint type-constraint (* 0 or more *)
+ */
+and parseTypeConstraints = p =>
+  parseRegion(~grammar=Grammar.TypeConstraint, ~f=parseTypeConstraint, p)
+
+and parseTypeEquationOrConstrDecl = p => {
+  let uidentStartPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Uident(uident) =>
+    Parser.next(p)
+    switch p.Parser.token {
+    | Dot =>
+      Parser.next(p)
+      let typeConstr = parseValuePathTail(p, uidentStartPos, Longident.Lident(uident))
+
+      let loc = mkLoc(uidentStartPos, p.prevEndPos)
+      let typ = parseTypeAlias(
+        p,
+        Ast_helper.Typ.constr(
+          ~loc,
+          typeConstr,
+          parseTypeConstructorArgs(~constrName=typeConstr, p),
+        ),
+      )
+      switch p.token {
+      | Equal =>
+        Parser.next(p)
+        let (priv, kind) = parseTypeRepresentation(p)
+        (Some(typ), priv, kind)
+      | EqualGreater =>
+        Parser.next(p)
+        let returnType = parseTypExpr(~alias=false, p)
+        let loc = mkLoc(uidentStartPos, p.prevEndPos)
+        let arrowType = Ast_helper.Typ.arrow(~loc, Asttypes.Nolabel, typ, returnType)
+        let typ = parseTypeAlias(p, arrowType)
+        (Some(typ), Asttypes.Public, Parsetree.Ptype_abstract)
+      | _ => (Some(typ), Asttypes.Public, Parsetree.Ptype_abstract)
+      }
+    | _ =>
+      let uidentEndPos = p.prevEndPos
+      let (args, res) = parseConstrDeclArgs(p)
+      let first = Some({
+        let uidentLoc = mkLoc(uidentStartPos, uidentEndPos)
+        Ast_helper.Type.constructor(
+          ~loc=mkLoc(uidentStartPos, p.prevEndPos),
+          ~res?,
+          ~args,
+          Location.mkloc(uident, uidentLoc),
+        )
+      })
+      (None, Asttypes.Public, Parsetree.Ptype_variant(parseTypeConstructorDeclarations(p, ~first?)))
+    }
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    /* TODO: is this a good idea? */
+    (None, Asttypes.Public, Parsetree.Ptype_abstract)
+  }
+}
+
+and parseRecordOrObjectDecl = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lbrace, p)
+  switch p.Parser.token {
+  | DotDot | Dot =>
+    let closedFlag = switch p.token {
+    | DotDot =>
+      Parser.next(p)
+      Asttypes.Open
+    | Dot =>
+      Parser.next(p)
+      Asttypes.Closed
+    | _ => Asttypes.Closed
+    }
+
+    let fields = parseCommaDelimitedRegion(
+      ~grammar=Grammar.StringFieldDeclarations,
+      ~closing=Rbrace,
+      ~f=parseStringFieldDeclaration,
+      p,
+    )
+
+    Parser.expect(Rbrace, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let typ = Ast_helper.Typ.object_(~loc, ~attrs=list{}, fields, closedFlag) |> parseTypeAlias(p)
+
+    let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+    (Some(typ), Asttypes.Public, Parsetree.Ptype_abstract)
+  | DotDotDot =>
+    let dotdotdotStart = p.startPos
+    let dotdotdotEnd = p.endPos
+    /* start of object type spreading, e.g. `type u = {...a, "u": int}` */
+    Parser.next(p)
+    let typ = parseTypExpr(p)
+    let () = switch p.token {
+    | Rbrace =>
+      /* {...x}, spread without extra fields */
+      Parser.err(
+        ~startPos=dotdotdotStart,
+        ~endPos=dotdotdotEnd,
+        p,
+        Diagnostics.message(ErrorMessages.sameTypeSpread),
+      )
+      Parser.next(p)
+    | _ => Parser.expect(Comma, p)
+    }
+
+    let () = switch p.token {
+    | Lident(_) =>
+      Parser.err(
+        ~startPos=dotdotdotStart,
+        ~endPos=dotdotdotEnd,
+        p,
+        Diagnostics.message(ErrorMessages.spreadInRecordDeclaration),
+      )
+    | _ => ()
+    }
+
+    let fields = list{
+      Parsetree.Oinherit(typ),
+      ...parseCommaDelimitedRegion(
+        ~grammar=Grammar.StringFieldDeclarations,
+        ~closing=Rbrace,
+        ~f=parseStringFieldDeclaration,
+        p,
+      ),
+    }
+
+    Parser.expect(Rbrace, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let typ = Ast_helper.Typ.object_(~loc, fields, Asttypes.Closed) |> parseTypeAlias(p)
+
+    let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+    (Some(typ), Asttypes.Public, Parsetree.Ptype_abstract)
+  | _ =>
+    let attrs = parseAttributes(p)
+    switch p.Parser.token {
+    | String(_) =>
+      let closedFlag = Asttypes.Closed
+      let fields = switch attrs {
+      | list{} =>
+        parseCommaDelimitedRegion(
+          ~grammar=Grammar.StringFieldDeclarations,
+          ~closing=Rbrace,
+          ~f=parseStringFieldDeclaration,
+          p,
+        )
+      | attrs =>
+        let first = {
+          Parser.leaveBreadcrumb(p, Grammar.StringFieldDeclarations)
+          let field = switch parseStringFieldDeclaration(p) {
+          | Some(field) => field
+          | None => assert false
+          }
+
+          /* parse comma after first */
+          let () = switch p.Parser.token {
+          | Rbrace | Eof => ()
+          | Comma => Parser.next(p)
+          | _ => Parser.expect(Comma, p)
+          }
+
+          Parser.eatBreadcrumb(p)
+          switch field {
+          | Parsetree.Otag(label, _, ct) => Parsetree.Otag(label, attrs, ct)
+          | Oinherit(ct) => Oinherit(ct)
+          }
+        }
+
+        list{
+          first,
+          ...parseCommaDelimitedRegion(
+            ~grammar=Grammar.StringFieldDeclarations,
+            ~closing=Rbrace,
+            ~f=parseStringFieldDeclaration,
+            p,
+          ),
+        }
+      }
+
+      Parser.expect(Rbrace, p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let typ = Ast_helper.Typ.object_(~loc, ~attrs=list{}, fields, closedFlag) |> parseTypeAlias(p)
+
+      let typ = parseArrowTypeRest(~es6Arrow=true, ~startPos, typ, p)
+      (Some(typ), Asttypes.Public, Parsetree.Ptype_abstract)
+    | _ =>
+      Parser.leaveBreadcrumb(p, Grammar.RecordDecl)
+      let fields = switch attrs {
+      | list{} =>
+        parseCommaDelimitedRegion(
+          ~grammar=Grammar.FieldDeclarations,
+          ~closing=Rbrace,
+          ~f=parseFieldDeclarationRegion,
+          p,
+        )
+      | list{attr, ..._} as attrs =>
+        let first = {
+          let field = parseFieldDeclaration(p)
+          Parser.optional(p, Comma) |> ignore
+          {
+            ...field,
+            Parsetree.pld_attributes: attrs,
+            pld_loc: {
+              ...field.Parsetree.pld_loc,
+              loc_start: (attr |> fst).loc.loc_start,
+            },
+          }
+        }
+
+        list{
+          first,
+          ...parseCommaDelimitedRegion(
+            ~grammar=Grammar.FieldDeclarations,
+            ~closing=Rbrace,
+            ~f=parseFieldDeclarationRegion,
+            p,
+          ),
+        }
+      }
+
+      let () = switch fields {
+      | list{} => Parser.err(~startPos, p, Diagnostics.message("A record needs at least one field"))
+      | _ => ()
+      }
+
+      Parser.expect(Rbrace, p)
+      Parser.eatBreadcrumb(p)
+      (None, Asttypes.Public, Parsetree.Ptype_record(fields))
+    }
+  }
+}
+
+and parsePrivateEqOrRepr = p => {
+  Parser.expect(Private, p)
+  switch p.Parser.token {
+  | Lbrace =>
+    let (manifest, _, kind) = parseRecordOrObjectDecl(p)
+    (manifest, Asttypes.Private, kind)
+  | Uident(_) =>
+    let (manifest, _, kind) = parseTypeEquationOrConstrDecl(p)
+    (manifest, Asttypes.Private, kind)
+  | Bar | DotDot =>
+    let (_, kind) = parseTypeRepresentation(p)
+    (None, Asttypes.Private, kind)
+  | t if Grammar.isTypExprStart(t) => (
+      Some(parseTypExpr(p)),
+      Asttypes.Private,
+      Parsetree.Ptype_abstract,
+    )
+  | _ =>
+    let (_, kind) = parseTypeRepresentation(p)
+    (None, Asttypes.Private, kind)
+  }
+}
+
+/*
+  polymorphic-variant-type	::=
+                            | [ tag-spec-first  { | tag-spec } ]
+                            | [> [ tag-spec ]  { | tag-spec } ]
+                            | [< [|] tag-spec-full  { | tag-spec-full }  [ > { `tag-name }+ ] ]
+
+            tag-spec-first	::=	`tag-name  [ of typexpr ]
+                            |	[ typexpr ] |  tag-spec
+
+                  tag-spec	::=	`tag-name  [ of typexpr ]
+                            |	typexpr
+
+              tag-spec-full	::=	`tag-name  [ of [&] typexpr  { & typexpr } ]
+                             |	typexpr
+*/
+and parsePolymorphicVariantType = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lbracket, p)
+  switch p.token {
+  | GreaterThan =>
+    Parser.next(p)
+    let rowFields = switch p.token {
+    | Rbracket => list{}
+    | Bar => parseTagSpecs(p)
+    | _ =>
+      let rowField = parseTagSpec(p)
+      list{rowField, ...parseTagSpecs(p)}
+    }
+
+    let variant = {
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Typ.variant(~attrs, ~loc, rowFields, Open, None)
+    }
+    Parser.expect(Rbracket, p)
+    variant
+  | LessThan =>
+    Parser.next(p)
+    Parser.optional(p, Bar) |> ignore
+    let rowField = parseTagSpecFull(p)
+    let rowFields = parseTagSpecFulls(p)
+    let tagNames = parseTagNames(p)
+    let variant = {
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Typ.variant(~attrs, ~loc, list{rowField, ...rowFields}, Closed, Some(tagNames))
+    }
+    Parser.expect(Rbracket, p)
+    variant
+  | _ =>
+    let rowFields1 = parseTagSpecFirst(p)
+    let rowFields2 = parseTagSpecs(p)
+    let variant = {
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Typ.variant(~attrs, ~loc, \"@"(rowFields1, rowFields2), Closed, None)
+    }
+    Parser.expect(Rbracket, p)
+    variant
+  }
+}
+
+and parseTagName = p =>
+  switch p.Parser.token {
+  | Hash =>
+    let (ident, _loc) = parseHashIdent(~startPos=p.startPos, p)
+    Some(ident)
+  | _ => None
+  }
+
+and parseTagNames = p =>
+  if p.Parser.token === GreaterThan {
+    Parser.next(p)
+    parseRegion(p, ~grammar=Grammar.TagNames, ~f=parseTagName)
+  } else {
+    list{}
+  }
+
+and parseTagSpecFulls = p =>
+  switch p.Parser.token {
+  | Rbracket => list{}
+  | GreaterThan => list{}
+  | Bar =>
+    Parser.next(p)
+    let rowField = parseTagSpecFull(p)
+    list{rowField, ...parseTagSpecFulls(p)}
+  | _ => list{}
+  }
+
+and parseTagSpecFull = p => {
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Hash => parsePolymorphicVariantTypeSpecHash(~attrs, ~full=true, p)
+  | _ =>
+    let typ = parseTypExpr(~attrs, p)
+    Parsetree.Rinherit(typ)
+  }
+}
+
+and parseTagSpecs = p =>
+  switch p.Parser.token {
+  | Bar =>
+    Parser.next(p)
+    let rowField = parseTagSpec(p)
+    list{rowField, ...parseTagSpecs(p)}
+  | _ => list{}
+  }
+
+and parseTagSpec = p => {
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Hash => parsePolymorphicVariantTypeSpecHash(~attrs, ~full=false, p)
+  | _ =>
+    let typ = parseTypExpr(~attrs, p)
+    Parsetree.Rinherit(typ)
+  }
+}
+
+and parseTagSpecFirst = p => {
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Bar =>
+    Parser.next(p)
+    list{parseTagSpec(p)}
+  | Hash => list{parsePolymorphicVariantTypeSpecHash(~attrs, ~full=false, p)}
+  | _ =>
+    let typ = parseTypExpr(~attrs, p)
+    switch p.token {
+    | Rbracket => /* example: [ListStyleType.t] */
+      list{Parsetree.Rinherit(typ)}
+    | _ =>
+      Parser.expect(Bar, p)
+      list{Parsetree.Rinherit(typ), parseTagSpec(p)}
+    }
+  }
+}
+
+and parsePolymorphicVariantTypeSpecHash = (~attrs, ~full, p): Parsetree.row_field => {
+  let startPos = p.Parser.startPos
+  let (ident, loc) = parseHashIdent(~startPos, p)
+  let rec loop = p =>
+    switch p.Parser.token {
+    | Band if full =>
+      Parser.next(p)
+      let rowField = parsePolymorphicVariantTypeArgs(p)
+      list{rowField, ...loop(p)}
+    | _ => list{}
+    }
+
+  let (firstTuple, tagContainsAConstantEmptyConstructor) = switch p.Parser.token {
+  | Band if full =>
+    Parser.next(p)
+    (list{parsePolymorphicVariantTypeArgs(p)}, true)
+  | Lparen => (list{parsePolymorphicVariantTypeArgs(p)}, false)
+  | _ => (list{}, true)
+  }
+
+  let tuples = \"@"(firstTuple, loop(p))
+  Parsetree.Rtag(Location.mkloc(ident, loc), attrs, tagContainsAConstantEmptyConstructor, tuples)
+}
+
+and parsePolymorphicVariantTypeArgs = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lparen, p)
+  let args = parseCommaDelimitedRegion(
+    ~grammar=Grammar.TypExprList,
+    ~closing=Rparen,
+    ~f=parseTypExprRegion,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  let attrs = list{}
+  let loc = mkLoc(startPos, p.prevEndPos)
+  switch args {
+  | list{{ptyp_desc: Ptyp_tuple(_)} as typ} as types =>
+    if p.mode == ParseForTypeChecker {
+      typ
+    } else {
+      Ast_helper.Typ.tuple(~loc, ~attrs, types)
+    }
+  | list{typ} => typ
+  | types => Ast_helper.Typ.tuple(~loc, ~attrs, types)
+  }
+}
+
+and parseTypeEquationAndRepresentation = p =>
+  switch p.Parser.token {
+  | (Equal | Bar) as token =>
+    if token == Bar {
+      Parser.expect(Equal, p)
+    }
+    Parser.next(p)
+    switch p.Parser.token {
+    | Uident(_) => parseTypeEquationOrConstrDecl(p)
+    | Lbrace => parseRecordOrObjectDecl(p)
+    | Private => parsePrivateEqOrRepr(p)
+    | Bar | DotDot =>
+      let (priv, kind) = parseTypeRepresentation(p)
+      (None, priv, kind)
+    | _ =>
+      let manifest = Some(parseTypExpr(p))
+      switch p.Parser.token {
+      | Equal =>
+        Parser.next(p)
+        let (priv, kind) = parseTypeRepresentation(p)
+        (manifest, priv, kind)
+      | _ => (manifest, Public, Parsetree.Ptype_abstract)
+      }
+    }
+  | _ => (None, Public, Parsetree.Ptype_abstract)
+  }
+
+/* type-definition	::=	type [rec] typedef  { and typedef }
+ * typedef	::=	typeconstr-name [type-params] type-information
+ * type-information	::=	[type-equation]  [type-representation]  { type-constraint }
+ * type-equation	::=	= typexpr */
+and parseTypeDef = (~attrs, ~startPos, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.TypeDef)
+  /* let attrs = match attrs with | Some attrs -> attrs | None -> parseAttributes p in */
+  Parser.leaveBreadcrumb(p, Grammar.TypeConstrName)
+  let (name, loc) = parseLident(p)
+  let typeConstrName = Location.mkloc(name, loc)
+  Parser.eatBreadcrumb(p)
+  let params = {
+    let constrName = Location.mkloc(Longident.Lident(name), loc)
+    parseTypeParams(~parent=constrName, p)
+  }
+  let typeDef = {
+    let (manifest, priv, kind) = parseTypeEquationAndRepresentation(p)
+    let cstrs = parseTypeConstraints(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Type.mk(~loc, ~attrs, ~priv, ~kind, ~params, ~cstrs, ~manifest?, typeConstrName)
+  }
+
+  Parser.eatBreadcrumb(p)
+  typeDef
+}
+
+and parseTypeExtension = (~params, ~attrs, ~name, p) => {
+  Parser.expect(PlusEqual, p)
+  let priv = if Parser.optional(p, Token.Private) {
+    Asttypes.Private
+  } else {
+    Asttypes.Public
+  }
+
+  let constrStart = p.Parser.startPos
+  Parser.optional(p, Bar) |> ignore
+  let first = {
+    let (attrs, name, kind) = switch p.Parser.token {
+    | Bar =>
+      Parser.next(p)
+      parseConstrDef(~parseAttrs=true, p)
+    | _ => parseConstrDef(~parseAttrs=true, p)
+    }
+
+    let loc = mkLoc(constrStart, p.prevEndPos)
+    Ast_helper.Te.constructor(~loc, ~attrs, name, kind)
+  }
+
+  let rec loop = (p, cs) =>
+    switch p.Parser.token {
+    | Bar =>
+      let startPos = p.Parser.startPos
+      Parser.next(p)
+      let (attrs, name, kind) = parseConstrDef(~parseAttrs=true, p)
+      let extConstr = Ast_helper.Te.constructor(
+        ~attrs,
+        ~loc=mkLoc(startPos, p.prevEndPos),
+        name,
+        kind,
+      )
+
+      loop(p, list{extConstr, ...cs})
+    | _ => List.rev(cs)
+    }
+
+  let constructors = loop(p, list{first})
+  Ast_helper.Te.mk(~attrs, ~params, ~priv, name, constructors)
+}
+
+and parseTypeDefinitions = (~attrs, ~name, ~params, ~startPos, p) => {
+  let typeDef = {
+    let (manifest, priv, kind) = parseTypeEquationAndRepresentation(p)
+    let cstrs = parseTypeConstraints(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Type.mk(
+      ~loc,
+      ~attrs,
+      ~priv,
+      ~kind,
+      ~params,
+      ~cstrs,
+      ~manifest?,
+      {...name, txt: lidentOfPath(name.Location.txt)},
+    )
+  }
+
+  let rec loop = (p, defs) => {
+    let startPos = p.Parser.startPos
+    let attrs = parseAttributesAndBinding(p)
+    switch p.Parser.token {
+    | And =>
+      Parser.next(p)
+      let attrs = switch p.token {
+      | Export =>
+        let exportLoc = mkLoc(p.startPos, p.endPos)
+        Parser.next(p)
+        let genTypeAttr = (Location.mkloc("genType", exportLoc), Parsetree.PStr(list{}))
+        list{genTypeAttr, ...attrs}
+      | _ => attrs
+      }
+
+      let typeDef = parseTypeDef(~attrs, ~startPos, p)
+      loop(p, list{typeDef, ...defs})
+    | _ => List.rev(defs)
+    }
+  }
+
+  loop(p, list{typeDef})
+}
+
+/* TODO: decide if we really want type extensions (eg. type x += Blue)
+ * It adds quite a bit of complexity that can be avoided,
+ * implemented for now. Needed to get a feel for the complexities of
+ * this territory of the grammar */
+and parseTypeDefinitionOrExtension = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Token.Typ, p)
+  let recFlag = switch p.token {
+  | Rec =>
+    Parser.next(p)
+    Asttypes.Recursive
+  | Lident("nonrec") =>
+    Parser.next(p)
+    Asttypes.Nonrecursive
+  | _ => Asttypes.Nonrecursive
+  }
+
+  let name = parseValuePath(p)
+  let params = parseTypeParams(~parent=name, p)
+  switch p.Parser.token {
+  | PlusEqual => TypeExt(parseTypeExtension(~params, ~attrs, ~name, p))
+  | _ =>
+    /* shape of type name should be Lident, i.e. `t` is accepted. `User.t` not */
+    let () = switch name.Location.txt {
+    | Lident(_) => ()
+    | longident =>
+      Parser.err(
+        ~startPos=name.loc.loc_start,
+        ~endPos=name.loc.loc_end,
+        p,
+        longident |> ErrorMessages.typeDeclarationNameLongident |> Diagnostics.message,
+      )
+    }
+
+    let typeDefs = parseTypeDefinitions(~attrs, ~name, ~params, ~startPos, p)
+    TypeDef({recFlag: recFlag, types: typeDefs})
+  }
+}
+
+/* external value-name : typexp = external-declaration */
+and parseExternalDef = (~attrs, ~startPos, p) => {
+  Parser.leaveBreadcrumb(p, Grammar.External)
+  Parser.expect(Token.External, p)
+  let (name, loc) = parseLident(p)
+  let name = Location.mkloc(name, loc)
+  Parser.expect(~grammar=Grammar.TypeExpression, Colon, p)
+  let typExpr = parseTypExpr(p)
+  let equalStart = p.startPos
+  let equalEnd = p.endPos
+  Parser.expect(Equal, p)
+  let prim = switch p.token {
+  | String(s) =>
+    Parser.next(p)
+    list{s}
+  | _ =>
+    Parser.err(
+      ~startPos=equalStart,
+      ~endPos=equalEnd,
+      p,
+      Diagnostics.message(
+        "An external requires the name of the JS value you're referring to, like \"" ++
+        (name.txt ++
+        "\"."),
+      ),
+    )
+    list{}
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  let vb = Ast_helper.Val.mk(~loc, ~attrs, ~prim, name, typExpr)
+  Parser.eatBreadcrumb(p)
+  vb
+}
+
+/* constr-def ::=
+ *  | constr-decl
+ *  | constr-name = constr
+ *
+ *  constr-decl ::= constr-name constr-args
+ *  constr-name ::= uident
+ *  constr      ::= path-uident */
+and parseConstrDef = (~parseAttrs, p) => {
+  let attrs = if parseAttrs {
+    parseAttributes(p)
+  } else {
+    list{}
+  }
+  let name = switch p.Parser.token {
+  | Uident(name) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(name, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  let kind = switch p.Parser.token {
+  | Lparen =>
+    let (args, res) = parseConstrDeclArgs(p)
+    Parsetree.Pext_decl(args, res)
+  | Equal =>
+    Parser.next(p)
+    let longident = parseModuleLongIdent(~lowercase=false, p)
+    Parsetree.Pext_rebind(longident)
+  | Colon =>
+    Parser.next(p)
+    let typ = parseTypExpr(p)
+    Parsetree.Pext_decl(Pcstr_tuple(list{}), Some(typ))
+  | _ => Parsetree.Pext_decl(Pcstr_tuple(list{}), None)
+  }
+
+  (attrs, name, kind)
+}
+
+/*
+ * exception-definition	::=
+ *  | exception constr-decl
+ *  ∣	exception constr-name = constr
+ *
+ *  constr-name ::= uident
+ *  constr ::= long_uident */
+and parseExceptionDef = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Token.Exception, p)
+  let (_, name, kind) = parseConstrDef(~parseAttrs=false, p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Te.constructor(~loc, ~attrs, name, kind)
+}
+
+and parseNewlineOrSemicolonStructure = p =>
+  switch p.Parser.token {
+  | Semicolon => Parser.next(p)
+  | token if Grammar.isStructureItemStart(token) =>
+    if p.prevEndPos.pos_lnum < p.startPos.pos_lnum {
+      ()
+    } else {
+      Parser.err(
+        ~startPos=p.prevEndPos,
+        ~endPos=p.endPos,
+        p,
+        Diagnostics.message(
+          "consecutive statements on a line must be separated by ';' or a newline",
+        ),
+      )
+    }
+  | _ => ()
+  }
+
+@progress((Parser.next, Parser.expect, Parser.checkProgress))
+and parseStructureItemRegion = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Open =>
+    let openDescription = parseOpenDescription(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.open_(~loc, openDescription))
+  | Let =>
+    let (recFlag, letBindings) = parseLetBindings(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.value(~loc, recFlag, letBindings))
+  | Typ =>
+    Parser.beginRegion(p)
+    switch parseTypeDefinitionOrExtension(~attrs, p) {
+    | TypeDef({recFlag, types}) =>
+      parseNewlineOrSemicolonStructure(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Str.type_(~loc, recFlag, types))
+    | TypeExt(ext) =>
+      parseNewlineOrSemicolonStructure(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Str.type_extension(~loc, ext))
+    }
+  | External =>
+    let externalDef = parseExternalDef(~attrs, ~startPos, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.primitive(~loc, externalDef))
+  | Import =>
+    let importDescr = parseJsImport(~startPos, ~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    let structureItem = JsFfi.toParsetree(importDescr)
+    Some({...structureItem, pstr_loc: loc})
+  | Exception =>
+    let exceptionDef = parseExceptionDef(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.exception_(~loc, exceptionDef))
+  | Include =>
+    let includeStatement = parseIncludeStatement(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.include_(~loc, includeStatement))
+  | Export =>
+    let structureItem = parseJsExport(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some({...structureItem, pstr_loc: loc})
+  | Module =>
+    Parser.beginRegion(p)
+    let structureItem = parseModuleOrModuleTypeImplOrPackExpr(~attrs, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Parser.endRegion(p)
+    Some({...structureItem, pstr_loc: loc})
+  | AtAt =>
+    let attr = parseStandaloneAttribute(p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.attribute(~loc, attr))
+  | PercentPercent =>
+    let extension = parseExtension(~moduleLanguage=true, p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Str.extension(~attrs, ~loc, extension))
+  | token if Grammar.isExprStart(token) =>
+    let prevEndPos = p.Parser.endPos
+    let exp = parseExpr(p)
+    parseNewlineOrSemicolonStructure(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Parser.checkProgress(~prevEndPos, ~result=Ast_helper.Str.eval(~loc, ~attrs, exp), p)
+  | _ =>
+    switch attrs {
+    | list{({Asttypes.loc: attrLoc}, _) as attr, ..._} =>
+      Parser.err(
+        ~startPos=attrLoc.loc_start,
+        ~endPos=attrLoc.loc_end,
+        p,
+        Diagnostics.message(ErrorMessages.attributeWithoutNode(attr)),
+      )
+      let expr = parseExpr(p)
+      Some(Ast_helper.Str.eval(~loc=mkLoc(p.startPos, p.prevEndPos), ~attrs, expr))
+    | _ => None
+    }
+  }
+}
+
+and parseJsImport = (~startPos, ~attrs, p) => {
+  Parser.expect(Token.Import, p)
+  let importSpec = switch p.Parser.token {
+  | Token.Lident(_) | Token.At =>
+    let decl = switch parseJsFfiDeclaration(p) {
+    | Some(decl) => decl
+    | None => assert false
+    }
+
+    JsFfi.Default(decl)
+  | _ => JsFfi.Spec(parseJsFfiDeclarations(p))
+  }
+
+  let scope = parseJsFfiScope(p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  JsFfi.importDescr(~attrs, ~importSpec, ~scope, ~loc)
+}
+
+and parseJsExport = (~attrs, p) => {
+  let exportStart = p.Parser.startPos
+  Parser.expect(Token.Export, p)
+  let exportLoc = mkLoc(exportStart, p.prevEndPos)
+  let genTypeAttr = (Location.mkloc("genType", exportLoc), Parsetree.PStr(list{}))
+  let attrs = list{genTypeAttr, ...attrs}
+  switch p.Parser.token {
+  | Typ =>
+    switch parseTypeDefinitionOrExtension(~attrs, p) {
+    | TypeDef({recFlag, types}) => Ast_helper.Str.type_(recFlag, types)
+    | TypeExt(ext) => Ast_helper.Str.type_extension(ext)
+    }
+  /* Let */ | _ =>
+    let (recFlag, letBindings) = parseLetBindings(~attrs, p)
+    Ast_helper.Str.value(recFlag, letBindings)
+  }
+}
+
+and parseSignJsExport = (~attrs, p) => {
+  let exportStart = p.Parser.startPos
+  Parser.expect(Token.Export, p)
+  let exportLoc = mkLoc(exportStart, p.prevEndPos)
+  let genTypeAttr = (Location.mkloc("genType", exportLoc), Parsetree.PStr(list{}))
+  let attrs = list{genTypeAttr, ...attrs}
+  switch p.Parser.token {
+  | Typ =>
+    switch parseTypeDefinitionOrExtension(~attrs, p) {
+    | TypeDef({recFlag, types}) =>
+      let loc = mkLoc(exportStart, p.prevEndPos)
+      Ast_helper.Sig.type_(recFlag, types, ~loc)
+    | TypeExt(ext) =>
+      let loc = mkLoc(exportStart, p.prevEndPos)
+      Ast_helper.Sig.type_extension(ext, ~loc)
+    }
+  /* Let */ | _ =>
+    let valueDesc = parseSignLetDesc(~attrs, p)
+    let loc = mkLoc(exportStart, p.prevEndPos)
+    Ast_helper.Sig.value(valueDesc, ~loc)
+  }
+}
+
+and parseJsFfiScope = p =>
+  switch p.Parser.token {
+  | Token.Lident("from") =>
+    Parser.next(p)
+    switch p.token {
+    | String(s) =>
+      Parser.next(p)
+      JsFfi.Module(s)
+    | Uident(_) | Lident(_) =>
+      let value = parseIdentPath(p)
+      JsFfi.Scope(value)
+    | _ => JsFfi.Global
+    }
+  | _ => JsFfi.Global
+  }
+
+and parseJsFfiDeclarations = p => {
+  Parser.expect(Token.Lbrace, p)
+  let decls = parseCommaDelimitedRegion(
+    ~grammar=Grammar.JsFfiImport,
+    ~closing=Rbrace,
+    ~f=parseJsFfiDeclaration,
+    p,
+  )
+
+  Parser.expect(Rbrace, p)
+  decls
+}
+
+and parseJsFfiDeclaration = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Lident(_) =>
+    let (ident, _) = parseLident(p)
+    let alias = switch p.token {
+    | As =>
+      Parser.next(p)
+      let (ident, _) = parseLident(p)
+      ident
+    | _ => ident
+    }
+
+    Parser.expect(Token.Colon, p)
+    let typ = parseTypExpr(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(JsFfi.decl(~loc, ~alias, ~attrs, ~name=ident, ~typ))
+  | _ => None
+  }
+}
+
+/* include-statement ::= include module-expr */
+and parseIncludeStatement = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Token.Include, p)
+  let modExpr = parseModuleExpr(p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Incl.mk(~loc, ~attrs, modExpr)
+}
+
+and parseAtomicModuleExpr = p => {
+  let startPos = p.Parser.startPos
+  switch p.Parser.token {
+  | Uident(_ident) =>
+    let longident = parseModuleLongIdent(~lowercase=false, p)
+    Ast_helper.Mod.ident(~loc=longident.loc, longident)
+  | Lbrace =>
+    Parser.next(p)
+    let structure = Ast_helper.Mod.structure(
+      parseDelimitedRegion(
+        ~grammar=Grammar.Structure,
+        ~closing=Rbrace,
+        ~f=parseStructureItemRegion,
+        p,
+      ),
+    )
+    Parser.expect(Rbrace, p)
+    let endPos = p.prevEndPos
+    {...structure, pmod_loc: mkLoc(startPos, endPos)}
+  | Lparen =>
+    Parser.next(p)
+    let modExpr = switch p.token {
+    | Rparen => Ast_helper.Mod.structure(~loc=mkLoc(startPos, p.prevEndPos), list{})
+    | _ => parseConstrainedModExpr(p)
+    }
+
+    Parser.expect(Rparen, p)
+    modExpr
+  | Lident("unpack") =>
+    /* TODO: should this be made a keyword?? */
+    Parser.next(p)
+    Parser.expect(Lparen, p)
+    let expr = parseExpr(p)
+    switch p.Parser.token {
+    | Colon =>
+      let colonStart = p.Parser.startPos
+      Parser.next(p)
+      let attrs = parseAttributes(p)
+      let packageType = parsePackageType(~startPos=colonStart, ~attrs, p)
+      Parser.expect(Rparen, p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      let constraintExpr = Ast_helper.Exp.constraint_(~loc, expr, packageType)
+
+      Ast_helper.Mod.unpack(~loc, constraintExpr)
+    | _ =>
+      Parser.expect(Rparen, p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Ast_helper.Mod.unpack(~loc, expr)
+    }
+  | Percent =>
+    let extension = parseExtension(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Mod.extension(~loc, extension)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Recover.defaultModuleExpr()
+  }
+}
+
+and parsePrimaryModExpr = p => {
+  let startPos = p.Parser.startPos
+  let modExpr = parseAtomicModuleExpr(p)
+  let rec loop = (p, modExpr) =>
+    switch p.Parser.token {
+    | Lparen if p.prevEndPos.pos_lnum === p.startPos.pos_lnum =>
+      loop(p, parseModuleApplication(p, modExpr))
+    | _ => modExpr
+    }
+
+  let modExpr = loop(p, modExpr)
+  {...modExpr, pmod_loc: mkLoc(startPos, p.prevEndPos)}
+}
+
+/*
+ * functor-arg ::=
+ *  | uident : modtype
+ *  | _ : modtype
+ *  | modtype           --> "punning" for _ : modtype
+ *  | attributes functor-arg
+ */
+and parseFunctorArg = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Uident(ident) =>
+    Parser.next(p)
+    let uidentEndPos = p.prevEndPos
+    switch p.Parser.token {
+    | Colon =>
+      Parser.next(p)
+      let moduleType = parseModuleType(p)
+      let loc = mkLoc(startPos, uidentEndPos)
+      let argName = Location.mkloc(ident, loc)
+      Some(attrs, argName, Some(moduleType), startPos)
+    | Dot =>
+      Parser.next(p)
+      let moduleType = {
+        let moduleLongIdent = parseModuleLongIdentTail(
+          ~lowercase=false,
+          p,
+          startPos,
+          Longident.Lident(ident),
+        )
+        Ast_helper.Mty.ident(~loc=moduleLongIdent.loc, moduleLongIdent)
+      }
+
+      let argName = Location.mknoloc("_")
+      Some(attrs, argName, Some(moduleType), startPos)
+    | _ =>
+      let loc = mkLoc(startPos, uidentEndPos)
+      let modIdent = Location.mkloc(Longident.Lident(ident), loc)
+      let moduleType = Ast_helper.Mty.ident(~loc, modIdent)
+      let argName = Location.mknoloc("_")
+      Some(attrs, argName, Some(moduleType), startPos)
+    }
+  | Underscore =>
+    Parser.next(p)
+    let argName = Location.mkloc("_", mkLoc(startPos, p.prevEndPos))
+    Parser.expect(Colon, p)
+    let moduleType = parseModuleType(p)
+    Some(attrs, argName, Some(moduleType), startPos)
+  | Lparen =>
+    Parser.next(p)
+    Parser.expect(Rparen, p)
+    let argName = Location.mkloc("*", mkLoc(startPos, p.prevEndPos))
+    Some(attrs, argName, None, startPos)
+  | _ => None
+  }
+}
+
+and parseFunctorArgs = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lparen, p)
+  let args = parseCommaDelimitedRegion(
+    ~grammar=Grammar.FunctorArgs,
+    ~closing=Rparen,
+    ~f=parseFunctorArg,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  switch args {
+  | list{} => list{(list{}, Location.mkloc("*", mkLoc(startPos, p.prevEndPos)), None, startPos)}
+  | args => args
+  }
+}
+
+and parseFunctorModuleExpr = p => {
+  let startPos = p.Parser.startPos
+  let args = parseFunctorArgs(p)
+  let returnType = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    Some(parseModuleType(~es6Arrow=false, p))
+  | _ => None
+  }
+
+  Parser.expect(EqualGreater, p)
+  let rhsModuleExpr = {
+    let modExpr = parseModuleExpr(p)
+    switch returnType {
+    | Some(modType) =>
+      Ast_helper.Mod.constraint_(
+        ~loc=mkLoc(modExpr.pmod_loc.loc_start, modType.Parsetree.pmty_loc.loc_end),
+        modExpr,
+        modType,
+      )
+    | None => modExpr
+    }
+  }
+
+  let endPos = p.prevEndPos
+  let modExpr = List.fold_right(
+    ((attrs, name, moduleType, startPos), acc) =>
+      Ast_helper.Mod.functor_(~loc=mkLoc(startPos, endPos), ~attrs, name, moduleType, acc),
+    args,
+    rhsModuleExpr,
+  )
+
+  {...modExpr, pmod_loc: mkLoc(startPos, endPos)}
+}
+
+/* module-expr	::=
+ *  | module-path
+ *  ∣	{ structure-items }
+ *  ∣	functorArgs =>  module-expr
+ *  ∣	module-expr(module-expr)
+ *  ∣	( module-expr )
+ *  ∣	( module-expr : module-type )
+ *  | extension
+ *  | attributes module-expr */
+and parseModuleExpr = p => {
+  let attrs = parseAttributes(p)
+  let modExpr = if isEs6ArrowFunctor(p) {
+    parseFunctorModuleExpr(p)
+  } else {
+    parsePrimaryModExpr(p)
+  }
+
+  {...modExpr, pmod_attributes: List.concat(list{modExpr.pmod_attributes, attrs})}
+}
+
+and parseConstrainedModExpr = p => {
+  let modExpr = parseModuleExpr(p)
+  switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    let modType = parseModuleType(p)
+    let loc = mkLoc(modExpr.pmod_loc.loc_start, modType.pmty_loc.loc_end)
+    Ast_helper.Mod.constraint_(~loc, modExpr, modType)
+  | _ => modExpr
+  }
+}
+
+and parseConstrainedModExprRegion = p =>
+  if Grammar.isModExprStart(p.Parser.token) {
+    Some(parseConstrainedModExpr(p))
+  } else {
+    None
+  }
+
+and parseModuleApplication = (p, modExpr) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Lparen, p)
+  let args = parseCommaDelimitedRegion(
+    ~grammar=Grammar.ModExprList,
+    ~closing=Rparen,
+    ~f=parseConstrainedModExprRegion,
+    p,
+  )
+
+  Parser.expect(Rparen, p)
+  let args = switch args {
+  | list{} =>
+    let loc = mkLoc(startPos, p.prevEndPos)
+    list{Ast_helper.Mod.structure(~loc, list{})}
+  | args => args
+  }
+
+  List.fold_left(
+    (modExpr, arg) =>
+      Ast_helper.Mod.apply(
+        ~loc=mkLoc(modExpr.Parsetree.pmod_loc.loc_start, arg.Parsetree.pmod_loc.loc_end),
+        modExpr,
+        arg,
+      ),
+    modExpr,
+    args,
+  )
+}
+
+and parseModuleOrModuleTypeImplOrPackExpr = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Module, p)
+  switch p.Parser.token {
+  | Typ => parseModuleTypeImpl(~attrs, startPos, p)
+  | Lparen =>
+    let expr = parseFirstClassModuleExpr(~startPos, p)
+    let a = parsePrimaryExpr(~operand=expr, p)
+    let expr = parseBinaryExpr(~a, p, 1)
+    let expr = parseTernaryExpr(expr, p)
+    Ast_helper.Str.eval(~attrs, expr)
+  | _ => parseMaybeRecModuleBinding(~attrs, ~startPos, p)
+  }
+}
+
+and parseModuleTypeImpl = (~attrs, startPos, p) => {
+  Parser.expect(Typ, p)
+  let nameStart = p.Parser.startPos
+  let name = switch p.Parser.token {
+  | Lident(ident) =>
+    Parser.next(p)
+    let loc = mkLoc(nameStart, p.prevEndPos)
+    Location.mkloc(ident, loc)
+  | Uident(ident) =>
+    Parser.next(p)
+    let loc = mkLoc(nameStart, p.prevEndPos)
+    Location.mkloc(ident, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  Parser.expect(Equal, p)
+  let moduleType = parseModuleType(p)
+  let moduleTypeDeclaration = Ast_helper.Mtd.mk(
+    ~attrs,
+    ~loc=mkLoc(nameStart, p.prevEndPos),
+    ~typ=moduleType,
+    name,
+  )
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Str.modtype(~loc, moduleTypeDeclaration)
+}
+
+/* definition	::=
+  ∣	 module rec module-name :  module-type =  module-expr   { and module-name
+  :  module-type =  module-expr } */
+and parseMaybeRecModuleBinding = (~attrs, ~startPos, p) =>
+  switch p.Parser.token {
+  | Token.Rec =>
+    Parser.next(p)
+    Ast_helper.Str.rec_module(parseModuleBindings(~startPos, ~attrs, p))
+  | _ => Ast_helper.Str.module_(parseModuleBinding(~attrs, ~startPos=p.Parser.startPos, p))
+  }
+
+and parseModuleBinding = (~attrs, ~startPos, p) => {
+  let name = switch p.Parser.token {
+  | Uident(ident) =>
+    let startPos = p.Parser.startPos
+    Parser.next(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Location.mkloc(ident, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  let body = parseModuleBindingBody(p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Mb.mk(~attrs, ~loc, name, body)
+}
+
+and parseModuleBindingBody = p => {
+  /* TODO: make required with good error message when rec module binding */
+  let returnModType = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    Some(parseModuleType(p))
+  | _ => None
+  }
+
+  Parser.expect(Equal, p)
+  let modExpr = parseModuleExpr(p)
+  switch returnModType {
+  | Some(modType) =>
+    Ast_helper.Mod.constraint_(
+      ~loc=mkLoc(modType.pmty_loc.loc_start, modExpr.pmod_loc.loc_end),
+      modExpr,
+      modType,
+    )
+  | None => modExpr
+  }
+}
+
+/* module-name :  module-type =  module-expr
+ * { and module-name :  module-type =  module-expr } */
+and parseModuleBindings = (~attrs, ~startPos, p) => {
+  let rec loop = (p, acc) => {
+    let startPos = p.Parser.startPos
+    let attrs = parseAttributesAndBinding(p)
+    switch p.Parser.token {
+    | And =>
+      Parser.next(p)
+      ignore(Parser.optional(p, Module)) /* over-parse for fault-tolerance */
+      let modBinding = parseModuleBinding(~attrs, ~startPos, p)
+      loop(p, list{modBinding, ...acc})
+    | _ => List.rev(acc)
+    }
+  }
+
+  let first = parseModuleBinding(~attrs, ~startPos, p)
+  loop(p, list{first})
+}
+
+and parseAtomicModuleType = p => {
+  let startPos = p.Parser.startPos
+  let moduleType = switch p.Parser.token {
+  | Uident(_) | Lident(_) =>
+    /* Ocaml allows module types to end with lowercase: module Foo : bar = { ... }
+     * lets go with uppercase terminal for now */
+    let moduleLongIdent = parseModuleLongIdent(~lowercase=true, p)
+    Ast_helper.Mty.ident(~loc=moduleLongIdent.loc, moduleLongIdent)
+  | Lparen =>
+    Parser.next(p)
+    let mty = parseModuleType(p)
+    Parser.expect(Rparen, p)
+    {...mty, pmty_loc: mkLoc(startPos, p.prevEndPos)}
+  | Lbrace =>
+    Parser.next(p)
+    let spec = parseDelimitedRegion(
+      ~grammar=Grammar.Signature,
+      ~closing=Rbrace,
+      ~f=parseSignatureItemRegion,
+      p,
+    )
+
+    Parser.expect(Rbrace, p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Mty.signature(~loc, spec)
+  | Module =>
+    /* TODO: check if this is still atomic when implementing first class modules */
+    parseModuleTypeOf(p)
+  | Percent =>
+    let extension = parseExtension(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Ast_helper.Mty.extension(~loc, extension)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Recover.defaultModuleType()
+  }
+
+  let moduleTypeLoc = mkLoc(startPos, p.prevEndPos)
+  {...moduleType, pmty_loc: moduleTypeLoc}
+}
+
+and parseFunctorModuleType = p => {
+  let startPos = p.Parser.startPos
+  let args = parseFunctorArgs(p)
+  Parser.expect(EqualGreater, p)
+  let rhs = parseModuleType(p)
+  let endPos = p.prevEndPos
+  let modType = List.fold_right(
+    ((attrs, name, moduleType, startPos), acc) =>
+      Ast_helper.Mty.functor_(~loc=mkLoc(startPos, endPos), ~attrs, name, moduleType, acc),
+    args,
+    rhs,
+  )
+
+  {...modType, pmty_loc: mkLoc(startPos, endPos)}
+}
+
+/* Module types are the module-level equivalent of type expressions: they
+ * specify the general shape and type properties of modules.
+ *
+ * module-type ::=
+ *  | modtype-path
+ *  | { signature }
+ *  | ( module-type )               --> parenthesized module-type
+ *  | functor-args => module-type   --> functor
+ *  | module-type => module-type    --> functor
+ *  | module type of module-expr
+ *  | attributes module-type
+ *  | module-type with-mod-constraints
+ *  | extension
+ */
+and parseModuleType = (~es6Arrow=true, ~with_=true, p) => {
+  let attrs = parseAttributes(p)
+  let modty = if es6Arrow && isEs6ArrowFunctor(p) {
+    parseFunctorModuleType(p)
+  } else {
+    let modty = parseAtomicModuleType(p)
+    switch p.Parser.token {
+    | EqualGreater if es6Arrow === true =>
+      Parser.next(p)
+      let rhs = parseModuleType(~with_=false, p)
+      let str = Location.mknoloc("_")
+      let loc = mkLoc(modty.pmty_loc.loc_start, p.prevEndPos)
+      Ast_helper.Mty.functor_(~loc, str, Some(modty), rhs)
+    | _ => modty
+    }
+  }
+
+  let moduleType = {
+    ...modty,
+    pmty_attributes: List.concat(list{modty.pmty_attributes, attrs}),
+  }
+  if with_ {
+    parseWithConstraints(moduleType, p)
+  } else {
+    moduleType
+  }
+}
+
+and parseWithConstraints = (moduleType, p) =>
+  switch p.Parser.token {
+  | Lident("with") =>
+    Parser.next(p)
+    let first = parseWithConstraint(p)
+    let rec loop = (p, acc) =>
+      switch p.Parser.token {
+      | And =>
+        Parser.next(p)
+        loop(p, list{parseWithConstraint(p), ...acc})
+      | _ => List.rev(acc)
+      }
+
+    let constraints = loop(p, list{first})
+    let loc = mkLoc(moduleType.pmty_loc.loc_start, p.prevEndPos)
+    Ast_helper.Mty.with_(~loc, moduleType, constraints)
+  | _ => moduleType
+  }
+
+/* mod-constraint	::=
+ *  |  type typeconstr<type-params> type-equation type-constraints?
+ *  ∣	 type typeconstr-name<type-params> := typexpr
+ *  ∣	 module module-path = extended-module-path
+ *  ∣	 module module-path :=  extended-module-path
+ *
+ *  TODO: split this up into multiple functions, better errors */
+and parseWithConstraint = p =>
+  switch p.Parser.token {
+  | Module =>
+    Parser.next(p)
+    let modulePath = parseModuleLongIdent(~lowercase=false, p)
+    switch p.Parser.token {
+    | ColonEqual =>
+      Parser.next(p)
+      let lident = parseModuleLongIdent(~lowercase=false, p)
+      Parsetree.Pwith_modsubst(modulePath, lident)
+    | Equal =>
+      Parser.next(p)
+      let lident = parseModuleLongIdent(~lowercase=false, p)
+      Parsetree.Pwith_module(modulePath, lident)
+    | token =>
+      /* TODO: revisit */
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      let lident = parseModuleLongIdent(~lowercase=false, p)
+      Parsetree.Pwith_modsubst(modulePath, lident)
+    }
+  | Typ =>
+    Parser.next(p)
+    let typeConstr = parseValuePath(p)
+    let params = parseTypeParams(~parent=typeConstr, p)
+    switch p.Parser.token {
+    | ColonEqual =>
+      Parser.next(p)
+      let typExpr = parseTypExpr(p)
+      Parsetree.Pwith_typesubst(
+        typeConstr,
+        Ast_helper.Type.mk(
+          ~loc=typeConstr.loc,
+          ~params,
+          ~manifest=typExpr,
+          Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc),
+        ),
+      )
+    | Equal =>
+      Parser.next(p)
+      let typExpr = parseTypExpr(p)
+      let typeConstraints = parseTypeConstraints(p)
+      Parsetree.Pwith_type(
+        typeConstr,
+        Ast_helper.Type.mk(
+          ~loc=typeConstr.loc,
+          ~params,
+          ~manifest=typExpr,
+          ~cstrs=typeConstraints,
+          Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc),
+        ),
+      )
+    | token =>
+      /* TODO: revisit */
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      let typExpr = parseTypExpr(p)
+      let typeConstraints = parseTypeConstraints(p)
+      Parsetree.Pwith_type(
+        typeConstr,
+        Ast_helper.Type.mk(
+          ~loc=typeConstr.loc,
+          ~params,
+          ~manifest=typExpr,
+          ~cstrs=typeConstraints,
+          Location.mkloc(Longident.last(typeConstr.txt), typeConstr.loc),
+        ),
+      )
+    }
+  | token =>
+    /* TODO: implement recovery strategy */
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Parsetree.Pwith_type(
+      Location.mknoloc(Longident.Lident("")),
+      Ast_helper.Type.mk(
+        ~params=list{},
+        ~manifest=Recover.defaultType(),
+        ~cstrs=list{},
+        Location.mknoloc(""),
+      ),
+    )
+  }
+
+and parseModuleTypeOf = p => {
+  let startPos = p.Parser.startPos
+  Parser.expect(Module, p)
+  Parser.expect(Typ, p)
+  Parser.expect(Of, p)
+  let moduleExpr = parseModuleExpr(p)
+  Ast_helper.Mty.typeof_(~loc=mkLoc(startPos, p.prevEndPos), moduleExpr)
+}
+
+and parseNewlineOrSemicolonSignature = p =>
+  switch p.Parser.token {
+  | Semicolon => Parser.next(p)
+  | token if Grammar.isSignatureItemStart(token) =>
+    if p.prevEndPos.pos_lnum < p.startPos.pos_lnum {
+      ()
+    } else {
+      Parser.err(
+        ~startPos=p.prevEndPos,
+        ~endPos=p.endPos,
+        p,
+        Diagnostics.message(
+          "consecutive specifications on a line must be separated by ';' or a newline",
+        ),
+      )
+    }
+  | _ => ()
+  }
+
+@progress((Parser.next, Parser.expect, Parser.checkProgress))
+and parseSignatureItemRegion = p => {
+  let startPos = p.Parser.startPos
+  let attrs = parseAttributes(p)
+  switch p.Parser.token {
+  | Let =>
+    Parser.beginRegion(p)
+    let valueDesc = parseSignLetDesc(~attrs, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Parser.endRegion(p)
+    Some(Ast_helper.Sig.value(~loc, valueDesc))
+  | Typ =>
+    Parser.beginRegion(p)
+    switch parseTypeDefinitionOrExtension(~attrs, p) {
+    | TypeDef({recFlag, types}) =>
+      parseNewlineOrSemicolonSignature(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Sig.type_(~loc, recFlag, types))
+    | TypeExt(ext) =>
+      parseNewlineOrSemicolonSignature(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Sig.type_extension(~loc, ext))
+    }
+  | External =>
+    let externalDef = parseExternalDef(~attrs, ~startPos, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.value(~loc, externalDef))
+  | Export =>
+    let signatureItem = parseSignJsExport(~attrs, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some({...signatureItem, psig_loc: loc})
+  | Exception =>
+    let exceptionDef = parseExceptionDef(~attrs, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.exception_(~loc, exceptionDef))
+  | Open =>
+    let openDescription = parseOpenDescription(~attrs, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.open_(~loc, openDescription))
+  | Include =>
+    Parser.next(p)
+    let moduleType = parseModuleType(p)
+    let includeDescription = Ast_helper.Incl.mk(
+      ~loc=mkLoc(startPos, p.prevEndPos),
+      ~attrs,
+      moduleType,
+    )
+
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.include_(~loc, includeDescription))
+  | Module =>
+    Parser.beginRegion(p)
+    Parser.next(p)
+    switch p.Parser.token {
+    | Uident(_) =>
+      let modDecl = parseModuleDeclarationOrAlias(~attrs, p)
+      parseNewlineOrSemicolonSignature(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Sig.module_(~loc, modDecl))
+    | Rec =>
+      let recModule = parseRecModuleSpec(~attrs, ~startPos, p)
+      parseNewlineOrSemicolonSignature(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Sig.rec_module(~loc, recModule))
+    | Typ =>
+      let modTypeDecl = parseModuleTypeDeclaration(~attrs, ~startPos, p)
+      Parser.endRegion(p)
+      Some(modTypeDecl)
+    | _t =>
+      let modDecl = parseModuleDeclarationOrAlias(~attrs, p)
+      parseNewlineOrSemicolonSignature(p)
+      let loc = mkLoc(startPos, p.prevEndPos)
+      Parser.endRegion(p)
+      Some(Ast_helper.Sig.module_(~loc, modDecl))
+    }
+  | AtAt =>
+    let attr = parseStandaloneAttribute(p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.attribute(~loc, attr))
+  | PercentPercent =>
+    let extension = parseExtension(~moduleLanguage=true, p)
+    parseNewlineOrSemicolonSignature(p)
+    let loc = mkLoc(startPos, p.prevEndPos)
+    Some(Ast_helper.Sig.extension(~attrs, ~loc, extension))
+  | Import =>
+    Parser.next(p)
+    parseSignatureItemRegion(p)
+  | _ =>
+    switch attrs {
+    | list{({Asttypes.loc: attrLoc}, _) as attr, ..._} =>
+      Parser.err(
+        ~startPos=attrLoc.loc_start,
+        ~endPos=attrLoc.loc_end,
+        p,
+        Diagnostics.message(ErrorMessages.attributeWithoutNode(attr)),
+      )
+      Some(Recover.defaultSignatureItem)
+    | _ => None
+    }
+  }
+}
+
+/* module rec module-name :  module-type  { and module-name:  module-type } */
+and parseRecModuleSpec = (~attrs, ~startPos, p) => {
+  Parser.expect(Rec, p)
+  let rec loop = (p, spec) => {
+    let startPos = p.Parser.startPos
+    let attrs = parseAttributesAndBinding(p)
+    switch p.Parser.token {
+    | And =>
+      /* TODO: give a good error message when with constraint, no parens
+       * and ASet: (Set.S with type elt = A.t)
+       * and BTree: (Btree.S with type elt = A.t)
+       * Without parens, the `and` signals the start of another
+       * `with-constraint`
+       */
+      Parser.expect(And, p)
+      let decl = parseRecModuleDeclaration(~attrs, ~startPos, p)
+      loop(p, list{decl, ...spec})
+    | _ => List.rev(spec)
+    }
+  }
+
+  let first = parseRecModuleDeclaration(~attrs, ~startPos, p)
+  loop(p, list{first})
+}
+
+/* module-name : module-type */
+and parseRecModuleDeclaration = (~attrs, ~startPos, p) => {
+  let name = switch p.Parser.token {
+  | Uident(modName) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(modName, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  Parser.expect(Colon, p)
+  let modType = parseModuleType(p)
+  Ast_helper.Md.mk(~loc=mkLoc(startPos, p.prevEndPos), ~attrs, name, modType)
+}
+
+and parseModuleDeclarationOrAlias = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  let moduleName = switch p.Parser.token {
+  | Uident(ident) =>
+    let loc = mkLoc(p.Parser.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(ident, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  let body = switch p.Parser.token {
+  | Colon =>
+    Parser.next(p)
+    parseModuleType(p)
+  | Equal =>
+    Parser.next(p)
+    let lident = parseModuleLongIdent(~lowercase=false, p)
+    Ast_helper.Mty.alias(lident)
+  | token =>
+    Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+    Recover.defaultModuleType()
+  }
+
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Md.mk(~loc, ~attrs, moduleName, body)
+}
+
+and parseModuleTypeDeclaration = (~attrs, ~startPos, p) => {
+  Parser.expect(Typ, p)
+  let moduleName = switch p.Parser.token {
+  | Uident(ident) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(ident, loc)
+  | Lident(ident) =>
+    let loc = mkLoc(p.startPos, p.endPos)
+    Parser.next(p)
+    Location.mkloc(ident, loc)
+  | t =>
+    Parser.err(p, Diagnostics.uident(t))
+    Location.mknoloc("_")
+  }
+
+  let typ = switch p.Parser.token {
+  | Equal =>
+    Parser.next(p)
+    Some(parseModuleType(p))
+  | _ => None
+  }
+
+  let moduleDecl = Ast_helper.Mtd.mk(~attrs, ~typ?, moduleName)
+  Ast_helper.Sig.modtype(~loc=mkLoc(startPos, p.prevEndPos), moduleDecl)
+}
+
+and parseSignLetDesc = (~attrs, p) => {
+  let startPos = p.Parser.startPos
+  Parser.optional(p, Let) |> ignore
+  let (name, loc) = parseLident(p)
+  let name = Location.mkloc(name, loc)
+  Parser.expect(Colon, p)
+  let typExpr = parsePolyTypeExpr(p)
+  let loc = mkLoc(startPos, p.prevEndPos)
+  Ast_helper.Val.mk(~loc, ~attrs, name, typExpr)
+}
+
+/* attr-id	::=	lowercase-ident
+∣	  capitalized-ident
+∣	  attr-id .  attr-id */
+and parseAttributeId = (~startPos, p) => {
+  let rec loop = (p, acc) =>
+    switch p.Parser.token {
+    | Lident(ident) | Uident(ident) =>
+      Parser.next(p)
+      let id = acc ++ ident
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        loop(p, id ++ ".")
+      | _ => id
+      }
+    | token if Token.isKeyword(token) =>
+      Parser.next(p)
+      let id = acc ++ Token.toString(token)
+      switch p.Parser.token {
+      | Dot =>
+        Parser.next(p)
+        loop(p, id ++ ".")
+      | _ => id
+      }
+    | token =>
+      Parser.err(p, Diagnostics.unexpected(token, p.breadcrumbs))
+      acc
+    }
+
+  let id = loop(p, "")
+  let endPos = p.prevEndPos
+  Location.mkloc(id, mkLoc(startPos, endPos))
+}
+
+/*
+ * payload ::=  empty
+ *          |  ( structure-item )
+ *
+ * TODO: what about multiple structure items?
+ * @attr({let x = 1; let x = 2})
+ *
+ * Also what about type-expressions and specifications?
+ * @attr(:myType) ???
+ */
+and parsePayload = p =>
+  switch p.Parser.token {
+  | Lparen if p.startPos.pos_cnum == p.prevEndPos.pos_cnum =>
+    Parser.leaveBreadcrumb(p, Grammar.AttributePayload)
+    Parser.next(p)
+    switch p.token {
+    | Colon =>
+      Parser.next(p)
+      let payload = if Grammar.isSignatureItemStart(p.token) {
+        Parsetree.PSig(
+          parseDelimitedRegion(
+            ~grammar=Grammar.Signature,
+            ~closing=Rparen,
+            ~f=parseSignatureItemRegion,
+            p,
+          ),
+        )
+      } else {
+        Parsetree.PTyp(parseTypExpr(p))
+      }
+
+      Parser.expect(Rparen, p)
+      Parser.eatBreadcrumb(p)
+      payload
+    | Question =>
+      Parser.next(p)
+      let pattern = parsePattern(p)
+      let expr = switch p.token {
+      | When | If =>
+        Parser.next(p)
+        Some(parseExpr(p))
+      | _ => None
+      }
+
+      Parser.expect(Rparen, p)
+      Parser.eatBreadcrumb(p)
+      Parsetree.PPat(pattern, expr)
+    | _ =>
+      let items = parseDelimitedRegion(
+        ~grammar=Grammar.Structure,
+        ~closing=Rparen,
+        ~f=parseStructureItemRegion,
+        p,
+      )
+
+      Parser.expect(Rparen, p)
+      Parser.eatBreadcrumb(p)
+      Parsetree.PStr(items)
+    }
+  | _ => Parsetree.PStr(list{})
+  }
+
+/* type attribute = string loc * payload */
+and parseAttribute = p =>
+  switch p.Parser.token {
+  | At =>
+    let startPos = p.startPos
+    Parser.next(p)
+    let attrId = parseAttributeId(~startPos, p)
+    let payload = parsePayload(p)
+    Some(attrId, payload)
+  | _ => None
+  }
+
+and parseAttributes = p => parseRegion(p, ~grammar=Grammar.Attribute, ~f=parseAttribute)
+
+/*
+ * standalone-attribute ::=
+ *  | @@ atribute-id
+ *  | @@ attribute-id ( structure-item )
+ */
+and parseStandaloneAttribute = p => {
+  let startPos = p.startPos
+  Parser.expect(AtAt, p)
+  let attrId = parseAttributeId(~startPos, p)
+  let payload = parsePayload(p)
+  (attrId, payload)
+}
+
+/* extension	::=	% attr-id  attr-payload
+ *              | %% attr-id(
+ *  expr	::=	 ...
+ *    ∣	 extension
+ *
+ *  typexpr	::=	 ...
+ *    ∣	 extension
+ *
+ *  pattern	::=	 ...
+ *    ∣	 extension
+ *
+ *  module-expr	::=	 ...
+ *    ∣	 extension
+ *
+ *  module-type	::=	 ...
+ *    ∣	 extension
+ *
+ *  class-expr	::=	 ...
+ *    ∣	 extension
+ *
+ *  class-type	::=	 ...
+ *    ∣	 extension
+ *
+ *
+ * item extension nodes usable in structures and signature
+ *
+ * item-extension ::= %% attr-id
+ *                  | %% attr-id(structure-item)
+ *
+ *  attr-payload ::= structure-item
+ *
+ *  ~moduleLanguage represents whether we're on the module level or not
+ */
+and parseExtension = (~moduleLanguage=false, p) => {
+  let startPos = p.Parser.startPos
+  if moduleLanguage {
+    Parser.expect(PercentPercent, p)
+  } else {
+    Parser.expect(Percent, p)
+  }
+  let attrId = parseAttributeId(~startPos, p)
+  let payload = parsePayload(p)
+  (attrId, payload)
+}
+
+/* module signature on the file level */
+let parseSpecification = (p): Parsetree.signature =>
+  parseRegion(p, ~grammar=Grammar.Specification, ~f=parseSignatureItemRegion)
+
+/* module structure on the file level */
+let parseImplementation = (p): Parsetree.structure =>
+  parseRegion(p, ~grammar=Grammar.Implementation, ~f=parseStructureItemRegion)
+
+
+let _ = parseImplementation
\ No newline at end of file
diff --git a/analysis/examples/larger-project/src/res_diagnostics.js b/analysis/examples/larger-project/src/res_diagnostics.js
new file mode 100644
index 0000000000..da24e35f17
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_diagnostics.js
@@ -0,0 +1,388 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Format from "./format.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Res_token from "./res_token.js";
+import * as Res_grammar from "./res_grammar.js";
+import * as Res_diagnostics_printing_utils from "./res_diagnostics_printing_utils.js";
+
+function getStartPos(t) {
+  return t.startPos;
+}
+
+function getEndPos(t) {
+  return t.endPos;
+}
+
+function defaultUnexpected(token) {
+  return "I'm not sure what to parse here when looking at \"" + (Res_token.toString(token) + "\".");
+}
+
+function reservedKeyword(token) {
+  var tokenTxt = Res_token.toString(token);
+  return "`" + (tokenTxt + ("` is a reserved keyword. Keywords need to be escaped: \\\"" + (tokenTxt + "\"")));
+}
+
+function explain(t) {
+  var currentToken = t.category;
+  if (typeof currentToken === "number") {
+    switch (currentToken) {
+      case /* UnclosedString */0 :
+          return "This string is missing a double quote at the end";
+      case /* UnclosedTemplate */1 :
+          return "Did you forget to close this template expression with a backtick?";
+      case /* UnclosedComment */2 :
+          return "This comment seems to be missing a closing `*/`";
+      
+    }
+  } else {
+    switch (currentToken.TAG | 0) {
+      case /* Unexpected */0 :
+          var breadcrumbs = currentToken.context;
+          var t$1 = currentToken.token;
+          var name = Res_token.toString(t$1);
+          if (breadcrumbs) {
+            var match = breadcrumbs.hd[0];
+            if (typeof match === "number") {
+              if (match >= 32) {
+                if (match !== 52) {
+                  if (match === 55) {
+                    var breadcrumbs$1 = breadcrumbs.tl;
+                    var exit = 0;
+                    if (typeof t$1 === "number") {
+                      if (t$1 !== 14) {
+                        if (t$1 !== 53) {
+                          if (t$1 !== 57 || !breadcrumbs$1) {
+                            exit = 2;
+                          } else {
+                            if (breadcrumbs$1.hd[0] === 23) {
+                              return "I was expecting a pattern to match on before the `=>`";
+                            }
+                            exit = 2;
+                          }
+                        } else if (breadcrumbs$1) {
+                          if (breadcrumbs$1.hd[0] === 16) {
+                            return "A for-loop has the following form: `for i in 0 to 10`. Did you forget to supply a name before `in`?";
+                          }
+                          exit = 2;
+                        } else {
+                          exit = 2;
+                        }
+                      } else if (breadcrumbs$1) {
+                        if (breadcrumbs$1.hd[0] === 24) {
+                          return "I was expecting a name for this let-binding. Example: `let message = \"hello\"`";
+                        }
+                        exit = 2;
+                      } else {
+                        exit = 2;
+                      }
+                    } else {
+                      exit = 2;
+                    }
+                    if (exit === 2) {
+                      if (Res_token.isKeyword(t$1)) {
+                        return reservedKeyword(t$1);
+                      } else {
+                        return defaultUnexpected(t$1);
+                      }
+                    }
+                    
+                  }
+                  
+                } else {
+                  var breadcrumbs$2 = breadcrumbs.tl;
+                  var exit$1 = 0;
+                  if (breadcrumbs$2) {
+                    var match$1 = breadcrumbs$2.hd[0];
+                    if (typeof match$1 === "number" && (match$1 === 38 || match$1 === 37)) {
+                      if (typeof t$1 === "number") {
+                        switch (t$1) {
+                          case /* Rbrace */23 :
+                          case /* Comma */25 :
+                          case /* Eof */26 :
+                          case /* At */75 :
+                              return "I'm missing a type here";
+                          default:
+                            exit$1 = 2;
+                        }
+                      } else {
+                        if (t$1.TAG === /* String */3) {
+                          return "I'm missing a type here";
+                        }
+                        exit$1 = 2;
+                      }
+                    } else {
+                      exit$1 = 2;
+                    }
+                  } else {
+                    exit$1 = 2;
+                  }
+                  if (exit$1 === 2) {
+                    if (Res_grammar.isStructureItemStart(t$1) || t$1 === /* Eof */26) {
+                      return "Missing a type here";
+                    } else {
+                      return defaultUnexpected(t$1);
+                    }
+                  }
+                  
+                }
+              } else if (match !== 7) {
+                if (match >= 31) {
+                  if (typeof t$1 === "number") {
+                    return "I'm not sure what to parse here when looking at \"" + (name + "\".");
+                  } else if (t$1.TAG === /* Lident */4) {
+                    return "Did you mean '" + (t$1._0 + "? A Type parameter starts with a quote.");
+                  } else {
+                    return "I'm not sure what to parse here when looking at \"" + (name + "\".");
+                  }
+                }
+                
+              } else {
+                var breadcrumbs$3 = breadcrumbs.tl;
+                var exit$2 = 0;
+                if (breadcrumbs$3) {
+                  var match$2 = breadcrumbs$3.hd[0];
+                  var exit$3 = 0;
+                  if (typeof match$2 !== "number") {
+                    return "Did you forget to write an expression here?";
+                  }
+                  switch (match$2) {
+                    case /* ExprUnary */8 :
+                        return "Did you forget to write an expression here?";
+                    case /* ExprSetField */9 :
+                        return "It seems that this record field mutation misses an expression";
+                    case /* ExprBlock */10 :
+                        if (typeof t$1 === "number") {
+                          if (t$1 === 17) {
+                            return "Looks like there might be an expression missing here";
+                          }
+                          if (t$1 === 23) {
+                            return "It seems that this expression block is empty";
+                          }
+                          exit$3 = 3;
+                        } else {
+                          exit$3 = 3;
+                        }
+                        break;
+                    case /* ExprArrayMutation */14 :
+                        return "Seems that an expression is missing, with what do I mutate the array?";
+                    case /* ExprCall */11 :
+                    case /* ExprList */12 :
+                    case /* ExprArrayAccess */13 :
+                    case /* ExprIf */15 :
+                    case /* ExprFor */16 :
+                    case /* IfCondition */17 :
+                    case /* IfBranch */18 :
+                    case /* ElseBranch */19 :
+                    case /* TypeExpression */20 :
+                    case /* External */21 :
+                    case /* PatternMatching */22 :
+                    case /* PatternMatchCase */23 :
+                        exit$3 = 3;
+                        break;
+                    case /* LetBinding */24 :
+                        return "This let-binding misses an expression";
+                    default:
+                      exit$3 = 3;
+                  }
+                  if (exit$3 === 3) {
+                    if (typeof t$1 === "number") {
+                      switch (t$1) {
+                        case /* Lbrace */22 :
+                        case /* Colon */24 :
+                        case /* Comma */25 :
+                            exit$2 = 2;
+                            break;
+                        case /* Rbracket */21 :
+                        case /* Rbrace */23 :
+                        case /* Eof */26 :
+                            return "Missing expression";
+                        default:
+                          exit$2 = 2;
+                      }
+                    } else {
+                      exit$2 = 2;
+                    }
+                  }
+                  
+                } else {
+                  exit$2 = 2;
+                }
+                if (exit$2 === 2) {
+                  return "I'm not sure what to parse here when looking at \"" + (name + "\".");
+                }
+                
+              }
+            }
+            
+          }
+          if (Res_token.isKeyword(t$1)) {
+            return "`" + (name + ("` is a reserved keyword. Keywords need to be escaped: \\\"" + (Res_token.toString(t$1) + "\"")));
+          } else {
+            return "I'm not sure what to parse here when looking at \"" + (name + "\".");
+          }
+      case /* Expected */1 :
+          var context = currentToken.context;
+          var hint = context !== undefined ? " It signals the start of " + Res_grammar.toString(context) : "";
+          return "Did you forget a `" + (Res_token.toString(currentToken.token) + ("` here?" + hint));
+      case /* Message */2 :
+          return currentToken._0;
+      case /* Uident */3 :
+          var currentToken$1 = currentToken._0;
+          if (typeof currentToken$1 !== "number" && currentToken$1.TAG === /* Lident */4) {
+            var lident = currentToken$1._0;
+            var guess = $$String.capitalize_ascii(lident);
+            return "Did you mean `" + (guess + ("` instead of `" + (lident + "`?")));
+          }
+          if (!Res_token.isKeyword(currentToken$1)) {
+            return "At this point, I'm looking for an uppercased name like `Belt` or `Array`";
+          }
+          var token = Res_token.toString(currentToken$1);
+          return "`" + (token + "` is a reserved keyword.");
+          break;
+      case /* Lident */4 :
+          var currentToken$2 = currentToken._0;
+          if (typeof currentToken$2 !== "number" && currentToken$2.TAG === /* Uident */5) {
+            var uident = currentToken$2._0;
+            var guess$1 = $$String.uncapitalize_ascii(uident);
+            return "Did you mean `" + (guess$1 + ("` instead of `" + (uident + "`?")));
+          }
+          if (!Res_token.isKeyword(currentToken$2)) {
+            if (currentToken$2 === 12) {
+              return "`_` isn't a valid name.";
+            } else {
+              return "I'm expecting a lowercase name like `user or `age`";
+            }
+          }
+          var token$1 = Res_token.toString(currentToken$2);
+          return "`" + (token$1 + ("` is a reserved keyword. Keywords need to be escaped: \\\"" + (token$1 + "\"")));
+          break;
+      case /* UnknownUchar */5 :
+          if (currentToken._0 !== 94) {
+            return "Not sure what to do with this character.";
+          } else {
+            return "Not sure what to do with this character.\n  If you're trying to dereference a mutable value, use `myValue.contents` instead.\n  To concatenate strings, use `\"a\" ++ \"b\"` instead.";
+          }
+      
+    }
+  }
+}
+
+function make(startPos, endPos, category) {
+  return {
+          startPos: startPos,
+          endPos: endPos,
+          category: category
+        };
+}
+
+function printReport(diagnostics, src) {
+  var print = function (_diagnostics, src) {
+    while(true) {
+      var diagnostics = _diagnostics;
+      if (!diagnostics) {
+        return ;
+      }
+      var rest = diagnostics.tl;
+      var d = diagnostics.hd;
+      Res_diagnostics_printing_utils.Super_location.super_error_reporter(Format.err_formatter, src, {
+            loc: {
+              loc_start: d.startPos,
+              loc_end: d.endPos,
+              loc_ghost: false
+            },
+            msg: explain(d),
+            sub: /* [] */0,
+            if_highlight: ""
+          });
+      if (rest) {
+        Curry._1(Format.fprintf(Format.err_formatter), "@.");
+      }
+      _diagnostics = rest;
+      continue ;
+    };
+  };
+  Curry._1(Format.fprintf(Format.err_formatter), "@[<v>");
+  print(List.rev(diagnostics), src);
+  return Curry._1(Format.fprintf(Format.err_formatter), "@]@.");
+}
+
+function unexpected(token, context) {
+  return {
+          TAG: /* Unexpected */0,
+          token: token,
+          context: context
+        };
+}
+
+function expected(grammar, pos, token) {
+  return {
+          TAG: /* Expected */1,
+          context: grammar,
+          pos: pos,
+          token: token
+        };
+}
+
+function uident(currentToken) {
+  return {
+          TAG: /* Uident */3,
+          _0: currentToken
+        };
+}
+
+function lident(currentToken) {
+  return {
+          TAG: /* Lident */4,
+          _0: currentToken
+        };
+}
+
+function unknownUchar(code) {
+  return {
+          TAG: /* UnknownUchar */5,
+          _0: code
+        };
+}
+
+function message(txt) {
+  return {
+          TAG: /* Message */2,
+          _0: txt
+        };
+}
+
+var Grammar;
+
+var Token;
+
+var unclosedString = /* UnclosedString */0;
+
+var unclosedComment = /* UnclosedComment */2;
+
+var unclosedTemplate = /* UnclosedTemplate */1;
+
+export {
+  Grammar ,
+  Token ,
+  getStartPos ,
+  getEndPos ,
+  defaultUnexpected ,
+  reservedKeyword ,
+  explain ,
+  make ,
+  printReport ,
+  unexpected ,
+  expected ,
+  uident ,
+  lident ,
+  unclosedString ,
+  unclosedComment ,
+  unclosedTemplate ,
+  unknownUchar ,
+  message ,
+  
+}
+/* Format Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_diagnostics.res b/analysis/examples/larger-project/src/res_diagnostics.res
new file mode 100644
index 0000000000..d5a5a57d6e
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_diagnostics.res
@@ -0,0 +1,200 @@
+module Grammar = Res_grammar
+module Token = Res_token
+
+type category =
+  | Unexpected({token: Token.t, context: list<(Grammar.t, Lexing.position)>})
+  | Expected({
+      context: option<Grammar.t>,
+      pos: Lexing.position /* prev token end */,
+      token: Token.t,
+    })
+  | Message(string)
+  | Uident(Token.t)
+  | Lident(Token.t)
+  | UnclosedString
+  | UnclosedTemplate
+  | UnclosedComment
+  | UnknownUchar(Char.t)
+
+type t = {
+  startPos: Lexing.position,
+  endPos: Lexing.position,
+  category: category,
+}
+
+type report = list<t>
+
+let getStartPos = t => t.startPos
+let getEndPos = t => t.endPos
+
+let defaultUnexpected = token =>
+  "I'm not sure what to parse here when looking at \"" ++ (Token.toString(token) ++ "\".")
+
+let reservedKeyword = token => {
+  let tokenTxt = Token.toString(token)
+  "`" ++
+  (tokenTxt ++
+  ("` is a reserved keyword. Keywords need to be escaped: \\\"" ++ (tokenTxt ++ "\"")))
+}
+
+let explain = t =>
+  switch t.category {
+  | Uident(currentToken) =>
+    switch currentToken {
+    | Lident(lident) =>
+      let guess = String.capitalize_ascii(lident)
+      "Did you mean `" ++ (guess ++ ("` instead of `" ++ (lident ++ "`?")))
+    | t if Token.isKeyword(t) =>
+      let token = Token.toString(t)
+      "`" ++ (token ++ "` is a reserved keyword.")
+    | _ => "At this point, I'm looking for an uppercased name like `Belt` or `Array`"
+    }
+  | Lident(currentToken) =>
+    switch currentToken {
+    | Uident(uident) =>
+      let guess = String.uncapitalize_ascii(uident)
+      "Did you mean `" ++ (guess ++ ("` instead of `" ++ (uident ++ "`?")))
+    | t if Token.isKeyword(t) =>
+      let token = Token.toString(t)
+      "`" ++
+      (token ++
+      ("` is a reserved keyword. Keywords need to be escaped: \\\"" ++ (token ++ "\"")))
+    | Underscore => "`_` isn't a valid name."
+    | _ => "I'm expecting a lowercase name like `user or `age`"
+    }
+  | Message(txt) => txt
+  | UnclosedString => "This string is missing a double quote at the end"
+  | UnclosedTemplate => "Did you forget to close this template expression with a backtick?"
+  | UnclosedComment => "This comment seems to be missing a closing `*/`"
+  | UnknownUchar(uchar) =>
+    switch uchar {
+    | '^' =>
+      "Not sure what to do with this character.\n" ++
+      ("  If you're trying to dereference a mutable value, use `myValue.contents` instead.\n" ++
+      "  To concatenate strings, use `\"a\" ++ \"b\"` instead.")
+    | _ => "Not sure what to do with this character."
+    }
+  | Expected({context, token: t}) =>
+    let hint = switch context {
+    | Some(grammar) => " It signals the start of " ++ Grammar.toString(grammar)
+    | None => ""
+    }
+
+    "Did you forget a `" ++ (Token.toString(t) ++ ("` here?" ++ hint))
+  | Unexpected({token: t, context: breadcrumbs}) =>
+    let name = Token.toString(t)
+    switch breadcrumbs {
+    | list{(AtomicTypExpr, _), ...breadcrumbs} =>
+      switch (breadcrumbs, t) {
+      | (
+          list{(StringFieldDeclarations | FieldDeclarations, _), ..._},
+          String(_) | At | Rbrace | Comma | Eof,
+        ) => "I'm missing a type here"
+      | (_, t) if Grammar.isStructureItemStart(t) || t == Eof => "Missing a type here"
+      | _ => defaultUnexpected(t)
+      }
+    | list{(ExprOperand, _), ...breadcrumbs} =>
+      switch (breadcrumbs, t) {
+      | (list{(ExprBlock, _), ..._}, Rbrace) => "It seems that this expression block is empty"
+      | (list{(ExprBlock, _), ..._}, Bar) => /* Pattern matching */
+        "Looks like there might be an expression missing here"
+      | (
+          list{(ExprSetField, _), ..._},
+          _,
+        ) => "It seems that this record field mutation misses an expression"
+      | (
+          list{(ExprArrayMutation, _), ..._},
+          _,
+        ) => "Seems that an expression is missing, with what do I mutate the array?"
+      | (
+          list{(ExprBinaryAfterOp(_) | ExprUnary, _), ..._},
+          _,
+        ) => "Did you forget to write an expression here?"
+      | (list{(Grammar.LetBinding, _), ..._}, _) => "This let-binding misses an expression"
+      | (list{_, ..._}, Rbracket | Rbrace | Eof) => "Missing expression"
+      | _ => "I'm not sure what to parse here when looking at \"" ++ (name ++ "\".")
+      }
+    | list{(TypeParam, _), ..._} =>
+      switch t {
+      | Lident(ident) => "Did you mean '" ++ (ident ++ "? A Type parameter starts with a quote.")
+      | _ => "I'm not sure what to parse here when looking at \"" ++ (name ++ "\".")
+      }
+    | list{(Pattern, _), ...breadcrumbs} =>
+      switch (t, breadcrumbs) {
+      | (
+          Equal,
+          list{(LetBinding, _), ..._},
+        ) => "I was expecting a name for this let-binding. Example: `let message = \"hello\"`"
+      | (
+          In,
+          list{(ExprFor, _), ..._},
+        ) => "A for-loop has the following form: `for i in 0 to 10`. Did you forget to supply a name before `in`?"
+      | (
+          EqualGreater,
+          list{(PatternMatchCase, _), ..._},
+        ) => "I was expecting a pattern to match on before the `=>`"
+      | (token, _) if Token.isKeyword(t) => reservedKeyword(token)
+      | (token, _) => defaultUnexpected(token)
+      }
+    | _ =>
+      /* TODO: match on circumstance to verify Lident needed ? */
+      if Token.isKeyword(t) {
+        "`" ++
+        (name ++
+        ("` is a reserved keyword. Keywords need to be escaped: \\\"" ++
+        (Token.toString(t) ++
+        "\"")))
+      } else {
+        "I'm not sure what to parse here when looking at \"" ++ (name ++ "\".")
+      }
+    }
+  }
+
+let make = (~startPos, ~endPos, category) => {
+  startPos: startPos,
+  endPos: endPos,
+  category: category,
+}
+
+let printReport = (diagnostics, src) => {
+  let rec print = (diagnostics, src) =>
+    switch diagnostics {
+    | list{} => ()
+    | list{d, ...rest} =>
+      Res_diagnostics_printing_utils.Super_location.super_error_reporter(
+        Format.err_formatter,
+        src,
+        {
+          open Location
+          {
+            loc: {loc_start: d.startPos, loc_end: d.endPos, loc_ghost: false},
+            msg: explain(d),
+            sub: list{},
+            if_highlight: "",
+          }
+        },
+      )
+      switch rest {
+      | list{} => ()
+      | _ => Format.fprintf(Format.err_formatter, "@.")
+      }
+      print(rest, src)
+    }
+
+  Format.fprintf(Format.err_formatter, "@[<v>")
+  print(List.rev(diagnostics), src)
+  Format.fprintf(Format.err_formatter, "@]@.")
+}
+
+let unexpected = (token, context) => Unexpected({token: token, context: context})
+
+let expected = (~grammar=?, pos, token) => Expected({context: grammar, pos: pos, token: token})
+
+let uident = currentToken => Uident(currentToken)
+let lident = currentToken => Lident(currentToken)
+let unclosedString = UnclosedString
+let unclosedComment = UnclosedComment
+let unclosedTemplate = UnclosedTemplate
+let unknownUchar = code => UnknownUchar(code)
+let message = txt => Message(txt)
+
diff --git a/analysis/examples/larger-project/src/res_diagnostics_printing_utils.js b/analysis/examples/larger-project/src/res_diagnostics_printing_utils.js
new file mode 100644
index 0000000000..3a33911d18
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_diagnostics_printing_utils.js
@@ -0,0 +1,484 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as P from "./P.js";
+import * as Caml from "rescript/lib/es6/caml.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Buffer from "rescript/lib/es6/buffer.js";
+import * as Format from "./format.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Caml_sys from "rescript/lib/es6/caml_sys.js";
+import * as $$Location from "./location.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+import * as Caml_external_polyfill from "rescript/lib/es6/caml_external_polyfill.js";
+
+function digits_count(n) {
+  var n$1 = Pervasives.abs(n);
+  var _base = 1;
+  var _count = 0;
+  while(true) {
+    var count = _count;
+    var base = _base;
+    if (n$1 < base) {
+      return count;
+    }
+    _count = count + 1 | 0;
+    _base = Math.imul(base, 10);
+    continue ;
+  };
+}
+
+function seek_2_lines_before(src, pos) {
+  var original_line = pos.pos_lnum;
+  var _current_line = 1;
+  var _current_char = 0;
+  while(true) {
+    var current_char = _current_char;
+    var current_line = _current_line;
+    if ((current_line + 2 | 0) >= original_line) {
+      return [
+              current_char,
+              current_line
+            ];
+    }
+    _current_char = current_char + 1 | 0;
+    _current_line = Caml_string.get(src, current_char) === /* '\n' */10 ? current_line + 1 | 0 : current_line;
+    continue ;
+  };
+}
+
+function seek_2_lines_after(src, pos) {
+  var original_line = pos.pos_lnum;
+  var _current_line = original_line;
+  var _current_char = pos.pos_cnum;
+  while(true) {
+    var current_char = _current_char;
+    var current_line = _current_line;
+    if (current_char === src.length) {
+      return [
+              current_char,
+              current_line
+            ];
+    }
+    var match = Caml_string.get(src, current_char);
+    if (match !== 10) {
+      _current_char = current_char + 1 | 0;
+      continue ;
+    }
+    if (current_line === (original_line + 2 | 0)) {
+      return [
+              current_char,
+              current_line
+            ];
+    }
+    _current_char = current_char + 1 | 0;
+    _current_line = current_line + 1 | 0;
+    continue ;
+  };
+}
+
+function leading_space_count(str) {
+  var _i = 0;
+  var _count = 0;
+  while(true) {
+    var count = _count;
+    var i = _i;
+    if (i === str.length) {
+      return count;
+    }
+    if (Caml_string.get(str, i) !== /* ' ' */32) {
+      return count;
+    }
+    _count = count + 1 | 0;
+    _i = i + 1 | 0;
+    continue ;
+  };
+}
+
+function break_long_line(max_width, line) {
+  var loop = function (_pos, _accum) {
+    while(true) {
+      var accum = _accum;
+      var pos = _pos;
+      if (pos === line.length) {
+        return accum;
+      }
+      var chunk_length = Caml.caml_int_min(max_width, line.length - pos | 0);
+      var chunk = $$String.sub(line, pos, chunk_length);
+      _accum = {
+        hd: chunk,
+        tl: accum
+      };
+      _pos = pos + chunk_length | 0;
+      continue ;
+    };
+  };
+  return List.rev(loop(0, /* [] */0));
+}
+
+function filter_mapi(f, l) {
+  var loop = function (f, _l, _i, _accum) {
+    while(true) {
+      var accum = _accum;
+      var i = _i;
+      var l = _l;
+      if (!l) {
+        return accum;
+      }
+      var result = Curry._2(f, i, l.hd);
+      var accum$1 = result !== undefined ? ({
+            hd: Caml_option.valFromOption(result),
+            tl: accum
+          }) : accum;
+      _accum = accum$1;
+      _i = i + 1 | 0;
+      _l = l.tl;
+      continue ;
+    };
+  };
+  return List.rev(loop(f, l, 0, /* [] */0));
+}
+
+var dim = "\x11[2m";
+
+var err = "\x11[1;31m";
+
+var warn = "\x11[1;33m";
+
+var reset = "\x11[0m";
+
+function should_enable_color(param) {
+  var term;
+  try {
+    term = Caml_sys.caml_sys_getenv("TERM");
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      term = "";
+    } else {
+      throw exn;
+    }
+  }
+  if (term !== "dumb" && term !== "") {
+    return Caml_external_polyfill.resolve("caml_sys_isatty")(P.stderr);
+  } else {
+    return false;
+  }
+}
+
+var color_enabled = {
+  contents: true
+};
+
+var first = {
+  contents: true
+};
+
+function setup(o) {
+  if (first.contents) {
+    first.contents = false;
+    var tmp;
+    if (o !== undefined) {
+      switch (o) {
+        case /* Auto */0 :
+            tmp = should_enable_color(undefined);
+            break;
+        case /* Always */1 :
+            tmp = true;
+            break;
+        case /* Never */2 :
+            tmp = false;
+            break;
+        
+      }
+    } else {
+      tmp = should_enable_color(undefined);
+    }
+    color_enabled.contents = tmp;
+  }
+  
+}
+
+var Color = {
+  dim: dim,
+  err: err,
+  warn: warn,
+  reset: reset,
+  should_enable_color: should_enable_color,
+  color_enabled: color_enabled,
+  setup: setup
+};
+
+function print(is_warning, src, startPos, endPos) {
+  var highlight_line_start_line = startPos.pos_lnum;
+  var highlight_line_end_line = endPos.pos_lnum;
+  var match = seek_2_lines_before(src, startPos);
+  var first_shown_line = match[1];
+  var start_line_line_offset = match[0];
+  var match$1 = seek_2_lines_after(src, endPos);
+  var more_than_5_highlighted_lines = ((highlight_line_end_line - highlight_line_start_line | 0) + 1 | 0) > 5;
+  var max_line_digits_count = digits_count(match$1[1]);
+  var line_width = ((78 - max_line_digits_count | 0) - 2 | 0) - 3 | 0;
+  var lines = filter_mapi((function (i, line) {
+          var line_number = i + first_shown_line | 0;
+          if (more_than_5_highlighted_lines) {
+            if (line_number === (highlight_line_start_line + 2 | 0)) {
+              return [
+                      /* Elided */0,
+                      line
+                    ];
+            } else if (line_number > (highlight_line_start_line + 2 | 0) && line_number < (highlight_line_end_line - 1 | 0)) {
+              return ;
+            } else {
+              return [
+                      /* Number */{
+                        _0: line_number
+                      },
+                      line
+                    ];
+            }
+          } else {
+            return [
+                    /* Number */{
+                      _0: line_number
+                    },
+                    line
+                  ];
+          }
+        }), $$String.split_on_char(/* '\n' */10, $$String.sub(src, start_line_line_offset, match$1[0] - start_line_line_offset | 0)));
+  var leading_space_to_cut = List.fold_left((function (current_max, param) {
+          var line = param[1];
+          var leading_spaces = leading_space_count(line);
+          if (line.length === leading_spaces || leading_spaces >= current_max) {
+            return current_max;
+          } else {
+            return leading_spaces;
+          }
+        }), 99999, lines);
+  var separator = leading_space_to_cut === 0 ? "\xe2\x94\x82" : "\xe2\x94\x86";
+  var stripped_lines = List.map((function (param) {
+          var line = param[1];
+          var gutter = param[0];
+          var new_content = line.length <= leading_space_to_cut ? ({
+                hd: {
+                  s: "",
+                  start: 0,
+                  end_: 0
+                },
+                tl: /* [] */0
+              }) : List.mapi((function (i, line) {
+                    if (!gutter) {
+                      return {
+                              s: line,
+                              start: 0,
+                              end_: 0
+                            };
+                    }
+                    var line_number = gutter._0;
+                    var highlight_line_start_offset = startPos.pos_cnum - startPos.pos_bol | 0;
+                    var highlight_line_end_offset = endPos.pos_cnum - endPos.pos_bol | 0;
+                    var start = i === 0 && line_number === highlight_line_start_line ? highlight_line_start_offset - leading_space_to_cut | 0 : 0;
+                    var end_ = line_number < highlight_line_start_line ? 0 : (
+                        line_number === highlight_line_start_line && line_number === highlight_line_end_line ? highlight_line_end_offset - leading_space_to_cut | 0 : (
+                            line_number === highlight_line_start_line ? line.length : (
+                                line_number > highlight_line_start_line && line_number < highlight_line_end_line ? line.length : (
+                                    line_number === highlight_line_end_line ? highlight_line_end_offset - leading_space_to_cut | 0 : 0
+                                  )
+                              )
+                          )
+                      );
+                    return {
+                            s: line,
+                            start: start,
+                            end_: end_
+                          };
+                  }), break_long_line(line_width, $$String.sub(line, leading_space_to_cut, line.length - leading_space_to_cut | 0)));
+          return {
+                  gutter: gutter,
+                  content: new_content
+                };
+        }), lines);
+  var buf = $$Buffer.create(100);
+  var last_color = {
+    contents: /* NoColor */3
+  };
+  var add_ch = function (color, ch) {
+    if (!color_enabled.contents || last_color.contents === color) {
+      return $$Buffer.add_char(buf, ch);
+    }
+    var match = last_color.contents;
+    var ansi;
+    var exit = 0;
+    if (match >= 3) {
+      switch (color) {
+        case /* Dim */0 :
+            ansi = dim;
+            break;
+        case /* Err */1 :
+            ansi = err;
+            break;
+        case /* Warn */2 :
+            ansi = warn;
+            break;
+        case /* NoColor */3 :
+            exit = 1;
+            break;
+        
+      }
+    } else {
+      exit = 1;
+    }
+    if (exit === 1) {
+      switch (color) {
+        case /* Dim */0 :
+            ansi = "\x11[0m\x11[2m";
+            break;
+        case /* Err */1 :
+            ansi = "\x11[0m\x11[1;31m";
+            break;
+        case /* Warn */2 :
+            ansi = "\x11[0m\x11[1;33m";
+            break;
+        case /* NoColor */3 :
+            ansi = reset;
+            break;
+        
+      }
+    }
+    $$Buffer.add_string(buf, ansi);
+    $$Buffer.add_char(buf, ch);
+    last_color.contents = color;
+    
+  };
+  var draw_gutter = function (color, s) {
+    for(var _i = 1 ,_i_finish = (max_line_digits_count + 2 | 0) - s.length | 0; _i <= _i_finish; ++_i){
+      add_ch(/* NoColor */3, /* ' ' */32);
+    }
+    $$String.iter((function (param) {
+            return add_ch(color, param);
+          }), s);
+    add_ch(/* NoColor */3, /* ' ' */32);
+    $$String.iter((function (param) {
+            return add_ch(/* Dim */0, param);
+          }), separator);
+    return add_ch(/* NoColor */3, /* ' ' */32);
+  };
+  List.iter((function (param) {
+          var gutter = param.gutter;
+          if (gutter) {
+            var line_number = gutter._0;
+            return List.iteri((function (i, line) {
+                          var gutter_content = i === 0 ? String(line_number) : "";
+                          var gutter_color = i === 0 && line_number >= highlight_line_start_line && line_number <= highlight_line_end_line ? (
+                              is_warning ? /* Warn */2 : /* Err */1
+                            ) : /* NoColor */3;
+                          draw_gutter(gutter_color, gutter_content);
+                          $$String.iteri((function (ii, ch) {
+                                  var c = ii >= line.start && ii < line.end_ ? (
+                                      is_warning ? /* Warn */2 : /* Err */1
+                                    ) : /* NoColor */3;
+                                  return add_ch(c, ch);
+                                }), line.s);
+                          return add_ch(/* NoColor */3, /* '\n' */10);
+                        }), param.content);
+          }
+          draw_gutter(/* Dim */0, ".");
+          add_ch(/* Dim */0, /* '.' */46);
+          add_ch(/* Dim */0, /* '.' */46);
+          add_ch(/* Dim */0, /* '.' */46);
+          return add_ch(/* NoColor */3, /* '\n' */10);
+        }), stripped_lines);
+  return $$Buffer.contents(buf);
+}
+
+var Super_code_frame = {
+  digits_count: digits_count,
+  seek_2_lines_before: seek_2_lines_before,
+  seek_2_lines_after: seek_2_lines_after,
+  leading_space_count: leading_space_count,
+  break_long_line: break_long_line,
+  filter_mapi: filter_mapi,
+  Color: Color,
+  setup: setup,
+  print: print
+};
+
+function print$1(message_kind, intro, src, ppf, loc) {
+  if (message_kind === "warning_as_error") {
+    Curry._2(Format.fprintf(ppf), "@[@{<error>%s@} (configured as error) @]@,", intro);
+  } else if (message_kind === "warning") {
+    Curry._2(Format.fprintf(ppf), "@[@{<info>%s@}@]@,", intro);
+  } else {
+    Curry._2(Format.fprintf(ppf), "@[@{<error>%s@}@]@,", intro);
+  }
+  var match = $$Location.get_pos_info(loc.loc_start);
+  var start_char = match[2];
+  var start_line = match[1];
+  var match$1 = $$Location.get_pos_info(loc.loc_end);
+  var end_char = match$1[2];
+  var end_line = match$1[1];
+  var normalizedRange;
+  if (start_char === -1 || end_char === -1) {
+    normalizedRange = undefined;
+  } else if (start_line === end_line && start_char >= end_char) {
+    var same_char = start_char + 1 | 0;
+    normalizedRange = [
+      [
+        start_line,
+        same_char
+      ],
+      [
+        end_line,
+        same_char
+      ]
+    ];
+  } else {
+    normalizedRange = [
+      [
+        start_line,
+        start_char + 1 | 0
+      ],
+      [
+        end_line,
+        end_char
+      ]
+    ];
+  }
+  if (normalizedRange === undefined) {
+    return ;
+  }
+  try {
+    return Curry._2(Format.fprintf(ppf), "@,%s", print(message_kind === "warning", src, loc.loc_start, loc.loc_end));
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === P.Sys_error) {
+      return ;
+    }
+    throw exn;
+  }
+}
+
+function super_error_reporter(ppf, src, param) {
+  return Curry._4(Format.fprintf(ppf), "@[<v>@,  %a@,  %s@,@]", (function (param, param$1) {
+                return print$1("error", "Syntax error!", src, param, param$1);
+              }), param.loc, param.msg);
+}
+
+var Super_location = {
+  fprintf: Format.fprintf,
+  print_filename: $$Location.print_filename,
+  print: print$1,
+  super_error_reporter: super_error_reporter
+};
+
+export {
+  Super_code_frame ,
+  Super_location ,
+  
+}
+/* P Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_diagnostics_printing_utils.res b/analysis/examples/larger-project/src/res_diagnostics_printing_utils.res
new file mode 100644
index 0000000000..c68b8581ba
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_diagnostics_printing_utils.res
@@ -0,0 +1,435 @@
+/*
+  This file is taken from ReScript's super_code_frame.ml and super_location.ml
+  We're copying the look of ReScript's terminal error reporting.
+  See https://github.com/rescript-lang/syntax/pull/77 for the rationale.
+  A few lines have been commented out and swapped for their tweaked version.
+*/
+
+/* ===== super_code_frame.ml */
+
+open P
+
+module Super_code_frame = {
+  let digits_count = n => {
+    let rec loop = (n, base, count) =>
+      if n >= base {
+        loop(n, base * 10, count + 1)
+      } else {
+        count
+      }
+
+    loop(abs(n), 1, 0)
+  }
+
+  let seek_2_lines_before = (src, pos) => {
+    open Lexing
+    let original_line = pos.pos_lnum
+    let rec loop = (current_line, current_char) =>
+      if current_line + 2 >= original_line {
+        (current_char, current_line)
+      } else {
+        loop(
+          if @doesNotRaise String.get(src, current_char) == '\n' {
+            current_line + 1
+          } else {
+            current_line
+          },
+          current_char + 1,
+        )
+      }
+
+    loop(1, 0)
+  }
+
+  let seek_2_lines_after = (src, pos) => {
+    open Lexing
+    let original_line = pos.pos_lnum
+    let rec loop = (current_line, current_char) =>
+      if current_char == String.length(src) {
+        (current_char, current_line)
+      } else {
+        switch @doesNotRaise
+        String.get(src, current_char) {
+        | '\n' if current_line == original_line + 2 => (current_char, current_line)
+        | '\n' => loop(current_line + 1, current_char + 1)
+        | _ => loop(current_line, current_char + 1)
+        }
+      }
+
+    loop(original_line, pos.pos_cnum)
+  }
+
+  let leading_space_count = str => {
+    let rec loop = (i, count) =>
+      if i == String.length(str) {
+        count
+      } else if @doesNotRaise String.get(str, i) !== ' ' {
+        count
+      } else {
+        loop(i + 1, count + 1)
+      }
+
+    loop(0, 0)
+  }
+
+  let break_long_line = (max_width, line) => {
+    let rec loop = (pos, accum) =>
+      if pos == String.length(line) {
+        accum
+      } else {
+        let chunk_length = min(max_width, String.length(line) - pos)
+        let chunk = (@doesNotRaise String.sub)(line, pos, chunk_length)
+        loop(pos + chunk_length, list{chunk, ...accum})
+      }
+
+    loop(0, list{}) |> List.rev
+  }
+
+  let filter_mapi = (f, l) => {
+    let rec loop = (f, l, i, accum) =>
+      switch l {
+      | list{} => accum
+      | list{head, ...rest} =>
+        let accum = switch f(i, head) {
+        | None => accum
+        | Some(result) => list{result, ...accum}
+        }
+
+        loop(f, rest, i + 1, accum)
+      }
+
+    loop(f, l, 0, list{}) |> List.rev
+  }
+
+  /* Spiritual equivalent of
+  https://github.com/ocaml/ocaml/blob/414bdec9ae387129b8102cc6bf3c0b6ae173eeb9/utils/misc.ml#L601
+*/
+  module Color = {
+    type color =
+      | Dim
+      /* | Filename */
+      | Err
+      | Warn
+      | NoColor
+
+    let dim = "\x1b[2m"
+    /* let filename = "\x1b[46m" */
+    let err = "\x1b[1;31m"
+    let warn = "\x1b[1;33m"
+    let reset = "\x1b[0m"
+
+    external isatty: out_channel => bool = "caml_sys_isatty"
+    /* reasonable heuristic on whether colors should be enabled */
+    let should_enable_color = () => {
+      let term = try Sys.getenv("TERM") catch {
+      | Not_found => ""
+      }
+      term != "dumb" && (term != "" && isatty(stderr))
+    }
+
+    let color_enabled = ref(true)
+
+    let setup = {
+      let first = ref(true) /* initialize only once */
+      o => {
+        if first.contents {
+          first := false
+          color_enabled :=
+            switch o {
+            | Some(Misc.Color.Always) => true
+            | Some(Auto) => should_enable_color()
+            | Some(Never) => false
+            | None => should_enable_color()
+            }
+        }
+        ()
+      }
+    }
+  }
+
+  let setup = Color.setup
+
+  type gutter = Number(int) | Elided
+  type highlighted_string = {s: string, start: int, end_: int}
+  type line = {
+    gutter: gutter,
+    content: list<highlighted_string>,
+  }
+  /*
+  Features:
+  - display a line gutter
+  - break long line into multiple for terminal display
+  - peek 2 lines before & after for context
+  - center snippet when it's heavily indented
+  - ellide intermediate lines when the reported range is huge
+*/
+  let print = (~is_warning, ~src, ~startPos, ~endPos) => {
+    open Lexing
+
+    let indent = 2
+    let highlight_line_start_line = startPos.pos_lnum
+    let highlight_line_end_line = endPos.pos_lnum
+    let (start_line_line_offset, first_shown_line) = seek_2_lines_before(src, startPos)
+    let (end_line_line_end_offset, last_shown_line) = seek_2_lines_after(src, endPos)
+
+    let more_than_5_highlighted_lines = highlight_line_end_line - highlight_line_start_line + 1 > 5
+
+    let max_line_digits_count = digits_count(last_shown_line)
+    /* TODO: change this back to a fixed 100? */
+    /* 3 for separator + the 2 spaces around it */
+    let line_width = 78 - max_line_digits_count - indent - 3
+    let lines =
+      (@doesNotRaise String.sub)(
+        src,
+        start_line_line_offset,
+        end_line_line_end_offset - start_line_line_offset,
+      )
+      |> String.split_on_char('\n')
+      |> filter_mapi((i, line) => {
+        let line_number = i + first_shown_line
+        if more_than_5_highlighted_lines {
+          if line_number == highlight_line_start_line + 2 {
+            Some(Elided, line)
+          } else if (
+            line_number > highlight_line_start_line + 2 && line_number < highlight_line_end_line - 1
+          ) {
+            None
+          } else {
+            Some(Number(line_number), line)
+          }
+        } else {
+          Some(Number(line_number), line)
+        }
+      })
+
+    let leading_space_to_cut = lines |> List.fold_left((current_max, (_, line)) => {
+      let leading_spaces = leading_space_count(line)
+      if String.length(line) == leading_spaces {
+        /* the line's nothing but spaces. Doesn't count */
+        current_max
+      } else {
+        min(leading_spaces, current_max)
+      }
+    }, 99999)
+
+    let separator = if leading_space_to_cut == 0 {
+      "│"
+    } else {
+      "┆"
+    }
+    let stripped_lines = lines |> List.map(((gutter, line)) => {
+      let new_content = if String.length(line) <= leading_space_to_cut {
+        list{{s: "", start: 0, end_: 0}}
+      } else {
+        (@doesNotRaise String.sub)(
+          line,
+          leading_space_to_cut,
+          String.length(line) - leading_space_to_cut,
+        )
+        |> break_long_line(line_width)
+        |> List.mapi((i, line) =>
+          switch gutter {
+          | Elided => {s: line, start: 0, end_: 0}
+          | Number(line_number) =>
+            let highlight_line_start_offset = startPos.pos_cnum - startPos.pos_bol
+            let highlight_line_end_offset = endPos.pos_cnum - endPos.pos_bol
+            let start = if i == 0 && line_number == highlight_line_start_line {
+              highlight_line_start_offset - leading_space_to_cut
+            } else {
+              0
+            }
+
+            let end_ = if line_number < highlight_line_start_line {
+              0
+            } else if (
+              line_number == highlight_line_start_line && line_number == highlight_line_end_line
+            ) {
+              highlight_line_end_offset - leading_space_to_cut
+            } else if line_number == highlight_line_start_line {
+              String.length(line)
+            } else if (
+              line_number > highlight_line_start_line && line_number < highlight_line_end_line
+            ) {
+              String.length(line)
+            } else if line_number == highlight_line_end_line {
+              highlight_line_end_offset - leading_space_to_cut
+            } else {
+              0
+            }
+
+            {s: line, start: start, end_: end_}
+          }
+        )
+      }
+
+      {gutter: gutter, content: new_content}
+    })
+
+    let buf = Buffer.create(100)
+    open Color
+    let add_ch = {
+      let last_color = ref(NoColor)
+      (color, ch) =>
+        if !Color.color_enabled.contents || last_color.contents == color {
+          Buffer.add_char(buf, ch)
+        } else {
+          let ansi = switch (last_color.contents, color) {
+          | (NoColor, Dim) => dim
+          /* | NoColor, Filename -> filename */
+          | (NoColor, Err) => err
+          | (NoColor, Warn) => warn
+          | (_, NoColor) => reset
+          | (_, Dim) => reset ++ dim
+          /* | _, Filename -> reset ^ filename */
+          | (_, Err) => reset ++ err
+          | (_, Warn) => reset ++ warn
+          }
+
+          Buffer.add_string(buf, ansi)
+          Buffer.add_char(buf, ch)
+          last_color := color
+        }
+    }
+
+    let draw_gutter = (color, s) => {
+      for _i in 1 to max_line_digits_count + indent - String.length(s) {
+        add_ch(NoColor, ' ')
+      }
+      s |> String.iter(add_ch(color))
+      add_ch(NoColor, ' ')
+      separator |> String.iter(add_ch(Dim))
+      add_ch(NoColor, ' ')
+    }
+
+    stripped_lines |> List.iter(({gutter, content}) =>
+      switch gutter {
+      | Elided =>
+        draw_gutter(Dim, ".")
+        add_ch(Dim, '.')
+        add_ch(Dim, '.')
+        add_ch(Dim, '.')
+        add_ch(NoColor, '\n')
+      | Number(line_number) =>
+        content |> List.iteri((i, line) => {
+          let gutter_content = if i == 0 {
+            string_of_int(line_number)
+          } else {
+            ""
+          }
+          let gutter_color = if (
+            i == 0 &&
+              (line_number >= highlight_line_start_line &&
+              line_number <= highlight_line_end_line)
+          ) {
+            if is_warning {
+              Warn
+            } else {
+              Err
+            }
+          } else {
+            NoColor
+          }
+
+          draw_gutter(gutter_color, gutter_content)
+
+          line.s |> String.iteri((ii, ch) => {
+            let c = if ii >= line.start && ii < line.end_ {
+              if is_warning {
+                Warn
+              } else {
+                Err
+              }
+            } else {
+              NoColor
+            }
+            add_ch(c, ch)
+          })
+          add_ch(NoColor, '\n')
+        })
+      }
+    )
+    Buffer.contents(buf)
+  }
+}
+
+/* ===== super_location.ml */
+module Super_location = {
+  let fprintf = Format.fprintf
+
+  let print_filename = Location.print_filename
+
+  /* let print ~message_kind intro ppf (loc : Location.t) = */
+  let print = (~message_kind, intro, src, ppf, loc: Location.t) => {
+    switch message_kind {
+    | #warning => fprintf(ppf, "@[@{<info>%s@}@]@,", intro)
+    | #warning_as_error => fprintf(ppf, "@[@{<error>%s@} (configured as error) @]@,", intro)
+    | #error => fprintf(ppf, "@[@{<error>%s@}@]@,", intro)
+    }
+    /* ocaml's reported line/col numbering is horrible and super error-prone
+     when being handled programmatically (or humanly for that matter. If you're
+     an ocaml contributor reading this: who the heck reads the character count
+     starting from the first erroring character?) */
+    /* let (file, start_line, start_char) = Location.get_pos_info loc.loc_start in */
+    let (_file, start_line, start_char) = Location.get_pos_info(loc.loc_start)
+    let (_, end_line, end_char) = Location.get_pos_info(loc.loc_end)
+    /* line is 1-indexed, column is 0-indexed. We convert all of them to 1-indexed to avoid confusion */
+    /* start_char is inclusive, end_char is exclusive */
+    let normalizedRange = /* TODO: lots of the handlings here aren't needed anymore because the new
+      rescript syntax has much stronger invariants regarding positions, e.g.
+      no -1 */
+    if start_char === -1 || end_char === -1 {
+      /* happens sometimes. Syntax error for example */
+      None
+    } else if start_line == end_line && start_char >= end_char {
+      /* in some errors, starting char and ending char can be the same. But
+         since ending char was supposed to be exclusive, here it might end up
+         smaller than the starting char if we naively did start_char + 1 to
+         just the starting char and forget ending char */
+      let same_char = start_char + 1
+      Some((start_line, same_char), (end_line, same_char))
+    } else {
+      /* again: end_char is exclusive, so +1-1=0 */
+      Some((start_line, start_char + 1), (end_line, end_char))
+    }
+
+    switch normalizedRange {
+    | None => ()
+    | Some(_) =>
+      try /* let src = Ext_io.load_file file in */
+      /* we're putting the line break `@,` here rather than above, because this
+           branch might not be reached (aka no inline file content display) so
+           we don't wanna end up with two line breaks in the the consequent */
+      fprintf(
+        ppf,
+        "@,%s",
+        Super_code_frame.print(
+          ~is_warning=message_kind == #warning,
+          ~src,
+          ~startPos=loc.loc_start,
+          ~endPos=loc.loc_end,
+        ),
+      ) catch {
+      /* this might happen if the file is e.g. "", "_none_" or any of the fake file name placeholders.
+       we've already printed the location above, so nothing more to do here. */
+      | Sys_error(_) => ()
+      }
+    }
+  }
+
+  /* taken from https://github.com/rescript-lang/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/parsing/location.ml#L380 */
+  /* This is the error report entry point. We'll replace the default reporter with this one. */
+  /* let rec super_error_reporter ppf ({loc; msg; sub} : Location.error) = */
+  let super_error_reporter = (ppf, src, {loc, msg}: Location.error) =>
+    /* open a vertical box. Everything in our message is indented 2 spaces */
+    /* Format.fprintf ppf "@[<v>@,  %a@,  %s@,@]" (print ~message_kind:`error "We've found a bug for you!") src loc msg; */
+    Format.fprintf(
+      ppf,
+      "@[<v>@,  %a@,  %s@,@]",
+      print(~message_kind=#error, "Syntax error!", src),
+      loc,
+      msg,
+    )
+  /* List.iter (Format.fprintf ppf "@,@[%a@]" super_error_reporter) sub */
+  /* no need to flush here; location's report_exception (which uses this ultimately) flushes */
+}
+
diff --git a/analysis/examples/larger-project/src/res_doc.js b/analysis/examples/larger-project/src/res_doc.js
new file mode 100644
index 0000000000..f731fd68cc
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_doc.js
@@ -0,0 +1,1170 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Res_minibuffer from "./res_minibuffer.js";
+
+var line = {
+  TAG: /* LineBreak */5,
+  _0: /* Classic */0
+};
+
+var softLine = {
+  TAG: /* LineBreak */5,
+  _0: /* Soft */1
+};
+
+function text(s) {
+  return {
+          TAG: /* Text */0,
+          _0: s
+        };
+}
+
+function _concat(_acc, _l) {
+  while(true) {
+    var l = _l;
+    var acc = _acc;
+    if (!l) {
+      return acc;
+    }
+    var s1 = l.hd;
+    if (typeof s1 === "number") {
+      if (s1 === /* Nil */0) {
+        _l = l.tl;
+        continue ;
+      }
+      
+    } else {
+      switch (s1.TAG | 0) {
+        case /* Text */0 :
+            var match = l.tl;
+            if (match) {
+              var s2 = match.hd;
+              if (typeof s2 !== "number" && s2.TAG === /* Text */0) {
+                return {
+                        hd: {
+                          TAG: /* Text */0,
+                          _0: s1._0 + s2._0
+                        },
+                        tl: _concat(acc, match.tl)
+                      };
+              }
+              
+            }
+            break;
+        case /* Concat */1 :
+            _l = s1._0;
+            _acc = _concat(acc, l.tl);
+            continue ;
+        default:
+          
+      }
+    }
+    var rest = l.tl;
+    var rest1 = _concat(acc, rest);
+    if (rest1 === rest) {
+      return l;
+    } else {
+      return {
+              hd: s1,
+              tl: rest1
+            };
+    }
+  };
+}
+
+function concat(l) {
+  return {
+          TAG: /* Concat */1,
+          _0: _concat(/* [] */0, l)
+        };
+}
+
+function indent(d) {
+  return {
+          TAG: /* Indent */2,
+          _0: d
+        };
+}
+
+function ifBreaks(t, f) {
+  return {
+          TAG: /* IfBreaks */3,
+          yes: t,
+          no: f,
+          broken: false
+        };
+}
+
+function lineSuffix(d) {
+  return {
+          TAG: /* LineSuffix */4,
+          _0: d
+        };
+}
+
+function group(d) {
+  return {
+          TAG: /* Group */6,
+          shouldBreak: false,
+          doc: d
+        };
+}
+
+function breakableGroup(forceBreak, d) {
+  return {
+          TAG: /* Group */6,
+          shouldBreak: forceBreak,
+          doc: d
+        };
+}
+
+function customLayout(gs) {
+  return {
+          TAG: /* CustomLayout */7,
+          _0: gs
+        };
+}
+
+var comma = {
+  TAG: /* Text */0,
+  _0: ","
+};
+
+var trailingComma = {
+  TAG: /* IfBreaks */3,
+  yes: comma,
+  no: /* Nil */0,
+  broken: false
+};
+
+function propagateForcedBreaks(doc) {
+  var walk = function (_doc) {
+    while(true) {
+      var doc = _doc;
+      if (typeof doc === "number") {
+        if (doc === /* BreakParent */1) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+      switch (doc.TAG | 0) {
+        case /* Concat */1 :
+            return List.fold_left((function (forceBreak, child) {
+                          var childForcesBreak = walk(child);
+                          if (forceBreak) {
+                            return true;
+                          } else {
+                            return childForcesBreak;
+                          }
+                        }), false, doc._0);
+        case /* Indent */2 :
+            _doc = doc._0;
+            continue ;
+        case /* IfBreaks */3 :
+            var trueDoc = doc.yes;
+            var falseForceBreak = walk(doc.no);
+            if (falseForceBreak) {
+              walk(trueDoc);
+              doc.broken = true;
+              return true;
+            }
+            _doc = trueDoc;
+            continue ;
+        case /* LineBreak */5 :
+            return doc._0 >= 2;
+        case /* Group */6 :
+            var forceBreak = doc.shouldBreak;
+            var childForcesBreak = walk(doc.doc);
+            var shouldBreak = forceBreak || childForcesBreak;
+            doc.shouldBreak = shouldBreak;
+            return shouldBreak;
+        case /* CustomLayout */7 :
+            walk({
+                  TAG: /* Concat */1,
+                  _0: doc._0
+                });
+            return false;
+        default:
+          return false;
+      }
+    };
+  };
+  walk(doc);
+  
+}
+
+function willBreak(_doc) {
+  while(true) {
+    var doc = _doc;
+    if (typeof doc === "number") {
+      if (doc === /* BreakParent */1) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+    switch (doc.TAG | 0) {
+      case /* Concat */1 :
+          return List.exists(willBreak, doc._0);
+      case /* Indent */2 :
+          _doc = doc._0;
+          continue ;
+      case /* IfBreaks */3 :
+          if (willBreak(doc.yes)) {
+            return true;
+          }
+          _doc = doc.no;
+          continue ;
+      case /* LineBreak */5 :
+          return doc._0 >= 2;
+      case /* Group */6 :
+          var match = doc.shouldBreak;
+          if (match) {
+            return true;
+          }
+          _doc = doc.doc;
+          continue ;
+      case /* CustomLayout */7 :
+          var match$1 = doc._0;
+          if (!match$1) {
+            return false;
+          }
+          _doc = match$1.hd;
+          continue ;
+      default:
+        return false;
+    }
+  };
+}
+
+function join(sep, docs) {
+  var loop = function (_acc, sep, _docs) {
+    while(true) {
+      var docs = _docs;
+      var acc = _acc;
+      if (!docs) {
+        return List.rev(acc);
+      }
+      var xs = docs.tl;
+      var x = docs.hd;
+      if (!xs) {
+        return List.rev({
+                    hd: x,
+                    tl: acc
+                  });
+      }
+      _docs = xs;
+      _acc = {
+        hd: sep,
+        tl: {
+          hd: x,
+          tl: acc
+        }
+      };
+      continue ;
+    };
+  };
+  var l = loop(/* [] */0, sep, docs);
+  return {
+          TAG: /* Concat */1,
+          _0: _concat(/* [] */0, l)
+        };
+}
+
+function fits(w, stack) {
+  var width = {
+    contents: w
+  };
+  var result = {
+    contents: undefined
+  };
+  var calculate = function (_indent, _mode, _doc) {
+    while(true) {
+      var doc = _doc;
+      var mode = _mode;
+      var indent = _indent;
+      if (result.contents !== undefined) {
+        return ;
+      }
+      if (width.contents < 0) {
+        result.contents = false;
+        return ;
+      }
+      if (typeof doc === "number") {
+        return ;
+      }
+      switch (doc.TAG | 0) {
+        case /* Text */0 :
+            width.contents = width.contents - doc._0.length | 0;
+            return ;
+        case /* Concat */1 :
+            var _docs = doc._0;
+            while(true) {
+              var docs = _docs;
+              if (result.contents !== undefined) {
+                return ;
+              }
+              if (!docs) {
+                return ;
+              }
+              calculate(indent, mode, docs.hd);
+              _docs = docs.tl;
+              continue ;
+            };
+        case /* Indent */2 :
+            _doc = doc._0;
+            _indent = indent + 2 | 0;
+            continue ;
+        case /* IfBreaks */3 :
+            var match = doc.broken;
+            if (match) {
+              _doc = doc.yes;
+              continue ;
+            }
+            break;
+        case /* LineBreak */5 :
+            break;
+        case /* Group */6 :
+            var match$1 = doc.shouldBreak;
+            if (match$1) {
+              _doc = doc.doc;
+              _mode = /* Break */0;
+              continue ;
+            }
+            _doc = doc.doc;
+            continue ;
+        case /* CustomLayout */7 :
+            var match$2 = doc._0;
+            if (!match$2) {
+              return ;
+            }
+            _doc = match$2.hd;
+            continue ;
+        default:
+          return ;
+      }
+      if (mode) {
+        if (typeof doc !== "number") {
+          if (doc.TAG === /* IfBreaks */3) {
+            _doc = doc.no;
+            continue ;
+          }
+          var match$3 = doc._0;
+          if (match$3 !== 1) {
+            if (match$3 !== 0) {
+              result.contents = true;
+            } else {
+              width.contents = width.contents - 1 | 0;
+            }
+            return ;
+          } else {
+            return ;
+          }
+        }
+        
+      } else if (typeof doc !== "number") {
+        if (doc.TAG !== /* IfBreaks */3) {
+          result.contents = true;
+          return ;
+        }
+        _doc = doc.yes;
+        continue ;
+      }
+      
+    };
+  };
+  var _stack = stack;
+  while(true) {
+    var stack$1 = _stack;
+    var match = result.contents;
+    if (match !== undefined) {
+      return match;
+    }
+    if (!stack$1) {
+      return width.contents >= 0;
+    }
+    var match$1 = stack$1.hd;
+    calculate(match$1[0], match$1[1], match$1[2]);
+    _stack = stack$1.tl;
+    continue ;
+  };
+}
+
+function toString(width, doc) {
+  propagateForcedBreaks(doc);
+  var buffer = Res_minibuffer.create(1000);
+  var $$process = function (_pos, _lineSuffices, _stack) {
+    while(true) {
+      var stack = _stack;
+      var lineSuffices = _lineSuffices;
+      var pos = _pos;
+      if (stack) {
+        var rest = stack.tl;
+        var cmd = stack.hd;
+        var doc = cmd[2];
+        var mode = cmd[1];
+        var ind = cmd[0];
+        if (typeof doc === "number") {
+          if (doc === /* Nil */0) {
+            _stack = rest;
+            continue ;
+          }
+          _stack = rest;
+          continue ;
+        } else {
+          switch (doc.TAG | 0) {
+            case /* Text */0 :
+                var txt = doc._0;
+                Res_minibuffer.add_string(buffer, txt);
+                _stack = rest;
+                _pos = txt.length + pos | 0;
+                continue ;
+            case /* Concat */1 :
+                var ops = List.map((function(ind,mode){
+                    return function (doc) {
+                      return [
+                              ind,
+                              mode,
+                              doc
+                            ];
+                    }
+                    }(ind,mode)), doc._0);
+                _stack = List.append(ops, rest);
+                continue ;
+            case /* Indent */2 :
+                _stack = {
+                  hd: [
+                    ind + 2 | 0,
+                    mode,
+                    doc._0
+                  ],
+                  tl: rest
+                };
+                continue ;
+            case /* IfBreaks */3 :
+                var breakDoc = doc.yes;
+                var match = doc.broken;
+                if (match) {
+                  _stack = {
+                    hd: [
+                      ind,
+                      mode,
+                      breakDoc
+                    ],
+                    tl: rest
+                  };
+                  continue ;
+                }
+                if (mode === /* Break */0) {
+                  _stack = {
+                    hd: [
+                      ind,
+                      mode,
+                      breakDoc
+                    ],
+                    tl: rest
+                  };
+                  continue ;
+                }
+                _stack = {
+                  hd: [
+                    ind,
+                    mode,
+                    doc.no
+                  ],
+                  tl: rest
+                };
+                continue ;
+            case /* LineSuffix */4 :
+                _stack = rest;
+                _lineSuffices = {
+                  hd: [
+                    ind,
+                    mode,
+                    doc._0
+                  ],
+                  tl: lineSuffices
+                };
+                continue ;
+            case /* LineBreak */5 :
+                var lineStyle = doc._0;
+                if (mode === /* Break */0) {
+                  if (lineSuffices) {
+                    _stack = List.concat({
+                          hd: List.rev(lineSuffices),
+                          tl: {
+                            hd: {
+                              hd: cmd,
+                              tl: rest
+                            },
+                            tl: /* [] */0
+                          }
+                        });
+                    _lineSuffices = /* [] */0;
+                    _pos = ind;
+                    continue ;
+                  }
+                  if (lineStyle === /* Literal */3) {
+                    Res_minibuffer.add_char(buffer, /* '\n' */10);
+                    _stack = rest;
+                    _lineSuffices = /* [] */0;
+                    _pos = 0;
+                    continue ;
+                  }
+                  Res_minibuffer.flush_newline(buffer);
+                  Res_minibuffer.add_string(buffer, $$String.make(ind, /* ' ' */32));
+                  _stack = rest;
+                  _lineSuffices = /* [] */0;
+                  _pos = ind;
+                  continue ;
+                }
+                var pos$1;
+                switch (lineStyle) {
+                  case /* Classic */0 :
+                      Res_minibuffer.add_string(buffer, " ");
+                      pos$1 = pos + 1 | 0;
+                      break;
+                  case /* Soft */1 :
+                      pos$1 = pos;
+                      break;
+                  case /* Hard */2 :
+                      Res_minibuffer.flush_newline(buffer);
+                      pos$1 = 0;
+                      break;
+                  case /* Literal */3 :
+                      Res_minibuffer.add_char(buffer, /* '\n' */10);
+                      pos$1 = 0;
+                      break;
+                  
+                }
+                _stack = rest;
+                _pos = pos$1;
+                continue ;
+            case /* Group */6 :
+                var shouldBreak = doc.shouldBreak;
+                var doc$1 = doc.doc;
+                if (shouldBreak || !fits(width - pos | 0, {
+                        hd: [
+                          ind,
+                          /* Flat */1,
+                          doc$1
+                        ],
+                        tl: rest
+                      })) {
+                  _stack = {
+                    hd: [
+                      ind,
+                      /* Break */0,
+                      doc$1
+                    ],
+                    tl: rest
+                  };
+                  continue ;
+                }
+                _stack = {
+                  hd: [
+                    ind,
+                    /* Flat */1,
+                    doc$1
+                  ],
+                  tl: rest
+                };
+                continue ;
+            case /* CustomLayout */7 :
+                var findGroupThatFits = (function(pos,ind,rest){
+                return function findGroupThatFits(_groups) {
+                  while(true) {
+                    var groups = _groups;
+                    if (!groups) {
+                      return /* Nil */0;
+                    }
+                    var docs = groups.tl;
+                    var lastGroup = groups.hd;
+                    if (!docs) {
+                      return lastGroup;
+                    }
+                    if (fits(width - pos | 0, {
+                            hd: [
+                              ind,
+                              /* Flat */1,
+                              lastGroup
+                            ],
+                            tl: rest
+                          })) {
+                      return lastGroup;
+                    }
+                    _groups = docs;
+                    continue ;
+                  };
+                }
+                }(pos,ind,rest));
+                var doc$2 = findGroupThatFits(doc._0);
+                _stack = {
+                  hd: [
+                    ind,
+                    /* Flat */1,
+                    doc$2
+                  ],
+                  tl: rest
+                };
+                continue ;
+            
+          }
+        }
+      } else {
+        if (!lineSuffices) {
+          return ;
+        }
+        _stack = List.rev(lineSuffices);
+        _lineSuffices = /* [] */0;
+        _pos = 0;
+        continue ;
+      }
+    };
+  };
+  $$process(0, /* [] */0, {
+        hd: [
+          0,
+          /* Flat */1,
+          doc
+        ],
+        tl: /* [] */0
+      });
+  return Res_minibuffer.contents(buffer);
+}
+
+function debug(t) {
+  var toDoc = function (_x) {
+    while(true) {
+      var x = _x;
+      if (typeof x === "number") {
+        if (x === /* Nil */0) {
+          return {
+                  TAG: /* Text */0,
+                  _0: "nil"
+                };
+        } else {
+          return {
+                  TAG: /* Text */0,
+                  _0: "breakparent"
+                };
+        }
+      }
+      switch (x.TAG | 0) {
+        case /* Text */0 :
+            return {
+                    TAG: /* Text */0,
+                    _0: "text(\"" + (x._0 + "\")")
+                  };
+        case /* Concat */1 :
+            var docs = x._0;
+            if (!docs) {
+              return {
+                      TAG: /* Text */0,
+                      _0: "concat()"
+                    };
+            }
+            var l_0 = {
+              TAG: /* Text */0,
+              _0: ","
+            };
+            var l_1 = {
+              hd: line,
+              tl: /* [] */0
+            };
+            var l = {
+              hd: l_0,
+              tl: l_1
+            };
+            var l_1$1 = {
+              hd: join({
+                    TAG: /* Concat */1,
+                    _0: _concat(/* [] */0, l)
+                  }, List.map(toDoc, docs)),
+              tl: /* [] */0
+            };
+            var l$1 = {
+              hd: line,
+              tl: l_1$1
+            };
+            var l_0$1 = {
+              TAG: /* Text */0,
+              _0: "concat("
+            };
+            var l_1$2 = {
+              hd: {
+                TAG: /* Indent */2,
+                _0: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$1)
+                }
+              },
+              tl: {
+                hd: line,
+                tl: {
+                  hd: {
+                    TAG: /* Text */0,
+                    _0: ")"
+                  },
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$2 = {
+              hd: l_0$1,
+              tl: l_1$2
+            };
+            return {
+                    TAG: /* Group */6,
+                    shouldBreak: false,
+                    doc: {
+                      TAG: /* Concat */1,
+                      _0: _concat(/* [] */0, l$2)
+                    }
+                  };
+        case /* Indent */2 :
+            var l_0$2 = {
+              TAG: /* Text */0,
+              _0: "indent("
+            };
+            var l_1$3 = {
+              hd: softLine,
+              tl: {
+                hd: toDoc(x._0),
+                tl: {
+                  hd: softLine,
+                  tl: {
+                    hd: {
+                      TAG: /* Text */0,
+                      _0: ")"
+                    },
+                    tl: /* [] */0
+                  }
+                }
+              }
+            };
+            var l$3 = {
+              hd: l_0$2,
+              tl: l_1$3
+            };
+            return {
+                    TAG: /* Concat */1,
+                    _0: _concat(/* [] */0, l$3)
+                  };
+        case /* IfBreaks */3 :
+            var trueDoc = x.yes;
+            var match = x.broken;
+            if (match) {
+              _x = trueDoc;
+              continue ;
+            }
+            var l_0$3 = {
+              TAG: /* Text */0,
+              _0: ","
+            };
+            var l_1$4 = {
+              hd: line,
+              tl: /* [] */0
+            };
+            var l$4 = {
+              hd: l_0$3,
+              tl: l_1$4
+            };
+            var l_1$5 = {
+              hd: toDoc(trueDoc),
+              tl: {
+                hd: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$4)
+                },
+                tl: {
+                  hd: toDoc(x.no),
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$5 = {
+              hd: line,
+              tl: l_1$5
+            };
+            var l_0$4 = {
+              TAG: /* Text */0,
+              _0: "ifBreaks("
+            };
+            var l_1$6 = {
+              hd: {
+                TAG: /* Indent */2,
+                _0: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$5)
+                }
+              },
+              tl: {
+                hd: line,
+                tl: {
+                  hd: {
+                    TAG: /* Text */0,
+                    _0: ")"
+                  },
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$6 = {
+              hd: l_0$4,
+              tl: l_1$6
+            };
+            return {
+                    TAG: /* Group */6,
+                    shouldBreak: false,
+                    doc: {
+                      TAG: /* Concat */1,
+                      _0: _concat(/* [] */0, l$6)
+                    }
+                  };
+        case /* LineSuffix */4 :
+            var l_1$7 = {
+              hd: toDoc(x._0),
+              tl: /* [] */0
+            };
+            var l$7 = {
+              hd: line,
+              tl: l_1$7
+            };
+            var l_0$5 = {
+              TAG: /* Text */0,
+              _0: "linesuffix("
+            };
+            var l_1$8 = {
+              hd: {
+                TAG: /* Indent */2,
+                _0: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$7)
+                }
+              },
+              tl: {
+                hd: line,
+                tl: {
+                  hd: {
+                    TAG: /* Text */0,
+                    _0: ")"
+                  },
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$8 = {
+              hd: l_0$5,
+              tl: l_1$8
+            };
+            return {
+                    TAG: /* Group */6,
+                    shouldBreak: false,
+                    doc: {
+                      TAG: /* Concat */1,
+                      _0: _concat(/* [] */0, l$8)
+                    }
+                  };
+        case /* LineBreak */5 :
+            var breakTxt;
+            switch (x._0) {
+              case /* Classic */0 :
+                  breakTxt = "Classic";
+                  break;
+              case /* Soft */1 :
+                  breakTxt = "Soft";
+                  break;
+              case /* Hard */2 :
+                  breakTxt = "Hard";
+                  break;
+              case /* Literal */3 :
+                  breakTxt = "Liteal";
+                  break;
+              
+            }
+            return {
+                    TAG: /* Text */0,
+                    _0: "LineBreak(" + (breakTxt + ")")
+                  };
+        case /* Group */6 :
+            var shouldBreak = x.shouldBreak;
+            var l_0$6 = {
+              TAG: /* Text */0,
+              _0: ","
+            };
+            var l_1$9 = {
+              hd: line,
+              tl: /* [] */0
+            };
+            var l$9 = {
+              hd: l_0$6,
+              tl: l_1$9
+            };
+            var l_1$10 = {
+              hd: {
+                TAG: /* Text */0,
+                _0: "{shouldBreak: " + (Pervasives.string_of_bool(shouldBreak) + "}")
+              },
+              tl: {
+                hd: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$9)
+                },
+                tl: {
+                  hd: toDoc(x.doc),
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$10 = {
+              hd: line,
+              tl: l_1$10
+            };
+            var l_0$7 = {
+              TAG: /* Text */0,
+              _0: "Group("
+            };
+            var l_1$11 = {
+              hd: {
+                TAG: /* Indent */2,
+                _0: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$10)
+                }
+              },
+              tl: {
+                hd: line,
+                tl: {
+                  hd: {
+                    TAG: /* Text */0,
+                    _0: ")"
+                  },
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$11 = {
+              hd: l_0$7,
+              tl: l_1$11
+            };
+            return {
+                    TAG: /* Group */6,
+                    shouldBreak: false,
+                    doc: {
+                      TAG: /* Concat */1,
+                      _0: _concat(/* [] */0, l$11)
+                    }
+                  };
+        case /* CustomLayout */7 :
+            var l_0$8 = {
+              TAG: /* Text */0,
+              _0: ","
+            };
+            var l_1$12 = {
+              hd: line,
+              tl: /* [] */0
+            };
+            var l$12 = {
+              hd: l_0$8,
+              tl: l_1$12
+            };
+            var l_1$13 = {
+              hd: join({
+                    TAG: /* Concat */1,
+                    _0: _concat(/* [] */0, l$12)
+                  }, List.map(toDoc, x._0)),
+              tl: /* [] */0
+            };
+            var l$13 = {
+              hd: line,
+              tl: l_1$13
+            };
+            var l_0$9 = {
+              TAG: /* Text */0,
+              _0: "customLayout("
+            };
+            var l_1$14 = {
+              hd: {
+                TAG: /* Indent */2,
+                _0: {
+                  TAG: /* Concat */1,
+                  _0: _concat(/* [] */0, l$13)
+                }
+              },
+              tl: {
+                hd: line,
+                tl: {
+                  hd: {
+                    TAG: /* Text */0,
+                    _0: ")"
+                  },
+                  tl: /* [] */0
+                }
+              }
+            };
+            var l$14 = {
+              hd: l_0$9,
+              tl: l_1$14
+            };
+            return {
+                    TAG: /* Group */6,
+                    shouldBreak: false,
+                    doc: {
+                      TAG: /* Concat */1,
+                      _0: _concat(/* [] */0, l$14)
+                    }
+                  };
+        
+      }
+    };
+  };
+  var doc = toDoc(t);
+  console.log(toString(10, doc));
+  
+}
+
+var MiniBuffer;
+
+var nil = /* Nil */0;
+
+var hardLine = {
+  TAG: /* LineBreak */5,
+  _0: /* Hard */2
+};
+
+var literalLine = {
+  TAG: /* LineBreak */5,
+  _0: /* Literal */3
+};
+
+var breakParent = /* BreakParent */1;
+
+var space = {
+  TAG: /* Text */0,
+  _0: " "
+};
+
+var dot = {
+  TAG: /* Text */0,
+  _0: "."
+};
+
+var dotdot = {
+  TAG: /* Text */0,
+  _0: ".."
+};
+
+var dotdotdot = {
+  TAG: /* Text */0,
+  _0: "..."
+};
+
+var lessThan = {
+  TAG: /* Text */0,
+  _0: "<"
+};
+
+var greaterThan = {
+  TAG: /* Text */0,
+  _0: ">"
+};
+
+var lbrace = {
+  TAG: /* Text */0,
+  _0: "{"
+};
+
+var rbrace = {
+  TAG: /* Text */0,
+  _0: "}"
+};
+
+var lparen = {
+  TAG: /* Text */0,
+  _0: "("
+};
+
+var rparen = {
+  TAG: /* Text */0,
+  _0: ")"
+};
+
+var lbracket = {
+  TAG: /* Text */0,
+  _0: "["
+};
+
+var rbracket = {
+  TAG: /* Text */0,
+  _0: "]"
+};
+
+var question = {
+  TAG: /* Text */0,
+  _0: "?"
+};
+
+var tilde = {
+  TAG: /* Text */0,
+  _0: "~"
+};
+
+var equal = {
+  TAG: /* Text */0,
+  _0: "="
+};
+
+var doubleQuote = {
+  TAG: /* Text */0,
+  _0: "\""
+};
+
+export {
+  MiniBuffer ,
+  nil ,
+  line ,
+  hardLine ,
+  softLine ,
+  literalLine ,
+  text ,
+  _concat ,
+  concat ,
+  indent ,
+  ifBreaks ,
+  lineSuffix ,
+  group ,
+  breakableGroup ,
+  customLayout ,
+  breakParent ,
+  space ,
+  comma ,
+  dot ,
+  dotdot ,
+  dotdotdot ,
+  lessThan ,
+  greaterThan ,
+  lbrace ,
+  rbrace ,
+  lparen ,
+  rparen ,
+  lbracket ,
+  rbracket ,
+  question ,
+  tilde ,
+  equal ,
+  trailingComma ,
+  doubleQuote ,
+  propagateForcedBreaks ,
+  willBreak ,
+  join ,
+  fits ,
+  toString ,
+  debug ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_doc.res b/analysis/examples/larger-project/src/res_doc.res
new file mode 100644
index 0000000000..efa0f9ef54
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_doc.res
@@ -0,0 +1,362 @@
+module MiniBuffer = Res_minibuffer
+
+type mode = Break | Flat
+
+type lineStyle =
+  | Classic /* fits? -> replace with space */
+  | Soft /* fits? -> replaced with nothing */
+  | Hard /* always included, forces breaks in parents */
+  /* always included, forces breaks in parents, but doesn't increase indentation
+   use case: template literals, multiline string content */
+  | Literal
+
+type rec t =
+  | Nil
+  | Text(string)
+  | Concat(list<t>)
+  | Indent(t)
+  | IfBreaks({
+      yes: t,
+      no: t,
+      mutable broken: bool,
+    }) /* when broken is true, treat as the yes branch */
+  | LineSuffix(t)
+  | LineBreak(lineStyle)
+  | Group({mutable shouldBreak: bool, doc: t})
+  | CustomLayout(list<t>)
+  | BreakParent
+
+let nil = Nil
+let line = LineBreak(Classic)
+let hardLine = LineBreak(Hard)
+let softLine = LineBreak(Soft)
+let literalLine = LineBreak(Literal)
+let text = s => Text(s)
+
+/* Optimization. We eagerly collapse and reduce whatever allocation we can */
+let rec _concat = (acc, l) =>
+  switch l {
+  | list{Text(s1), Text(s2), ...rest} => list{Text(s1 ++ s2), ..._concat(acc, rest)}
+  | list{Nil, ...rest} => _concat(acc, rest)
+  | list{Concat(l2), ...rest} => _concat(_concat(acc, rest), l2) /* notice the order here */
+  | list{x, ...rest} =>
+    let rest1 = _concat(acc, rest)
+    if rest1 === rest {
+      l
+    } else {
+      list{x, ...rest1}
+    }
+  | list{} => acc
+  }
+
+let concat = l => Concat(_concat(list{}, l))
+
+let indent = d => Indent(d)
+let ifBreaks = (t, f) => IfBreaks({yes: t, no: f, broken: false})
+let lineSuffix = d => LineSuffix(d)
+let group = d => Group({shouldBreak: false, doc: d})
+let breakableGroup = (~forceBreak, d) => Group({shouldBreak: forceBreak, doc: d})
+let customLayout = gs => CustomLayout(gs)
+let breakParent = BreakParent
+
+let space = Text(" ")
+let comma = Text(",")
+let dot = Text(".")
+let dotdot = Text("..")
+let dotdotdot = Text("...")
+let lessThan = Text("<")
+let greaterThan = Text(">")
+let lbrace = Text("{")
+let rbrace = Text("}")
+let lparen = Text("(")
+let rparen = Text(")")
+let lbracket = Text("[")
+let rbracket = Text("]")
+let question = Text("?")
+let tilde = Text("~")
+let equal = Text("=")
+let trailingComma = ifBreaks(comma, nil)
+let doubleQuote = Text("\"")
+
+let propagateForcedBreaks = doc => {
+  let rec walk = doc =>
+    switch doc {
+    | Text(_) | Nil | LineSuffix(_) => false
+    | BreakParent => true
+    | LineBreak(Hard | Literal) => true
+    | LineBreak(Classic | Soft) => false
+    | Indent(children) =>
+      let childForcesBreak = walk(children)
+      childForcesBreak
+    | IfBreaks({yes: trueDoc, no: falseDoc} as ib) =>
+      let falseForceBreak = walk(falseDoc)
+      if falseForceBreak {
+        let _ = walk(trueDoc)
+        ib.broken = true
+        true
+      } else {
+        let forceBreak = walk(trueDoc)
+        forceBreak
+      }
+    | Group({shouldBreak: forceBreak, doc: children} as gr) =>
+      let childForcesBreak = walk(children)
+      let shouldBreak = forceBreak || childForcesBreak
+      gr.shouldBreak = shouldBreak
+      shouldBreak
+    | Concat(children) => List.fold_left((forceBreak, child) => {
+        let childForcesBreak = walk(child)
+        forceBreak || childForcesBreak
+      }, false, children)
+    | CustomLayout(children) =>
+      /* When using CustomLayout, we don't want to propagate forced breaks
+       * from the children up. By definition it picks the first layout that fits
+       * otherwise it takes the last of the list.
+       * However we do want to propagate forced breaks in the sublayouts. They
+       * might need to be broken. We just don't propagate them any higher here */
+      let _ = walk(Concat(children))
+      false
+    }
+
+  let _ = walk(doc)
+}
+
+/* See documentation in interface file */
+let rec willBreak = doc =>
+  switch doc {
+  | LineBreak(Hard | Literal) | BreakParent | Group({shouldBreak: true}) => true
+  | Group({doc}) | Indent(doc) | CustomLayout(list{doc, ..._}) => willBreak(doc)
+  | Concat(docs) => List.exists(willBreak, docs)
+  | IfBreaks({yes, no}) => willBreak(yes) || willBreak(no)
+  | _ => false
+  }
+
+let join = (~sep, docs) => {
+  let rec loop = (acc, sep, docs) =>
+    switch docs {
+    | list{} => List.rev(acc)
+    | list{x} => List.rev(list{x, ...acc})
+    | list{x, ...xs} => loop(list{sep, x, ...acc}, sep, xs)
+    }
+
+  concat(loop(list{}, sep, docs))
+}
+
+let fits = (w, stack) => {
+  let width = ref(w)
+  let result = ref(None)
+
+  let rec calculate = (indent, mode, doc) =>
+    switch (mode, doc) {
+    | _ if result.contents !== None => ()
+    | _ if width.contents < 0 => result := Some(false)
+    | (_, Nil)
+    | (_, LineSuffix(_))
+    | (_, BreakParent) => ()
+    | (_, Text(txt)) => width := width.contents - String.length(txt)
+    | (_, Indent(doc)) => calculate(indent + 2, mode, doc)
+    | (Flat, LineBreak(Hard))
+    | (Flat, LineBreak(Literal)) =>
+      result := Some(true)
+    | (Flat, LineBreak(Classic)) => width := width.contents - 1
+    | (Flat, LineBreak(Soft)) => ()
+    | (Break, LineBreak(_)) => result := Some(true)
+    | (_, Group({shouldBreak: true, doc})) => calculate(indent, Break, doc)
+    | (_, Group({doc})) => calculate(indent, mode, doc)
+    | (_, IfBreaks({yes: breakDoc, broken: true})) => calculate(indent, mode, breakDoc)
+    | (Break, IfBreaks({yes: breakDoc})) => calculate(indent, mode, breakDoc)
+    | (Flat, IfBreaks({no: flatDoc})) => calculate(indent, mode, flatDoc)
+    | (_, Concat(docs)) => calculateConcat(indent, mode, docs)
+    | (_, CustomLayout(list{hd, ..._})) =>
+      /* TODO: if we have nested custom layouts, what we should do here? */
+      calculate(indent, mode, hd)
+    | (_, CustomLayout(list{})) => ()
+    }
+  and calculateConcat = (indent, mode, docs) =>
+    if result.contents === None {
+      switch docs {
+      | list{} => ()
+      | list{doc, ...rest} =>
+        calculate(indent, mode, doc)
+        calculateConcat(indent, mode, rest)
+      }
+    }
+
+  let rec calculateAll = stack =>
+    switch (result.contents, stack) {
+    | (Some(r), _) => r
+    | (None, list{}) => width.contents >= 0
+    | (None, list{(indent, mode, doc), ...rest}) =>
+      calculate(indent, mode, doc)
+      calculateAll(rest)
+    }
+
+  calculateAll(stack)
+}
+
+let toString = (~width, doc) => {
+  propagateForcedBreaks(doc)
+  let buffer = MiniBuffer.create(1000)
+
+  let rec process = (~pos, lineSuffices, stack) =>
+    switch stack {
+    | list{(ind, mode, doc) as cmd, ...rest} =>
+      switch doc {
+      | Nil | BreakParent => process(~pos, lineSuffices, rest)
+      | Text(txt) =>
+        MiniBuffer.add_string(buffer, txt)
+        process(~pos=String.length(txt) + pos, lineSuffices, rest)
+      | LineSuffix(doc) => process(~pos, list{(ind, mode, doc), ...lineSuffices}, rest)
+      | Concat(docs) =>
+        let ops = List.map(doc => (ind, mode, doc), docs)
+        process(~pos, lineSuffices, List.append(ops, rest))
+      | Indent(doc) => process(~pos, lineSuffices, list{(ind + 2, mode, doc), ...rest})
+      | IfBreaks({yes: breakDoc, broken: true}) =>
+        process(~pos, lineSuffices, list{(ind, mode, breakDoc), ...rest})
+      | IfBreaks({yes: breakDoc, no: flatDoc}) =>
+        if mode == Break {
+          process(~pos, lineSuffices, list{(ind, mode, breakDoc), ...rest})
+        } else {
+          process(~pos, lineSuffices, list{(ind, mode, flatDoc), ...rest})
+        }
+      | LineBreak(lineStyle) =>
+        if mode == Break {
+          switch lineSuffices {
+          | list{} =>
+            if lineStyle == Literal {
+              MiniBuffer.add_char(buffer, '\n')
+              process(~pos=0, list{}, rest)
+            } else {
+              MiniBuffer.flush_newline(buffer)
+              MiniBuffer.add_string(buffer, @doesNotRaise String.make(ind, ' '))
+              process(~pos=ind, list{}, rest)
+            }
+          | _docs =>
+            process(~pos=ind, list{}, List.concat(list{List.rev(lineSuffices), list{cmd, ...rest}}))
+          }
+        } else {
+          /* mode = Flat */
+          let pos = switch lineStyle {
+          | Classic =>
+            MiniBuffer.add_string(buffer, " ")
+            pos + 1
+          | Hard =>
+            MiniBuffer.flush_newline(buffer)
+            0
+          | Literal =>
+            MiniBuffer.add_char(buffer, '\n')
+            0
+          | Soft => pos
+          }
+
+          process(~pos, lineSuffices, rest)
+        }
+      | Group({shouldBreak, doc}) =>
+        if shouldBreak || !fits(width - pos, list{(ind, Flat, doc), ...rest}) {
+          process(~pos, lineSuffices, list{(ind, Break, doc), ...rest})
+        } else {
+          process(~pos, lineSuffices, list{(ind, Flat, doc), ...rest})
+        }
+      | CustomLayout(docs) =>
+        let rec findGroupThatFits = groups =>
+          switch groups {
+          | list{} => Nil
+          | list{lastGroup} => lastGroup
+          | list{doc, ...docs} =>
+            if fits(width - pos, list{(ind, Flat, doc), ...rest}) {
+              doc
+            } else {
+              findGroupThatFits(docs)
+            }
+          }
+
+        let doc = findGroupThatFits(docs)
+        process(~pos, lineSuffices, list{(ind, Flat, doc), ...rest})
+      }
+    | list{} =>
+      switch lineSuffices {
+      | list{} => ()
+      | suffices => process(~pos=0, list{}, List.rev(suffices))
+      }
+    }
+
+  process(~pos=0, list{}, list{(0, Flat, doc)})
+  MiniBuffer.contents(buffer)
+}
+
+@live
+let debug = t => {
+  let rec toDoc = x =>
+    switch x {
+    | Nil => text("nil")
+    | BreakParent => text("breakparent")
+    | Text(txt) => text("text(\"" ++ (txt ++ "\")"))
+    | LineSuffix(doc) =>
+      group(
+        concat(list{text("linesuffix("), indent(concat(list{line, toDoc(doc)})), line, text(")")}),
+      )
+    | Concat(list{}) => text("concat()")
+    | Concat(docs) =>
+      group(
+        concat(list{
+          text("concat("),
+          indent(
+            concat(list{line, join(~sep=concat(list{text(","), line}), List.map(toDoc, docs))}),
+          ),
+          line,
+          text(")"),
+        }),
+      )
+    | CustomLayout(docs) =>
+      group(
+        concat(list{
+          text("customLayout("),
+          indent(
+            concat(list{line, join(~sep=concat(list{text(","), line}), List.map(toDoc, docs))}),
+          ),
+          line,
+          text(")"),
+        }),
+      )
+    | Indent(doc) => concat(list{text("indent("), softLine, toDoc(doc), softLine, text(")")})
+    | IfBreaks({yes: trueDoc, broken: true}) => toDoc(trueDoc)
+    | IfBreaks({yes: trueDoc, no: falseDoc}) =>
+      group(
+        concat(list{
+          text("ifBreaks("),
+          indent(
+            concat(list{line, toDoc(trueDoc), concat(list{text(","), line}), toDoc(falseDoc)}),
+          ),
+          line,
+          text(")"),
+        }),
+      )
+    | LineBreak(break) =>
+      let breakTxt = switch break {
+      | Classic => "Classic"
+      | Soft => "Soft"
+      | Hard => "Hard"
+      | Literal => "Liteal"
+      }
+
+      text("LineBreak(" ++ (breakTxt ++ ")"))
+    | Group({shouldBreak, doc}) =>
+      group(
+        concat(list{
+          text("Group("),
+          indent(
+            concat(list{
+              line,
+              text("{shouldBreak: " ++ (string_of_bool(shouldBreak) ++ "}")),
+              concat(list{text(","), line}),
+              toDoc(doc),
+            }),
+          ),
+          line,
+          text(")"),
+        }),
+      )
+    }
+
+  let doc = toDoc(t)
+  toString(~width=10, doc) |> print_endline
+}
diff --git a/analysis/examples/larger-project/src/res_grammar.js b/analysis/examples/larger-project/src/res_grammar.js
new file mode 100644
index 0000000000..cc5feef37c
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_grammar.js
@@ -0,0 +1,1158 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Res_token from "./res_token.js";
+
+function toString(x) {
+  if (typeof x !== "number") {
+    return "an expression after the operator \"" + (Res_token.toString(x._0) + "\"");
+  }
+  switch (x) {
+    case /* OpenDescription */0 :
+        return "an open description";
+    case /* ModuleLongIdent */1 :
+        return "a module path";
+    case /* Ternary */2 :
+        return "a ternary expression";
+    case /* Es6ArrowExpr */3 :
+        return "an es6 arrow function";
+    case /* Jsx */4 :
+        return "a jsx expression";
+    case /* JsxAttribute */5 :
+        return "a jsx attribute";
+    case /* JsxChild */6 :
+        return "jsx child";
+    case /* ExprOperand */7 :
+        return "a basic expression";
+    case /* ExprUnary */8 :
+        return "a unary expression";
+    case /* ExprSetField */9 :
+        return "a record field mutation";
+    case /* ExprBlock */10 :
+        return "a block with expressions";
+    case /* ExprCall */11 :
+        return "a function application";
+    case /* ExprList */12 :
+        return "multiple expressions";
+    case /* ExprArrayAccess */13 :
+        return "an array access expression";
+    case /* ExprArrayMutation */14 :
+        return "an array mutation";
+    case /* ExprIf */15 :
+        return "an if expression";
+    case /* ExprFor */16 :
+        return "a for expression";
+    case /* IfCondition */17 :
+        return "the condition of an if expression";
+    case /* IfBranch */18 :
+        return "the true-branch of an if expression";
+    case /* ElseBranch */19 :
+        return "the else-branch of an if expression";
+    case /* External */21 :
+        return "an external";
+    case /* PatternMatching */22 :
+        return "the cases of a pattern match";
+    case /* PatternMatchCase */23 :
+        return "a pattern match case";
+    case /* LetBinding */24 :
+        return "a let binding";
+    case /* PatternList */25 :
+        return "multiple patterns";
+    case /* PatternOcamlList */26 :
+        return "a list pattern";
+    case /* PatternRecord */27 :
+        return "a record pattern";
+    case /* TypeDef */28 :
+        return "a type definition";
+    case /* TypeConstrName */29 :
+        return "a type-constructor name";
+    case /* TypeParams */30 :
+        return "type parameters";
+    case /* TypeParam */31 :
+        return "a type parameter";
+    case /* PackageConstraint */32 :
+        return "a package constraint";
+    case /* TypeRepresentation */33 :
+        return "a type representation";
+    case /* RecordDecl */34 :
+        return "a record declaration";
+    case /* ConstructorDeclaration */35 :
+        return "a constructor declaration";
+    case /* ParameterList */36 :
+        return "parameters";
+    case /* StringFieldDeclarations */37 :
+        return "string field declarations";
+    case /* FieldDeclarations */38 :
+        return "field declarations";
+    case /* TypExprList */39 :
+        return "list of types";
+    case /* FunctorArgs */40 :
+        return "functor arguments";
+    case /* ModExprList */41 :
+        return "list of module expressions";
+    case /* TypeParameters */42 :
+        return "list of type parameters";
+    case /* RecordRows */43 :
+        return "rows of a record";
+    case /* RecordRowsStringKey */44 :
+        return "rows of a record with string keys";
+    case /* ArgumentList */45 :
+        return "arguments";
+    case /* Signature */46 :
+        return "signature";
+    case /* Specification */47 :
+        return "specification";
+    case /* Structure */48 :
+        return "structure";
+    case /* Implementation */49 :
+        return "implementation";
+    case /* Attribute */50 :
+        return "an attribute";
+    case /* TypeConstraint */51 :
+        return "constraints on a type";
+    case /* TypeExpression */20 :
+    case /* AtomicTypExpr */52 :
+        return "a type";
+    case /* ListExpr */53 :
+        return "an ocaml list expr";
+    case /* JsFfiImport */54 :
+        return "js ffi import";
+    case /* Pattern */55 :
+        return "pattern";
+    case /* AttributePayload */56 :
+        return "an attribute payload";
+    case /* TagNames */57 :
+        return "tag names";
+    
+  }
+}
+
+function isSignatureItemStart(x) {
+  if (typeof x !== "number") {
+    return false;
+  }
+  if (x < 10) {
+    return x > 8 || x < 1;
+  }
+  if (x === 27) {
+    return true;
+  }
+  if (x < 59) {
+    return false;
+  }
+  switch (x) {
+    case /* Private */61 :
+    case /* Mutable */62 :
+    case /* Constraint */63 :
+    case /* Of */66 :
+    case /* Land */67 :
+    case /* Lor */68 :
+    case /* Band */69 :
+    case /* BangEqual */70 :
+    case /* BangEqualEqual */71 :
+    case /* LessEqual */72 :
+    case /* GreaterEqual */73 :
+    case /* ColonEqual */74 :
+    case /* Percent */77 :
+    case /* List */79 :
+    case /* Backtick */80 :
+    case /* BarGreater */81 :
+    case /* Try */82 :
+    case /* Import */83 :
+        return false;
+    case /* External */59 :
+    case /* Typ */60 :
+    case /* Include */64 :
+    case /* Module */65 :
+    case /* At */75 :
+    case /* AtAt */76 :
+    case /* PercentPercent */78 :
+    case /* Export */84 :
+        return true;
+    
+  }
+}
+
+function isAtomicPatternStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Underscore */12 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* Exception */27 :
+      case /* Lazy */47 :
+      case /* Percent */77 :
+      case /* List */79 :
+      case /* Backtick */80 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* String */3 :
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isAtomicExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* True */1 :
+      case /* False */2 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* LessThan */42 :
+      case /* Hash */44 :
+      case /* Module */65 :
+      case /* Percent */77 :
+      case /* List */79 :
+      case /* Backtick */80 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isAtomicTypExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Underscore */12 :
+      case /* SingleQuote */13 :
+      case /* Lparen */18 :
+      case /* Lbrace */22 :
+      case /* Percent */77 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* True */1 :
+      case /* False */2 :
+      case /* Bang */7 :
+      case /* Underscore */12 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* Minus */34 :
+      case /* MinusDot */35 :
+      case /* Plus */36 :
+      case /* PlusDot */37 :
+      case /* LessThan */42 :
+      case /* Hash */44 :
+      case /* Assert */46 :
+      case /* Lazy */47 :
+      case /* If */50 :
+      case /* For */52 :
+      case /* While */54 :
+      case /* Switch */55 :
+      case /* Module */65 :
+      case /* At */75 :
+      case /* Percent */77 :
+      case /* List */79 :
+      case /* Backtick */80 :
+      case /* Try */82 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isJsxAttributeStart(x) {
+  if (typeof x === "number") {
+    if (x === /* Question */49) {
+      return true;
+    } else {
+      return false;
+    }
+  } else if (x.TAG === /* Lident */4) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isStructureItemStart(x) {
+  if (typeof x === "number") {
+    if (x >= 10) {
+      if (x === 27) {
+        return true;
+      }
+      if (x >= 59) {
+        switch (x) {
+          case /* Private */61 :
+          case /* Mutable */62 :
+          case /* Constraint */63 :
+          case /* Of */66 :
+          case /* Land */67 :
+          case /* Lor */68 :
+          case /* Band */69 :
+          case /* BangEqual */70 :
+          case /* BangEqualEqual */71 :
+          case /* LessEqual */72 :
+          case /* GreaterEqual */73 :
+          case /* ColonEqual */74 :
+          case /* Percent */77 :
+          case /* List */79 :
+          case /* Backtick */80 :
+          case /* BarGreater */81 :
+          case /* Try */82 :
+              break;
+          case /* External */59 :
+          case /* Typ */60 :
+          case /* Include */64 :
+          case /* Module */65 :
+          case /* At */75 :
+          case /* AtAt */76 :
+          case /* PercentPercent */78 :
+          case /* Import */83 :
+          case /* Export */84 :
+              return true;
+          
+        }
+      }
+      
+    } else if (x > 8 || x < 1) {
+      return true;
+    }
+    
+  }
+  if (isExprStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isPatternStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* True */1 :
+      case /* False */2 :
+      case /* Underscore */12 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* Exception */27 :
+      case /* Minus */34 :
+      case /* Plus */36 :
+      case /* Hash */44 :
+      case /* Lazy */47 :
+      case /* Module */65 :
+      case /* At */75 :
+      case /* Percent */77 :
+      case /* List */79 :
+      case /* Backtick */80 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isParameterStart(x) {
+  if (typeof x === "number") {
+    if (x > 48 || x < 4) {
+      if (x === 60) {
+        return true;
+      }
+      
+    } else if (x > 47 || x < 5) {
+      return true;
+    }
+    
+  }
+  if (isPatternStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isStringFieldDeclStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* DotDotDot */6 :
+      case /* At */75 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* String */3 :
+      case /* Lident */4 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isFieldDeclStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Mutable */62 :
+      case /* At */75 :
+          return true;
+      default:
+        if (Res_token.isKeyword(x)) {
+          return true;
+        } else {
+          return false;
+        }
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        if (Res_token.isKeyword(x)) {
+          return true;
+        } else {
+          return false;
+        }
+    }
+  }
+}
+
+function isRecordDeclStart(x) {
+  if (typeof x !== "number") {
+    if (x.TAG === /* Lident */4) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  switch (x) {
+    case /* Mutable */62 :
+    case /* At */75 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isTypExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Underscore */12 :
+      case /* SingleQuote */13 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* Module */65 :
+      case /* At */75 :
+      case /* Percent */77 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isTypeParameterStart(x) {
+  if (typeof x === "number") {
+    if (x !== 4 && x !== 48 && !isTypExprStart(x)) {
+      return false;
+    } else {
+      return true;
+    }
+  } else if (isTypExprStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isTypeParamStart(x) {
+  if (typeof x === "number") {
+    if (x > 34 || x < 12) {
+      return x === 36;
+    } else {
+      return x > 33 || x < 14;
+    }
+  } else {
+    return false;
+  }
+}
+
+function isFunctorArgStart(x) {
+  if (typeof x !== "number") {
+    if (x.TAG === /* Uident */5) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  switch (x) {
+    case /* Underscore */12 :
+    case /* Lparen */18 :
+    case /* Lbrace */22 :
+    case /* At */75 :
+    case /* Percent */77 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isModExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Lparen */18 :
+      case /* Lbrace */22 :
+      case /* At */75 :
+      case /* Percent */77 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Lident */4 :
+          if (x._0 === "unpack") {
+            return true;
+          } else {
+            return false;
+          }
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isRecordRowStart(x) {
+  if (typeof x === "number") {
+    if (x === /* DotDotDot */6 || Res_token.isKeyword(x)) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  switch (x.TAG | 0) {
+    case /* Lident */4 :
+    case /* Uident */5 :
+        return true;
+    default:
+      if (Res_token.isKeyword(x)) {
+        return true;
+      } else {
+        return false;
+      }
+  }
+}
+
+function isRecordRowStringKeyStart(x) {
+  if (typeof x === "number" || x.TAG !== /* String */3) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function isArgumentStart(x) {
+  if (typeof x === "number") {
+    if (x > 12 || x < 4) {
+      if (x === 48) {
+        return true;
+      }
+      
+    } else if (x > 11 || x < 5) {
+      return true;
+    }
+    
+  }
+  if (isExprStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isPatternMatchStart(x) {
+  if (x === 17 || isPatternStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isPatternOcamlListStart(x) {
+  if (x === 6 || isPatternStart(x)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isPatternRecordItemStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* DotDotDot */6 :
+      case /* Underscore */12 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isAttributeStart(x) {
+  return x === 75;
+}
+
+function isJsFfiImportStart(x) {
+  if (typeof x === "number") {
+    if (x === /* At */75) {
+      return true;
+    } else {
+      return false;
+    }
+  } else if (x.TAG === /* Lident */4) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isBlockExprStart(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Open */0 :
+      case /* True */1 :
+      case /* False */2 :
+      case /* Bang */7 :
+      case /* Let */9 :
+      case /* Underscore */12 :
+      case /* Lparen */18 :
+      case /* Lbracket */20 :
+      case /* Lbrace */22 :
+      case /* Exception */27 :
+      case /* Forwardslash */29 :
+      case /* Minus */34 :
+      case /* MinusDot */35 :
+      case /* Plus */36 :
+      case /* PlusDot */37 :
+      case /* LessThan */42 :
+      case /* Hash */44 :
+      case /* Assert */46 :
+      case /* Lazy */47 :
+      case /* If */50 :
+      case /* For */52 :
+      case /* While */54 :
+      case /* Switch */55 :
+      case /* Module */65 :
+      case /* At */75 :
+      case /* Percent */77 :
+      case /* List */79 :
+      case /* Backtick */80 :
+      case /* Try */82 :
+          return true;
+      default:
+        return false;
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+      case /* Int */1 :
+      case /* Float */2 :
+      case /* String */3 :
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return true;
+      default:
+        return false;
+    }
+  }
+}
+
+function isListElement(grammar, token) {
+  if (typeof grammar !== "number") {
+    return false;
+  }
+  switch (grammar) {
+    case /* JsxAttribute */5 :
+        return isJsxAttributeStart(token);
+    case /* PatternMatching */22 :
+        return isPatternMatchStart(token);
+    case /* PatternList */25 :
+        if (token === /* DotDotDot */6) {
+          return true;
+        } else {
+          return isPatternStart(token);
+        }
+    case /* PatternOcamlList */26 :
+        return isPatternOcamlListStart(token);
+    case /* PatternRecord */27 :
+        return isPatternRecordItemStart(token);
+    case /* TypeParams */30 :
+        return isTypeParamStart(token);
+    case /* PackageConstraint */32 :
+        return token === /* And */10;
+    case /* RecordDecl */34 :
+        return isRecordDeclStart(token);
+    case /* ConstructorDeclaration */35 :
+        return token === /* Bar */17;
+    case /* ParameterList */36 :
+        return isParameterStart(token);
+    case /* StringFieldDeclarations */37 :
+        return isStringFieldDeclStart(token);
+    case /* FieldDeclarations */38 :
+        return isFieldDeclStart(token);
+    case /* TypExprList */39 :
+        if (isTypExprStart(token)) {
+          return true;
+        } else {
+          return token === /* LessThan */42;
+        }
+    case /* FunctorArgs */40 :
+        return isFunctorArgStart(token);
+    case /* ModExprList */41 :
+        return isModExprStart(token);
+    case /* TypeParameters */42 :
+        return isTypeParameterStart(token);
+    case /* RecordRows */43 :
+        return isRecordRowStart(token);
+    case /* RecordRowsStringKey */44 :
+        return isRecordRowStringKeyStart(token);
+    case /* ArgumentList */45 :
+        return isArgumentStart(token);
+    case /* Signature */46 :
+    case /* Specification */47 :
+        return isSignatureItemStart(token);
+    case /* Structure */48 :
+    case /* Implementation */49 :
+        return isStructureItemStart(token);
+    case /* Attribute */50 :
+        return token === 75;
+    case /* TypeConstraint */51 :
+        return token === /* Constraint */63;
+    case /* ExprList */12 :
+    case /* ListExpr */53 :
+        if (token === /* DotDotDot */6) {
+          return true;
+        } else {
+          return isExprStart(token);
+        }
+    case /* JsFfiImport */54 :
+        return isJsFfiImportStart(token);
+    case /* OpenDescription */0 :
+    case /* ModuleLongIdent */1 :
+    case /* Ternary */2 :
+    case /* Es6ArrowExpr */3 :
+    case /* Jsx */4 :
+    case /* JsxChild */6 :
+    case /* ExprOperand */7 :
+    case /* ExprUnary */8 :
+    case /* ExprSetField */9 :
+    case /* ExprBlock */10 :
+    case /* ExprCall */11 :
+    case /* ExprArrayAccess */13 :
+    case /* ExprArrayMutation */14 :
+    case /* ExprIf */15 :
+    case /* ExprFor */16 :
+    case /* IfCondition */17 :
+    case /* IfBranch */18 :
+    case /* ElseBranch */19 :
+    case /* TypeExpression */20 :
+    case /* External */21 :
+    case /* PatternMatchCase */23 :
+    case /* LetBinding */24 :
+    case /* TypeDef */28 :
+    case /* TypeConstrName */29 :
+    case /* TypeParam */31 :
+    case /* TypeRepresentation */33 :
+    case /* AtomicTypExpr */52 :
+    case /* Pattern */55 :
+        return false;
+    case /* AttributePayload */56 :
+        return token === /* Lparen */18;
+    case /* TagNames */57 :
+        return token === /* Hash */44;
+    
+  }
+}
+
+function isListTerminator(grammar, token) {
+  var exit = 0;
+  if (typeof grammar === "number") {
+    var exit$1 = 0;
+    switch (grammar) {
+      case /* JsxAttribute */5 :
+          if (typeof token === "number") {
+            if (token === 29) {
+              return true;
+            }
+            if (token === 41) {
+              return true;
+            }
+            exit = 2;
+          } else {
+            exit = 2;
+          }
+          break;
+      case /* ExprList */12 :
+          if (typeof token === "number") {
+            switch (token) {
+              case /* Lbracket */20 :
+              case /* Lbrace */22 :
+              case /* Rbrace */23 :
+              case /* Colon */24 :
+              case /* Comma */25 :
+              case /* Eof */26 :
+              case /* Exception */27 :
+              case /* Backslash */28 :
+                  exit = 2;
+                  break;
+              case /* Rparen */19 :
+              case /* Rbracket */21 :
+              case /* Forwardslash */29 :
+                  return true;
+              default:
+                exit = 2;
+            }
+          } else {
+            exit = 2;
+          }
+          break;
+      case /* ParameterList */36 :
+          if (typeof token === "number") {
+            if (token === 22) {
+              return true;
+            }
+            if (token === 57) {
+              return true;
+            }
+            exit = 2;
+          } else {
+            exit = 2;
+          }
+          break;
+      case /* TypExprList */39 :
+          if (typeof token === "number") {
+            if (token >= 20) {
+              if (token === 29) {
+                return true;
+              }
+              if (token === 41) {
+                return true;
+              }
+              exit = 2;
+            } else {
+              if (token === 14) {
+                return true;
+              }
+              if (token >= 19) {
+                return true;
+              }
+              exit = 2;
+            }
+          } else {
+            exit = 2;
+          }
+          break;
+      case /* TypeParams */30 :
+      case /* ModExprList */41 :
+      case /* ArgumentList */45 :
+      case /* ListExpr */53 :
+          exit$1 = 4;
+          break;
+      case /* ExprBlock */10 :
+      case /* StringFieldDeclarations */37 :
+      case /* JsFfiImport */54 :
+          exit$1 = 3;
+          break;
+      case /* OpenDescription */0 :
+      case /* ModuleLongIdent */1 :
+      case /* Ternary */2 :
+      case /* Es6ArrowExpr */3 :
+      case /* Jsx */4 :
+      case /* JsxChild */6 :
+      case /* ExprOperand */7 :
+      case /* ExprUnary */8 :
+      case /* ExprSetField */9 :
+      case /* ExprCall */11 :
+      case /* ExprArrayAccess */13 :
+      case /* ExprArrayMutation */14 :
+      case /* ExprIf */15 :
+      case /* ExprFor */16 :
+      case /* IfCondition */17 :
+      case /* IfBranch */18 :
+      case /* ElseBranch */19 :
+      case /* TypeExpression */20 :
+      case /* External */21 :
+      case /* PatternMatching */22 :
+      case /* PatternMatchCase */23 :
+      case /* LetBinding */24 :
+      case /* PatternList */25 :
+      case /* PatternOcamlList */26 :
+      case /* PatternRecord */27 :
+      case /* TypeDef */28 :
+      case /* TypeConstrName */29 :
+      case /* TypeParam */31 :
+      case /* PackageConstraint */32 :
+      case /* TypeRepresentation */33 :
+      case /* RecordDecl */34 :
+      case /* ConstructorDeclaration */35 :
+      case /* FieldDeclarations */38 :
+      case /* FunctorArgs */40 :
+      case /* TypeParameters */42 :
+      case /* RecordRows */43 :
+      case /* RecordRowsStringKey */44 :
+      case /* Signature */46 :
+      case /* Specification */47 :
+      case /* Structure */48 :
+      case /* Implementation */49 :
+      case /* Attribute */50 :
+      case /* TypeConstraint */51 :
+      case /* AtomicTypExpr */52 :
+      case /* Pattern */55 :
+          exit = 2;
+          break;
+      case /* AttributePayload */56 :
+          if (token === 19) {
+            return true;
+          }
+          exit = 2;
+          break;
+      case /* TagNames */57 :
+          if (token === 21) {
+            return true;
+          }
+          exit = 2;
+          break;
+      
+    }
+    switch (exit$1) {
+      case 3 :
+          if (token === 23) {
+            return true;
+          }
+          exit = 2;
+          break;
+      case 4 :
+          if (token === 19) {
+            return true;
+          }
+          exit = 2;
+          break;
+      
+    }
+  } else {
+    exit = 2;
+  }
+  if (exit === 2) {
+    if (token === 26) {
+      return true;
+    }
+    if (typeof grammar !== "number") {
+      return false;
+    }
+    if (grammar < 25) {
+      return false;
+    }
+    switch (grammar) {
+      case /* PatternList */25 :
+      case /* PatternOcamlList */26 :
+      case /* PatternRecord */27 :
+          break;
+      case /* PackageConstraint */32 :
+          return token !== /* And */10;
+      case /* ConstructorDeclaration */35 :
+          return token !== /* Bar */17;
+      case /* Signature */46 :
+      case /* Structure */48 :
+          return token === 23;
+      case /* Attribute */50 :
+          return token !== /* At */75;
+      case /* TypeConstraint */51 :
+          return token !== /* Constraint */63;
+      case /* TypeDef */28 :
+      case /* TypeConstrName */29 :
+      case /* TypeParams */30 :
+      case /* TypeParam */31 :
+      case /* TypeRepresentation */33 :
+      case /* RecordDecl */34 :
+      case /* ParameterList */36 :
+      case /* StringFieldDeclarations */37 :
+      case /* FieldDeclarations */38 :
+      case /* TypExprList */39 :
+      case /* FunctorArgs */40 :
+      case /* ModExprList */41 :
+      case /* TypeParameters */42 :
+      case /* RecordRows */43 :
+      case /* RecordRowsStringKey */44 :
+      case /* ArgumentList */45 :
+      case /* Specification */47 :
+      case /* Implementation */49 :
+      case /* AtomicTypExpr */52 :
+      case /* ListExpr */53 :
+      case /* JsFfiImport */54 :
+      case /* Pattern */55 :
+      case /* AttributePayload */56 :
+      case /* TagNames */57 :
+          return false;
+      
+    }
+  }
+  if (typeof token !== "number") {
+    return false;
+  }
+  if (token >= 30) {
+    if (token !== 53) {
+      return token === 57;
+    } else {
+      return true;
+    }
+  }
+  if (token === 14) {
+    return true;
+  }
+  if (token < 19) {
+    return false;
+  }
+  switch (token) {
+    case /* Lbracket */20 :
+    case /* Lbrace */22 :
+    case /* Rbrace */23 :
+    case /* Colon */24 :
+    case /* Comma */25 :
+    case /* Eof */26 :
+    case /* Exception */27 :
+    case /* Backslash */28 :
+        return false;
+    case /* Rparen */19 :
+    case /* Rbracket */21 :
+    case /* Forwardslash */29 :
+        return true;
+    
+  }
+}
+
+function isPartOfList(grammar, token) {
+  if (isListElement(grammar, token)) {
+    return true;
+  } else {
+    return isListTerminator(grammar, token);
+  }
+}
+
+var Token;
+
+var isJsxChildStart = isAtomicExprStart;
+
+export {
+  Token ,
+  toString ,
+  isSignatureItemStart ,
+  isAtomicPatternStart ,
+  isAtomicExprStart ,
+  isAtomicTypExprStart ,
+  isExprStart ,
+  isJsxAttributeStart ,
+  isStructureItemStart ,
+  isPatternStart ,
+  isParameterStart ,
+  isStringFieldDeclStart ,
+  isFieldDeclStart ,
+  isRecordDeclStart ,
+  isTypExprStart ,
+  isTypeParameterStart ,
+  isTypeParamStart ,
+  isFunctorArgStart ,
+  isModExprStart ,
+  isRecordRowStart ,
+  isRecordRowStringKeyStart ,
+  isArgumentStart ,
+  isPatternMatchStart ,
+  isPatternOcamlListStart ,
+  isPatternRecordItemStart ,
+  isAttributeStart ,
+  isJsFfiImportStart ,
+  isJsxChildStart ,
+  isBlockExprStart ,
+  isListElement ,
+  isListTerminator ,
+  isPartOfList ,
+  
+}
+/* Res_token Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_grammar.res b/analysis/examples/larger-project/src/res_grammar.res
new file mode 100644
index 0000000000..ceeb8e7121
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_grammar.res
@@ -0,0 +1,526 @@
+module Token = Res_token
+
+type t =
+  | OpenDescription /* open Belt */
+  | @live ModuleLongIdent /* Foo or Foo.Bar */
+  | Ternary /* condExpr ? trueExpr : falseExpr */
+  | Es6ArrowExpr
+  | Jsx
+  | JsxAttribute
+  | @live JsxChild
+  | ExprOperand
+  | ExprUnary
+  | ExprSetField
+  | ExprBinaryAfterOp(Token.t)
+  | ExprBlock
+  | ExprCall
+  | ExprList
+  | ExprArrayAccess
+  | ExprArrayMutation
+  | ExprIf
+  | ExprFor
+  | IfCondition
+  | IfBranch
+  | ElseBranch
+  | TypeExpression
+  | External
+  | PatternMatching
+  | PatternMatchCase
+  | LetBinding
+  | PatternList
+  | PatternOcamlList
+  | PatternRecord
+
+  | TypeDef
+  | TypeConstrName
+  | TypeParams
+  | @live TypeParam
+  | PackageConstraint
+  | TypeRepresentation
+  | RecordDecl
+  | ConstructorDeclaration
+  | ParameterList
+  | StringFieldDeclarations
+  | FieldDeclarations
+  | TypExprList
+  | FunctorArgs
+  | ModExprList
+  | TypeParameters
+  | RecordRows
+  | RecordRowsStringKey
+  | ArgumentList
+  | Signature
+  | Specification
+  | Structure
+  | Implementation
+  | Attribute
+  | TypeConstraint
+  | AtomicTypExpr
+  | ListExpr
+  | JsFfiImport
+  | Pattern
+  | AttributePayload
+  | TagNames
+
+let toString = x =>
+  switch x {
+  | OpenDescription => "an open description"
+  | ModuleLongIdent => "a module path"
+  | Ternary => "a ternary expression"
+  | Es6ArrowExpr => "an es6 arrow function"
+  | Jsx => "a jsx expression"
+  | JsxAttribute => "a jsx attribute"
+  | ExprOperand => "a basic expression"
+  | ExprUnary => "a unary expression"
+  | ExprBinaryAfterOp(op) => "an expression after the operator \"" ++ (Token.toString(op) ++ "\"")
+  | ExprIf => "an if expression"
+  | IfCondition => "the condition of an if expression"
+  | IfBranch => "the true-branch of an if expression"
+  | ElseBranch => "the else-branch of an if expression"
+  | TypeExpression => "a type"
+  | External => "an external"
+  | PatternMatching => "the cases of a pattern match"
+  | ExprBlock => "a block with expressions"
+  | ExprSetField => "a record field mutation"
+  | ExprCall => "a function application"
+  | ExprArrayAccess => "an array access expression"
+  | ExprArrayMutation => "an array mutation"
+  | LetBinding => "a let binding"
+  | TypeDef => "a type definition"
+  | TypeParams => "type parameters"
+  | TypeParam => "a type parameter"
+  | TypeConstrName => "a type-constructor name"
+  | TypeRepresentation => "a type representation"
+  | RecordDecl => "a record declaration"
+  | PatternMatchCase => "a pattern match case"
+  | ConstructorDeclaration => "a constructor declaration"
+  | ExprList => "multiple expressions"
+  | PatternList => "multiple patterns"
+  | PatternOcamlList => "a list pattern"
+  | PatternRecord => "a record pattern"
+  | ParameterList => "parameters"
+  | StringFieldDeclarations => "string field declarations"
+  | FieldDeclarations => "field declarations"
+  | TypExprList => "list of types"
+  | FunctorArgs => "functor arguments"
+  | ModExprList => "list of module expressions"
+  | TypeParameters => "list of type parameters"
+  | RecordRows => "rows of a record"
+  | RecordRowsStringKey => "rows of a record with string keys"
+  | ArgumentList => "arguments"
+  | Signature => "signature"
+  | Specification => "specification"
+  | Structure => "structure"
+  | Implementation => "implementation"
+  | Attribute => "an attribute"
+  | TypeConstraint => "constraints on a type"
+  | AtomicTypExpr => "a type"
+  | ListExpr => "an ocaml list expr"
+  | PackageConstraint => "a package constraint"
+  | JsFfiImport => "js ffi import"
+  | JsxChild => "jsx child"
+  | Pattern => "pattern"
+  | ExprFor => "a for expression"
+  | AttributePayload => "an attribute payload"
+  | TagNames => "tag names"
+  }
+
+let isSignatureItemStart = x =>
+  switch x {
+  | Token.At
+  | Let
+  | Typ
+  | External
+  | Exception
+  | Open
+  | Include
+  | Module
+  | AtAt
+  | Export
+  | PercentPercent => true
+  | _ => false
+  }
+
+let isAtomicPatternStart = x =>
+  switch x {
+  | Token.Int(_)
+  | String(_)
+  | Codepoint(_)
+  | Backtick
+  | Lparen
+  | Lbracket
+  | Lbrace
+  | Underscore
+  | Lident(_)
+  | Uident(_)
+  | List
+  | Exception
+  | Lazy
+  | Percent => true
+  | _ => false
+  }
+
+let isAtomicExprStart = x =>
+  switch x {
+  | Token.True
+  | False
+  | Int(_)
+  | String(_)
+  | Float(_)
+  | Codepoint(_)
+  | Backtick
+  | Uident(_)
+  | Lident(_)
+  | Hash
+  | Lparen
+  | List
+  | Lbracket
+  | Lbrace
+  | LessThan
+  | Module
+  | Percent => true
+  | _ => false
+  }
+
+let isAtomicTypExprStart = x =>
+  switch x {
+  | Token.SingleQuote
+  | Underscore
+  | Lparen
+  | Lbrace
+  | Uident(_)
+  | Lident(_)
+  | Percent => true
+  | _ => false
+  }
+
+let isExprStart = x =>
+  switch x {
+  | Token.True
+  | False
+  | Int(_)
+  | String(_)
+  | Float(_)
+  | Codepoint(_)
+  | Backtick
+  | Underscore
+  | Uident(_)
+  | Lident(_)
+  | Hash
+  | Lparen
+  | List
+  | Module
+  | Lbracket
+  | Lbrace
+  | LessThan
+  | Minus
+  | MinusDot
+  | Plus
+  | PlusDot
+  | Bang
+  | Percent
+  | At
+  | If
+  | Switch
+  | While
+  | For
+  | Assert
+  | Lazy
+  | Try => true
+  | _ => false
+  }
+
+let isJsxAttributeStart = x =>
+  switch x {
+  | Token.Lident(_) | Question => true
+  | _ => false
+  }
+
+let isStructureItemStart = x =>
+  switch x {
+  | Token.Open
+  | Let
+  | Typ
+  | External
+  | Import
+  | Export
+  | Exception
+  | Include
+  | Module
+  | AtAt
+  | PercentPercent
+  | At => true
+  | t if isExprStart(t) => true
+  | _ => false
+  }
+
+let isPatternStart = x =>
+  switch x {
+  | Token.Int(_)
+  | Float(_)
+  | String(_)
+  | Codepoint(_)
+  | Backtick
+  | True
+  | False
+  | Minus
+  | Plus
+  | Lparen
+  | Lbracket
+  | Lbrace
+  | List
+  | Underscore
+  | Lident(_)
+  | Uident(_)
+  | Hash
+  | Exception
+  | Lazy
+  | Percent
+  | Module
+  | At => true
+  | _ => false
+  }
+
+let isParameterStart = x =>
+  switch x {
+  | Token.Typ | Tilde | Dot => true
+  | token if isPatternStart(token) => true
+  | _ => false
+  }
+
+/* TODO: overparse Uident ? */
+let isStringFieldDeclStart = x =>
+  switch x {
+  | Token.String(_) | Lident(_) | At | DotDotDot => true
+  | _ => false
+  }
+
+/* TODO: overparse Uident ? */
+let isFieldDeclStart = x =>
+  switch x {
+  | Token.At | Mutable | Lident(_) => true
+  /* recovery, TODO: this is not ideal… */
+  | Uident(_) => true
+  | t if Token.isKeyword(t) => true
+  | _ => false
+  }
+
+let isRecordDeclStart = x =>
+  switch x {
+  | Token.At
+  | Mutable
+  | Lident(_) => true
+  | _ => false
+  }
+
+let isTypExprStart = x =>
+  switch x {
+  | Token.At
+  | SingleQuote
+  | Underscore
+  | Lparen
+  | Lbracket
+  | Uident(_)
+  | Lident(_)
+  | Module
+  | Percent
+  | Lbrace => true
+  | _ => false
+  }
+
+let isTypeParameterStart = x =>
+  switch x {
+  | Token.Tilde | Dot => true
+  | token if isTypExprStart(token) => true
+  | _ => false
+  }
+
+let isTypeParamStart = x =>
+  switch x {
+  | Token.Plus | Minus | SingleQuote | Underscore => true
+  | _ => false
+  }
+
+let isFunctorArgStart = x =>
+  switch x {
+  | Token.At
+  | Uident(_)
+  | Underscore
+  | Percent
+  | Lbrace
+  | Lparen => true
+  | _ => false
+  }
+
+let isModExprStart = x =>
+  switch x {
+  | Token.At
+  | Percent
+  | Uident(_)
+  | Lbrace
+  | Lparen
+  | Lident("unpack") => true
+  | _ => false
+  }
+
+let isRecordRowStart = x =>
+  switch x {
+  | Token.DotDotDot => true
+  | Token.Uident(_) | Lident(_) => true
+  /* TODO */
+  | t if Token.isKeyword(t) => true
+  | _ => false
+  }
+
+let isRecordRowStringKeyStart = x =>
+  switch x {
+  | Token.String(_) => true
+  | _ => false
+  }
+
+let isArgumentStart = x =>
+  switch x {
+  | Token.Tilde | Dot | Underscore => true
+  | t if isExprStart(t) => true
+  | _ => false
+  }
+
+let isPatternMatchStart = x =>
+  switch x {
+  | Token.Bar => true
+  | t if isPatternStart(t) => true
+  | _ => false
+  }
+
+let isPatternOcamlListStart = x =>
+  switch x {
+  | Token.DotDotDot => true
+  | t if isPatternStart(t) => true
+  | _ => false
+  }
+
+let isPatternRecordItemStart = x =>
+  switch x {
+  | Token.DotDotDot | Uident(_) | Lident(_) | Underscore => true
+  | _ => false
+  }
+
+let isAttributeStart = x =>
+  switch x {
+  | Token.At => true
+  | _ => false
+  }
+
+let isJsFfiImportStart = x =>
+  switch x {
+  | Token.Lident(_) | At => true
+  | _ => false
+  }
+
+let isJsxChildStart = isAtomicExprStart
+
+let isBlockExprStart = x =>
+  switch x {
+  | Token.At
+  | Hash
+  | Percent
+  | Minus
+  | MinusDot
+  | Plus
+  | PlusDot
+  | Bang
+  | True
+  | False
+  | Float(_)
+  | Int(_)
+  | String(_)
+  | Codepoint(_)
+  | Lident(_)
+  | Uident(_)
+  | Lparen
+  | List
+  | Lbracket
+  | Lbrace
+  | Forwardslash
+  | Assert
+  | Lazy
+  | If
+  | For
+  | While
+  | Switch
+  | Open
+  | Module
+  | Exception
+  | Let
+  | LessThan
+  | Backtick
+  | Try
+  | Underscore => true
+  | _ => false
+  }
+
+let isListElement = (grammar, token) =>
+  switch grammar {
+  | ExprList => token == Token.DotDotDot || isExprStart(token)
+  | ListExpr => token == DotDotDot || isExprStart(token)
+  | PatternList => token == DotDotDot || isPatternStart(token)
+  | ParameterList => isParameterStart(token)
+  | StringFieldDeclarations => isStringFieldDeclStart(token)
+  | FieldDeclarations => isFieldDeclStart(token)
+  | RecordDecl => isRecordDeclStart(token)
+  | TypExprList => isTypExprStart(token) || token == Token.LessThan
+  | TypeParams => isTypeParamStart(token)
+  | FunctorArgs => isFunctorArgStart(token)
+  | ModExprList => isModExprStart(token)
+  | TypeParameters => isTypeParameterStart(token)
+  | RecordRows => isRecordRowStart(token)
+  | RecordRowsStringKey => isRecordRowStringKeyStart(token)
+  | ArgumentList => isArgumentStart(token)
+  | Signature | Specification => isSignatureItemStart(token)
+  | Structure | Implementation => isStructureItemStart(token)
+  | PatternMatching => isPatternMatchStart(token)
+  | PatternOcamlList => isPatternOcamlListStart(token)
+  | PatternRecord => isPatternRecordItemStart(token)
+  | Attribute => isAttributeStart(token)
+  | TypeConstraint => token == Constraint
+  | PackageConstraint => token == And
+  | ConstructorDeclaration => token == Bar
+  | JsxAttribute => isJsxAttributeStart(token)
+  | JsFfiImport => isJsFfiImportStart(token)
+  | AttributePayload => token == Lparen
+  | TagNames => token == Hash
+  | _ => false
+  }
+
+let isListTerminator = (grammar, token) =>
+  switch (grammar, token) {
+  | (_, Token.Eof)
+  | (ExprList, Rparen | Forwardslash | Rbracket)
+  | (ListExpr, Rparen)
+  | (ArgumentList, Rparen)
+  | (TypExprList, Rparen | Forwardslash | GreaterThan | Equal)
+  | (ModExprList, Rparen)
+  | (
+    PatternList | PatternOcamlList | PatternRecord,
+    Forwardslash | Rbracket | Rparen | EqualGreater | In | Equal /* let {x} = foo */,
+  )
+  | (ExprBlock, Rbrace)
+  | (Structure | Signature, Rbrace)
+  | (TypeParams, Rparen)
+  | (ParameterList, EqualGreater | Lbrace)
+  | (JsxAttribute, Forwardslash | GreaterThan)
+  | (JsFfiImport, Rbrace)
+  | (StringFieldDeclarations, Rbrace) => true
+
+  | (Attribute, token) if token != At => true
+  | (TypeConstraint, token) if token != Constraint => true
+  | (PackageConstraint, token) if token != And => true
+  | (ConstructorDeclaration, token) if token != Bar => true
+  | (AttributePayload, Rparen) => true
+  | (TagNames, Rbracket) => true
+
+  | _ => false
+  }
+
+let isPartOfList = (grammar, token) =>
+  isListElement(grammar, token) || isListTerminator(grammar, token)
diff --git a/analysis/examples/larger-project/src/res_js_ffi.js b/analysis/examples/larger-project/src/res_js_ffi.js
new file mode 100644
index 0000000000..816692074a
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_js_ffi.js
@@ -0,0 +1,221 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Location from "./location.js";
+import * as Longident from "./longident.js";
+import * as Ast_helper from "./ast_helper.js";
+
+function decl(attrs, loc, name, alias, typ) {
+  return {
+          jld_attributes: attrs,
+          jld_name: name,
+          jld_alias: alias,
+          jld_type: typ,
+          jld_loc: loc
+        };
+}
+
+function importDescr(attrs, scope, importSpec, loc) {
+  return {
+          jid_loc: loc,
+          jid_spec: importSpec,
+          jid_scope: scope,
+          jid_attributes: attrs
+        };
+}
+
+function toParsetree(importDescr) {
+  var bsVal_0 = $$Location.mknoloc("val");
+  var bsVal_1 = {
+    TAG: /* PStr */0,
+    _0: /* [] */0
+  };
+  var bsVal = [
+    bsVal_0,
+    bsVal_1
+  ];
+  var s = importDescr.jid_scope;
+  var attrs;
+  if (typeof s === "number") {
+    attrs = {
+      hd: bsVal,
+      tl: /* [] */0
+    };
+  } else if (s.TAG === /* Module */0) {
+    var arg = Ast_helper.Str.$$eval;
+    var arg$1 = Ast_helper.Exp.constant;
+    var structure_0 = Curry._3(arg, undefined, undefined, Curry._3(arg$1, undefined, undefined, {
+              TAG: /* Pconst_string */2,
+              _0: s._0,
+              _1: undefined
+            }));
+    var structure = {
+      hd: structure_0,
+      tl: /* [] */0
+    };
+    var genType_0 = $$Location.mknoloc("genType.import");
+    var genType_1 = {
+      TAG: /* PStr */0,
+      _0: structure
+    };
+    var genType = [
+      genType_0,
+      genType_1
+    ];
+    attrs = {
+      hd: genType,
+      tl: /* [] */0
+    };
+  } else {
+    var match = List.map((function (s) {
+            return Ast_helper.Exp.constant(undefined, undefined, {
+                        TAG: /* Pconst_string */2,
+                        _0: s,
+                        _1: undefined
+                      });
+          }), Longident.flatten(s._0));
+    var expr;
+    var exit = 0;
+    var exprs;
+    if (match && !match.tl) {
+      expr = match.hd;
+    } else {
+      exprs = match;
+      exit = 1;
+    }
+    if (exit === 1) {
+      var arg$2 = Ast_helper.Exp.tuple;
+      expr = Curry._3(arg$2, undefined, undefined, exprs);
+    }
+    var structureItem = Ast_helper.Str.$$eval(undefined, undefined, expr);
+    var bsScope_0 = $$Location.mknoloc("scope");
+    var bsScope_1 = {
+      TAG: /* PStr */0,
+      _0: {
+        hd: structureItem,
+        tl: /* [] */0
+      }
+    };
+    var bsScope = [
+      bsScope_0,
+      bsScope_1
+    ];
+    attrs = {
+      hd: bsVal,
+      tl: {
+        hd: bsScope,
+        tl: /* [] */0
+      }
+    };
+  }
+  var decl = importDescr.jid_spec;
+  var valueDescrs;
+  if (decl.TAG === /* Default */0) {
+    var decl$1 = decl._0;
+    var prim_0 = decl$1.jld_name;
+    var prim = {
+      hd: prim_0,
+      tl: /* [] */0
+    };
+    var allAttrs = List.map((function (attr) {
+            var id = attr[0];
+            if (id.txt !== "genType.import") {
+              return attr;
+            }
+            var match = attr[1];
+            if (match.TAG !== /* PStr */0) {
+              return attr;
+            }
+            var match$1 = match._0;
+            if (!match$1) {
+              return attr;
+            }
+            var match$2 = match$1.hd.pstr_desc;
+            if (match$2.TAG !== /* Pstr_eval */0) {
+              return attr;
+            }
+            if (match$1.tl) {
+              return attr;
+            }
+            var arg = Ast_helper.Exp.constant;
+            var $$default = Curry._3(arg, undefined, undefined, {
+                  TAG: /* Pconst_string */2,
+                  _0: "default",
+                  _1: undefined
+                });
+            var arg$1 = Ast_helper.Str.$$eval;
+            var arg$2 = Ast_helper.Exp.tuple;
+            var structureItem = Curry._3(arg$1, undefined, undefined, Curry._3(arg$2, undefined, undefined, {
+                      hd: match$2._0,
+                      tl: {
+                        hd: $$default,
+                        tl: /* [] */0
+                      }
+                    }));
+            return [
+                    id,
+                    {
+                      TAG: /* PStr */0,
+                      _0: {
+                        hd: structureItem,
+                        tl: /* [] */0
+                      }
+                    }
+                  ];
+          }), List.concat({
+              hd: attrs,
+              tl: {
+                hd: importDescr.jid_attributes,
+                tl: /* [] */0
+              }
+            }));
+    var arg$3 = Ast_helper.Str.primitive;
+    valueDescrs = {
+      hd: Curry._2(arg$3, undefined, Ast_helper.Val.mk(importDescr.jid_loc, allAttrs, undefined, prim, $$Location.mknoloc(decl$1.jld_alias), decl$1.jld_type)),
+      tl: /* [] */0
+    };
+  } else {
+    valueDescrs = List.map((function (decl) {
+            var prim_0 = decl.jld_name;
+            var prim = {
+              hd: prim_0,
+              tl: /* [] */0
+            };
+            var allAttrs = List.concat({
+                  hd: attrs,
+                  tl: {
+                    hd: decl.jld_attributes,
+                    tl: /* [] */0
+                  }
+                });
+            return Ast_helper.Str.primitive(decl.jld_loc, Ast_helper.Val.mk(importDescr.jid_loc, allAttrs, undefined, prim, $$Location.mknoloc(decl.jld_alias), decl.jld_type));
+          }), decl._0);
+  }
+  var jsFfiAttr_0 = $$Location.mknoloc("ns.jsFfi");
+  var jsFfiAttr_1 = {
+    TAG: /* PStr */0,
+    _0: /* [] */0
+  };
+  var jsFfiAttr = [
+    jsFfiAttr_0,
+    jsFfiAttr_1
+  ];
+  var partial_arg = {
+    hd: jsFfiAttr,
+    tl: /* [] */0
+  };
+  var partial_arg$1 = importDescr.jid_loc;
+  var arg$4 = function (param, param$1) {
+    return Ast_helper.Incl.mk(partial_arg$1, partial_arg, param, param$1);
+  };
+  return Ast_helper.Str.include_(importDescr.jid_loc, Curry._2(arg$4, undefined, Ast_helper.Mod.structure(importDescr.jid_loc, undefined, valueDescrs)));
+}
+
+export {
+  decl ,
+  importDescr ,
+  toParsetree ,
+  
+}
+/* Location Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_js_ffi.res b/analysis/examples/larger-project/src/res_js_ffi.res
new file mode 100644
index 0000000000..53280f2dff
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_js_ffi.res
@@ -0,0 +1,114 @@
+/* AST for js externals */
+type scope =
+  | Global
+  | Module(string) /* bs.module("path") */
+  | Scope(Longident.t) /* bs.scope(/"window", "location"/) */
+
+type label_declaration = {
+  @live jld_attributes: Parsetree.attributes,
+  jld_name: string,
+  jld_alias: string,
+  jld_type: Parsetree.core_type,
+  jld_loc: Location.t,
+}
+
+type importSpec =
+  | Default(label_declaration)
+  | Spec(list<label_declaration>)
+
+type import_description = {
+  jid_loc: Location.t,
+  jid_spec: importSpec,
+  jid_scope: scope,
+  jid_attributes: Parsetree.attributes,
+}
+
+let decl = (~attrs, ~loc, ~name, ~alias, ~typ) => {
+  jld_loc: loc,
+  jld_attributes: attrs,
+  jld_name: name,
+  jld_alias: alias,
+  jld_type: typ,
+}
+
+let importDescr = (~attrs, ~scope, ~importSpec, ~loc) => {
+  jid_loc: loc,
+  jid_spec: importSpec,
+  jid_scope: scope,
+  jid_attributes: attrs,
+}
+
+let toParsetree = importDescr => {
+  let bsVal = (Location.mknoloc("val"), Parsetree.PStr(list{}))
+  let attrs = switch importDescr.jid_scope {
+  | Global => list{bsVal}
+  /* @genType.import("./MyMath"),
+   * @genType.import(/"./MyMath", "default"/) */
+  | Module(s) =>
+    let structure = list{
+      Parsetree.Pconst_string(s, None) |> Ast_helper.Exp.constant |> Ast_helper.Str.eval,
+    }
+    let genType = (Location.mknoloc("genType.import"), Parsetree.PStr(structure))
+    list{genType}
+  | Scope(longident) =>
+    let structureItem = {
+      let expr = switch Longident.flatten(longident) |> List.map(s =>
+        Ast_helper.Exp.constant(Parsetree.Pconst_string(s, None))
+      ) {
+      | list{expr} => expr
+      | list{} as exprs | _ as exprs => exprs |> Ast_helper.Exp.tuple
+      }
+
+      Ast_helper.Str.eval(expr)
+    }
+
+    let bsScope = (Location.mknoloc("scope"), Parsetree.PStr(list{structureItem}))
+    list{bsVal, bsScope}
+  }
+
+  let valueDescrs = switch importDescr.jid_spec {
+  | Default(decl) =>
+    let prim = list{decl.jld_name}
+    let allAttrs = List.concat(list{attrs, importDescr.jid_attributes}) |> List.map(attr =>
+      switch attr {
+      | (
+          {Location.txt: "genType.import"} as id,
+          Parsetree.PStr(list{{pstr_desc: Parsetree.Pstr_eval(moduleName, _)}}),
+        ) =>
+        let default = Parsetree.Pconst_string("default", None) |> Ast_helper.Exp.constant
+
+        let structureItem = list{moduleName, default} |> Ast_helper.Exp.tuple |> Ast_helper.Str.eval
+
+        (id, Parsetree.PStr(list{structureItem}))
+      | attr => attr
+      }
+    )
+
+    list{
+      Ast_helper.Val.mk(
+        ~loc=importDescr.jid_loc,
+        ~prim,
+        ~attrs=allAttrs,
+        Location.mknoloc(decl.jld_alias),
+        decl.jld_type,
+      ) |> Ast_helper.Str.primitive,
+    }
+  | Spec(decls) => List.map(decl => {
+      let prim = list{decl.jld_name}
+      let allAttrs = List.concat(list{attrs, decl.jld_attributes})
+      Ast_helper.Val.mk(
+        ~loc=importDescr.jid_loc,
+        ~prim,
+        ~attrs=allAttrs,
+        Location.mknoloc(decl.jld_alias),
+        decl.jld_type,
+      ) |> Ast_helper.Str.primitive(~loc=decl.jld_loc)
+    }, decls)
+  }
+
+  let jsFfiAttr = (Location.mknoloc("ns.jsFfi"), Parsetree.PStr(list{}))
+  Ast_helper.Mod.structure(~loc=importDescr.jid_loc, valueDescrs)
+  |> Ast_helper.Incl.mk(~attrs=list{jsFfiAttr}, ~loc=importDescr.jid_loc)
+  |> Ast_helper.Str.include_(~loc=importDescr.jid_loc)
+}
+
diff --git a/analysis/examples/larger-project/src/res_minibuffer.js b/analysis/examples/larger-project/src/res_minibuffer.js
new file mode 100644
index 0000000000..72aa92e703
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_minibuffer.js
@@ -0,0 +1,76 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Sys from "rescript/lib/es6/sys.js";
+import * as Bytes from "rescript/lib/es6/bytes.js";
+import * as Caml_bytes from "rescript/lib/es6/caml_bytes.js";
+
+function create(n) {
+  var n$1 = n < 1 ? 1 : n;
+  var s = Caml_bytes.caml_create_bytes(n$1);
+  return {
+          buffer: s,
+          position: 0,
+          length: n$1
+        };
+}
+
+function contents(b) {
+  return Bytes.sub_string(b.buffer, 0, b.position);
+}
+
+function resize_internal(b, more) {
+  var len = b.length;
+  var new_len = len;
+  while((b.position + more | 0) > new_len) {
+    new_len = (new_len << 1);
+  };
+  if (new_len > Sys.max_string_length && (b.position + more | 0) <= Sys.max_string_length) {
+    new_len = Sys.max_string_length;
+  }
+  var new_buffer = Caml_bytes.caml_create_bytes(new_len);
+  Bytes.blit(b.buffer, 0, new_buffer, 0, b.position);
+  b.buffer = new_buffer;
+  b.length = new_len;
+  
+}
+
+function add_char(b, c) {
+  var pos = b.position;
+  if (pos >= b.length) {
+    resize_internal(b, 1);
+  }
+  b.buffer[pos] = c;
+  b.position = pos + 1 | 0;
+  
+}
+
+function add_string(b, s) {
+  var len = s.length;
+  var new_position = b.position + len | 0;
+  if (new_position > b.length) {
+    resize_internal(b, len);
+  }
+  Bytes.blit_string(s, 0, b.buffer, b.position, len);
+  b.position = new_position;
+  
+}
+
+function flush_newline(b) {
+  var position = b.position;
+  while(b.buffer[position - 1 | 0] === /* ' ' */32 && position >= 0) {
+    position = position - 1 | 0;
+  };
+  b.position = position;
+  return add_char(b, /* '\n' */10);
+}
+
+export {
+  create ,
+  contents ,
+  resize_internal ,
+  add_char ,
+  add_string ,
+  flush_newline ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_minibuffer.res b/analysis/examples/larger-project/src/res_minibuffer.res
new file mode 100644
index 0000000000..d2d889a980
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_minibuffer.res
@@ -0,0 +1,70 @@
+type t = {
+  mutable buffer: bytes,
+  mutable position: int,
+  mutable length: int,
+}
+
+let create = n => {
+  let n = if n < 1 {
+    1
+  } else {
+    n
+  }
+  let s = (@doesNotRaise Bytes.create)(n)
+  {buffer: s, position: 0, length: n}
+}
+
+let contents = b => (@doesNotRaise Bytes.sub_string)(b.buffer, 0, b.position)
+
+/* Can't be called directly, don't add to the interface */
+let resize_internal = (b, more) => {
+  let len = b.length
+  let new_len = ref(len)
+  while b.position + more > new_len.contents {
+    new_len := 2 * new_len.contents
+  }
+  if new_len.contents > Sys.max_string_length {
+    if b.position + more <= Sys.max_string_length {
+      new_len := Sys.max_string_length
+    }
+  }
+  let new_buffer = (@doesNotRaise Bytes.create)(new_len.contents)
+  /* PR#6148: let's keep using [blit] rather than [unsafe_blit] in
+   this tricky function that is slow anyway. */
+
+  @doesNotRaise
+  Bytes.blit(b.buffer, 0, new_buffer, 0, b.position)
+  b.buffer = new_buffer
+  b.length = new_len.contents
+}
+
+let add_char = (b, c) => {
+  let pos = b.position
+  if pos >= b.length {
+    resize_internal(b, 1)
+  }
+  Bytes.unsafe_set(b.buffer, pos, c)
+  b.position = pos + 1
+}
+
+let add_string = (b, s) => {
+  let len = String.length(s)
+  let new_position = b.position + len
+  if new_position > b.length {
+    resize_internal(b, len)
+  }
+
+  @doesNotRaise
+  Bytes.blit_string(s, 0, b.buffer, b.position, len)
+  b.position = new_position
+}
+
+/* adds newline and trims all preceding whitespace */
+let flush_newline = b => {
+  let position = ref(b.position)
+  while Bytes.unsafe_get(b.buffer, position.contents - 1) == ' ' && position.contents >= 0 {
+    position := position.contents - 1
+  }
+  b.position = position.contents
+  add_char(b, '\n')
+}
diff --git a/analysis/examples/larger-project/src/res_parens.js b/analysis/examples/larger-project/src/res_parens.js
new file mode 100644
index 0000000000..6e1308f080
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parens.js
@@ -0,0 +1,953 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Res_parsetree_viewer from "./res_parsetree_viewer.js";
+
+function expr(expr$1) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr$1);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr$1.pexp_desc;
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  if (match$1.TAG !== /* Pexp_constraint */19) {
+    return /* Nothing */1;
+  }
+  var tmp = match$1._0.pexp_desc;
+  if (typeof tmp === "number") {
+    return /* Parenthesized */0;
+  }
+  if (tmp.TAG !== /* Pexp_pack */32) {
+    return /* Parenthesized */0;
+  }
+  var tmp$1 = match$1._1.ptyp_desc;
+  if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+    return /* Parenthesized */0;
+  } else {
+    return /* Nothing */1;
+  }
+}
+
+function callExpr(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  var match$2 = Res_parsetree_viewer.filterParsingAttrs(expr.pexp_attributes);
+  if (match$2 ? true : false) {
+    return /* Parenthesized */0;
+  }
+  if (Res_parsetree_viewer.isUnaryExpression(expr) || Res_parsetree_viewer.isBinaryExpression(expr)) {
+    return /* Parenthesized */0;
+  }
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  switch (match$1.TAG | 0) {
+    case /* Pexp_fun */4 :
+        if (Res_parsetree_viewer.isUnderscoreApplySugar(expr)) {
+          return /* Nothing */1;
+        } else {
+          return /* Parenthesized */0;
+        }
+    case /* Pexp_constraint */19 :
+        var tmp = match$1._0.pexp_desc;
+        if (typeof tmp === "number") {
+          return /* Parenthesized */0;
+        }
+        if (tmp.TAG !== /* Pexp_pack */32) {
+          return /* Parenthesized */0;
+        }
+        var tmp$1 = match$1._1.ptyp_desc;
+        if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_function */3 :
+    case /* Pexp_match */6 :
+    case /* Pexp_try */7 :
+    case /* Pexp_setfield */13 :
+    case /* Pexp_ifthenelse */15 :
+    case /* Pexp_while */17 :
+    case /* Pexp_for */18 :
+    case /* Pexp_assert */27 :
+    case /* Pexp_lazy */28 :
+    case /* Pexp_newtype */31 :
+        return /* Parenthesized */0;
+    default:
+      return /* Nothing */1;
+  }
+}
+
+function structureExpr(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  if (Res_parsetree_viewer.hasAttributes(expr.pexp_attributes) && !Res_parsetree_viewer.isJsxExpression(expr)) {
+    return /* Parenthesized */0;
+  }
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  if (match$1.TAG !== /* Pexp_constraint */19) {
+    return /* Nothing */1;
+  }
+  var tmp = match$1._0.pexp_desc;
+  if (typeof tmp === "number") {
+    return /* Parenthesized */0;
+  }
+  if (tmp.TAG !== /* Pexp_pack */32) {
+    return /* Parenthesized */0;
+  }
+  var tmp$1 = match$1._1.ptyp_desc;
+  if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+    return /* Parenthesized */0;
+  } else {
+    return /* Nothing */1;
+  }
+}
+
+function unaryExprOperand(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  var match$2 = Res_parsetree_viewer.filterParsingAttrs(expr.pexp_attributes);
+  if (match$2 ? true : false) {
+    return /* Parenthesized */0;
+  }
+  if (Res_parsetree_viewer.isUnaryExpression(expr) || Res_parsetree_viewer.isBinaryExpression(expr)) {
+    return /* Parenthesized */0;
+  }
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  switch (match$1.TAG | 0) {
+    case /* Pexp_fun */4 :
+        if (Res_parsetree_viewer.isUnderscoreApplySugar(expr)) {
+          return /* Nothing */1;
+        } else {
+          return /* Parenthesized */0;
+        }
+    case /* Pexp_constraint */19 :
+        var tmp = match$1._0.pexp_desc;
+        if (typeof tmp === "number") {
+          return /* Parenthesized */0;
+        }
+        if (tmp.TAG !== /* Pexp_pack */32) {
+          return /* Parenthesized */0;
+        }
+        var tmp$1 = match$1._1.ptyp_desc;
+        if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_function */3 :
+    case /* Pexp_match */6 :
+    case /* Pexp_try */7 :
+    case /* Pexp_setfield */13 :
+    case /* Pexp_ifthenelse */15 :
+    case /* Pexp_while */17 :
+    case /* Pexp_for */18 :
+    case /* Pexp_assert */27 :
+    case /* Pexp_lazy */28 :
+    case /* Pexp_newtype */31 :
+    case /* Pexp_extension */34 :
+        return /* Parenthesized */0;
+    default:
+      return /* Nothing */1;
+  }
+}
+
+function binaryExprOperand(isLhs, expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  var exit = 0;
+  if (typeof match$1 === "number") {
+    exit = 2;
+  } else {
+    switch (match$1.TAG | 0) {
+      case /* Pexp_fun */4 :
+          if (Res_parsetree_viewer.isUnderscoreApplySugar(expr)) {
+            return /* Nothing */1;
+          } else {
+            return /* Parenthesized */0;
+          }
+      case /* Pexp_constraint */19 :
+          var tmp = match$1._0.pexp_desc;
+          if (typeof tmp === "number") {
+            return /* Parenthesized */0;
+          }
+          if (tmp.TAG !== /* Pexp_pack */32) {
+            return /* Parenthesized */0;
+          }
+          var tmp$1 = match$1._1.ptyp_desc;
+          if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+            return /* Parenthesized */0;
+          } else {
+            return /* Nothing */1;
+          }
+      case /* Pexp_function */3 :
+      case /* Pexp_newtype */31 :
+          return /* Parenthesized */0;
+      default:
+        exit = 2;
+    }
+  }
+  if (exit === 2) {
+    if (Res_parsetree_viewer.isBinaryExpression(expr)) {
+      return /* Parenthesized */0;
+    }
+    if (Res_parsetree_viewer.isTernaryExpr(expr)) {
+      return /* Parenthesized */0;
+    }
+    if (typeof match$1 !== "number") {
+      switch (match$1.TAG | 0) {
+        case /* Pexp_assert */27 :
+        case /* Pexp_lazy */28 :
+            if (isLhs) {
+              return /* Parenthesized */0;
+            }
+            break;
+        default:
+          
+      }
+    }
+    
+  }
+  if (Res_parsetree_viewer.hasPrintableAttributes(expr.pexp_attributes)) {
+    return /* Parenthesized */0;
+  } else {
+    return /* Nothing */1;
+  }
+}
+
+function subBinaryExprOperand(parentOperator, childOperator) {
+  var precParent = Res_parsetree_viewer.operatorPrecedence(parentOperator);
+  var precChild = Res_parsetree_viewer.operatorPrecedence(childOperator);
+  if (precParent > precChild || precParent === precChild && !Res_parsetree_viewer.flattenableOperators(parentOperator, childOperator)) {
+    return true;
+  } else if (parentOperator === "||") {
+    return childOperator === "&&";
+  } else {
+    return false;
+  }
+}
+
+function rhsBinaryExprOperand(parentOperator, rhs) {
+  var match = rhs.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0;
+  var match$2 = match$1.pexp_desc;
+  if (typeof match$2 === "number") {
+    return false;
+  }
+  if (match$2.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var match$3 = match$2._0;
+  var operator = match$3.txt;
+  switch (operator.TAG | 0) {
+    case /* Lident */0 :
+        if (match$1.pexp_attributes) {
+          return false;
+        }
+        var match$4 = match._1;
+        if (!match$4) {
+          return false;
+        }
+        var match$5 = match$4.tl;
+        if (!match$5) {
+          return false;
+        }
+        if (match$5.tl) {
+          return false;
+        }
+        var operator$1 = operator._0;
+        if (!(Res_parsetree_viewer.isBinaryOperator(operator$1) && !(match$3.loc.loc_ghost && operator$1 === "^"))) {
+          return false;
+        }
+        var precParent = Res_parsetree_viewer.operatorPrecedence(parentOperator);
+        var precChild = Res_parsetree_viewer.operatorPrecedence(operator$1);
+        return precParent === precChild;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function flattenOperandRhs(parentOperator, rhs) {
+  var match = rhs.pexp_desc;
+  if (typeof match !== "number") {
+    switch (match.TAG | 0) {
+      case /* Pexp_fun */4 :
+          if (Res_parsetree_viewer.isUnderscoreApplySugar(rhs)) {
+            return false;
+          } else {
+            return true;
+          }
+      case /* Pexp_apply */5 :
+          var match$1 = match._0.pexp_desc;
+          if (typeof match$1 !== "number" && match$1.TAG === /* Pexp_ident */0) {
+            var match$2 = match$1._0;
+            var operator = match$2.txt;
+            switch (operator.TAG | 0) {
+              case /* Lident */0 :
+                  var match$3 = match._1;
+                  if (match$3) {
+                    var match$4 = match$3.tl;
+                    if (match$4 && !match$4.tl) {
+                      var operator$1 = operator._0;
+                      if (Res_parsetree_viewer.isBinaryOperator(operator$1) && !(match$2.loc.loc_ghost && operator$1 === "^")) {
+                        var precParent = Res_parsetree_viewer.operatorPrecedence(parentOperator);
+                        var precChild = Res_parsetree_viewer.operatorPrecedence(operator$1);
+                        if (precParent >= precChild) {
+                          return true;
+                        } else {
+                          return rhs.pexp_attributes !== /* [] */0;
+                        }
+                      }
+                      
+                    }
+                    
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  break;
+              
+            }
+          }
+          break;
+      case /* Pexp_constraint */19 :
+          var tmp = match._0.pexp_desc;
+          if (typeof tmp === "number") {
+            return true;
+          }
+          if (tmp.TAG !== /* Pexp_pack */32) {
+            return true;
+          }
+          var tmp$1 = match._1.ptyp_desc;
+          if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+            return true;
+          } else {
+            return false;
+          }
+      case /* Pexp_setfield */13 :
+      case /* Pexp_newtype */31 :
+          return true;
+      default:
+        
+    }
+  }
+  if (Res_parsetree_viewer.isTernaryExpr(rhs)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function lazyOrAssertExprRhs(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  var match$2 = Res_parsetree_viewer.filterParsingAttrs(expr.pexp_attributes);
+  if (match$2 ? true : false) {
+    return /* Parenthesized */0;
+  }
+  if (Res_parsetree_viewer.isBinaryExpression(expr)) {
+    return /* Parenthesized */0;
+  }
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  switch (match$1.TAG | 0) {
+    case /* Pexp_fun */4 :
+        if (Res_parsetree_viewer.isUnderscoreApplySugar(expr)) {
+          return /* Nothing */1;
+        } else {
+          return /* Parenthesized */0;
+        }
+    case /* Pexp_constraint */19 :
+        var tmp = match$1._0.pexp_desc;
+        if (typeof tmp === "number") {
+          return /* Parenthesized */0;
+        }
+        if (tmp.TAG !== /* Pexp_pack */32) {
+          return /* Parenthesized */0;
+        }
+        var tmp$1 = match$1._1.ptyp_desc;
+        if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_function */3 :
+    case /* Pexp_match */6 :
+    case /* Pexp_try */7 :
+    case /* Pexp_setfield */13 :
+    case /* Pexp_ifthenelse */15 :
+    case /* Pexp_while */17 :
+    case /* Pexp_for */18 :
+    case /* Pexp_assert */27 :
+    case /* Pexp_lazy */28 :
+    case /* Pexp_newtype */31 :
+        return /* Parenthesized */0;
+    default:
+      return /* Nothing */1;
+  }
+}
+
+function isNegativeConstant(constant) {
+  var isNeg = function (txt) {
+    var len = txt.length;
+    if (len > 0) {
+      return Caml_string.get(txt, 0) === /* '-' */45;
+    } else {
+      return false;
+    }
+  };
+  switch (constant.TAG | 0) {
+    case /* Pconst_char */1 :
+    case /* Pconst_string */2 :
+        return false;
+    case /* Pconst_integer */0 :
+    case /* Pconst_float */3 :
+        if (isNeg(constant._0)) {
+          return true;
+        } else {
+          return false;
+        }
+    
+  }
+}
+
+function fieldExpr(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var c = expr.pexp_desc;
+  var match$1 = Res_parsetree_viewer.filterParsingAttrs(expr.pexp_attributes);
+  if (match$1 ? true : false) {
+    return /* Parenthesized */0;
+  }
+  if (Res_parsetree_viewer.isBinaryExpression(expr) || Res_parsetree_viewer.isUnaryExpression(expr)) {
+    return /* Parenthesized */0;
+  }
+  if (typeof c === "number") {
+    return /* Nothing */1;
+  }
+  switch (c.TAG | 0) {
+    case /* Pexp_constant */1 :
+        if (isNegativeConstant(c._0)) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_fun */4 :
+        if (Res_parsetree_viewer.isUnderscoreApplySugar(expr)) {
+          return /* Nothing */1;
+        } else {
+          return /* Parenthesized */0;
+        }
+    case /* Pexp_constraint */19 :
+        var tmp = c._0.pexp_desc;
+        if (typeof tmp === "number") {
+          return /* Parenthesized */0;
+        }
+        if (tmp.TAG !== /* Pexp_pack */32) {
+          return /* Parenthesized */0;
+        }
+        var tmp$1 = c._1.ptyp_desc;
+        if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_function */3 :
+    case /* Pexp_match */6 :
+    case /* Pexp_try */7 :
+    case /* Pexp_setfield */13 :
+    case /* Pexp_ifthenelse */15 :
+    case /* Pexp_while */17 :
+    case /* Pexp_for */18 :
+    case /* Pexp_assert */27 :
+    case /* Pexp_lazy */28 :
+    case /* Pexp_newtype */31 :
+    case /* Pexp_extension */34 :
+        return /* Parenthesized */0;
+    default:
+      return /* Nothing */1;
+  }
+}
+
+function setFieldExprRhs(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  if (match$1.TAG !== /* Pexp_constraint */19) {
+    return /* Nothing */1;
+  }
+  var tmp = match$1._0.pexp_desc;
+  if (typeof tmp === "number") {
+    return /* Parenthesized */0;
+  }
+  if (tmp.TAG !== /* Pexp_pack */32) {
+    return /* Parenthesized */0;
+  }
+  var tmp$1 = match$1._1.ptyp_desc;
+  if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+    return /* Parenthesized */0;
+  } else {
+    return /* Nothing */1;
+  }
+}
+
+function ternaryOperand(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  }
+  var match$1 = expr.pexp_desc;
+  if (typeof match$1 === "number") {
+    return /* Nothing */1;
+  }
+  switch (match$1.TAG | 0) {
+    case /* Pexp_constraint */19 :
+        var tmp = match$1._0.pexp_desc;
+        if (typeof tmp === "number") {
+          return /* Parenthesized */0;
+        }
+        if (tmp.TAG !== /* Pexp_pack */32) {
+          return /* Parenthesized */0;
+        }
+        var tmp$1 = match$1._1.ptyp_desc;
+        if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+          return /* Parenthesized */0;
+        } else {
+          return /* Nothing */1;
+        }
+    case /* Pexp_fun */4 :
+    case /* Pexp_newtype */31 :
+        break;
+    default:
+      return /* Nothing */1;
+  }
+  var match$2 = Res_parsetree_viewer.funExpr(expr);
+  var match$3 = match$2[2].pexp_desc;
+  if (typeof match$3 === "number" || match$3.TAG !== /* Pexp_constraint */19) {
+    return /* Nothing */1;
+  } else {
+    return /* Parenthesized */0;
+  }
+}
+
+function startsWithMinus(txt) {
+  var len = txt.length;
+  if (len === 0) {
+    return false;
+  }
+  var s = Caml_string.get(txt, 0);
+  return s === /* '-' */45;
+}
+
+function jsxPropExpr(expr) {
+  var match = expr.pexp_desc;
+  var exit = 0;
+  if (typeof match === "number") {
+    exit = 1;
+  } else {
+    switch (match.TAG | 0) {
+      case /* Pexp_let */2 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_open */33 :
+          return /* Nothing */1;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    var match$1 = Res_parsetree_viewer.processBracesAttr(expr);
+    var optBraces = match$1[0];
+    if (optBraces !== undefined) {
+      return /* Braced */{
+              _0: optBraces[0].loc
+            };
+    }
+    var match$2 = expr.pexp_desc;
+    var exit$1 = 0;
+    if (typeof match$2 === "number") {
+      return /* Parenthesized */0;
+    }
+    switch (match$2.TAG | 0) {
+      case /* Pexp_constant */1 :
+          var match$3 = match$2._0;
+          var exit$2 = 0;
+          switch (match$3.TAG | 0) {
+            case /* Pconst_char */1 :
+            case /* Pconst_string */2 :
+                exit$1 = 2;
+                break;
+            case /* Pconst_integer */0 :
+            case /* Pconst_float */3 :
+                exit$2 = 3;
+                break;
+            
+          }
+          if (exit$2 === 3) {
+            if (expr.pexp_attributes) {
+              exit$1 = 2;
+            } else {
+              if (startsWithMinus(match$3._0)) {
+                return /* Parenthesized */0;
+              }
+              exit$1 = 2;
+            }
+          }
+          break;
+      case /* Pexp_constraint */19 :
+          var tmp = match$2._0.pexp_desc;
+          if (typeof tmp === "number") {
+            return /* Parenthesized */0;
+          }
+          if (tmp.TAG !== /* Pexp_pack */32) {
+            return /* Parenthesized */0;
+          }
+          var tmp$1 = match$2._1.ptyp_desc;
+          if (typeof tmp$1 === "number" || !(tmp$1.TAG === /* Ptyp_package */9 && !expr.pexp_attributes)) {
+            return /* Parenthesized */0;
+          } else {
+            return /* Nothing */1;
+          }
+      case /* Pexp_ident */0 :
+      case /* Pexp_let */2 :
+      case /* Pexp_tuple */8 :
+      case /* Pexp_construct */9 :
+      case /* Pexp_variant */10 :
+      case /* Pexp_record */11 :
+      case /* Pexp_field */12 :
+      case /* Pexp_array */14 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_pack */32 :
+      case /* Pexp_open */33 :
+      case /* Pexp_extension */34 :
+          exit$1 = 2;
+          break;
+      default:
+        return /* Parenthesized */0;
+    }
+    if (exit$1 === 2) {
+      if (expr.pexp_attributes) {
+        return /* Parenthesized */0;
+      } else {
+        return /* Nothing */1;
+      }
+    }
+    
+  }
+  
+}
+
+function jsxChildExpr(expr) {
+  var match = expr.pexp_desc;
+  var exit = 0;
+  if (typeof match === "number") {
+    exit = 1;
+  } else {
+    switch (match.TAG | 0) {
+      case /* Pexp_let */2 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_open */33 :
+          return /* Nothing */1;
+      default:
+        exit = 1;
+    }
+  }
+  if (exit === 1) {
+    var match$1 = Res_parsetree_viewer.processBracesAttr(expr);
+    var optBraces = match$1[0];
+    if (optBraces !== undefined) {
+      return /* Braced */{
+              _0: optBraces[0].loc
+            };
+    }
+    var match$2 = expr.pexp_desc;
+    var exit$1 = 0;
+    var exit$2 = 0;
+    if (typeof match$2 === "number") {
+      exit$1 = 2;
+    } else {
+      switch (match$2.TAG | 0) {
+        case /* Pexp_constant */1 :
+            var match$3 = match$2._0;
+            var exit$3 = 0;
+            switch (match$3.TAG | 0) {
+              case /* Pconst_char */1 :
+              case /* Pconst_string */2 :
+                  exit$2 = 3;
+                  break;
+              case /* Pconst_integer */0 :
+              case /* Pconst_float */3 :
+                  exit$3 = 4;
+                  break;
+              
+            }
+            if (exit$3 === 4) {
+              if (expr.pexp_attributes) {
+                exit$2 = 3;
+              } else {
+                if (startsWithMinus(match$3._0)) {
+                  return /* Parenthesized */0;
+                }
+                exit$2 = 3;
+              }
+            }
+            break;
+        case /* Pexp_constraint */19 :
+            var tmp = match$2._0.pexp_desc;
+            if (typeof tmp === "number" || tmp.TAG !== /* Pexp_pack */32) {
+              exit$1 = 2;
+            } else {
+              var tmp$1 = match$2._1.ptyp_desc;
+              if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+                exit$1 = 2;
+              } else {
+                if (!expr.pexp_attributes) {
+                  return /* Nothing */1;
+                }
+                exit$1 = 2;
+              }
+            }
+            break;
+        case /* Pexp_ident */0 :
+        case /* Pexp_let */2 :
+        case /* Pexp_construct */9 :
+        case /* Pexp_variant */10 :
+        case /* Pexp_record */11 :
+        case /* Pexp_field */12 :
+        case /* Pexp_array */14 :
+        case /* Pexp_sequence */16 :
+        case /* Pexp_letmodule */25 :
+        case /* Pexp_letexception */26 :
+        case /* Pexp_pack */32 :
+        case /* Pexp_open */33 :
+        case /* Pexp_extension */34 :
+            exit$2 = 3;
+            break;
+        default:
+          exit$1 = 2;
+      }
+    }
+    if (exit$2 === 3) {
+      if (!expr.pexp_attributes) {
+        return /* Nothing */1;
+      }
+      exit$1 = 2;
+    }
+    if (exit$1 === 2) {
+      if (Res_parsetree_viewer.isJsxExpression(expr)) {
+        return /* Nothing */1;
+      } else {
+        return /* Parenthesized */0;
+      }
+    }
+    
+  }
+  
+}
+
+function binaryExpr(expr) {
+  var match = Res_parsetree_viewer.processBracesAttr(expr);
+  var optBraces = match[0];
+  if (optBraces !== undefined) {
+    return /* Braced */{
+            _0: optBraces[0].loc
+          };
+  } else if (expr.pexp_attributes && Res_parsetree_viewer.isBinaryExpression(expr)) {
+    return /* Parenthesized */0;
+  } else {
+    return /* Nothing */1;
+  }
+}
+
+function modTypeFunctorReturn(modType) {
+  if (modType.pmty_desc.TAG === /* Pmty_with */3) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function modTypeWithOperand(modType) {
+  switch (modType.pmty_desc.TAG | 0) {
+    case /* Pmty_functor */2 :
+    case /* Pmty_with */3 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function modExprFunctorConstraint(modType) {
+  switch (modType.pmty_desc.TAG | 0) {
+    case /* Pmty_functor */2 :
+    case /* Pmty_with */3 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function bracedExpr(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_constraint */19) {
+    return false;
+  }
+  var tmp = match._0.pexp_desc;
+  if (typeof tmp === "number") {
+    return true;
+  }
+  if (tmp.TAG !== /* Pexp_pack */32) {
+    return true;
+  }
+  var tmp$1 = match._1.ptyp_desc;
+  if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function includeModExpr(modExpr) {
+  var match = modExpr.pmod_desc;
+  if (match.TAG === /* Pmod_constraint */4) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function arrowReturnTypExpr(typExpr) {
+  var match = typExpr.ptyp_desc;
+  if (typeof match === "number" || match.TAG !== /* Ptyp_arrow */1) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function patternRecordRowRhs(pattern) {
+  var match = pattern.ppat_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Ppat_constraint */10) {
+    return false;
+  }
+  var tmp = match._0.ppat_desc;
+  if (typeof tmp === "number") {
+    return true;
+  }
+  if (tmp.TAG !== /* Ppat_unpack */13) {
+    return true;
+  }
+  var tmp$1 = match._1.ptyp_desc;
+  if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+var ParsetreeViewer;
+
+export {
+  ParsetreeViewer ,
+  expr ,
+  callExpr ,
+  structureExpr ,
+  unaryExprOperand ,
+  binaryExprOperand ,
+  subBinaryExprOperand ,
+  rhsBinaryExprOperand ,
+  flattenOperandRhs ,
+  lazyOrAssertExprRhs ,
+  isNegativeConstant ,
+  fieldExpr ,
+  setFieldExprRhs ,
+  ternaryOperand ,
+  startsWithMinus ,
+  jsxPropExpr ,
+  jsxChildExpr ,
+  binaryExpr ,
+  modTypeFunctorReturn ,
+  modTypeWithOperand ,
+  modExprFunctorConstraint ,
+  bracedExpr ,
+  includeModExpr ,
+  arrowReturnTypExpr ,
+  patternRecordRowRhs ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_parens.res b/analysis/examples/larger-project/src/res_parens.res
new file mode 100644
index 0000000000..53622a572a
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parens.res
@@ -0,0 +1,517 @@
+module ParsetreeViewer = Res_parsetree_viewer
+type kind = Parenthesized | Braced(Location.t) | Nothing
+
+let expr = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | _ =>
+    switch expr {
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_constraint(_)} => Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let callExpr = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | _ =>
+    switch expr {
+    | {Parsetree.pexp_attributes: attrs}
+      if switch ParsetreeViewer.filterParsingAttrs(attrs) {
+      | list{_, ..._} => true
+      | list{} => false
+      } =>
+      Parenthesized
+    | _ if ParsetreeViewer.isUnaryExpression(expr) || ParsetreeViewer.isBinaryExpression(expr) =>
+      Parenthesized
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_fun(_)} if ParsetreeViewer.isUnderscoreApplySugar(expr) => Nothing
+    | {
+        pexp_desc:
+          Pexp_lazy(_)
+          | Pexp_assert(_)
+          | Pexp_fun(_)
+          | Pexp_newtype(_)
+          | Pexp_function(_)
+          | Pexp_constraint(_)
+          | Pexp_setfield(_)
+          | Pexp_match(_)
+          | Pexp_try(_)
+          | Pexp_while(_)
+          | Pexp_for(_)
+          | Pexp_ifthenelse(_),
+      } =>
+      Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let structureExpr = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | _
+      if ParsetreeViewer.hasAttributes(expr.pexp_attributes) &&
+      !ParsetreeViewer.isJsxExpression(expr) =>
+      Parenthesized
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_constraint(_)} => Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let unaryExprOperand = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {Parsetree.pexp_attributes: attrs}
+      if switch ParsetreeViewer.filterParsingAttrs(attrs) {
+      | list{_, ..._} => true
+      | list{} => false
+      } =>
+      Parenthesized
+    | expr if ParsetreeViewer.isUnaryExpression(expr) || ParsetreeViewer.isBinaryExpression(expr) =>
+      Parenthesized
+    | {pexp_desc: Pexp_constraint({pexp_desc: Pexp_pack(_)}, {ptyp_desc: Ptyp_package(_)})} =>
+      Nothing
+    | {pexp_desc: Pexp_fun(_)} if ParsetreeViewer.isUnderscoreApplySugar(expr) => Nothing
+    | {
+        pexp_desc:
+          Pexp_lazy(_)
+          | Pexp_assert(_)
+          | Pexp_fun(_)
+          | Pexp_newtype(_)
+          | Pexp_function(_)
+          | Pexp_constraint(_)
+          | Pexp_setfield(_)
+          | Pexp_extension(_)
+          | Pexp_match(_)
+          | Pexp_try(_)
+          | Pexp_while(_)
+          | Pexp_for(_)
+          | Pexp_ifthenelse(_),
+      } =>
+      Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let binaryExprOperand = (~isLhs, expr) => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_fun(_)} if ParsetreeViewer.isUnderscoreApplySugar(expr) => Nothing
+    | {pexp_desc: Pexp_constraint(_) | Pexp_fun(_) | Pexp_function(_) | Pexp_newtype(_)} =>
+      Parenthesized
+    | expr if ParsetreeViewer.isBinaryExpression(expr) => Parenthesized
+    | expr if ParsetreeViewer.isTernaryExpr(expr) => Parenthesized
+    | {
+        pexp_desc:
+          Pexp_lazy(_)
+          | Pexp_assert(_),
+      } if isLhs =>
+      Parenthesized
+    | {Parsetree.pexp_attributes: attrs} =>
+      if ParsetreeViewer.hasPrintableAttributes(attrs) {
+        Parenthesized
+      } else {
+        Nothing
+      }
+    }
+  }
+}
+
+let subBinaryExprOperand = (parentOperator, childOperator) => {
+  let precParent = ParsetreeViewer.operatorPrecedence(parentOperator)
+  let precChild = ParsetreeViewer.operatorPrecedence(childOperator)
+  precParent > precChild ||
+    ((precParent === precChild &&
+      !ParsetreeViewer.flattenableOperators(parentOperator, childOperator)) ||
+    /* a && b || c, add parens to (a && b) for readability, who knows the difference by heart… */
+    parentOperator == "||" && childOperator == "&&")
+}
+
+let rhsBinaryExprOperand = (parentOperator, rhs) =>
+  switch rhs.Parsetree.pexp_desc {
+  | Parsetree.Pexp_apply(
+      {
+        pexp_attributes: list{},
+        pexp_desc: Pexp_ident({txt: Longident.Lident(operator), loc: operatorLoc}),
+      },
+      list{(_, _left), (_, _right)},
+    )
+    if ParsetreeViewer.isBinaryOperator(operator) && !(operatorLoc.loc_ghost && operator == "^") =>
+    let precParent = ParsetreeViewer.operatorPrecedence(parentOperator)
+    let precChild = ParsetreeViewer.operatorPrecedence(operator)
+    precParent === precChild
+  | _ => false
+  }
+
+let flattenOperandRhs = (parentOperator, rhs) =>
+  switch rhs.Parsetree.pexp_desc {
+  | Parsetree.Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident(operator), loc: operatorLoc})},
+      list{(_, _left), (_, _right)},
+    )
+    if ParsetreeViewer.isBinaryOperator(operator) && !(operatorLoc.loc_ghost && operator == "^") =>
+    let precParent = ParsetreeViewer.operatorPrecedence(parentOperator)
+    let precChild = ParsetreeViewer.operatorPrecedence(operator)
+    precParent >= precChild || rhs.pexp_attributes != list{}
+  | Pexp_constraint({pexp_desc: Pexp_pack(_)}, {ptyp_desc: Ptyp_package(_)}) => false
+  | Pexp_fun(_) if ParsetreeViewer.isUnderscoreApplySugar(rhs) => false
+  | Pexp_fun(_)
+  | Pexp_newtype(_)
+  | Pexp_setfield(_)
+  | Pexp_constraint(_) => true
+  | _ if ParsetreeViewer.isTernaryExpr(rhs) => true
+  | _ => false
+  }
+
+let lazyOrAssertExprRhs = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {Parsetree.pexp_attributes: attrs}
+      if switch ParsetreeViewer.filterParsingAttrs(attrs) {
+      | list{_, ..._} => true
+      | list{} => false
+      } =>
+      Parenthesized
+    | expr if ParsetreeViewer.isBinaryExpression(expr) => Parenthesized
+    | {pexp_desc: Pexp_constraint({pexp_desc: Pexp_pack(_)}, {ptyp_desc: Ptyp_package(_)})} =>
+      Nothing
+    | {pexp_desc: Pexp_fun(_)} if ParsetreeViewer.isUnderscoreApplySugar(expr) => Nothing
+    | {
+        pexp_desc:
+          Pexp_lazy(_)
+          | Pexp_assert(_)
+          | Pexp_fun(_)
+          | Pexp_newtype(_)
+          | Pexp_function(_)
+          | Pexp_constraint(_)
+          | Pexp_setfield(_)
+          | Pexp_match(_)
+          | Pexp_try(_)
+          | Pexp_while(_)
+          | Pexp_for(_)
+          | Pexp_ifthenelse(_),
+      } =>
+      Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let isNegativeConstant = constant => {
+  let isNeg = txt => {
+    let len = String.length(txt)
+    len > 0 && (@doesNotRaise String.get)(txt, 0) == '-'
+  }
+
+  switch constant {
+  | Parsetree.Pconst_integer(i, _) | Pconst_float(i, _) if isNeg(i) => true
+  | _ => false
+  }
+}
+
+let fieldExpr = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {Parsetree.pexp_attributes: attrs}
+      if switch ParsetreeViewer.filterParsingAttrs(attrs) {
+      | list{_, ..._} => true
+      | list{} => false
+      } =>
+      Parenthesized
+    | expr if ParsetreeViewer.isBinaryExpression(expr) || ParsetreeViewer.isUnaryExpression(expr) =>
+      Parenthesized
+    | {pexp_desc: Pexp_constraint({pexp_desc: Pexp_pack(_)}, {ptyp_desc: Ptyp_package(_)})} =>
+      Nothing
+    | {pexp_desc: Pexp_constant(c)} if isNegativeConstant(c) => Parenthesized
+    | {pexp_desc: Pexp_fun(_)} if ParsetreeViewer.isUnderscoreApplySugar(expr) => Nothing
+    | {
+        pexp_desc:
+          Pexp_lazy(_)
+          | Pexp_assert(_)
+          | Pexp_extension(_)
+          | Pexp_fun(_)
+          | Pexp_newtype(_)
+          | Pexp_function(_)
+          | Pexp_constraint(_)
+          | Pexp_setfield(_)
+          | Pexp_match(_)
+          | Pexp_try(_)
+          | Pexp_while(_)
+          | Pexp_for(_)
+          | Pexp_ifthenelse(_),
+      } =>
+      Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let setFieldExprRhs = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_constraint(_)} => Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let ternaryOperand = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {
+        Parsetree.pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_pack(_)},
+          {ptyp_desc: Ptyp_package(_)},
+        ),
+      } =>
+      Nothing
+    | {pexp_desc: Pexp_constraint(_)} => Parenthesized
+    | {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)} =>
+      let (_attrsOnArrow, _parameters, returnExpr) = ParsetreeViewer.funExpr(expr)
+      switch returnExpr.pexp_desc {
+      | Pexp_constraint(_) => Parenthesized
+      | _ => Nothing
+      }
+    | _ => Nothing
+    }
+  }
+}
+
+let startsWithMinus = txt => {
+  let len = String.length(txt)
+  if len === 0 {
+    false
+  } else {
+    let s = (@doesNotRaise String.get)(txt, 0)
+    s == '-'
+  }
+}
+
+let jsxPropExpr = expr =>
+  switch expr.Parsetree.pexp_desc {
+  | Parsetree.Pexp_let(_)
+  | Pexp_sequence(_)
+  | Pexp_letexception(_)
+  | Pexp_letmodule(_)
+  | Pexp_open(_) =>
+    Nothing
+  | _ =>
+    let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+    switch optBraces {
+    | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+    | None =>
+      switch expr {
+      | {
+          Parsetree.pexp_desc: Pexp_constant(Pconst_integer(x, _) | Pconst_float(x, _)),
+          pexp_attributes: list{},
+        } if startsWithMinus(x) =>
+        Parenthesized
+      | {
+          Parsetree.pexp_desc:
+            Pexp_ident(_)
+            | Pexp_constant(_)
+            | Pexp_field(_)
+            | Pexp_construct(_)
+            | Pexp_variant(_)
+            | Pexp_array(_)
+            | Pexp_pack(_)
+            | Pexp_record(_)
+            | Pexp_extension(_)
+            | Pexp_letmodule(_)
+            | Pexp_letexception(_)
+            | Pexp_open(_)
+            | Pexp_sequence(_)
+            | Pexp_let(_)
+            | Pexp_tuple(_),
+          pexp_attributes: list{},
+        } =>
+        Nothing
+      | {
+          Parsetree.pexp_desc: Pexp_constraint(
+            {pexp_desc: Pexp_pack(_)},
+            {ptyp_desc: Ptyp_package(_)},
+          ),
+          pexp_attributes: list{},
+        } =>
+        Nothing
+      | _ => Parenthesized
+      }
+    }
+  }
+
+let jsxChildExpr = expr =>
+  switch expr.Parsetree.pexp_desc {
+  | Parsetree.Pexp_let(_)
+  | Pexp_sequence(_)
+  | Pexp_letexception(_)
+  | Pexp_letmodule(_)
+  | Pexp_open(_) =>
+    Nothing
+  | _ =>
+    let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+    switch optBraces {
+    | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+    | _ =>
+      switch expr {
+      | {
+          Parsetree.pexp_desc: Pexp_constant(Pconst_integer(x, _) | Pconst_float(x, _)),
+          pexp_attributes: list{},
+        } if startsWithMinus(x) =>
+        Parenthesized
+      | {
+          Parsetree.pexp_desc:
+            Pexp_ident(_)
+            | Pexp_constant(_)
+            | Pexp_field(_)
+            | Pexp_construct(_)
+            | Pexp_variant(_)
+            | Pexp_array(_)
+            | Pexp_pack(_)
+            | Pexp_record(_)
+            | Pexp_extension(_)
+            | Pexp_letmodule(_)
+            | Pexp_letexception(_)
+            | Pexp_open(_)
+            | Pexp_sequence(_)
+            | Pexp_let(_),
+          pexp_attributes: list{},
+        } =>
+        Nothing
+      | {
+          Parsetree.pexp_desc: Pexp_constraint(
+            {pexp_desc: Pexp_pack(_)},
+            {ptyp_desc: Ptyp_package(_)},
+          ),
+          pexp_attributes: list{},
+        } =>
+        Nothing
+      | expr if ParsetreeViewer.isJsxExpression(expr) => Nothing
+      | _ => Parenthesized
+      }
+    }
+  }
+
+let binaryExpr = expr => {
+  let (optBraces, _) = ParsetreeViewer.processBracesAttr(expr)
+  switch optBraces {
+  | Some({Location.loc: bracesLoc}, _) => Braced(bracesLoc)
+  | None =>
+    switch expr {
+    | {Parsetree.pexp_attributes: list{_, ..._}} as expr
+      if ParsetreeViewer.isBinaryExpression(expr) =>
+      Parenthesized
+    | _ => Nothing
+    }
+  }
+}
+
+let modTypeFunctorReturn = modType =>
+  switch modType {
+  | {Parsetree.pmty_desc: Pmty_with(_)} => true
+  | _ => false
+  }
+
+/* Add parens for readability:
+       module type Functor = SetLike => Set with type t = A.t
+     This is actually:
+       module type Functor = (SetLike => Set) with type t = A.t
+ */
+let modTypeWithOperand = modType =>
+  switch modType {
+  | {Parsetree.pmty_desc: Pmty_functor(_) | Pmty_with(_)} => true
+  | _ => false
+  }
+
+let modExprFunctorConstraint = modType =>
+  switch modType {
+  | {Parsetree.pmty_desc: Pmty_functor(_) | Pmty_with(_)} => true
+  | _ => false
+  }
+
+let bracedExpr = expr =>
+  switch expr.Parsetree.pexp_desc {
+  | Pexp_constraint({pexp_desc: Pexp_pack(_)}, {ptyp_desc: Ptyp_package(_)}) => false
+  | Pexp_constraint(_) => true
+  | _ => false
+  }
+
+let includeModExpr = modExpr =>
+  switch modExpr.Parsetree.pmod_desc {
+  | Parsetree.Pmod_constraint(_) => true
+  | _ => false
+  }
+
+let arrowReturnTypExpr = typExpr =>
+  switch typExpr.Parsetree.ptyp_desc {
+  | Parsetree.Ptyp_arrow(_) => true
+  | _ => false
+  }
+
+let patternRecordRowRhs = (pattern: Parsetree.pattern) =>
+  switch pattern.ppat_desc {
+  | Ppat_constraint({ppat_desc: Ppat_unpack(_)}, {ptyp_desc: Ptyp_package(_)}) => false
+  | Ppat_constraint(_) => true
+  | _ => false
+  }
+
diff --git a/analysis/examples/larger-project/src/res_parser.js b/analysis/examples/larger-project/src/res_parser.js
new file mode 100644
index 0000000000..a60ae3d599
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parser.js
@@ -0,0 +1,250 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Lexing from "rescript/lib/es6/lexing.js";
+import * as Caml_obj from "rescript/lib/es6/caml_obj.js";
+import * as Caml_option from "rescript/lib/es6/caml_option.js";
+import * as Res_comment from "./res_comment.js";
+import * as Res_scanner from "./res_scanner.js";
+import * as Res_diagnostics from "./res_diagnostics.js";
+
+function err(startPos, endPos, p, error) {
+  var match = p.regions;
+  if (!match) {
+    return ;
+  }
+  var region = match.hd;
+  var match$1 = region.contents;
+  if (match$1) {
+    return ;
+  }
+  var d = Res_diagnostics.make(startPos !== undefined ? startPos : p.startPos, endPos !== undefined ? endPos : p.endPos, error);
+  p.diagnostics = {
+    hd: d,
+    tl: p.diagnostics
+  };
+  region.contents = /* Silent */1;
+  
+}
+
+function beginRegion(p) {
+  p.regions = {
+    hd: {
+      contents: /* Report */0
+    },
+    tl: p.regions
+  };
+  
+}
+
+function endRegion(p) {
+  var match = p.regions;
+  if (match) {
+    p.regions = match.tl;
+    return ;
+  }
+  
+}
+
+function next(_prevEndPos, p) {
+  while(true) {
+    var prevEndPos = _prevEndPos;
+    if (p.token === /* Eof */26) {
+      throw {
+            RE_EXN_ID: "Assert_failure",
+            _1: [
+              "res_parser.res",
+              59,
+              4
+            ],
+            Error: new Error()
+          };
+    }
+    var prevEndPos$1 = prevEndPos !== undefined ? prevEndPos : p.endPos;
+    var match = Res_scanner.scan(p.scanner);
+    var token = match[2];
+    var endPos = match[1];
+    if (typeof token !== "number" && token.TAG === /* Comment */6) {
+      var c = token._0;
+      Res_comment.setPrevTokEndPos(c, p.endPos);
+      p.comments = {
+        hd: c,
+        tl: p.comments
+      };
+      p.prevEndPos = p.endPos;
+      p.endPos = endPos;
+      _prevEndPos = prevEndPos$1;
+      continue ;
+    }
+    p.token = token;
+    p.prevEndPos = prevEndPos$1;
+    p.startPos = match[0];
+    p.endPos = endPos;
+    return ;
+  };
+}
+
+function nextUnsafe(p) {
+  if (p.token !== /* Eof */26) {
+    return next(undefined, p);
+  }
+  
+}
+
+function nextTemplateLiteralToken(p) {
+  var match = Res_scanner.scanTemplateLiteralToken(p.scanner);
+  p.token = match[2];
+  p.prevEndPos = p.endPos;
+  p.startPos = match[0];
+  p.endPos = match[1];
+  
+}
+
+function checkProgress(prevEndPos, result, p) {
+  if (p.endPos === prevEndPos) {
+    return ;
+  } else {
+    return Caml_option.some(result);
+  }
+}
+
+function make(modeOpt, src, filename) {
+  var mode = modeOpt !== undefined ? modeOpt : /* ParseForTypeChecker */0;
+  var scanner = Res_scanner.make(filename, src);
+  var parserState = {
+    mode: mode,
+    scanner: scanner,
+    token: /* Semicolon */8,
+    startPos: Lexing.dummy_pos,
+    endPos: Lexing.dummy_pos,
+    prevEndPos: Lexing.dummy_pos,
+    breadcrumbs: /* [] */0,
+    errors: /* [] */0,
+    diagnostics: /* [] */0,
+    comments: /* [] */0,
+    regions: {
+      hd: {
+        contents: /* Report */0
+      },
+      tl: /* [] */0
+    }
+  };
+  parserState.scanner.err = (function (startPos, endPos, error) {
+      var diagnostic = Res_diagnostics.make(startPos, endPos, error);
+      parserState.diagnostics = {
+        hd: diagnostic,
+        tl: parserState.diagnostics
+      };
+      
+    });
+  next(undefined, parserState);
+  return parserState;
+}
+
+function leaveBreadcrumb(p, circumstance) {
+  var crumb_1 = p.startPos;
+  var crumb = [
+    circumstance,
+    crumb_1
+  ];
+  p.breadcrumbs = {
+    hd: crumb,
+    tl: p.breadcrumbs
+  };
+  
+}
+
+function eatBreadcrumb(p) {
+  var match = p.breadcrumbs;
+  if (match) {
+    p.breadcrumbs = match.tl;
+    return ;
+  }
+  
+}
+
+function optional(p, token) {
+  if (Caml_obj.caml_equal(p.token, token)) {
+    next(undefined, p);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function expect(grammar, token, p) {
+  if (Caml_obj.caml_equal(p.token, token)) {
+    return next(undefined, p);
+  }
+  var error = Res_diagnostics.expected(grammar, p.prevEndPos, token);
+  return err(p.prevEndPos, undefined, p, error);
+}
+
+function lookahead(p, callback) {
+  var err = p.scanner.err;
+  var ch = p.scanner.ch;
+  var offset = p.scanner.offset;
+  var lineOffset = p.scanner.lineOffset;
+  var lnum = p.scanner.lnum;
+  var mode = p.scanner.mode;
+  var token = p.token;
+  var startPos = p.startPos;
+  var endPos = p.endPos;
+  var prevEndPos = p.prevEndPos;
+  var breadcrumbs = p.breadcrumbs;
+  var errors = p.errors;
+  var diagnostics = p.diagnostics;
+  var comments = p.comments;
+  var res = Curry._1(callback, p);
+  p.scanner.err = err;
+  p.scanner.ch = ch;
+  p.scanner.offset = offset;
+  p.scanner.lineOffset = lineOffset;
+  p.scanner.lnum = lnum;
+  p.scanner.mode = mode;
+  p.token = token;
+  p.startPos = startPos;
+  p.endPos = endPos;
+  p.prevEndPos = prevEndPos;
+  p.breadcrumbs = breadcrumbs;
+  p.errors = errors;
+  p.diagnostics = diagnostics;
+  p.comments = comments;
+  return res;
+}
+
+var Scanner;
+
+var Diagnostics;
+
+var Token;
+
+var Grammar;
+
+var Reporting;
+
+var $$Comment;
+
+export {
+  Scanner ,
+  Diagnostics ,
+  Token ,
+  Grammar ,
+  Reporting ,
+  $$Comment ,
+  err ,
+  beginRegion ,
+  endRegion ,
+  next ,
+  nextUnsafe ,
+  nextTemplateLiteralToken ,
+  checkProgress ,
+  make ,
+  leaveBreadcrumb ,
+  eatBreadcrumb ,
+  optional ,
+  expect ,
+  lookahead ,
+  
+}
+/* Res_comment Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_parser.res b/analysis/examples/larger-project/src/res_parser.res
new file mode 100644
index 0000000000..b46a1923d1
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parser.res
@@ -0,0 +1,191 @@
+module Scanner = Res_scanner
+module Diagnostics = Res_diagnostics
+module Token = Res_token
+module Grammar = Res_grammar
+module Reporting = Res_reporting
+
+module Comment = Res_comment
+
+type mode = ParseForTypeChecker | Default
+
+type regionStatus = Report | Silent
+
+type t = {
+  mode: mode,
+  mutable scanner: Scanner.t,
+  mutable token: Token.t,
+  mutable startPos: Lexing.position,
+  mutable endPos: Lexing.position,
+  mutable prevEndPos: Lexing.position,
+  mutable breadcrumbs: list<(Grammar.t, Lexing.position)>,
+  mutable errors: list<Reporting.parseError>,
+  mutable diagnostics: list<Diagnostics.t>,
+  mutable comments: list<Comment.t>,
+  mutable regions: list<ref<regionStatus>>,
+}
+
+let err = (~startPos=?, ~endPos=?, p, error) =>
+  switch p.regions {
+  | list{{contents: Report} as region, ..._} =>
+    let d = Diagnostics.make(
+      ~startPos=switch startPos {
+      | Some(pos) => pos
+      | None => p.startPos
+      },
+      ~endPos=switch endPos {
+      | Some(pos) => pos
+      | None => p.endPos
+      },
+      error,
+    )
+
+    p.diagnostics = list{d, ...p.diagnostics}
+    region := Silent
+  | _ => ()
+  }
+
+let beginRegion = p => p.regions = list{ref(Report), ...p.regions}
+let endRegion = p =>
+  switch p.regions {
+  | list{} => ()
+  | list{_, ...rest} => p.regions = rest
+  }
+
+/* Advance to the next non-comment token and store any encountered comment
+ * in the parser's state. Every comment contains the end position of its
+ * previous token to facilite comment interleaving */
+let rec next = (~prevEndPos=?, p) => {
+  if p.token == Eof {
+    assert false
+  }
+  let prevEndPos = switch prevEndPos {
+  | Some(pos) => pos
+  | None => p.endPos
+  }
+  let (startPos, endPos, token) = Scanner.scan(p.scanner)
+  switch token {
+  | Comment(c) =>
+    Comment.setPrevTokEndPos(c, p.endPos)
+    p.comments = list{c, ...p.comments}
+    p.prevEndPos = p.endPos
+    p.endPos = endPos
+    next(~prevEndPos, p)
+  | _ =>
+    p.token = token
+
+    /* p.prevEndPos <- prevEndPos; */
+    p.prevEndPos = prevEndPos
+    p.startPos = startPos
+    p.endPos = endPos
+  }
+}
+
+let nextUnsafe = p =>
+  if p.token != Eof {
+    next(p)
+  }
+
+let nextTemplateLiteralToken = p => {
+  let (startPos, endPos, token) = Scanner.scanTemplateLiteralToken(p.scanner)
+  p.token = token
+  p.prevEndPos = p.endPos
+  p.startPos = startPos
+  p.endPos = endPos
+}
+
+let checkProgress = (~prevEndPos, ~result, p) =>
+  if p.endPos === prevEndPos {
+    None
+  } else {
+    Some(result)
+  }
+
+let make = (~mode=ParseForTypeChecker, src, filename) => {
+  let scanner = Scanner.make(~filename, src)
+  let parserState = {
+    mode: mode,
+    scanner: scanner,
+    token: Token.Semicolon,
+    startPos: Lexing.dummy_pos,
+    prevEndPos: Lexing.dummy_pos,
+    endPos: Lexing.dummy_pos,
+    breadcrumbs: list{},
+    errors: list{},
+    diagnostics: list{},
+    comments: list{},
+    regions: list{ref(Report)},
+  }
+  parserState.scanner.err = (~startPos, ~endPos, error) => {
+    let diagnostic = Diagnostics.make(~startPos, ~endPos, error)
+
+    parserState.diagnostics = list{diagnostic, ...parserState.diagnostics}
+  }
+  next(parserState)
+  parserState
+}
+
+let leaveBreadcrumb = (p, circumstance) => {
+  let crumb = (circumstance, p.startPos)
+  p.breadcrumbs = list{crumb, ...p.breadcrumbs}
+}
+
+let eatBreadcrumb = p =>
+  switch p.breadcrumbs {
+  | list{} => ()
+  | list{_, ...crumbs} => p.breadcrumbs = crumbs
+  }
+
+let optional = (p, token) =>
+  if p.token == token {
+    let () = next(p)
+    true
+  } else {
+    false
+  }
+
+let expect = (~grammar=?, token, p) =>
+  if p.token == token {
+    next(p)
+  } else {
+    let error = Diagnostics.expected(~grammar?, p.prevEndPos, token)
+    err(~startPos=p.prevEndPos, p, error)
+  }
+
+/* Don't use immutable copies here, it trashes certain heuristics
+ * in the ocaml compiler, resulting in massive slowdowns of the parser */
+let lookahead = (p, callback) => {
+  let err = p.scanner.err
+  let ch = p.scanner.ch
+  let offset = p.scanner.offset
+  let lineOffset = p.scanner.lineOffset
+  let lnum = p.scanner.lnum
+  let mode = p.scanner.mode
+  let token = p.token
+  let startPos = p.startPos
+  let endPos = p.endPos
+  let prevEndPos = p.prevEndPos
+  let breadcrumbs = p.breadcrumbs
+  let errors = p.errors
+  let diagnostics = p.diagnostics
+  let comments = p.comments
+
+  let res = callback(p)
+
+  p.scanner.err = err
+  p.scanner.ch = ch
+  p.scanner.offset = offset
+  p.scanner.lineOffset = lineOffset
+  p.scanner.lnum = lnum
+  p.scanner.mode = mode
+  p.token = token
+  p.startPos = startPos
+  p.endPos = endPos
+  p.prevEndPos = prevEndPos
+  p.breadcrumbs = breadcrumbs
+  p.errors = errors
+  p.diagnostics = diagnostics
+  p.comments = comments
+
+  res
+}
+
diff --git a/analysis/examples/larger-project/src/res_parsetree_viewer.js b/analysis/examples/larger-project/src/res_parsetree_viewer.js
new file mode 100644
index 0000000000..f3ffcaa2a0
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parsetree_viewer.js
@@ -0,0 +1,1911 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as List from "rescript/lib/es6/list.js";
+
+function arrowType(ct) {
+  var $$process = function (attrsBefore, _acc, _typ) {
+    while(true) {
+      var typ = _typ;
+      var acc = _acc;
+      var match = typ.ptyp_desc;
+      if (typeof match === "number") {
+        return [
+                attrsBefore,
+                List.rev(acc),
+                typ
+              ];
+      }
+      if (match.TAG !== /* Ptyp_arrow */1) {
+        return [
+                attrsBefore,
+                List.rev(acc),
+                typ
+              ];
+      }
+      var lbl = match._0;
+      if (typeof lbl === "number") {
+        var attrs = typ.ptyp_attributes;
+        var typ2 = match._2;
+        var typ1 = match._1;
+        if (attrs) {
+          if (attrs.hd[0].txt === "bs" && !attrs.tl) {
+            var arg = [
+              attrs,
+              lbl,
+              typ1
+            ];
+            _typ = typ2;
+            _acc = {
+              hd: arg,
+              tl: acc
+            };
+            continue ;
+          }
+          
+        } else {
+          var arg$1 = [
+            /* [] */0,
+            lbl,
+            typ1
+          ];
+          _typ = typ2;
+          _acc = {
+            hd: arg$1,
+            tl: acc
+          };
+          continue ;
+        }
+        var args = List.rev(acc);
+        return [
+                attrsBefore,
+                args,
+                typ
+              ];
+      }
+      var arg_0 = typ.ptyp_attributes;
+      var arg_2 = match._1;
+      var arg$2 = [
+        arg_0,
+        lbl,
+        arg_2
+      ];
+      _typ = match._2;
+      _acc = {
+        hd: arg$2,
+        tl: acc
+      };
+      continue ;
+    };
+  };
+  var match = ct.ptyp_desc;
+  if (typeof match === "number" || !(match.TAG === /* Ptyp_arrow */1 && typeof match._0 === "number")) {
+    return $$process(/* [] */0, /* [] */0, ct);
+  } else {
+    return $$process(ct.ptyp_attributes, /* [] */0, {
+                ptyp_desc: ct.ptyp_desc,
+                ptyp_loc: ct.ptyp_loc,
+                ptyp_attributes: /* [] */0
+              });
+  }
+}
+
+function functorType(modtype) {
+  var _acc = /* [] */0;
+  var _modtype = modtype;
+  while(true) {
+    var modtype$1 = _modtype;
+    var acc = _acc;
+    var match = modtype$1.pmty_desc;
+    if (match.TAG !== /* Pmty_functor */2) {
+      return [
+              List.rev(acc),
+              modtype$1
+            ];
+    }
+    var arg_0 = modtype$1.pmty_attributes;
+    var arg_1 = match._0;
+    var arg_2 = match._1;
+    var arg = [
+      arg_0,
+      arg_1,
+      arg_2
+    ];
+    _modtype = match._2;
+    _acc = {
+      hd: arg,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function processUncurriedAttribute(attrs) {
+  var _uncurriedSpotted = false;
+  var _acc = /* [] */0;
+  var _attrs = attrs;
+  while(true) {
+    var attrs$1 = _attrs;
+    var acc = _acc;
+    var uncurriedSpotted = _uncurriedSpotted;
+    if (!attrs$1) {
+      return [
+              uncurriedSpotted,
+              List.rev(acc)
+            ];
+    }
+    var attr = attrs$1.hd;
+    if (attr[0].txt === "bs") {
+      _attrs = attrs$1.tl;
+      _uncurriedSpotted = true;
+      continue ;
+    }
+    _attrs = attrs$1.tl;
+    _acc = {
+      hd: attr,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function collectListExpressions(expr) {
+  var _acc = /* [] */0;
+  var _expr = expr;
+  while(true) {
+    var expr$1 = _expr;
+    var acc = _acc;
+    var match = expr$1.pexp_desc;
+    if (typeof match !== "number" && match.TAG === /* Pexp_construct */9) {
+      var match$1 = match._0.txt;
+      switch (match$1.TAG | 0) {
+        case /* Lident */0 :
+            switch (match$1._0) {
+              case "::" :
+                  var match$2 = match._1;
+                  if (match$2 !== undefined) {
+                    var match$3 = match$2.pexp_desc;
+                    if (typeof match$3 !== "number" && match$3.TAG === /* Pexp_tuple */8) {
+                      var match$4 = match$3._0;
+                      if (match$4) {
+                        var match$5 = match$4.tl;
+                        if (match$5 && !match$5.tl) {
+                          _expr = match$5.hd;
+                          _acc = {
+                            hd: match$4.hd,
+                            tl: acc
+                          };
+                          continue ;
+                        }
+                        
+                      }
+                      
+                    }
+                    
+                  }
+                  break;
+              case "[]" :
+                  return [
+                          List.rev(acc),
+                          undefined
+                        ];
+              default:
+                
+            }
+            break;
+        case /* Ldot */1 :
+        case /* Lapply */2 :
+            break;
+        
+      }
+    }
+    return [
+            List.rev(acc),
+            expr$1
+          ];
+  };
+}
+
+function rewriteUnderscoreApply(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return expr;
+  }
+  if (match.TAG !== /* Pexp_fun */4) {
+    return expr;
+  }
+  if (typeof match._0 !== "number") {
+    return expr;
+  }
+  if (match._1 !== undefined) {
+    return expr;
+  }
+  var match$1 = match._2.ppat_desc;
+  if (typeof match$1 === "number") {
+    return expr;
+  }
+  if (match$1.TAG !== /* Ppat_var */0) {
+    return expr;
+  }
+  if (match$1._0.txt !== "__x") {
+    return expr;
+  }
+  var e = match._3;
+  var match$2 = e.pexp_desc;
+  if (typeof match$2 === "number") {
+    return expr;
+  }
+  if (match$2.TAG !== /* Pexp_apply */5) {
+    return expr;
+  }
+  var newArgs = List.map((function (arg) {
+          var argExpr = arg[1];
+          var lid = argExpr.pexp_desc;
+          if (typeof lid === "number") {
+            return arg;
+          }
+          if (lid.TAG !== /* Pexp_ident */0) {
+            return arg;
+          }
+          var lid$1 = lid._0;
+          var match = lid$1.txt;
+          switch (match.TAG | 0) {
+            case /* Lident */0 :
+                if (match._0 === "__x") {
+                  return [
+                          arg[0],
+                          {
+                            pexp_desc: {
+                              TAG: /* Pexp_ident */0,
+                              _0: {
+                                txt: {
+                                  TAG: /* Lident */0,
+                                  _0: "_"
+                                },
+                                loc: lid$1.loc
+                              }
+                            },
+                            pexp_loc: argExpr.pexp_loc,
+                            pexp_attributes: argExpr.pexp_attributes
+                          }
+                        ];
+                } else {
+                  return arg;
+                }
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                return arg;
+            
+          }
+        }), match$2._1);
+  return {
+          pexp_desc: {
+            TAG: /* Pexp_apply */5,
+            _0: match$2._0,
+            _1: newArgs
+          },
+          pexp_loc: e.pexp_loc,
+          pexp_attributes: e.pexp_attributes
+        };
+}
+
+function funExpr(expr) {
+  var collectNewTypes = function (_acc, _returnExpr) {
+    while(true) {
+      var returnExpr = _returnExpr;
+      var acc = _acc;
+      var match = returnExpr.pexp_desc;
+      if (typeof match === "number") {
+        return [
+                List.rev(acc),
+                returnExpr
+              ];
+      }
+      if (match.TAG !== /* Pexp_newtype */31) {
+        return [
+                List.rev(acc),
+                returnExpr
+              ];
+      }
+      if (returnExpr.pexp_attributes) {
+        return [
+                List.rev(acc),
+                returnExpr
+              ];
+      }
+      _returnExpr = match._1;
+      _acc = {
+        hd: match._0,
+        tl: acc
+      };
+      continue ;
+    };
+  };
+  var collect = function (attrsBefore, _acc, _expr) {
+    while(true) {
+      var expr = _expr;
+      var acc = _acc;
+      var match = expr.pexp_desc;
+      if (typeof match !== "number") {
+        switch (match.TAG | 0) {
+          case /* Pexp_fun */4 :
+              var lbl = match._0;
+              var exit = 0;
+              var exit$1 = 0;
+              if (typeof lbl === "number" && match._1 === undefined) {
+                var match$1 = match._2.ppat_desc;
+                if (typeof match$1 === "number" || !(match$1.TAG === /* Ppat_var */0 && match$1._0.txt === "__x")) {
+                  exit$1 = 3;
+                } else {
+                  var tmp = match._3.pexp_desc;
+                  if (typeof tmp === "number") {
+                    exit$1 = 3;
+                  } else {
+                    if (tmp.TAG === /* Pexp_apply */5) {
+                      return [
+                              attrsBefore,
+                              List.rev(acc),
+                              rewriteUnderscoreApply(expr)
+                            ];
+                    }
+                    exit$1 = 3;
+                  }
+                }
+              } else {
+                exit$1 = 3;
+              }
+              if (exit$1 === 3) {
+                var attrs = expr.pexp_attributes;
+                var returnExpr = match._3;
+                var pattern = match._2;
+                var defaultExpr = match._1;
+                if (attrs) {
+                  if (attrs.hd[0].txt === "bs" && !attrs.tl) {
+                    var parameter = {
+                      TAG: /* Parameter */0,
+                      attrs: attrs,
+                      lbl: lbl,
+                      defaultExpr: defaultExpr,
+                      pat: pattern
+                    };
+                    _expr = returnExpr;
+                    _acc = {
+                      hd: parameter,
+                      tl: acc
+                    };
+                    continue ;
+                  }
+                  exit = 2;
+                } else {
+                  var parameter$1 = {
+                    TAG: /* Parameter */0,
+                    attrs: /* [] */0,
+                    lbl: lbl,
+                    defaultExpr: defaultExpr,
+                    pat: pattern
+                  };
+                  _expr = returnExpr;
+                  _acc = {
+                    hd: parameter$1,
+                    tl: acc
+                  };
+                  continue ;
+                }
+              }
+              if (exit === 2 && typeof lbl !== "number") {
+                var parameter_0 = expr.pexp_attributes;
+                var parameter_2 = match._1;
+                var parameter_3 = match._2;
+                var parameter$2 = {
+                  TAG: /* Parameter */0,
+                  attrs: parameter_0,
+                  lbl: lbl,
+                  defaultExpr: parameter_2,
+                  pat: parameter_3
+                };
+                _expr = match._3;
+                _acc = {
+                  hd: parameter$2,
+                  tl: acc
+                };
+                continue ;
+              }
+              break;
+          case /* Pexp_newtype */31 :
+              var match$2 = collectNewTypes({
+                    hd: match._0,
+                    tl: /* [] */0
+                  }, match._1);
+              var param_0 = expr.pexp_attributes;
+              var param_1 = match$2[0];
+              var param = {
+                TAG: /* NewTypes */1,
+                attrs: param_0,
+                locs: param_1
+              };
+              _expr = match$2[1];
+              _acc = {
+                hd: param,
+                tl: acc
+              };
+              continue ;
+          default:
+            
+        }
+      }
+      return [
+              attrsBefore,
+              List.rev(acc),
+              expr
+            ];
+    };
+  };
+  var match = expr.pexp_desc;
+  if (typeof match === "number" || !(match.TAG === /* Pexp_fun */4 && typeof match._0 === "number")) {
+    return collect(/* [] */0, /* [] */0, expr);
+  } else {
+    return collect(expr.pexp_attributes, /* [] */0, {
+                pexp_desc: expr.pexp_desc,
+                pexp_loc: expr.pexp_loc,
+                pexp_attributes: /* [] */0
+              });
+  }
+}
+
+function processBracesAttr(expr) {
+  var match = expr.pexp_attributes;
+  if (!match) {
+    return [
+            undefined,
+            expr
+          ];
+  }
+  var attr = match.hd;
+  if (attr[0].txt === "ns.braces") {
+    return [
+            attr,
+            {
+              pexp_desc: expr.pexp_desc,
+              pexp_loc: expr.pexp_loc,
+              pexp_attributes: match.tl
+            }
+          ];
+  } else {
+    return [
+            undefined,
+            expr
+          ];
+  }
+}
+
+function filterParsingAttrs(attrs) {
+  return List.filter(function (attr) {
+                switch (attr[0].txt) {
+                  case "bs" :
+                  case "ns.braces" :
+                  case "ns.iflet" :
+                  case "ns.namedArgLoc" :
+                  case "ns.ternary" :
+                  case "res.template" :
+                      return false;
+                  default:
+                    return true;
+                }
+              })(attrs);
+}
+
+function isBlockExpr(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  switch (match.TAG | 0) {
+    case /* Pexp_let */2 :
+    case /* Pexp_sequence */16 :
+    case /* Pexp_letmodule */25 :
+    case /* Pexp_letexception */26 :
+    case /* Pexp_open */33 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isBracedExpr(expr) {
+  var match = processBracesAttr(expr);
+  return match[0] !== undefined;
+}
+
+function isMultilineText(txt) {
+  var len = txt.length;
+  var _i = 0;
+  while(true) {
+    var i = _i;
+    if (i >= len) {
+      return false;
+    }
+    var c = txt.charCodeAt(i);
+    if (c > 13 || c < 10) {
+      if (c !== 92) {
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if ((i + 2 | 0) === len) {
+        return false;
+      }
+      _i = i + 2 | 0;
+      continue ;
+    }
+    if (!(c === 12 || c === 11)) {
+      return true;
+    }
+    _i = i + 1 | 0;
+    continue ;
+  };
+}
+
+function isHuggableExpression(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match !== "number") {
+    switch (match.TAG | 0) {
+      case /* Pexp_constant */1 :
+          var match$1 = match._0;
+          if (match$1.TAG === /* Pconst_string */2 && match$1._1 !== undefined) {
+            return true;
+          }
+          break;
+      case /* Pexp_construct */9 :
+          var match$2 = match._0.txt;
+          switch (match$2.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$2._0) {
+                  case "::" :
+                  case "[]" :
+                      return true;
+                  default:
+                    
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                break;
+            
+          }
+          break;
+      case /* Pexp_tuple */8 :
+      case /* Pexp_record */11 :
+      case /* Pexp_array */14 :
+          return true;
+      case /* Pexp_extension */34 :
+          switch (match._0[0].txt) {
+            case "bs.obj" :
+            case "obj" :
+                return true;
+            default:
+              
+          }
+          break;
+      default:
+        
+    }
+  }
+  if (isBlockExpr(expr)) {
+    return true;
+  }
+  if (isBracedExpr(expr)) {
+    return true;
+  }
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_constant */1) {
+    return false;
+  }
+  var match$3 = match._0;
+  if (match$3.TAG === /* Pconst_string */2 && isMultilineText(match$3._0)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isHuggableRhs(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match !== "number") {
+    switch (match.TAG | 0) {
+      case /* Pexp_construct */9 :
+          var match$1 = match._0.txt;
+          switch (match$1.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$1._0) {
+                  case "::" :
+                  case "[]" :
+                      return true;
+                  default:
+                    
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                break;
+            
+          }
+          break;
+      case /* Pexp_tuple */8 :
+      case /* Pexp_record */11 :
+      case /* Pexp_array */14 :
+          return true;
+      case /* Pexp_extension */34 :
+          switch (match._0[0].txt) {
+            case "bs.obj" :
+            case "obj" :
+                return true;
+            default:
+              
+          }
+          break;
+      default:
+        
+    }
+  }
+  if (isBracedExpr(expr)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function isHuggablePattern(pattern) {
+  var match = pattern.ppat_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  switch (match.TAG | 0) {
+    case /* Ppat_tuple */4 :
+    case /* Ppat_construct */5 :
+    case /* Ppat_variant */6 :
+    case /* Ppat_record */7 :
+    case /* Ppat_array */8 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function operatorPrecedence(operator) {
+  switch (operator) {
+    case "&&" :
+        return 3;
+    case "**" :
+        return 7;
+    case "*" :
+    case "*." :
+    case "/" :
+    case "/." :
+        return 6;
+    case ":=" :
+        return 1;
+    case "+" :
+    case "+." :
+    case "-" :
+    case "-." :
+    case "^" :
+        return 5;
+    case "#" :
+    case "##" :
+    case "|." :
+        return 8;
+    case "!=" :
+    case "!==" :
+    case "<" :
+    case "<=" :
+    case "<>" :
+    case "=" :
+    case "==" :
+    case ">" :
+    case ">=" :
+    case "|>" :
+        return 4;
+    case "||" :
+        return 2;
+    default:
+      return 0;
+  }
+}
+
+function isUnaryOperator(operator) {
+  switch (operator) {
+    case "not" :
+    case "~+" :
+    case "~+." :
+    case "~-" :
+    case "~-." :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isUnaryExpression(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var operator = match$1._0.txt;
+  switch (operator.TAG | 0) {
+    case /* Lident */0 :
+        var match$2 = match._1;
+        if (match$2 && typeof match$2.hd[0] === "number" && !(match$2.tl || !isUnaryOperator(operator._0))) {
+          return true;
+        } else {
+          return false;
+        }
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function isBinaryOperator(operator) {
+  switch (operator) {
+    case "!=" :
+    case "!==" :
+    case "&&" :
+    case "*" :
+    case "**" :
+    case "*." :
+    case "+" :
+    case "+." :
+    case "-" :
+    case "-." :
+    case "/" :
+    case "/." :
+    case ":=" :
+    case "<" :
+    case "<=" :
+    case "<>" :
+    case "=" :
+    case "==" :
+    case ">" :
+    case ">=" :
+    case "^" :
+    case "|." :
+    case "|>" :
+    case "||" :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isBinaryExpression(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var match$2 = match$1._0;
+  var operator = match$2.txt;
+  switch (operator.TAG | 0) {
+    case /* Lident */0 :
+        var match$3 = match._1;
+        if (!match$3) {
+          return false;
+        }
+        if (typeof match$3.hd[0] !== "number") {
+          return false;
+        }
+        var match$4 = match$3.tl;
+        if (!match$4) {
+          return false;
+        }
+        if (typeof match$4.hd[0] !== "number") {
+          return false;
+        }
+        if (match$4.tl) {
+          return false;
+        }
+        var operator$1 = operator._0;
+        if (isBinaryOperator(operator$1)) {
+          return !(match$2.loc.loc_ghost && operator$1 === "^");
+        } else {
+          return false;
+        }
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function isEqualityOperator(operator) {
+  switch (operator) {
+    case "!=" :
+    case "<>" :
+    case "=" :
+    case "==" :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function flattenableOperators(parentOperator, childOperator) {
+  var precParent = operatorPrecedence(parentOperator);
+  var precChild = operatorPrecedence(childOperator);
+  if (precParent === precChild) {
+    return !(isEqualityOperator(parentOperator) && isEqualityOperator(childOperator));
+  } else {
+    return false;
+  }
+}
+
+function hasIfLetAttribute(_attrs) {
+  while(true) {
+    var attrs = _attrs;
+    if (!attrs) {
+      return false;
+    }
+    if (attrs.hd[0].txt === "ns.iflet") {
+      return true;
+    }
+    _attrs = attrs.tl;
+    continue ;
+  };
+}
+
+function isIfLetExpr(expr) {
+  var tmp = expr.pexp_desc;
+  if (typeof tmp === "number" || !(tmp.TAG === /* Pexp_match */6 && hasIfLetAttribute(expr.pexp_attributes))) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function hasAttributes(attrs) {
+  return List.exists((function (attr) {
+                switch (attr[0].txt) {
+                  case "bs" :
+                  case "ns.braces" :
+                  case "ns.iflet" :
+                  case "ns.ternary" :
+                  case "res.template" :
+                      return false;
+                  case "warning" :
+                      var match = attr[1];
+                      if (match.TAG !== /* PStr */0) {
+                        return true;
+                      }
+                      var match$1 = match._0;
+                      if (!match$1) {
+                        return true;
+                      }
+                      var match$2 = match$1.hd.pstr_desc;
+                      if (match$2.TAG !== /* Pstr_eval */0) {
+                        return true;
+                      }
+                      var match$3 = match$2._0.pexp_desc;
+                      if (typeof match$3 === "number") {
+                        return true;
+                      }
+                      if (match$3.TAG !== /* Pexp_constant */1) {
+                        return true;
+                      }
+                      var match$4 = match$3._0;
+                      if (match$4.TAG === /* Pconst_string */2 && match$4._0 === "-4" && !(match$4._1 !== undefined || match$1.tl)) {
+                        return !hasIfLetAttribute(attrs);
+                      } else {
+                        return true;
+                      }
+                  default:
+                    return true;
+                }
+              }), attrs);
+}
+
+function isArrayAccess(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var match$2 = match$1._0.txt;
+  switch (match$2.TAG | 0) {
+    case /* Ldot */1 :
+        var match$3 = match$2._0;
+        switch (match$3.TAG | 0) {
+          case /* Lident */0 :
+              if (match$3._0 !== "Array") {
+                return false;
+              }
+              if (match$2._1 !== "get") {
+                return false;
+              }
+              var match$4 = match._1;
+              if (!match$4) {
+                return false;
+              }
+              if (typeof match$4.hd[0] !== "number") {
+                return false;
+              }
+              var match$5 = match$4.tl;
+              if (match$5 && typeof match$5.hd[0] === "number" && !match$5.tl) {
+                return true;
+              } else {
+                return false;
+              }
+          case /* Ldot */1 :
+          case /* Lapply */2 :
+              return false;
+          
+        }
+    case /* Lident */0 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function collectIfExpressions(expr) {
+  var _acc = /* [] */0;
+  var _expr = expr;
+  while(true) {
+    var expr$1 = _expr;
+    var acc = _acc;
+    var match = expr$1.pexp_desc;
+    if (typeof match !== "number") {
+      switch (match.TAG | 0) {
+        case /* Pexp_match */6 :
+            var match$1 = match._1;
+            if (match$1) {
+              var match$2 = match$1.hd;
+              if (match$2.pc_guard === undefined) {
+                var match$3 = match$1.tl;
+                if (match$3) {
+                  var elseExpr = match$3.hd.pc_rhs;
+                  var match$4 = elseExpr.pexp_desc;
+                  var thenExpr = match$2.pc_rhs;
+                  var pattern = match$2.pc_lhs;
+                  var condition = match._0;
+                  var exit = 0;
+                  if (typeof match$4 === "number" || match$4.TAG !== /* Pexp_construct */9) {
+                    exit = 2;
+                  } else {
+                    var match$5 = match$4._0.txt;
+                    switch (match$5.TAG | 0) {
+                      case /* Lident */0 :
+                          if (match$5._0 === "()") {
+                            if (!match$3.tl) {
+                              if (isIfLetExpr(expr$1)) {
+                                var ifs = List.rev({
+                                      hd: [
+                                        {
+                                          TAG: /* IfLet */1,
+                                          _0: pattern,
+                                          _1: condition
+                                        },
+                                        thenExpr
+                                      ],
+                                      tl: acc
+                                    });
+                                return [
+                                        ifs,
+                                        undefined
+                                      ];
+                              }
+                              exit = 2;
+                            }
+                            
+                          } else {
+                            exit = 2;
+                          }
+                          break;
+                      case /* Ldot */1 :
+                      case /* Lapply */2 :
+                          exit = 2;
+                          break;
+                      
+                    }
+                  }
+                  if (exit === 2 && !match$3.tl && isIfLetExpr(expr$1)) {
+                    _expr = elseExpr;
+                    _acc = {
+                      hd: [
+                        {
+                          TAG: /* IfLet */1,
+                          _0: pattern,
+                          _1: condition
+                        },
+                        thenExpr
+                      ],
+                      tl: acc
+                    };
+                    continue ;
+                  }
+                  
+                }
+                
+              }
+              
+            }
+            break;
+        case /* Pexp_ifthenelse */15 :
+            var elseExpr$1 = match._2;
+            var thenExpr$1 = match._1;
+            var ifExpr = match._0;
+            if (elseExpr$1 !== undefined) {
+              _expr = elseExpr$1;
+              _acc = {
+                hd: [
+                  {
+                    TAG: /* If */0,
+                    _0: ifExpr
+                  },
+                  thenExpr$1
+                ],
+                tl: acc
+              };
+              continue ;
+            }
+            var ifs$1 = List.rev({
+                  hd: [
+                    {
+                      TAG: /* If */0,
+                      _0: ifExpr
+                    },
+                    thenExpr$1
+                  ],
+                  tl: acc
+                });
+            return [
+                    ifs$1,
+                    elseExpr$1
+                  ];
+        default:
+          
+      }
+    }
+    return [
+            List.rev(acc),
+            expr$1
+          ];
+  };
+}
+
+function hasTernaryAttribute(_attrs) {
+  while(true) {
+    var attrs = _attrs;
+    if (!attrs) {
+      return false;
+    }
+    if (attrs.hd[0].txt === "ns.ternary") {
+      return true;
+    }
+    _attrs = attrs.tl;
+    continue ;
+  };
+}
+
+function isTernaryExpr(expr) {
+  var tmp = expr.pexp_desc;
+  if (typeof tmp === "number" || !(tmp.TAG === /* Pexp_ifthenelse */15 && hasTernaryAttribute(expr.pexp_attributes))) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function collectTernaryParts(expr) {
+  var _acc = /* [] */0;
+  var _expr = expr;
+  while(true) {
+    var expr$1 = _expr;
+    var acc = _acc;
+    var match = expr$1.pexp_desc;
+    if (typeof match === "number") {
+      return [
+              List.rev(acc),
+              expr$1
+            ];
+    }
+    if (match.TAG !== /* Pexp_ifthenelse */15) {
+      return [
+              List.rev(acc),
+              expr$1
+            ];
+    }
+    var alternate = match._2;
+    if (alternate === undefined) {
+      return [
+              List.rev(acc),
+              expr$1
+            ];
+    }
+    if (!hasTernaryAttribute(expr$1.pexp_attributes)) {
+      return [
+              List.rev(acc),
+              expr$1
+            ];
+    }
+    _expr = alternate;
+    _acc = {
+      hd: [
+        match._0,
+        match._1
+      ],
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function parametersShouldHug(parameters) {
+  if (!parameters) {
+    return false;
+  }
+  var match = parameters.hd;
+  if (match.TAG === /* Parameter */0 && !(match.attrs || !(typeof match.lbl === "number" && !(match.defaultExpr !== undefined || parameters.tl || !isHuggablePattern(match.pat))))) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function filterTernaryAttributes(attrs) {
+  return List.filter(function (attr) {
+                if (attr[0].txt === "ns.ternary") {
+                  return false;
+                } else {
+                  return true;
+                }
+              })(attrs);
+}
+
+function filterFragileMatchAttributes(attrs) {
+  return List.filter(function (attr) {
+                if (attr[0].txt !== "warning") {
+                  return true;
+                }
+                var match = attr[1];
+                if (match.TAG !== /* PStr */0) {
+                  return true;
+                }
+                var match$1 = match._0;
+                if (!match$1) {
+                  return true;
+                }
+                var match$2 = match$1.hd.pstr_desc;
+                if (match$2.TAG !== /* Pstr_eval */0) {
+                  return true;
+                }
+                var match$3 = match$2._0.pexp_desc;
+                if (typeof match$3 === "number") {
+                  return true;
+                }
+                if (match$3.TAG !== /* Pexp_constant */1) {
+                  return true;
+                }
+                var match$4 = match$3._0;
+                if (match$4.TAG === /* Pconst_string */2 && match$4._0 === "-4" && !match$1.tl) {
+                  return false;
+                } else {
+                  return true;
+                }
+              })(attrs);
+}
+
+function isJsxExpression(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number" || match.TAG !== /* Pexp_apply */5) {
+    return false;
+  } else {
+    var _attrs = expr.pexp_attributes;
+    while(true) {
+      var attrs = _attrs;
+      if (!attrs) {
+        return false;
+      }
+      if (attrs.hd[0].txt === "JSX") {
+        return true;
+      }
+      _attrs = attrs.tl;
+      continue ;
+    };
+  }
+}
+
+function hasJsxAttribute(attributes) {
+  var _attrs = attributes;
+  while(true) {
+    var attrs = _attrs;
+    if (!attrs) {
+      return false;
+    }
+    if (attrs.hd[0].txt === "JSX") {
+      return true;
+    }
+    _attrs = attrs.tl;
+    continue ;
+  };
+}
+
+function shouldIndentBinaryExpr(expr) {
+  var samePrecedenceSubExpression = function (operator, subExpression) {
+    var match = subExpression.pexp_desc;
+    if (typeof match === "number") {
+      return true;
+    }
+    if (match.TAG !== /* Pexp_apply */5) {
+      return true;
+    }
+    var match$1 = match._0.pexp_desc;
+    if (typeof match$1 === "number") {
+      return true;
+    }
+    if (match$1.TAG !== /* Pexp_ident */0) {
+      return true;
+    }
+    var subOperator = match$1._0.txt;
+    switch (subOperator.TAG | 0) {
+      case /* Lident */0 :
+          var match$2 = match._1;
+          if (!match$2) {
+            return true;
+          }
+          if (typeof match$2.hd[0] !== "number") {
+            return true;
+          }
+          var match$3 = match$2.tl;
+          if (!match$3) {
+            return true;
+          }
+          if (typeof match$3.hd[0] !== "number") {
+            return true;
+          }
+          if (match$3.tl) {
+            return true;
+          }
+          var subOperator$1 = subOperator._0;
+          if (isBinaryOperator(subOperator$1)) {
+            return flattenableOperators(operator, subOperator$1);
+          } else {
+            return true;
+          }
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return true;
+      
+    }
+  };
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var operator = match$1._0.txt;
+  switch (operator.TAG | 0) {
+    case /* Lident */0 :
+        var match$2 = match._1;
+        if (!match$2) {
+          return false;
+        }
+        var match$3 = match$2.hd;
+        if (typeof match$3[0] !== "number") {
+          return false;
+        }
+        var match$4 = match$2.tl;
+        if (!match$4) {
+          return false;
+        }
+        if (typeof match$4.hd[0] !== "number") {
+          return false;
+        }
+        if (match$4.tl) {
+          return false;
+        }
+        var operator$1 = operator._0;
+        if (isBinaryOperator(operator$1)) {
+          if (isEqualityOperator(operator$1) || !samePrecedenceSubExpression(operator$1, match$3[1])) {
+            return true;
+          } else {
+            return operator$1 === ":=";
+          }
+        } else {
+          return false;
+        }
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function shouldInlineRhsBinaryExpr(rhs) {
+  var match = rhs.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  switch (match.TAG | 0) {
+    case /* Pexp_constant */1 :
+    case /* Pexp_let */2 :
+    case /* Pexp_try */7 :
+    case /* Pexp_record */11 :
+    case /* Pexp_array */14 :
+    case /* Pexp_ifthenelse */15 :
+    case /* Pexp_sequence */16 :
+    case /* Pexp_while */17 :
+    case /* Pexp_for */18 :
+    case /* Pexp_letmodule */25 :
+    case /* Pexp_letexception */26 :
+    case /* Pexp_open */33 :
+        return true;
+    default:
+      return false;
+  }
+}
+
+function isPrintableAttribute(attr) {
+  switch (attr[0].txt) {
+    case "JSX" :
+    case "bs" :
+    case "ns.braces" :
+    case "ns.iflet" :
+    case "ns.ternary" :
+    case "res.template" :
+        return false;
+    default:
+      return true;
+  }
+}
+
+function hasPrintableAttributes(attrs) {
+  return List.exists(isPrintableAttribute, attrs);
+}
+
+function filterPrintableAttributes(attrs) {
+  return List.filter(isPrintableAttribute)(attrs);
+}
+
+function partitionPrintableAttributes(attrs) {
+  return List.partition(isPrintableAttribute, attrs);
+}
+
+function requiresSpecialCallbackPrintingLastArg(args) {
+  var _args = args;
+  while(true) {
+    var args$1 = _args;
+    if (!args$1) {
+      return false;
+    }
+    var tmp = args$1.hd[1].pexp_desc;
+    if (typeof tmp === "number") {
+      _args = args$1.tl;
+      continue ;
+    }
+    switch (tmp.TAG | 0) {
+      case /* Pexp_fun */4 :
+      case /* Pexp_newtype */31 :
+          if (args$1.tl) {
+            return false;
+          } else {
+            return true;
+          }
+      default:
+        _args = args$1.tl;
+        continue ;
+    }
+  };
+}
+
+function requiresSpecialCallbackPrintingFirstArg(args) {
+  if (!args) {
+    return false;
+  }
+  var tmp = args.hd[1].pexp_desc;
+  if (typeof tmp === "number") {
+    return false;
+  }
+  switch (tmp.TAG | 0) {
+    case /* Pexp_fun */4 :
+    case /* Pexp_newtype */31 :
+        break;
+    default:
+      return false;
+  }
+  var rest = args.tl;
+  if (rest) {
+    var _args = rest;
+    while(true) {
+      var args$1 = _args;
+      if (!args$1) {
+        return true;
+      }
+      var tmp$1 = args$1.hd[1].pexp_desc;
+      if (typeof tmp$1 === "number") {
+        _args = args$1.tl;
+        continue ;
+      }
+      switch (tmp$1.TAG | 0) {
+        case /* Pexp_fun */4 :
+        case /* Pexp_newtype */31 :
+            return false;
+        default:
+          _args = args$1.tl;
+          continue ;
+      }
+    };
+  } else {
+    return false;
+  }
+}
+
+function modExprApply(modExpr) {
+  var _acc = /* [] */0;
+  var _modExpr = modExpr;
+  while(true) {
+    var modExpr$1 = _modExpr;
+    var acc = _acc;
+    var match = modExpr$1.pmod_desc;
+    if (match.TAG !== /* Pmod_apply */3) {
+      return [
+              acc,
+              modExpr$1
+            ];
+    }
+    _modExpr = match._0;
+    _acc = {
+      hd: match._1,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function modExprFunctor(modExpr) {
+  var _acc = /* [] */0;
+  var _modExpr = modExpr;
+  while(true) {
+    var modExpr$1 = _modExpr;
+    var acc = _acc;
+    var match = modExpr$1.pmod_desc;
+    if (match.TAG !== /* Pmod_functor */2) {
+      return [
+              List.rev(acc),
+              modExpr$1
+            ];
+    }
+    var param_0 = modExpr$1.pmod_attributes;
+    var param_1 = match._0;
+    var param_2 = match._1;
+    var param = [
+      param_0,
+      param_1,
+      param_2
+    ];
+    _modExpr = match._2;
+    _acc = {
+      hd: param,
+      tl: acc
+    };
+    continue ;
+  };
+}
+
+function collectPatternsFromListConstruct(_acc, _pattern) {
+  while(true) {
+    var pattern = _pattern;
+    var acc = _acc;
+    var match = pattern.ppat_desc;
+    if (typeof match === "number") {
+      return [
+              List.rev(acc),
+              pattern
+            ];
+    }
+    if (match.TAG !== /* Ppat_construct */5) {
+      return [
+              List.rev(acc),
+              pattern
+            ];
+    }
+    var match$1 = match._0.txt;
+    switch (match$1.TAG | 0) {
+      case /* Lident */0 :
+          if (match$1._0 !== "::") {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          var match$2 = match._1;
+          if (match$2 === undefined) {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          var match$3 = match$2.ppat_desc;
+          if (typeof match$3 === "number") {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          if (match$3.TAG !== /* Ppat_tuple */4) {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          var match$4 = match$3._0;
+          if (!match$4) {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          var match$5 = match$4.tl;
+          if (!match$5) {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          if (match$5.tl) {
+            return [
+                    List.rev(acc),
+                    pattern
+                  ];
+          }
+          _pattern = match$5.hd;
+          _acc = {
+            hd: match$4.hd,
+            tl: acc
+          };
+          continue ;
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return [
+                  List.rev(acc),
+                  pattern
+                ];
+      
+    }
+  };
+}
+
+function hasTemplateLiteralAttr(attrs) {
+  return List.exists((function (attr) {
+                if (attr[0].txt === "res.template") {
+                  return true;
+                } else {
+                  return false;
+                }
+              }), attrs);
+}
+
+function isTemplateLiteral(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  switch (match.TAG | 0) {
+    case /* Pexp_constant */1 :
+        var match$1 = match._0;
+        if (match$1.TAG === /* Pconst_string */2) {
+          var match$2 = match$1._1;
+          if (match$2 === "") {
+            return true;
+          }
+          
+        }
+        if (hasTemplateLiteralAttr(expr.pexp_attributes)) {
+          return true;
+        } else {
+          return false;
+        }
+    case /* Pexp_apply */5 :
+        var match$3 = match._0.pexp_desc;
+        if (typeof match$3 === "number") {
+          return false;
+        }
+        if (match$3.TAG !== /* Pexp_ident */0) {
+          return false;
+        }
+        var match$4 = match$3._0.txt;
+        switch (match$4.TAG | 0) {
+          case /* Lident */0 :
+              if (match$4._0 !== "^") {
+                return false;
+              }
+              var match$5 = match._1;
+              if (!match$5) {
+                return false;
+              }
+              if (typeof match$5.hd[0] !== "number") {
+                return false;
+              }
+              var match$6 = match$5.tl;
+              if (match$6 && typeof match$6.hd[0] === "number" && !(match$6.tl || !hasTemplateLiteralAttr(expr.pexp_attributes))) {
+                return true;
+              } else {
+                return false;
+              }
+          case /* Ldot */1 :
+          case /* Lapply */2 :
+              return false;
+          
+        }
+    default:
+      return false;
+  }
+}
+
+function collectOrPatternChain(pat) {
+  var _pattern = pat;
+  var _chain = /* [] */0;
+  while(true) {
+    var chain = _chain;
+    var pattern = _pattern;
+    var match = pattern.ppat_desc;
+    if (typeof match === "number") {
+      return {
+              hd: pattern,
+              tl: chain
+            };
+    }
+    if (match.TAG !== /* Ppat_or */9) {
+      return {
+              hd: pattern,
+              tl: chain
+            };
+    }
+    _chain = {
+      hd: match._1,
+      tl: chain
+    };
+    _pattern = match._0;
+    continue ;
+  };
+}
+
+function isSinglePipeExpr(expr) {
+  var isPipeExpr = function (expr) {
+    var match = expr.pexp_desc;
+    if (typeof match === "number") {
+      return false;
+    }
+    if (match.TAG !== /* Pexp_apply */5) {
+      return false;
+    }
+    var match$1 = match._0.pexp_desc;
+    if (typeof match$1 === "number") {
+      return false;
+    }
+    if (match$1.TAG !== /* Pexp_ident */0) {
+      return false;
+    }
+    var match$2 = match$1._0.txt;
+    switch (match$2.TAG | 0) {
+      case /* Lident */0 :
+          switch (match$2._0) {
+            case "|." :
+            case "|>" :
+                break;
+            default:
+              return false;
+          }
+          var match$3 = match._1;
+          if (!match$3) {
+            return false;
+          }
+          if (typeof match$3.hd[0] !== "number") {
+            return false;
+          }
+          var match$4 = match$3.tl;
+          if (match$4 && typeof match$4.hd[0] === "number" && !match$4.tl) {
+            return true;
+          } else {
+            return false;
+          }
+          break;
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          return false;
+      
+    }
+  };
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return false;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var match$2 = match$1._0.txt;
+  switch (match$2.TAG | 0) {
+    case /* Lident */0 :
+        switch (match$2._0) {
+          case "|." :
+          case "|>" :
+              break;
+          default:
+            return false;
+        }
+        var match$3 = match._1;
+        if (!match$3) {
+          return false;
+        }
+        var match$4 = match$3.hd;
+        if (typeof match$4[0] !== "number") {
+          return false;
+        }
+        var match$5 = match$3.tl;
+        if (match$5 && typeof match$5.hd[0] === "number" && !match$5.tl) {
+          return !isPipeExpr(match$4[1]);
+        } else {
+          return false;
+        }
+        break;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+function isUnderscoreApplySugar(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_fun */4) {
+    return false;
+  }
+  if (typeof match._0 !== "number") {
+    return false;
+  }
+  if (match._1 !== undefined) {
+    return false;
+  }
+  var match$1 = match._2.ppat_desc;
+  if (typeof match$1 === "number") {
+    return false;
+  }
+  if (match$1.TAG !== /* Ppat_var */0) {
+    return false;
+  }
+  if (match$1._0.txt !== "__x") {
+    return false;
+  }
+  var tmp = match._3.pexp_desc;
+  if (typeof tmp === "number" || tmp.TAG !== /* Pexp_apply */5) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function isRewrittenUnderscoreApplySugar(expr) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return false;
+  }
+  if (match.TAG !== /* Pexp_ident */0) {
+    return false;
+  }
+  var match$1 = match._0.txt;
+  switch (match$1.TAG | 0) {
+    case /* Lident */0 :
+        if (match$1._0 === "_") {
+          return true;
+        } else {
+          return false;
+        }
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return false;
+    
+  }
+}
+
+export {
+  arrowType ,
+  functorType ,
+  processUncurriedAttribute ,
+  collectListExpressions ,
+  rewriteUnderscoreApply ,
+  funExpr ,
+  processBracesAttr ,
+  filterParsingAttrs ,
+  isBlockExpr ,
+  isBracedExpr ,
+  isMultilineText ,
+  isHuggableExpression ,
+  isHuggableRhs ,
+  isHuggablePattern ,
+  operatorPrecedence ,
+  isUnaryOperator ,
+  isUnaryExpression ,
+  isBinaryOperator ,
+  isBinaryExpression ,
+  isEqualityOperator ,
+  flattenableOperators ,
+  hasIfLetAttribute ,
+  isIfLetExpr ,
+  hasAttributes ,
+  isArrayAccess ,
+  collectIfExpressions ,
+  hasTernaryAttribute ,
+  isTernaryExpr ,
+  collectTernaryParts ,
+  parametersShouldHug ,
+  filterTernaryAttributes ,
+  filterFragileMatchAttributes ,
+  isJsxExpression ,
+  hasJsxAttribute ,
+  shouldIndentBinaryExpr ,
+  shouldInlineRhsBinaryExpr ,
+  isPrintableAttribute ,
+  hasPrintableAttributes ,
+  filterPrintableAttributes ,
+  partitionPrintableAttributes ,
+  requiresSpecialCallbackPrintingLastArg ,
+  requiresSpecialCallbackPrintingFirstArg ,
+  modExprApply ,
+  modExprFunctor ,
+  collectPatternsFromListConstruct ,
+  hasTemplateLiteralAttr ,
+  isTemplateLiteral ,
+  collectOrPatternChain ,
+  isSinglePipeExpr ,
+  isUnderscoreApplySugar ,
+  isRewrittenUnderscoreApplySugar ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_parsetree_viewer.res b/analysis/examples/larger-project/src/res_parsetree_viewer.res
new file mode 100644
index 0000000000..9070883a7c
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_parsetree_viewer.res
@@ -0,0 +1,678 @@
+open Parsetree
+
+let arrowType = ct => {
+  let rec process = (attrsBefore, acc, typ) =>
+    switch typ {
+    | {ptyp_desc: Ptyp_arrow(Nolabel as lbl, typ1, typ2), ptyp_attributes: list{}} =>
+      let arg = (list{}, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | {
+        ptyp_desc: Ptyp_arrow(Nolabel as lbl, typ1, typ2),
+        ptyp_attributes: list{({txt: "bs"}, _)} as attrs,
+      } =>
+      let arg = (attrs, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | {ptyp_desc: Ptyp_arrow(Nolabel, _typ1, _typ2), ptyp_attributes: _attrs} as returnType =>
+      let args = List.rev(acc)
+      (attrsBefore, args, returnType)
+    | {
+        ptyp_desc: Ptyp_arrow((Labelled(_) | Optional(_)) as lbl, typ1, typ2),
+        ptyp_attributes: attrs,
+      } =>
+      let arg = (attrs, lbl, typ1)
+      process(attrsBefore, list{arg, ...acc}, typ2)
+    | typ => (attrsBefore, List.rev(acc), typ)
+    }
+
+  switch ct {
+  | {ptyp_desc: Ptyp_arrow(Nolabel, _typ1, _typ2), ptyp_attributes: attrs} as typ =>
+    process(attrs, list{}, {...typ, ptyp_attributes: list{}})
+  | typ => process(list{}, list{}, typ)
+  }
+}
+
+let functorType = modtype => {
+  let rec process = (acc, modtype) =>
+    switch modtype {
+    | {pmty_desc: Pmty_functor(lbl, argType, returnType), pmty_attributes: attrs} =>
+      let arg = (attrs, lbl, argType)
+      process(list{arg, ...acc}, returnType)
+    | modType => (List.rev(acc), modType)
+    }
+
+  process(list{}, modtype)
+}
+
+let processUncurriedAttribute = attrs => {
+  let rec process = (uncurriedSpotted, acc, attrs) =>
+    switch attrs {
+    | list{} => (uncurriedSpotted, List.rev(acc))
+    | list{({Location.txt: "bs"}, _), ...rest} => process(true, acc, rest)
+    | list{attr, ...rest} => process(uncurriedSpotted, list{attr, ...acc}, rest)
+    }
+
+  process(false, list{}, attrs)
+}
+
+let collectListExpressions = expr => {
+  let rec collect = (acc, expr) =>
+    switch expr.pexp_desc {
+    | Pexp_construct({txt: Longident.Lident("[]")}, _) => (List.rev(acc), None)
+    | Pexp_construct(
+        {txt: Longident.Lident("::")},
+        Some({pexp_desc: Pexp_tuple(list{hd, tail})}),
+      ) =>
+      collect(list{hd, ...acc}, tail)
+    | _ => (List.rev(acc), Some(expr))
+    }
+
+  collect(list{}, expr)
+}
+
+/* (__x) => f(a, __x, c) -----> f(a, _, c) */
+let rewriteUnderscoreApply = expr =>
+  switch expr.pexp_desc {
+  | Pexp_fun(
+      Nolabel,
+      None,
+      {ppat_desc: Ppat_var({txt: "__x"})},
+      {pexp_desc: Pexp_apply(callExpr, args)} as e,
+    ) =>
+    let newArgs = List.map(arg =>
+      switch arg {
+      | (lbl, {pexp_desc: Pexp_ident({txt: Longident.Lident("__x")} as lid)} as argExpr) => (
+          lbl,
+          {...argExpr, pexp_desc: Pexp_ident({...lid, txt: Longident.Lident("_")})},
+        )
+      | arg => arg
+      }
+    , args)
+    {...e, pexp_desc: Pexp_apply(callExpr, newArgs)}
+  | _ => expr
+  }
+
+type funParamKind =
+  | Parameter({
+      attrs: Parsetree.attributes,
+      lbl: Asttypes.arg_label,
+      defaultExpr: option<Parsetree.expression>,
+      pat: Parsetree.pattern,
+    })
+  | NewTypes({attrs: Parsetree.attributes, locs: list<Asttypes.loc<string>>})
+
+let funExpr = expr => {
+  /* Turns (type t, type u, type z) into "type t u z" */
+  let rec collectNewTypes = (acc, returnExpr) =>
+    switch returnExpr {
+    | {pexp_desc: Pexp_newtype(stringLoc, returnExpr), pexp_attributes: list{}} =>
+      collectNewTypes(list{stringLoc, ...acc}, returnExpr)
+    | returnExpr => (List.rev(acc), returnExpr)
+    }
+
+  let rec collect = (attrsBefore, acc, expr) =>
+    switch expr {
+    | {
+        pexp_desc: Pexp_fun(
+          Nolabel,
+          None,
+          {ppat_desc: Ppat_var({txt: "__x"})},
+          {pexp_desc: Pexp_apply(_)},
+        ),
+      } => (attrsBefore, List.rev(acc), rewriteUnderscoreApply(expr))
+    | {pexp_desc: Pexp_fun(lbl, defaultExpr, pattern, returnExpr), pexp_attributes: list{}} =>
+      let parameter = Parameter({
+        attrs: list{},
+        lbl: lbl,
+        defaultExpr: defaultExpr,
+        pat: pattern,
+      })
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | {pexp_desc: Pexp_newtype(stringLoc, rest), pexp_attributes: attrs} =>
+      let (stringLocs, returnExpr) = collectNewTypes(list{stringLoc}, rest)
+      let param = NewTypes({attrs: attrs, locs: stringLocs})
+      collect(attrsBefore, list{param, ...acc}, returnExpr)
+    | {
+        pexp_desc: Pexp_fun(lbl, defaultExpr, pattern, returnExpr),
+        pexp_attributes: list{({txt: "bs"}, _)} as attrs,
+      } =>
+      let parameter = Parameter({
+        attrs: attrs,
+        lbl: lbl,
+        defaultExpr: defaultExpr,
+        pat: pattern,
+      })
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | {
+        pexp_desc: Pexp_fun((Labelled(_) | Optional(_)) as lbl, defaultExpr, pattern, returnExpr),
+        pexp_attributes: attrs,
+      } =>
+      let parameter = Parameter({
+        attrs: attrs,
+        lbl: lbl,
+        defaultExpr: defaultExpr,
+        pat: pattern,
+      })
+      collect(attrsBefore, list{parameter, ...acc}, returnExpr)
+    | expr => (attrsBefore, List.rev(acc), expr)
+    }
+
+  switch expr {
+  | {
+      pexp_desc: Pexp_fun(Nolabel, _defaultExpr, _pattern, _returnExpr),
+      pexp_attributes: attrs,
+    } as expr =>
+    collect(attrs, list{}, {...expr, pexp_attributes: list{}})
+  | expr => collect(list{}, list{}, expr)
+  }
+}
+
+let processBracesAttr = expr =>
+  switch expr.pexp_attributes {
+  | list{({txt: "ns.braces"}, _) as attr, ...attrs} => (
+      Some(attr),
+      {...expr, pexp_attributes: attrs},
+    )
+  | _ => (None, expr)
+  }
+
+let filterParsingAttrs = attrs => List.filter(attr =>
+    switch attr {
+    | (
+        {
+          Location.txt:
+            "ns.ternary" | "ns.braces" | "res.template" | "bs" | "ns.iflet" | "ns.namedArgLoc",
+        },
+        _,
+      ) => false
+    | _ => true
+    }
+  , attrs)
+
+let isBlockExpr = expr =>
+  switch expr.pexp_desc {
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_let(_)
+  | Pexp_open(_)
+  | Pexp_sequence(_) => true
+  | _ => false
+  }
+
+let isBracedExpr = expr =>
+  switch processBracesAttr(expr) {
+  | (Some(_), _) => true
+  | _ => false
+  }
+
+let isMultilineText = txt => {
+  let len = String.length(txt)
+  let rec check = i =>
+    if i >= len {
+      false
+    } else {
+      let c = String.unsafe_get(txt, i)
+      switch c {
+      | '\n' | '\r' => true
+      | '\\' =>
+        if i + 2 == len {
+          false
+        } else {
+          check(i + 2)
+        }
+      | _ => check(i + 1)
+      }
+    }
+
+  check(0)
+}
+
+let isHuggableExpression = expr =>
+  switch expr.pexp_desc {
+  | Pexp_array(_)
+  | Pexp_tuple(_)
+  | Pexp_constant(Pconst_string(_, Some(_)))
+  | Pexp_construct({txt: Longident.Lident("::" | "[]")}, _)
+  | Pexp_extension({txt: "bs.obj" | "obj"}, _)
+  | Pexp_record(_) => true
+  | _ if isBlockExpr(expr) => true
+  | _ if isBracedExpr(expr) => true
+  | Pexp_constant(Pconst_string(txt, None)) if isMultilineText(txt) => true
+  | _ => false
+  }
+
+let isHuggableRhs = expr =>
+  switch expr.pexp_desc {
+  | Pexp_array(_)
+  | Pexp_tuple(_)
+  | Pexp_construct({txt: Longident.Lident("::" | "[]")}, _)
+  | Pexp_extension({txt: "bs.obj" | "obj"}, _)
+  | Pexp_record(_) => true
+  | _ if isBracedExpr(expr) => true
+  | _ => false
+  }
+
+let isHuggablePattern = pattern =>
+  switch pattern.ppat_desc {
+  | Ppat_array(_)
+  | Ppat_tuple(_)
+  | Ppat_record(_)
+  | Ppat_variant(_)
+  | Ppat_construct(_) => true
+  | _ => false
+  }
+
+let operatorPrecedence = operator =>
+  switch operator {
+  | ":=" => 1
+  | "||" => 2
+  | "&&" => 3
+  | "=" | "==" | "<" | ">" | "!=" | "<>" | "!==" | "<=" | ">=" | "|>" => 4
+  | "+" | "+." | "-" | "-." | "^" => 5
+  | "*" | "*." | "/" | "/." => 6
+  | "**" => 7
+  | "#" | "##" | "|." => 8
+  | _ => 0
+  }
+
+let isUnaryOperator = operator =>
+  switch operator {
+  | "~+" | "~+." | "~-" | "~-." | "not" => true
+  | _ => false
+  }
+
+let isUnaryExpression = expr =>
+  switch expr.pexp_desc {
+  | Pexp_apply({pexp_desc: Pexp_ident({txt: Longident.Lident(operator)})}, list{(Nolabel, _arg)})
+    if isUnaryOperator(operator) => true
+  | _ => false
+  }
+
+/* TODO: tweak this to check for ghost ^ as template literal */
+let isBinaryOperator = operator =>
+  switch operator {
+  | ":="
+  | "||"
+  | "&&"
+  | "="
+  | "=="
+  | "<"
+  | ">"
+  | "!="
+  | "!=="
+  | "<="
+  | ">="
+  | "|>"
+  | "+"
+  | "+."
+  | "-"
+  | "-."
+  | "^"
+  | "*"
+  | "*."
+  | "/"
+  | "/."
+  | "**"
+  | "|."
+  | "<>" => true
+  | _ => false
+  }
+
+let isBinaryExpression = expr =>
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident(operator), loc: operatorLoc})},
+      list{(Nolabel, _operand1), (Nolabel, _operand2)},
+    )
+    if isBinaryOperator(operator) &&
+    !(operatorLoc.loc_ghost && operator == "^") /* template literal */ => true
+  | _ => false
+  }
+
+let isEqualityOperator = operator =>
+  switch operator {
+  | "=" | "==" | "<>" | "!=" => true
+  | _ => false
+  }
+
+let flattenableOperators = (parentOperator, childOperator) => {
+  let precParent = operatorPrecedence(parentOperator)
+  let precChild = operatorPrecedence(childOperator)
+  if precParent === precChild {
+    !(isEqualityOperator(parentOperator) && isEqualityOperator(childOperator))
+  } else {
+    false
+  }
+}
+
+let rec hasIfLetAttribute = attrs =>
+  switch attrs {
+  | list{} => false
+  | list{({Location.txt: "ns.iflet"}, _), ..._} => true
+  | list{_, ...attrs} => hasIfLetAttribute(attrs)
+  }
+
+let isIfLetExpr = expr =>
+  switch expr {
+  | {pexp_attributes: attrs, pexp_desc: Pexp_match(_)} if hasIfLetAttribute(attrs) => true
+  | _ => false
+  }
+
+let hasAttributes = attrs => List.exists(attr =>
+    switch attr {
+    | ({Location.txt: "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet"}, _) => false
+    /* Remove the fragile pattern warning for iflet expressions */
+    | (
+        {Location.txt: "warning"},
+        PStr(list{{
+          pstr_desc: Pstr_eval({pexp_desc: Pexp_constant(Pconst_string("-4", None))}, _),
+        }}),
+      ) =>
+      !hasIfLetAttribute(attrs)
+    | _ => true
+    }
+  , attrs)
+
+let isArrayAccess = expr =>
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Ldot(Lident("Array"), "get")})},
+      list{(Nolabel, _parentExpr), (Nolabel, _memberExpr)},
+    ) => true
+  | _ => false
+  }
+
+type ifConditionKind =
+  | If(Parsetree.expression)
+  | IfLet(Parsetree.pattern, Parsetree.expression)
+
+let collectIfExpressions = expr => {
+  let rec collect = (acc, expr) =>
+    switch expr.pexp_desc {
+    | Pexp_ifthenelse(ifExpr, thenExpr, Some(elseExpr)) =>
+      collect(list{(If(ifExpr), thenExpr), ...acc}, elseExpr)
+    | Pexp_ifthenelse(ifExpr, thenExpr, None as elseExpr) =>
+      let ifs = List.rev(list{(If(ifExpr), thenExpr), ...acc})
+      (ifs, elseExpr)
+    | Pexp_match(
+        condition,
+        list{
+          {pc_lhs: pattern, pc_guard: None, pc_rhs: thenExpr},
+          {pc_rhs: {pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, _)}},
+        },
+      ) if isIfLetExpr(expr) =>
+      let ifs = List.rev(list{(IfLet(pattern, condition), thenExpr), ...acc})
+      (ifs, None)
+    | Pexp_match(
+        condition,
+        list{{pc_lhs: pattern, pc_guard: None, pc_rhs: thenExpr}, {pc_rhs: elseExpr}},
+      ) if isIfLetExpr(expr) =>
+      collect(list{(IfLet(pattern, condition), thenExpr), ...acc}, elseExpr)
+    | _ => (List.rev(acc), Some(expr))
+    }
+
+  collect(list{}, expr)
+}
+
+let rec hasTernaryAttribute = attrs =>
+  switch attrs {
+  | list{} => false
+  | list{({Location.txt: "ns.ternary"}, _), ..._} => true
+  | list{_, ...attrs} => hasTernaryAttribute(attrs)
+  }
+
+let isTernaryExpr = expr =>
+  switch expr {
+  | {pexp_attributes: attrs, pexp_desc: Pexp_ifthenelse(_)} if hasTernaryAttribute(attrs) => true
+  | _ => false
+  }
+
+let collectTernaryParts = expr => {
+  let rec collect = (acc, expr) =>
+    switch expr {
+    | {pexp_attributes: attrs, pexp_desc: Pexp_ifthenelse(condition, consequent, Some(alternate))}
+      if hasTernaryAttribute(attrs) =>
+      collect(list{(condition, consequent), ...acc}, alternate)
+    | alternate => (List.rev(acc), alternate)
+    }
+
+  collect(list{}, expr)
+}
+
+let parametersShouldHug = parameters =>
+  switch parameters {
+  | list{Parameter({attrs: list{}, lbl: Asttypes.Nolabel, defaultExpr: None, pat})}
+    if isHuggablePattern(pat) => true
+  | _ => false
+  }
+
+let filterTernaryAttributes = attrs => List.filter(attr =>
+    switch attr {
+    | ({Location.txt: "ns.ternary"}, _) => false
+    | _ => true
+    }
+  , attrs)
+
+let filterFragileMatchAttributes = attrs => List.filter(attr =>
+    switch attr {
+    | (
+        {Location.txt: "warning"},
+        PStr(list{{pstr_desc: Pstr_eval({pexp_desc: Pexp_constant(Pconst_string("-4", _))}, _)}}),
+      ) => false
+    | _ => true
+    }
+  , attrs)
+
+let isJsxExpression = expr => {
+  let rec loop = attrs =>
+    switch attrs {
+    | list{} => false
+    | list{({Location.txt: "JSX"}, _), ..._} => true
+    | list{_, ...attrs} => loop(attrs)
+    }
+
+  switch expr.pexp_desc {
+  | Pexp_apply(_) => loop(expr.Parsetree.pexp_attributes)
+  | _ => false
+  }
+}
+
+let hasJsxAttribute = attributes => {
+  let rec loop = attrs =>
+    switch attrs {
+    | list{} => false
+    | list{({Location.txt: "JSX"}, _), ..._} => true
+    | list{_, ...attrs} => loop(attrs)
+    }
+
+  loop(attributes)
+}
+
+let shouldIndentBinaryExpr = expr => {
+  let samePrecedenceSubExpression = (operator, subExpression) =>
+    switch subExpression {
+    | {
+        pexp_desc: Pexp_apply(
+          {pexp_desc: Pexp_ident({txt: Longident.Lident(subOperator)})},
+          list{(Nolabel, _lhs), (Nolabel, _rhs)},
+        ),
+      } if isBinaryOperator(subOperator) =>
+      flattenableOperators(operator, subOperator)
+    | _ => true
+    }
+
+  switch expr {
+  | {
+      pexp_desc: Pexp_apply(
+        {pexp_desc: Pexp_ident({txt: Longident.Lident(operator)})},
+        list{(Nolabel, lhs), (Nolabel, _rhs)},
+      ),
+    } if isBinaryOperator(operator) =>
+    isEqualityOperator(operator) ||
+    (!samePrecedenceSubExpression(operator, lhs) ||
+    operator == ":=")
+  | _ => false
+  }
+}
+
+let shouldInlineRhsBinaryExpr = rhs =>
+  switch rhs.pexp_desc {
+  | Parsetree.Pexp_constant(_)
+  | Pexp_let(_)
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_sequence(_)
+  | Pexp_open(_)
+  | Pexp_ifthenelse(_)
+  | Pexp_for(_)
+  | Pexp_while(_)
+  | Pexp_try(_)
+  | Pexp_array(_)
+  | Pexp_record(_) => true
+  | _ => false
+  }
+
+let isPrintableAttribute = attr =>
+  switch attr {
+  | (
+      {Location.txt: "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet" | "JSX"},
+      _,
+    ) => false
+  | _ => true
+  }
+
+let hasPrintableAttributes = attrs => List.exists(isPrintableAttribute, attrs)
+
+let filterPrintableAttributes = attrs => List.filter(isPrintableAttribute, attrs)
+
+let partitionPrintableAttributes = attrs => List.partition(isPrintableAttribute, attrs)
+
+let requiresSpecialCallbackPrintingLastArg = args => {
+  let rec loop = args =>
+    switch args {
+    | list{} => false
+    | list{(_, {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)})} => true
+    | list{(_, {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)}), ..._} => false
+    | list{_, ...rest} => loop(rest)
+    }
+
+  loop(args)
+}
+
+let requiresSpecialCallbackPrintingFirstArg = args => {
+  let rec loop = args =>
+    switch args {
+    | list{} => true
+    | list{(_, {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)}), ..._} => false
+    | list{_, ...rest} => loop(rest)
+    }
+
+  switch args {
+  | list{(_, {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)})} => false
+  | list{(_, {pexp_desc: Pexp_fun(_) | Pexp_newtype(_)}), ...rest} => loop(rest)
+  | _ => false
+  }
+}
+
+let modExprApply = modExpr => {
+  let rec loop = (acc, modExpr) =>
+    switch modExpr {
+    | {pmod_desc: Pmod_apply(next, arg)} => loop(list{arg, ...acc}, next)
+    | _ => (acc, modExpr)
+    }
+
+  loop(list{}, modExpr)
+}
+
+let modExprFunctor = modExpr => {
+  let rec loop = (acc, modExpr) =>
+    switch modExpr {
+    | {pmod_desc: Pmod_functor(lbl, modType, returnModExpr), pmod_attributes: attrs} =>
+      let param = (attrs, lbl, modType)
+      loop(list{param, ...acc}, returnModExpr)
+    | returnModExpr => (List.rev(acc), returnModExpr)
+    }
+
+  loop(list{}, modExpr)
+}
+
+let rec collectPatternsFromListConstruct = (acc, pattern) => {
+  open Parsetree
+  switch pattern.ppat_desc {
+  | Ppat_construct({txt: Longident.Lident("::")}, Some({ppat_desc: Ppat_tuple(list{pat, rest})})) =>
+    collectPatternsFromListConstruct(list{pat, ...acc}, rest)
+  | _ => (List.rev(acc), pattern)
+  }
+}
+
+let hasTemplateLiteralAttr = attrs => List.exists(attr =>
+    switch attr {
+    | ({Location.txt: "res.template"}, _) => true
+    | _ => false
+    }
+  , attrs)
+
+let isTemplateLiteral = expr =>
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident("^")})},
+      list{(Nolabel, _), (Nolabel, _)},
+    ) if hasTemplateLiteralAttr(expr.pexp_attributes) => true
+  | Pexp_constant(Pconst_string(_, Some(""))) => true
+  | Pexp_constant(_) if hasTemplateLiteralAttr(expr.pexp_attributes) => true
+  | _ => false
+  }
+
+/* Blue | Red | Green -> [Blue; Red; Green] */
+let collectOrPatternChain = pat => {
+  let rec loop = (pattern, chain) =>
+    switch pattern.ppat_desc {
+    | Ppat_or(left, right) => loop(left, list{right, ...chain})
+    | _ => list{pattern, ...chain}
+    }
+
+  loop(pat, list{})
+}
+
+let isSinglePipeExpr = expr => {
+  /* handles:
+   *   x
+   *   ->Js.Dict.get("wm-property")
+   *   ->Option.flatMap(Js.Json.decodeString)
+   *   ->Option.flatMap(x =>
+   *     switch x {
+   *     | "like-of" => Some(#like)
+   *     | "repost-of" => Some(#repost)
+   *     | _ => None
+   *     }
+   *   )
+   */
+  let isPipeExpr = expr =>
+    switch expr.pexp_desc {
+    | Pexp_apply(
+        {pexp_desc: Pexp_ident({txt: Longident.Lident("|." | "|>")})},
+        list{(Nolabel, _operand1), (Nolabel, _operand2)},
+      ) => true
+    | _ => false
+    }
+
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident("|." | "|>")})},
+      list{(Nolabel, operand1), (Nolabel, _operand2)},
+    ) if !isPipeExpr(operand1) => true
+  | _ => false
+  }
+}
+
+let isUnderscoreApplySugar = expr =>
+  switch expr.pexp_desc {
+  | Pexp_fun(Nolabel, None, {ppat_desc: Ppat_var({txt: "__x"})}, {pexp_desc: Pexp_apply(_)}) => true
+  | _ => false
+  }
+
+let isRewrittenUnderscoreApplySugar = expr =>
+  switch expr.pexp_desc {
+  | Pexp_ident({txt: Longident.Lident("_")}) => true
+  | _ => false
+  }
+
diff --git a/analysis/examples/larger-project/src/res_printer.js b/analysis/examples/larger-project/src/res_printer.js
new file mode 100644
index 0000000000..0d511ded0d
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_printer.js
@@ -0,0 +1,9576 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Char from "rescript/lib/es6/char.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as Bytes from "rescript/lib/es6/bytes.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Hashtbl from "rescript/lib/es6/hashtbl.js";
+import * as Res_doc from "./res_doc.js";
+import * as $$Location from "./location.js";
+import * as Res_utf8 from "./res_utf8.js";
+import * as Longident from "./longident.js";
+import * as Res_token from "./res_token.js";
+import * as Res_parens from "./res_parens.js";
+import * as Res_comment from "./res_comment.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+import * as Res_comments_table from "./res_comments_table.js";
+import * as Res_parsetree_viewer from "./res_parsetree_viewer.js";
+
+function convertBsExternalAttribute(x) {
+  switch (x) {
+    case "bs.as" :
+        return "as";
+    case "bs.deriving" :
+        return "deriving";
+    case "bs.get" :
+        return "get";
+    case "bs.get_index" :
+        return "get_index";
+    case "bs.ignore" :
+        return "ignore";
+    case "bs.inline" :
+        return "inline";
+    case "bs.int" :
+        return "int";
+    case "bs.meth" :
+        return "meth";
+    case "bs.module" :
+        return "module";
+    case "bs.new" :
+        return "new";
+    case "bs.obj" :
+        return "obj";
+    case "bs.optional" :
+        return "optional";
+    case "bs.return" :
+        return "return";
+    case "bs.scope" :
+        return "scope";
+    case "bs.send" :
+        return "send";
+    case "bs.set" :
+        return "set";
+    case "bs.set_index" :
+        return "set_index";
+    case "bs.string" :
+        return "string";
+    case "bs.this" :
+        return "this";
+    case "bs.uncurry" :
+        return "uncurry";
+    case "bs.unwrap" :
+        return "unwrap";
+    case "bs.val" :
+        return "val";
+    case "bs.splice" :
+    case "bs.variadic" :
+        return "variadic";
+    default:
+      return x;
+  }
+}
+
+function convertBsExtension(x) {
+  switch (x) {
+    case "bs.debugger" :
+        return "debugger";
+    case "bs.obj" :
+        return "obj";
+    case "bs.external" :
+    case "bs.raw" :
+        return "raw";
+    case "bs.re" :
+        return "re";
+    default:
+      return x;
+  }
+}
+
+function addParens(doc) {
+  return Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.lparen,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.softLine,
+                              tl: {
+                                hd: doc,
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: {
+                      hd: Res_doc.softLine,
+                      tl: {
+                        hd: Res_doc.rparen,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function addBraces(doc) {
+  return Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.lbrace,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.softLine,
+                              tl: {
+                                hd: doc,
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: {
+                      hd: Res_doc.softLine,
+                      tl: {
+                        hd: Res_doc.rbrace,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function getFirstLeadingComment(tbl, loc) {
+  var val;
+  try {
+    val = Hashtbl.find(tbl.leading, loc);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return ;
+    }
+    throw exn;
+  }
+  if (val) {
+    return val.hd;
+  }
+  
+}
+
+function hasLeadingLineComment(tbl, loc) {
+  var comment = getFirstLeadingComment(tbl, loc);
+  if (comment !== undefined) {
+    return Res_comment.isSingleLineComment(comment);
+  } else {
+    return false;
+  }
+}
+
+function hasCommentBelow(tbl, loc) {
+  var val;
+  try {
+    val = Hashtbl.find(tbl.trailing, loc);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return false;
+    }
+    throw exn;
+  }
+  if (!val) {
+    return false;
+  }
+  var commentLoc = Res_comment.loc(val.hd);
+  return commentLoc.loc_start.pos_lnum > loc.loc_end.pos_lnum;
+}
+
+function printMultilineCommentContent(txt) {
+  var indentStars = function (_lines, _acc) {
+    while(true) {
+      var acc = _acc;
+      var lines = _lines;
+      if (!lines) {
+        return Res_doc.nil;
+      }
+      var lines$1 = lines.tl;
+      var lastLine = lines.hd;
+      if (lines$1) {
+        var line = $$String.trim(lastLine);
+        if (line !== "" && line[0] === "*") {
+          var doc = Res_doc.text(" " + line);
+          _acc = {
+            hd: Res_doc.hardLine,
+            tl: {
+              hd: doc,
+              tl: acc
+            }
+          };
+          _lines = lines$1;
+          continue ;
+        }
+        var len = txt.length;
+        var trailingSpace = len > 0 && txt[len - 1 | 0] === " " ? Res_doc.space : Res_doc.nil;
+        var content = Res_comment.trimSpaces(txt);
+        return Res_doc.concat({
+                    hd: Res_doc.text(content),
+                    tl: {
+                      hd: trailingSpace,
+                      tl: /* [] */0
+                    }
+                  });
+      }
+      var line$1 = $$String.trim(lastLine);
+      var doc$1 = Res_doc.text(" " + line$1);
+      var trailingSpace$1 = line$1 === "" ? Res_doc.nil : Res_doc.space;
+      return Res_doc.concat(List.rev({
+                      hd: trailingSpace$1,
+                      tl: {
+                        hd: doc$1,
+                        tl: acc
+                      }
+                    }));
+    };
+  };
+  var lines = $$String.split_on_char(/* '\n' */10, txt);
+  if (!lines) {
+    return Res_doc.text("/* */");
+  }
+  var rest = lines.tl;
+  var line = lines.hd;
+  if (!rest) {
+    return Res_doc.concat({
+                hd: Res_doc.text("/* "),
+                tl: {
+                  hd: Res_doc.text(Res_comment.trimSpaces(line)),
+                  tl: {
+                    hd: Res_doc.text(" */"),
+                    tl: /* [] */0
+                  }
+                }
+              });
+  }
+  var firstLine = Res_comment.trimSpaces(line);
+  var tmp;
+  switch (firstLine) {
+    case "" :
+    case "*" :
+        tmp = Res_doc.nil;
+        break;
+    default:
+      tmp = Res_doc.space;
+  }
+  return Res_doc.concat({
+              hd: Res_doc.text("/*"),
+              tl: {
+                hd: tmp,
+                tl: {
+                  hd: indentStars(rest, {
+                        hd: Res_doc.hardLine,
+                        tl: {
+                          hd: Res_doc.text(firstLine),
+                          tl: /* [] */0
+                        }
+                      }),
+                  tl: {
+                    hd: Res_doc.text("*/"),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printTrailingComment(prevLoc, nodeLoc, comment) {
+  var singleLine = Res_comment.isSingleLineComment(comment);
+  var txt = Res_comment.txt(comment);
+  var content = singleLine ? Res_doc.text("//" + txt) : printMultilineCommentContent(txt);
+  var cmtStart = Res_comment.loc(comment).loc_start;
+  var diff = cmtStart.pos_lnum - prevLoc.loc_end.pos_lnum | 0;
+  var isBelow = Res_comment.loc(comment).loc_start.pos_lnum > nodeLoc.loc_end.pos_lnum;
+  if (diff > 0 || isBelow) {
+    return Res_doc.concat({
+                hd: Res_doc.breakParent,
+                tl: {
+                  hd: Res_doc.lineSuffix(Res_doc.concat({
+                            hd: Res_doc.hardLine,
+                            tl: {
+                              hd: diff > 1 ? Res_doc.hardLine : Res_doc.nil,
+                              tl: {
+                                hd: content,
+                                tl: /* [] */0
+                              }
+                            }
+                          })),
+                  tl: /* [] */0
+                }
+              });
+  } else if (singleLine) {
+    return Res_doc.lineSuffix(Res_doc.concat({
+                    hd: Res_doc.space,
+                    tl: {
+                      hd: content,
+                      tl: /* [] */0
+                    }
+                  }));
+  } else {
+    return Res_doc.concat({
+                hd: Res_doc.space,
+                tl: {
+                  hd: content,
+                  tl: /* [] */0
+                }
+              });
+  }
+}
+
+function printLeadingComment(nextComment, comment) {
+  var singleLine = Res_comment.isSingleLineComment(comment);
+  var txt = Res_comment.txt(comment);
+  var content = singleLine ? Res_doc.text("//" + txt) : printMultilineCommentContent(txt);
+  var tmp;
+  if (nextComment !== undefined) {
+    var nextLoc = Res_comment.loc(nextComment);
+    var currLoc = Res_comment.loc(comment);
+    var diff = nextLoc.loc_start.pos_lnum - currLoc.loc_end.pos_lnum | 0;
+    var nextSingleLine = Res_comment.isSingleLineComment(nextComment);
+    tmp = singleLine && nextSingleLine ? (
+        diff > 1 ? Res_doc.hardLine : Res_doc.nil
+      ) : (
+        singleLine && !nextSingleLine ? (
+            diff > 1 ? Res_doc.hardLine : Res_doc.nil
+          ) : (
+            diff > 1 ? Res_doc.concat({
+                    hd: Res_doc.hardLine,
+                    tl: {
+                      hd: Res_doc.hardLine,
+                      tl: /* [] */0
+                    }
+                  }) : (
+                diff === 1 ? Res_doc.hardLine : Res_doc.space
+              )
+          )
+      );
+  } else {
+    tmp = Res_doc.nil;
+  }
+  var separator = Res_doc.concat({
+        hd: singleLine ? Res_doc.concat({
+                hd: Res_doc.hardLine,
+                tl: {
+                  hd: Res_doc.breakParent,
+                  tl: /* [] */0
+                }
+              }) : Res_doc.nil,
+        tl: {
+          hd: tmp,
+          tl: /* [] */0
+        }
+      });
+  return Res_doc.concat({
+              hd: content,
+              tl: {
+                hd: separator,
+                tl: /* [] */0
+              }
+            });
+}
+
+function printCommentsInside(cmtTbl, loc) {
+  var loop = function (_acc, _comments) {
+    while(true) {
+      var comments = _comments;
+      var acc = _acc;
+      if (!comments) {
+        return Res_doc.nil;
+      }
+      var rest = comments.tl;
+      var comment = comments.hd;
+      if (rest) {
+        var cmtDoc = printLeadingComment(rest.hd, comment);
+        _comments = rest;
+        _acc = {
+          hd: cmtDoc,
+          tl: acc
+        };
+        continue ;
+      }
+      var cmtDoc$1 = printLeadingComment(undefined, comment);
+      return Res_doc.group(Res_doc.concat({
+                      hd: Res_doc.concat(List.rev({
+                                hd: cmtDoc$1,
+                                tl: acc
+                              })),
+                      tl: /* [] */0
+                    }));
+    };
+  };
+  var comments;
+  try {
+    comments = Hashtbl.find(cmtTbl.inside, loc);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return Res_doc.nil;
+    }
+    throw exn;
+  }
+  Hashtbl.remove(cmtTbl.inside, loc);
+  return Res_doc.group(loop(/* [] */0, comments));
+}
+
+function printLeadingComments(node, tbl, loc) {
+  var comments;
+  try {
+    comments = Hashtbl.find(tbl, loc);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return node;
+    }
+    throw exn;
+  }
+  Hashtbl.remove(tbl, loc);
+  var _acc = /* [] */0;
+  var _comments = comments;
+  while(true) {
+    var comments$1 = _comments;
+    var acc = _acc;
+    if (!comments$1) {
+      return node;
+    }
+    var rest = comments$1.tl;
+    var comment = comments$1.hd;
+    if (rest) {
+      var cmtDoc = printLeadingComment(rest.hd, comment);
+      _comments = rest;
+      _acc = {
+        hd: cmtDoc,
+        tl: acc
+      };
+      continue ;
+    }
+    var cmtDoc$1 = printLeadingComment(undefined, comment);
+    var diff = loc.loc_start.pos_lnum - Res_comment.loc(comment).loc_end.pos_lnum | 0;
+    var separator = Res_comment.isSingleLineComment(comment) ? (
+        diff > 1 ? Res_doc.hardLine : Res_doc.nil
+      ) : (
+        diff === 0 ? Res_doc.space : (
+            diff > 1 ? Res_doc.concat({
+                    hd: Res_doc.hardLine,
+                    tl: {
+                      hd: Res_doc.hardLine,
+                      tl: /* [] */0
+                    }
+                  }) : Res_doc.hardLine
+          )
+      );
+    return Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.concat(List.rev({
+                              hd: cmtDoc$1,
+                              tl: acc
+                            })),
+                    tl: {
+                      hd: separator,
+                      tl: {
+                        hd: node,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+  };
+}
+
+function printTrailingComments(node, tbl, loc) {
+  var loop = function (_prev, _acc, _comments) {
+    while(true) {
+      var comments = _comments;
+      var acc = _acc;
+      var prev = _prev;
+      if (!comments) {
+        return Res_doc.concat(List.rev(acc));
+      }
+      var comment = comments.hd;
+      var cmtDoc = printTrailingComment(prev, loc, comment);
+      _comments = comments.tl;
+      _acc = {
+        hd: cmtDoc,
+        tl: acc
+      };
+      _prev = Res_comment.loc(comment);
+      continue ;
+    };
+  };
+  var comments;
+  try {
+    comments = Hashtbl.find(tbl, loc);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return node;
+    }
+    throw exn;
+  }
+  if (!comments) {
+    return node;
+  }
+  Hashtbl.remove(tbl, loc);
+  var cmtsDoc = loop(loc, /* [] */0, comments);
+  return Res_doc.concat({
+              hd: node,
+              tl: {
+                hd: cmtsDoc,
+                tl: /* [] */0
+              }
+            });
+}
+
+function printComments(doc, tbl, loc) {
+  var docWithLeadingComments = printLeadingComments(doc, tbl.leading, loc);
+  return printTrailingComments(docWithLeadingComments, tbl.trailing, loc);
+}
+
+function printList(getLoc, nodes, print, forceBreakOpt, t) {
+  var forceBreak = forceBreakOpt !== undefined ? forceBreakOpt : false;
+  var loop = function (_prevLoc, _acc, _nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var acc = _acc;
+      var prevLoc = _prevLoc;
+      if (!nodes) {
+        return [
+                prevLoc,
+                Res_doc.concat(List.rev(acc))
+              ];
+      }
+      var node = nodes.hd;
+      var loc = Curry._1(getLoc, node);
+      var comment = getFirstLeadingComment(t, loc);
+      var startPos = comment !== undefined ? Res_comment.loc(comment).loc_start : loc.loc_start;
+      var sep = (startPos.pos_lnum - prevLoc.loc_end.pos_lnum | 0) > 1 ? Res_doc.concat({
+              hd: Res_doc.hardLine,
+              tl: {
+                hd: Res_doc.hardLine,
+                tl: /* [] */0
+              }
+            }) : Res_doc.hardLine;
+      var doc = printComments(Curry._2(print, node, t), t, loc);
+      _nodes = nodes.tl;
+      _acc = {
+        hd: doc,
+        tl: {
+          hd: sep,
+          tl: acc
+        }
+      };
+      _prevLoc = loc;
+      continue ;
+    };
+  };
+  if (!nodes) {
+    return Res_doc.nil;
+  }
+  var node = nodes.hd;
+  var firstLoc = Curry._1(getLoc, node);
+  var doc = printComments(Curry._2(print, node, t), t, firstLoc);
+  var match = loop(firstLoc, {
+        hd: doc,
+        tl: /* [] */0
+      }, nodes.tl);
+  var forceBreak$1 = forceBreak || firstLoc.loc_start.pos_lnum !== match[0].loc_end.pos_lnum;
+  return Res_doc.breakableGroup(forceBreak$1, match[1]);
+}
+
+function printListi(getLoc, nodes, print, forceBreakOpt, t) {
+  var forceBreak = forceBreakOpt !== undefined ? forceBreakOpt : false;
+  var loop = function (_i, _prevLoc, _acc, _nodes) {
+    while(true) {
+      var nodes = _nodes;
+      var acc = _acc;
+      var prevLoc = _prevLoc;
+      var i = _i;
+      if (!nodes) {
+        return [
+                prevLoc,
+                Res_doc.concat(List.rev(acc))
+              ];
+      }
+      var node = nodes.hd;
+      var loc = Curry._1(getLoc, node);
+      var comment = getFirstLeadingComment(t, loc);
+      var startPos = comment !== undefined ? Res_comment.loc(comment).loc_start : loc.loc_start;
+      var sep = (startPos.pos_lnum - prevLoc.loc_end.pos_lnum | 0) > 1 ? Res_doc.concat({
+              hd: Res_doc.hardLine,
+              tl: {
+                hd: Res_doc.hardLine,
+                tl: /* [] */0
+              }
+            }) : Res_doc.line;
+      var doc = printComments(Curry._3(print, node, t, i), t, loc);
+      _nodes = nodes.tl;
+      _acc = {
+        hd: doc,
+        tl: {
+          hd: sep,
+          tl: acc
+        }
+      };
+      _prevLoc = loc;
+      _i = i + 1 | 0;
+      continue ;
+    };
+  };
+  if (!nodes) {
+    return Res_doc.nil;
+  }
+  var node = nodes.hd;
+  var firstLoc = Curry._1(getLoc, node);
+  var doc = printComments(Curry._3(print, node, t, 0), t, firstLoc);
+  var match = loop(1, firstLoc, {
+        hd: doc,
+        tl: /* [] */0
+      }, nodes.tl);
+  var forceBreak$1 = forceBreak || firstLoc.loc_start.pos_lnum !== match[0].loc_end.pos_lnum;
+  return Res_doc.breakableGroup(forceBreak$1, match[1]);
+}
+
+function printLongidentAux(_accu, _x) {
+  while(true) {
+    var x = _x;
+    var accu = _accu;
+    switch (x.TAG | 0) {
+      case /* Lident */0 :
+          return {
+                  hd: Res_doc.text(x._0),
+                  tl: accu
+                };
+      case /* Ldot */1 :
+          _x = x._0;
+          _accu = {
+            hd: Res_doc.text(x._1),
+            tl: accu
+          };
+          continue ;
+      case /* Lapply */2 :
+          var d1 = Res_doc.join(Res_doc.dot, printLongidentAux(/* [] */0, x._0));
+          var d2 = Res_doc.join(Res_doc.dot, printLongidentAux(/* [] */0, x._1));
+          return {
+                  hd: Res_doc.concat({
+                        hd: d1,
+                        tl: {
+                          hd: Res_doc.lparen,
+                          tl: {
+                            hd: d2,
+                            tl: {
+                              hd: Res_doc.rparen,
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      }),
+                  tl: accu
+                };
+      
+    }
+  };
+}
+
+function printLongident(x) {
+  switch (x.TAG | 0) {
+    case /* Lident */0 :
+        return Res_doc.text(x._0);
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return Res_doc.join(Res_doc.dot, printLongidentAux(/* [] */0, x));
+    
+  }
+}
+
+function classifyIdentContent(allowUidentOpt, txt) {
+  var allowUident = allowUidentOpt !== undefined ? allowUidentOpt : false;
+  if (Res_token.isKeywordTxt(txt)) {
+    return /* ExoticIdent */0;
+  }
+  var len = txt.length;
+  var _i = 0;
+  while(true) {
+    var i = _i;
+    if (i === len) {
+      return /* NormalIdent */1;
+    }
+    if (i === 0) {
+      var match = txt.charCodeAt(i);
+      if (match > 122 || match < 95) {
+        if (match > 90 || match < 65) {
+          return /* ExoticIdent */0;
+        }
+        if (!allowUident) {
+          return /* ExoticIdent */0;
+        }
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if (match === 96) {
+        return /* ExoticIdent */0;
+      }
+      _i = i + 1 | 0;
+      continue ;
+    }
+    var match$1 = txt.charCodeAt(i);
+    if (match$1 >= 65) {
+      if (match$1 > 96 || match$1 < 91) {
+        if (match$1 >= 123) {
+          return /* ExoticIdent */0;
+        }
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if (match$1 !== 95) {
+        return /* ExoticIdent */0;
+      }
+      _i = i + 1 | 0;
+      continue ;
+    }
+    if (match$1 >= 48) {
+      if (match$1 >= 58) {
+        return /* ExoticIdent */0;
+      }
+      _i = i + 1 | 0;
+      continue ;
+    }
+    if (match$1 !== 39) {
+      return /* ExoticIdent */0;
+    }
+    _i = i + 1 | 0;
+    continue ;
+  };
+}
+
+function printIdentLike(allowUident, txt) {
+  var match = classifyIdentContent(allowUident, txt);
+  if (match) {
+    return Res_doc.text(txt);
+  } else {
+    return Res_doc.concat({
+                hd: Res_doc.text("\\\""),
+                tl: {
+                  hd: Res_doc.text(txt),
+                  tl: {
+                    hd: Res_doc.text("\""),
+                    tl: /* [] */0
+                  }
+                }
+              });
+  }
+}
+
+function unsafe_for_all_range(s, _start, finish, p) {
+  while(true) {
+    var start = _start;
+    if (start > finish) {
+      return true;
+    }
+    if (!Curry._1(p, s.charCodeAt(start))) {
+      return false;
+    }
+    _start = start + 1 | 0;
+    continue ;
+  };
+}
+
+function for_all_from(s, start, p) {
+  var len = s.length;
+  return unsafe_for_all_range(s, start, len - 1 | 0, p);
+}
+
+function isValidNumericPolyvarNumber(x) {
+  var len = x.length;
+  if (len <= 0) {
+    return false;
+  }
+  var a = x.charCodeAt(0);
+  if (a <= 57) {
+    if (len > 1) {
+      if (a > 48) {
+        return for_all_from(x, 1, (function (x) {
+                      return !(x > 57 || x < 48);
+                    }));
+      } else {
+        return false;
+      }
+    } else {
+      return a >= 48;
+    }
+  } else {
+    return false;
+  }
+}
+
+function printPolyVarIdent(txt) {
+  if (isValidNumericPolyvarNumber(txt)) {
+    return Res_doc.text(txt);
+  }
+  var match = classifyIdentContent(true, txt);
+  if (match && txt !== "") {
+    return Res_doc.text(txt);
+  } else {
+    return Res_doc.concat({
+                hd: Res_doc.text("\""),
+                tl: {
+                  hd: Res_doc.text(txt),
+                  tl: {
+                    hd: Res_doc.text("\""),
+                    tl: /* [] */0
+                  }
+                }
+              });
+  }
+}
+
+function printLident(l) {
+  var flatLidOpt = function (lid) {
+    var _accu = /* [] */0;
+    var _x = lid;
+    while(true) {
+      var x = _x;
+      var accu = _accu;
+      switch (x.TAG | 0) {
+        case /* Lident */0 :
+            return {
+                    hd: x._0,
+                    tl: accu
+                  };
+        case /* Ldot */1 :
+            _x = x._0;
+            _accu = {
+              hd: x._1,
+              tl: accu
+            };
+            continue ;
+        case /* Lapply */2 :
+            return ;
+        
+      }
+    };
+  };
+  switch (l.TAG | 0) {
+    case /* Lident */0 :
+        return printIdentLike(undefined, l._0);
+    case /* Ldot */1 :
+        var txts = flatLidOpt(l._0);
+        if (txts !== undefined) {
+          return Res_doc.concat({
+                      hd: Res_doc.join(Res_doc.dot, List.map(Res_doc.text, txts)),
+                      tl: {
+                        hd: Res_doc.dot,
+                        tl: {
+                          hd: printIdentLike(undefined, l._1),
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+        } else {
+          return Res_doc.text("printLident: Longident.Lapply is not supported");
+        }
+    case /* Lapply */2 :
+        return Res_doc.text("printLident: Longident.Lapply is not supported");
+    
+  }
+}
+
+function printLongidentLocation(l, cmtTbl) {
+  var doc = printLongident(l.txt);
+  return printComments(doc, cmtTbl, l.loc);
+}
+
+function printLidentPath(path, cmtTbl) {
+  var doc = printLident(path.txt);
+  return printComments(doc, cmtTbl, path.loc);
+}
+
+function printIdentPath(path, cmtTbl) {
+  var doc = printLident(path.txt);
+  return printComments(doc, cmtTbl, path.loc);
+}
+
+function printStringLoc(sloc, cmtTbl) {
+  var doc = printIdentLike(undefined, sloc.txt);
+  return printComments(doc, cmtTbl, sloc.loc);
+}
+
+function printStringContents(txt) {
+  var lines = $$String.split_on_char(/* '\n' */10, txt);
+  return Res_doc.join(Res_doc.literalLine, List.map(Res_doc.text, lines));
+}
+
+function printConstant(templateLiteralOpt, c) {
+  var templateLiteral = templateLiteralOpt !== undefined ? templateLiteralOpt : false;
+  switch (c.TAG | 0) {
+    case /* Pconst_integer */0 :
+        var suffix = c._1;
+        var s = c._0;
+        if (suffix !== undefined) {
+          return Res_doc.text(s + Char.escaped(suffix));
+        } else {
+          return Res_doc.text(s);
+        }
+    case /* Pconst_char */1 :
+        var c$1 = c._0;
+        var str;
+        var exit = 0;
+        if (c$1 >= 40) {
+          if (c$1 !== 92) {
+            if (c$1 >= 127) {
+              str = Res_utf8.encodeCodePoint(c$1);
+            } else {
+              exit = 1;
+            }
+          } else {
+            str = "\\\\";
+          }
+        } else if (c$1 >= 32) {
+          if (c$1 >= 39) {
+            str = "\\'";
+          } else {
+            exit = 1;
+          }
+        } else if (c$1 >= 14) {
+          str = Res_utf8.encodeCodePoint(c$1);
+        } else {
+          switch (c$1) {
+            case 8 :
+                str = "\\b";
+                break;
+            case 9 :
+                str = "\\t";
+                break;
+            case 10 :
+                str = "\\n";
+                break;
+            case 0 :
+            case 1 :
+            case 2 :
+            case 3 :
+            case 4 :
+            case 5 :
+            case 6 :
+            case 7 :
+            case 11 :
+            case 12 :
+                str = Res_utf8.encodeCodePoint(c$1);
+                break;
+            case 13 :
+                str = "\\r";
+                break;
+            
+          }
+        }
+        if (exit === 1) {
+          var s$1 = [0];
+          s$1[0] = c$1;
+          str = Bytes.unsafe_to_string(s$1);
+        }
+        return Res_doc.text("'" + (str + "'"));
+    case /* Pconst_string */2 :
+        var prefix = c._1;
+        var txt = c._0;
+        if (prefix === undefined) {
+          return Res_doc.concat({
+                      hd: Res_doc.text("\""),
+                      tl: {
+                        hd: printStringContents(txt),
+                        tl: {
+                          hd: Res_doc.text("\""),
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+        }
+        if (prefix === "INTERNAL_RES_CHAR_CONTENTS") {
+          return Res_doc.concat({
+                      hd: Res_doc.text("'"),
+                      tl: {
+                        hd: Res_doc.text(txt),
+                        tl: {
+                          hd: Res_doc.text("'"),
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+        }
+        var match = templateLiteral ? [
+            "`",
+            "`"
+          ] : [
+            "\"",
+            "\""
+          ];
+        return Res_doc.concat({
+                    hd: prefix === "js" ? Res_doc.nil : Res_doc.text(prefix),
+                    tl: {
+                      hd: Res_doc.text(match[0]),
+                      tl: {
+                        hd: printStringContents(txt),
+                        tl: {
+                          hd: Res_doc.text(match[1]),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+    case /* Pconst_float */3 :
+        return Res_doc.text(c._0);
+    
+  }
+}
+
+function printStructure(s, t) {
+  if (s) {
+    return printList((function (s) {
+                  return s.pstr_loc;
+                }), s, printStructureItem, undefined, t);
+  } else {
+    return printCommentsInside(t, $$Location.none);
+  }
+}
+
+function printStructureItem(si, cmtTbl) {
+  var valueDescription = si.pstr_desc;
+  switch (valueDescription.TAG | 0) {
+    case /* Pstr_eval */0 :
+        var expr = valueDescription._0;
+        var doc = printExpressionWithComments(expr, cmtTbl);
+        var braces = Res_parens.structureExpr(expr);
+        var exprDoc = typeof braces === "number" ? (
+            braces !== 0 ? doc : addParens(doc)
+          ) : printBraces(doc, expr, braces._0);
+        return Res_doc.concat({
+                    hd: printAttributes(undefined, undefined, valueDescription._1, cmtTbl),
+                    tl: {
+                      hd: exprDoc,
+                      tl: /* [] */0
+                    }
+                  });
+    case /* Pstr_value */1 :
+        var recFlag = valueDescription._0 ? Res_doc.text("rec ") : Res_doc.nil;
+        return printValueBindings(recFlag, valueDescription._1, cmtTbl);
+    case /* Pstr_primitive */2 :
+        return printValueDescription(valueDescription._0, cmtTbl);
+    case /* Pstr_type */3 :
+        var recFlag$1 = valueDescription._0 ? Res_doc.text("rec ") : Res_doc.nil;
+        return printTypeDeclarations(recFlag$1, valueDescription._1, cmtTbl);
+    case /* Pstr_typext */4 :
+        return printTypeExtension(valueDescription._0, cmtTbl);
+    case /* Pstr_exception */5 :
+        return printExceptionDef(valueDescription._0, cmtTbl);
+    case /* Pstr_module */6 :
+        return printModuleBinding(false, valueDescription._0, cmtTbl, 0);
+    case /* Pstr_recmodule */7 :
+        return printListi((function (mb) {
+                      return mb.pmb_loc;
+                    }), valueDescription._0, (function (param, param$1, param$2) {
+                      return printModuleBinding(true, param, param$1, param$2);
+                    }), undefined, cmtTbl);
+    case /* Pstr_modtype */8 :
+        return printModuleTypeDeclaration(valueDescription._0, cmtTbl);
+    case /* Pstr_open */9 :
+        return printOpenDescription(valueDescription._0, cmtTbl);
+    case /* Pstr_class */10 :
+    case /* Pstr_class_type */11 :
+        return Res_doc.nil;
+    case /* Pstr_include */12 :
+        return printIncludeDeclaration(valueDescription._0, cmtTbl);
+    case /* Pstr_attribute */13 :
+        return Res_doc.concat({
+                    hd: Res_doc.text("@"),
+                    tl: {
+                      hd: printAttribute(valueDescription._0, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  });
+    case /* Pstr_extension */14 :
+        return Res_doc.concat({
+                    hd: printAttributes(undefined, undefined, valueDescription._1, cmtTbl),
+                    tl: {
+                      hd: Res_doc.concat({
+                            hd: printExtension(true, valueDescription._0, cmtTbl),
+                            tl: /* [] */0
+                          }),
+                      tl: /* [] */0
+                    }
+                  });
+    
+  }
+}
+
+function printTypeExtension(te, cmtTbl) {
+  var prefix = Res_doc.text("type ");
+  var name = printLidentPath(te.ptyext_path, cmtTbl);
+  var typeParams = printTypeParams(te.ptyext_params, cmtTbl);
+  var ecs = te.ptyext_constructors;
+  var match = List.rev(ecs);
+  var forceBreak;
+  if (ecs && match) {
+    var first = ecs.hd;
+    forceBreak = first.pext_loc.loc_start.pos_lnum > te.ptyext_path.loc.loc_end.pos_lnum || first.pext_loc.loc_start.pos_lnum < match.hd.pext_loc.loc_end.pos_lnum;
+  } else {
+    forceBreak = false;
+  }
+  var match$1 = te.ptyext_private;
+  var privateFlag = match$1 ? Res_doc.nil : Res_doc.concat({
+          hd: Res_doc.text("private"),
+          tl: {
+            hd: Res_doc.line,
+            tl: /* [] */0
+          }
+        });
+  var rows = printListi((function (n) {
+          return n.pext_loc;
+        }), ecs, printExtensionConstructor, forceBreak, cmtTbl);
+  var extensionConstructors = Res_doc.breakableGroup(forceBreak, Res_doc.indent(Res_doc.concat({
+                hd: Res_doc.line,
+                tl: {
+                  hd: privateFlag,
+                  tl: {
+                    hd: rows,
+                    tl: /* [] */0
+                  }
+                }
+              })));
+  return Res_doc.group(Res_doc.concat({
+                  hd: printAttributes(te.ptyext_path.loc, undefined, te.ptyext_attributes, cmtTbl),
+                  tl: {
+                    hd: prefix,
+                    tl: {
+                      hd: name,
+                      tl: {
+                        hd: typeParams,
+                        tl: {
+                          hd: Res_doc.text(" +="),
+                          tl: {
+                            hd: extensionConstructors,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printModuleBinding(isRec, moduleBinding, cmtTbl, i) {
+  var prefix = i === 0 ? Res_doc.concat({
+          hd: Res_doc.text("module "),
+          tl: {
+            hd: isRec ? Res_doc.text("rec ") : Res_doc.nil,
+            tl: /* [] */0
+          }
+        }) : Res_doc.text("and ");
+  var modExpr = moduleBinding.pmb_expr;
+  var match = modExpr.pmod_desc;
+  var match$1;
+  match$1 = match.TAG === /* Pmod_constraint */4 ? [
+      printModExpr(match._0, cmtTbl),
+      Res_doc.concat({
+            hd: Res_doc.text(": "),
+            tl: {
+              hd: printModType(match._1, cmtTbl),
+              tl: /* [] */0
+            }
+          })
+    ] : [
+      printModExpr(modExpr, cmtTbl),
+      Res_doc.nil
+    ];
+  var doc = Res_doc.text(moduleBinding.pmb_name.txt);
+  var modName = printComments(doc, cmtTbl, moduleBinding.pmb_name.loc);
+  var doc$1 = Res_doc.concat({
+        hd: printAttributes(moduleBinding.pmb_name.loc, undefined, moduleBinding.pmb_attributes, cmtTbl),
+        tl: {
+          hd: prefix,
+          tl: {
+            hd: modName,
+            tl: {
+              hd: match$1[1],
+              tl: {
+                hd: Res_doc.text(" = "),
+                tl: {
+                  hd: match$1[0],
+                  tl: /* [] */0
+                }
+              }
+            }
+          }
+        }
+      });
+  return printComments(doc$1, cmtTbl, moduleBinding.pmb_loc);
+}
+
+function printModuleTypeDeclaration(modTypeDecl, cmtTbl) {
+  var doc = Res_doc.text(modTypeDecl.pmtd_name.txt);
+  var modName = printComments(doc, cmtTbl, modTypeDecl.pmtd_name.loc);
+  var modType = modTypeDecl.pmtd_type;
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, modTypeDecl.pmtd_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text("module type "),
+                tl: {
+                  hd: modName,
+                  tl: {
+                    hd: modType !== undefined ? Res_doc.concat({
+                            hd: Res_doc.text(" = "),
+                            tl: {
+                              hd: printModType(modType, cmtTbl),
+                              tl: /* [] */0
+                            }
+                          }) : Res_doc.nil,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printModType(modType, cmtTbl) {
+  var longident = modType.pmty_desc;
+  var modTypeDoc;
+  switch (longident.TAG | 0) {
+    case /* Pmty_ident */0 :
+        var longident$1 = longident._0;
+        modTypeDoc = Res_doc.concat({
+              hd: printAttributes(longident$1.loc, undefined, modType.pmty_attributes, cmtTbl),
+              tl: {
+                hd: printLongidentLocation(longident$1, cmtTbl),
+                tl: /* [] */0
+              }
+            });
+        break;
+    case /* Pmty_signature */1 :
+        var signature = longident._0;
+        if (signature) {
+          var signatureDoc = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: printSignature(signature, cmtTbl),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+          modTypeDoc = Res_doc.concat({
+                hd: printAttributes(undefined, undefined, modType.pmty_attributes, cmtTbl),
+                tl: {
+                  hd: signatureDoc,
+                  tl: /* [] */0
+                }
+              });
+        } else {
+          var shouldBreak = modType.pmty_loc.loc_start.pos_lnum < modType.pmty_loc.loc_end.pos_lnum;
+          modTypeDoc = Res_doc.breakableGroup(shouldBreak, Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printCommentsInside(cmtTbl, modType.pmty_loc),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+        }
+        break;
+    case /* Pmty_functor */2 :
+        var match = Res_parsetree_viewer.functorType(modType);
+        var returnType = match[1];
+        var parameters = match[0];
+        var parametersDoc;
+        var exit = 0;
+        if (parameters) {
+          var match$1 = parameters.hd;
+          var match$2 = match$1[1];
+          if (match$2.txt === "_") {
+            var modType$1 = match$1[2];
+            if (modType$1 !== undefined && !parameters.tl) {
+              var loc = match$2.loc;
+              var cmtLoc_loc_start = loc.loc_start;
+              var cmtLoc_loc_end = modType$1.pmty_loc.loc_end;
+              var cmtLoc_loc_ghost = loc.loc_ghost;
+              var cmtLoc = {
+                loc_start: cmtLoc_loc_start,
+                loc_end: cmtLoc_loc_end,
+                loc_ghost: cmtLoc_loc_ghost
+              };
+              var attrs = printAttributes(undefined, undefined, match$1[0], cmtTbl);
+              var doc = Res_doc.concat({
+                    hd: attrs,
+                    tl: {
+                      hd: printModType(modType$1, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  });
+              parametersDoc = printComments(doc, cmtTbl, cmtLoc);
+            } else {
+              exit = 1;
+            }
+          } else {
+            exit = 1;
+          }
+        } else {
+          parametersDoc = Res_doc.nil;
+        }
+        if (exit === 1) {
+          parametersDoc = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.comma,
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (param) {
+                                              var modType = param[2];
+                                              var lbl = param[1];
+                                              var cmtLoc;
+                                              if (modType !== undefined) {
+                                                var init = lbl.loc;
+                                                cmtLoc = {
+                                                  loc_start: init.loc_start,
+                                                  loc_end: modType.pmty_loc.loc_end,
+                                                  loc_ghost: init.loc_ghost
+                                                };
+                                              } else {
+                                                cmtLoc = lbl.loc;
+                                              }
+                                              var attrs = printAttributes(undefined, undefined, param[0], cmtTbl);
+                                              var lblDoc;
+                                              if (lbl.txt === "_" || lbl.txt === "*") {
+                                                lblDoc = Res_doc.nil;
+                                              } else {
+                                                var doc = Res_doc.text(lbl.txt);
+                                                lblDoc = printComments(doc, cmtTbl, lbl.loc);
+                                              }
+                                              var doc$1 = Res_doc.concat({
+                                                    hd: attrs,
+                                                    tl: {
+                                                      hd: lblDoc,
+                                                      tl: {
+                                                        hd: modType !== undefined ? Res_doc.concat({
+                                                                hd: lbl.txt === "_" ? Res_doc.nil : Res_doc.text(": "),
+                                                                tl: {
+                                                                  hd: printModType(modType, cmtTbl),
+                                                                  tl: /* [] */0
+                                                                }
+                                                              }) : Res_doc.nil,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }
+                                                  });
+                                              return printComments(doc$1, cmtTbl, cmtLoc);
+                                            }), parameters)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.trailingComma,
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+        }
+        var doc$1 = printModType(returnType, cmtTbl);
+        var returnDoc = Res_parens.modTypeFunctorReturn(returnType) ? addParens(doc$1) : doc$1;
+        modTypeDoc = Res_doc.group(Res_doc.concat({
+                  hd: parametersDoc,
+                  tl: {
+                    hd: Res_doc.group(Res_doc.concat({
+                              hd: Res_doc.text(" =>"),
+                              tl: {
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: returnDoc,
+                                  tl: /* [] */0
+                                }
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                }));
+        break;
+    case /* Pmty_with */3 :
+        var modType$2 = longident._0;
+        var doc$2 = printModType(modType$2, cmtTbl);
+        var operand = Res_parens.modTypeWithOperand(modType$2) ? addParens(doc$2) : doc$2;
+        modTypeDoc = Res_doc.group(Res_doc.concat({
+                  hd: operand,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.line,
+                              tl: {
+                                hd: printWithConstraints(longident._1, cmtTbl),
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                }));
+        break;
+    case /* Pmty_typeof */4 :
+        modTypeDoc = Res_doc.concat({
+              hd: Res_doc.text("module type of "),
+              tl: {
+                hd: printModExpr(longident._0, cmtTbl),
+                tl: /* [] */0
+              }
+            });
+        break;
+    case /* Pmty_extension */5 :
+        modTypeDoc = printExtension(false, longident._0, cmtTbl);
+        break;
+    case /* Pmty_alias */6 :
+        modTypeDoc = Res_doc.concat({
+              hd: Res_doc.text("module "),
+              tl: {
+                hd: printLongidentLocation(longident._0, cmtTbl),
+                tl: /* [] */0
+              }
+            });
+        break;
+    
+  }
+  var match$3 = modType.pmty_desc;
+  var attrsAlreadyPrinted;
+  switch (match$3.TAG | 0) {
+    case /* Pmty_ident */0 :
+    case /* Pmty_signature */1 :
+    case /* Pmty_functor */2 :
+        attrsAlreadyPrinted = true;
+        break;
+    default:
+      attrsAlreadyPrinted = false;
+  }
+  var doc$3 = Res_doc.concat({
+        hd: attrsAlreadyPrinted ? Res_doc.nil : printAttributes(undefined, undefined, modType.pmty_attributes, cmtTbl),
+        tl: {
+          hd: modTypeDoc,
+          tl: /* [] */0
+        }
+      });
+  return printComments(doc$3, cmtTbl, modType.pmty_loc);
+}
+
+function printWithConstraints(withConstraints, cmtTbl) {
+  var rows = List.mapi((function (i, withConstraint) {
+          return Res_doc.group(Res_doc.concat({
+                          hd: i === 0 ? Res_doc.text("with ") : Res_doc.text("and "),
+                          tl: {
+                            hd: printWithConstraint(withConstraint, cmtTbl),
+                            tl: /* [] */0
+                          }
+                        }));
+        }), withConstraints);
+  return Res_doc.join(Res_doc.line, rows);
+}
+
+function printWithConstraint(withConstraint, cmtTbl) {
+  switch (withConstraint.TAG | 0) {
+    case /* Pwith_type */0 :
+        return Res_doc.group(printTypeDeclaration(printLidentPath(withConstraint._0, cmtTbl), "=", Res_doc.nil, 0, withConstraint._1, Res_comments_table.empty));
+    case /* Pwith_module */1 :
+        return Res_doc.concat({
+                    hd: Res_doc.text("module "),
+                    tl: {
+                      hd: printLongident(withConstraint._0.txt),
+                      tl: {
+                        hd: Res_doc.text(" ="),
+                        tl: {
+                          hd: Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: printLongident(withConstraint._1.txt),
+                                      tl: /* [] */0
+                                    }
+                                  })),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+    case /* Pwith_typesubst */2 :
+        return Res_doc.group(printTypeDeclaration(printLidentPath(withConstraint._0, cmtTbl), ":=", Res_doc.nil, 0, withConstraint._1, Res_comments_table.empty));
+    case /* Pwith_modsubst */3 :
+        return Res_doc.concat({
+                    hd: Res_doc.text("module "),
+                    tl: {
+                      hd: printLongident(withConstraint._0.txt),
+                      tl: {
+                        hd: Res_doc.text(" :="),
+                        tl: {
+                          hd: Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: printLongident(withConstraint._1.txt),
+                                      tl: /* [] */0
+                                    }
+                                  })),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+    
+  }
+}
+
+function printSignature(signature, cmtTbl) {
+  if (signature) {
+    return printList((function (s) {
+                  return s.psig_loc;
+                }), signature, printSignatureItem, undefined, cmtTbl);
+  } else {
+    return printCommentsInside(cmtTbl, $$Location.none);
+  }
+}
+
+function printSignatureItem(si, cmtTbl) {
+  var valueDescription = si.psig_desc;
+  switch (valueDescription.TAG | 0) {
+    case /* Psig_value */0 :
+        return printValueDescription(valueDescription._0, cmtTbl);
+    case /* Psig_type */1 :
+        var recFlag = valueDescription._0 ? Res_doc.text("rec ") : Res_doc.nil;
+        return printTypeDeclarations(recFlag, valueDescription._1, cmtTbl);
+    case /* Psig_typext */2 :
+        return printTypeExtension(valueDescription._0, cmtTbl);
+    case /* Psig_exception */3 :
+        return printExceptionDef(valueDescription._0, cmtTbl);
+    case /* Psig_module */4 :
+        return printModuleDeclaration(valueDescription._0, cmtTbl);
+    case /* Psig_recmodule */5 :
+        return printRecModuleDeclarations(valueDescription._0, cmtTbl);
+    case /* Psig_modtype */6 :
+        return printModuleTypeDeclaration(valueDescription._0, cmtTbl);
+    case /* Psig_open */7 :
+        return printOpenDescription(valueDescription._0, cmtTbl);
+    case /* Psig_include */8 :
+        return printIncludeDescription(valueDescription._0, cmtTbl);
+    case /* Psig_class */9 :
+    case /* Psig_class_type */10 :
+        return Res_doc.nil;
+    case /* Psig_attribute */11 :
+        return Res_doc.concat({
+                    hd: Res_doc.text("@"),
+                    tl: {
+                      hd: printAttribute(valueDescription._0, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  });
+    case /* Psig_extension */12 :
+        return Res_doc.concat({
+                    hd: printAttributes(undefined, undefined, valueDescription._1, cmtTbl),
+                    tl: {
+                      hd: Res_doc.concat({
+                            hd: printExtension(true, valueDescription._0, cmtTbl),
+                            tl: /* [] */0
+                          }),
+                      tl: /* [] */0
+                    }
+                  });
+    
+  }
+}
+
+function printRecModuleDeclarations(moduleDeclarations, cmtTbl) {
+  return printListi((function (n) {
+                return n.pmd_loc;
+              }), moduleDeclarations, printRecModuleDeclaration, undefined, cmtTbl);
+}
+
+function printRecModuleDeclaration(md, cmtTbl, i) {
+  var longident = md.pmd_type.pmty_desc;
+  var body;
+  if (longident.TAG === /* Pmty_alias */6) {
+    body = Res_doc.concat({
+          hd: Res_doc.text(" = "),
+          tl: {
+            hd: printLongidentLocation(longident._0, cmtTbl),
+            tl: /* [] */0
+          }
+        });
+  } else {
+    var match = md.pmd_type.pmty_desc;
+    var needsParens;
+    needsParens = match.TAG === /* Pmty_with */3 ? true : false;
+    var doc = printModType(md.pmd_type, cmtTbl);
+    var modTypeDoc = needsParens ? addParens(doc) : doc;
+    body = Res_doc.concat({
+          hd: Res_doc.text(": "),
+          tl: {
+            hd: modTypeDoc,
+            tl: /* [] */0
+          }
+        });
+  }
+  var prefix = i < 1 ? "module rec " : "and ";
+  return Res_doc.concat({
+              hd: printAttributes(md.pmd_name.loc, undefined, md.pmd_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text(prefix),
+                tl: {
+                  hd: printComments(Res_doc.text(md.pmd_name.txt), cmtTbl, md.pmd_name.loc),
+                  tl: {
+                    hd: body,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printModuleDeclaration(md, cmtTbl) {
+  var longident = md.pmd_type.pmty_desc;
+  var body;
+  body = longident.TAG === /* Pmty_alias */6 ? Res_doc.concat({
+          hd: Res_doc.text(" = "),
+          tl: {
+            hd: printLongidentLocation(longident._0, cmtTbl),
+            tl: /* [] */0
+          }
+        }) : Res_doc.concat({
+          hd: Res_doc.text(": "),
+          tl: {
+            hd: printModType(md.pmd_type, cmtTbl),
+            tl: /* [] */0
+          }
+        });
+  return Res_doc.concat({
+              hd: printAttributes(md.pmd_name.loc, undefined, md.pmd_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text("module "),
+                tl: {
+                  hd: printComments(Res_doc.text(md.pmd_name.txt), cmtTbl, md.pmd_name.loc),
+                  tl: {
+                    hd: body,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printOpenDescription(openDescription, cmtTbl) {
+  var match = openDescription.popen_override;
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, openDescription.popen_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text("open"),
+                tl: {
+                  hd: match ? Res_doc.space : Res_doc.text("! "),
+                  tl: {
+                    hd: printLongidentLocation(openDescription.popen_lid, cmtTbl),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printIncludeDescription(includeDescription, cmtTbl) {
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, includeDescription.pincl_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text("include "),
+                tl: {
+                  hd: printModType(includeDescription.pincl_mod, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            });
+}
+
+function printIncludeDeclaration(includeDeclaration, cmtTbl) {
+  var includeDoc = printModExpr(includeDeclaration.pincl_mod, cmtTbl);
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, includeDeclaration.pincl_attributes, cmtTbl),
+              tl: {
+                hd: Res_doc.text("include "),
+                tl: {
+                  hd: Res_parens.includeModExpr(includeDeclaration.pincl_mod) ? addParens(includeDoc) : includeDoc,
+                  tl: /* [] */0
+                }
+              }
+            });
+}
+
+function printValueBindings(recFlag, vbs, cmtTbl) {
+  return printListi((function (vb) {
+                return vb.pvb_loc;
+              }), vbs, (function (param, param$1, param$2) {
+                return printValueBinding(recFlag, param, param$1, param$2);
+              }), undefined, cmtTbl);
+}
+
+function printValueDescription(valueDescription, cmtTbl) {
+  var match = valueDescription.pval_prim;
+  var isExternal = match ? true : false;
+  var attrs = printAttributes(valueDescription.pval_name.loc, undefined, valueDescription.pval_attributes, cmtTbl);
+  var header = isExternal ? "external " : "let ";
+  return Res_doc.group(Res_doc.concat({
+                  hd: attrs,
+                  tl: {
+                    hd: Res_doc.text(header),
+                    tl: {
+                      hd: printComments(printIdentLike(undefined, valueDescription.pval_name.txt), cmtTbl, valueDescription.pval_name.loc),
+                      tl: {
+                        hd: Res_doc.text(": "),
+                        tl: {
+                          hd: printTypExpr(valueDescription.pval_type, cmtTbl),
+                          tl: {
+                            hd: isExternal ? Res_doc.group(Res_doc.concat({
+                                        hd: Res_doc.text(" ="),
+                                        tl: {
+                                          hd: Res_doc.indent(Res_doc.concat({
+                                                    hd: Res_doc.line,
+                                                    tl: {
+                                                      hd: Res_doc.join(Res_doc.line, List.map((function (s) {
+                                                                  return Res_doc.concat({
+                                                                              hd: Res_doc.text("\""),
+                                                                              tl: {
+                                                                                hd: Res_doc.text(s),
+                                                                                tl: {
+                                                                                  hd: Res_doc.text("\""),
+                                                                                  tl: /* [] */0
+                                                                                }
+                                                                              }
+                                                                            });
+                                                                }), valueDescription.pval_prim)),
+                                                      tl: /* [] */0
+                                                    }
+                                                  })),
+                                          tl: /* [] */0
+                                        }
+                                      })) : Res_doc.nil,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printTypeDeclarations(recFlag, typeDeclarations, cmtTbl) {
+  return printListi((function (n) {
+                return n.ptype_loc;
+              }), typeDeclarations, (function (param, param$1, param$2) {
+                return printTypeDeclaration2(recFlag, param, param$1, param$2);
+              }), undefined, cmtTbl);
+}
+
+function printTypeDeclaration(name, equalSign, recFlag, i, td, cmtTbl) {
+  var attrs = printAttributes(td.ptype_loc, undefined, td.ptype_attributes, cmtTbl);
+  var prefix = i > 0 ? Res_doc.text("and ") : Res_doc.concat({
+          hd: Res_doc.text("type "),
+          tl: {
+            hd: recFlag,
+            tl: /* [] */0
+          }
+        });
+  var typeParams = printTypeParams(td.ptype_params, cmtTbl);
+  var lds = td.ptype_kind;
+  var manifestAndKind;
+  if (typeof lds === "number") {
+    if (lds === /* Ptype_abstract */0) {
+      var typ = td.ptype_manifest;
+      manifestAndKind = typ !== undefined ? Res_doc.concat({
+              hd: Res_doc.concat({
+                    hd: Res_doc.space,
+                    tl: {
+                      hd: Res_doc.text(equalSign),
+                      tl: {
+                        hd: Res_doc.space,
+                        tl: /* [] */0
+                      }
+                    }
+                  }),
+              tl: {
+                hd: printPrivateFlag(td.ptype_private),
+                tl: {
+                  hd: printTypExpr(typ, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            }) : Res_doc.nil;
+    } else {
+      manifestAndKind = Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printPrivateFlag(td.ptype_private),
+              tl: {
+                hd: Res_doc.text(".."),
+                tl: /* [] */0
+              }
+            }
+          });
+    }
+  } else if (lds.TAG === /* Ptype_variant */0) {
+    var typ$1 = td.ptype_manifest;
+    var manifest = typ$1 !== undefined ? Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printTypExpr(typ$1, cmtTbl),
+              tl: /* [] */0
+            }
+          }) : Res_doc.nil;
+    manifestAndKind = Res_doc.concat({
+          hd: manifest,
+          tl: {
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: /* [] */0
+                  }
+                }),
+            tl: {
+              hd: printConstructorDeclarations(td.ptype_private, lds._0, cmtTbl),
+              tl: /* [] */0
+            }
+          }
+        });
+  } else {
+    var typ$2 = td.ptype_manifest;
+    var manifest$1 = typ$2 !== undefined ? Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printTypExpr(typ$2, cmtTbl),
+              tl: /* [] */0
+            }
+          }) : Res_doc.nil;
+    manifestAndKind = Res_doc.concat({
+          hd: manifest$1,
+          tl: {
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printPrivateFlag(td.ptype_private),
+              tl: {
+                hd: printRecordDeclaration(lds._0, cmtTbl),
+                tl: /* [] */0
+              }
+            }
+          }
+        });
+  }
+  var constraints = printTypeDefinitionConstraints(td.ptype_cstrs);
+  return Res_doc.group(Res_doc.concat({
+                  hd: attrs,
+                  tl: {
+                    hd: prefix,
+                    tl: {
+                      hd: name,
+                      tl: {
+                        hd: typeParams,
+                        tl: {
+                          hd: manifestAndKind,
+                          tl: {
+                            hd: constraints,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printTypeDeclaration2(recFlag, td, cmtTbl, i) {
+  var doc = printIdentLike(undefined, td.ptype_name.txt);
+  var name = printComments(doc, cmtTbl, td.ptype_name.loc);
+  var equalSign = "=";
+  var attrs = printAttributes(td.ptype_loc, undefined, td.ptype_attributes, cmtTbl);
+  var prefix = i > 0 ? Res_doc.text("and ") : Res_doc.concat({
+          hd: Res_doc.text("type "),
+          tl: {
+            hd: recFlag,
+            tl: /* [] */0
+          }
+        });
+  var typeParams = printTypeParams(td.ptype_params, cmtTbl);
+  var lds = td.ptype_kind;
+  var manifestAndKind;
+  if (typeof lds === "number") {
+    if (lds === /* Ptype_abstract */0) {
+      var typ = td.ptype_manifest;
+      manifestAndKind = typ !== undefined ? Res_doc.concat({
+              hd: Res_doc.concat({
+                    hd: Res_doc.space,
+                    tl: {
+                      hd: Res_doc.text(equalSign),
+                      tl: {
+                        hd: Res_doc.space,
+                        tl: /* [] */0
+                      }
+                    }
+                  }),
+              tl: {
+                hd: printPrivateFlag(td.ptype_private),
+                tl: {
+                  hd: printTypExpr(typ, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            }) : Res_doc.nil;
+    } else {
+      manifestAndKind = Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printPrivateFlag(td.ptype_private),
+              tl: {
+                hd: Res_doc.text(".."),
+                tl: /* [] */0
+              }
+            }
+          });
+    }
+  } else if (lds.TAG === /* Ptype_variant */0) {
+    var typ$1 = td.ptype_manifest;
+    var manifest = typ$1 !== undefined ? Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printTypExpr(typ$1, cmtTbl),
+              tl: /* [] */0
+            }
+          }) : Res_doc.nil;
+    manifestAndKind = Res_doc.concat({
+          hd: manifest,
+          tl: {
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: /* [] */0
+                  }
+                }),
+            tl: {
+              hd: printConstructorDeclarations(td.ptype_private, lds._0, cmtTbl),
+              tl: /* [] */0
+            }
+          }
+        });
+  } else {
+    var typ$2 = td.ptype_manifest;
+    var manifest$1 = typ$2 !== undefined ? Res_doc.concat({
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printTypExpr(typ$2, cmtTbl),
+              tl: /* [] */0
+            }
+          }) : Res_doc.nil;
+    manifestAndKind = Res_doc.concat({
+          hd: manifest$1,
+          tl: {
+            hd: Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: Res_doc.text(equalSign),
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: /* [] */0
+                    }
+                  }
+                }),
+            tl: {
+              hd: printPrivateFlag(td.ptype_private),
+              tl: {
+                hd: printRecordDeclaration(lds._0, cmtTbl),
+                tl: /* [] */0
+              }
+            }
+          }
+        });
+  }
+  var constraints = printTypeDefinitionConstraints(td.ptype_cstrs);
+  return Res_doc.group(Res_doc.concat({
+                  hd: attrs,
+                  tl: {
+                    hd: prefix,
+                    tl: {
+                      hd: name,
+                      tl: {
+                        hd: typeParams,
+                        tl: {
+                          hd: manifestAndKind,
+                          tl: {
+                            hd: constraints,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printTypeDefinitionConstraints(cstrs) {
+  if (cstrs) {
+    return Res_doc.indent(Res_doc.group(Res_doc.concat({
+                        hd: Res_doc.line,
+                        tl: {
+                          hd: Res_doc.group(Res_doc.join(Res_doc.line, List.map(printTypeDefinitionConstraint, cstrs))),
+                          tl: /* [] */0
+                        }
+                      })));
+  } else {
+    return Res_doc.nil;
+  }
+}
+
+function printTypeDefinitionConstraint(param) {
+  return Res_doc.concat({
+              hd: Res_doc.text("constraint "),
+              tl: {
+                hd: printTypExpr(param[0], Res_comments_table.empty),
+                tl: {
+                  hd: Res_doc.text(" = "),
+                  tl: {
+                    hd: printTypExpr(param[1], Res_comments_table.empty),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printPrivateFlag(flag) {
+  if (flag) {
+    return Res_doc.nil;
+  } else {
+    return Res_doc.text("private ");
+  }
+}
+
+function printTypeParams(typeParams, cmtTbl) {
+  if (typeParams) {
+    return Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.lessThan,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.comma,
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (typeParam) {
+                                              var doc = printTypeParam(typeParam, cmtTbl);
+                                              return printComments(doc, cmtTbl, typeParam[0].ptyp_loc);
+                                            }), typeParams)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.trailingComma,
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.greaterThan,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+  } else {
+    return Res_doc.nil;
+  }
+}
+
+function printTypeParam(param, cmtTbl) {
+  var printedVariance;
+  switch (param[1]) {
+    case /* Covariant */0 :
+        printedVariance = Res_doc.text("+");
+        break;
+    case /* Contravariant */1 :
+        printedVariance = Res_doc.text("-");
+        break;
+    case /* Invariant */2 :
+        printedVariance = Res_doc.nil;
+        break;
+    
+  }
+  return Res_doc.concat({
+              hd: printedVariance,
+              tl: {
+                hd: printTypExpr(param[0], cmtTbl),
+                tl: /* [] */0
+              }
+            });
+}
+
+function printRecordDeclaration(lds, cmtTbl) {
+  var match = List.rev(lds);
+  var forceBreak = lds && match ? lds.hd.pld_loc.loc_start.pos_lnum < match.hd.pld_loc.loc_end.pos_lnum : false;
+  return Res_doc.breakableGroup(forceBreak, Res_doc.concat({
+                  hd: Res_doc.lbrace,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.softLine,
+                              tl: {
+                                hd: Res_doc.join(Res_doc.concat({
+                                          hd: Res_doc.comma,
+                                          tl: {
+                                            hd: Res_doc.line,
+                                            tl: /* [] */0
+                                          }
+                                        }), List.map((function (ld) {
+                                            var doc = printLabelDeclaration(ld, cmtTbl);
+                                            return printComments(doc, cmtTbl, ld.pld_loc);
+                                          }), lds)),
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: {
+                      hd: Res_doc.trailingComma,
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printConstructorDeclarations(privateFlag, cds, cmtTbl) {
+  var match = List.rev(cds);
+  var forceBreak = cds && match ? cds.hd.pcd_loc.loc_start.pos_lnum < match.hd.pcd_loc.loc_end.pos_lnum : false;
+  var privateFlag$1 = privateFlag ? Res_doc.nil : Res_doc.concat({
+          hd: Res_doc.text("private"),
+          tl: {
+            hd: Res_doc.line,
+            tl: /* [] */0
+          }
+        });
+  var rows = printListi((function (cd) {
+          return cd.pcd_loc;
+        }), cds, (function (cd, cmtTbl, i) {
+          var doc = printConstructorDeclaration2(i, cd, cmtTbl);
+          return printComments(doc, cmtTbl, cd.pcd_loc);
+        }), forceBreak, cmtTbl);
+  return Res_doc.breakableGroup(forceBreak, Res_doc.indent(Res_doc.concat({
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: privateFlag$1,
+                        tl: {
+                          hd: rows,
+                          tl: /* [] */0
+                        }
+                      }
+                    })));
+}
+
+function printConstructorDeclaration2(i, cd, cmtTbl) {
+  var attrs = printAttributes(undefined, undefined, cd.pcd_attributes, cmtTbl);
+  var bar = i > 0 || cd.pcd_attributes !== /* [] */0 ? Res_doc.text("| ") : Res_doc.ifBreaks(Res_doc.text("| "), Res_doc.nil);
+  var doc = Res_doc.text(cd.pcd_name.txt);
+  var constrName = printComments(doc, cmtTbl, cd.pcd_name.loc);
+  var constrArgs = printConstructorArguments(true, cd.pcd_args, cmtTbl);
+  var typ = cd.pcd_res;
+  var gadt = typ !== undefined ? Res_doc.indent(Res_doc.concat({
+              hd: Res_doc.text(": "),
+              tl: {
+                hd: printTypExpr(typ, cmtTbl),
+                tl: /* [] */0
+              }
+            })) : Res_doc.nil;
+  return Res_doc.concat({
+              hd: bar,
+              tl: {
+                hd: Res_doc.group(Res_doc.concat({
+                          hd: attrs,
+                          tl: {
+                            hd: constrName,
+                            tl: {
+                              hd: constrArgs,
+                              tl: {
+                                hd: gadt,
+                                tl: /* [] */0
+                              }
+                            }
+                          }
+                        })),
+                tl: /* [] */0
+              }
+            });
+}
+
+function printConstructorArguments(indent, cdArgs, cmtTbl) {
+  if (cdArgs.TAG === /* Pcstr_tuple */0) {
+    var types = cdArgs._0;
+    if (!types) {
+      return Res_doc.nil;
+    }
+    var args = Res_doc.concat({
+          hd: Res_doc.lparen,
+          tl: {
+            hd: Res_doc.indent(Res_doc.concat({
+                      hd: Res_doc.softLine,
+                      tl: {
+                        hd: Res_doc.join(Res_doc.concat({
+                                  hd: Res_doc.comma,
+                                  tl: {
+                                    hd: Res_doc.line,
+                                    tl: /* [] */0
+                                  }
+                                }), List.map((function (typexpr) {
+                                    return printTypExpr(typexpr, cmtTbl);
+                                  }), types)),
+                        tl: /* [] */0
+                      }
+                    })),
+            tl: {
+              hd: Res_doc.trailingComma,
+              tl: {
+                hd: Res_doc.softLine,
+                tl: {
+                  hd: Res_doc.rparen,
+                  tl: /* [] */0
+                }
+              }
+            }
+          }
+        });
+    return Res_doc.group(indent ? Res_doc.indent(args) : args);
+  }
+  var args$1 = Res_doc.concat({
+        hd: Res_doc.lparen,
+        tl: {
+          hd: Res_doc.lbrace,
+          tl: {
+            hd: Res_doc.indent(Res_doc.concat({
+                      hd: Res_doc.softLine,
+                      tl: {
+                        hd: Res_doc.join(Res_doc.concat({
+                                  hd: Res_doc.comma,
+                                  tl: {
+                                    hd: Res_doc.line,
+                                    tl: /* [] */0
+                                  }
+                                }), List.map((function (ld) {
+                                    var doc = printLabelDeclaration(ld, cmtTbl);
+                                    return printComments(doc, cmtTbl, ld.pld_loc);
+                                  }), cdArgs._0)),
+                        tl: /* [] */0
+                      }
+                    })),
+            tl: {
+              hd: Res_doc.trailingComma,
+              tl: {
+                hd: Res_doc.softLine,
+                tl: {
+                  hd: Res_doc.rbrace,
+                  tl: {
+                    hd: Res_doc.rparen,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            }
+          }
+        }
+      });
+  if (indent) {
+    return Res_doc.indent(args$1);
+  } else {
+    return args$1;
+  }
+}
+
+function printLabelDeclaration(ld, cmtTbl) {
+  var attrs = printAttributes(ld.pld_name.loc, undefined, ld.pld_attributes, cmtTbl);
+  var match = ld.pld_mutable;
+  var mutableFlag = match ? Res_doc.text("mutable ") : Res_doc.nil;
+  var doc = printIdentLike(undefined, ld.pld_name.txt);
+  var name = printComments(doc, cmtTbl, ld.pld_name.loc);
+  return Res_doc.group(Res_doc.concat({
+                  hd: attrs,
+                  tl: {
+                    hd: mutableFlag,
+                    tl: {
+                      hd: name,
+                      tl: {
+                        hd: Res_doc.text(": "),
+                        tl: {
+                          hd: printTypExpr(ld.pld_type, cmtTbl),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printTypExpr(typExpr, cmtTbl) {
+  var $$var = typExpr.ptyp_desc;
+  var renderedType;
+  if (typeof $$var === "number") {
+    renderedType = Res_doc.text("_");
+  } else {
+    switch ($$var.TAG | 0) {
+      case /* Ptyp_var */0 :
+          renderedType = Res_doc.concat({
+                hd: Res_doc.text("'"),
+                tl: {
+                  hd: printIdentLike(true, $$var._0),
+                  tl: /* [] */0
+                }
+              });
+          break;
+      case /* Ptyp_arrow */1 :
+          var match = Res_parsetree_viewer.arrowType(typExpr);
+          var returnType = match[2];
+          var args = match[1];
+          var attrsBefore = match[0];
+          var match$1 = returnType.ptyp_desc;
+          var returnTypeNeedsParens;
+          returnTypeNeedsParens = typeof match$1 === "number" || match$1.TAG !== /* Ptyp_alias */6 ? false : true;
+          var doc = printTypExpr(returnType, cmtTbl);
+          var returnDoc = returnTypeNeedsParens ? Res_doc.concat({
+                  hd: Res_doc.lparen,
+                  tl: {
+                    hd: doc,
+                    tl: {
+                      hd: Res_doc.rparen,
+                      tl: /* [] */0
+                    }
+                  }
+                }) : doc;
+          var match$2 = Res_parsetree_viewer.processUncurriedAttribute(attrsBefore);
+          var attrs = match$2[1];
+          var isUncurried = match$2[0];
+          var exit = 0;
+          if (args) {
+            var match$3 = args.hd;
+            if (match$3[0] || !(typeof match$3[1] === "number" && !(args.tl || isUncurried))) {
+              exit = 1;
+            } else {
+              var n = match$3[2];
+              var hasAttrsBefore = attrs !== /* [] */0;
+              var attrs$1 = hasAttrsBefore ? printAttributes(undefined, true, attrsBefore, cmtTbl) : Res_doc.nil;
+              var doc$1 = printTypExpr(n, cmtTbl);
+              var match$4 = n.ptyp_desc;
+              var typDoc;
+              if (typeof match$4 === "number") {
+                typDoc = doc$1;
+              } else {
+                switch (match$4.TAG | 0) {
+                  case /* Ptyp_arrow */1 :
+                  case /* Ptyp_tuple */2 :
+                  case /* Ptyp_alias */6 :
+                      typDoc = addParens(doc$1);
+                      break;
+                  default:
+                    typDoc = doc$1;
+                }
+              }
+              renderedType = Res_doc.group(Res_doc.concat({
+                        hd: Res_doc.group(attrs$1),
+                        tl: {
+                          hd: Res_doc.group(hasAttrsBefore ? Res_doc.concat({
+                                      hd: Res_doc.lparen,
+                                      tl: {
+                                        hd: Res_doc.indent(Res_doc.concat({
+                                                  hd: Res_doc.softLine,
+                                                  tl: {
+                                                    hd: typDoc,
+                                                    tl: {
+                                                      hd: Res_doc.text(" => "),
+                                                      tl: {
+                                                        hd: returnDoc,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }
+                                                  }
+                                                })),
+                                        tl: {
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.rparen,
+                                            tl: /* [] */0
+                                          }
+                                        }
+                                      }
+                                    }) : Res_doc.concat({
+                                      hd: typDoc,
+                                      tl: {
+                                        hd: Res_doc.text(" => "),
+                                        tl: {
+                                          hd: returnDoc,
+                                          tl: /* [] */0
+                                        }
+                                      }
+                                    })),
+                          tl: /* [] */0
+                        }
+                      }));
+            }
+          } else {
+            renderedType = Res_doc.nil;
+          }
+          if (exit === 1) {
+            var attrs$2 = printAttributes(undefined, true, attrs, cmtTbl);
+            var renderedArgs = Res_doc.concat({
+                  hd: attrs$2,
+                  tl: {
+                    hd: Res_doc.text("("),
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: isUncurried ? Res_doc.concat({
+                                          hd: Res_doc.dot,
+                                          tl: {
+                                            hd: Res_doc.space,
+                                            tl: /* [] */0
+                                          }
+                                        }) : Res_doc.nil,
+                                  tl: {
+                                    hd: Res_doc.join(Res_doc.concat({
+                                              hd: Res_doc.comma,
+                                              tl: {
+                                                hd: Res_doc.line,
+                                                tl: /* [] */0
+                                              }
+                                            }), List.map((function (tp) {
+                                                return printTypeParameter(tp, cmtTbl);
+                                              }), args)),
+                                    tl: /* [] */0
+                                  }
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.trailingComma,
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.text(")"),
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }
+                });
+            renderedType = Res_doc.group(Res_doc.concat({
+                      hd: renderedArgs,
+                      tl: {
+                        hd: Res_doc.text(" => "),
+                        tl: {
+                          hd: returnDoc,
+                          tl: /* [] */0
+                        }
+                      }
+                    }));
+          }
+          break;
+      case /* Ptyp_tuple */2 :
+          renderedType = printTupleType(false, $$var._0, cmtTbl);
+          break;
+      case /* Ptyp_constr */3 :
+          var constrArgs = $$var._1;
+          var longidentLoc = $$var._0;
+          var exit$1 = 0;
+          if (constrArgs) {
+            var tuple = constrArgs.hd.ptyp_desc;
+            if (typeof tuple === "number") {
+              exit$1 = 1;
+            } else {
+              switch (tuple.TAG | 0) {
+                case /* Ptyp_tuple */2 :
+                    if (constrArgs.tl) {
+                      exit$1 = 1;
+                    } else {
+                      var constrName = printLidentPath(longidentLoc, cmtTbl);
+                      renderedType = Res_doc.group(Res_doc.concat({
+                                hd: constrName,
+                                tl: {
+                                  hd: Res_doc.lessThan,
+                                  tl: {
+                                    hd: printTupleType(true, tuple._0, cmtTbl),
+                                    tl: {
+                                      hd: Res_doc.greaterThan,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }));
+                    }
+                    break;
+                case /* Ptyp_object */4 :
+                    if (constrArgs.tl) {
+                      exit$1 = 1;
+                    } else {
+                      var constrName$1 = printLidentPath(longidentLoc, cmtTbl);
+                      renderedType = Res_doc.concat({
+                            hd: constrName$1,
+                            tl: {
+                              hd: Res_doc.lessThan,
+                              tl: {
+                                hd: printObject(true, tuple._0, tuple._1, cmtTbl),
+                                tl: {
+                                  hd: Res_doc.greaterThan,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          });
+                    }
+                    break;
+                default:
+                  exit$1 = 1;
+              }
+            }
+          } else {
+            exit$1 = 1;
+          }
+          if (exit$1 === 1) {
+            var constrName$2 = printLidentPath(longidentLoc, cmtTbl);
+            renderedType = constrArgs ? Res_doc.group(Res_doc.concat({
+                        hd: constrName$2,
+                        tl: {
+                          hd: Res_doc.lessThan,
+                          tl: {
+                            hd: Res_doc.indent(Res_doc.concat({
+                                      hd: Res_doc.softLine,
+                                      tl: {
+                                        hd: Res_doc.join(Res_doc.concat({
+                                                  hd: Res_doc.comma,
+                                                  tl: {
+                                                    hd: Res_doc.line,
+                                                    tl: /* [] */0
+                                                  }
+                                                }), List.map((function (typexpr) {
+                                                    return printTypExpr(typexpr, cmtTbl);
+                                                  }), constrArgs)),
+                                        tl: /* [] */0
+                                      }
+                                    })),
+                            tl: {
+                              hd: Res_doc.trailingComma,
+                              tl: {
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.greaterThan,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          }
+                        }
+                      })) : constrName$2;
+          }
+          break;
+      case /* Ptyp_object */4 :
+          renderedType = printObject(false, $$var._0, $$var._1, cmtTbl);
+          break;
+      case /* Ptyp_class */5 :
+          renderedType = Res_doc.text("classes are not supported in types");
+          break;
+      case /* Ptyp_alias */6 :
+          var typ = $$var._0;
+          var match$5 = typ.ptyp_desc;
+          var needsParens;
+          needsParens = typeof match$5 === "number" || match$5.TAG !== /* Ptyp_arrow */1 ? false : true;
+          var doc$2 = printTypExpr(typ, cmtTbl);
+          var typ$1 = needsParens ? Res_doc.concat({
+                  hd: Res_doc.lparen,
+                  tl: {
+                    hd: doc$2,
+                    tl: {
+                      hd: Res_doc.rparen,
+                      tl: /* [] */0
+                    }
+                  }
+                }) : doc$2;
+          renderedType = Res_doc.concat({
+                hd: typ$1,
+                tl: {
+                  hd: Res_doc.text(" as "),
+                  tl: {
+                    hd: Res_doc.concat({
+                          hd: Res_doc.text("'"),
+                          tl: {
+                            hd: printIdentLike(undefined, $$var._1),
+                            tl: /* [] */0
+                          }
+                        }),
+                    tl: /* [] */0
+                  }
+                }
+              });
+          break;
+      case /* Ptyp_variant */7 :
+          var labelsOpt = $$var._2;
+          var forceBreak = typExpr.ptyp_loc.loc_start.pos_lnum < typExpr.ptyp_loc.loc_end.pos_lnum;
+          var printRowField = function (x) {
+            if (x.TAG !== /* Rtag */0) {
+              return printTypExpr(x._0, cmtTbl);
+            }
+            var truth = x._2;
+            var attrs = x._1;
+            var txt = x._0.txt;
+            if (truth && !x._3) {
+              return Res_doc.group(Res_doc.concat({
+                              hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                              tl: {
+                                hd: Res_doc.concat({
+                                      hd: Res_doc.text("#"),
+                                      tl: {
+                                        hd: printPolyVarIdent(txt),
+                                        tl: /* [] */0
+                                      }
+                                    }),
+                                tl: /* [] */0
+                              }
+                            }));
+            }
+            var doType = function (t) {
+              var match = t.ptyp_desc;
+              if (typeof match !== "number" && match.TAG === /* Ptyp_tuple */2) {
+                return printTypExpr(t, cmtTbl);
+              }
+              return Res_doc.concat({
+                          hd: Res_doc.lparen,
+                          tl: {
+                            hd: printTypExpr(t, cmtTbl),
+                            tl: {
+                              hd: Res_doc.rparen,
+                              tl: /* [] */0
+                            }
+                          }
+                        });
+            };
+            var printedTypes = List.map(doType, x._3);
+            var cases = Res_doc.join(Res_doc.concat({
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: Res_doc.text("& "),
+                        tl: /* [] */0
+                      }
+                    }), printedTypes);
+            var cases$1 = truth ? Res_doc.concat({
+                    hd: Res_doc.line,
+                    tl: {
+                      hd: Res_doc.text("& "),
+                      tl: {
+                        hd: cases,
+                        tl: /* [] */0
+                      }
+                    }
+                  }) : cases;
+            return Res_doc.group(Res_doc.concat({
+                            hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                            tl: {
+                              hd: Res_doc.concat({
+                                    hd: Res_doc.text("#"),
+                                    tl: {
+                                      hd: printPolyVarIdent(txt),
+                                      tl: /* [] */0
+                                    }
+                                  }),
+                              tl: {
+                                hd: cases$1,
+                                tl: /* [] */0
+                              }
+                            }
+                          }));
+          };
+          var docs = List.map(printRowField, $$var._0);
+          var cases = Res_doc.join(Res_doc.concat({
+                    hd: Res_doc.line,
+                    tl: {
+                      hd: Res_doc.text("| "),
+                      tl: /* [] */0
+                    }
+                  }), docs);
+          var cases$1 = docs === /* [] */0 ? cases : Res_doc.concat({
+                  hd: Res_doc.ifBreaks(Res_doc.text("| "), Res_doc.nil),
+                  tl: {
+                    hd: cases,
+                    tl: /* [] */0
+                  }
+                });
+          var openingSymbol = $$var._1 === /* Open */1 ? Res_doc.concat({
+                  hd: Res_doc.greaterThan,
+                  tl: {
+                    hd: Res_doc.line,
+                    tl: /* [] */0
+                  }
+                }) : (
+              labelsOpt === undefined ? Res_doc.softLine : Res_doc.concat({
+                      hd: Res_doc.lessThan,
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: /* [] */0
+                      }
+                    })
+            );
+          var labels = labelsOpt !== undefined && labelsOpt ? Res_doc.concat(List.map((function (label) {
+                        return Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: Res_doc.text("#"),
+                                      tl: {
+                                        hd: printPolyVarIdent(label),
+                                        tl: /* [] */0
+                                      }
+                                    }
+                                  });
+                      }), labelsOpt)) : Res_doc.nil;
+          var closingSymbol = labelsOpt !== undefined && labelsOpt ? Res_doc.text(" >") : Res_doc.nil;
+          renderedType = Res_doc.breakableGroup(forceBreak, Res_doc.concat({
+                    hd: Res_doc.lbracket,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: openingSymbol,
+                                tl: {
+                                  hd: cases$1,
+                                  tl: {
+                                    hd: closingSymbol,
+                                    tl: {
+                                      hd: labels,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rbracket,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Ptyp_poly */8 :
+          var stringLocs = $$var._0;
+          renderedType = stringLocs ? Res_doc.concat({
+                  hd: Res_doc.join(Res_doc.space, List.map((function (param) {
+                              var doc = Res_doc.concat({
+                                    hd: Res_doc.text("'"),
+                                    tl: {
+                                      hd: Res_doc.text(param.txt),
+                                      tl: /* [] */0
+                                    }
+                                  });
+                              return printComments(doc, cmtTbl, param.loc);
+                            }), stringLocs)),
+                  tl: {
+                    hd: Res_doc.dot,
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: {
+                        hd: printTypExpr($$var._1, cmtTbl),
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }) : printTypExpr($$var._1, cmtTbl);
+          break;
+      case /* Ptyp_package */9 :
+          renderedType = printPackageType(true, $$var._0, cmtTbl);
+          break;
+      case /* Ptyp_extension */10 :
+          renderedType = printExtension(false, $$var._0, cmtTbl);
+          break;
+      
+    }
+  }
+  var match$6 = typExpr.ptyp_desc;
+  var shouldPrintItsOwnAttributes;
+  shouldPrintItsOwnAttributes = typeof match$6 === "number" || match$6.TAG !== /* Ptyp_arrow */1 ? false : true;
+  var attrs$3 = typExpr.ptyp_attributes;
+  var doc$3 = attrs$3 && !shouldPrintItsOwnAttributes ? Res_doc.group(Res_doc.concat({
+              hd: printAttributes(undefined, undefined, attrs$3, cmtTbl),
+              tl: {
+                hd: renderedType,
+                tl: /* [] */0
+              }
+            })) : renderedType;
+  return printComments(doc$3, cmtTbl, typExpr.ptyp_loc);
+}
+
+function printObject(inline, fields, openFlag, cmtTbl) {
+  var doc;
+  if (fields) {
+    var tmp;
+    tmp = openFlag ? (
+        fields && fields.hd.TAG !== /* Otag */0 ? Res_doc.text(".. ") : Res_doc.dotdot
+      ) : Res_doc.nil;
+    doc = Res_doc.concat({
+          hd: Res_doc.lbrace,
+          tl: {
+            hd: tmp,
+            tl: {
+              hd: Res_doc.indent(Res_doc.concat({
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.join(Res_doc.concat({
+                                    hd: Res_doc.comma,
+                                    tl: {
+                                      hd: Res_doc.line,
+                                      tl: /* [] */0
+                                    }
+                                  }), List.map((function (field) {
+                                      return printObjectField(field, cmtTbl);
+                                    }), fields)),
+                          tl: /* [] */0
+                        }
+                      })),
+              tl: {
+                hd: Res_doc.trailingComma,
+                tl: {
+                  hd: Res_doc.softLine,
+                  tl: {
+                    hd: Res_doc.rbrace,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            }
+          }
+        });
+  } else {
+    doc = Res_doc.concat({
+          hd: Res_doc.lbrace,
+          tl: {
+            hd: openFlag ? Res_doc.dotdot : Res_doc.dot,
+            tl: {
+              hd: Res_doc.rbrace,
+              tl: /* [] */0
+            }
+          }
+        });
+  }
+  if (inline) {
+    return doc;
+  } else {
+    return Res_doc.group(doc);
+  }
+}
+
+function printTupleType(inline, types, cmtTbl) {
+  var tuple = Res_doc.concat({
+        hd: Res_doc.lparen,
+        tl: {
+          hd: Res_doc.indent(Res_doc.concat({
+                    hd: Res_doc.softLine,
+                    tl: {
+                      hd: Res_doc.join(Res_doc.concat({
+                                hd: Res_doc.comma,
+                                tl: {
+                                  hd: Res_doc.line,
+                                  tl: /* [] */0
+                                }
+                              }), List.map((function (typexpr) {
+                                  return printTypExpr(typexpr, cmtTbl);
+                                }), types)),
+                      tl: /* [] */0
+                    }
+                  })),
+          tl: {
+            hd: Res_doc.trailingComma,
+            tl: {
+              hd: Res_doc.softLine,
+              tl: {
+                hd: Res_doc.rparen,
+                tl: /* [] */0
+              }
+            }
+          }
+        }
+      });
+  if (inline === false) {
+    return Res_doc.group(tuple);
+  } else {
+    return tuple;
+  }
+}
+
+function printObjectField(field, cmtTbl) {
+  if (field.TAG !== /* Otag */0) {
+    return Res_doc.concat({
+                hd: Res_doc.dotdotdot,
+                tl: {
+                  hd: printTypExpr(field._0, cmtTbl),
+                  tl: /* [] */0
+                }
+              });
+  }
+  var typ = field._2;
+  var labelLoc = field._0;
+  var doc = Res_doc.text("\"" + (labelLoc.txt + "\""));
+  var lbl = printComments(doc, cmtTbl, labelLoc.loc);
+  var doc$1 = Res_doc.concat({
+        hd: printAttributes(labelLoc.loc, undefined, field._1, cmtTbl),
+        tl: {
+          hd: lbl,
+          tl: {
+            hd: Res_doc.text(": "),
+            tl: {
+              hd: printTypExpr(typ, cmtTbl),
+              tl: /* [] */0
+            }
+          }
+        }
+      });
+  var init = labelLoc.loc;
+  var cmtLoc_loc_start = init.loc_start;
+  var cmtLoc_loc_end = typ.ptyp_loc.loc_end;
+  var cmtLoc_loc_ghost = init.loc_ghost;
+  var cmtLoc = {
+    loc_start: cmtLoc_loc_start,
+    loc_end: cmtLoc_loc_end,
+    loc_ghost: cmtLoc_loc_ghost
+  };
+  return printComments(doc$1, cmtTbl, cmtLoc);
+}
+
+function printTypeParameter(param, cmtTbl) {
+  var typ = param[2];
+  var lbl = param[1];
+  var match = Res_parsetree_viewer.processUncurriedAttribute(param[0]);
+  var uncurried = match[0] ? Res_doc.concat({
+          hd: Res_doc.dot,
+          tl: {
+            hd: Res_doc.space,
+            tl: /* [] */0
+          }
+        }) : Res_doc.nil;
+  var attrs = printAttributes(undefined, undefined, match[1], cmtTbl);
+  var label;
+  label = typeof lbl === "number" ? Res_doc.nil : Res_doc.concat({
+          hd: Res_doc.text("~"),
+          tl: {
+            hd: printIdentLike(undefined, lbl._0),
+            tl: {
+              hd: Res_doc.text(": "),
+              tl: /* [] */0
+            }
+          }
+        });
+  var optionalIndicator;
+  optionalIndicator = typeof lbl === "number" || lbl.TAG === /* Labelled */0 ? Res_doc.nil : Res_doc.text("=?");
+  var match$1 = typ.ptyp_attributes;
+  var match$2;
+  if (match$1) {
+    var match$3 = match$1.hd[0];
+    if (match$3.txt === "ns.namedArgLoc") {
+      var loc = match$3.loc;
+      match$2 = [
+        {
+          loc_start: loc.loc_start,
+          loc_end: typ.ptyp_loc.loc_end,
+          loc_ghost: loc.loc_ghost
+        },
+        {
+          ptyp_desc: typ.ptyp_desc,
+          ptyp_loc: typ.ptyp_loc,
+          ptyp_attributes: match$1.tl
+        }
+      ];
+    } else {
+      match$2 = [
+        typ.ptyp_loc,
+        typ
+      ];
+    }
+  } else {
+    match$2 = [
+      typ.ptyp_loc,
+      typ
+    ];
+  }
+  var doc = Res_doc.group(Res_doc.concat({
+            hd: uncurried,
+            tl: {
+              hd: attrs,
+              tl: {
+                hd: label,
+                tl: {
+                  hd: printTypExpr(match$2[1], cmtTbl),
+                  tl: {
+                    hd: optionalIndicator,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            }
+          }));
+  return printComments(doc, cmtTbl, match$2[0]);
+}
+
+function printValueBinding(recFlag, vb, cmtTbl, i) {
+  var attrs = printAttributes(vb.pvb_pat.ppat_loc, undefined, vb.pvb_attributes, cmtTbl);
+  var header = i === 0 ? Res_doc.concat({
+          hd: Res_doc.text("let "),
+          tl: {
+            hd: recFlag,
+            tl: /* [] */0
+          }
+        }) : Res_doc.text("and ");
+  var match = vb.pvb_pat.ppat_desc;
+  var exit = 0;
+  if (typeof match === "number" || match.TAG !== /* Ppat_constraint */10) {
+    exit = 1;
+  } else {
+    var patTyp = match._1;
+    var tmp = patTyp.ptyp_desc;
+    if (typeof tmp === "number" || tmp.TAG !== /* Ptyp_poly */8) {
+      exit = 1;
+    } else {
+      var expr = vb.pvb_expr;
+      var tmp$1 = expr.pexp_desc;
+      if (typeof tmp$1 === "number" || tmp$1.TAG !== /* Pexp_newtype */31) {
+        exit = 1;
+      } else {
+        var pattern = match._0;
+        var match$1 = Res_parsetree_viewer.funExpr(expr);
+        var parameters = match$1[1];
+        var abstractType;
+        if (parameters) {
+          var match$2 = parameters.hd;
+          abstractType = match$2.TAG === /* Parameter */0 || parameters.tl ? Res_doc.nil : Res_doc.concat({
+                  hd: Res_doc.text("type "),
+                  tl: {
+                    hd: Res_doc.join(Res_doc.space, List.map((function ($$var) {
+                                return Res_doc.text($$var.txt);
+                              }), match$2.locs)),
+                    tl: {
+                      hd: Res_doc.dot,
+                      tl: /* [] */0
+                    }
+                  }
+                });
+        } else {
+          abstractType = Res_doc.nil;
+        }
+        var match$3 = match$1[2].pexp_desc;
+        var exit$1 = 0;
+        if (typeof match$3 === "number") {
+          exit$1 = 2;
+        } else {
+          if (match$3.TAG === /* Pexp_constraint */19) {
+            return Res_doc.group(Res_doc.concat({
+                            hd: attrs,
+                            tl: {
+                              hd: header,
+                              tl: {
+                                hd: printPattern(pattern, cmtTbl),
+                                tl: {
+                                  hd: Res_doc.text(":"),
+                                  tl: {
+                                    hd: Res_doc.indent(Res_doc.concat({
+                                              hd: Res_doc.line,
+                                              tl: {
+                                                hd: abstractType,
+                                                tl: {
+                                                  hd: Res_doc.space,
+                                                  tl: {
+                                                    hd: printTypExpr(match$3._1, cmtTbl),
+                                                    tl: {
+                                                      hd: Res_doc.text(" ="),
+                                                      tl: {
+                                                        hd: Res_doc.concat({
+                                                              hd: Res_doc.line,
+                                                              tl: {
+                                                                hd: printExpressionWithComments(match$3._0, cmtTbl),
+                                                                tl: /* [] */0
+                                                              }
+                                                            }),
+                                                        tl: /* [] */0
+                                                      }
+                                                    }
+                                                  }
+                                                }
+                                              }
+                                            })),
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            }
+                          }));
+          }
+          exit$1 = 2;
+        }
+        if (exit$1 === 2) {
+          return Res_doc.group(Res_doc.concat({
+                          hd: attrs,
+                          tl: {
+                            hd: header,
+                            tl: {
+                              hd: printPattern(pattern, cmtTbl),
+                              tl: {
+                                hd: Res_doc.text(":"),
+                                tl: {
+                                  hd: Res_doc.indent(Res_doc.concat({
+                                            hd: Res_doc.line,
+                                            tl: {
+                                              hd: abstractType,
+                                              tl: {
+                                                hd: Res_doc.space,
+                                                tl: {
+                                                  hd: printTypExpr(patTyp, cmtTbl),
+                                                  tl: {
+                                                    hd: Res_doc.text(" ="),
+                                                    tl: {
+                                                      hd: Res_doc.concat({
+                                                            hd: Res_doc.line,
+                                                            tl: {
+                                                              hd: printExpressionWithComments(expr, cmtTbl),
+                                                              tl: /* [] */0
+                                                            }
+                                                          }),
+                                                      tl: /* [] */0
+                                                    }
+                                                  }
+                                                }
+                                              }
+                                            }
+                                          })),
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          }
+                        }));
+        }
+        
+      }
+    }
+  }
+  if (exit === 1) {
+    var match$4 = Res_parsetree_viewer.processBracesAttr(vb.pvb_expr);
+    var expr$1 = match$4[1];
+    var doc = printExpressionWithComments(vb.pvb_expr, cmtTbl);
+    var braces = Res_parens.expr(vb.pvb_expr);
+    var printedExpr = typeof braces === "number" ? (
+        braces !== 0 ? doc : addParens(doc)
+      ) : printBraces(doc, expr$1, braces._0);
+    var patternDoc = printPattern(vb.pvb_pat, cmtTbl);
+    if (Res_parsetree_viewer.isSinglePipeExpr(vb.pvb_expr)) {
+      return Res_doc.customLayout({
+                  hd: Res_doc.group(Res_doc.concat({
+                            hd: attrs,
+                            tl: {
+                              hd: header,
+                              tl: {
+                                hd: patternDoc,
+                                tl: {
+                                  hd: Res_doc.text(" ="),
+                                  tl: {
+                                    hd: Res_doc.space,
+                                    tl: {
+                                      hd: printedExpr,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          })),
+                  tl: {
+                    hd: Res_doc.group(Res_doc.concat({
+                              hd: attrs,
+                              tl: {
+                                hd: header,
+                                tl: {
+                                  hd: patternDoc,
+                                  tl: {
+                                    hd: Res_doc.text(" ="),
+                                    tl: {
+                                      hd: Res_doc.indent(Res_doc.concat({
+                                                hd: Res_doc.line,
+                                                tl: {
+                                                  hd: printedExpr,
+                                                  tl: /* [] */0
+                                                }
+                                              })),
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            })),
+                    tl: /* [] */0
+                  }
+                });
+    }
+    var shouldIndent;
+    if (match$4[0] !== undefined) {
+      shouldIndent = false;
+    } else if (Res_parsetree_viewer.isBinaryExpression(expr$1)) {
+      shouldIndent = true;
+    } else {
+      var e = vb.pvb_expr;
+      var match$5 = e.pexp_desc;
+      var tmp$2;
+      var exit$2 = 0;
+      if (typeof match$5 === "number") {
+        exit$2 = 2;
+      } else {
+        switch (match$5.TAG | 0) {
+          case /* Pexp_ifthenelse */15 :
+              var match$6 = e.pexp_attributes;
+              if (match$6) {
+                var ifExpr = match$5._0;
+                if (match$6.hd[0].txt === "ns.ternary" && !match$6.tl) {
+                  tmp$2 = Res_parsetree_viewer.isBinaryExpression(ifExpr) || Res_parsetree_viewer.hasAttributes(ifExpr.pexp_attributes);
+                } else {
+                  exit$2 = 2;
+                }
+              } else {
+                exit$2 = 2;
+              }
+              break;
+          case /* Pexp_newtype */31 :
+              tmp$2 = false;
+              break;
+          default:
+            exit$2 = 2;
+        }
+      }
+      if (exit$2 === 2) {
+        tmp$2 = Res_parsetree_viewer.hasAttributes(e.pexp_attributes) || Res_parsetree_viewer.isArrayAccess(e);
+      }
+      shouldIndent = tmp$2;
+    }
+    return Res_doc.group(Res_doc.concat({
+                    hd: attrs,
+                    tl: {
+                      hd: header,
+                      tl: {
+                        hd: patternDoc,
+                        tl: {
+                          hd: Res_doc.text(" ="),
+                          tl: {
+                            hd: shouldIndent ? Res_doc.indent(Res_doc.concat({
+                                        hd: Res_doc.line,
+                                        tl: {
+                                          hd: printedExpr,
+                                          tl: /* [] */0
+                                        }
+                                      })) : Res_doc.concat({
+                                    hd: Res_doc.space,
+                                    tl: {
+                                      hd: printedExpr,
+                                      tl: /* [] */0
+                                    }
+                                  }),
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+  }
+  
+}
+
+function printPackageType(printModuleKeywordAndParens, packageType, cmtTbl) {
+  var packageConstraints = packageType[1];
+  var longidentLoc = packageType[0];
+  var doc = packageConstraints ? Res_doc.group(Res_doc.concat({
+              hd: printLongidentLocation(longidentLoc, cmtTbl),
+              tl: {
+                hd: printPackageConstraints(packageConstraints, cmtTbl),
+                tl: {
+                  hd: Res_doc.softLine,
+                  tl: /* [] */0
+                }
+              }
+            })) : Res_doc.group(Res_doc.concat({
+              hd: printLongidentLocation(longidentLoc, cmtTbl),
+              tl: /* [] */0
+            }));
+  if (printModuleKeywordAndParens) {
+    return Res_doc.concat({
+                hd: Res_doc.text("module("),
+                tl: {
+                  hd: doc,
+                  tl: {
+                    hd: Res_doc.rparen,
+                    tl: /* [] */0
+                  }
+                }
+              });
+  } else {
+    return doc;
+  }
+}
+
+function printPackageConstraints(packageConstraints, cmtTbl) {
+  return Res_doc.concat({
+              hd: Res_doc.text(" with"),
+              tl: {
+                hd: Res_doc.indent(Res_doc.concat({
+                          hd: Res_doc.line,
+                          tl: {
+                            hd: Res_doc.join(Res_doc.line, List.mapi((function (i, pc) {
+                                        var init = pc[0].loc;
+                                        var cmtLoc_loc_start = init.loc_start;
+                                        var cmtLoc_loc_end = pc[1].ptyp_loc.loc_end;
+                                        var cmtLoc_loc_ghost = init.loc_ghost;
+                                        var cmtLoc = {
+                                          loc_start: cmtLoc_loc_start,
+                                          loc_end: cmtLoc_loc_end,
+                                          loc_ghost: cmtLoc_loc_ghost
+                                        };
+                                        var doc = printPackageConstraint(i, cmtTbl, pc);
+                                        return printComments(doc, cmtTbl, cmtLoc);
+                                      }), packageConstraints)),
+                            tl: /* [] */0
+                          }
+                        })),
+                tl: /* [] */0
+              }
+            });
+}
+
+function printPackageConstraint(i, cmtTbl, param) {
+  var prefix = i === 0 ? Res_doc.text("type ") : Res_doc.text("and type ");
+  return Res_doc.concat({
+              hd: prefix,
+              tl: {
+                hd: printLongidentLocation(param[0], cmtTbl),
+                tl: {
+                  hd: Res_doc.text(" = "),
+                  tl: {
+                    hd: printTypExpr(param[1], cmtTbl),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printExtension(atModuleLvl, param, cmtTbl) {
+  var stringLoc = param[0];
+  var txt = convertBsExtension(stringLoc.txt);
+  var doc = Res_doc.concat({
+        hd: Res_doc.text("%"),
+        tl: {
+          hd: atModuleLvl ? Res_doc.text("%") : Res_doc.nil,
+          tl: {
+            hd: Res_doc.text(txt),
+            tl: /* [] */0
+          }
+        }
+      });
+  var extName = printComments(doc, cmtTbl, stringLoc.loc);
+  return Res_doc.group(Res_doc.concat({
+                  hd: extName,
+                  tl: {
+                    hd: printPayload(param[1], cmtTbl),
+                    tl: /* [] */0
+                  }
+                }));
+}
+
+function printPattern(p, cmtTbl) {
+  var $$var = p.ppat_desc;
+  var patternWithoutAttributes;
+  if (typeof $$var === "number") {
+    patternWithoutAttributes = Res_doc.text("_");
+  } else {
+    switch ($$var.TAG | 0) {
+      case /* Ppat_var */0 :
+          patternWithoutAttributes = printIdentLike(undefined, $$var._0.txt);
+          break;
+      case /* Ppat_alias */1 :
+          var p$1 = $$var._0;
+          var match = p$1.ppat_desc;
+          var needsParens;
+          if (typeof match === "number") {
+            needsParens = false;
+          } else {
+            switch (match.TAG | 0) {
+              case /* Ppat_alias */1 :
+              case /* Ppat_or */9 :
+                  needsParens = true;
+                  break;
+              default:
+                needsParens = false;
+            }
+          }
+          var p$2 = printPattern(p$1, cmtTbl);
+          var renderedPattern = needsParens ? Res_doc.concat({
+                  hd: Res_doc.text("("),
+                  tl: {
+                    hd: p$2,
+                    tl: {
+                      hd: Res_doc.text(")"),
+                      tl: /* [] */0
+                    }
+                  }
+                }) : p$2;
+          patternWithoutAttributes = Res_doc.concat({
+                hd: renderedPattern,
+                tl: {
+                  hd: Res_doc.text(" as "),
+                  tl: {
+                    hd: printStringLoc($$var._1, cmtTbl),
+                    tl: /* [] */0
+                  }
+                }
+              });
+          break;
+      case /* Ppat_constant */2 :
+          var templateLiteral = Res_parsetree_viewer.hasTemplateLiteralAttr(p.ppat_attributes);
+          patternWithoutAttributes = printConstant(templateLiteral, $$var._0);
+          break;
+      case /* Ppat_interval */3 :
+          patternWithoutAttributes = Res_doc.concat({
+                hd: printConstant(undefined, $$var._0),
+                tl: {
+                  hd: Res_doc.text(" .. "),
+                  tl: {
+                    hd: printConstant(undefined, $$var._1),
+                    tl: /* [] */0
+                  }
+                }
+              });
+          break;
+      case /* Ppat_tuple */4 :
+          patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.text(","),
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (pat) {
+                                              return printPattern(pat, cmtTbl);
+                                            }), $$var._0)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.trailingComma,
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Ppat_construct */5 :
+          var constrName = $$var._0;
+          var match$1 = constrName.txt;
+          var exit = 0;
+          switch (match$1.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$1._0) {
+                  case "()" :
+                      patternWithoutAttributes = Res_doc.concat({
+                            hd: Res_doc.lparen,
+                            tl: {
+                              hd: printCommentsInside(cmtTbl, p.ppat_loc),
+                              tl: {
+                                hd: Res_doc.rparen,
+                                tl: /* [] */0
+                              }
+                            }
+                          });
+                      break;
+                  case "::" :
+                      var match$2 = Res_parsetree_viewer.collectPatternsFromListConstruct(/* [] */0, p);
+                      var tail = match$2[1];
+                      var patterns = match$2[0];
+                      var shouldHug;
+                      if (patterns && !patterns.tl) {
+                        var match$3 = tail.ppat_desc;
+                        if (typeof match$3 === "number" || match$3.TAG !== /* Ppat_construct */5) {
+                          shouldHug = false;
+                        } else {
+                          var match$4 = match$3._0.txt;
+                          switch (match$4.TAG | 0) {
+                            case /* Lident */0 :
+                                shouldHug = match$4._0 === "[]" && Res_parsetree_viewer.isHuggablePattern(patterns.hd) ? true : false;
+                                break;
+                            case /* Ldot */1 :
+                            case /* Lapply */2 :
+                                shouldHug = false;
+                                break;
+                            
+                          }
+                        }
+                      } else {
+                        shouldHug = false;
+                      }
+                      var match$5 = tail.ppat_desc;
+                      var tmp;
+                      var exit$1 = 0;
+                      if (typeof match$5 === "number" || match$5.TAG !== /* Ppat_construct */5) {
+                        exit$1 = 2;
+                      } else {
+                        var match$6 = match$5._0.txt;
+                        switch (match$6.TAG | 0) {
+                          case /* Lident */0 :
+                              if (match$6._0 === "[]") {
+                                tmp = Res_doc.nil;
+                              } else {
+                                exit$1 = 2;
+                              }
+                              break;
+                          case /* Ldot */1 :
+                          case /* Lapply */2 :
+                              exit$1 = 2;
+                              break;
+                          
+                        }
+                      }
+                      if (exit$1 === 2) {
+                        var doc = Res_doc.concat({
+                              hd: Res_doc.text("..."),
+                              tl: {
+                                hd: printPattern(tail, cmtTbl),
+                                tl: /* [] */0
+                              }
+                            });
+                        var tail$1 = printComments(doc, cmtTbl, tail.ppat_loc);
+                        tmp = Res_doc.concat({
+                              hd: Res_doc.text(","),
+                              tl: {
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: tail$1,
+                                  tl: /* [] */0
+                                }
+                              }
+                            });
+                      }
+                      var children = Res_doc.concat({
+                            hd: shouldHug ? Res_doc.nil : Res_doc.softLine,
+                            tl: {
+                              hd: Res_doc.join(Res_doc.concat({
+                                        hd: Res_doc.text(","),
+                                        tl: {
+                                          hd: Res_doc.line,
+                                          tl: /* [] */0
+                                        }
+                                      }), List.map((function (pat) {
+                                          return printPattern(pat, cmtTbl);
+                                        }), patterns)),
+                              tl: {
+                                hd: tmp,
+                                tl: /* [] */0
+                              }
+                            }
+                          });
+                      patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                                hd: Res_doc.text("list{"),
+                                tl: {
+                                  hd: shouldHug ? children : Res_doc.concat({
+                                          hd: Res_doc.indent(children),
+                                          tl: {
+                                            hd: Res_doc.ifBreaks(Res_doc.text(","), Res_doc.nil),
+                                            tl: {
+                                              hd: Res_doc.softLine,
+                                              tl: /* [] */0
+                                            }
+                                          }
+                                        }),
+                                  tl: {
+                                    hd: Res_doc.rbrace,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }));
+                      break;
+                  case "[]" :
+                      patternWithoutAttributes = Res_doc.concat({
+                            hd: Res_doc.text("list{"),
+                            tl: {
+                              hd: printCommentsInside(cmtTbl, p.ppat_loc),
+                              tl: {
+                                hd: Res_doc.rbrace,
+                                tl: /* [] */0
+                              }
+                            }
+                          });
+                      break;
+                  default:
+                    exit = 1;
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                exit = 1;
+                break;
+            
+          }
+          if (exit === 1) {
+            var constructorArgs = $$var._1;
+            var constrName$1 = printLongidentLocation(constrName, cmtTbl);
+            var argsDoc;
+            if (constructorArgs !== undefined) {
+              var patterns$1 = constructorArgs.ppat_desc;
+              var exit$2 = 0;
+              if (typeof patterns$1 === "number") {
+                exit$2 = 2;
+              } else {
+                switch (patterns$1.TAG | 0) {
+                  case /* Ppat_tuple */4 :
+                      var patterns$2 = patterns$1._0;
+                      var exit$3 = 0;
+                      if (patterns$2) {
+                        var arg = patterns$2.hd;
+                        var tmp$1 = arg.ppat_desc;
+                        if (typeof tmp$1 === "number" || !(tmp$1.TAG === /* Ppat_tuple */4 && !patterns$2.tl)) {
+                          exit$3 = 3;
+                        } else {
+                          argsDoc = Res_doc.concat({
+                                hd: Res_doc.lparen,
+                                tl: {
+                                  hd: printPattern(arg, cmtTbl),
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              });
+                        }
+                      } else {
+                        argsDoc = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printCommentsInside(cmtTbl, constructorArgs.ppat_loc),
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            });
+                      }
+                      if (exit$3 === 3) {
+                        argsDoc = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.join(Res_doc.concat({
+                                                      hd: Res_doc.comma,
+                                                      tl: {
+                                                        hd: Res_doc.line,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }), List.map((function (pat) {
+                                                        return printPattern(pat, cmtTbl);
+                                                      }), patterns$2)),
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: {
+                                      hd: Res_doc.rparen,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            });
+                      }
+                      break;
+                  case /* Ppat_construct */5 :
+                      var match$7 = patterns$1._0.txt;
+                      switch (match$7.TAG | 0) {
+                        case /* Lident */0 :
+                            if (match$7._0 === "()") {
+                              argsDoc = Res_doc.concat({
+                                    hd: Res_doc.lparen,
+                                    tl: {
+                                      hd: printCommentsInside(cmtTbl, constructorArgs.ppat_loc),
+                                      tl: {
+                                        hd: Res_doc.rparen,
+                                        tl: /* [] */0
+                                      }
+                                    }
+                                  });
+                            } else {
+                              exit$2 = 2;
+                            }
+                            break;
+                        case /* Ldot */1 :
+                        case /* Lapply */2 :
+                            exit$2 = 2;
+                            break;
+                        
+                      }
+                      break;
+                  default:
+                    exit$2 = 2;
+                }
+              }
+              if (exit$2 === 2) {
+                var argDoc = printPattern(constructorArgs, cmtTbl);
+                var shouldHug$1 = Res_parsetree_viewer.isHuggablePattern(constructorArgs);
+                argsDoc = Res_doc.concat({
+                      hd: Res_doc.lparen,
+                      tl: {
+                        hd: shouldHug$1 ? argDoc : Res_doc.concat({
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: argDoc,
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }),
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+              }
+              
+            } else {
+              argsDoc = Res_doc.nil;
+            }
+            patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                      hd: constrName$1,
+                      tl: {
+                        hd: argsDoc,
+                        tl: /* [] */0
+                      }
+                    }));
+          }
+          break;
+      case /* Ppat_variant */6 :
+          var variantArgs = $$var._1;
+          var label = $$var._0;
+          if (variantArgs !== undefined) {
+            var variantName = Res_doc.concat({
+                  hd: Res_doc.text("#"),
+                  tl: {
+                    hd: printPolyVarIdent(label),
+                    tl: /* [] */0
+                  }
+                });
+            var argsDoc$1;
+            if (variantArgs !== undefined) {
+              var patterns$3 = variantArgs.ppat_desc;
+              var exit$4 = 0;
+              if (typeof patterns$3 === "number") {
+                exit$4 = 1;
+              } else {
+                switch (patterns$3.TAG | 0) {
+                  case /* Ppat_tuple */4 :
+                      var patterns$4 = patterns$3._0;
+                      var exit$5 = 0;
+                      if (patterns$4) {
+                        var arg$1 = patterns$4.hd;
+                        var tmp$2 = arg$1.ppat_desc;
+                        if (typeof tmp$2 === "number" || !(tmp$2.TAG === /* Ppat_tuple */4 && !patterns$4.tl)) {
+                          exit$5 = 2;
+                        } else {
+                          argsDoc$1 = Res_doc.concat({
+                                hd: Res_doc.lparen,
+                                tl: {
+                                  hd: printPattern(arg$1, cmtTbl),
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              });
+                        }
+                      } else {
+                        argsDoc$1 = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printCommentsInside(cmtTbl, variantArgs.ppat_loc),
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            });
+                      }
+                      if (exit$5 === 2) {
+                        argsDoc$1 = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.join(Res_doc.concat({
+                                                      hd: Res_doc.comma,
+                                                      tl: {
+                                                        hd: Res_doc.line,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }), List.map((function (pat) {
+                                                        return printPattern(pat, cmtTbl);
+                                                      }), patterns$4)),
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: {
+                                      hd: Res_doc.rparen,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            });
+                      }
+                      break;
+                  case /* Ppat_construct */5 :
+                      var match$8 = patterns$3._0.txt;
+                      switch (match$8.TAG | 0) {
+                        case /* Lident */0 :
+                            if (match$8._0 === "()") {
+                              argsDoc$1 = Res_doc.text("()");
+                            } else {
+                              exit$4 = 1;
+                            }
+                            break;
+                        case /* Ldot */1 :
+                        case /* Lapply */2 :
+                            exit$4 = 1;
+                            break;
+                        
+                      }
+                      break;
+                  default:
+                    exit$4 = 1;
+                }
+              }
+              if (exit$4 === 1) {
+                var argDoc$1 = printPattern(variantArgs, cmtTbl);
+                var shouldHug$2 = Res_parsetree_viewer.isHuggablePattern(variantArgs);
+                argsDoc$1 = Res_doc.concat({
+                      hd: Res_doc.lparen,
+                      tl: {
+                        hd: shouldHug$2 ? argDoc$1 : Res_doc.concat({
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: argDoc$1,
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }),
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+              }
+              
+            } else {
+              argsDoc$1 = Res_doc.nil;
+            }
+            patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                      hd: variantName,
+                      tl: {
+                        hd: argsDoc$1,
+                        tl: /* [] */0
+                      }
+                    }));
+          } else {
+            patternWithoutAttributes = Res_doc.concat({
+                  hd: Res_doc.text("#"),
+                  tl: {
+                    hd: printPolyVarIdent(label),
+                    tl: /* [] */0
+                  }
+                });
+          }
+          break;
+      case /* Ppat_record */7 :
+          patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.text(","),
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (row) {
+                                              return printPatternRecordRow(row, cmtTbl);
+                                            }), $$var._0)),
+                                  tl: {
+                                    hd: $$var._1 ? Res_doc.concat({
+                                            hd: Res_doc.text(","),
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: {
+                                                hd: Res_doc.text("_"),
+                                                tl: /* [] */0
+                                              }
+                                            }
+                                          }) : Res_doc.nil,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.ifBreaks(Res_doc.text(","), Res_doc.nil),
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rbrace,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Ppat_array */8 :
+          var patterns$5 = $$var._0;
+          patternWithoutAttributes = patterns$5 ? Res_doc.group(Res_doc.concat({
+                      hd: Res_doc.text("["),
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat({
+                                  hd: Res_doc.softLine,
+                                  tl: {
+                                    hd: Res_doc.join(Res_doc.concat({
+                                              hd: Res_doc.text(","),
+                                              tl: {
+                                                hd: Res_doc.line,
+                                                tl: /* [] */0
+                                              }
+                                            }), List.map((function (pat) {
+                                                return printPattern(pat, cmtTbl);
+                                              }), patterns$5)),
+                                    tl: /* [] */0
+                                  }
+                                })),
+                        tl: {
+                          hd: Res_doc.trailingComma,
+                          tl: {
+                            hd: Res_doc.softLine,
+                            tl: {
+                              hd: Res_doc.text("]"),
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      }
+                    })) : Res_doc.concat({
+                  hd: Res_doc.lbracket,
+                  tl: {
+                    hd: printCommentsInside(cmtTbl, p.ppat_loc),
+                    tl: {
+                      hd: Res_doc.rbracket,
+                      tl: /* [] */0
+                    }
+                  }
+                });
+          break;
+      case /* Ppat_or */9 :
+          var orChain = Res_parsetree_viewer.collectOrPatternChain(p);
+          var docs = List.mapi((function (i, pat) {
+                  var patternDoc = printPattern(pat, cmtTbl);
+                  var match = pat.ppat_desc;
+                  var tmp;
+                  tmp = typeof match === "number" || match.TAG !== /* Ppat_or */9 ? patternDoc : addParens(patternDoc);
+                  return Res_doc.concat({
+                              hd: i === 0 ? Res_doc.nil : Res_doc.concat({
+                                      hd: Res_doc.line,
+                                      tl: {
+                                        hd: Res_doc.text("| "),
+                                        tl: /* [] */0
+                                      }
+                                    }),
+                              tl: {
+                                hd: tmp,
+                                tl: /* [] */0
+                              }
+                            });
+                }), orChain);
+          var match$9 = List.rev(orChain);
+          var isSpreadOverMultipleLines = orChain && match$9 ? orChain.hd.ppat_loc.loc_start.pos_lnum < match$9.hd.ppat_loc.loc_end.pos_lnum : false;
+          patternWithoutAttributes = Res_doc.breakableGroup(isSpreadOverMultipleLines, Res_doc.concat(docs));
+          break;
+      case /* Ppat_constraint */10 :
+          var pattern = $$var._0;
+          var stringLoc = pattern.ppat_desc;
+          var exit$6 = 0;
+          if (typeof stringLoc === "number" || stringLoc.TAG !== /* Ppat_unpack */13) {
+            exit$6 = 1;
+          } else {
+            var match$10 = $$var._1;
+            var packageType = match$10.ptyp_desc;
+            if (typeof packageType === "number" || packageType.TAG !== /* Ptyp_package */9) {
+              exit$6 = 1;
+            } else {
+              var stringLoc$1 = stringLoc._0;
+              patternWithoutAttributes = Res_doc.concat({
+                    hd: Res_doc.text("module("),
+                    tl: {
+                      hd: printComments(Res_doc.text(stringLoc$1.txt), cmtTbl, stringLoc$1.loc),
+                      tl: {
+                        hd: Res_doc.text(": "),
+                        tl: {
+                          hd: printComments(printPackageType(false, packageType._0, cmtTbl), cmtTbl, match$10.ptyp_loc),
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  });
+            }
+          }
+          if (exit$6 === 1) {
+            patternWithoutAttributes = Res_doc.concat({
+                  hd: printPattern(pattern, cmtTbl),
+                  tl: {
+                    hd: Res_doc.text(": "),
+                    tl: {
+                      hd: printTypExpr($$var._1, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  }
+                });
+          }
+          break;
+      case /* Ppat_type */11 :
+          patternWithoutAttributes = Res_doc.concat({
+                hd: Res_doc.text("#..."),
+                tl: {
+                  hd: printIdentPath($$var._0, cmtTbl),
+                  tl: /* [] */0
+                }
+              });
+          break;
+      case /* Ppat_lazy */12 :
+          var p$3 = $$var._0;
+          var match$11 = p$3.ppat_desc;
+          var needsParens$1;
+          if (typeof match$11 === "number") {
+            needsParens$1 = false;
+          } else {
+            switch (match$11.TAG | 0) {
+              case /* Ppat_alias */1 :
+              case /* Ppat_or */9 :
+                  needsParens$1 = true;
+                  break;
+              default:
+                needsParens$1 = false;
+            }
+          }
+          var p$4 = printPattern(p$3, cmtTbl);
+          var pat = needsParens$1 ? Res_doc.concat({
+                  hd: Res_doc.text("("),
+                  tl: {
+                    hd: p$4,
+                    tl: {
+                      hd: Res_doc.text(")"),
+                      tl: /* [] */0
+                    }
+                  }
+                }) : p$4;
+          patternWithoutAttributes = Res_doc.concat({
+                hd: Res_doc.text("lazy "),
+                tl: {
+                  hd: pat,
+                  tl: /* [] */0
+                }
+              });
+          break;
+      case /* Ppat_unpack */13 :
+          var stringLoc$2 = $$var._0;
+          patternWithoutAttributes = Res_doc.concat({
+                hd: Res_doc.text("module("),
+                tl: {
+                  hd: printComments(Res_doc.text(stringLoc$2.txt), cmtTbl, stringLoc$2.loc),
+                  tl: {
+                    hd: Res_doc.rparen,
+                    tl: /* [] */0
+                  }
+                }
+              });
+          break;
+      case /* Ppat_exception */14 :
+          var p$5 = $$var._0;
+          var match$12 = p$5.ppat_desc;
+          var needsParens$2;
+          if (typeof match$12 === "number") {
+            needsParens$2 = false;
+          } else {
+            switch (match$12.TAG | 0) {
+              case /* Ppat_alias */1 :
+              case /* Ppat_or */9 :
+                  needsParens$2 = true;
+                  break;
+              default:
+                needsParens$2 = false;
+            }
+          }
+          var p$6 = printPattern(p$5, cmtTbl);
+          var pat$1 = needsParens$2 ? Res_doc.concat({
+                  hd: Res_doc.text("("),
+                  tl: {
+                    hd: p$6,
+                    tl: {
+                      hd: Res_doc.text(")"),
+                      tl: /* [] */0
+                    }
+                  }
+                }) : p$6;
+          patternWithoutAttributes = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.text("exception"),
+                    tl: {
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: pat$1,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+          break;
+      case /* Ppat_extension */15 :
+          patternWithoutAttributes = printExtension(false, $$var._0, cmtTbl);
+          break;
+      case /* Ppat_open */16 :
+          patternWithoutAttributes = Res_doc.nil;
+          break;
+      
+    }
+  }
+  var attrs = p.ppat_attributes;
+  var doc$1 = attrs ? Res_doc.group(Res_doc.concat({
+              hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+              tl: {
+                hd: patternWithoutAttributes,
+                tl: /* [] */0
+              }
+            })) : patternWithoutAttributes;
+  return printComments(doc$1, cmtTbl, p.ppat_loc);
+}
+
+function printPatternRecordRow(row, cmtTbl) {
+  var longident = row[0];
+  var ident = longident.txt;
+  switch (ident.TAG | 0) {
+    case /* Lident */0 :
+        var match = row[1].ppat_desc;
+        if (typeof match !== "number" && match.TAG === /* Ppat_var */0 && ident._0 === match._0.txt) {
+          return printLidentPath(longident, cmtTbl);
+        }
+        break;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        break;
+    
+  }
+  var pattern = row[1];
+  var init = longident.loc;
+  var locForComments_loc_start = init.loc_start;
+  var locForComments_loc_end = pattern.ppat_loc.loc_end;
+  var locForComments_loc_ghost = init.loc_ghost;
+  var locForComments = {
+    loc_start: locForComments_loc_start,
+    loc_end: locForComments_loc_end,
+    loc_ghost: locForComments_loc_ghost
+  };
+  var doc = printPattern(pattern, cmtTbl);
+  var rhsDoc = Res_parens.patternRecordRowRhs(pattern) ? addParens(doc) : doc;
+  var doc$1 = Res_doc.group(Res_doc.concat({
+            hd: printLidentPath(longident, cmtTbl),
+            tl: {
+              hd: Res_doc.text(":"),
+              tl: {
+                hd: Res_parsetree_viewer.isHuggablePattern(pattern) ? Res_doc.concat({
+                        hd: Res_doc.space,
+                        tl: {
+                          hd: rhsDoc,
+                          tl: /* [] */0
+                        }
+                      }) : Res_doc.indent(Res_doc.concat({
+                            hd: Res_doc.line,
+                            tl: {
+                              hd: rhsDoc,
+                              tl: /* [] */0
+                            }
+                          })),
+                tl: /* [] */0
+              }
+            }
+          }));
+  return printComments(doc$1, cmtTbl, locForComments);
+}
+
+function printExpressionWithComments(expr, cmtTbl) {
+  var doc = printExpression(expr, cmtTbl);
+  return printComments(doc, cmtTbl, expr.pexp_loc);
+}
+
+function printIfChain(pexp_attributes, ifs, elseExpr, cmtTbl) {
+  var ifDocs = Res_doc.join(Res_doc.space, List.mapi((function (i, param) {
+              var thenExpr = param[1];
+              var ifExpr = param[0];
+              var ifTxt = i > 0 ? Res_doc.text("else if ") : Res_doc.text("if ");
+              if (ifExpr.TAG === /* If */0) {
+                var ifExpr$1 = ifExpr._0;
+                var condition;
+                if (Res_parsetree_viewer.isBlockExpr(ifExpr$1)) {
+                  condition = printExpressionBlock(true, ifExpr$1, cmtTbl);
+                } else {
+                  var doc = printExpressionWithComments(ifExpr$1, cmtTbl);
+                  var braces = Res_parens.expr(ifExpr$1);
+                  condition = typeof braces === "number" ? (
+                      braces !== 0 ? Res_doc.ifBreaks(addParens(doc), doc) : addParens(doc)
+                    ) : printBraces(doc, ifExpr$1, braces._0);
+                }
+                var match = Res_parsetree_viewer.processBracesAttr(thenExpr);
+                var thenExpr$1 = match[0] !== undefined ? match[1] : thenExpr;
+                return Res_doc.concat({
+                            hd: ifTxt,
+                            tl: {
+                              hd: Res_doc.group(condition),
+                              tl: {
+                                hd: Res_doc.space,
+                                tl: {
+                                  hd: printExpressionBlock(true, thenExpr$1, cmtTbl),
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          });
+              }
+              var conditionExpr = ifExpr._1;
+              var doc$1 = printExpressionWithComments(conditionExpr, cmtTbl);
+              var braces$1 = Res_parens.expr(conditionExpr);
+              var conditionDoc = typeof braces$1 === "number" ? (
+                  braces$1 !== 0 ? doc$1 : addParens(doc$1)
+                ) : printBraces(doc$1, conditionExpr, braces$1._0);
+              return Res_doc.concat({
+                          hd: ifTxt,
+                          tl: {
+                            hd: Res_doc.text("let "),
+                            tl: {
+                              hd: printPattern(ifExpr._0, cmtTbl),
+                              tl: {
+                                hd: Res_doc.text(" = "),
+                                tl: {
+                                  hd: conditionDoc,
+                                  tl: {
+                                    hd: Res_doc.space,
+                                    tl: {
+                                      hd: printExpressionBlock(true, thenExpr, cmtTbl),
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        });
+            }), ifs));
+  var elseDoc = elseExpr !== undefined ? Res_doc.concat({
+          hd: Res_doc.text(" else "),
+          tl: {
+            hd: printExpressionBlock(true, elseExpr, cmtTbl),
+            tl: /* [] */0
+          }
+        }) : Res_doc.nil;
+  var attrs = Res_parsetree_viewer.filterFragileMatchAttributes(pexp_attributes);
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+              tl: {
+                hd: ifDocs,
+                tl: {
+                  hd: elseDoc,
+                  tl: /* [] */0
+                }
+              }
+            });
+}
+
+function printExpression(e, cmtTbl) {
+  var c = e.pexp_desc;
+  var printedExpression;
+  var exit = 0;
+  if (typeof c === "number") {
+    printedExpression = Res_doc.dot;
+  } else {
+    switch (c.TAG | 0) {
+      case /* Pexp_ident */0 :
+          printedExpression = printLidentPath(c._0, cmtTbl);
+          break;
+      case /* Pexp_constant */1 :
+          printedExpression = printConstant(Res_parsetree_viewer.isTemplateLiteral(e), c._0);
+          break;
+      case /* Pexp_function */3 :
+          printedExpression = Res_doc.concat({
+                hd: Res_doc.text("x => switch x "),
+                tl: {
+                  hd: printCases(c._0, cmtTbl),
+                  tl: /* [] */0
+                }
+              });
+          break;
+      case /* Pexp_fun */4 :
+          if (typeof c._0 === "number" && c._1 === undefined) {
+            var match = c._2.ppat_desc;
+            if (typeof match === "number" || !(match.TAG === /* Ppat_var */0 && match._0.txt === "__x")) {
+              exit = 1;
+            } else {
+              var tmp = c._3.pexp_desc;
+              if (typeof tmp === "number" || tmp.TAG !== /* Pexp_apply */5) {
+                exit = 1;
+              } else {
+                printedExpression = printExpressionWithComments(Res_parsetree_viewer.rewriteUnderscoreApply(e), cmtTbl);
+              }
+            }
+          } else {
+            exit = 1;
+          }
+          break;
+      case /* Pexp_apply */5 :
+          printedExpression = Res_parsetree_viewer.isUnaryExpression(e) ? printUnaryExpression(e, cmtTbl) : (
+              Res_parsetree_viewer.isTemplateLiteral(e) ? printTemplateLiteral(e, cmtTbl) : (
+                  Res_parsetree_viewer.isBinaryExpression(e) ? printBinaryExpression(e, cmtTbl) : printPexpApply(e, cmtTbl)
+                )
+            );
+          break;
+      case /* Pexp_match */6 :
+          var cases = c._1;
+          var expr = c._0;
+          var exit$1 = 0;
+          if (cases) {
+            var match$1 = cases.tl;
+            if (match$1 && !(match$1.tl || !Res_parsetree_viewer.isIfLetExpr(e))) {
+              var match$2 = Res_parsetree_viewer.collectIfExpressions(e);
+              printedExpression = printIfChain(e.pexp_attributes, match$2[0], match$2[1], cmtTbl);
+            } else {
+              exit$1 = 2;
+            }
+          } else {
+            exit$1 = 2;
+          }
+          if (exit$1 === 2) {
+            var doc = printExpressionWithComments(expr, cmtTbl);
+            var braces = Res_parens.expr(expr);
+            var exprDoc = typeof braces === "number" ? (
+                braces !== 0 ? doc : addParens(doc)
+              ) : printBraces(doc, expr, braces._0);
+            printedExpression = Res_doc.concat({
+                  hd: Res_doc.text("switch "),
+                  tl: {
+                    hd: exprDoc,
+                    tl: {
+                      hd: Res_doc.space,
+                      tl: {
+                        hd: printCases(cases, cmtTbl),
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                });
+          }
+          break;
+      case /* Pexp_try */7 :
+          var expr$1 = c._0;
+          var doc$1 = printExpressionWithComments(expr$1, cmtTbl);
+          var braces$1 = Res_parens.expr(expr$1);
+          var exprDoc$1 = typeof braces$1 === "number" ? (
+              braces$1 !== 0 ? doc$1 : addParens(doc$1)
+            ) : printBraces(doc$1, expr$1, braces$1._0);
+          printedExpression = Res_doc.concat({
+                hd: Res_doc.text("try "),
+                tl: {
+                  hd: exprDoc$1,
+                  tl: {
+                    hd: Res_doc.text(" catch "),
+                    tl: {
+                      hd: printCases(c._1, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  }
+                }
+              });
+          break;
+      case /* Pexp_tuple */8 :
+          printedExpression = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.join(Res_doc.concat({
+                                            hd: Res_doc.text(","),
+                                            tl: {
+                                              hd: Res_doc.line,
+                                              tl: /* [] */0
+                                            }
+                                          }), List.map((function (expr) {
+                                              var doc = printExpressionWithComments(expr, cmtTbl);
+                                              var braces = Res_parens.expr(expr);
+                                              if (typeof braces === "number") {
+                                                if (braces !== 0) {
+                                                  return doc;
+                                                } else {
+                                                  return addParens(doc);
+                                                }
+                                              } else {
+                                                return printBraces(doc, expr, braces._0);
+                                              }
+                                            }), c._0)),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.ifBreaks(Res_doc.text(","), Res_doc.nil),
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_construct */9 :
+          var longidentLoc = c._0;
+          var match$3 = longidentLoc.txt;
+          var exit$2 = 0;
+          if (Res_parsetree_viewer.hasJsxAttribute(e.pexp_attributes)) {
+            printedExpression = printJsxFragment(e, cmtTbl);
+          } else {
+            switch (match$3.TAG | 0) {
+              case /* Lident */0 :
+                  switch (match$3._0) {
+                    case "()" :
+                        printedExpression = Res_doc.text("()");
+                        break;
+                    case "::" :
+                        var match$4 = Res_parsetree_viewer.collectListExpressions(e);
+                        var spread = match$4[1];
+                        var spreadDoc;
+                        if (spread !== undefined) {
+                          var doc$2 = printExpressionWithComments(spread, cmtTbl);
+                          var braces$2 = Res_parens.expr(spread);
+                          spreadDoc = Res_doc.concat({
+                                hd: Res_doc.text(","),
+                                tl: {
+                                  hd: Res_doc.line,
+                                  tl: {
+                                    hd: Res_doc.dotdotdot,
+                                    tl: {
+                                      hd: typeof braces$2 === "number" ? (
+                                          braces$2 !== 0 ? doc$2 : addParens(doc$2)
+                                        ) : printBraces(doc$2, spread, braces$2._0),
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              });
+                        } else {
+                          spreadDoc = Res_doc.nil;
+                        }
+                        printedExpression = Res_doc.group(Res_doc.concat({
+                                  hd: Res_doc.text("list{"),
+                                  tl: {
+                                    hd: Res_doc.indent(Res_doc.concat({
+                                              hd: Res_doc.softLine,
+                                              tl: {
+                                                hd: Res_doc.join(Res_doc.concat({
+                                                          hd: Res_doc.text(","),
+                                                          tl: {
+                                                            hd: Res_doc.line,
+                                                            tl: /* [] */0
+                                                          }
+                                                        }), List.map((function (expr) {
+                                                            var doc = printExpressionWithComments(expr, cmtTbl);
+                                                            var braces = Res_parens.expr(expr);
+                                                            if (typeof braces === "number") {
+                                                              if (braces !== 0) {
+                                                                return doc;
+                                                              } else {
+                                                                return addParens(doc);
+                                                              }
+                                                            } else {
+                                                              return printBraces(doc, expr, braces._0);
+                                                            }
+                                                          }), match$4[0])),
+                                                tl: {
+                                                  hd: spreadDoc,
+                                                  tl: /* [] */0
+                                                }
+                                              }
+                                            })),
+                                    tl: {
+                                      hd: Res_doc.trailingComma,
+                                      tl: {
+                                        hd: Res_doc.softLine,
+                                        tl: {
+                                          hd: Res_doc.rbrace,
+                                          tl: /* [] */0
+                                        }
+                                      }
+                                    }
+                                  }
+                                }));
+                        break;
+                    case "[]" :
+                        printedExpression = Res_doc.concat({
+                              hd: Res_doc.text("list{"),
+                              tl: {
+                                hd: printCommentsInside(cmtTbl, e.pexp_loc),
+                                tl: {
+                                  hd: Res_doc.rbrace,
+                                  tl: /* [] */0
+                                }
+                              }
+                            });
+                        break;
+                    default:
+                      exit$2 = 2;
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  exit$2 = 2;
+                  break;
+              
+            }
+          }
+          if (exit$2 === 2) {
+            var args = c._1;
+            var constr = printLongidentLocation(longidentLoc, cmtTbl);
+            var args$1;
+            if (args !== undefined) {
+              var args$2 = args.pexp_desc;
+              var exit$3 = 0;
+              if (typeof args$2 === "number") {
+                exit$3 = 3;
+              } else {
+                switch (args$2.TAG | 0) {
+                  case /* Pexp_tuple */8 :
+                      var args$3 = args$2._0;
+                      var exit$4 = 0;
+                      if (args$3) {
+                        var arg = args$3.hd;
+                        var tmp$1 = arg.pexp_desc;
+                        if (typeof tmp$1 === "number" || !(tmp$1.TAG === /* Pexp_tuple */8 && !args$3.tl)) {
+                          exit$4 = 4;
+                        } else {
+                          var doc$3 = printExpressionWithComments(arg, cmtTbl);
+                          var braces$3 = Res_parens.expr(arg);
+                          args$1 = Res_doc.concat({
+                                hd: Res_doc.lparen,
+                                tl: {
+                                  hd: typeof braces$3 === "number" ? (
+                                      braces$3 !== 0 ? doc$3 : addParens(doc$3)
+                                    ) : printBraces(doc$3, arg, braces$3._0),
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              });
+                        }
+                      } else {
+                        exit$4 = 4;
+                      }
+                      if (exit$4 === 4) {
+                        args$1 = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.join(Res_doc.concat({
+                                                      hd: Res_doc.comma,
+                                                      tl: {
+                                                        hd: Res_doc.line,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }), List.map((function (expr) {
+                                                        var doc = printExpressionWithComments(expr, cmtTbl);
+                                                        var braces = Res_parens.expr(expr);
+                                                        if (typeof braces === "number") {
+                                                          if (braces !== 0) {
+                                                            return doc;
+                                                          } else {
+                                                            return addParens(doc);
+                                                          }
+                                                        } else {
+                                                          return printBraces(doc, expr, braces._0);
+                                                        }
+                                                      }), args$3)),
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: {
+                                      hd: Res_doc.rparen,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            });
+                      }
+                      break;
+                  case /* Pexp_construct */9 :
+                      var match$5 = args$2._0.txt;
+                      switch (match$5.TAG | 0) {
+                        case /* Lident */0 :
+                            if (match$5._0 === "()") {
+                              args$1 = Res_doc.text("()");
+                            } else {
+                              exit$3 = 3;
+                            }
+                            break;
+                        case /* Ldot */1 :
+                        case /* Lapply */2 :
+                            exit$3 = 3;
+                            break;
+                        
+                      }
+                      break;
+                  default:
+                    exit$3 = 3;
+                }
+              }
+              if (exit$3 === 3) {
+                var doc$4 = printExpressionWithComments(args, cmtTbl);
+                var braces$4 = Res_parens.expr(args);
+                var argDoc = typeof braces$4 === "number" ? (
+                    braces$4 !== 0 ? doc$4 : addParens(doc$4)
+                  ) : printBraces(doc$4, args, braces$4._0);
+                var shouldHug = Res_parsetree_viewer.isHuggableExpression(args);
+                args$1 = Res_doc.concat({
+                      hd: Res_doc.lparen,
+                      tl: {
+                        hd: shouldHug ? argDoc : Res_doc.concat({
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: argDoc,
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }),
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+              }
+              
+            } else {
+              args$1 = Res_doc.nil;
+            }
+            printedExpression = Res_doc.group(Res_doc.concat({
+                      hd: constr,
+                      tl: {
+                        hd: args$1,
+                        tl: /* [] */0
+                      }
+                    }));
+          }
+          break;
+      case /* Pexp_variant */10 :
+          var args$4 = c._1;
+          var variantName = Res_doc.concat({
+                hd: Res_doc.text("#"),
+                tl: {
+                  hd: printPolyVarIdent(c._0),
+                  tl: /* [] */0
+                }
+              });
+          var args$5;
+          if (args$4 !== undefined) {
+            var args$6 = args$4.pexp_desc;
+            var exit$5 = 0;
+            if (typeof args$6 === "number") {
+              exit$5 = 2;
+            } else {
+              switch (args$6.TAG | 0) {
+                case /* Pexp_tuple */8 :
+                    var args$7 = args$6._0;
+                    var exit$6 = 0;
+                    if (args$7) {
+                      var arg$1 = args$7.hd;
+                      var tmp$2 = arg$1.pexp_desc;
+                      if (typeof tmp$2 === "number" || !(tmp$2.TAG === /* Pexp_tuple */8 && !args$7.tl)) {
+                        exit$6 = 3;
+                      } else {
+                        var doc$5 = printExpressionWithComments(arg$1, cmtTbl);
+                        var braces$5 = Res_parens.expr(arg$1);
+                        args$5 = Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: typeof braces$5 === "number" ? (
+                                    braces$5 !== 0 ? doc$5 : addParens(doc$5)
+                                  ) : printBraces(doc$5, arg$1, braces$5._0),
+                                tl: {
+                                  hd: Res_doc.rparen,
+                                  tl: /* [] */0
+                                }
+                              }
+                            });
+                      }
+                    } else {
+                      exit$6 = 3;
+                    }
+                    if (exit$6 === 3) {
+                      args$5 = Res_doc.concat({
+                            hd: Res_doc.lparen,
+                            tl: {
+                              hd: Res_doc.indent(Res_doc.concat({
+                                        hd: Res_doc.softLine,
+                                        tl: {
+                                          hd: Res_doc.join(Res_doc.concat({
+                                                    hd: Res_doc.comma,
+                                                    tl: {
+                                                      hd: Res_doc.line,
+                                                      tl: /* [] */0
+                                                    }
+                                                  }), List.map((function (expr) {
+                                                      var doc = printExpressionWithComments(expr, cmtTbl);
+                                                      var braces = Res_parens.expr(expr);
+                                                      if (typeof braces === "number") {
+                                                        if (braces !== 0) {
+                                                          return doc;
+                                                        } else {
+                                                          return addParens(doc);
+                                                        }
+                                                      } else {
+                                                        return printBraces(doc, expr, braces._0);
+                                                      }
+                                                    }), args$7)),
+                                          tl: /* [] */0
+                                        }
+                                      })),
+                              tl: {
+                                hd: Res_doc.trailingComma,
+                                tl: {
+                                  hd: Res_doc.softLine,
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            }
+                          });
+                    }
+                    break;
+                case /* Pexp_construct */9 :
+                    var match$6 = args$6._0.txt;
+                    switch (match$6.TAG | 0) {
+                      case /* Lident */0 :
+                          if (match$6._0 === "()") {
+                            args$5 = Res_doc.text("()");
+                          } else {
+                            exit$5 = 2;
+                          }
+                          break;
+                      case /* Ldot */1 :
+                      case /* Lapply */2 :
+                          exit$5 = 2;
+                          break;
+                      
+                    }
+                    break;
+                default:
+                  exit$5 = 2;
+              }
+            }
+            if (exit$5 === 2) {
+              var doc$6 = printExpressionWithComments(args$4, cmtTbl);
+              var braces$6 = Res_parens.expr(args$4);
+              var argDoc$1 = typeof braces$6 === "number" ? (
+                  braces$6 !== 0 ? doc$6 : addParens(doc$6)
+                ) : printBraces(doc$6, args$4, braces$6._0);
+              var shouldHug$1 = Res_parsetree_viewer.isHuggableExpression(args$4);
+              args$5 = Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: shouldHug$1 ? argDoc$1 : Res_doc.concat({
+                              hd: Res_doc.indent(Res_doc.concat({
+                                        hd: Res_doc.softLine,
+                                        tl: {
+                                          hd: argDoc$1,
+                                          tl: /* [] */0
+                                        }
+                                      })),
+                              tl: {
+                                hd: Res_doc.trailingComma,
+                                tl: {
+                                  hd: Res_doc.softLine,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }),
+                      tl: {
+                        hd: Res_doc.rparen,
+                        tl: /* [] */0
+                      }
+                    }
+                  });
+            }
+            
+          } else {
+            args$5 = Res_doc.nil;
+          }
+          printedExpression = Res_doc.group(Res_doc.concat({
+                    hd: variantName,
+                    tl: {
+                      hd: args$5,
+                      tl: /* [] */0
+                    }
+                  }));
+          break;
+      case /* Pexp_record */11 :
+          var spreadExpr = c._1;
+          var rows = c._0;
+          var spread$1;
+          if (spreadExpr !== undefined) {
+            var doc$7 = printExpressionWithComments(spreadExpr, cmtTbl);
+            var braces$7 = Res_parens.expr(spreadExpr);
+            spread$1 = Res_doc.concat({
+                  hd: Res_doc.dotdotdot,
+                  tl: {
+                    hd: typeof braces$7 === "number" ? (
+                        braces$7 !== 0 ? doc$7 : addParens(doc$7)
+                      ) : printBraces(doc$7, spreadExpr, braces$7._0),
+                    tl: {
+                      hd: Res_doc.comma,
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                });
+          } else {
+            spread$1 = Res_doc.nil;
+          }
+          var forceBreak = e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum;
+          var punningAllowed = spreadExpr !== undefined || !(rows && !rows.tl) ? true : false;
+          printedExpression = Res_doc.breakableGroup(forceBreak, Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: spread$1,
+                                  tl: {
+                                    hd: Res_doc.join(Res_doc.concat({
+                                              hd: Res_doc.text(","),
+                                              tl: {
+                                                hd: Res_doc.line,
+                                                tl: /* [] */0
+                                              }
+                                            }), List.map((function (row) {
+                                                return printRecordRow(row, cmtTbl, punningAllowed);
+                                              }), rows)),
+                                    tl: /* [] */0
+                                  }
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.trailingComma,
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rbrace,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_field */12 :
+          var expr$2 = c._0;
+          var doc$8 = printExpressionWithComments(expr$2, cmtTbl);
+          var braces$8 = Res_parens.fieldExpr(expr$2);
+          var lhs = typeof braces$8 === "number" ? (
+              braces$8 !== 0 ? doc$8 : addParens(doc$8)
+            ) : printBraces(doc$8, expr$2, braces$8._0);
+          printedExpression = Res_doc.concat({
+                hd: lhs,
+                tl: {
+                  hd: Res_doc.dot,
+                  tl: {
+                    hd: printLidentPath(c._1, cmtTbl),
+                    tl: /* [] */0
+                  }
+                }
+              });
+          break;
+      case /* Pexp_setfield */13 :
+          printedExpression = printSetFieldExpr(e.pexp_attributes, c._0, c._1, c._2, e.pexp_loc, cmtTbl);
+          break;
+      case /* Pexp_array */14 :
+          var exprs = c._0;
+          printedExpression = exprs ? Res_doc.group(Res_doc.concat({
+                      hd: Res_doc.lbracket,
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat({
+                                  hd: Res_doc.softLine,
+                                  tl: {
+                                    hd: Res_doc.join(Res_doc.concat({
+                                              hd: Res_doc.text(","),
+                                              tl: {
+                                                hd: Res_doc.line,
+                                                tl: /* [] */0
+                                              }
+                                            }), List.map((function (expr) {
+                                                var doc = printExpressionWithComments(expr, cmtTbl);
+                                                var braces = Res_parens.expr(expr);
+                                                if (typeof braces === "number") {
+                                                  if (braces !== 0) {
+                                                    return doc;
+                                                  } else {
+                                                    return addParens(doc);
+                                                  }
+                                                } else {
+                                                  return printBraces(doc, expr, braces._0);
+                                                }
+                                              }), exprs)),
+                                    tl: /* [] */0
+                                  }
+                                })),
+                        tl: {
+                          hd: Res_doc.trailingComma,
+                          tl: {
+                            hd: Res_doc.softLine,
+                            tl: {
+                              hd: Res_doc.rbracket,
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      }
+                    })) : Res_doc.concat({
+                  hd: Res_doc.lbracket,
+                  tl: {
+                    hd: printCommentsInside(cmtTbl, e.pexp_loc),
+                    tl: {
+                      hd: Res_doc.rbracket,
+                      tl: /* [] */0
+                    }
+                  }
+                });
+          break;
+      case /* Pexp_ifthenelse */15 :
+          if (Res_parsetree_viewer.isTernaryExpr(e)) {
+            var match$7 = Res_parsetree_viewer.collectTernaryParts(e);
+            var parts = match$7[0];
+            var ternaryDoc;
+            if (parts) {
+              var match$8 = parts.hd;
+              ternaryDoc = Res_doc.group(Res_doc.concat({
+                        hd: printTernaryOperand(match$8[0], cmtTbl),
+                        tl: {
+                          hd: Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: Res_doc.indent(Res_doc.concat({
+                                                hd: Res_doc.text("? "),
+                                                tl: {
+                                                  hd: printTernaryOperand(match$8[1], cmtTbl),
+                                                  tl: /* [] */0
+                                                }
+                                              })),
+                                      tl: {
+                                        hd: Res_doc.concat(List.map((function (param) {
+                                                    return Res_doc.concat({
+                                                                hd: Res_doc.line,
+                                                                tl: {
+                                                                  hd: Res_doc.text(": "),
+                                                                  tl: {
+                                                                    hd: printTernaryOperand(param[0], cmtTbl),
+                                                                    tl: {
+                                                                      hd: Res_doc.line,
+                                                                      tl: {
+                                                                        hd: Res_doc.text("? "),
+                                                                        tl: {
+                                                                          hd: printTernaryOperand(param[1], cmtTbl),
+                                                                          tl: /* [] */0
+                                                                        }
+                                                                      }
+                                                                    }
+                                                                  }
+                                                                }
+                                                              });
+                                                  }), parts.tl)),
+                                        tl: {
+                                          hd: Res_doc.line,
+                                          tl: {
+                                            hd: Res_doc.text(": "),
+                                            tl: {
+                                              hd: Res_doc.indent(printTernaryOperand(match$7[1], cmtTbl)),
+                                              tl: /* [] */0
+                                            }
+                                          }
+                                        }
+                                      }
+                                    }
+                                  })),
+                          tl: /* [] */0
+                        }
+                      }));
+            } else {
+              ternaryDoc = Res_doc.nil;
+            }
+            var attrs = Res_parsetree_viewer.filterTernaryAttributes(e.pexp_attributes);
+            var match$9 = Res_parsetree_viewer.filterParsingAttrs(attrs);
+            var needsParens = match$9 ? true : false;
+            printedExpression = Res_doc.concat({
+                  hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                  tl: {
+                    hd: needsParens ? addParens(ternaryDoc) : ternaryDoc,
+                    tl: /* [] */0
+                  }
+                });
+          } else {
+            var match$10 = Res_parsetree_viewer.collectIfExpressions(e);
+            printedExpression = printIfChain(e.pexp_attributes, match$10[0], match$10[1], cmtTbl);
+          }
+          break;
+      case /* Pexp_while */17 :
+          var expr1 = c._0;
+          var doc$9 = printExpressionWithComments(expr1, cmtTbl);
+          var braces$9 = Res_parens.expr(expr1);
+          var condition = typeof braces$9 === "number" ? (
+              braces$9 !== 0 ? doc$9 : addParens(doc$9)
+            ) : printBraces(doc$9, expr1, braces$9._0);
+          printedExpression = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: Res_doc.text("while "),
+                    tl: {
+                      hd: Res_parsetree_viewer.isBlockExpr(expr1) ? condition : Res_doc.group(Res_doc.ifBreaks(addParens(condition), condition)),
+                      tl: {
+                        hd: Res_doc.space,
+                        tl: {
+                          hd: printExpressionBlock(true, c._1, cmtTbl),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_for */18 :
+          var toExpr = c._2;
+          var fromExpr = c._1;
+          var doc$10 = printExpressionWithComments(fromExpr, cmtTbl);
+          var braces$10 = Res_parens.expr(fromExpr);
+          var doc$11 = printExpressionWithComments(toExpr, cmtTbl);
+          var braces$11 = Res_parens.expr(toExpr);
+          printedExpression = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: Res_doc.text("for "),
+                    tl: {
+                      hd: printPattern(c._0, cmtTbl),
+                      tl: {
+                        hd: Res_doc.text(" in "),
+                        tl: {
+                          hd: typeof braces$10 === "number" ? (
+                              braces$10 !== 0 ? doc$10 : addParens(doc$10)
+                            ) : printBraces(doc$10, fromExpr, braces$10._0),
+                          tl: {
+                            hd: printDirectionFlag(c._3),
+                            tl: {
+                              hd: typeof braces$11 === "number" ? (
+                                  braces$11 !== 0 ? doc$11 : addParens(doc$11)
+                                ) : printBraces(doc$11, toExpr, braces$11._0),
+                              tl: {
+                                hd: Res_doc.space,
+                                tl: {
+                                  hd: printExpressionBlock(true, c._4, cmtTbl),
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_constraint */19 :
+          var expr$3 = c._0;
+          var modExpr = expr$3.pexp_desc;
+          var exit$7 = 0;
+          if (typeof modExpr === "number" || modExpr.TAG !== /* Pexp_pack */32) {
+            exit$7 = 2;
+          } else {
+            var match$11 = c._1;
+            var packageType = match$11.ptyp_desc;
+            if (typeof packageType === "number" || packageType.TAG !== /* Ptyp_package */9) {
+              exit$7 = 2;
+            } else {
+              printedExpression = Res_doc.group(Res_doc.concat({
+                        hd: Res_doc.text("module("),
+                        tl: {
+                          hd: Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.softLine,
+                                    tl: {
+                                      hd: printModExpr(modExpr._0, cmtTbl),
+                                      tl: {
+                                        hd: Res_doc.text(": "),
+                                        tl: {
+                                          hd: printComments(printPackageType(false, packageType._0, cmtTbl), cmtTbl, match$11.ptyp_loc),
+                                          tl: /* [] */0
+                                        }
+                                      }
+                                    }
+                                  })),
+                          tl: {
+                            hd: Res_doc.softLine,
+                            tl: {
+                              hd: Res_doc.rparen,
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      }));
+            }
+          }
+          if (exit$7 === 2) {
+            var doc$12 = printExpressionWithComments(expr$3, cmtTbl);
+            var braces$12 = Res_parens.expr(expr$3);
+            var exprDoc$2 = typeof braces$12 === "number" ? (
+                braces$12 !== 0 ? doc$12 : addParens(doc$12)
+              ) : printBraces(doc$12, expr$3, braces$12._0);
+            printedExpression = Res_doc.concat({
+                  hd: exprDoc$2,
+                  tl: {
+                    hd: Res_doc.text(": "),
+                    tl: {
+                      hd: printTypExpr(c._1, cmtTbl),
+                      tl: /* [] */0
+                    }
+                  }
+                });
+          }
+          break;
+      case /* Pexp_coerce */20 :
+          var typOpt = c._1;
+          var docExpr = printExpressionWithComments(c._0, cmtTbl);
+          var docTyp = printTypExpr(c._2, cmtTbl);
+          var ofType = typOpt !== undefined ? Res_doc.concat({
+                  hd: Res_doc.text(": "),
+                  tl: {
+                    hd: printTypExpr(typOpt, cmtTbl),
+                    tl: /* [] */0
+                  }
+                }) : Res_doc.nil;
+          printedExpression = Res_doc.concat({
+                hd: Res_doc.lparen,
+                tl: {
+                  hd: docExpr,
+                  tl: {
+                    hd: ofType,
+                    tl: {
+                      hd: Res_doc.text(" :> "),
+                      tl: {
+                        hd: docTyp,
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }
+                }
+              });
+          break;
+      case /* Pexp_send */21 :
+          var label = c._1;
+          var parentExpr = c._0;
+          var doc$13 = printExpressionWithComments(parentExpr, cmtTbl);
+          var braces$13 = Res_parens.unaryExprOperand(parentExpr);
+          var parentDoc = typeof braces$13 === "number" ? (
+              braces$13 !== 0 ? doc$13 : addParens(doc$13)
+            ) : printBraces(doc$13, parentExpr, braces$13._0);
+          var memberDoc = printComments(Res_doc.text(label.txt), cmtTbl, label.loc);
+          var member = Res_doc.concat({
+                hd: Res_doc.text("\""),
+                tl: {
+                  hd: memberDoc,
+                  tl: {
+                    hd: Res_doc.text("\""),
+                    tl: /* [] */0
+                  }
+                }
+              });
+          printedExpression = Res_doc.group(Res_doc.concat({
+                    hd: parentDoc,
+                    tl: {
+                      hd: Res_doc.lbracket,
+                      tl: {
+                        hd: member,
+                        tl: {
+                          hd: Res_doc.rbracket,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_new */22 :
+          printedExpression = Res_doc.text("Pexp_new not impemented in printer");
+          break;
+      case /* Pexp_setinstvar */23 :
+          printedExpression = Res_doc.text("Pexp_setinstvar not impemented in printer");
+          break;
+      case /* Pexp_override */24 :
+          printedExpression = Res_doc.text("Pexp_override not impemented in printer");
+          break;
+      case /* Pexp_assert */27 :
+          var expr$4 = c._0;
+          var doc$14 = printExpressionWithComments(expr$4, cmtTbl);
+          var braces$14 = Res_parens.lazyOrAssertExprRhs(expr$4);
+          var rhs = typeof braces$14 === "number" ? (
+              braces$14 !== 0 ? doc$14 : addParens(doc$14)
+            ) : printBraces(doc$14, expr$4, braces$14._0);
+          printedExpression = Res_doc.concat({
+                hd: Res_doc.text("assert "),
+                tl: {
+                  hd: rhs,
+                  tl: /* [] */0
+                }
+              });
+          break;
+      case /* Pexp_lazy */28 :
+          var expr$5 = c._0;
+          var doc$15 = printExpressionWithComments(expr$5, cmtTbl);
+          var braces$15 = Res_parens.lazyOrAssertExprRhs(expr$5);
+          var rhs$1 = typeof braces$15 === "number" ? (
+              braces$15 !== 0 ? doc$15 : addParens(doc$15)
+            ) : printBraces(doc$15, expr$5, braces$15._0);
+          printedExpression = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.text("lazy "),
+                    tl: {
+                      hd: rhs$1,
+                      tl: /* [] */0
+                    }
+                  }));
+          break;
+      case /* Pexp_poly */29 :
+          printedExpression = Res_doc.text("Pexp_poly not impemented in printer");
+          break;
+      case /* Pexp_object */30 :
+          printedExpression = Res_doc.text("Pexp_object not impemented in printer");
+          break;
+      case /* Pexp_newtype */31 :
+          exit = 1;
+          break;
+      case /* Pexp_pack */32 :
+          printedExpression = Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.text("module("),
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printModExpr(c._0, cmtTbl),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+          break;
+      case /* Pexp_extension */34 :
+          var extension = c._0;
+          var exit$8 = 0;
+          switch (extension[0].txt) {
+            case "bs.obj" :
+            case "obj" :
+                exit$8 = 2;
+                break;
+            default:
+              printedExpression = printExtension(false, extension, cmtTbl);
+          }
+          if (exit$8 === 2) {
+            var match$12 = extension[1];
+            if (match$12.TAG === /* PStr */0) {
+              var match$13 = match$12._0;
+              if (match$13) {
+                var match$14 = match$13.hd;
+                var match$15 = match$14.pstr_desc;
+                if (match$15.TAG === /* Pstr_eval */0) {
+                  var match$16 = match$15._0.pexp_desc;
+                  if (typeof match$16 === "number" || !(match$16.TAG === /* Pexp_record */11 && !(match$15._1 || match$13.tl))) {
+                    printedExpression = printExtension(false, extension, cmtTbl);
+                  } else {
+                    var loc = match$14.pstr_loc;
+                    var forceBreak$1 = loc.loc_start.pos_lnum < loc.loc_end.pos_lnum;
+                    printedExpression = Res_doc.breakableGroup(forceBreak$1, Res_doc.concat({
+                              hd: Res_doc.lbrace,
+                              tl: {
+                                hd: Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.join(Res_doc.concat({
+                                                      hd: Res_doc.text(","),
+                                                      tl: {
+                                                        hd: Res_doc.line,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }), List.map((function (row) {
+                                                        return printBsObjectRow(row, cmtTbl);
+                                                      }), match$16._0)),
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                                tl: {
+                                  hd: Res_doc.trailingComma,
+                                  tl: {
+                                    hd: Res_doc.softLine,
+                                    tl: {
+                                      hd: Res_doc.rbrace,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            }));
+                  }
+                } else {
+                  printedExpression = printExtension(false, extension, cmtTbl);
+                }
+              } else {
+                printedExpression = printExtension(false, extension, cmtTbl);
+              }
+            } else {
+              printedExpression = printExtension(false, extension, cmtTbl);
+            }
+          }
+          break;
+      default:
+        printedExpression = printExpressionBlock(true, e, cmtTbl);
+    }
+  }
+  if (exit === 1) {
+    var match$17 = Res_parsetree_viewer.funExpr(e);
+    var returnExpr = match$17[2];
+    var match$18 = Res_parsetree_viewer.processUncurriedAttribute(match$17[0]);
+    var match$19 = returnExpr.pexp_desc;
+    var match$20;
+    if (typeof match$19 === "number" || match$19.TAG !== /* Pexp_constraint */19) {
+      match$20 = [
+        returnExpr,
+        undefined
+      ];
+    } else {
+      var expr$6 = match$19._0;
+      match$20 = [
+        {
+          pexp_desc: expr$6.pexp_desc,
+          pexp_loc: expr$6.pexp_loc,
+          pexp_attributes: List.concat({
+                hd: expr$6.pexp_attributes,
+                tl: {
+                  hd: returnExpr.pexp_attributes,
+                  tl: /* [] */0
+                }
+              })
+        },
+        match$19._1
+      ];
+    }
+    var typConstraint = match$20[1];
+    var returnExpr$1 = match$20[0];
+    var hasConstraint = typConstraint !== undefined;
+    var parametersDoc = printExprFunParameters(/* NoCallback */0, match$18[0], hasConstraint, match$17[1], cmtTbl);
+    var match$21 = Res_parsetree_viewer.processBracesAttr(returnExpr$1);
+    var match$22 = returnExpr$1.pexp_desc;
+    var shouldInline;
+    if (match$21[0] !== undefined) {
+      shouldInline = true;
+    } else if (typeof match$22 === "number") {
+      shouldInline = false;
+    } else {
+      switch (match$22.TAG | 0) {
+        case /* Pexp_construct */9 :
+            shouldInline = match$22._1 !== undefined;
+            break;
+        case /* Pexp_tuple */8 :
+        case /* Pexp_record */11 :
+        case /* Pexp_array */14 :
+            shouldInline = true;
+            break;
+        default:
+          shouldInline = false;
+      }
+    }
+    var match$23 = returnExpr$1.pexp_desc;
+    var shouldIndent;
+    if (typeof match$23 === "number") {
+      shouldIndent = true;
+    } else {
+      switch (match$23.TAG | 0) {
+        case /* Pexp_let */2 :
+        case /* Pexp_sequence */16 :
+        case /* Pexp_letmodule */25 :
+        case /* Pexp_letexception */26 :
+        case /* Pexp_open */33 :
+            shouldIndent = false;
+            break;
+        default:
+          shouldIndent = true;
+      }
+    }
+    var doc$16 = printExpressionWithComments(returnExpr$1, cmtTbl);
+    var braces$16 = Res_parens.expr(returnExpr$1);
+    var returnDoc = typeof braces$16 === "number" ? (
+        braces$16 !== 0 ? doc$16 : addParens(doc$16)
+      ) : printBraces(doc$16, returnExpr$1, braces$16._0);
+    var returnExprDoc = shouldInline ? Res_doc.concat({
+            hd: Res_doc.space,
+            tl: {
+              hd: returnDoc,
+              tl: /* [] */0
+            }
+          }) : Res_doc.group(shouldIndent ? Res_doc.indent(Res_doc.concat({
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: returnDoc,
+                        tl: /* [] */0
+                      }
+                    })) : Res_doc.concat({
+                  hd: Res_doc.space,
+                  tl: {
+                    hd: returnDoc,
+                    tl: /* [] */0
+                  }
+                }));
+    var typConstraintDoc;
+    if (typConstraint !== undefined) {
+      var doc$17 = printTypExpr(typConstraint, cmtTbl);
+      var typDoc = Res_parens.arrowReturnTypExpr(typConstraint) ? addParens(doc$17) : doc$17;
+      typConstraintDoc = Res_doc.concat({
+            hd: Res_doc.text(": "),
+            tl: {
+              hd: typDoc,
+              tl: /* [] */0
+            }
+          });
+    } else {
+      typConstraintDoc = Res_doc.nil;
+    }
+    var attrs$1 = printAttributes(undefined, undefined, match$18[1], cmtTbl);
+    printedExpression = Res_doc.group(Res_doc.concat({
+              hd: attrs$1,
+              tl: {
+                hd: parametersDoc,
+                tl: {
+                  hd: typConstraintDoc,
+                  tl: {
+                    hd: Res_doc.text(" =>"),
+                    tl: {
+                      hd: returnExprDoc,
+                      tl: /* [] */0
+                    }
+                  }
+                }
+              }
+            }));
+  }
+  var match$24 = e.pexp_desc;
+  var shouldPrintItsOwnAttributes;
+  if (typeof match$24 === "number") {
+    shouldPrintItsOwnAttributes = false;
+  } else {
+    switch (match$24.TAG | 0) {
+      case /* Pexp_match */6 :
+          shouldPrintItsOwnAttributes = Res_parsetree_viewer.isIfLetExpr(e) ? true : false;
+          break;
+      case /* Pexp_construct */9 :
+          shouldPrintItsOwnAttributes = Res_parsetree_viewer.hasJsxAttribute(e.pexp_attributes) ? true : false;
+          break;
+      case /* Pexp_fun */4 :
+      case /* Pexp_apply */5 :
+      case /* Pexp_setfield */13 :
+      case /* Pexp_ifthenelse */15 :
+      case /* Pexp_newtype */31 :
+          shouldPrintItsOwnAttributes = true;
+          break;
+      default:
+        shouldPrintItsOwnAttributes = false;
+    }
+  }
+  var attrs$2 = e.pexp_attributes;
+  if (attrs$2 && !shouldPrintItsOwnAttributes) {
+    return Res_doc.group(Res_doc.concat({
+                    hd: printAttributes(undefined, undefined, attrs$2, cmtTbl),
+                    tl: {
+                      hd: printedExpression,
+                      tl: /* [] */0
+                    }
+                  }));
+  } else {
+    return printedExpression;
+  }
+}
+
+function printPexpFun(inCallback, e, cmtTbl) {
+  var match = Res_parsetree_viewer.funExpr(e);
+  var returnExpr = match[2];
+  var match$1 = Res_parsetree_viewer.processUncurriedAttribute(match[0]);
+  var match$2 = returnExpr.pexp_desc;
+  var match$3;
+  if (typeof match$2 === "number" || match$2.TAG !== /* Pexp_constraint */19) {
+    match$3 = [
+      returnExpr,
+      undefined
+    ];
+  } else {
+    var expr = match$2._0;
+    match$3 = [
+      {
+        pexp_desc: expr.pexp_desc,
+        pexp_loc: expr.pexp_loc,
+        pexp_attributes: List.concat({
+              hd: expr.pexp_attributes,
+              tl: {
+                hd: returnExpr.pexp_attributes,
+                tl: /* [] */0
+              }
+            })
+      },
+      match$2._1
+    ];
+  }
+  var typConstraint = match$3[1];
+  var returnExpr$1 = match$3[0];
+  var parametersDoc = printExprFunParameters(inCallback, match$1[0], typConstraint !== undefined, match[1], cmtTbl);
+  var match$4 = returnExpr$1.pexp_desc;
+  var returnShouldIndent;
+  if (typeof match$4 === "number") {
+    returnShouldIndent = true;
+  } else {
+    switch (match$4.TAG | 0) {
+      case /* Pexp_let */2 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_open */33 :
+          returnShouldIndent = false;
+          break;
+      default:
+        returnShouldIndent = true;
+    }
+  }
+  var match$5 = Res_parsetree_viewer.processBracesAttr(returnExpr$1);
+  var match$6 = returnExpr$1.pexp_desc;
+  var shouldInline;
+  if (match$5[0] !== undefined) {
+    shouldInline = true;
+  } else if (typeof match$6 === "number") {
+    shouldInline = false;
+  } else {
+    switch (match$6.TAG | 0) {
+      case /* Pexp_construct */9 :
+          shouldInline = match$6._1 !== undefined;
+          break;
+      case /* Pexp_tuple */8 :
+      case /* Pexp_record */11 :
+      case /* Pexp_array */14 :
+          shouldInline = true;
+          break;
+      default:
+        shouldInline = false;
+    }
+  }
+  var doc = printExpressionWithComments(returnExpr$1, cmtTbl);
+  var braces = Res_parens.expr(returnExpr$1);
+  var returnDoc = typeof braces === "number" ? (
+      braces !== 0 ? doc : addParens(doc)
+    ) : printBraces(doc, returnExpr$1, braces._0);
+  var returnExprDoc = shouldInline ? Res_doc.concat({
+          hd: Res_doc.space,
+          tl: {
+            hd: returnDoc,
+            tl: /* [] */0
+          }
+        }) : Res_doc.group(returnShouldIndent ? Res_doc.concat({
+                hd: Res_doc.indent(Res_doc.concat({
+                          hd: Res_doc.line,
+                          tl: {
+                            hd: returnDoc,
+                            tl: /* [] */0
+                          }
+                        })),
+                tl: {
+                  hd: inCallback !== 0 ? Res_doc.softLine : Res_doc.nil,
+                  tl: /* [] */0
+                }
+              }) : Res_doc.concat({
+                hd: Res_doc.space,
+                tl: {
+                  hd: returnDoc,
+                  tl: /* [] */0
+                }
+              }));
+  var typConstraintDoc = typConstraint !== undefined ? Res_doc.concat({
+          hd: Res_doc.text(": "),
+          tl: {
+            hd: printTypExpr(typConstraint, cmtTbl),
+            tl: /* [] */0
+          }
+        }) : Res_doc.nil;
+  return Res_doc.concat({
+              hd: printAttributes(undefined, undefined, match$1[1], cmtTbl),
+              tl: {
+                hd: parametersDoc,
+                tl: {
+                  hd: typConstraintDoc,
+                  tl: {
+                    hd: Res_doc.text(" =>"),
+                    tl: {
+                      hd: returnExprDoc,
+                      tl: /* [] */0
+                    }
+                  }
+                }
+              }
+            });
+}
+
+function printTernaryOperand(expr, cmtTbl) {
+  var doc = printExpressionWithComments(expr, cmtTbl);
+  var braces = Res_parens.ternaryOperand(expr);
+  if (typeof braces === "number") {
+    if (braces !== 0) {
+      return doc;
+    } else {
+      return addParens(doc);
+    }
+  } else {
+    return printBraces(doc, expr, braces._0);
+  }
+}
+
+function printSetFieldExpr(attrs, lhs, longidentLoc, rhs, loc, cmtTbl) {
+  var doc = printExpressionWithComments(rhs, cmtTbl);
+  var braces = Res_parens.setFieldExprRhs(rhs);
+  var rhsDoc = typeof braces === "number" ? (
+      braces !== 0 ? doc : addParens(doc)
+    ) : printBraces(doc, rhs, braces._0);
+  var doc$1 = printExpressionWithComments(lhs, cmtTbl);
+  var braces$1 = Res_parens.fieldExpr(lhs);
+  var lhsDoc = typeof braces$1 === "number" ? (
+      braces$1 !== 0 ? doc$1 : addParens(doc$1)
+    ) : printBraces(doc$1, lhs, braces$1._0);
+  var shouldIndent = Res_parsetree_viewer.isBinaryExpression(rhs);
+  var doc$2 = Res_doc.group(Res_doc.concat({
+            hd: lhsDoc,
+            tl: {
+              hd: Res_doc.dot,
+              tl: {
+                hd: printLidentPath(longidentLoc, cmtTbl),
+                tl: {
+                  hd: Res_doc.text(" ="),
+                  tl: {
+                    hd: shouldIndent ? Res_doc.group(Res_doc.indent(Res_doc.concat({
+                                    hd: Res_doc.line,
+                                    tl: {
+                                      hd: rhsDoc,
+                                      tl: /* [] */0
+                                    }
+                                  }))) : Res_doc.concat({
+                            hd: Res_doc.space,
+                            tl: {
+                              hd: rhsDoc,
+                              tl: /* [] */0
+                            }
+                          }),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            }
+          }));
+  var doc$3 = attrs ? Res_doc.group(Res_doc.concat({
+              hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+              tl: {
+                hd: doc$2,
+                tl: /* [] */0
+              }
+            })) : doc$2;
+  return printComments(doc$3, cmtTbl, loc);
+}
+
+function printTemplateLiteral(expr, cmtTbl) {
+  var tag = {
+    contents: "js"
+  };
+  var walkExpr = function (expr) {
+    var match = expr.pexp_desc;
+    if (typeof match !== "number") {
+      switch (match.TAG | 0) {
+        case /* Pexp_constant */1 :
+            var match$1 = match._0;
+            if (match$1.TAG === /* Pconst_string */2) {
+              var prefix = match$1._1;
+              if (prefix !== undefined) {
+                tag.contents = prefix;
+                return printStringContents(match$1._0);
+              }
+              
+            }
+            break;
+        case /* Pexp_apply */5 :
+            var match$2 = match._0.pexp_desc;
+            if (typeof match$2 !== "number" && match$2.TAG === /* Pexp_ident */0) {
+              var match$3 = match$2._0.txt;
+              switch (match$3.TAG | 0) {
+                case /* Lident */0 :
+                    if (match$3._0 === "^") {
+                      var match$4 = match._1;
+                      if (match$4) {
+                        var match$5 = match$4.hd;
+                        if (typeof match$5[0] === "number") {
+                          var match$6 = match$4.tl;
+                          if (match$6) {
+                            var match$7 = match$6.hd;
+                            if (typeof match$7[0] === "number" && !match$6.tl) {
+                              var lhs = walkExpr(match$5[1]);
+                              var rhs = walkExpr(match$7[1]);
+                              return Res_doc.concat({
+                                          hd: lhs,
+                                          tl: {
+                                            hd: rhs,
+                                            tl: /* [] */0
+                                          }
+                                        });
+                            }
+                            
+                          }
+                          
+                        }
+                        
+                      }
+                      
+                    }
+                    break;
+                case /* Ldot */1 :
+                case /* Lapply */2 :
+                    break;
+                
+              }
+            }
+            break;
+        default:
+          
+      }
+    }
+    var doc = printExpressionWithComments(expr, cmtTbl);
+    return Res_doc.group(Res_doc.concat({
+                    hd: Res_doc.text("${"),
+                    tl: {
+                      hd: Res_doc.indent(doc),
+                      tl: {
+                        hd: Res_doc.rbrace,
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+  };
+  var content = walkExpr(expr);
+  return Res_doc.concat({
+              hd: tag.contents === "js" ? Res_doc.nil : Res_doc.text(tag.contents),
+              tl: {
+                hd: Res_doc.text("`"),
+                tl: {
+                  hd: content,
+                  tl: {
+                    hd: Res_doc.text("`"),
+                    tl: /* [] */0
+                  }
+                }
+              }
+            });
+}
+
+function printUnaryExpression(expr, cmtTbl) {
+  var printUnaryOperator = function (op) {
+    var tmp;
+    switch (op) {
+      case "not" :
+          tmp = "!";
+          break;
+      case "~+" :
+          tmp = "+";
+          break;
+      case "~+." :
+          tmp = "+.";
+          break;
+      case "~-" :
+          tmp = "-";
+          break;
+      case "~-." :
+          tmp = "-.";
+          break;
+      default:
+        throw {
+              RE_EXN_ID: "Assert_failure",
+              _1: [
+                "res_printer.res",
+                3472,
+                13
+              ],
+              Error: new Error()
+            };
+    }
+    return Res_doc.text(tmp);
+  };
+  var match = expr.pexp_desc;
+  if (typeof match !== "number" && match.TAG === /* Pexp_apply */5) {
+    var match$1 = match._0.pexp_desc;
+    if (typeof match$1 !== "number" && match$1.TAG === /* Pexp_ident */0) {
+      var operator = match$1._0.txt;
+      switch (operator.TAG | 0) {
+        case /* Lident */0 :
+            var match$2 = match._1;
+            if (match$2) {
+              var match$3 = match$2.hd;
+              if (typeof match$3[0] === "number" && !match$2.tl) {
+                var operand = match$3[1];
+                var doc = printExpressionWithComments(operand, cmtTbl);
+                var braces = Res_parens.unaryExprOperand(operand);
+                var printedOperand = typeof braces === "number" ? (
+                    braces !== 0 ? doc : addParens(doc)
+                  ) : printBraces(doc, operand, braces._0);
+                var doc$1 = Res_doc.concat({
+                      hd: printUnaryOperator(operator._0),
+                      tl: {
+                        hd: printedOperand,
+                        tl: /* [] */0
+                      }
+                    });
+                return printComments(doc$1, cmtTbl, expr.pexp_loc);
+              }
+              
+            }
+            break;
+        case /* Ldot */1 :
+        case /* Lapply */2 :
+            break;
+        
+      }
+    }
+    
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_printer.res",
+          3491,
+          9
+        ],
+        Error: new Error()
+      };
+}
+
+function printBinaryExpression(expr, cmtTbl) {
+  var printBinaryOperator = function (inlineRhs, operator) {
+    var operatorTxt;
+    switch (operator) {
+      case "!=" :
+          operatorTxt = "!==";
+          break;
+      case "<>" :
+          operatorTxt = "!=";
+          break;
+      case "=" :
+          operatorTxt = "==";
+          break;
+      case "==" :
+          operatorTxt = "===";
+          break;
+      case "^" :
+          operatorTxt = "++";
+          break;
+      case "|." :
+          operatorTxt = "->";
+          break;
+      default:
+        operatorTxt = operator;
+    }
+    var spacingBeforeOperator = operator === "|." ? Res_doc.softLine : (
+        operator === "|>" ? Res_doc.line : Res_doc.space
+      );
+    var spacingAfterOperator = operator === "|." ? Res_doc.nil : (
+        operator === "|>" || inlineRhs ? Res_doc.space : Res_doc.line
+      );
+    return Res_doc.concat({
+                hd: spacingBeforeOperator,
+                tl: {
+                  hd: Res_doc.text(operatorTxt),
+                  tl: {
+                    hd: spacingAfterOperator,
+                    tl: /* [] */0
+                  }
+                }
+              });
+  };
+  var printOperand = function (isLhs, expr, parentOperator) {
+    var flatten = function (isLhs, expr, parentOperator) {
+      if (Res_parsetree_viewer.isBinaryExpression(expr)) {
+        var match = expr.pexp_desc;
+        if (typeof match !== "number" && match.TAG === /* Pexp_apply */5) {
+          var match$1 = match._0.pexp_desc;
+          if (typeof match$1 !== "number" && match$1.TAG === /* Pexp_ident */0) {
+            var operator = match$1._0.txt;
+            switch (operator.TAG | 0) {
+              case /* Lident */0 :
+                  var match$2 = match._1;
+                  if (match$2) {
+                    var match$3 = match$2.tl;
+                    if (match$3 && !match$3.tl) {
+                      var right = match$3.hd[1];
+                      var operator$1 = operator._0;
+                      if (Res_parsetree_viewer.flattenableOperators(parentOperator, operator$1) && !Res_parsetree_viewer.hasAttributes(expr.pexp_attributes)) {
+                        var leftPrinted = flatten(true, match$2.hd[1], operator$1);
+                        var match$4 = Res_parsetree_viewer.partitionPrintableAttributes(right.pexp_attributes);
+                        var doc = printExpressionWithComments({
+                              pexp_desc: right.pexp_desc,
+                              pexp_loc: right.pexp_loc,
+                              pexp_attributes: match$4[1]
+                            }, cmtTbl);
+                        var doc$1 = Res_parens.flattenOperandRhs(parentOperator, right) ? Res_doc.concat({
+                                hd: Res_doc.lparen,
+                                tl: {
+                                  hd: doc,
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }) : doc;
+                        var printableAttrs = Res_parsetree_viewer.filterPrintableAttributes(right.pexp_attributes);
+                        var doc$2 = Res_doc.concat({
+                              hd: printAttributes(undefined, undefined, printableAttrs, cmtTbl),
+                              tl: {
+                                hd: doc$1,
+                                tl: /* [] */0
+                              }
+                            });
+                        var rightPrinted = printableAttrs ? addParens(doc$2) : doc$2;
+                        var doc$3 = Res_doc.concat({
+                              hd: leftPrinted,
+                              tl: {
+                                hd: printBinaryOperator(false, operator$1),
+                                tl: {
+                                  hd: rightPrinted,
+                                  tl: /* [] */0
+                                }
+                              }
+                            });
+                        var doc$4 = !isLhs && Res_parens.rhsBinaryExprOperand(operator$1, expr) ? Res_doc.concat({
+                                hd: Res_doc.lparen,
+                                tl: {
+                                  hd: doc$3,
+                                  tl: {
+                                    hd: Res_doc.rparen,
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }) : doc$3;
+                        return printComments(doc$4, cmtTbl, expr.pexp_loc);
+                      }
+                      var doc$5 = printExpressionWithComments({
+                            pexp_desc: expr.pexp_desc,
+                            pexp_loc: expr.pexp_loc,
+                            pexp_attributes: /* [] */0
+                          }, cmtTbl);
+                      var doc$6 = Res_parens.subBinaryExprOperand(parentOperator, operator$1) || expr.pexp_attributes !== /* [] */0 && (Res_parsetree_viewer.isBinaryExpression(expr) || Res_parsetree_viewer.isTernaryExpr(expr)) ? Res_doc.concat({
+                              hd: Res_doc.lparen,
+                              tl: {
+                                hd: doc$5,
+                                tl: {
+                                  hd: Res_doc.rparen,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }) : doc$5;
+                      return Res_doc.concat({
+                                  hd: printAttributes(undefined, undefined, expr.pexp_attributes, cmtTbl),
+                                  tl: {
+                                    hd: doc$6,
+                                    tl: /* [] */0
+                                  }
+                                });
+                    }
+                    
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  break;
+              
+            }
+          }
+          
+        }
+        throw {
+              RE_EXN_ID: "Assert_failure",
+              _1: [
+                "res_printer.res",
+                3590,
+                15
+              ],
+              Error: new Error()
+            };
+      }
+      var match$5 = expr.pexp_desc;
+      if (typeof match$5 !== "number") {
+        switch (match$5.TAG | 0) {
+          case /* Pexp_apply */5 :
+              var match$6 = match$5._0.pexp_desc;
+              if (typeof match$6 !== "number" && match$6.TAG === /* Pexp_ident */0) {
+                var match$7 = match$6._0;
+                var match$8 = match$7.txt;
+                switch (match$8.TAG | 0) {
+                  case /* Lident */0 :
+                      switch (match$8._0) {
+                        case "#=" :
+                            var match$9 = match$5._1;
+                            if (match$9) {
+                              var match$10 = match$9.hd;
+                              if (typeof match$10[0] === "number") {
+                                var match$11 = match$9.tl;
+                                if (match$11) {
+                                  var match$12 = match$11.hd;
+                                  if (typeof match$12[0] === "number" && !match$11.tl) {
+                                    var rhs = match$12[1];
+                                    var rhsDoc = printExpressionWithComments(rhs, cmtTbl);
+                                    var lhsDoc = printExpressionWithComments(match$10[1], cmtTbl);
+                                    var shouldIndent = Res_parsetree_viewer.isBinaryExpression(rhs);
+                                    var doc$7 = Res_doc.group(Res_doc.concat({
+                                              hd: lhsDoc,
+                                              tl: {
+                                                hd: Res_doc.text(" ="),
+                                                tl: {
+                                                  hd: shouldIndent ? Res_doc.group(Res_doc.indent(Res_doc.concat({
+                                                                  hd: Res_doc.line,
+                                                                  tl: {
+                                                                    hd: rhsDoc,
+                                                                    tl: /* [] */0
+                                                                  }
+                                                                }))) : Res_doc.concat({
+                                                          hd: Res_doc.space,
+                                                          tl: {
+                                                            hd: rhsDoc,
+                                                            tl: /* [] */0
+                                                          }
+                                                        }),
+                                                  tl: /* [] */0
+                                                }
+                                              }
+                                            }));
+                                    var attrs = expr.pexp_attributes;
+                                    var doc$8 = attrs ? Res_doc.group(Res_doc.concat({
+                                                hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                                                tl: {
+                                                  hd: doc$7,
+                                                  tl: /* [] */0
+                                                }
+                                              })) : doc$7;
+                                    if (isLhs) {
+                                      return addParens(doc$8);
+                                    } else {
+                                      return doc$8;
+                                    }
+                                  }
+                                  
+                                }
+                                
+                              }
+                              
+                            }
+                            break;
+                        case "^" :
+                            var match$13 = match$5._1;
+                            if (match$13 && typeof match$13.hd[0] === "number") {
+                              var match$14 = match$13.tl;
+                              if (match$14 && typeof match$14.hd[0] === "number" && !match$14.tl && match$7.loc.loc_ghost) {
+                                var doc$9 = printTemplateLiteral(expr, cmtTbl);
+                                return printComments(doc$9, cmtTbl, expr.pexp_loc);
+                              }
+                              
+                            }
+                            break;
+                        default:
+                          
+                      }
+                      break;
+                  case /* Ldot */1 :
+                  case /* Lapply */2 :
+                      break;
+                  
+                }
+              }
+              break;
+          case /* Pexp_setfield */13 :
+              var doc$10 = printSetFieldExpr(expr.pexp_attributes, match$5._0, match$5._1, match$5._2, expr.pexp_loc, cmtTbl);
+              if (isLhs) {
+                return addParens(doc$10);
+              } else {
+                return doc$10;
+              }
+          default:
+            
+        }
+      }
+      var doc$11 = printExpressionWithComments(expr, cmtTbl);
+      var braces = Res_parens.binaryExprOperand(isLhs, expr);
+      if (typeof braces === "number") {
+        if (braces !== 0) {
+          return doc$11;
+        } else {
+          return addParens(doc$11);
+        }
+      } else {
+        return printBraces(doc$11, expr, braces._0);
+      }
+    };
+    return flatten(isLhs, expr, parentOperator);
+  };
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    return Res_doc.nil;
+  }
+  if (match.TAG !== /* Pexp_apply */5) {
+    return Res_doc.nil;
+  }
+  var match$1 = match._0.pexp_desc;
+  if (typeof match$1 === "number") {
+    return Res_doc.nil;
+  }
+  if (match$1.TAG !== /* Pexp_ident */0) {
+    return Res_doc.nil;
+  }
+  var op = match$1._0.txt;
+  switch (op.TAG | 0) {
+    case /* Lident */0 :
+        var op$1 = op._0;
+        var exit = 0;
+        switch (op$1) {
+          case "|." :
+          case "|>" :
+              exit = 2;
+              break;
+          default:
+            
+        }
+        if (exit === 2) {
+          var match$2 = match._1;
+          if (!match$2) {
+            return Res_doc.nil;
+          }
+          var match$3 = match$2.hd;
+          if (typeof match$3[0] !== "number") {
+            return Res_doc.nil;
+          }
+          var match$4 = match$2.tl;
+          if (!match$4) {
+            return Res_doc.nil;
+          }
+          var match$5 = match$4.hd;
+          if (typeof match$5[0] !== "number") {
+            return Res_doc.nil;
+          }
+          if (match$4.tl) {
+            return Res_doc.nil;
+          }
+          var rhs = match$5[1];
+          var lhs = match$3[1];
+          if (!(Res_parsetree_viewer.isBinaryExpression(lhs) || Res_parsetree_viewer.isBinaryExpression(rhs))) {
+            var lhsHasCommentBelow = hasCommentBelow(cmtTbl, lhs.pexp_loc);
+            var lhsDoc = printOperand(true, lhs, op$1);
+            var rhsDoc = printOperand(false, rhs, op$1);
+            var tmp;
+            if (lhsHasCommentBelow) {
+              switch (op$1) {
+                case "|." :
+                    tmp = Res_doc.concat({
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.text("->"),
+                            tl: /* [] */0
+                          }
+                        });
+                    break;
+                case "|>" :
+                    tmp = Res_doc.concat({
+                          hd: Res_doc.line,
+                          tl: {
+                            hd: Res_doc.text("|> "),
+                            tl: /* [] */0
+                          }
+                        });
+                    break;
+                default:
+                  tmp = Res_doc.nil;
+              }
+            } else {
+              switch (op$1) {
+                case "|." :
+                    tmp = Res_doc.text("->");
+                    break;
+                case "|>" :
+                    tmp = Res_doc.text(" |> ");
+                    break;
+                default:
+                  tmp = Res_doc.nil;
+              }
+            }
+            return Res_doc.group(Res_doc.concat({
+                            hd: lhsDoc,
+                            tl: {
+                              hd: tmp,
+                              tl: {
+                                hd: rhsDoc,
+                                tl: /* [] */0
+                              }
+                            }
+                          }));
+          }
+          
+        }
+        var match$6 = match._1;
+        if (!match$6) {
+          return Res_doc.nil;
+        }
+        var match$7 = match$6.hd;
+        if (typeof match$7[0] !== "number") {
+          return Res_doc.nil;
+        }
+        var match$8 = match$6.tl;
+        if (!match$8) {
+          return Res_doc.nil;
+        }
+        var match$9 = match$8.hd;
+        if (typeof match$9[0] !== "number") {
+          return Res_doc.nil;
+        }
+        if (match$8.tl) {
+          return Res_doc.nil;
+        }
+        var rhs$1 = match$9[1];
+        var rhsDoc$1 = printOperand(false, rhs$1, op$1);
+        var operatorWithRhs = Res_doc.concat({
+              hd: printBinaryOperator(Res_parsetree_viewer.shouldInlineRhsBinaryExpr(rhs$1), op$1),
+              tl: {
+                hd: rhsDoc$1,
+                tl: /* [] */0
+              }
+            });
+        var right = Res_parsetree_viewer.shouldIndentBinaryExpr(expr) ? Res_doc.group(Res_doc.indent(operatorWithRhs)) : operatorWithRhs;
+        var doc = Res_doc.group(Res_doc.concat({
+                  hd: printOperand(true, match$7[1], op$1),
+                  tl: {
+                    hd: right,
+                    tl: /* [] */0
+                  }
+                }));
+        var bracesLoc = Res_parens.binaryExpr({
+              pexp_desc: expr.pexp_desc,
+              pexp_loc: expr.pexp_loc,
+              pexp_attributes: List.filter(function (attr) {
+                      if (attr[0].txt === "ns.braces") {
+                        return false;
+                      } else {
+                        return true;
+                      }
+                    })(expr.pexp_attributes)
+            });
+        return Res_doc.group(Res_doc.concat({
+                        hd: printAttributes(undefined, undefined, expr.pexp_attributes, cmtTbl),
+                        tl: {
+                          hd: typeof bracesLoc === "number" ? (
+                              bracesLoc !== 0 ? doc : addParens(doc)
+                            ) : printBraces(doc, expr, bracesLoc._0),
+                          tl: /* [] */0
+                        }
+                      }));
+        break;
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        return Res_doc.nil;
+    
+  }
+}
+
+function printPexpApply(expr, cmtTbl) {
+  var match = expr.pexp_desc;
+  if (typeof match === "number") {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_printer.res",
+            3945,
+            9
+          ],
+          Error: new Error()
+        };
+  }
+  if (match.TAG === /* Pexp_apply */5) {
+    var callExpr = match._0;
+    var lident = callExpr.pexp_desc;
+    if (typeof lident !== "number" && lident.TAG === /* Pexp_ident */0) {
+      var lident$1 = lident._0;
+      var match$1 = lident$1.txt;
+      var exit = 0;
+      switch (match$1.TAG | 0) {
+        case /* Lident */0 :
+            switch (match$1._0) {
+              case "##" :
+                  var match$2 = match._1;
+                  if (match$2) {
+                    var match$3 = match$2.hd;
+                    if (typeof match$3[0] === "number") {
+                      var match$4 = match$2.tl;
+                      if (match$4) {
+                        var match$5 = match$4.hd;
+                        if (typeof match$5[0] === "number" && !match$4.tl) {
+                          var memberExpr = match$5[1];
+                          var parentExpr = match$3[1];
+                          var doc = printExpressionWithComments(parentExpr, cmtTbl);
+                          var braces = Res_parens.unaryExprOperand(parentExpr);
+                          var parentDoc = typeof braces === "number" ? (
+                              braces !== 0 ? doc : addParens(doc)
+                            ) : printBraces(doc, parentExpr, braces._0);
+                          var lident$2 = memberExpr.pexp_desc;
+                          var memberDoc;
+                          memberDoc = typeof lident$2 === "number" || lident$2.TAG !== /* Pexp_ident */0 ? printExpressionWithComments(memberExpr, cmtTbl) : printComments(printLongident(lident$2._0.txt), cmtTbl, memberExpr.pexp_loc);
+                          var member = Res_doc.concat({
+                                hd: Res_doc.text("\""),
+                                tl: {
+                                  hd: memberDoc,
+                                  tl: {
+                                    hd: Res_doc.text("\""),
+                                    tl: /* [] */0
+                                  }
+                                }
+                              });
+                          return Res_doc.group(Res_doc.concat({
+                                          hd: printAttributes(undefined, undefined, expr.pexp_attributes, cmtTbl),
+                                          tl: {
+                                            hd: parentDoc,
+                                            tl: {
+                                              hd: Res_doc.lbracket,
+                                              tl: {
+                                                hd: member,
+                                                tl: {
+                                                  hd: Res_doc.rbracket,
+                                                  tl: /* [] */0
+                                                }
+                                              }
+                                            }
+                                          }
+                                        }));
+                        }
+                        exit = 2;
+                      } else {
+                        exit = 2;
+                      }
+                    } else {
+                      exit = 2;
+                    }
+                  } else {
+                    exit = 2;
+                  }
+                  break;
+              case "#=" :
+                  var match$6 = match._1;
+                  if (match$6) {
+                    var match$7 = match$6.hd;
+                    if (typeof match$7[0] === "number") {
+                      var match$8 = match$6.tl;
+                      if (match$8) {
+                        var match$9 = match$8.hd;
+                        if (typeof match$9[0] === "number" && !match$8.tl) {
+                          var rhs = match$9[1];
+                          var doc$1 = printExpressionWithComments(rhs, cmtTbl);
+                          var braces$1 = Res_parens.expr(rhs);
+                          var rhsDoc = typeof braces$1 === "number" ? (
+                              braces$1 !== 0 ? doc$1 : addParens(doc$1)
+                            ) : printBraces(doc$1, rhs, braces$1._0);
+                          var shouldIndent = !Res_parsetree_viewer.isBracedExpr(rhs) && Res_parsetree_viewer.isBinaryExpression(rhs);
+                          var doc$2 = Res_doc.group(Res_doc.concat({
+                                    hd: printExpressionWithComments(match$7[1], cmtTbl),
+                                    tl: {
+                                      hd: Res_doc.text(" ="),
+                                      tl: {
+                                        hd: shouldIndent ? Res_doc.group(Res_doc.indent(Res_doc.concat({
+                                                        hd: Res_doc.line,
+                                                        tl: {
+                                                          hd: rhsDoc,
+                                                          tl: /* [] */0
+                                                        }
+                                                      }))) : Res_doc.concat({
+                                                hd: Res_doc.space,
+                                                tl: {
+                                                  hd: rhsDoc,
+                                                  tl: /* [] */0
+                                                }
+                                              }),
+                                        tl: /* [] */0
+                                      }
+                                    }
+                                  }));
+                          var attrs = expr.pexp_attributes;
+                          if (attrs) {
+                            return Res_doc.group(Res_doc.concat({
+                                            hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                                            tl: {
+                                              hd: doc$2,
+                                              tl: /* [] */0
+                                            }
+                                          }));
+                          } else {
+                            return doc$2;
+                          }
+                        }
+                        exit = 2;
+                      } else {
+                        exit = 2;
+                      }
+                    } else {
+                      exit = 2;
+                    }
+                  } else {
+                    exit = 2;
+                  }
+                  break;
+              default:
+                exit = 2;
+            }
+            break;
+        case /* Ldot */1 :
+            var match$10 = match$1._0;
+            switch (match$10.TAG | 0) {
+              case /* Lident */0 :
+                  if (match$10._0 === "Array") {
+                    switch (match$1._1) {
+                      case "get" :
+                          var match$11 = match._1;
+                          if (match$11) {
+                            var match$12 = match$11.hd;
+                            if (typeof match$12[0] === "number") {
+                              var match$13 = match$11.tl;
+                              if (match$13) {
+                                var match$14 = match$13.hd;
+                                if (typeof match$14[0] === "number" && !match$13.tl) {
+                                  var memberExpr$1 = match$14[1];
+                                  var parentExpr$1 = match$12[1];
+                                  if (Res_parsetree_viewer.isRewrittenUnderscoreApplySugar(parentExpr$1)) {
+                                    exit = 2;
+                                  } else {
+                                    var doc$3 = printExpressionWithComments(memberExpr$1, cmtTbl);
+                                    var braces$2 = Res_parens.expr(memberExpr$1);
+                                    var memberDoc$1 = typeof braces$2 === "number" ? (
+                                        braces$2 !== 0 ? doc$3 : addParens(doc$3)
+                                      ) : printBraces(doc$3, memberExpr$1, braces$2._0);
+                                    var match$15 = memberExpr$1.pexp_desc;
+                                    var shouldInline;
+                                    if (typeof match$15 === "number") {
+                                      shouldInline = false;
+                                    } else {
+                                      switch (match$15.TAG | 0) {
+                                        case /* Pexp_ident */0 :
+                                        case /* Pexp_constant */1 :
+                                            shouldInline = true;
+                                            break;
+                                        default:
+                                          shouldInline = false;
+                                      }
+                                    }
+                                    var member$1 = shouldInline ? memberDoc$1 : Res_doc.concat({
+                                            hd: Res_doc.indent(Res_doc.concat({
+                                                      hd: Res_doc.softLine,
+                                                      tl: {
+                                                        hd: memberDoc$1,
+                                                        tl: /* [] */0
+                                                      }
+                                                    })),
+                                            tl: {
+                                              hd: Res_doc.softLine,
+                                              tl: /* [] */0
+                                            }
+                                          });
+                                    var doc$4 = printExpressionWithComments(parentExpr$1, cmtTbl);
+                                    var braces$3 = Res_parens.unaryExprOperand(parentExpr$1);
+                                    var parentDoc$1 = typeof braces$3 === "number" ? (
+                                        braces$3 !== 0 ? doc$4 : addParens(doc$4)
+                                      ) : printBraces(doc$4, parentExpr$1, braces$3._0);
+                                    return Res_doc.group(Res_doc.concat({
+                                                    hd: printAttributes(undefined, undefined, expr.pexp_attributes, cmtTbl),
+                                                    tl: {
+                                                      hd: parentDoc$1,
+                                                      tl: {
+                                                        hd: Res_doc.lbracket,
+                                                        tl: {
+                                                          hd: member$1,
+                                                          tl: {
+                                                            hd: Res_doc.rbracket,
+                                                            tl: /* [] */0
+                                                          }
+                                                        }
+                                                      }
+                                                    }
+                                                  }));
+                                  }
+                                } else {
+                                  exit = 2;
+                                }
+                              } else {
+                                exit = 2;
+                              }
+                            } else {
+                              exit = 2;
+                            }
+                          } else {
+                            exit = 2;
+                          }
+                          break;
+                      case "set" :
+                          var match$16 = match._1;
+                          if (match$16) {
+                            var match$17 = match$16.hd;
+                            if (typeof match$17[0] === "number") {
+                              var match$18 = match$16.tl;
+                              if (match$18) {
+                                var match$19 = match$18.hd;
+                                if (typeof match$19[0] === "number") {
+                                  var match$20 = match$18.tl;
+                                  if (match$20) {
+                                    var match$21 = match$20.hd;
+                                    if (typeof match$21[0] === "number" && !match$20.tl) {
+                                      var targetExpr = match$21[1];
+                                      var memberExpr$2 = match$19[1];
+                                      var parentExpr$2 = match$17[1];
+                                      var doc$5 = printExpressionWithComments(memberExpr$2, cmtTbl);
+                                      var braces$4 = Res_parens.expr(memberExpr$2);
+                                      var memberDoc$2 = typeof braces$4 === "number" ? (
+                                          braces$4 !== 0 ? doc$5 : addParens(doc$5)
+                                        ) : printBraces(doc$5, memberExpr$2, braces$4._0);
+                                      var match$22 = memberExpr$2.pexp_desc;
+                                      var shouldInline$1;
+                                      if (typeof match$22 === "number") {
+                                        shouldInline$1 = false;
+                                      } else {
+                                        switch (match$22.TAG | 0) {
+                                          case /* Pexp_ident */0 :
+                                          case /* Pexp_constant */1 :
+                                              shouldInline$1 = true;
+                                              break;
+                                          default:
+                                            shouldInline$1 = false;
+                                        }
+                                      }
+                                      var member$2 = shouldInline$1 ? memberDoc$2 : Res_doc.concat({
+                                              hd: Res_doc.indent(Res_doc.concat({
+                                                        hd: Res_doc.softLine,
+                                                        tl: {
+                                                          hd: memberDoc$2,
+                                                          tl: /* [] */0
+                                                        }
+                                                      })),
+                                              tl: {
+                                                hd: Res_doc.softLine,
+                                                tl: /* [] */0
+                                              }
+                                            });
+                                      var shouldIndentTargetExpr;
+                                      if (Res_parsetree_viewer.isBracedExpr(targetExpr)) {
+                                        shouldIndentTargetExpr = false;
+                                      } else if (Res_parsetree_viewer.isBinaryExpression(targetExpr)) {
+                                        shouldIndentTargetExpr = true;
+                                      } else {
+                                        var match$23 = targetExpr.pexp_desc;
+                                        var tmp;
+                                        var exit$1 = 0;
+                                        if (typeof match$23 === "number") {
+                                          exit$1 = 3;
+                                        } else {
+                                          switch (match$23.TAG | 0) {
+                                            case /* Pexp_ifthenelse */15 :
+                                                var match$24 = targetExpr.pexp_attributes;
+                                                if (match$24) {
+                                                  var ifExpr = match$23._0;
+                                                  if (match$24.hd[0].txt === "ns.ternary" && !match$24.tl) {
+                                                    tmp = Res_parsetree_viewer.isBinaryExpression(ifExpr) || Res_parsetree_viewer.hasAttributes(ifExpr.pexp_attributes);
+                                                  } else {
+                                                    exit$1 = 3;
+                                                  }
+                                                } else {
+                                                  exit$1 = 3;
+                                                }
+                                                break;
+                                            case /* Pexp_newtype */31 :
+                                                tmp = false;
+                                                break;
+                                            default:
+                                              exit$1 = 3;
+                                          }
+                                        }
+                                        if (exit$1 === 3) {
+                                          tmp = Res_parsetree_viewer.hasAttributes(targetExpr.pexp_attributes) || Res_parsetree_viewer.isArrayAccess(targetExpr);
+                                        }
+                                        shouldIndentTargetExpr = tmp;
+                                      }
+                                      var doc$6 = printExpressionWithComments(targetExpr, cmtTbl);
+                                      var braces$5 = Res_parens.expr(targetExpr);
+                                      var targetExpr$1 = typeof braces$5 === "number" ? (
+                                          braces$5 !== 0 ? doc$6 : addParens(doc$6)
+                                        ) : printBraces(doc$6, targetExpr, braces$5._0);
+                                      var doc$7 = printExpressionWithComments(parentExpr$2, cmtTbl);
+                                      var braces$6 = Res_parens.unaryExprOperand(parentExpr$2);
+                                      var parentDoc$2 = typeof braces$6 === "number" ? (
+                                          braces$6 !== 0 ? doc$7 : addParens(doc$7)
+                                        ) : printBraces(doc$7, parentExpr$2, braces$6._0);
+                                      return Res_doc.group(Res_doc.concat({
+                                                      hd: printAttributes(undefined, undefined, expr.pexp_attributes, cmtTbl),
+                                                      tl: {
+                                                        hd: parentDoc$2,
+                                                        tl: {
+                                                          hd: Res_doc.lbracket,
+                                                          tl: {
+                                                            hd: member$2,
+                                                            tl: {
+                                                              hd: Res_doc.rbracket,
+                                                              tl: {
+                                                                hd: Res_doc.text(" ="),
+                                                                tl: {
+                                                                  hd: shouldIndentTargetExpr ? Res_doc.indent(Res_doc.concat({
+                                                                              hd: Res_doc.line,
+                                                                              tl: {
+                                                                                hd: targetExpr$1,
+                                                                                tl: /* [] */0
+                                                                              }
+                                                                            })) : Res_doc.concat({
+                                                                          hd: Res_doc.space,
+                                                                          tl: {
+                                                                            hd: targetExpr$1,
+                                                                            tl: /* [] */0
+                                                                          }
+                                                                        }),
+                                                                  tl: /* [] */0
+                                                                }
+                                                              }
+                                                            }
+                                                          }
+                                                        }
+                                                      }
+                                                    }));
+                                    }
+                                    exit = 2;
+                                  } else {
+                                    exit = 2;
+                                  }
+                                } else {
+                                  exit = 2;
+                                }
+                              } else {
+                                exit = 2;
+                              }
+                            } else {
+                              exit = 2;
+                            }
+                          } else {
+                            exit = 2;
+                          }
+                          break;
+                      default:
+                        exit = 2;
+                    }
+                  } else {
+                    exit = 2;
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  exit = 2;
+                  break;
+              
+            }
+            break;
+        case /* Lapply */2 :
+            exit = 2;
+            break;
+        
+      }
+      if (exit === 2 && Res_parsetree_viewer.isJsxExpression(expr)) {
+        return printJsxExpression(lident$1, match._1, cmtTbl);
+      }
+      
+    }
+    var args = List.map((function (param) {
+            return [
+                    param[0],
+                    Res_parsetree_viewer.rewriteUnderscoreApply(param[1])
+                  ];
+          }), match._1);
+    var match$25 = Res_parsetree_viewer.processUncurriedAttribute(expr.pexp_attributes);
+    var attrs$1 = match$25[1];
+    var uncurried = match$25[0];
+    var doc$8 = printExpressionWithComments(callExpr, cmtTbl);
+    var braces$7 = Res_parens.callExpr(callExpr);
+    var callExprDoc = typeof braces$7 === "number" ? (
+        braces$7 !== 0 ? doc$8 : addParens(doc$8)
+      ) : printBraces(doc$8, callExpr, braces$7._0);
+    if (Res_parsetree_viewer.requiresSpecialCallbackPrintingFirstArg(args)) {
+      var argsDoc = printArgumentsWithCallbackInFirstPosition(uncurried, args, cmtTbl);
+      return Res_doc.concat({
+                  hd: printAttributes(undefined, undefined, attrs$1, cmtTbl),
+                  tl: {
+                    hd: callExprDoc,
+                    tl: {
+                      hd: argsDoc,
+                      tl: /* [] */0
+                    }
+                  }
+                });
+    }
+    if (Res_parsetree_viewer.requiresSpecialCallbackPrintingLastArg(args)) {
+      var argsDoc$1 = printArgumentsWithCallbackInLastPosition(uncurried, args, cmtTbl);
+      var maybeBreakParent = Res_doc.willBreak(argsDoc$1) ? Res_doc.breakParent : Res_doc.nil;
+      return Res_doc.concat({
+                  hd: maybeBreakParent,
+                  tl: {
+                    hd: printAttributes(undefined, undefined, attrs$1, cmtTbl),
+                    tl: {
+                      hd: callExprDoc,
+                      tl: {
+                        hd: argsDoc$1,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                });
+    }
+    var argsDoc$2 = printArguments(uncurried, args, cmtTbl);
+    return Res_doc.concat({
+                hd: printAttributes(undefined, undefined, attrs$1, cmtTbl),
+                tl: {
+                  hd: callExprDoc,
+                  tl: {
+                    hd: argsDoc$2,
+                    tl: /* [] */0
+                  }
+                }
+              });
+  }
+  throw {
+        RE_EXN_ID: "Assert_failure",
+        _1: [
+          "res_printer.res",
+          3945,
+          9
+        ],
+        Error: new Error()
+      };
+}
+
+function printJsxExpression(lident, args, cmtTbl) {
+  var name = printJsxName(lident);
+  var match = printJsxProps(args, cmtTbl);
+  var children = match[1];
+  var isSelfClosing;
+  if (children !== undefined) {
+    var match$1 = children.pexp_desc;
+    if (typeof match$1 === "number" || match$1.TAG !== /* Pexp_construct */9) {
+      isSelfClosing = false;
+    } else {
+      var match$2 = match$1._0.txt;
+      switch (match$2.TAG | 0) {
+        case /* Lident */0 :
+            isSelfClosing = match$2._0 === "[]" ? match$1._1 === undefined : false;
+            break;
+        case /* Ldot */1 :
+        case /* Lapply */2 :
+            isSelfClosing = false;
+            break;
+        
+      }
+    }
+  } else {
+    isSelfClosing = false;
+  }
+  return Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.group(Res_doc.concat({
+                            hd: printComments(Res_doc.concat({
+                                      hd: Res_doc.lessThan,
+                                      tl: {
+                                        hd: name,
+                                        tl: /* [] */0
+                                      }
+                                    }), cmtTbl, lident.loc),
+                            tl: {
+                              hd: match[0],
+                              tl: {
+                                hd: isSelfClosing ? Res_doc.concat({
+                                        hd: Res_doc.line,
+                                        tl: {
+                                          hd: Res_doc.text("/>"),
+                                          tl: /* [] */0
+                                        }
+                                      }) : Res_doc.nil,
+                                tl: /* [] */0
+                              }
+                            }
+                          })),
+                  tl: {
+                    hd: isSelfClosing ? Res_doc.nil : Res_doc.concat({
+                            hd: Res_doc.greaterThan,
+                            tl: {
+                              hd: Res_doc.indent(Res_doc.concat({
+                                        hd: Res_doc.line,
+                                        tl: {
+                                          hd: children !== undefined ? printJsxChildren(children, cmtTbl) : Res_doc.nil,
+                                          tl: /* [] */0
+                                        }
+                                      })),
+                              tl: {
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: Res_doc.text("</"),
+                                  tl: {
+                                    hd: name,
+                                    tl: {
+                                      hd: Res_doc.greaterThan,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }),
+                    tl: /* [] */0
+                  }
+                }));
+}
+
+function printJsxFragment(expr, cmtTbl) {
+  var opening = Res_doc.text("<>");
+  var closing = Res_doc.text("</>");
+  var match = expr.pexp_desc;
+  var tmp;
+  var exit = 0;
+  if (typeof match === "number" || match.TAG !== /* Pexp_construct */9) {
+    exit = 1;
+  } else {
+    var match$1 = match._0.txt;
+    switch (match$1.TAG | 0) {
+      case /* Lident */0 :
+          if (match$1._0 === "[]" && match._1 === undefined) {
+            tmp = Res_doc.nil;
+          } else {
+            exit = 1;
+          }
+          break;
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          exit = 1;
+          break;
+      
+    }
+  }
+  if (exit === 1) {
+    tmp = Res_doc.indent(Res_doc.concat({
+              hd: Res_doc.line,
+              tl: {
+                hd: printJsxChildren(expr, cmtTbl),
+                tl: /* [] */0
+              }
+            }));
+  }
+  return Res_doc.group(Res_doc.concat({
+                  hd: opening,
+                  tl: {
+                    hd: tmp,
+                    tl: {
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: closing,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function printJsxChildren(childrenExpr, cmtTbl) {
+  var match = childrenExpr.pexp_desc;
+  var exit = 0;
+  if (typeof match === "number" || match.TAG !== /* Pexp_construct */9) {
+    exit = 1;
+  } else {
+    var match$1 = match._0.txt;
+    switch (match$1.TAG | 0) {
+      case /* Lident */0 :
+          if (match$1._0 === "::") {
+            var match$2 = Res_parsetree_viewer.collectListExpressions(childrenExpr);
+            return Res_doc.group(Res_doc.join(Res_doc.line, List.map((function (expr) {
+                                  var leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, expr.pexp_loc);
+                                  var exprDoc = printExpressionWithComments(expr, cmtTbl);
+                                  var match = Res_parens.jsxChildExpr(expr);
+                                  if (typeof match === "number" && match !== 0) {
+                                    return exprDoc;
+                                  }
+                                  var innerDoc = Res_parens.bracedExpr(expr) ? addParens(exprDoc) : exprDoc;
+                                  if (leadingLineCommentPresent) {
+                                    return addBraces(innerDoc);
+                                  } else {
+                                    return Res_doc.concat({
+                                                hd: Res_doc.lbrace,
+                                                tl: {
+                                                  hd: innerDoc,
+                                                  tl: {
+                                                    hd: Res_doc.rbrace,
+                                                    tl: /* [] */0
+                                                  }
+                                                }
+                                              });
+                                  }
+                                }), match$2[0])));
+          }
+          exit = 1;
+          break;
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          exit = 1;
+          break;
+      
+    }
+  }
+  if (exit === 1) {
+    var leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, childrenExpr.pexp_loc);
+    var exprDoc = printExpressionWithComments(childrenExpr, cmtTbl);
+    var match$3 = Res_parens.jsxChildExpr(childrenExpr);
+    var tmp;
+    var exit$1 = 0;
+    if (typeof match$3 === "number" && match$3 !== 0) {
+      tmp = exprDoc;
+    } else {
+      exit$1 = 2;
+    }
+    if (exit$1 === 2) {
+      var innerDoc = Res_parens.bracedExpr(childrenExpr) ? addParens(exprDoc) : exprDoc;
+      tmp = leadingLineCommentPresent ? addBraces(innerDoc) : Res_doc.concat({
+              hd: Res_doc.lbrace,
+              tl: {
+                hd: innerDoc,
+                tl: {
+                  hd: Res_doc.rbrace,
+                  tl: /* [] */0
+                }
+              }
+            });
+    }
+    return Res_doc.concat({
+                hd: Res_doc.dotdotdot,
+                tl: {
+                  hd: tmp,
+                  tl: /* [] */0
+                }
+              });
+  }
+  
+}
+
+function printJsxProps(args, cmtTbl) {
+  var _props = /* [] */0;
+  var _args = args;
+  while(true) {
+    var args$1 = _args;
+    var props = _props;
+    if (!args$1) {
+      return [
+              Res_doc.nil,
+              undefined
+            ];
+    }
+    var arg = args$1.hd;
+    var match = arg[0];
+    if (typeof match !== "number" && match.TAG === /* Labelled */0 && match._0 === "children") {
+      var match$1 = args$1.tl;
+      if (match$1) {
+        var match$2 = match$1.hd;
+        if (typeof match$2[0] === "number") {
+          var match$3 = match$2[1].pexp_desc;
+          if (typeof match$3 !== "number" && match$3.TAG === /* Pexp_construct */9) {
+            var match$4 = match$3._0.txt;
+            switch (match$4.TAG | 0) {
+              case /* Lident */0 :
+                  if (match$4._0 === "()" && match$3._1 === undefined && !match$1.tl) {
+                    var formattedProps = Res_doc.indent(props ? Res_doc.concat({
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: Res_doc.group(Res_doc.join(Res_doc.line, List.rev(props))),
+                                  tl: /* [] */0
+                                }
+                              }) : Res_doc.nil);
+                    return [
+                            formattedProps,
+                            arg[1]
+                          ];
+                  }
+                  break;
+              case /* Ldot */1 :
+              case /* Lapply */2 :
+                  break;
+              
+            }
+          }
+          
+        }
+        
+      }
+      
+    }
+    var propDoc = printJsxProp(arg, cmtTbl);
+    _args = args$1.tl;
+    _props = {
+      hd: propDoc,
+      tl: props
+    };
+    continue ;
+  };
+}
+
+function printJsxProp(arg, cmtTbl) {
+  var lbl = arg[0];
+  var exit = 0;
+  var exit$1 = 0;
+  if (typeof lbl === "number") {
+    exit = 1;
+  } else {
+    var match = arg[1];
+    var match$1 = match.pexp_desc;
+    if (typeof match$1 === "number" || match$1.TAG !== /* Pexp_ident */0) {
+      exit$1 = 2;
+    } else {
+      var ident = match$1._0.txt;
+      switch (ident.TAG | 0) {
+        case /* Lident */0 :
+            var match$2 = match.pexp_attributes;
+            if (match$2) {
+              var match$3 = match$2.hd[0];
+              var ident$1 = ident._0;
+              if (match$3.txt === "ns.namedArgLoc" && !(match$2.tl || lbl._0 !== ident$1)) {
+                var argLoc = match$3.loc;
+                if (typeof lbl === "number") {
+                  return Res_doc.nil;
+                }
+                if (lbl.TAG === /* Labelled */0) {
+                  return printComments(printIdentLike(undefined, ident$1), cmtTbl, argLoc);
+                }
+                var doc = Res_doc.concat({
+                      hd: Res_doc.question,
+                      tl: {
+                        hd: printIdentLike(undefined, ident$1),
+                        tl: /* [] */0
+                      }
+                    });
+                return printComments(doc, cmtTbl, argLoc);
+              } else {
+                exit = 1;
+              }
+            } else {
+              exit$1 = 2;
+            }
+            break;
+        case /* Ldot */1 :
+        case /* Lapply */2 :
+            exit = 1;
+            break;
+        
+      }
+    }
+  }
+  if (exit$1 === 2) {
+    var match$4 = arg[1];
+    var match$5 = match$4.pexp_desc;
+    if (typeof match$5 === "number" || match$5.TAG !== /* Pexp_ident */0) {
+      exit = 1;
+    } else {
+      var ident$2 = match$5._0.txt;
+      switch (ident$2.TAG | 0) {
+        case /* Lident */0 :
+            if (match$4.pexp_attributes) {
+              exit = 1;
+            } else {
+              var ident$3 = ident$2._0;
+              if (lbl._0 === ident$3) {
+                if (typeof lbl === "number") {
+                  return Res_doc.nil;
+                } else if (lbl.TAG === /* Labelled */0) {
+                  return printIdentLike(undefined, ident$3);
+                } else {
+                  return Res_doc.concat({
+                              hd: Res_doc.question,
+                              tl: {
+                                hd: printIdentLike(undefined, ident$3),
+                                tl: /* [] */0
+                              }
+                            });
+                }
+              }
+              exit = 1;
+            }
+            break;
+        case /* Ldot */1 :
+        case /* Lapply */2 :
+            exit = 1;
+            break;
+        
+      }
+    }
+  }
+  if (exit === 1) {
+    var expr = arg[1];
+    var match$6 = expr.pexp_attributes;
+    var match$7;
+    if (match$6) {
+      var match$8 = match$6.hd[0];
+      match$7 = match$8.txt === "ns.namedArgLoc" ? [
+          match$8.loc,
+          {
+            pexp_desc: expr.pexp_desc,
+            pexp_loc: expr.pexp_loc,
+            pexp_attributes: match$6.tl
+          }
+        ] : [
+          $$Location.none,
+          expr
+        ];
+    } else {
+      match$7 = [
+        $$Location.none,
+        expr
+      ];
+    }
+    var expr$1 = match$7[1];
+    var argLoc$1 = match$7[0];
+    var lblDoc;
+    if (typeof lbl === "number") {
+      lblDoc = Res_doc.nil;
+    } else if (lbl.TAG === /* Labelled */0) {
+      var lbl$1 = printComments(printIdentLike(undefined, lbl._0), cmtTbl, argLoc$1);
+      lblDoc = Res_doc.concat({
+            hd: lbl$1,
+            tl: {
+              hd: Res_doc.equal,
+              tl: /* [] */0
+            }
+          });
+    } else {
+      var lbl$2 = printComments(printIdentLike(undefined, lbl._0), cmtTbl, argLoc$1);
+      lblDoc = Res_doc.concat({
+            hd: lbl$2,
+            tl: {
+              hd: Res_doc.equal,
+              tl: {
+                hd: Res_doc.question,
+                tl: /* [] */0
+              }
+            }
+          });
+    }
+    var leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, expr$1.pexp_loc);
+    var doc$1 = printExpressionWithComments(expr$1, cmtTbl);
+    var match$9 = Res_parens.jsxPropExpr(expr$1);
+    var exprDoc;
+    var exit$2 = 0;
+    if (typeof match$9 === "number" && match$9 !== 0) {
+      exprDoc = doc$1;
+    } else {
+      exit$2 = 2;
+    }
+    if (exit$2 === 2) {
+      var innerDoc = Res_parens.bracedExpr(expr$1) ? addParens(doc$1) : doc$1;
+      exprDoc = leadingLineCommentPresent ? addBraces(innerDoc) : Res_doc.concat({
+              hd: Res_doc.lbrace,
+              tl: {
+                hd: innerDoc,
+                tl: {
+                  hd: Res_doc.rbrace,
+                  tl: /* [] */0
+                }
+              }
+            });
+    }
+    var fullLoc_loc_start = argLoc$1.loc_start;
+    var fullLoc_loc_end = expr$1.pexp_loc.loc_end;
+    var fullLoc_loc_ghost = argLoc$1.loc_ghost;
+    var fullLoc = {
+      loc_start: fullLoc_loc_start,
+      loc_end: fullLoc_loc_end,
+      loc_ghost: fullLoc_loc_ghost
+    };
+    return printComments(Res_doc.concat({
+                    hd: lblDoc,
+                    tl: {
+                      hd: exprDoc,
+                      tl: /* [] */0
+                    }
+                  }), cmtTbl, fullLoc);
+  }
+  
+}
+
+function printJsxName(param) {
+  var lident = param.txt;
+  var flatten = function (_acc, _lident) {
+    while(true) {
+      var lident = _lident;
+      var acc = _acc;
+      switch (lident.TAG | 0) {
+        case /* Lident */0 :
+            return {
+                    hd: lident._0,
+                    tl: acc
+                  };
+        case /* Ldot */1 :
+            var txt = lident._1;
+            var acc$1 = txt === "createElement" ? acc : ({
+                  hd: txt,
+                  tl: acc
+                });
+            _lident = lident._0;
+            _acc = acc$1;
+            continue ;
+        case /* Lapply */2 :
+            return acc;
+        
+      }
+    };
+  };
+  switch (lident.TAG | 0) {
+    case /* Lident */0 :
+        return Res_doc.text(lident._0);
+    case /* Ldot */1 :
+    case /* Lapply */2 :
+        break;
+    
+  }
+  var segments = flatten(/* [] */0, lident);
+  return Res_doc.join(Res_doc.dot, List.map(Res_doc.text, segments));
+}
+
+function printArgumentsWithCallbackInFirstPosition(uncurried, args, cmtTbl) {
+  var cmtTblCopy = Res_comments_table.copy(cmtTbl);
+  var match;
+  if (args) {
+    var match$1 = args.hd;
+    var expr = match$1[1];
+    var lbl = match$1[0];
+    var lblDoc;
+    lblDoc = typeof lbl === "number" ? Res_doc.nil : (
+        lbl.TAG === /* Labelled */0 ? Res_doc.concat({
+                hd: Res_doc.tilde,
+                tl: {
+                  hd: printIdentLike(undefined, lbl._0),
+                  tl: {
+                    hd: Res_doc.equal,
+                    tl: /* [] */0
+                  }
+                }
+              }) : Res_doc.concat({
+                hd: Res_doc.tilde,
+                tl: {
+                  hd: printIdentLike(undefined, lbl._0),
+                  tl: {
+                    hd: Res_doc.equal,
+                    tl: {
+                      hd: Res_doc.question,
+                      tl: /* [] */0
+                    }
+                  }
+                }
+              })
+      );
+    var callback = Res_doc.concat({
+          hd: lblDoc,
+          tl: {
+            hd: printPexpFun(/* FitsOnOneLine */1, expr, cmtTbl),
+            tl: /* [] */0
+          }
+        });
+    var callback$1 = printComments(callback, cmtTbl, expr.pexp_loc);
+    var printedArgs = Res_doc.join(Res_doc.concat({
+              hd: Res_doc.comma,
+              tl: {
+                hd: Res_doc.line,
+                tl: /* [] */0
+              }
+            }), List.map((function (arg) {
+                return printArgument(arg, cmtTbl);
+              }), args.tl));
+    match = [
+      callback$1,
+      printedArgs
+    ];
+  } else {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_printer.res",
+            4197,
+            9
+          ],
+          Error: new Error()
+        };
+  }
+  var printedArgs$1 = match[1];
+  var fitsOnOneLine = Res_doc.concat({
+        hd: uncurried ? Res_doc.text("(. ") : Res_doc.lparen,
+        tl: {
+          hd: match[0],
+          tl: {
+            hd: Res_doc.comma,
+            tl: {
+              hd: Res_doc.line,
+              tl: {
+                hd: printedArgs$1,
+                tl: {
+                  hd: Res_doc.rparen,
+                  tl: /* [] */0
+                }
+              }
+            }
+          }
+        }
+      });
+  var breakAllArgs = printArguments(uncurried, args, cmtTblCopy);
+  if (Res_doc.willBreak(printedArgs$1)) {
+    return breakAllArgs;
+  } else {
+    return Res_doc.customLayout({
+                hd: fitsOnOneLine,
+                tl: {
+                  hd: breakAllArgs,
+                  tl: /* [] */0
+                }
+              });
+  }
+}
+
+function printArgumentsWithCallbackInLastPosition(uncurried, args, cmtTbl) {
+  var cmtTblCopy = Res_comments_table.copy(cmtTbl);
+  var cmtTblCopy2 = Res_comments_table.copy(cmtTbl);
+  var loop = function (_acc, _args) {
+    while(true) {
+      var args = _args;
+      var acc = _acc;
+      if (!args) {
+        return [
+                Res_doc.nil,
+                Res_doc.nil,
+                Res_doc.nil
+              ];
+      }
+      var args$1 = args.tl;
+      var arg = args.hd;
+      var expr = arg[1];
+      var lbl = arg[0];
+      if (args$1) {
+        var argDoc = printArgument(arg, cmtTbl);
+        _args = args$1;
+        _acc = {
+          hd: Res_doc.line,
+          tl: {
+            hd: Res_doc.comma,
+            tl: {
+              hd: argDoc,
+              tl: acc
+            }
+          }
+        };
+        continue ;
+      }
+      var lblDoc;
+      lblDoc = typeof lbl === "number" ? Res_doc.nil : (
+          lbl.TAG === /* Labelled */0 ? Res_doc.concat({
+                  hd: Res_doc.tilde,
+                  tl: {
+                    hd: printIdentLike(undefined, lbl._0),
+                    tl: {
+                      hd: Res_doc.equal,
+                      tl: /* [] */0
+                    }
+                  }
+                }) : Res_doc.concat({
+                  hd: Res_doc.tilde,
+                  tl: {
+                    hd: printIdentLike(undefined, lbl._0),
+                    tl: {
+                      hd: Res_doc.equal,
+                      tl: {
+                        hd: Res_doc.question,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                })
+        );
+      var pexpFunDoc = printPexpFun(/* FitsOnOneLine */1, expr, cmtTbl);
+      var doc = Res_doc.concat({
+            hd: lblDoc,
+            tl: {
+              hd: pexpFunDoc,
+              tl: /* [] */0
+            }
+          });
+      var callbackFitsOnOneLine = printComments(doc, cmtTbl, expr.pexp_loc);
+      var pexpFunDoc$1 = printPexpFun(/* ArgumentsFitOnOneLine */2, expr, cmtTblCopy);
+      var doc$1 = Res_doc.concat({
+            hd: lblDoc,
+            tl: {
+              hd: pexpFunDoc$1,
+              tl: /* [] */0
+            }
+          });
+      var callbackArgumentsFitsOnOneLine = printComments(doc$1, cmtTblCopy, expr.pexp_loc);
+      return [
+              Res_doc.concat(List.rev(acc)),
+              callbackFitsOnOneLine,
+              callbackArgumentsFitsOnOneLine
+            ];
+    };
+  };
+  var match = loop(/* [] */0, args);
+  var printedArgs = match[0];
+  var fitsOnOneLine = Res_doc.concat({
+        hd: uncurried ? Res_doc.text("(.") : Res_doc.lparen,
+        tl: {
+          hd: printedArgs,
+          tl: {
+            hd: match[1],
+            tl: {
+              hd: Res_doc.rparen,
+              tl: /* [] */0
+            }
+          }
+        }
+      });
+  var arugmentsFitOnOneLine = Res_doc.concat({
+        hd: uncurried ? Res_doc.text("(.") : Res_doc.lparen,
+        tl: {
+          hd: printedArgs,
+          tl: {
+            hd: Res_doc.breakableGroup(true, match[2]),
+            tl: {
+              hd: Res_doc.rparen,
+              tl: /* [] */0
+            }
+          }
+        }
+      });
+  var breakAllArgs = printArguments(uncurried, args, cmtTblCopy2);
+  if (Res_doc.willBreak(printedArgs)) {
+    return breakAllArgs;
+  } else {
+    return Res_doc.customLayout({
+                hd: fitsOnOneLine,
+                tl: {
+                  hd: arugmentsFitOnOneLine,
+                  tl: {
+                    hd: breakAllArgs,
+                    tl: /* [] */0
+                  }
+                }
+              });
+  }
+}
+
+function printArguments(uncurried, args, cmtTbl) {
+  if (args) {
+    var match = args.hd;
+    if (typeof match[0] === "number") {
+      var arg = match[1];
+      var match$1 = arg.pexp_desc;
+      var exit = 0;
+      if (typeof match$1 === "number" || match$1.TAG !== /* Pexp_construct */9) {
+        exit = 2;
+      } else {
+        var match$2 = match$1._0.txt;
+        switch (match$2.TAG | 0) {
+          case /* Lident */0 :
+              if (match$2._0 === "()") {
+                if (!args.tl) {
+                  var match$3 = arg.pexp_loc.loc_ghost;
+                  if (uncurried) {
+                    if (match$3) {
+                      return Res_doc.text("(.)");
+                    } else {
+                      return Res_doc.text("(. ())");
+                    }
+                  } else {
+                    return Res_doc.text("()");
+                  }
+                }
+                
+              } else {
+                exit = 2;
+              }
+              break;
+          case /* Ldot */1 :
+          case /* Lapply */2 :
+              exit = 2;
+              break;
+          
+        }
+      }
+      if (exit === 2 && !args.tl && Res_parsetree_viewer.isHuggableExpression(arg)) {
+        var doc = printExpressionWithComments(arg, cmtTbl);
+        var braces = Res_parens.expr(arg);
+        var argDoc = typeof braces === "number" ? (
+            braces !== 0 ? doc : addParens(doc)
+          ) : printBraces(doc, arg, braces._0);
+        return Res_doc.concat({
+                    hd: uncurried ? Res_doc.text("(. ") : Res_doc.lparen,
+                    tl: {
+                      hd: argDoc,
+                      tl: {
+                        hd: Res_doc.rparen,
+                        tl: /* [] */0
+                      }
+                    }
+                  });
+      }
+      
+    }
+    
+  }
+  return Res_doc.group(Res_doc.concat({
+                  hd: uncurried ? Res_doc.text("(.") : Res_doc.lparen,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: uncurried ? Res_doc.line : Res_doc.softLine,
+                              tl: {
+                                hd: Res_doc.join(Res_doc.concat({
+                                          hd: Res_doc.comma,
+                                          tl: {
+                                            hd: Res_doc.line,
+                                            tl: /* [] */0
+                                          }
+                                        }), List.map((function (arg) {
+                                            return printArgument(arg, cmtTbl);
+                                          }), args)),
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: {
+                      hd: Res_doc.trailingComma,
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }
+                }));
+}
+
+function printArgument(param, cmtTbl) {
+  var arg = param[1];
+  var argLbl = param[0];
+  if (typeof argLbl !== "number") {
+    if (argLbl.TAG === /* Labelled */0) {
+      var match = arg.pexp_desc;
+      var lbl = argLbl._0;
+      if (typeof match !== "number") {
+        switch (match.TAG | 0) {
+          case /* Pexp_ident */0 :
+              var name = match._0.txt;
+              switch (name.TAG | 0) {
+                case /* Lident */0 :
+                    var match$1 = arg.pexp_attributes;
+                    var exit = 0;
+                    if (!(match$1 && !(match$1.hd[0].txt === "ns.namedArgLoc" && !match$1.tl))) {
+                      exit = 2;
+                    }
+                    if (exit === 2 && lbl === name._0 && !Res_parsetree_viewer.isBracedExpr(arg)) {
+                      var match$2 = arg.pexp_attributes;
+                      var loc;
+                      if (match$2) {
+                        var match$3 = match$2.hd[0];
+                        loc = match$3.txt === "ns.namedArgLoc" ? match$3.loc : arg.pexp_loc;
+                      } else {
+                        loc = arg.pexp_loc;
+                      }
+                      var doc = Res_doc.concat({
+                            hd: Res_doc.tilde,
+                            tl: {
+                              hd: printIdentLike(undefined, lbl),
+                              tl: /* [] */0
+                            }
+                          });
+                      return printComments(doc, cmtTbl, loc);
+                    }
+                    break;
+                case /* Ldot */1 :
+                case /* Lapply */2 :
+                    break;
+                
+              }
+              break;
+          case /* Pexp_constraint */19 :
+              var argExpr = match._0;
+              var match$4 = argExpr.pexp_desc;
+              if (typeof match$4 !== "number" && match$4.TAG === /* Pexp_ident */0) {
+                var name$1 = match$4._0.txt;
+                switch (name$1.TAG | 0) {
+                  case /* Lident */0 :
+                      var attrs = arg.pexp_attributes;
+                      var exit$1 = 0;
+                      if (!(attrs && !(attrs.hd[0].txt === "ns.namedArgLoc" && !attrs.tl))) {
+                        exit$1 = 2;
+                      }
+                      if (exit$1 === 2 && lbl === name$1._0 && !Res_parsetree_viewer.isBracedExpr(argExpr)) {
+                        var loc$1;
+                        if (attrs) {
+                          var match$5 = attrs.hd[0];
+                          if (match$5.txt === "ns.namedArgLoc") {
+                            var loc$2 = match$5.loc;
+                            loc$1 = {
+                              loc_start: loc$2.loc_start,
+                              loc_end: arg.pexp_loc.loc_end,
+                              loc_ghost: loc$2.loc_ghost
+                            };
+                          } else {
+                            loc$1 = arg.pexp_loc;
+                          }
+                        } else {
+                          loc$1 = arg.pexp_loc;
+                        }
+                        var doc$1 = Res_doc.concat({
+                              hd: Res_doc.tilde,
+                              tl: {
+                                hd: printIdentLike(undefined, lbl),
+                                tl: {
+                                  hd: Res_doc.text(": "),
+                                  tl: {
+                                    hd: printTypExpr(match._1, cmtTbl),
+                                    tl: /* [] */0
+                                  }
+                                }
+                              }
+                            });
+                        return printComments(doc$1, cmtTbl, loc$1);
+                      }
+                      break;
+                  case /* Ldot */1 :
+                  case /* Lapply */2 :
+                      break;
+                  
+                }
+              }
+              break;
+          default:
+            
+        }
+      }
+      
+    } else {
+      var match$6 = arg.pexp_desc;
+      if (typeof match$6 !== "number" && match$6.TAG === /* Pexp_ident */0) {
+        var name$2 = match$6._0.txt;
+        var lbl$1 = argLbl._0;
+        switch (name$2.TAG | 0) {
+          case /* Lident */0 :
+              var match$7 = arg.pexp_attributes;
+              var exit$2 = 0;
+              if (!(match$7 && !(match$7.hd[0].txt === "ns.namedArgLoc" && !match$7.tl))) {
+                exit$2 = 2;
+              }
+              if (exit$2 === 2 && lbl$1 === name$2._0) {
+                var match$8 = arg.pexp_attributes;
+                var loc$3;
+                if (match$8) {
+                  var match$9 = match$8.hd[0];
+                  loc$3 = match$9.txt === "ns.namedArgLoc" ? match$9.loc : arg.pexp_loc;
+                } else {
+                  loc$3 = arg.pexp_loc;
+                }
+                var doc$2 = Res_doc.concat({
+                      hd: Res_doc.tilde,
+                      tl: {
+                        hd: printIdentLike(undefined, lbl$1),
+                        tl: {
+                          hd: Res_doc.question,
+                          tl: /* [] */0
+                        }
+                      }
+                    });
+                return printComments(doc$2, cmtTbl, loc$3);
+              }
+              break;
+          case /* Ldot */1 :
+          case /* Lapply */2 :
+              break;
+          
+        }
+      }
+      
+    }
+  }
+  var match$10 = arg.pexp_attributes;
+  var match$11;
+  if (match$10) {
+    var match$12 = match$10.hd[0];
+    match$11 = match$12.txt === "ns.namedArgLoc" ? [
+        match$12.loc,
+        {
+          pexp_desc: arg.pexp_desc,
+          pexp_loc: arg.pexp_loc,
+          pexp_attributes: match$10.tl
+        }
+      ] : [
+        arg.pexp_loc,
+        arg
+      ];
+  } else {
+    match$11 = [
+      arg.pexp_loc,
+      arg
+    ];
+  }
+  var expr = match$11[1];
+  var argLoc = match$11[0];
+  var printedLbl;
+  if (typeof argLbl === "number") {
+    printedLbl = Res_doc.nil;
+  } else if (argLbl.TAG === /* Labelled */0) {
+    var doc$3 = Res_doc.concat({
+          hd: Res_doc.tilde,
+          tl: {
+            hd: printIdentLike(undefined, argLbl._0),
+            tl: {
+              hd: Res_doc.equal,
+              tl: /* [] */0
+            }
+          }
+        });
+    printedLbl = printComments(doc$3, cmtTbl, argLoc);
+  } else {
+    var doc$4 = Res_doc.concat({
+          hd: Res_doc.tilde,
+          tl: {
+            hd: printIdentLike(undefined, argLbl._0),
+            tl: {
+              hd: Res_doc.equal,
+              tl: {
+                hd: Res_doc.question,
+                tl: /* [] */0
+              }
+            }
+          }
+        });
+    printedLbl = printComments(doc$4, cmtTbl, argLoc);
+  }
+  var doc$5 = printExpressionWithComments(expr, cmtTbl);
+  var braces = Res_parens.expr(expr);
+  var printedExpr = typeof braces === "number" ? (
+      braces !== 0 ? doc$5 : addParens(doc$5)
+    ) : printBraces(doc$5, expr, braces._0);
+  var loc_loc_start = argLoc.loc_start;
+  var loc_loc_end = expr.pexp_loc.loc_end;
+  var loc_loc_ghost = argLoc.loc_ghost;
+  var loc$4 = {
+    loc_start: loc_loc_start,
+    loc_end: loc_loc_end,
+    loc_ghost: loc_loc_ghost
+  };
+  var doc$6 = Res_doc.concat({
+        hd: printedLbl,
+        tl: {
+          hd: printedExpr,
+          tl: /* [] */0
+        }
+      });
+  return printComments(doc$6, cmtTbl, loc$4);
+}
+
+function printCases(cases, cmtTbl) {
+  return Res_doc.breakableGroup(true, Res_doc.concat({
+                  hd: Res_doc.lbrace,
+                  tl: {
+                    hd: Res_doc.concat({
+                          hd: Res_doc.line,
+                          tl: {
+                            hd: printList((function (n) {
+                                    var init = n.pc_lhs.ppat_loc;
+                                    return {
+                                            loc_start: init.loc_start,
+                                            loc_end: n.pc_rhs.pexp_loc.loc_end,
+                                            loc_ghost: init.loc_ghost
+                                          };
+                                  }), cases, printCase, undefined, cmtTbl),
+                            tl: /* [] */0
+                          }
+                        }),
+                    tl: {
+                      hd: Res_doc.line,
+                      tl: {
+                        hd: Res_doc.rbrace,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function printCase($$case, cmtTbl) {
+  var match = $$case.pc_rhs.pexp_desc;
+  var rhs;
+  var exit = 0;
+  if (typeof match === "number") {
+    exit = 1;
+  } else {
+    switch (match.TAG | 0) {
+      case /* Pexp_let */2 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_open */33 :
+          exit = 2;
+          break;
+      default:
+        exit = 1;
+    }
+  }
+  switch (exit) {
+    case 1 :
+        var doc = printExpressionWithComments($$case.pc_rhs, cmtTbl);
+        var match$1 = Res_parens.expr($$case.pc_rhs);
+        rhs = match$1 === 0 ? addParens(doc) : doc;
+        break;
+    case 2 :
+        rhs = printExpressionBlock(Res_parsetree_viewer.isBracedExpr($$case.pc_rhs), $$case.pc_rhs, cmtTbl);
+        break;
+    
+  }
+  var expr = $$case.pc_guard;
+  var guard = expr !== undefined ? Res_doc.group(Res_doc.concat({
+              hd: Res_doc.line,
+              tl: {
+                hd: Res_doc.text("if "),
+                tl: {
+                  hd: printExpressionWithComments(expr, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            })) : Res_doc.nil;
+  var match$2 = $$case.pc_rhs.pexp_desc;
+  var shouldInlineRhs;
+  var exit$1 = 0;
+  if (typeof match$2 === "number") {
+    exit$1 = 1;
+  } else {
+    switch (match$2.TAG | 0) {
+      case /* Pexp_ident */0 :
+      case /* Pexp_constant */1 :
+          shouldInlineRhs = true;
+          break;
+      case /* Pexp_construct */9 :
+          var match$3 = match$2._0.txt;
+          switch (match$3.TAG | 0) {
+            case /* Lident */0 :
+                switch (match$3._0) {
+                  case "()" :
+                  case "false" :
+                  case "true" :
+                      shouldInlineRhs = true;
+                      break;
+                  default:
+                    exit$1 = 1;
+                }
+                break;
+            case /* Ldot */1 :
+            case /* Lapply */2 :
+                exit$1 = 1;
+                break;
+            
+          }
+          break;
+      default:
+        exit$1 = 1;
+    }
+  }
+  if (exit$1 === 1) {
+    shouldInlineRhs = Res_parsetree_viewer.isHuggableRhs($$case.pc_rhs) ? true : false;
+  }
+  var match$4 = $$case.pc_lhs.ppat_desc;
+  var shouldIndentPattern;
+  shouldIndentPattern = typeof match$4 === "number" || match$4.TAG !== /* Ppat_or */9 ? true : false;
+  var doc$1 = printPattern($$case.pc_lhs, cmtTbl);
+  var match$5 = $$case.pc_lhs.ppat_desc;
+  var patternDoc;
+  patternDoc = typeof match$5 === "number" || match$5.TAG !== /* Ppat_constraint */10 ? doc$1 : addParens(doc$1);
+  var content = Res_doc.concat({
+        hd: shouldIndentPattern ? Res_doc.indent(patternDoc) : patternDoc,
+        tl: {
+          hd: Res_doc.indent(guard),
+          tl: {
+            hd: Res_doc.text(" =>"),
+            tl: {
+              hd: Res_doc.indent(Res_doc.concat({
+                        hd: shouldInlineRhs ? Res_doc.space : Res_doc.line,
+                        tl: {
+                          hd: rhs,
+                          tl: /* [] */0
+                        }
+                      })),
+              tl: /* [] */0
+            }
+          }
+        }
+      });
+  return Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.text("| "),
+                  tl: {
+                    hd: content,
+                    tl: /* [] */0
+                  }
+                }));
+}
+
+function printExprFunParameters(inCallback, uncurried, hasConstraint, parameters, cmtTbl) {
+  if (parameters) {
+    var match = parameters.hd;
+    if (match.TAG === /* Parameter */0 && !match.attrs && typeof match.lbl === "number" && match.defaultExpr === undefined) {
+      var stringLoc = match.pat.ppat_desc;
+      if (typeof stringLoc === "number") {
+        if (!parameters.tl && !uncurried) {
+          if (hasConstraint) {
+            return Res_doc.text("(_)");
+          } else {
+            return Res_doc.text("_");
+          }
+        }
+        
+      } else {
+        switch (stringLoc.TAG | 0) {
+          case /* Ppat_var */0 :
+              if (!parameters.tl && !uncurried) {
+                var stringLoc$1 = stringLoc._0;
+                var $$var = printIdentLike(undefined, stringLoc$1.txt);
+                var txtDoc = hasConstraint ? addParens($$var) : $$var;
+                return printComments(txtDoc, cmtTbl, stringLoc$1.loc);
+              }
+              break;
+          case /* Ppat_construct */5 :
+              var match$1 = stringLoc._0.txt;
+              switch (match$1.TAG | 0) {
+                case /* Lident */0 :
+                    if (match$1._0 === "()" && stringLoc._1 === undefined && !parameters.tl && !uncurried) {
+                      return Res_doc.text("()");
+                    }
+                    break;
+                case /* Ldot */1 :
+                case /* Lapply */2 :
+                    break;
+                
+              }
+              break;
+          default:
+            
+        }
+      }
+    }
+    
+  }
+  var inCallback$1 = inCallback === 1;
+  var lparen = uncurried ? Res_doc.text("(. ") : Res_doc.lparen;
+  var shouldHug = Res_parsetree_viewer.parametersShouldHug(parameters);
+  var printedParamaters = Res_doc.concat({
+        hd: shouldHug || inCallback$1 ? Res_doc.nil : Res_doc.softLine,
+        tl: {
+          hd: Res_doc.join(Res_doc.concat({
+                    hd: Res_doc.comma,
+                    tl: {
+                      hd: Res_doc.line,
+                      tl: /* [] */0
+                    }
+                  }), List.map((function (p) {
+                      return printExpFunParameter(p, cmtTbl);
+                    }), parameters)),
+          tl: /* [] */0
+        }
+      });
+  return Res_doc.group(Res_doc.concat({
+                  hd: lparen,
+                  tl: {
+                    hd: shouldHug || inCallback$1 ? printedParamaters : Res_doc.concat({
+                            hd: Res_doc.indent(printedParamaters),
+                            tl: {
+                              hd: Res_doc.trailingComma,
+                              tl: {
+                                hd: Res_doc.softLine,
+                                tl: /* [] */0
+                              }
+                            }
+                          }),
+                    tl: {
+                      hd: Res_doc.rparen,
+                      tl: /* [] */0
+                    }
+                  }
+                }));
+}
+
+function printExpFunParameter(parameter, cmtTbl) {
+  if (parameter.TAG !== /* Parameter */0) {
+    return Res_doc.group(Res_doc.concat({
+                    hd: printAttributes(undefined, undefined, parameter.attrs, cmtTbl),
+                    tl: {
+                      hd: Res_doc.text("type "),
+                      tl: {
+                        hd: Res_doc.join(Res_doc.space, List.map((function (lbl) {
+                                    return printComments(printIdentLike(undefined, lbl.txt), cmtTbl, lbl.loc);
+                                  }), parameter.locs)),
+                        tl: /* [] */0
+                      }
+                    }
+                  }));
+  }
+  var pattern = parameter.pat;
+  var defaultExpr = parameter.defaultExpr;
+  var lbl = parameter.lbl;
+  var match = Res_parsetree_viewer.processUncurriedAttribute(parameter.attrs);
+  var uncurried = match[0] ? Res_doc.concat({
+          hd: Res_doc.dot,
+          tl: {
+            hd: Res_doc.space,
+            tl: /* [] */0
+          }
+        }) : Res_doc.nil;
+  var attrs = printAttributes(undefined, undefined, match[1], cmtTbl);
+  var defaultExprDoc = defaultExpr !== undefined ? Res_doc.concat({
+          hd: Res_doc.text("="),
+          tl: {
+            hd: printExpressionWithComments(defaultExpr, cmtTbl),
+            tl: /* [] */0
+          }
+        }) : Res_doc.nil;
+  var labelWithPattern;
+  var exit = 0;
+  if (typeof lbl === "number") {
+    labelWithPattern = printPattern(pattern, cmtTbl);
+  } else {
+    var lbl$1 = lbl._0;
+    var stringLoc = pattern.ppat_desc;
+    if (typeof stringLoc === "number") {
+      exit = 1;
+    } else {
+      switch (stringLoc.TAG | 0) {
+        case /* Ppat_var */0 :
+            var match$1 = pattern.ppat_attributes;
+            var exit$1 = 0;
+            if (match$1 && !(match$1.hd[0].txt === "ns.namedArgLoc" && !match$1.tl)) {
+              exit = 1;
+            } else {
+              exit$1 = 2;
+            }
+            if (exit$1 === 2) {
+              if (lbl$1 === stringLoc._0.txt) {
+                labelWithPattern = Res_doc.concat({
+                      hd: Res_doc.text("~"),
+                      tl: {
+                        hd: printIdentLike(undefined, lbl$1),
+                        tl: /* [] */0
+                      }
+                    });
+              } else {
+                exit = 1;
+              }
+            }
+            break;
+        case /* Ppat_constraint */10 :
+            var lbl$2 = lbl._0;
+            var match$2 = pattern.ppat_desc;
+            var match$3 = match$2._0.ppat_desc;
+            if (typeof match$3 === "number" || match$3.TAG !== /* Ppat_var */0) {
+              exit = 1;
+            } else {
+              var match$4 = pattern.ppat_attributes;
+              var exit$2 = 0;
+              if (match$4 && !(match$4.hd[0].txt === "ns.namedArgLoc" && !match$4.tl)) {
+                exit = 1;
+              } else {
+                exit$2 = 2;
+              }
+              if (exit$2 === 2) {
+                if (lbl$2 === match$3._0.txt) {
+                  labelWithPattern = Res_doc.concat({
+                        hd: Res_doc.text("~"),
+                        tl: {
+                          hd: printIdentLike(undefined, lbl$2),
+                          tl: {
+                            hd: Res_doc.text(": "),
+                            tl: {
+                              hd: printTypExpr(match$2._1, cmtTbl),
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      });
+                } else {
+                  exit = 1;
+                }
+              }
+              
+            }
+            break;
+        default:
+          exit = 1;
+      }
+    }
+  }
+  if (exit === 1) {
+    labelWithPattern = Res_doc.concat({
+          hd: Res_doc.text("~"),
+          tl: {
+            hd: printIdentLike(undefined, lbl._0),
+            tl: {
+              hd: Res_doc.text(" as "),
+              tl: {
+                hd: printPattern(pattern, cmtTbl),
+                tl: /* [] */0
+              }
+            }
+          }
+        });
+  }
+  var optionalLabelSuffix;
+  optionalLabelSuffix = typeof lbl === "number" || lbl.TAG === /* Labelled */0 || defaultExpr !== undefined ? Res_doc.nil : Res_doc.text("=?");
+  var doc = Res_doc.group(Res_doc.concat({
+            hd: uncurried,
+            tl: {
+              hd: attrs,
+              tl: {
+                hd: labelWithPattern,
+                tl: {
+                  hd: defaultExprDoc,
+                  tl: {
+                    hd: optionalLabelSuffix,
+                    tl: /* [] */0
+                  }
+                }
+              }
+            }
+          }));
+  var cmtLoc;
+  if (defaultExpr !== undefined) {
+    var match$5 = pattern.ppat_attributes;
+    var startPos;
+    if (match$5) {
+      var match$6 = match$5.hd[0];
+      startPos = match$6.txt === "ns.namedArgLoc" ? match$6.loc.loc_start : pattern.ppat_loc.loc_start;
+    } else {
+      startPos = pattern.ppat_loc.loc_start;
+    }
+    var init = pattern.ppat_loc;
+    cmtLoc = {
+      loc_start: startPos,
+      loc_end: defaultExpr.pexp_loc.loc_end,
+      loc_ghost: init.loc_ghost
+    };
+  } else {
+    var match$7 = pattern.ppat_attributes;
+    if (match$7) {
+      var match$8 = match$7.hd[0];
+      if (match$8.txt === "ns.namedArgLoc") {
+        var loc = match$8.loc;
+        cmtLoc = {
+          loc_start: loc.loc_start,
+          loc_end: pattern.ppat_loc.loc_end,
+          loc_ghost: loc.loc_ghost
+        };
+      } else {
+        cmtLoc = pattern.ppat_loc;
+      }
+    } else {
+      cmtLoc = pattern.ppat_loc;
+    }
+  }
+  return printComments(doc, cmtTbl, cmtLoc);
+}
+
+function printExpressionBlock(braces, expr, cmtTbl) {
+  var collectRows = function (_acc, _expr) {
+    while(true) {
+      var expr = _expr;
+      var acc = _acc;
+      var match = expr.pexp_desc;
+      if (typeof match !== "number") {
+        switch (match.TAG | 0) {
+          case /* Pexp_let */2 :
+              var expr2 = match._2;
+              var valueBindings = match._1;
+              var match$1 = List.rev(valueBindings);
+              var loc;
+              if (valueBindings && match$1) {
+                var init = valueBindings.hd.pvb_loc;
+                loc = {
+                  loc_start: init.loc_start,
+                  loc_end: match$1.hd.pvb_loc.loc_end,
+                  loc_ghost: init.loc_ghost
+                };
+              } else {
+                loc = $$Location.none;
+              }
+              var comment = getFirstLeadingComment(cmtTbl, loc);
+              var loc$1;
+              if (comment !== undefined) {
+                var cmtLoc = Res_comment.loc(comment);
+                loc$1 = {
+                  loc_start: cmtLoc.loc_start,
+                  loc_end: loc.loc_end,
+                  loc_ghost: cmtLoc.loc_ghost
+                };
+              } else {
+                loc$1 = loc;
+              }
+              var recFlag = match._0 ? Res_doc.text("rec ") : Res_doc.nil;
+              var letDoc = printValueBindings(recFlag, valueBindings, cmtTbl);
+              var match$2 = expr2.pexp_desc;
+              var exit = 0;
+              if (typeof match$2 === "number" || match$2.TAG !== /* Pexp_construct */9) {
+                exit = 2;
+              } else {
+                var match$3 = match$2._0.txt;
+                switch (match$3.TAG | 0) {
+                  case /* Lident */0 :
+                      if (match$3._0 === "()") {
+                        return List.rev({
+                                    hd: [
+                                      loc$1,
+                                      letDoc
+                                    ],
+                                    tl: acc
+                                  });
+                      }
+                      exit = 2;
+                      break;
+                  case /* Ldot */1 :
+                  case /* Lapply */2 :
+                      exit = 2;
+                      break;
+                  
+                }
+              }
+              if (exit === 2) {
+                _expr = expr2;
+                _acc = {
+                  hd: [
+                    loc$1,
+                    letDoc
+                  ],
+                  tl: acc
+                };
+                continue ;
+              }
+              break;
+          case /* Pexp_sequence */16 :
+              var expr1 = match._0;
+              var doc = printExpression(expr1, cmtTbl);
+              var braces = Res_parens.expr(expr1);
+              var exprDoc = typeof braces === "number" ? (
+                  braces !== 0 ? doc : addParens(doc)
+                ) : printBraces(doc, expr1, braces._0);
+              var loc$2 = expr1.pexp_loc;
+              _expr = match._1;
+              _acc = {
+                hd: [
+                  loc$2,
+                  exprDoc
+                ],
+                tl: acc
+              };
+              continue ;
+          case /* Pexp_letmodule */25 :
+              var modExpr = match._1;
+              var modName = match._0;
+              var doc$1 = Res_doc.text(modName.txt);
+              var name = printComments(doc$1, cmtTbl, modName.loc);
+              var letModuleDoc = Res_doc.concat({
+                    hd: Res_doc.text("module "),
+                    tl: {
+                      hd: name,
+                      tl: {
+                        hd: Res_doc.text(" = "),
+                        tl: {
+                          hd: printModExpr(modExpr, cmtTbl),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+              var init$1 = expr.pexp_loc;
+              var loc_loc_start = init$1.loc_start;
+              var loc_loc_end = modExpr.pmod_loc.loc_end;
+              var loc_loc_ghost = init$1.loc_ghost;
+              var loc$3 = {
+                loc_start: loc_loc_start,
+                loc_end: loc_loc_end,
+                loc_ghost: loc_loc_ghost
+              };
+              _expr = match._2;
+              _acc = {
+                hd: [
+                  loc$3,
+                  letModuleDoc
+                ],
+                tl: acc
+              };
+              continue ;
+          case /* Pexp_letexception */26 :
+              var extensionConstructor = match._0;
+              var init$2 = expr.pexp_loc;
+              var loc_loc_start$1 = init$2.loc_start;
+              var loc_loc_end$1 = extensionConstructor.pext_loc.loc_end;
+              var loc_loc_ghost$1 = init$2.loc_ghost;
+              var loc$4 = {
+                loc_start: loc_loc_start$1,
+                loc_end: loc_loc_end$1,
+                loc_ghost: loc_loc_ghost$1
+              };
+              var comment$1 = getFirstLeadingComment(cmtTbl, loc$4);
+              var loc$5;
+              if (comment$1 !== undefined) {
+                var cmtLoc$1 = Res_comment.loc(comment$1);
+                loc$5 = {
+                  loc_start: cmtLoc$1.loc_start,
+                  loc_end: loc_loc_end$1,
+                  loc_ghost: cmtLoc$1.loc_ghost
+                };
+              } else {
+                loc$5 = loc$4;
+              }
+              var letExceptionDoc = printExceptionDef(extensionConstructor, cmtTbl);
+              _expr = match._1;
+              _acc = {
+                hd: [
+                  loc$5,
+                  letExceptionDoc
+                ],
+                tl: acc
+              };
+              continue ;
+          case /* Pexp_open */33 :
+              var longidentLoc = match._1;
+              var openDoc = Res_doc.concat({
+                    hd: Res_doc.text("open"),
+                    tl: {
+                      hd: printOverrideFlag(match._0),
+                      tl: {
+                        hd: Res_doc.space,
+                        tl: {
+                          hd: printLongidentLocation(longidentLoc, cmtTbl),
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+              var init$3 = expr.pexp_loc;
+              var loc_loc_start$2 = init$3.loc_start;
+              var loc_loc_end$2 = longidentLoc.loc.loc_end;
+              var loc_loc_ghost$2 = init$3.loc_ghost;
+              var loc$6 = {
+                loc_start: loc_loc_start$2,
+                loc_end: loc_loc_end$2,
+                loc_ghost: loc_loc_ghost$2
+              };
+              _expr = match._2;
+              _acc = {
+                hd: [
+                  loc$6,
+                  openDoc
+                ],
+                tl: acc
+              };
+              continue ;
+          default:
+            
+        }
+      }
+      var doc$2 = printExpression(expr, cmtTbl);
+      var braces$1 = Res_parens.expr(expr);
+      var exprDoc$1 = typeof braces$1 === "number" ? (
+          braces$1 !== 0 ? doc$2 : addParens(doc$2)
+        ) : printBraces(doc$2, expr, braces$1._0);
+      return List.rev({
+                  hd: [
+                    expr.pexp_loc,
+                    exprDoc$1
+                  ],
+                  tl: acc
+                });
+    };
+  };
+  var rows = collectRows(/* [] */0, expr);
+  var block = printList((function (prim) {
+          return prim[0];
+        }), rows, (function (param, param$1) {
+          return param[1];
+        }), true, cmtTbl);
+  return Res_doc.breakableGroup(true, braces ? Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.line,
+                                tl: {
+                                  hd: block,
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.line,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }) : block);
+}
+
+function printBraces(doc, expr, bracesLoc) {
+  var overMultipleLines = bracesLoc.loc_end.pos_lnum > bracesLoc.loc_start.pos_lnum;
+  var match = expr.pexp_desc;
+  if (typeof match !== "number") {
+    switch (match.TAG | 0) {
+      case /* Pexp_let */2 :
+      case /* Pexp_sequence */16 :
+      case /* Pexp_letmodule */25 :
+      case /* Pexp_letexception */26 :
+      case /* Pexp_open */33 :
+          return doc;
+      default:
+        
+    }
+  }
+  return Res_doc.breakableGroup(overMultipleLines, Res_doc.concat({
+                  hd: Res_doc.lbrace,
+                  tl: {
+                    hd: Res_doc.indent(Res_doc.concat({
+                              hd: Res_doc.softLine,
+                              tl: {
+                                hd: Res_parens.bracedExpr(expr) ? addParens(doc) : doc,
+                                tl: /* [] */0
+                              }
+                            })),
+                    tl: {
+                      hd: Res_doc.softLine,
+                      tl: {
+                        hd: Res_doc.rbrace,
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function printOverrideFlag(overrideFlag) {
+  if (overrideFlag) {
+    return Res_doc.nil;
+  } else {
+    return Res_doc.text("!");
+  }
+}
+
+function printDirectionFlag(flag) {
+  if (flag) {
+    return Res_doc.text(" downto ");
+  } else {
+    return Res_doc.text(" to ");
+  }
+}
+
+function printRecordRow(param, cmtTbl, punningAllowed) {
+  var expr = param[1];
+  var lbl = param[0];
+  var init = lbl.loc;
+  var cmtLoc_loc_start = init.loc_start;
+  var cmtLoc_loc_end = expr.pexp_loc.loc_end;
+  var cmtLoc_loc_ghost = init.loc_ghost;
+  var cmtLoc = {
+    loc_start: cmtLoc_loc_start,
+    loc_end: cmtLoc_loc_end,
+    loc_ghost: cmtLoc_loc_ghost
+  };
+  var match = expr.pexp_desc;
+  var tmp;
+  var exit = 0;
+  if (typeof match === "number" || match.TAG !== /* Pexp_ident */0) {
+    exit = 1;
+  } else {
+    var match$1 = match._0;
+    var key = match$1.txt;
+    switch (key.TAG | 0) {
+      case /* Lident */0 :
+          if (punningAllowed && Longident.last(lbl.txt) === key._0 && lbl.loc.loc_start.pos_cnum === match$1.loc.loc_start.pos_cnum) {
+            tmp = printLidentPath(lbl, cmtTbl);
+          } else {
+            exit = 1;
+          }
+          break;
+      case /* Ldot */1 :
+      case /* Lapply */2 :
+          exit = 1;
+          break;
+      
+    }
+  }
+  if (exit === 1) {
+    var doc = printExpressionWithComments(expr, cmtTbl);
+    var braces = Res_parens.expr(expr);
+    tmp = Res_doc.concat({
+          hd: printLidentPath(lbl, cmtTbl),
+          tl: {
+            hd: Res_doc.text(": "),
+            tl: {
+              hd: typeof braces === "number" ? (
+                  braces !== 0 ? doc : addParens(doc)
+                ) : printBraces(doc, expr, braces._0),
+              tl: /* [] */0
+            }
+          }
+        });
+  }
+  var doc$1 = Res_doc.group(tmp);
+  return printComments(doc$1, cmtTbl, cmtLoc);
+}
+
+function printBsObjectRow(param, cmtTbl) {
+  var expr = param[1];
+  var lbl = param[0];
+  var init = lbl.loc;
+  var cmtLoc_loc_start = init.loc_start;
+  var cmtLoc_loc_end = expr.pexp_loc.loc_end;
+  var cmtLoc_loc_ghost = init.loc_ghost;
+  var cmtLoc = {
+    loc_start: cmtLoc_loc_start,
+    loc_end: cmtLoc_loc_end,
+    loc_ghost: cmtLoc_loc_ghost
+  };
+  var doc = Res_doc.concat({
+        hd: Res_doc.text("\""),
+        tl: {
+          hd: printLongident(lbl.txt),
+          tl: {
+            hd: Res_doc.text("\""),
+            tl: /* [] */0
+          }
+        }
+      });
+  var lblDoc = printComments(doc, cmtTbl, lbl.loc);
+  var doc$1 = printExpressionWithComments(expr, cmtTbl);
+  var braces = Res_parens.expr(expr);
+  var doc$2 = Res_doc.concat({
+        hd: lblDoc,
+        tl: {
+          hd: Res_doc.text(": "),
+          tl: {
+            hd: typeof braces === "number" ? (
+                braces !== 0 ? doc$1 : addParens(doc$1)
+              ) : printBraces(doc$1, expr, braces._0),
+            tl: /* [] */0
+          }
+        }
+      });
+  return printComments(doc$2, cmtTbl, cmtLoc);
+}
+
+function printAttributes(loc, inlineOpt, attrs, cmtTbl) {
+  var inline = inlineOpt !== undefined ? inlineOpt : false;
+  var attrs$1 = Res_parsetree_viewer.filterParsingAttrs(attrs);
+  if (!attrs$1) {
+    return Res_doc.nil;
+  }
+  var lineBreak;
+  if (loc !== undefined) {
+    var match = List.rev(attrs$1);
+    lineBreak = match && loc.loc_start.pos_lnum > match.hd[0].loc.loc_end.pos_lnum ? Res_doc.hardLine : Res_doc.line;
+  } else {
+    lineBreak = Res_doc.line;
+  }
+  return Res_doc.concat({
+              hd: Res_doc.group(Res_doc.join(Res_doc.line, List.map((function (attr) {
+                              return printAttribute(attr, cmtTbl);
+                            }), attrs$1))),
+              tl: {
+                hd: inline ? Res_doc.space : lineBreak,
+                tl: /* [] */0
+              }
+            });
+}
+
+function printPayload(payload, cmtTbl) {
+  switch (payload.TAG | 0) {
+    case /* PStr */0 :
+        var structure = payload._0;
+        if (!structure) {
+          return Res_doc.nil;
+        }
+        var si = structure.hd;
+        var match = si.pstr_desc;
+        switch (match.TAG | 0) {
+          case /* Pstr_eval */0 :
+              if (structure.tl) {
+                return addParens(printStructure(structure, cmtTbl));
+              }
+              var attrs = match._1;
+              var expr = match._0;
+              var exprDoc = printExpressionWithComments(expr, cmtTbl);
+              var needsParens = attrs ? true : false;
+              var shouldHug = Res_parsetree_viewer.isHuggableExpression(expr);
+              if (shouldHug) {
+                return Res_doc.concat({
+                            hd: Res_doc.lparen,
+                            tl: {
+                              hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                              tl: {
+                                hd: needsParens ? addParens(exprDoc) : exprDoc,
+                                tl: {
+                                  hd: Res_doc.rparen,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          });
+              } else {
+                return Res_doc.concat({
+                            hd: Res_doc.lparen,
+                            tl: {
+                              hd: Res_doc.indent(Res_doc.concat({
+                                        hd: Res_doc.softLine,
+                                        tl: {
+                                          hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                                          tl: {
+                                            hd: needsParens ? addParens(exprDoc) : exprDoc,
+                                            tl: /* [] */0
+                                          }
+                                        }
+                                      })),
+                              tl: {
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.rparen,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          });
+              }
+          case /* Pstr_value */1 :
+              if (structure.tl) {
+                return addParens(printStructure(structure, cmtTbl));
+              } else {
+                return addParens(printStructureItem(si, cmtTbl));
+              }
+          default:
+            return addParens(printStructure(structure, cmtTbl));
+        }
+    case /* PSig */1 :
+        return Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.text(":"),
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat({
+                                  hd: Res_doc.line,
+                                  tl: {
+                                    hd: printSignature(payload._0, cmtTbl),
+                                    tl: /* [] */0
+                                  }
+                                })),
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  });
+    case /* PTyp */2 :
+        return Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.text(":"),
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat({
+                                  hd: Res_doc.line,
+                                  tl: {
+                                    hd: printTypExpr(payload._0, cmtTbl),
+                                    tl: /* [] */0
+                                  }
+                                })),
+                        tl: {
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.rparen,
+                            tl: /* [] */0
+                          }
+                        }
+                      }
+                    }
+                  });
+    case /* PPat */3 :
+        var optExpr = payload._1;
+        var whenDoc = optExpr !== undefined ? Res_doc.concat({
+                hd: Res_doc.line,
+                tl: {
+                  hd: Res_doc.text("if "),
+                  tl: {
+                    hd: printExpressionWithComments(optExpr, cmtTbl),
+                    tl: /* [] */0
+                  }
+                }
+              }) : Res_doc.nil;
+        return Res_doc.concat({
+                    hd: Res_doc.lparen,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: Res_doc.text("? "),
+                                  tl: {
+                                    hd: printPattern(payload._0, cmtTbl),
+                                    tl: {
+                                      hd: whenDoc,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rparen,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  });
+    
+  }
+}
+
+function printAttribute(param, cmtTbl) {
+  return Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.text("@"),
+                  tl: {
+                    hd: Res_doc.text(convertBsExternalAttribute(param[0].txt)),
+                    tl: {
+                      hd: printPayload(param[1], cmtTbl),
+                      tl: /* [] */0
+                    }
+                  }
+                }));
+}
+
+function printModExpr(modExpr, cmtTbl) {
+  var longidentLoc = modExpr.pmod_desc;
+  var doc;
+  switch (longidentLoc.TAG | 0) {
+    case /* Pmod_ident */0 :
+        doc = printLongidentLocation(longidentLoc._0, cmtTbl);
+        break;
+    case /* Pmod_structure */1 :
+        var structure = longidentLoc._0;
+        if (structure) {
+          doc = Res_doc.breakableGroup(true, Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printStructure(structure, cmtTbl),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+        } else {
+          var shouldBreak = modExpr.pmod_loc.loc_start.pos_lnum < modExpr.pmod_loc.loc_end.pos_lnum;
+          doc = Res_doc.breakableGroup(shouldBreak, Res_doc.concat({
+                    hd: Res_doc.lbrace,
+                    tl: {
+                      hd: Res_doc.indent(Res_doc.concat({
+                                hd: Res_doc.softLine,
+                                tl: {
+                                  hd: printCommentsInside(cmtTbl, modExpr.pmod_loc),
+                                  tl: /* [] */0
+                                }
+                              })),
+                      tl: {
+                        hd: Res_doc.softLine,
+                        tl: {
+                          hd: Res_doc.rbrace,
+                          tl: /* [] */0
+                        }
+                      }
+                    }
+                  }));
+        }
+        break;
+    case /* Pmod_functor */2 :
+        doc = printModFunctor(modExpr, cmtTbl);
+        break;
+    case /* Pmod_apply */3 :
+        var match = Res_parsetree_viewer.modExprApply(modExpr);
+        var args = match[0];
+        var isUnitSugar;
+        if (args) {
+          var match$1 = args.hd.pmod_desc;
+          isUnitSugar = match$1.TAG === /* Pmod_structure */1 && !(match$1._0 || args.tl) ? true : false;
+        } else {
+          isUnitSugar = false;
+        }
+        var shouldHug = args && args.hd.pmod_desc.TAG === /* Pmod_structure */1 && !args.tl ? true : false;
+        doc = Res_doc.group(Res_doc.concat({
+                  hd: printModExpr(match[1], cmtTbl),
+                  tl: {
+                    hd: isUnitSugar ? printModApplyArg(List.hd(args), cmtTbl) : Res_doc.concat({
+                            hd: Res_doc.lparen,
+                            tl: {
+                              hd: shouldHug ? printModApplyArg(List.hd(args), cmtTbl) : Res_doc.indent(Res_doc.concat({
+                                          hd: Res_doc.softLine,
+                                          tl: {
+                                            hd: Res_doc.join(Res_doc.concat({
+                                                      hd: Res_doc.comma,
+                                                      tl: {
+                                                        hd: Res_doc.line,
+                                                        tl: /* [] */0
+                                                      }
+                                                    }), List.map((function (modArg) {
+                                                        return printModApplyArg(modArg, cmtTbl);
+                                                      }), args)),
+                                            tl: /* [] */0
+                                          }
+                                        })),
+                              tl: {
+                                hd: shouldHug ? Res_doc.nil : Res_doc.concat({
+                                        hd: Res_doc.trailingComma,
+                                        tl: {
+                                          hd: Res_doc.softLine,
+                                          tl: /* [] */0
+                                        }
+                                      }),
+                                tl: {
+                                  hd: Res_doc.rparen,
+                                  tl: /* [] */0
+                                }
+                              }
+                            }
+                          }),
+                    tl: /* [] */0
+                  }
+                }));
+        break;
+    case /* Pmod_constraint */4 :
+        doc = Res_doc.concat({
+              hd: printModExpr(longidentLoc._0, cmtTbl),
+              tl: {
+                hd: Res_doc.text(": "),
+                tl: {
+                  hd: printModType(longidentLoc._1, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            });
+        break;
+    case /* Pmod_unpack */5 :
+        var expr = longidentLoc._0;
+        var match$2 = expr.pexp_desc;
+        var shouldHug$1;
+        if (typeof match$2 === "number") {
+          shouldHug$1 = false;
+        } else {
+          switch (match$2.TAG | 0) {
+            case /* Pexp_let */2 :
+                shouldHug$1 = true;
+                break;
+            case /* Pexp_constraint */19 :
+                var tmp = match$2._0.pexp_desc;
+                if (typeof tmp === "number" || tmp.TAG !== /* Pexp_let */2) {
+                  shouldHug$1 = false;
+                } else {
+                  var tmp$1 = match$2._1.ptyp_desc;
+                  shouldHug$1 = typeof tmp$1 === "number" || tmp$1.TAG !== /* Ptyp_package */9 ? false : true;
+                }
+                break;
+            default:
+              shouldHug$1 = false;
+          }
+        }
+        var match$3 = expr.pexp_desc;
+        var match$4;
+        if (typeof match$3 === "number" || match$3.TAG !== /* Pexp_constraint */19) {
+          match$4 = [
+            expr,
+            Res_doc.nil
+          ];
+        } else {
+          var match$5 = match$3._1;
+          var packageType = match$5.ptyp_desc;
+          if (typeof packageType === "number" || packageType.TAG !== /* Ptyp_package */9) {
+            match$4 = [
+              expr,
+              Res_doc.nil
+            ];
+          } else {
+            var doc$1 = printPackageType(false, packageType._0, cmtTbl);
+            var packageDoc = printComments(doc$1, cmtTbl, match$5.ptyp_loc);
+            var typeDoc = Res_doc.group(Res_doc.concat({
+                      hd: Res_doc.text(":"),
+                      tl: {
+                        hd: Res_doc.indent(Res_doc.concat({
+                                  hd: Res_doc.line,
+                                  tl: {
+                                    hd: packageDoc,
+                                    tl: /* [] */0
+                                  }
+                                })),
+                        tl: /* [] */0
+                      }
+                    }));
+            match$4 = [
+              match$3._0,
+              typeDoc
+            ];
+          }
+        }
+        var unpackDoc = Res_doc.group(Res_doc.concat({
+                  hd: printExpressionWithComments(match$4[0], cmtTbl),
+                  tl: {
+                    hd: match$4[1],
+                    tl: /* [] */0
+                  }
+                }));
+        doc = Res_doc.group(Res_doc.concat({
+                  hd: Res_doc.text("unpack("),
+                  tl: {
+                    hd: shouldHug$1 ? unpackDoc : Res_doc.concat({
+                            hd: Res_doc.indent(Res_doc.concat({
+                                      hd: Res_doc.softLine,
+                                      tl: {
+                                        hd: unpackDoc,
+                                        tl: /* [] */0
+                                      }
+                                    })),
+                            tl: {
+                              hd: Res_doc.softLine,
+                              tl: /* [] */0
+                            }
+                          }),
+                    tl: {
+                      hd: Res_doc.rparen,
+                      tl: /* [] */0
+                    }
+                  }
+                }));
+        break;
+    case /* Pmod_extension */6 :
+        doc = printExtension(false, longidentLoc._0, cmtTbl);
+        break;
+    
+  }
+  return printComments(doc, cmtTbl, modExpr.pmod_loc);
+}
+
+function printModFunctor(modExpr, cmtTbl) {
+  var match = Res_parsetree_viewer.modExprFunctor(modExpr);
+  var returnModExpr = match[1];
+  var parameters = match[0];
+  var match$1 = returnModExpr.pmod_desc;
+  var match$2;
+  if (match$1.TAG === /* Pmod_constraint */4) {
+    var modType = match$1._1;
+    var doc = printModType(modType, cmtTbl);
+    var constraintDoc = Res_parens.modExprFunctorConstraint(modType) ? addParens(doc) : doc;
+    var modConstraint = Res_doc.concat({
+          hd: Res_doc.text(": "),
+          tl: {
+            hd: constraintDoc,
+            tl: /* [] */0
+          }
+        });
+    match$2 = [
+      modConstraint,
+      printModExpr(match$1._0, cmtTbl)
+    ];
+  } else {
+    match$2 = [
+      Res_doc.nil,
+      printModExpr(returnModExpr, cmtTbl)
+    ];
+  }
+  var parametersDoc;
+  var exit = 0;
+  if (parameters) {
+    var match$3 = parameters.hd;
+    var attrs = match$3[0];
+    if (match$3[1].txt === "*") {
+      if (match$3[2] !== undefined || parameters.tl) {
+        exit = 1;
+      } else {
+        parametersDoc = Res_doc.group(Res_doc.concat({
+                  hd: printAttributes(undefined, undefined, attrs, cmtTbl),
+                  tl: {
+                    hd: Res_doc.text("()"),
+                    tl: /* [] */0
+                  }
+                }));
+      }
+    } else if (attrs || match$3[2] !== undefined || parameters.tl) {
+      exit = 1;
+    } else {
+      parametersDoc = Res_doc.text(match$3[1].txt);
+    }
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    parametersDoc = Res_doc.group(Res_doc.concat({
+              hd: Res_doc.lparen,
+              tl: {
+                hd: Res_doc.indent(Res_doc.concat({
+                          hd: Res_doc.softLine,
+                          tl: {
+                            hd: Res_doc.join(Res_doc.concat({
+                                      hd: Res_doc.comma,
+                                      tl: {
+                                        hd: Res_doc.line,
+                                        tl: /* [] */0
+                                      }
+                                    }), List.map((function (param) {
+                                        return printModFunctorParam(param, cmtTbl);
+                                      }), parameters)),
+                            tl: /* [] */0
+                          }
+                        })),
+                tl: {
+                  hd: Res_doc.trailingComma,
+                  tl: {
+                    hd: Res_doc.softLine,
+                    tl: {
+                      hd: Res_doc.rparen,
+                      tl: /* [] */0
+                    }
+                  }
+                }
+              }
+            }));
+  }
+  return Res_doc.group(Res_doc.concat({
+                  hd: parametersDoc,
+                  tl: {
+                    hd: match$2[0],
+                    tl: {
+                      hd: Res_doc.text(" => "),
+                      tl: {
+                        hd: match$2[1],
+                        tl: /* [] */0
+                      }
+                    }
+                  }
+                }));
+}
+
+function printModFunctorParam(param, cmtTbl) {
+  var optModType = param[2];
+  var lbl = param[1];
+  var cmtLoc;
+  if (optModType !== undefined) {
+    var init = lbl.loc;
+    cmtLoc = {
+      loc_start: init.loc_start,
+      loc_end: optModType.pmty_loc.loc_end,
+      loc_ghost: init.loc_ghost
+    };
+  } else {
+    cmtLoc = lbl.loc;
+  }
+  var attrs = printAttributes(undefined, undefined, param[0], cmtTbl);
+  var doc = lbl.txt === "*" ? Res_doc.text("()") : Res_doc.text(lbl.txt);
+  var lblDoc = printComments(doc, cmtTbl, lbl.loc);
+  var doc$1 = Res_doc.group(Res_doc.concat({
+            hd: attrs,
+            tl: {
+              hd: lblDoc,
+              tl: {
+                hd: optModType !== undefined ? Res_doc.concat({
+                        hd: Res_doc.text(": "),
+                        tl: {
+                          hd: printModType(optModType, cmtTbl),
+                          tl: /* [] */0
+                        }
+                      }) : Res_doc.nil,
+                tl: /* [] */0
+              }
+            }
+          }));
+  return printComments(doc$1, cmtTbl, cmtLoc);
+}
+
+function printModApplyArg(modExpr, cmtTbl) {
+  var match = modExpr.pmod_desc;
+  if (match.TAG === /* Pmod_structure */1 && !match._0) {
+    return Res_doc.text("()");
+  } else {
+    return printModExpr(modExpr, cmtTbl);
+  }
+}
+
+function printExceptionDef(constr, cmtTbl) {
+  var longident = constr.pext_kind;
+  var kind;
+  if (longident.TAG === /* Pext_decl */0) {
+    var args = longident._0;
+    var exit = 0;
+    if (args.TAG === /* Pcstr_tuple */0 && !(args._0 || longident._1 !== undefined)) {
+      kind = Res_doc.nil;
+    } else {
+      exit = 1;
+    }
+    if (exit === 1) {
+      var gadt = longident._1;
+      var gadtDoc = gadt !== undefined ? Res_doc.concat({
+              hd: Res_doc.text(": "),
+              tl: {
+                hd: printTypExpr(gadt, cmtTbl),
+                tl: /* [] */0
+              }
+            }) : Res_doc.nil;
+      kind = Res_doc.concat({
+            hd: printConstructorArguments(false, args, cmtTbl),
+            tl: {
+              hd: gadtDoc,
+              tl: /* [] */0
+            }
+          });
+    }
+    
+  } else {
+    kind = Res_doc.indent(Res_doc.concat({
+              hd: Res_doc.text(" ="),
+              tl: {
+                hd: Res_doc.line,
+                tl: {
+                  hd: printLongidentLocation(longident._0, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            }));
+  }
+  var name = printComments(Res_doc.text(constr.pext_name.txt), cmtTbl, constr.pext_name.loc);
+  var doc = Res_doc.group(Res_doc.concat({
+            hd: printAttributes(undefined, undefined, constr.pext_attributes, cmtTbl),
+            tl: {
+              hd: Res_doc.text("exception "),
+              tl: {
+                hd: name,
+                tl: {
+                  hd: kind,
+                  tl: /* [] */0
+                }
+              }
+            }
+          }));
+  return printComments(doc, cmtTbl, constr.pext_loc);
+}
+
+function printExtensionConstructor(constr, cmtTbl, i) {
+  var attrs = printAttributes(undefined, undefined, constr.pext_attributes, cmtTbl);
+  var bar = i > 0 ? Res_doc.text("| ") : Res_doc.ifBreaks(Res_doc.text("| "), Res_doc.nil);
+  var longident = constr.pext_kind;
+  var kind;
+  if (longident.TAG === /* Pext_decl */0) {
+    var args = longident._0;
+    var exit = 0;
+    if (args.TAG === /* Pcstr_tuple */0 && !(args._0 || longident._1 !== undefined)) {
+      kind = Res_doc.nil;
+    } else {
+      exit = 1;
+    }
+    if (exit === 1) {
+      var gadt = longident._1;
+      var gadtDoc = gadt !== undefined ? Res_doc.concat({
+              hd: Res_doc.text(": "),
+              tl: {
+                hd: printTypExpr(gadt, cmtTbl),
+                tl: /* [] */0
+              }
+            }) : Res_doc.nil;
+      kind = Res_doc.concat({
+            hd: printConstructorArguments(false, args, cmtTbl),
+            tl: {
+              hd: gadtDoc,
+              tl: /* [] */0
+            }
+          });
+    }
+    
+  } else {
+    kind = Res_doc.indent(Res_doc.concat({
+              hd: Res_doc.text(" ="),
+              tl: {
+                hd: Res_doc.line,
+                tl: {
+                  hd: printLongidentLocation(longident._0, cmtTbl),
+                  tl: /* [] */0
+                }
+              }
+            }));
+  }
+  var name = printComments(Res_doc.text(constr.pext_name.txt), cmtTbl, constr.pext_name.loc);
+  return Res_doc.concat({
+              hd: bar,
+              tl: {
+                hd: Res_doc.group(Res_doc.concat({
+                          hd: attrs,
+                          tl: {
+                            hd: name,
+                            tl: {
+                              hd: kind,
+                              tl: /* [] */0
+                            }
+                          }
+                        })),
+                tl: /* [] */0
+              }
+            });
+}
+
+function printImplementation(width, s, comments) {
+  var cmtTbl = Res_comments_table.make(undefined);
+  Res_comments_table.walkStructure(s, cmtTbl, comments);
+  var doc = printStructure(s, cmtTbl);
+  return Res_doc.toString(width, doc) + "\n";
+}
+
+function printInterface(width, s, comments) {
+  var cmtTbl = Res_comments_table.make(undefined);
+  Res_comments_table.walkSignature(s, cmtTbl, comments);
+  return Res_doc.toString(width, printSignature(s, cmtTbl)) + "\n";
+}
+
+var Doc;
+
+var CommentTable;
+
+var $$Comment;
+
+var Token;
+
+var Parens;
+
+var ParsetreeViewer;
+
+export {
+  Doc ,
+  CommentTable ,
+  $$Comment ,
+  Token ,
+  Parens ,
+  ParsetreeViewer ,
+  convertBsExternalAttribute ,
+  convertBsExtension ,
+  addParens ,
+  addBraces ,
+  getFirstLeadingComment ,
+  hasLeadingLineComment ,
+  hasCommentBelow ,
+  printMultilineCommentContent ,
+  printTrailingComment ,
+  printLeadingComment ,
+  printCommentsInside ,
+  printLeadingComments ,
+  printTrailingComments ,
+  printComments ,
+  printList ,
+  printListi ,
+  printLongidentAux ,
+  printLongident ,
+  classifyIdentContent ,
+  printIdentLike ,
+  unsafe_for_all_range ,
+  for_all_from ,
+  isValidNumericPolyvarNumber ,
+  printPolyVarIdent ,
+  printLident ,
+  printLongidentLocation ,
+  printLidentPath ,
+  printIdentPath ,
+  printStringLoc ,
+  printStringContents ,
+  printConstant ,
+  printStructure ,
+  printStructureItem ,
+  printTypeExtension ,
+  printModuleBinding ,
+  printModuleTypeDeclaration ,
+  printModType ,
+  printWithConstraints ,
+  printWithConstraint ,
+  printSignature ,
+  printSignatureItem ,
+  printRecModuleDeclarations ,
+  printRecModuleDeclaration ,
+  printModuleDeclaration ,
+  printOpenDescription ,
+  printIncludeDescription ,
+  printIncludeDeclaration ,
+  printValueBindings ,
+  printValueDescription ,
+  printTypeDeclarations ,
+  printTypeDeclaration ,
+  printTypeDeclaration2 ,
+  printTypeDefinitionConstraints ,
+  printTypeDefinitionConstraint ,
+  printPrivateFlag ,
+  printTypeParams ,
+  printTypeParam ,
+  printRecordDeclaration ,
+  printConstructorDeclarations ,
+  printConstructorDeclaration2 ,
+  printConstructorArguments ,
+  printLabelDeclaration ,
+  printTypExpr ,
+  printObject ,
+  printTupleType ,
+  printObjectField ,
+  printTypeParameter ,
+  printValueBinding ,
+  printPackageType ,
+  printPackageConstraints ,
+  printPackageConstraint ,
+  printExtension ,
+  printPattern ,
+  printPatternRecordRow ,
+  printExpressionWithComments ,
+  printIfChain ,
+  printExpression ,
+  printPexpFun ,
+  printTernaryOperand ,
+  printSetFieldExpr ,
+  printTemplateLiteral ,
+  printUnaryExpression ,
+  printBinaryExpression ,
+  printPexpApply ,
+  printJsxExpression ,
+  printJsxFragment ,
+  printJsxChildren ,
+  printJsxProps ,
+  printJsxProp ,
+  printJsxName ,
+  printArgumentsWithCallbackInFirstPosition ,
+  printArgumentsWithCallbackInLastPosition ,
+  printArguments ,
+  printArgument ,
+  printCases ,
+  printCase ,
+  printExprFunParameters ,
+  printExpFunParameter ,
+  printExpressionBlock ,
+  printBraces ,
+  printOverrideFlag ,
+  printDirectionFlag ,
+  printRecordRow ,
+  printBsObjectRow ,
+  printAttributes ,
+  printPayload ,
+  printAttribute ,
+  printModExpr ,
+  printModFunctor ,
+  printModFunctorParam ,
+  printModApplyArg ,
+  printExceptionDef ,
+  printExtensionConstructor ,
+  printImplementation ,
+  printInterface ,
+  
+}
+/* Location Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_printer.res b/analysis/examples/larger-project/src/res_printer.res
new file mode 100644
index 0000000000..7dfa5014fe
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_printer.res
@@ -0,0 +1,5385 @@
+module Doc = Res_doc
+module CommentTable = Res_comments_table
+module Comment = Res_comment
+module Token = Res_token
+module Parens = Res_parens
+module ParsetreeViewer = Res_parsetree_viewer
+
+type callbackStyle =
+  /* regular arrow function, example: `let f = x => x + 1` */
+  | NoCallback
+  /* `Thing.map(foo, (arg1, arg2) => MyModuleBlah.toList(argument))` */
+  | FitsOnOneLine
+  /* Thing.map(longArgumet, veryLooooongArgument, (arg1, arg2) =>
+   *   MyModuleBlah.toList(argument)
+   * )
+   */
+  | ArgumentsFitOnOneLine
+
+/* Since compiler version 8.3, the bs. prefix is no longer needed */
+/* Synced from
+ https://github.com/rescript-lang/rescript-compiler/blob/29174de1a5fde3b16cf05d10f5ac109cfac5c4ca/jscomp/frontend/ast_external_process.ml#L291-L367 */
+let convertBsExternalAttribute = x =>
+  switch x {
+  | "bs.as" => "as"
+  | "bs.deriving" => "deriving"
+  | "bs.get" => "get"
+  | "bs.get_index" => "get_index"
+  | "bs.ignore" => "ignore"
+  | "bs.inline" => "inline"
+  | "bs.int" => "int"
+  | "bs.meth" => "meth"
+  | "bs.module" => "module"
+  | "bs.new" => "new"
+  | "bs.obj" => "obj"
+  | "bs.optional" => "optional"
+  | "bs.return" => "return"
+  | "bs.send" => "send"
+  | "bs.scope" => "scope"
+  | "bs.set" => "set"
+  | "bs.set_index" => "set_index"
+  | "bs.splice" | "bs.variadic" => "variadic"
+  | "bs.string" => "string"
+  | "bs.this" => "this"
+  | "bs.uncurry" => "uncurry"
+  | "bs.unwrap" => "unwrap"
+  | "bs.val" => "val"
+  /* bs.send.pipe shouldn't be transformed */
+  | txt => txt
+  }
+
+/* These haven't been needed for a long time now */
+/* Synced from
+ https://github.com/rescript-lang/rescript-compiler/blob/29174de1a5fde3b16cf05d10f5ac109cfac5c4ca/jscomp/frontend/ast_exp_extension.ml */
+let convertBsExtension = x =>
+  switch x {
+  | "bs.debugger" => "debugger"
+  | "bs.external" => "raw"
+  /* We should never see this one since we use the sugared object form, but still */
+  | "bs.obj" => "obj"
+  | "bs.raw" => "raw"
+  | "bs.re" => "re"
+  /* TODO: what about bs.time and bs.node? */
+  | txt => txt
+  }
+
+let addParens = doc =>
+  Doc.group(
+    Doc.concat(list{
+      Doc.lparen,
+      Doc.indent(Doc.concat(list{Doc.softLine, doc})),
+      Doc.softLine,
+      Doc.rparen,
+    }),
+  )
+
+let addBraces = doc =>
+  Doc.group(
+    Doc.concat(list{
+      Doc.lbrace,
+      Doc.indent(Doc.concat(list{Doc.softLine, doc})),
+      Doc.softLine,
+      Doc.rbrace,
+    }),
+  )
+
+let getFirstLeadingComment = (tbl, loc) =>
+  switch Hashtbl.find(tbl.CommentTable.leading, loc) {
+  | list{comment, ..._} => Some(comment)
+  | list{} => None
+  | exception Not_found => None
+  }
+
+/* Checks if `loc` has a leading line comment, i.e. `// comment above` */
+let hasLeadingLineComment = (tbl, loc) =>
+  switch getFirstLeadingComment(tbl, loc) {
+  | Some(comment) => Comment.isSingleLineComment(comment)
+  | None => false
+  }
+
+let hasCommentBelow = (tbl, loc) =>
+  switch Hashtbl.find(tbl.CommentTable.trailing, loc) {
+  | list{comment, ..._} =>
+    let commentLoc = Comment.loc(comment)
+    commentLoc.Location.loc_start.pos_lnum > loc.Location.loc_end.pos_lnum
+  | list{} => false
+  | exception Not_found => false
+  }
+
+let printMultilineCommentContent = txt => {
+  /* Turns
+   *         |* first line
+   *  * second line
+   *      * third line *|
+   * Into
+   * |* first line
+   *  * second line
+   *  * third line *|
+   *
+   * What makes a comment suitable for this kind of indentation?
+   *  ->  multiple lines + every line starts with a star
+   */
+  let rec indentStars = (lines, acc) =>
+    switch lines {
+    | list{} => Doc.nil
+    | list{lastLine} =>
+      let line = String.trim(lastLine)
+      let doc = Doc.text(" " ++ line)
+      let trailingSpace = if line == "" {
+        Doc.nil
+      } else {
+        Doc.space
+      }
+      List.rev(list{trailingSpace, doc, ...acc}) |> Doc.concat
+    | list{line, ...lines} =>
+      let line = String.trim(line)
+      if line !== "" && String.unsafe_get(line, 0) === '*' {
+        let doc = Doc.text(" " ++ line)
+        indentStars(lines, list{Doc.hardLine, doc, ...acc})
+      } else {
+        let trailingSpace = {
+          let len = String.length(txt)
+          if len > 0 && String.unsafe_get(txt, len - 1) == ' ' {
+            Doc.space
+          } else {
+            Doc.nil
+          }
+        }
+
+        let content = Comment.trimSpaces(txt)
+        Doc.concat(list{Doc.text(content), trailingSpace})
+      }
+    }
+
+  let lines = String.split_on_char('\n', txt)
+  switch lines {
+  | list{} => Doc.text("/* */")
+  | list{line} =>
+    Doc.concat(list{Doc.text("/* "), Doc.text(Comment.trimSpaces(line)), Doc.text(" */")})
+  | list{first, ...rest} =>
+    let firstLine = Comment.trimSpaces(first)
+    Doc.concat(list{
+      Doc.text("/*"),
+      switch firstLine {
+      | "" | "*" => Doc.nil
+      | _ => Doc.space
+      },
+      indentStars(rest, list{Doc.hardLine, Doc.text(firstLine)}),
+      Doc.text("*/"),
+    })
+  }
+}
+
+let printTrailingComment = (prevLoc: Location.t, nodeLoc: Location.t, comment) => {
+  let singleLine = Comment.isSingleLineComment(comment)
+  let content = {
+    let txt = Comment.txt(comment)
+    if singleLine {
+      Doc.text("//" ++ txt)
+    } else {
+      printMultilineCommentContent(txt)
+    }
+  }
+
+  let diff = {
+    let cmtStart = Comment.loc(comment).loc_start
+    cmtStart.pos_lnum - prevLoc.loc_end.pos_lnum
+  }
+
+  let isBelow = Comment.loc(comment).loc_start.pos_lnum > nodeLoc.loc_end.pos_lnum
+  if diff > 0 || isBelow {
+    Doc.concat(list{
+      Doc.breakParent,
+      Doc.lineSuffix(
+        Doc.concat(list{
+          Doc.hardLine,
+          if diff > 1 {
+            Doc.hardLine
+          } else {
+            Doc.nil
+          },
+          content,
+        }),
+      ),
+    })
+  } else if !singleLine {
+    Doc.concat(list{Doc.space, content})
+  } else {
+    Doc.lineSuffix(Doc.concat(list{Doc.space, content}))
+  }
+}
+
+let printLeadingComment = (~nextComment=?, comment) => {
+  let singleLine = Comment.isSingleLineComment(comment)
+  let content = {
+    let txt = Comment.txt(comment)
+    if singleLine {
+      Doc.text("//" ++ txt)
+    } else {
+      printMultilineCommentContent(txt)
+    }
+  }
+
+  let separator = Doc.concat(list{
+    if singleLine {
+      Doc.concat(list{Doc.hardLine, Doc.breakParent})
+    } else {
+      Doc.nil
+    },
+    switch nextComment {
+    | Some(next) =>
+      let nextLoc = Comment.loc(next)
+      let currLoc = Comment.loc(comment)
+      let diff = nextLoc.Location.loc_start.pos_lnum - currLoc.Location.loc_end.pos_lnum
+
+      let nextSingleLine = Comment.isSingleLineComment(next)
+      if singleLine && nextSingleLine {
+        if diff > 1 {
+          Doc.hardLine
+        } else {
+          Doc.nil
+        }
+      } else if singleLine && !nextSingleLine {
+        if diff > 1 {
+          Doc.hardLine
+        } else {
+          Doc.nil
+        }
+      } else if diff > 1 {
+        Doc.concat(list{Doc.hardLine, Doc.hardLine})
+      } else if diff === 1 {
+        Doc.hardLine
+      } else {
+        Doc.space
+      }
+    | None => Doc.nil
+    },
+  })
+
+  Doc.concat(list{content, separator})
+}
+
+let printCommentsInside = (cmtTbl, loc) => {
+  let rec loop = (acc, comments) =>
+    switch comments {
+    | list{} => Doc.nil
+    | list{comment} =>
+      let cmtDoc = printLeadingComment(comment)
+      let doc = Doc.group(Doc.concat(list{Doc.concat(List.rev(list{cmtDoc, ...acc}))}))
+
+      doc
+    | list{comment, ...list{nextComment, ..._comments} as rest} =>
+      let cmtDoc = printLeadingComment(~nextComment, comment)
+      loop(list{cmtDoc, ...acc}, rest)
+    }
+
+  switch Hashtbl.find(cmtTbl.CommentTable.inside, loc) {
+  | exception Not_found => Doc.nil
+  | comments =>
+    Hashtbl.remove(cmtTbl.inside, loc)
+    Doc.group(loop(list{}, comments))
+  }
+}
+
+let printLeadingComments = (node, tbl, loc) => {
+  let rec loop = (acc, comments) =>
+    switch comments {
+    | list{} => node
+    | list{comment} =>
+      let cmtDoc = printLeadingComment(comment)
+      let diff = loc.Location.loc_start.pos_lnum - Comment.loc(comment).Location.loc_end.pos_lnum
+
+      let separator = if Comment.isSingleLineComment(comment) {
+        if diff > 1 {
+          Doc.hardLine
+        } else {
+          Doc.nil
+        }
+      } else if diff === 0 {
+        Doc.space
+      } else if diff > 1 {
+        Doc.concat(list{Doc.hardLine, Doc.hardLine})
+      } else {
+        Doc.hardLine
+      }
+
+      let doc = Doc.group(
+        Doc.concat(list{Doc.concat(List.rev(list{cmtDoc, ...acc})), separator, node}),
+      )
+
+      doc
+    | list{comment, ...list{nextComment, ..._comments} as rest} =>
+      let cmtDoc = printLeadingComment(~nextComment, comment)
+      loop(list{cmtDoc, ...acc}, rest)
+    }
+
+  switch Hashtbl.find(tbl, loc) {
+  | exception Not_found => node
+  | comments =>
+    /* Remove comments from tbl: Some ast nodes have the same location.
+     * We only want to print comments once */
+    Hashtbl.remove(tbl, loc)
+    loop(list{}, comments)
+  }
+}
+
+let printTrailingComments = (node, tbl, loc) => {
+  let rec loop = (prev, acc, comments) =>
+    switch comments {
+    | list{} => Doc.concat(List.rev(acc))
+    | list{comment, ...comments} =>
+      let cmtDoc = printTrailingComment(prev, loc, comment)
+      loop(Comment.loc(comment), list{cmtDoc, ...acc}, comments)
+    }
+
+  switch Hashtbl.find(tbl, loc) {
+  | exception Not_found => node
+  | list{} => node
+  | list{_first, ..._} as comments =>
+    /* Remove comments from tbl: Some ast nodes have the same location.
+     * We only want to print comments once */
+    Hashtbl.remove(tbl, loc)
+    let cmtsDoc = loop(loc, list{}, comments)
+    Doc.concat(list{node, cmtsDoc})
+  }
+}
+
+let printComments = (doc, tbl: CommentTable.t, loc) => {
+  let docWithLeadingComments = printLeadingComments(doc, tbl.leading, loc)
+  printTrailingComments(docWithLeadingComments, tbl.trailing, loc)
+}
+
+let printList = (~getLoc, ~nodes, ~print, ~forceBreak=false, t) => {
+  let rec loop = (prevLoc: Location.t, acc, nodes) =>
+    switch nodes {
+    | list{} => (prevLoc, Doc.concat(List.rev(acc)))
+    | list{node, ...nodes} =>
+      let loc = getLoc(node)
+      let startPos = switch getFirstLeadingComment(t, loc) {
+      | None => loc.loc_start
+      | Some(comment) => Comment.loc(comment).loc_start
+      }
+
+      let sep = if startPos.pos_lnum - prevLoc.loc_end.pos_lnum > 1 {
+        Doc.concat(list{Doc.hardLine, Doc.hardLine})
+      } else {
+        Doc.hardLine
+      }
+
+      let doc = printComments(print(node, t), t, loc)
+      loop(loc, list{doc, sep, ...acc}, nodes)
+    }
+
+  switch nodes {
+  | list{} => Doc.nil
+  | list{node, ...nodes} =>
+    let firstLoc = getLoc(node)
+    let doc = printComments(print(node, t), t, firstLoc)
+    let (lastLoc, docs) = loop(firstLoc, list{doc}, nodes)
+    let forceBreak = forceBreak || firstLoc.loc_start.pos_lnum !== lastLoc.loc_end.pos_lnum
+
+    Doc.breakableGroup(~forceBreak, docs)
+  }
+}
+
+let printListi = (~getLoc, ~nodes, ~print, ~forceBreak=false, t) => {
+  let rec loop = (i, prevLoc: Location.t, acc, nodes) =>
+    switch nodes {
+    | list{} => (prevLoc, Doc.concat(List.rev(acc)))
+    | list{node, ...nodes} =>
+      let loc = getLoc(node)
+      let startPos = switch getFirstLeadingComment(t, loc) {
+      | None => loc.loc_start
+      | Some(comment) => Comment.loc(comment).loc_start
+      }
+
+      let sep = if startPos.pos_lnum - prevLoc.loc_end.pos_lnum > 1 {
+        Doc.concat(list{Doc.hardLine, Doc.hardLine})
+      } else {
+        Doc.line
+      }
+
+      let doc = printComments(print(node, t, i), t, loc)
+      loop(i + 1, loc, list{doc, sep, ...acc}, nodes)
+    }
+
+  switch nodes {
+  | list{} => Doc.nil
+  | list{node, ...nodes} =>
+    let firstLoc = getLoc(node)
+    let doc = printComments(print(node, t, 0), t, firstLoc)
+    let (lastLoc, docs) = loop(1, firstLoc, list{doc}, nodes)
+    let forceBreak = forceBreak || firstLoc.loc_start.pos_lnum !== lastLoc.loc_end.pos_lnum
+
+    Doc.breakableGroup(~forceBreak, docs)
+  }
+}
+
+let rec printLongidentAux = (accu, x) =>
+  switch x {
+  | Longident.Lident(s) => list{Doc.text(s), ...accu}
+  | Ldot(lid, s) => printLongidentAux(list{Doc.text(s), ...accu}, lid)
+  | Lapply(lid1, lid2) =>
+    let d1 = Doc.join(~sep=Doc.dot, printLongidentAux(list{}, lid1))
+    let d2 = Doc.join(~sep=Doc.dot, printLongidentAux(list{}, lid2))
+    list{Doc.concat(list{d1, Doc.lparen, d2, Doc.rparen}), ...accu}
+  }
+
+let printLongident = x =>
+  switch x {
+  | Longident.Lident(txt) => Doc.text(txt)
+  | lid => Doc.join(~sep=Doc.dot, printLongidentAux(list{}, lid))
+  }
+
+type identifierStyle =
+  | ExoticIdent
+  | NormalIdent
+
+let classifyIdentContent = (~allowUident=false, txt) =>
+  if Token.isKeywordTxt(txt) {
+    ExoticIdent
+  } else {
+    let len = String.length(txt)
+    let rec loop = i =>
+      if i === len {
+        NormalIdent
+      } else if i === 0 {
+        switch String.unsafe_get(txt, i) {
+        | 'A' .. 'Z' if allowUident => loop(i + 1)
+        | 'a' .. 'z' | '_' => loop(i + 1)
+        | _ => ExoticIdent
+        }
+      } else {
+        switch String.unsafe_get(txt, i) {
+        | 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '\'' | '_' => loop(i + 1)
+        | _ => ExoticIdent
+        }
+      }
+
+    loop(0)
+  }
+
+let printIdentLike = (~allowUident=?, txt) =>
+  switch classifyIdentContent(~allowUident?, txt) {
+  | ExoticIdent => Doc.concat(list{Doc.text("\\\""), Doc.text(txt), Doc.text("\"")})
+  | NormalIdent => Doc.text(txt)
+  }
+
+let rec unsafe_for_all_range = (s, ~start, ~finish, p) =>
+  start > finish ||
+    (p(String.unsafe_get(s, start)) && unsafe_for_all_range(s, ~start=start + 1, ~finish, p))
+
+let for_all_from = (s, start, p) => {
+  let len = String.length(s)
+  unsafe_for_all_range(s, ~start, ~finish=len - 1, p)
+}
+
+/* See https://github.com/rescript-lang/rescript-compiler/blob/726cfa534314b586e5b5734471bc2023ad99ebd9/jscomp/ext/ext_string.ml#L510 */
+let isValidNumericPolyvarNumber = (x: string) => {
+  let len = String.length(x)
+  len > 0 && {
+      let a = Char.code(String.unsafe_get(x, 0))
+      a <= 57 && if len > 1 {
+          a > 48 &&
+            for_all_from(x, 1, x =>
+              switch x {
+              | '0' .. '9' => true
+              | _ => false
+              }
+            )
+        } else {
+          a >= 48
+        }
+    }
+}
+
+/* Exotic identifiers in poly-vars have a "lighter" syntax: #"ease-in" */
+let printPolyVarIdent = txt =>
+  /* numeric poly-vars don't need quotes: #644 */
+  if isValidNumericPolyvarNumber(txt) {
+    Doc.text(txt)
+  } else {
+    switch classifyIdentContent(~allowUident=true, txt) {
+    | ExoticIdent => Doc.concat(list{Doc.text("\""), Doc.text(txt), Doc.text("\"")})
+    | NormalIdent =>
+      switch txt {
+      | "" => Doc.concat(list{Doc.text("\""), Doc.text(txt), Doc.text("\"")})
+      | _ => Doc.text(txt)
+      }
+    }
+  }
+
+let printLident = l => {
+  let flatLidOpt = lid => {
+    let rec flat = (accu, x) =>
+      switch x {
+      | Longident.Lident(s) => Some(list{s, ...accu})
+      | Ldot(lid, s) => flat(list{s, ...accu}, lid)
+      | Lapply(_, _) => None
+      }
+
+    flat(list{}, lid)
+  }
+
+  switch l {
+  | Longident.Lident(txt) => printIdentLike(txt)
+  | Longident.Ldot(path, txt) =>
+    let doc = switch flatLidOpt(path) {
+    | Some(txts) =>
+      Doc.concat(list{
+        Doc.join(~sep=Doc.dot, List.map(Doc.text, txts)),
+        Doc.dot,
+        printIdentLike(txt),
+      })
+    | None => Doc.text("printLident: Longident.Lapply is not supported")
+    }
+
+    doc
+  | Lapply(_, _) => Doc.text("printLident: Longident.Lapply is not supported")
+  }
+}
+
+let printLongidentLocation = (l, cmtTbl) => {
+  let doc = printLongident(l.Location.txt)
+  printComments(doc, cmtTbl, l.loc)
+}
+
+/* Module.SubModule.x */
+let printLidentPath = (path, cmtTbl) => {
+  let doc = printLident(path.Location.txt)
+  printComments(doc, cmtTbl, path.loc)
+}
+
+/* Module.SubModule.x or Module.SubModule.X */
+let printIdentPath = (path, cmtTbl) => {
+  let doc = printLident(path.Location.txt)
+  printComments(doc, cmtTbl, path.loc)
+}
+
+let printStringLoc = (sloc, cmtTbl) => {
+  let doc = printIdentLike(sloc.Location.txt)
+  printComments(doc, cmtTbl, sloc.loc)
+}
+
+let printStringContents = txt => {
+  let lines = String.split_on_char('\n', txt)
+  Doc.join(~sep=Doc.literalLine, List.map(Doc.text, lines))
+}
+
+let printConstant = (~templateLiteral=false, c) =>
+  switch c {
+  | Parsetree.Pconst_integer(s, suffix) =>
+    switch suffix {
+    | Some(c) => Doc.text(s ++ Char.escaped(c))
+    | None => Doc.text(s)
+    }
+  | Pconst_string(txt, None) =>
+    Doc.concat(list{Doc.text("\""), printStringContents(txt), Doc.text("\"")})
+  | Pconst_string(txt, Some(prefix)) =>
+    if prefix == "INTERNAL_RES_CHAR_CONTENTS" {
+      Doc.concat(list{Doc.text("'"), Doc.text(txt), Doc.text("'")})
+    } else {
+      let (lquote, rquote) = if templateLiteral {
+        ("`", "`")
+      } else {
+        ("\"", "\"")
+      }
+
+      Doc.concat(list{
+        if prefix == "js" {
+          Doc.nil
+        } else {
+          Doc.text(prefix)
+        },
+        Doc.text(lquote),
+        printStringContents(txt),
+        Doc.text(rquote),
+      })
+    }
+  | Pconst_float(s, _) => Doc.text(s)
+  | Pconst_char(c) =>
+    let str = switch c {
+    | '\'' => "\\'"
+    | '\\' => "\\\\"
+    | '\n' => "\\n"
+    | '\t' => "\\t"
+    | '\r' => "\\r"
+    | '\b' => "\\b"
+    | ' ' .. '~' as c =>
+      let s = (@doesNotRaise Bytes.create)(1)
+      Bytes.unsafe_set(s, 0, c)
+      Bytes.unsafe_to_string(s)
+    | c => Res_utf8.encodeCodePoint(Obj.magic(c))
+    }
+
+    Doc.text("'" ++ (str ++ "'"))
+  }
+
+let rec printStructure = (s: Parsetree.structure, t) =>
+  switch s {
+  | list{} => printCommentsInside(t, Location.none)
+  | structure =>
+    printList(~getLoc=s => s.Parsetree.pstr_loc, ~nodes=structure, ~print=printStructureItem, t)
+  }
+
+and printStructureItem = (si: Parsetree.structure_item, cmtTbl) =>
+  switch si.pstr_desc {
+  | Pstr_value(rec_flag, valueBindings) =>
+    let recFlag = switch rec_flag {
+    | Asttypes.Nonrecursive => Doc.nil
+    | Asttypes.Recursive => Doc.text("rec ")
+    }
+
+    printValueBindings(~recFlag, valueBindings, cmtTbl)
+  | Pstr_type(recFlag, typeDeclarations) =>
+    let recFlag = switch recFlag {
+    | Asttypes.Nonrecursive => Doc.nil
+    | Asttypes.Recursive => Doc.text("rec ")
+    }
+
+    printTypeDeclarations(~recFlag, typeDeclarations, cmtTbl)
+  | Pstr_primitive(valueDescription) => printValueDescription(valueDescription, cmtTbl)
+  | Pstr_eval(expr, attrs) =>
+    let exprDoc = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.structureExpr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{printAttributes(attrs, cmtTbl), exprDoc})
+  | Pstr_attribute(attr) => Doc.concat(list{Doc.text("@"), printAttribute(attr, cmtTbl)})
+  | Pstr_extension(extension, attrs) =>
+    Doc.concat(list{
+      printAttributes(attrs, cmtTbl),
+      Doc.concat(list{printExtension(~atModuleLvl=true, extension, cmtTbl)}),
+    })
+  | Pstr_include(includeDeclaration) => printIncludeDeclaration(includeDeclaration, cmtTbl)
+  | Pstr_open(openDescription) => printOpenDescription(openDescription, cmtTbl)
+  | Pstr_modtype(modTypeDecl) => printModuleTypeDeclaration(modTypeDecl, cmtTbl)
+  | Pstr_module(moduleBinding) => printModuleBinding(~isRec=false, moduleBinding, cmtTbl, 0)
+  | Pstr_recmodule(moduleBindings) =>
+    printListi(
+      ~getLoc=mb => mb.Parsetree.pmb_loc,
+      ~nodes=moduleBindings,
+      ~print=printModuleBinding(~isRec=true),
+      cmtTbl,
+    )
+  | Pstr_exception(extensionConstructor) => printExceptionDef(extensionConstructor, cmtTbl)
+  | Pstr_typext(typeExtension) => printTypeExtension(typeExtension, cmtTbl)
+  | Pstr_class(_) | Pstr_class_type(_) => Doc.nil
+  }
+
+and printTypeExtension = (te: Parsetree.type_extension, cmtTbl) => {
+  let prefix = Doc.text("type ")
+  let name = printLidentPath(te.ptyext_path, cmtTbl)
+  let typeParams = printTypeParams(te.ptyext_params, cmtTbl)
+  let extensionConstructors = {
+    let ecs = te.ptyext_constructors
+    let forceBreak = switch (ecs, List.rev(ecs)) {
+    | (list{first, ..._}, list{last, ..._}) =>
+      first.pext_loc.loc_start.pos_lnum > te.ptyext_path.loc.loc_end.pos_lnum ||
+        first.pext_loc.loc_start.pos_lnum < last.pext_loc.loc_end.pos_lnum
+    | _ => false
+    }
+
+    let privateFlag = switch te.ptyext_private {
+    | Asttypes.Private => Doc.concat(list{Doc.text("private"), Doc.line})
+    | Public => Doc.nil
+    }
+
+    let rows = printListi(
+      ~getLoc=n => n.Parsetree.pext_loc,
+      ~print=printExtensionConstructor,
+      ~nodes=ecs,
+      ~forceBreak,
+      cmtTbl,
+    )
+
+    Doc.breakableGroup(
+      ~forceBreak,
+      Doc.indent(
+        Doc.concat(list{
+          Doc.line,
+          privateFlag,
+          rows,
+          /* Doc.join ~sep:Doc.line ( */
+          /* List.mapi printExtensionConstructor ecs */
+          /* ) */
+        }),
+      ),
+    )
+  }
+
+  Doc.group(
+    Doc.concat(list{
+      printAttributes(~loc=te.ptyext_path.loc, te.ptyext_attributes, cmtTbl),
+      prefix,
+      name,
+      typeParams,
+      Doc.text(" +="),
+      extensionConstructors,
+    }),
+  )
+}
+
+and printModuleBinding = (~isRec, moduleBinding, cmtTbl, i) => {
+  let prefix = if i == 0 {
+    Doc.concat(list{
+      Doc.text("module "),
+      if isRec {
+        Doc.text("rec ")
+      } else {
+        Doc.nil
+      },
+    })
+  } else {
+    Doc.text("and ")
+  }
+
+  let (modExprDoc, modConstraintDoc) = switch moduleBinding.pmb_expr {
+  | {pmod_desc: Pmod_constraint(modExpr, modType)} => (
+      printModExpr(modExpr, cmtTbl),
+      Doc.concat(list{Doc.text(": "), printModType(modType, cmtTbl)}),
+    )
+  | modExpr => (printModExpr(modExpr, cmtTbl), Doc.nil)
+  }
+
+  let modName = {
+    let doc = Doc.text(moduleBinding.pmb_name.Location.txt)
+    printComments(doc, cmtTbl, moduleBinding.pmb_name.loc)
+  }
+
+  let doc = Doc.concat(list{
+    printAttributes(~loc=moduleBinding.pmb_name.loc, moduleBinding.pmb_attributes, cmtTbl),
+    prefix,
+    modName,
+    modConstraintDoc,
+    Doc.text(" = "),
+    modExprDoc,
+  })
+  printComments(doc, cmtTbl, moduleBinding.pmb_loc)
+}
+
+and printModuleTypeDeclaration = (modTypeDecl: Parsetree.module_type_declaration, cmtTbl) => {
+  let modName = {
+    let doc = Doc.text(modTypeDecl.pmtd_name.txt)
+    printComments(doc, cmtTbl, modTypeDecl.pmtd_name.loc)
+  }
+
+  Doc.concat(list{
+    printAttributes(modTypeDecl.pmtd_attributes, cmtTbl),
+    Doc.text("module type "),
+    modName,
+    switch modTypeDecl.pmtd_type {
+    | None => Doc.nil
+    | Some(modType) => Doc.concat(list{Doc.text(" = "), printModType(modType, cmtTbl)})
+    },
+  })
+}
+
+and printModType = (modType, cmtTbl) => {
+  let modTypeDoc = switch modType.pmty_desc {
+  | Parsetree.Pmty_ident(longident) =>
+    Doc.concat(list{
+      printAttributes(~loc=longident.loc, modType.pmty_attributes, cmtTbl),
+      printLongidentLocation(longident, cmtTbl),
+    })
+  | Pmty_signature(list{}) =>
+    let shouldBreak = modType.pmty_loc.loc_start.pos_lnum < modType.pmty_loc.loc_end.pos_lnum
+
+    Doc.breakableGroup(
+      ~forceBreak=shouldBreak,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(Doc.concat(list{Doc.softLine, printCommentsInside(cmtTbl, modType.pmty_loc)})),
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  | Pmty_signature(signature) =>
+    let signatureDoc = Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(Doc.concat(list{Doc.line, printSignature(signature, cmtTbl)})),
+        Doc.line,
+        Doc.rbrace,
+      }),
+    )
+    Doc.concat(list{printAttributes(modType.pmty_attributes, cmtTbl), signatureDoc})
+  | Pmty_functor(_) =>
+    let (parameters, returnType) = ParsetreeViewer.functorType(modType)
+    let parametersDoc = switch parameters {
+    | list{} => Doc.nil
+    | list{(attrs, {Location.txt: "_", loc}, Some(modType))} =>
+      let cmtLoc = {...loc, loc_end: modType.Parsetree.pmty_loc.loc_end}
+
+      let attrs = printAttributes(attrs, cmtTbl)
+      let doc = Doc.concat(list{attrs, printModType(modType, cmtTbl)})
+      printComments(doc, cmtTbl, cmtLoc)
+    | params =>
+      Doc.group(
+        Doc.concat(list{
+          Doc.lparen,
+          Doc.indent(
+            Doc.concat(list{
+              Doc.softLine,
+              Doc.join(
+                ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+                List.map(((attrs, lbl, modType)) => {
+                  let cmtLoc = switch modType {
+                  | None => lbl.Asttypes.loc
+                  | Some(modType) => {
+                      ...lbl.Asttypes.loc,
+                      loc_end: modType.Parsetree.pmty_loc.loc_end,
+                    }
+                  }
+
+                  let attrs = printAttributes(attrs, cmtTbl)
+                  let lblDoc = if lbl.Location.txt == "_" || lbl.txt == "*" {
+                    Doc.nil
+                  } else {
+                    let doc = Doc.text(lbl.txt)
+                    printComments(doc, cmtTbl, lbl.loc)
+                  }
+
+                  let doc = Doc.concat(list{
+                    attrs,
+                    lblDoc,
+                    switch modType {
+                    | None => Doc.nil
+                    | Some(modType) =>
+                      Doc.concat(list{
+                        if lbl.txt == "_" {
+                          Doc.nil
+                        } else {
+                          Doc.text(": ")
+                        },
+                        printModType(modType, cmtTbl),
+                      })
+                    },
+                  })
+                  printComments(doc, cmtTbl, cmtLoc)
+                }, params),
+              ),
+            }),
+          ),
+          Doc.trailingComma,
+          Doc.softLine,
+          Doc.rparen,
+        }),
+      )
+    }
+
+    let returnDoc = {
+      let doc = printModType(returnType, cmtTbl)
+      if Parens.modTypeFunctorReturn(returnType) {
+        addParens(doc)
+      } else {
+        doc
+      }
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        parametersDoc,
+        Doc.group(Doc.concat(list{Doc.text(" =>"), Doc.line, returnDoc})),
+      }),
+    )
+  | Pmty_typeof(modExpr) =>
+    Doc.concat(list{Doc.text("module type of "), printModExpr(modExpr, cmtTbl)})
+  | Pmty_extension(extension) => printExtension(~atModuleLvl=false, extension, cmtTbl)
+  | Pmty_alias(longident) =>
+    Doc.concat(list{Doc.text("module "), printLongidentLocation(longident, cmtTbl)})
+  | Pmty_with(modType, withConstraints) =>
+    let operand = {
+      let doc = printModType(modType, cmtTbl)
+      if Parens.modTypeWithOperand(modType) {
+        addParens(doc)
+      } else {
+        doc
+      }
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        operand,
+        Doc.indent(Doc.concat(list{Doc.line, printWithConstraints(withConstraints, cmtTbl)})),
+      }),
+    )
+  }
+
+  let attrsAlreadyPrinted = switch modType.pmty_desc {
+  | Pmty_functor(_) | Pmty_signature(_) | Pmty_ident(_) => true
+  | _ => false
+  }
+
+  let doc = Doc.concat(list{
+    if attrsAlreadyPrinted {
+      Doc.nil
+    } else {
+      printAttributes(modType.pmty_attributes, cmtTbl)
+    },
+    modTypeDoc,
+  })
+  printComments(doc, cmtTbl, modType.pmty_loc)
+}
+
+and printWithConstraints = (withConstraints, cmtTbl) => {
+  let rows = List.mapi((i, withConstraint) =>
+    Doc.group(
+      Doc.concat(list{
+        if i === 0 {
+          Doc.text("with ")
+        } else {
+          Doc.text("and ")
+        },
+        printWithConstraint(withConstraint, cmtTbl),
+      }),
+    )
+  , withConstraints)
+
+  Doc.join(~sep=Doc.line, rows)
+}
+
+and printWithConstraint = (withConstraint: Parsetree.with_constraint, cmtTbl) =>
+  switch withConstraint {
+  /* with type X.t = ... */
+  | Pwith_type(longident, typeDeclaration) =>
+    Doc.group(
+      printTypeDeclaration(
+        ~name=printLidentPath(longident, cmtTbl),
+        ~equalSign="=",
+        ~recFlag=Doc.nil,
+        0,
+        typeDeclaration,
+        CommentTable.empty,
+      ),
+    )
+  /* with module X.Y = Z */
+  | Pwith_module({txt: longident1}, {txt: longident2}) =>
+    Doc.concat(list{
+      Doc.text("module "),
+      printLongident(longident1),
+      Doc.text(" ="),
+      Doc.indent(Doc.concat(list{Doc.line, printLongident(longident2)})),
+    })
+  /* with type X.t := ..., same format as [Pwith_type] */
+  | Pwith_typesubst(longident, typeDeclaration) =>
+    Doc.group(
+      printTypeDeclaration(
+        ~name=printLidentPath(longident, cmtTbl),
+        ~equalSign=":=",
+        ~recFlag=Doc.nil,
+        0,
+        typeDeclaration,
+        CommentTable.empty,
+      ),
+    )
+  | Pwith_modsubst({txt: longident1}, {txt: longident2}) =>
+    Doc.concat(list{
+      Doc.text("module "),
+      printLongident(longident1),
+      Doc.text(" :="),
+      Doc.indent(Doc.concat(list{Doc.line, printLongident(longident2)})),
+    })
+  }
+
+and printSignature = (signature, cmtTbl) =>
+  switch signature {
+  | list{} => printCommentsInside(cmtTbl, Location.none)
+  | signature =>
+    printList(
+      ~getLoc=s => s.Parsetree.psig_loc,
+      ~nodes=signature,
+      ~print=printSignatureItem,
+      cmtTbl,
+    )
+  }
+
+and printSignatureItem = (si: Parsetree.signature_item, cmtTbl) =>
+  switch si.psig_desc {
+  | Parsetree.Psig_value(valueDescription) => printValueDescription(valueDescription, cmtTbl)
+  | Psig_type(recFlag, typeDeclarations) =>
+    let recFlag = switch recFlag {
+    | Asttypes.Nonrecursive => Doc.nil
+    | Asttypes.Recursive => Doc.text("rec ")
+    }
+
+    printTypeDeclarations(~recFlag, typeDeclarations, cmtTbl)
+  | Psig_typext(typeExtension) => printTypeExtension(typeExtension, cmtTbl)
+  | Psig_exception(extensionConstructor) => printExceptionDef(extensionConstructor, cmtTbl)
+  | Psig_module(moduleDeclaration) => printModuleDeclaration(moduleDeclaration, cmtTbl)
+  | Psig_recmodule(moduleDeclarations) => printRecModuleDeclarations(moduleDeclarations, cmtTbl)
+  | Psig_modtype(modTypeDecl) => printModuleTypeDeclaration(modTypeDecl, cmtTbl)
+  | Psig_open(openDescription) => printOpenDescription(openDescription, cmtTbl)
+  | Psig_include(includeDescription) => printIncludeDescription(includeDescription, cmtTbl)
+  | Psig_attribute(attr) => Doc.concat(list{Doc.text("@"), printAttribute(attr, cmtTbl)})
+  | Psig_extension(extension, attrs) =>
+    Doc.concat(list{
+      printAttributes(attrs, cmtTbl),
+      Doc.concat(list{printExtension(~atModuleLvl=true, extension, cmtTbl)}),
+    })
+  | Psig_class(_) | Psig_class_type(_) => Doc.nil
+  }
+
+and printRecModuleDeclarations = (moduleDeclarations, cmtTbl) =>
+  printListi(
+    ~getLoc=n => n.Parsetree.pmd_loc,
+    ~nodes=moduleDeclarations,
+    ~print=printRecModuleDeclaration,
+    cmtTbl,
+  )
+
+and printRecModuleDeclaration = (md, cmtTbl, i) => {
+  let body = switch md.pmd_type.pmty_desc {
+  | Parsetree.Pmty_alias(longident) =>
+    Doc.concat(list{Doc.text(" = "), printLongidentLocation(longident, cmtTbl)})
+  | _ =>
+    let needsParens = switch md.pmd_type.pmty_desc {
+    | Pmty_with(_) => true
+    | _ => false
+    }
+
+    let modTypeDoc = {
+      let doc = printModType(md.pmd_type, cmtTbl)
+      if needsParens {
+        addParens(doc)
+      } else {
+        doc
+      }
+    }
+
+    Doc.concat(list{Doc.text(": "), modTypeDoc})
+  }
+
+  let prefix = if i < 1 {
+    "module rec "
+  } else {
+    "and "
+  }
+  Doc.concat(list{
+    printAttributes(~loc=md.pmd_name.loc, md.pmd_attributes, cmtTbl),
+    Doc.text(prefix),
+    printComments(Doc.text(md.pmd_name.txt), cmtTbl, md.pmd_name.loc),
+    body,
+  })
+}
+
+and printModuleDeclaration = (md: Parsetree.module_declaration, cmtTbl) => {
+  let body = switch md.pmd_type.pmty_desc {
+  | Parsetree.Pmty_alias(longident) =>
+    Doc.concat(list{Doc.text(" = "), printLongidentLocation(longident, cmtTbl)})
+  | _ => Doc.concat(list{Doc.text(": "), printModType(md.pmd_type, cmtTbl)})
+  }
+
+  Doc.concat(list{
+    printAttributes(~loc=md.pmd_name.loc, md.pmd_attributes, cmtTbl),
+    Doc.text("module "),
+    printComments(Doc.text(md.pmd_name.txt), cmtTbl, md.pmd_name.loc),
+    body,
+  })
+}
+
+and printOpenDescription = (openDescription: Parsetree.open_description, cmtTbl) =>
+  Doc.concat(list{
+    printAttributes(openDescription.popen_attributes, cmtTbl),
+    Doc.text("open"),
+    switch openDescription.popen_override {
+    | Asttypes.Fresh => Doc.space
+    | Asttypes.Override => Doc.text("! ")
+    },
+    printLongidentLocation(openDescription.popen_lid, cmtTbl),
+  })
+
+and printIncludeDescription = (includeDescription: Parsetree.include_description, cmtTbl) =>
+  Doc.concat(list{
+    printAttributes(includeDescription.pincl_attributes, cmtTbl),
+    Doc.text("include "),
+    printModType(includeDescription.pincl_mod, cmtTbl),
+  })
+
+and printIncludeDeclaration = (includeDeclaration: Parsetree.include_declaration, cmtTbl) =>
+  Doc.concat(list{
+    printAttributes(includeDeclaration.pincl_attributes, cmtTbl),
+    Doc.text("include "),
+    {
+      let includeDoc = printModExpr(includeDeclaration.pincl_mod, cmtTbl)
+
+      if Parens.includeModExpr(includeDeclaration.pincl_mod) {
+        addParens(includeDoc)
+      } else {
+        includeDoc
+      }
+    },
+  })
+
+and printValueBindings = (~recFlag, vbs: list<Parsetree.value_binding>, cmtTbl) =>
+  printListi(
+    ~getLoc=vb => vb.Parsetree.pvb_loc,
+    ~nodes=vbs,
+    ~print=printValueBinding(~recFlag),
+    cmtTbl,
+  )
+
+and printValueDescription = (valueDescription, cmtTbl) => {
+  let isExternal = switch valueDescription.pval_prim {
+  | list{} => false
+  | _ => true
+  }
+
+  let attrs = printAttributes(
+    ~loc=valueDescription.pval_name.loc,
+    valueDescription.pval_attributes,
+    cmtTbl,
+  )
+
+  let header = if isExternal {
+    "external "
+  } else {
+    "let "
+  }
+  Doc.group(
+    Doc.concat(list{
+      attrs,
+      Doc.text(header),
+      printComments(
+        printIdentLike(valueDescription.pval_name.txt),
+        cmtTbl,
+        valueDescription.pval_name.loc,
+      ),
+      Doc.text(": "),
+      printTypExpr(valueDescription.pval_type, cmtTbl),
+      if isExternal {
+        Doc.group(
+          Doc.concat(list{
+            Doc.text(" ="),
+            Doc.indent(
+              Doc.concat(list{
+                Doc.line,
+                Doc.join(
+                  ~sep=Doc.line,
+                  List.map(
+                    s => Doc.concat(list{Doc.text("\""), Doc.text(s), Doc.text("\"")}),
+                    valueDescription.pval_prim,
+                  ),
+                ),
+              }),
+            ),
+          }),
+        )
+      } else {
+        Doc.nil
+      },
+    }),
+  )
+}
+
+and printTypeDeclarations = (~recFlag, typeDeclarations, cmtTbl) =>
+  printListi(
+    ~getLoc=n => n.Parsetree.ptype_loc,
+    ~nodes=typeDeclarations,
+    ~print=printTypeDeclaration2(~recFlag),
+    cmtTbl,
+  )
+
+/*
+ * type_declaration = {
+ *    ptype_name: string loc;
+ *    ptype_params: (core_type * variance) list;
+ *          (* ('a1,...'an) t; None represents  _*)
+ *    ptype_cstrs: (core_type * core_type * Location.t) list;
+ *          (* ... constraint T1=T1'  ... constraint Tn=Tn' *)
+ *    ptype_kind: type_kind;
+ *    ptype_private: private_flag;   (* = private ... *)
+ *    ptype_manifest: core_type option;  (* = T *)
+ *    ptype_attributes: attributes;   (* ... [@@id1] [@@id2] *)
+ *    ptype_loc: Location.t;
+ * }
+ *
+ *
+ *  type t                     (abstract, no manifest)
+ *  type t = T0                (abstract, manifest=T0)
+ *  type t = C of T | ...      (variant,  no manifest)
+ *  type t = T0 = C of T | ... (variant,  manifest=T0)
+ *  type t = {l: T; ...}       (record,   no manifest)
+ *  type t = T0 = {l : T; ...} (record,   manifest=T0)
+ *  type t = ..                (open,     no manifest)
+ *
+ *
+ * and type_kind =
+ *  | Ptype_abstract
+ *  | Ptype_variant of constructor_declaration list
+ *        (* Invariant: non-empty list *)
+ *  | Ptype_record of label_declaration list
+ *        (* Invariant: non-empty list *)
+ *  | Ptype_open
+ */
+and printTypeDeclaration = (
+  ~name,
+  ~equalSign,
+  ~recFlag,
+  i,
+  td: Parsetree.type_declaration,
+  cmtTbl,
+) => {
+  let attrs = printAttributes(~loc=td.ptype_loc, td.ptype_attributes, cmtTbl)
+  let prefix = if i > 0 {
+    Doc.text("and ")
+  } else {
+    Doc.concat(list{Doc.text("type "), recFlag})
+  }
+
+  let typeName = name
+  let typeParams = printTypeParams(td.ptype_params, cmtTbl)
+  let manifestAndKind = switch td.ptype_kind {
+  | Ptype_abstract =>
+    switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printPrivateFlag(td.ptype_private),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+  | Ptype_open =>
+    Doc.concat(list{
+      Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+      printPrivateFlag(td.ptype_private),
+      Doc.text(".."),
+    })
+  | Ptype_record(lds) =>
+    let manifest = switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+
+    Doc.concat(list{
+      manifest,
+      Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+      printPrivateFlag(td.ptype_private),
+      printRecordDeclaration(lds, cmtTbl),
+    })
+  | Ptype_variant(cds) =>
+    let manifest = switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+
+    Doc.concat(list{
+      manifest,
+      Doc.concat(list{Doc.space, Doc.text(equalSign)}),
+      printConstructorDeclarations(~privateFlag=td.ptype_private, cds, cmtTbl),
+    })
+  }
+
+  let constraints = printTypeDefinitionConstraints(td.ptype_cstrs)
+  Doc.group(Doc.concat(list{attrs, prefix, typeName, typeParams, manifestAndKind, constraints}))
+}
+
+and printTypeDeclaration2 = (~recFlag, td: Parsetree.type_declaration, cmtTbl, i) => {
+  let name = {
+    let doc = printIdentLike(td.Parsetree.ptype_name.txt)
+    printComments(doc, cmtTbl, td.ptype_name.loc)
+  }
+
+  let equalSign = "="
+  let attrs = printAttributes(~loc=td.ptype_loc, td.ptype_attributes, cmtTbl)
+  let prefix = if i > 0 {
+    Doc.text("and ")
+  } else {
+    Doc.concat(list{Doc.text("type "), recFlag})
+  }
+
+  let typeName = name
+  let typeParams = printTypeParams(td.ptype_params, cmtTbl)
+  let manifestAndKind = switch td.ptype_kind {
+  | Ptype_abstract =>
+    switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printPrivateFlag(td.ptype_private),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+  | Ptype_open =>
+    Doc.concat(list{
+      Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+      printPrivateFlag(td.ptype_private),
+      Doc.text(".."),
+    })
+  | Ptype_record(lds) =>
+    let manifest = switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+
+    Doc.concat(list{
+      manifest,
+      Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+      printPrivateFlag(td.ptype_private),
+      printRecordDeclaration(lds, cmtTbl),
+    })
+  | Ptype_variant(cds) =>
+    let manifest = switch td.ptype_manifest {
+    | None => Doc.nil
+    | Some(typ) =>
+      Doc.concat(list{
+        Doc.concat(list{Doc.space, Doc.text(equalSign), Doc.space}),
+        printTypExpr(typ, cmtTbl),
+      })
+    }
+
+    Doc.concat(list{
+      manifest,
+      Doc.concat(list{Doc.space, Doc.text(equalSign)}),
+      printConstructorDeclarations(~privateFlag=td.ptype_private, cds, cmtTbl),
+    })
+  }
+
+  let constraints = printTypeDefinitionConstraints(td.ptype_cstrs)
+  Doc.group(Doc.concat(list{attrs, prefix, typeName, typeParams, manifestAndKind, constraints}))
+}
+
+and printTypeDefinitionConstraints = cstrs =>
+  switch cstrs {
+  | list{} => Doc.nil
+  | cstrs =>
+    Doc.indent(
+      Doc.group(
+        Doc.concat(list{
+          Doc.line,
+          Doc.group(Doc.join(~sep=Doc.line, List.map(printTypeDefinitionConstraint, cstrs))),
+        }),
+      ),
+    )
+  }
+
+and printTypeDefinitionConstraint = (
+  (typ1, typ2, _loc): (Parsetree.core_type, Parsetree.core_type, Location.t),
+) =>
+  Doc.concat(list{
+    Doc.text("constraint "),
+    printTypExpr(typ1, CommentTable.empty),
+    Doc.text(" = "),
+    printTypExpr(typ2, CommentTable.empty),
+  })
+
+and printPrivateFlag = (flag: Asttypes.private_flag) =>
+  switch flag {
+  | Private => Doc.text("private ")
+  | Public => Doc.nil
+  }
+
+and printTypeParams = (typeParams, cmtTbl) =>
+  switch typeParams {
+  | list{} => Doc.nil
+  | typeParams =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lessThan,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.comma, Doc.line}), List.map(typeParam => {
+                let doc = printTypeParam(typeParam, cmtTbl)
+                printComments(doc, cmtTbl, fst(typeParam).Parsetree.ptyp_loc)
+              }, typeParams)),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.greaterThan,
+      }),
+    )
+  }
+
+and printTypeParam = (param: (Parsetree.core_type, Asttypes.variance), cmtTbl) => {
+  let (typ, variance) = param
+  let printedVariance = switch variance {
+  | Covariant => Doc.text("+")
+  | Contravariant => Doc.text("-")
+  | Invariant => Doc.nil
+  }
+
+  Doc.concat(list{printedVariance, printTypExpr(typ, cmtTbl)})
+}
+
+and printRecordDeclaration = (lds: list<Parsetree.label_declaration>, cmtTbl) => {
+  let forceBreak = switch (lds, List.rev(lds)) {
+  | (list{first, ..._}, list{last, ..._}) =>
+    first.pld_loc.loc_start.pos_lnum < last.pld_loc.loc_end.pos_lnum
+  | _ => false
+  }
+
+  Doc.breakableGroup(
+    ~forceBreak,
+    Doc.concat(list{
+      Doc.lbrace,
+      Doc.indent(
+        Doc.concat(list{
+          Doc.softLine,
+          Doc.join(~sep=Doc.concat(list{Doc.comma, Doc.line}), List.map(ld => {
+              let doc = printLabelDeclaration(ld, cmtTbl)
+              printComments(doc, cmtTbl, ld.Parsetree.pld_loc)
+            }, lds)),
+        }),
+      ),
+      Doc.trailingComma,
+      Doc.softLine,
+      Doc.rbrace,
+    }),
+  )
+}
+
+and printConstructorDeclarations = (
+  ~privateFlag,
+  cds: list<Parsetree.constructor_declaration>,
+  cmtTbl,
+) => {
+  let forceBreak = switch (cds, List.rev(cds)) {
+  | (list{first, ..._}, list{last, ..._}) =>
+    first.pcd_loc.loc_start.pos_lnum < last.pcd_loc.loc_end.pos_lnum
+  | _ => false
+  }
+
+  let privateFlag = switch privateFlag {
+  | Asttypes.Private => Doc.concat(list{Doc.text("private"), Doc.line})
+  | Public => Doc.nil
+  }
+
+  let rows = printListi(
+    ~getLoc=cd => cd.Parsetree.pcd_loc,
+    ~nodes=cds,
+    ~print=(cd, cmtTbl, i) => {
+      let doc = printConstructorDeclaration2(i, cd, cmtTbl)
+      printComments(doc, cmtTbl, cd.Parsetree.pcd_loc)
+    },
+    ~forceBreak,
+    cmtTbl,
+  )
+
+  Doc.breakableGroup(~forceBreak, Doc.indent(Doc.concat(list{Doc.line, privateFlag, rows})))
+}
+
+and printConstructorDeclaration2 = (i, cd: Parsetree.constructor_declaration, cmtTbl) => {
+  let attrs = printAttributes(cd.pcd_attributes, cmtTbl)
+  let bar = if i > 0 || cd.pcd_attributes != list{} {
+    Doc.text("| ")
+  } else {
+    Doc.ifBreaks(Doc.text("| "), Doc.nil)
+  }
+
+  let constrName = {
+    let doc = Doc.text(cd.pcd_name.txt)
+    printComments(doc, cmtTbl, cd.pcd_name.loc)
+  }
+
+  let constrArgs = printConstructorArguments(~indent=true, cd.pcd_args, cmtTbl)
+  let gadt = switch cd.pcd_res {
+  | None => Doc.nil
+  | Some(typ) => Doc.indent(Doc.concat(list{Doc.text(": "), printTypExpr(typ, cmtTbl)}))
+  }
+
+  Doc.concat(list{
+    bar,
+    Doc.group(
+      Doc.concat(list{
+        attrs /* TODO: fix parsing of attributes, so when can print them above the bar? */,
+        constrName,
+        constrArgs,
+        gadt,
+      }),
+    ),
+  })
+}
+
+and printConstructorArguments = (~indent, cdArgs: Parsetree.constructor_arguments, cmtTbl) =>
+  switch cdArgs {
+  | Pcstr_tuple(list{}) => Doc.nil
+  | Pcstr_tuple(types) =>
+    let args = Doc.concat(list{
+      Doc.lparen,
+      Doc.indent(
+        Doc.concat(list{
+          Doc.softLine,
+          Doc.join(
+            ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+            List.map(typexpr => printTypExpr(typexpr, cmtTbl), types),
+          ),
+        }),
+      ),
+      Doc.trailingComma,
+      Doc.softLine,
+      Doc.rparen,
+    })
+    Doc.group(
+      if indent {
+        Doc.indent(args)
+      } else {
+        args
+      },
+    )
+  | Pcstr_record(lds) =>
+    let args = Doc.concat(list{
+      Doc.lparen,
+      /* manually inline the printRecordDeclaration, gives better layout */
+      Doc.lbrace,
+      Doc.indent(
+        Doc.concat(list{
+          Doc.softLine,
+          Doc.join(~sep=Doc.concat(list{Doc.comma, Doc.line}), List.map(ld => {
+              let doc = printLabelDeclaration(ld, cmtTbl)
+              printComments(doc, cmtTbl, ld.Parsetree.pld_loc)
+            }, lds)),
+        }),
+      ),
+      Doc.trailingComma,
+      Doc.softLine,
+      Doc.rbrace,
+      Doc.rparen,
+    })
+    if indent {
+      Doc.indent(args)
+    } else {
+      args
+    }
+  }
+
+and printLabelDeclaration = (ld: Parsetree.label_declaration, cmtTbl) => {
+  let attrs = printAttributes(~loc=ld.pld_name.loc, ld.pld_attributes, cmtTbl)
+  let mutableFlag = switch ld.pld_mutable {
+  | Mutable => Doc.text("mutable ")
+  | Immutable => Doc.nil
+  }
+
+  let name = {
+    let doc = printIdentLike(ld.pld_name.txt)
+    printComments(doc, cmtTbl, ld.pld_name.loc)
+  }
+
+  Doc.group(
+    Doc.concat(list{attrs, mutableFlag, name, Doc.text(": "), printTypExpr(ld.pld_type, cmtTbl)}),
+  )
+}
+
+and printTypExpr = (typExpr: Parsetree.core_type, cmtTbl) => {
+  let renderedType = switch typExpr.ptyp_desc {
+  | Ptyp_any => Doc.text("_")
+  | Ptyp_var(var) => Doc.concat(list{Doc.text("'"), printIdentLike(~allowUident=true, var)})
+  | Ptyp_extension(extension) => printExtension(~atModuleLvl=false, extension, cmtTbl)
+  | Ptyp_alias(typ, alias) =>
+    let typ = {
+      /* Technically type t = (string, float) => unit as 'x, doesn't require
+       * parens around the arrow expression. This is very confusing though.
+       * Is the "as" part of "unit" or "(string, float) => unit". By printing
+       * parens we guide the user towards its meaning. */
+      let needsParens = switch typ.ptyp_desc {
+      | Ptyp_arrow(_) => true
+      | _ => false
+      }
+
+      let doc = printTypExpr(typ, cmtTbl)
+      if needsParens {
+        Doc.concat(list{Doc.lparen, doc, Doc.rparen})
+      } else {
+        doc
+      }
+    }
+
+    Doc.concat(list{typ, Doc.text(" as "), Doc.concat(list{Doc.text("'"), printIdentLike(alias)})})
+
+  /* object printings */
+  | Ptyp_object(fields, openFlag) => printObject(~inline=false, fields, openFlag, cmtTbl)
+  | Ptyp_constr(longidentLoc, list{{ptyp_desc: Ptyp_object(fields, openFlag)}}) =>
+    /* for foo<{"a": b}>, when the object is long and needs a line break, we
+     want the <{ and }> to stay hugged together */
+    let constrName = printLidentPath(longidentLoc, cmtTbl)
+    Doc.concat(list{
+      constrName,
+      Doc.lessThan,
+      printObject(~inline=true, fields, openFlag, cmtTbl),
+      Doc.greaterThan,
+    })
+
+  | Ptyp_constr(longidentLoc, list{{ptyp_desc: Parsetree.Ptyp_tuple(tuple)}}) =>
+    let constrName = printLidentPath(longidentLoc, cmtTbl)
+    Doc.group(
+      Doc.concat(list{
+        constrName,
+        Doc.lessThan,
+        printTupleType(~inline=true, tuple, cmtTbl),
+        Doc.greaterThan,
+      }),
+    )
+  | Ptyp_constr(longidentLoc, constrArgs) =>
+    let constrName = printLidentPath(longidentLoc, cmtTbl)
+    switch constrArgs {
+    | list{} => constrName
+    | _args =>
+      Doc.group(
+        Doc.concat(list{
+          constrName,
+          Doc.lessThan,
+          Doc.indent(
+            Doc.concat(list{
+              Doc.softLine,
+              Doc.join(
+                ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+                List.map(typexpr => printTypExpr(typexpr, cmtTbl), constrArgs),
+              ),
+            }),
+          ),
+          Doc.trailingComma,
+          Doc.softLine,
+          Doc.greaterThan,
+        }),
+      )
+    }
+  | Ptyp_arrow(_) =>
+    let (attrsBefore, args, returnType) = ParsetreeViewer.arrowType(typExpr)
+    let returnTypeNeedsParens = switch returnType.ptyp_desc {
+    | Ptyp_alias(_) => true
+    | _ => false
+    }
+
+    let returnDoc = {
+      let doc = printTypExpr(returnType, cmtTbl)
+      if returnTypeNeedsParens {
+        Doc.concat(list{Doc.lparen, doc, Doc.rparen})
+      } else {
+        doc
+      }
+    }
+
+    let (isUncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(attrsBefore)
+
+    switch args {
+    | list{} => Doc.nil
+    | list{(list{}, Nolabel, n)} if !isUncurried =>
+      let hasAttrsBefore = !(attrs == list{})
+      let attrs = if hasAttrsBefore {
+        printAttributes(~inline=true, attrsBefore, cmtTbl)
+      } else {
+        Doc.nil
+      }
+
+      let typDoc = {
+        let doc = printTypExpr(n, cmtTbl)
+        switch n.ptyp_desc {
+        | Ptyp_arrow(_) | Ptyp_tuple(_) | Ptyp_alias(_) => addParens(doc)
+        | _ => doc
+        }
+      }
+
+      Doc.group(
+        Doc.concat(list{
+          Doc.group(attrs),
+          Doc.group(
+            if hasAttrsBefore {
+              Doc.concat(list{
+                Doc.lparen,
+                Doc.indent(Doc.concat(list{Doc.softLine, typDoc, Doc.text(" => "), returnDoc})),
+                Doc.softLine,
+                Doc.rparen,
+              })
+            } else {
+              Doc.concat(list{typDoc, Doc.text(" => "), returnDoc})
+            },
+          ),
+        }),
+      )
+    | args =>
+      let attrs = printAttributes(~inline=true, attrs, cmtTbl)
+      let renderedArgs = Doc.concat(list{
+        attrs,
+        Doc.text("("),
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            if isUncurried {
+              Doc.concat(list{Doc.dot, Doc.space})
+            } else {
+              Doc.nil
+            },
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(tp => printTypeParameter(tp, cmtTbl), args),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.text(")"),
+      })
+      Doc.group(Doc.concat(list{renderedArgs, Doc.text(" => "), returnDoc}))
+    }
+  | Ptyp_tuple(types) => printTupleType(~inline=false, types, cmtTbl)
+  | Ptyp_poly(list{}, typ) => printTypExpr(typ, cmtTbl)
+  | Ptyp_poly(stringLocs, typ) =>
+    Doc.concat(list{Doc.join(~sep=Doc.space, List.map(({Location.txt: txt, loc}) => {
+          let doc = Doc.concat(list{Doc.text("'"), Doc.text(txt)})
+          printComments(doc, cmtTbl, loc)
+        }, stringLocs)), Doc.dot, Doc.space, printTypExpr(typ, cmtTbl)})
+  | Ptyp_package(packageType) =>
+    printPackageType(~printModuleKeywordAndParens=true, packageType, cmtTbl)
+  | Ptyp_class(_) => Doc.text("classes are not supported in types")
+  | Ptyp_variant(rowFields, closedFlag, labelsOpt) =>
+    let forceBreak =
+      typExpr.ptyp_loc.Location.loc_start.pos_lnum < typExpr.ptyp_loc.loc_end.pos_lnum
+    let printRowField = x =>
+      switch x {
+      | Parsetree.Rtag({txt}, attrs, true, list{}) =>
+        Doc.group(
+          Doc.concat(list{
+            printAttributes(attrs, cmtTbl),
+            Doc.concat(list{Doc.text("#"), printPolyVarIdent(txt)}),
+          }),
+        )
+      | Rtag({txt}, attrs, truth, types) =>
+        let doType = t =>
+          switch t.Parsetree.ptyp_desc {
+          | Ptyp_tuple(_) => printTypExpr(t, cmtTbl)
+          | _ => Doc.concat(list{Doc.lparen, printTypExpr(t, cmtTbl), Doc.rparen})
+          }
+
+        let printedTypes = List.map(doType, types)
+        let cases = Doc.join(~sep=Doc.concat(list{Doc.line, Doc.text("& ")}), printedTypes)
+        let cases = if truth {
+          Doc.concat(list{Doc.line, Doc.text("& "), cases})
+        } else {
+          cases
+        }
+        Doc.group(
+          Doc.concat(list{
+            printAttributes(attrs, cmtTbl),
+            Doc.concat(list{Doc.text("#"), printPolyVarIdent(txt)}),
+            cases,
+          }),
+        )
+      | Rinherit(coreType) => printTypExpr(coreType, cmtTbl)
+      }
+
+    let docs = List.map(printRowField, rowFields)
+    let cases = Doc.join(~sep=Doc.concat(list{Doc.line, Doc.text("| ")}), docs)
+    let cases = if docs == list{} {
+      cases
+    } else {
+      Doc.concat(list{Doc.ifBreaks(Doc.text("| "), Doc.nil), cases})
+    }
+
+    let openingSymbol = if closedFlag == Open {
+      Doc.concat(list{Doc.greaterThan, Doc.line})
+    } else if labelsOpt == None {
+      Doc.softLine
+    } else {
+      Doc.concat(list{Doc.lessThan, Doc.line})
+    }
+    let labels = switch labelsOpt {
+    | None
+    | Some(list{}) => Doc.nil
+    | Some(labels) =>
+      Doc.concat(
+        List.map(
+          label => Doc.concat(list{Doc.line, Doc.text("#"), printPolyVarIdent(label)}),
+          labels,
+        ),
+      )
+    }
+
+    let closingSymbol = switch labelsOpt {
+    | None | Some(list{}) => Doc.nil
+    | _ => Doc.text(" >")
+    }
+
+    Doc.breakableGroup(
+      ~forceBreak,
+      Doc.concat(list{
+        Doc.lbracket,
+        Doc.indent(Doc.concat(list{openingSymbol, cases, closingSymbol, labels})),
+        Doc.softLine,
+        Doc.rbracket,
+      }),
+    )
+  }
+
+  let shouldPrintItsOwnAttributes = switch typExpr.ptyp_desc {
+  | Ptyp_arrow(_) /* es6 arrow types print their own attributes */ => true
+  | _ => false
+  }
+
+  let doc = switch typExpr.ptyp_attributes {
+  | list{_, ..._} as attrs if !shouldPrintItsOwnAttributes =>
+    Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), renderedType}))
+  | _ => renderedType
+  }
+
+  printComments(doc, cmtTbl, typExpr.ptyp_loc)
+}
+
+and printObject = (~inline, fields, openFlag, cmtTbl) => {
+  let doc = switch fields {
+  | list{} =>
+    Doc.concat(list{
+      Doc.lbrace,
+      switch openFlag {
+      | Asttypes.Closed => Doc.dot
+      | Open => Doc.dotdot
+      },
+      Doc.rbrace,
+    })
+  | fields =>
+    Doc.concat(list{
+      Doc.lbrace,
+      switch openFlag {
+      | Asttypes.Closed => Doc.nil
+      | Open =>
+        switch fields {
+        /* handle `type t = {.. ...objType, "x": int}`
+         * .. and ... should have a space in between */
+        | list{Oinherit(_), ..._} => Doc.text(".. ")
+        | _ => Doc.dotdot
+        }
+      },
+      Doc.indent(
+        Doc.concat(list{
+          Doc.softLine,
+          Doc.join(
+            ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+            List.map(field => printObjectField(field, cmtTbl), fields),
+          ),
+        }),
+      ),
+      Doc.trailingComma,
+      Doc.softLine,
+      Doc.rbrace,
+    })
+  }
+
+  if inline {
+    doc
+  } else {
+    Doc.group(doc)
+  }
+}
+
+and printTupleType = (~inline, types: list<Parsetree.core_type>, cmtTbl) => {
+  let tuple = Doc.concat(list{
+    Doc.lparen,
+    Doc.indent(
+      Doc.concat(list{
+        Doc.softLine,
+        Doc.join(
+          ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+          List.map(typexpr => printTypExpr(typexpr, cmtTbl), types),
+        ),
+      }),
+    ),
+    Doc.trailingComma,
+    Doc.softLine,
+    Doc.rparen,
+  })
+
+  if inline === false {
+    Doc.group(tuple)
+  } else {
+    tuple
+  }
+}
+
+and printObjectField = (field: Parsetree.object_field, cmtTbl) =>
+  switch field {
+  | Otag(labelLoc, attrs, typ) =>
+    let lbl = {
+      let doc = Doc.text("\"" ++ (labelLoc.txt ++ "\""))
+      printComments(doc, cmtTbl, labelLoc.loc)
+    }
+
+    let doc = Doc.concat(list{
+      printAttributes(~loc=labelLoc.loc, attrs, cmtTbl),
+      lbl,
+      Doc.text(": "),
+      printTypExpr(typ, cmtTbl),
+    })
+    let cmtLoc = {...labelLoc.loc, loc_end: typ.ptyp_loc.loc_end}
+    printComments(doc, cmtTbl, cmtLoc)
+  | Oinherit(typexpr) => Doc.concat(list{Doc.dotdotdot, printTypExpr(typexpr, cmtTbl)})
+  }
+
+/* es6 arrow type arg
+ * type t = (~foo: string, ~bar: float=?, unit) => unit
+ * i.e. ~foo: string, ~bar: float */
+and printTypeParameter = ((attrs, lbl, typ), cmtTbl) => {
+  let (isUncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(attrs)
+  let uncurried = if isUncurried {
+    Doc.concat(list{Doc.dot, Doc.space})
+  } else {
+    Doc.nil
+  }
+  let attrs = printAttributes(attrs, cmtTbl)
+  let label = switch lbl {
+  | Asttypes.Nolabel => Doc.nil
+  | Labelled(lbl) => Doc.concat(list{Doc.text("~"), printIdentLike(lbl), Doc.text(": ")})
+  | Optional(lbl) => Doc.concat(list{Doc.text("~"), printIdentLike(lbl), Doc.text(": ")})
+  }
+
+  let optionalIndicator = switch lbl {
+  | Asttypes.Nolabel
+  | Labelled(_) => Doc.nil
+  | Optional(_lbl) => Doc.text("=?")
+  }
+
+  let (loc, typ) = switch typ.ptyp_attributes {
+  | list{({Location.txt: "ns.namedArgLoc", loc}, _), ...attrs} => (
+      {...loc, loc_end: typ.ptyp_loc.loc_end},
+      {...typ, ptyp_attributes: attrs},
+    )
+  | _ => (typ.ptyp_loc, typ)
+  }
+
+  let doc = Doc.group(
+    Doc.concat(list{uncurried, attrs, label, printTypExpr(typ, cmtTbl), optionalIndicator}),
+  )
+  printComments(doc, cmtTbl, loc)
+}
+
+and printValueBinding = (~recFlag, vb, cmtTbl, i) => {
+  let attrs = printAttributes(~loc=vb.pvb_pat.ppat_loc, vb.pvb_attributes, cmtTbl)
+  let header = if i === 0 {
+    Doc.concat(list{Doc.text("let "), recFlag})
+  } else {
+    Doc.text("and ")
+  }
+
+  switch vb {
+  | {
+      pvb_pat: {ppat_desc: Ppat_constraint(pattern, {ptyp_desc: Ptyp_poly(_)} as patTyp)},
+      pvb_expr: {pexp_desc: Pexp_newtype(_)} as expr,
+    } =>
+    let (_attrs, parameters, returnExpr) = ParsetreeViewer.funExpr(expr)
+    let abstractType = switch parameters {
+    | list{NewTypes({locs: vars})} =>
+      Doc.concat(list{
+        Doc.text("type "),
+        Doc.join(~sep=Doc.space, List.map(var => Doc.text(var.Asttypes.txt), vars)),
+        Doc.dot,
+      })
+    | _ => Doc.nil
+    }
+
+    switch returnExpr.pexp_desc {
+    | Pexp_constraint(expr, typ) =>
+      Doc.group(
+        Doc.concat(list{
+          attrs,
+          header,
+          printPattern(pattern, cmtTbl),
+          Doc.text(":"),
+          Doc.indent(
+            Doc.concat(list{
+              Doc.line,
+              abstractType,
+              Doc.space,
+              printTypExpr(typ, cmtTbl),
+              Doc.text(" ="),
+              Doc.concat(list{Doc.line, printExpressionWithComments(expr, cmtTbl)}),
+            }),
+          ),
+        }),
+      )
+    | _ =>
+      /* Example:
+       * let cancel_and_collect_callbacks:
+       *   'a 'u 'c. (list<packed_callbacks>, promise<'a, 'u, 'c>) => list<packed_callbacks> =         *  (type x, callbacks_accumulator, p: promise<_, _, c>)
+       */
+      Doc.group(
+        Doc.concat(list{
+          attrs,
+          header,
+          printPattern(pattern, cmtTbl),
+          Doc.text(":"),
+          Doc.indent(
+            Doc.concat(list{
+              Doc.line,
+              abstractType,
+              Doc.space,
+              printTypExpr(patTyp, cmtTbl),
+              Doc.text(" ="),
+              Doc.concat(list{Doc.line, printExpressionWithComments(expr, cmtTbl)}),
+            }),
+          ),
+        }),
+      )
+    }
+  | _ =>
+    let (optBraces, expr) = ParsetreeViewer.processBracesAttr(vb.pvb_expr)
+    let printedExpr = {
+      let doc = printExpressionWithComments(vb.pvb_expr, cmtTbl)
+      switch Parens.expr(vb.pvb_expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    let patternDoc = printPattern(vb.pvb_pat, cmtTbl)
+    /*
+     * we want to optimize the layout of one pipe:
+     *   let tbl = data->Js.Array2.reduce((map, curr) => {
+     *     ...
+     *   })
+     * important is that we don't do this for multiple pipes:
+     *   let decoratorTags =
+     *     items
+     *     ->Js.Array2.filter(items => {items.category === Decorators})
+     *     ->Belt.Array.map(...)
+     * Multiple pipes chained together lend themselves more towards the last layout.
+     */
+    if ParsetreeViewer.isSinglePipeExpr(vb.pvb_expr) {
+      Doc.customLayout(list{
+        Doc.group(
+          Doc.concat(list{attrs, header, patternDoc, Doc.text(" ="), Doc.space, printedExpr}),
+        ),
+        Doc.group(
+          Doc.concat(list{
+            attrs,
+            header,
+            patternDoc,
+            Doc.text(" ="),
+            Doc.indent(Doc.concat(list{Doc.line, printedExpr})),
+          }),
+        ),
+      })
+    } else {
+      let shouldIndent = switch optBraces {
+      | Some(_) => false
+      | _ =>
+        ParsetreeViewer.isBinaryExpression(expr) ||
+        switch vb.pvb_expr {
+        | {
+            pexp_attributes: list{({Location.txt: "ns.ternary"}, _)},
+            pexp_desc: Pexp_ifthenelse(ifExpr, _, _),
+          } =>
+          ParsetreeViewer.isBinaryExpression(ifExpr) ||
+          ParsetreeViewer.hasAttributes(ifExpr.pexp_attributes)
+        | {pexp_desc: Pexp_newtype(_)} => false
+        | e => ParsetreeViewer.hasAttributes(e.pexp_attributes) || ParsetreeViewer.isArrayAccess(e)
+        }
+      }
+
+      Doc.group(
+        Doc.concat(list{
+          attrs,
+          header,
+          patternDoc,
+          Doc.text(" ="),
+          if shouldIndent {
+            Doc.indent(Doc.concat(list{Doc.line, printedExpr}))
+          } else {
+            Doc.concat(list{Doc.space, printedExpr})
+          },
+        }),
+      )
+    }
+  }
+}
+
+and printPackageType = (
+  ~printModuleKeywordAndParens,
+  packageType: Parsetree.package_type,
+  cmtTbl,
+) => {
+  let doc = switch packageType {
+  | (longidentLoc, list{}) =>
+    Doc.group(Doc.concat(list{printLongidentLocation(longidentLoc, cmtTbl)}))
+  | (longidentLoc, packageConstraints) =>
+    Doc.group(
+      Doc.concat(list{
+        printLongidentLocation(longidentLoc, cmtTbl),
+        printPackageConstraints(packageConstraints, cmtTbl),
+        Doc.softLine,
+      }),
+    )
+  }
+
+  if printModuleKeywordAndParens {
+    Doc.concat(list{Doc.text("module("), doc, Doc.rparen})
+  } else {
+    doc
+  }
+}
+
+and printPackageConstraints = (packageConstraints, cmtTbl) =>
+  Doc.concat(list{
+    Doc.text(" with"),
+    Doc.indent(Doc.concat(list{Doc.line, Doc.join(~sep=Doc.line, List.mapi((i, pc) => {
+            let (longident, typexpr) = pc
+            let cmtLoc = {
+              ...longident.Asttypes.loc,
+              loc_end: typexpr.Parsetree.ptyp_loc.loc_end,
+            }
+            let doc = printPackageConstraint(i, cmtTbl, pc)
+            printComments(doc, cmtTbl, cmtLoc)
+          }, packageConstraints))})),
+  })
+
+and printPackageConstraint = (i, cmtTbl, (longidentLoc, typ)) => {
+  let prefix = if i === 0 {
+    Doc.text("type ")
+  } else {
+    Doc.text("and type ")
+  }
+  Doc.concat(list{
+    prefix,
+    printLongidentLocation(longidentLoc, cmtTbl),
+    Doc.text(" = "),
+    printTypExpr(typ, cmtTbl),
+  })
+}
+
+and printExtension = (~atModuleLvl, (stringLoc, payload), cmtTbl) => {
+  let txt = convertBsExtension(stringLoc.Location.txt)
+  let extName = {
+    let doc = Doc.concat(list{
+      Doc.text("%"),
+      if atModuleLvl {
+        Doc.text("%")
+      } else {
+        Doc.nil
+      },
+      Doc.text(txt),
+    })
+    printComments(doc, cmtTbl, stringLoc.Location.loc)
+  }
+
+  Doc.group(Doc.concat(list{extName, printPayload(payload, cmtTbl)}))
+}
+
+and printPattern = (p: Parsetree.pattern, cmtTbl) => {
+  let patternWithoutAttributes = switch p.ppat_desc {
+  | Ppat_any => Doc.text("_")
+  | Ppat_var(var) => printIdentLike(var.txt)
+  | Ppat_constant(c) =>
+    let templateLiteral = ParsetreeViewer.hasTemplateLiteralAttr(p.ppat_attributes)
+    printConstant(~templateLiteral, c)
+  | Ppat_tuple(patterns) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+              List.map(pat => printPattern(pat, cmtTbl), patterns),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+  | Ppat_array(list{}) =>
+    Doc.concat(list{Doc.lbracket, printCommentsInside(cmtTbl, p.ppat_loc), Doc.rbracket})
+  | Ppat_array(patterns) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("["),
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+              List.map(pat => printPattern(pat, cmtTbl), patterns),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.text("]"),
+      }),
+    )
+  | Ppat_construct({txt: Longident.Lident("()")}, _) =>
+    Doc.concat(list{Doc.lparen, printCommentsInside(cmtTbl, p.ppat_loc), Doc.rparen})
+  | Ppat_construct({txt: Longident.Lident("[]")}, _) =>
+    Doc.concat(list{Doc.text("list{"), printCommentsInside(cmtTbl, p.ppat_loc), Doc.rbrace})
+  | Ppat_construct({txt: Longident.Lident("::")}, _) =>
+    let (patterns, tail) = ParsetreeViewer.collectPatternsFromListConstruct(list{}, p)
+    let shouldHug = switch (patterns, tail) {
+    | (list{pat}, {ppat_desc: Ppat_construct({txt: Longident.Lident("[]")}, _)})
+      if ParsetreeViewer.isHuggablePattern(pat) => true
+    | _ => false
+    }
+
+    let children = Doc.concat(list{
+      if shouldHug {
+        Doc.nil
+      } else {
+        Doc.softLine
+      },
+      Doc.join(
+        ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+        List.map(pat => printPattern(pat, cmtTbl), patterns),
+      ),
+      switch tail.Parsetree.ppat_desc {
+      | Ppat_construct({txt: Longident.Lident("[]")}, _) => Doc.nil
+      | _ =>
+        let doc = Doc.concat(list{Doc.text("..."), printPattern(tail, cmtTbl)})
+        let tail = printComments(doc, cmtTbl, tail.ppat_loc)
+        Doc.concat(list{Doc.text(","), Doc.line, tail})
+      },
+    })
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("list{"),
+        if shouldHug {
+          children
+        } else {
+          Doc.concat(list{Doc.indent(children), Doc.ifBreaks(Doc.text(","), Doc.nil), Doc.softLine})
+        },
+        Doc.rbrace,
+      }),
+    )
+  | Ppat_construct(constrName, constructorArgs) =>
+    let constrName = printLongidentLocation(constrName, cmtTbl)
+    let argsDoc = switch constructorArgs {
+    | None => Doc.nil
+    | Some({ppat_loc, ppat_desc: Ppat_construct({txt: Longident.Lident("()")}, _)}) =>
+      Doc.concat(list{Doc.lparen, printCommentsInside(cmtTbl, ppat_loc), Doc.rparen})
+    | Some({ppat_desc: Ppat_tuple(list{}), ppat_loc: loc}) =>
+      Doc.concat(list{Doc.lparen, Doc.softLine, printCommentsInside(cmtTbl, loc), Doc.rparen})
+    /* Some((1, 2) */
+    | Some({ppat_desc: Ppat_tuple(list{{ppat_desc: Ppat_tuple(_)} as arg})}) =>
+      Doc.concat(list{Doc.lparen, printPattern(arg, cmtTbl), Doc.rparen})
+    | Some({ppat_desc: Ppat_tuple(patterns)}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(pat => printPattern(pat, cmtTbl), patterns),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      })
+    | Some(arg) =>
+      let argDoc = printPattern(arg, cmtTbl)
+      let shouldHug = ParsetreeViewer.isHuggablePattern(arg)
+      Doc.concat(list{
+        Doc.lparen,
+        if shouldHug {
+          argDoc
+        } else {
+          Doc.concat(list{
+            Doc.indent(Doc.concat(list{Doc.softLine, argDoc})),
+            Doc.trailingComma,
+            Doc.softLine,
+          })
+        },
+        Doc.rparen,
+      })
+    }
+
+    Doc.group(Doc.concat(list{constrName, argsDoc}))
+  | Ppat_variant(label, None) => Doc.concat(list{Doc.text("#"), printPolyVarIdent(label)})
+  | Ppat_variant(label, variantArgs) =>
+    let variantName = Doc.concat(list{Doc.text("#"), printPolyVarIdent(label)})
+    let argsDoc = switch variantArgs {
+    | None => Doc.nil
+    | Some({ppat_desc: Ppat_construct({txt: Longident.Lident("()")}, _)}) => Doc.text("()")
+    | Some({ppat_desc: Ppat_tuple(list{}), ppat_loc: loc}) =>
+      Doc.concat(list{Doc.lparen, Doc.softLine, printCommentsInside(cmtTbl, loc), Doc.rparen})
+    /* Some((1, 2) */
+    | Some({ppat_desc: Ppat_tuple(list{{ppat_desc: Ppat_tuple(_)} as arg})}) =>
+      Doc.concat(list{Doc.lparen, printPattern(arg, cmtTbl), Doc.rparen})
+    | Some({ppat_desc: Ppat_tuple(patterns)}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(pat => printPattern(pat, cmtTbl), patterns),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      })
+    | Some(arg) =>
+      let argDoc = printPattern(arg, cmtTbl)
+      let shouldHug = ParsetreeViewer.isHuggablePattern(arg)
+      Doc.concat(list{
+        Doc.lparen,
+        if shouldHug {
+          argDoc
+        } else {
+          Doc.concat(list{
+            Doc.indent(Doc.concat(list{Doc.softLine, argDoc})),
+            Doc.trailingComma,
+            Doc.softLine,
+          })
+        },
+        Doc.rparen,
+      })
+    }
+
+    Doc.group(Doc.concat(list{variantName, argsDoc}))
+  | Ppat_type(ident) => Doc.concat(list{Doc.text("#..."), printIdentPath(ident, cmtTbl)})
+  | Ppat_record(rows, openFlag) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+              List.map(row => printPatternRecordRow(row, cmtTbl), rows),
+            ),
+            switch openFlag {
+            | Open => Doc.concat(list{Doc.text(","), Doc.line, Doc.text("_")})
+            | Closed => Doc.nil
+            },
+          }),
+        ),
+        Doc.ifBreaks(Doc.text(","), Doc.nil),
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+
+  | Ppat_exception(p) =>
+    let needsParens = switch p.ppat_desc {
+    | Ppat_or(_, _) | Ppat_alias(_, _) => true
+    | _ => false
+    }
+
+    let pat = {
+      let p = printPattern(p, cmtTbl)
+      if needsParens {
+        Doc.concat(list{Doc.text("("), p, Doc.text(")")})
+      } else {
+        p
+      }
+    }
+
+    Doc.group(Doc.concat(list{Doc.text("exception"), Doc.line, pat}))
+  | Ppat_or(_) =>
+    /* Blue | Red | Green -> [Blue; Red; Green] */
+    let orChain = ParsetreeViewer.collectOrPatternChain(p)
+    let docs = List.mapi((i, pat) => {
+      let patternDoc = printPattern(pat, cmtTbl)
+      Doc.concat(list{
+        if i === 0 {
+          Doc.nil
+        } else {
+          Doc.concat(list{Doc.line, Doc.text("| ")})
+        },
+        switch pat.ppat_desc {
+        /* (Blue | Red) | (Green | Black) | White */
+        | Ppat_or(_) => addParens(patternDoc)
+        | _ => patternDoc
+        },
+      })
+    }, orChain)
+    let isSpreadOverMultipleLines = switch (orChain, List.rev(orChain)) {
+    | (list{first, ..._}, list{last, ..._}) =>
+      first.ppat_loc.loc_start.pos_lnum < last.ppat_loc.loc_end.pos_lnum
+    | _ => false
+    }
+
+    Doc.breakableGroup(~forceBreak=isSpreadOverMultipleLines, Doc.concat(docs))
+  | Ppat_extension(ext) => printExtension(~atModuleLvl=false, ext, cmtTbl)
+  | Ppat_lazy(p) =>
+    let needsParens = switch p.ppat_desc {
+    | Ppat_or(_, _) | Ppat_alias(_, _) => true
+    | _ => false
+    }
+
+    let pat = {
+      let p = printPattern(p, cmtTbl)
+      if needsParens {
+        Doc.concat(list{Doc.text("("), p, Doc.text(")")})
+      } else {
+        p
+      }
+    }
+
+    Doc.concat(list{Doc.text("lazy "), pat})
+  | Ppat_alias(p, aliasLoc) =>
+    let needsParens = switch p.ppat_desc {
+    | Ppat_or(_, _) | Ppat_alias(_, _) => true
+    | _ => false
+    }
+
+    let renderedPattern = {
+      let p = printPattern(p, cmtTbl)
+      if needsParens {
+        Doc.concat(list{Doc.text("("), p, Doc.text(")")})
+      } else {
+        p
+      }
+    }
+
+    Doc.concat(list{renderedPattern, Doc.text(" as "), printStringLoc(aliasLoc, cmtTbl)})
+
+  /* Note: module(P : S) is represented as */
+  /* Ppat_constraint(Ppat_unpack, Ptyp_package) */
+  | Ppat_constraint(
+      {ppat_desc: Ppat_unpack(stringLoc)},
+      {ptyp_desc: Ptyp_package(packageType), ptyp_loc},
+    ) =>
+    Doc.concat(list{
+      Doc.text("module("),
+      printComments(Doc.text(stringLoc.txt), cmtTbl, stringLoc.loc),
+      Doc.text(": "),
+      printComments(
+        printPackageType(~printModuleKeywordAndParens=false, packageType, cmtTbl),
+        cmtTbl,
+        ptyp_loc,
+      ),
+      Doc.rparen,
+    })
+  | Ppat_constraint(pattern, typ) =>
+    Doc.concat(list{printPattern(pattern, cmtTbl), Doc.text(": "), printTypExpr(typ, cmtTbl)})
+
+  /* Note: module(P : S) is represented as */
+  /* Ppat_constraint(Ppat_unpack, Ptyp_package) */
+  | Ppat_unpack(stringLoc) =>
+    Doc.concat(list{
+      Doc.text("module("),
+      printComments(Doc.text(stringLoc.txt), cmtTbl, stringLoc.loc),
+      Doc.rparen,
+    })
+  | Ppat_interval(a, b) => Doc.concat(list{printConstant(a), Doc.text(" .. "), printConstant(b)})
+  | Ppat_open(_) => Doc.nil
+  }
+
+  let doc = switch p.ppat_attributes {
+  | list{} => patternWithoutAttributes
+  | attrs => Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), patternWithoutAttributes}))
+  }
+
+  printComments(doc, cmtTbl, p.ppat_loc)
+}
+
+and printPatternRecordRow = (row, cmtTbl) =>
+  switch row {
+  /* punned {x} */
+  | (
+      {Location.txt: Longident.Lident(ident)} as longident,
+      {Parsetree.ppat_desc: Ppat_var({txt, _})},
+    ) if ident == txt =>
+    printLidentPath(longident, cmtTbl)
+  | (longident, pattern) =>
+    let locForComments = {
+      ...longident.loc,
+      loc_end: pattern.Parsetree.ppat_loc.loc_end,
+    }
+    let rhsDoc = {
+      let doc = printPattern(pattern, cmtTbl)
+      if Parens.patternRecordRowRhs(pattern) {
+        addParens(doc)
+      } else {
+        doc
+      }
+    }
+
+    let doc = Doc.group(
+      Doc.concat(list{
+        printLidentPath(longident, cmtTbl),
+        Doc.text(":"),
+        if ParsetreeViewer.isHuggablePattern(pattern) {
+          Doc.concat(list{Doc.space, rhsDoc})
+        } else {
+          Doc.indent(Doc.concat(list{Doc.line, rhsDoc}))
+        },
+      }),
+    )
+    printComments(doc, cmtTbl, locForComments)
+  }
+
+and printExpressionWithComments = (expr, cmtTbl) => {
+  let doc = printExpression(expr, cmtTbl)
+  printComments(doc, cmtTbl, expr.Parsetree.pexp_loc)
+}
+
+and printIfChain = (pexp_attributes, ifs, elseExpr, cmtTbl) => {
+  let ifDocs = Doc.join(~sep=Doc.space, List.mapi((i, (ifExpr, thenExpr)) => {
+      let ifTxt = if i > 0 {
+        Doc.text("else if ")
+      } else {
+        Doc.text("if ")
+      }
+      switch ifExpr {
+      | ParsetreeViewer.If(ifExpr) =>
+        let condition = if ParsetreeViewer.isBlockExpr(ifExpr) {
+          printExpressionBlock(~braces=true, ifExpr, cmtTbl)
+        } else {
+          let doc = printExpressionWithComments(ifExpr, cmtTbl)
+          switch Parens.expr(ifExpr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, ifExpr, braces)
+          | Nothing => Doc.ifBreaks(addParens(doc), doc)
+          }
+        }
+
+        Doc.concat(list{
+          ifTxt,
+          Doc.group(condition),
+          Doc.space,
+          {
+            let thenExpr = switch ParsetreeViewer.processBracesAttr(thenExpr) {
+            /* This case only happens when coming from Reason, we strip braces */
+            | (Some(_), expr) => expr
+            | _ => thenExpr
+            }
+
+            printExpressionBlock(~braces=true, thenExpr, cmtTbl)
+          },
+        })
+      | IfLet(pattern, conditionExpr) =>
+        let conditionDoc = {
+          let doc = printExpressionWithComments(conditionExpr, cmtTbl)
+          switch Parens.expr(conditionExpr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, conditionExpr, braces)
+          | Nothing => doc
+          }
+        }
+
+        Doc.concat(list{
+          ifTxt,
+          Doc.text("let "),
+          printPattern(pattern, cmtTbl),
+          Doc.text(" = "),
+          conditionDoc,
+          Doc.space,
+          printExpressionBlock(~braces=true, thenExpr, cmtTbl),
+        })
+      }
+    }, ifs))
+  let elseDoc = switch elseExpr {
+  | None => Doc.nil
+  | Some(expr) =>
+    Doc.concat(list{Doc.text(" else "), printExpressionBlock(~braces=true, expr, cmtTbl)})
+  }
+
+  let attrs = ParsetreeViewer.filterFragileMatchAttributes(pexp_attributes)
+  Doc.concat(list{printAttributes(attrs, cmtTbl), ifDocs, elseDoc})
+}
+
+and printExpression = (e: Parsetree.expression, cmtTbl) => {
+  let printedExpression = switch e.pexp_desc {
+  | Parsetree.Pexp_constant(c) =>
+    printConstant(~templateLiteral=ParsetreeViewer.isTemplateLiteral(e), c)
+  | Pexp_construct(_) if ParsetreeViewer.hasJsxAttribute(e.pexp_attributes) =>
+    printJsxFragment(e, cmtTbl)
+  | Pexp_construct({txt: Longident.Lident("()")}, _) => Doc.text("()")
+  | Pexp_construct({txt: Longident.Lident("[]")}, _) =>
+    Doc.concat(list{Doc.text("list{"), printCommentsInside(cmtTbl, e.pexp_loc), Doc.rbrace})
+  | Pexp_construct({txt: Longident.Lident("::")}, _) =>
+    let (expressions, spread) = ParsetreeViewer.collectListExpressions(e)
+    let spreadDoc = switch spread {
+    | Some(expr) =>
+      Doc.concat(list{
+        Doc.text(","),
+        Doc.line,
+        Doc.dotdotdot,
+        {
+          let doc = printExpressionWithComments(expr, cmtTbl)
+          switch Parens.expr(expr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, expr, braces)
+          | Nothing => doc
+          }
+        },
+      })
+    | None => Doc.nil
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("list{"),
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.text(","), Doc.line}), List.map(expr => {
+                let doc = printExpressionWithComments(expr, cmtTbl)
+                switch Parens.expr(expr) {
+                | Parens.Parenthesized => addParens(doc)
+                | Braced(braces) => printBraces(doc, expr, braces)
+                | Nothing => doc
+                }
+              }, expressions)),
+            spreadDoc,
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  | Pexp_construct(longidentLoc, args) =>
+    let constr = printLongidentLocation(longidentLoc, cmtTbl)
+    let args = switch args {
+    | None => Doc.nil
+    | Some({pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, _)}) => Doc.text("()")
+    /* Some((1, 2)) */
+    | Some({pexp_desc: Pexp_tuple(list{{pexp_desc: Pexp_tuple(_)} as arg})}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        {
+          let doc = printExpressionWithComments(arg, cmtTbl)
+          switch Parens.expr(arg) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, arg, braces)
+          | Nothing => doc
+          }
+        },
+        Doc.rparen,
+      })
+    | Some({pexp_desc: Pexp_tuple(args)}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.comma, Doc.line}), List.map(expr => {
+                let doc = printExpressionWithComments(expr, cmtTbl)
+                switch Parens.expr(expr) {
+                | Parens.Parenthesized => addParens(doc)
+                | Braced(braces) => printBraces(doc, expr, braces)
+                | Nothing => doc
+                }
+              }, args)),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      })
+    | Some(arg) =>
+      let argDoc = {
+        let doc = printExpressionWithComments(arg, cmtTbl)
+        switch Parens.expr(arg) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, arg, braces)
+        | Nothing => doc
+        }
+      }
+
+      let shouldHug = ParsetreeViewer.isHuggableExpression(arg)
+      Doc.concat(list{
+        Doc.lparen,
+        if shouldHug {
+          argDoc
+        } else {
+          Doc.concat(list{
+            Doc.indent(Doc.concat(list{Doc.softLine, argDoc})),
+            Doc.trailingComma,
+            Doc.softLine,
+          })
+        },
+        Doc.rparen,
+      })
+    }
+
+    Doc.group(Doc.concat(list{constr, args}))
+  | Pexp_ident(path) => printLidentPath(path, cmtTbl)
+  | Pexp_tuple(exprs) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.text(","), Doc.line}), List.map(expr => {
+                let doc = printExpressionWithComments(expr, cmtTbl)
+                switch Parens.expr(expr) {
+                | Parens.Parenthesized => addParens(doc)
+                | Braced(braces) => printBraces(doc, expr, braces)
+                | Nothing => doc
+                }
+              }, exprs)),
+          }),
+        ),
+        Doc.ifBreaks(Doc.text(","), Doc.nil),
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+  | Pexp_array(list{}) =>
+    Doc.concat(list{Doc.lbracket, printCommentsInside(cmtTbl, e.pexp_loc), Doc.rbracket})
+  | Pexp_array(exprs) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lbracket,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.text(","), Doc.line}), List.map(expr => {
+                let doc = printExpressionWithComments(expr, cmtTbl)
+                switch Parens.expr(expr) {
+                | Parens.Parenthesized => addParens(doc)
+                | Braced(braces) => printBraces(doc, expr, braces)
+                | Nothing => doc
+                }
+              }, exprs)),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rbracket,
+      }),
+    )
+  | Pexp_variant(label, args) =>
+    let variantName = Doc.concat(list{Doc.text("#"), printPolyVarIdent(label)})
+    let args = switch args {
+    | None => Doc.nil
+    | Some({pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, _)}) => Doc.text("()")
+    /* #poly((1, 2) */
+    | Some({pexp_desc: Pexp_tuple(list{{pexp_desc: Pexp_tuple(_)} as arg})}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        {
+          let doc = printExpressionWithComments(arg, cmtTbl)
+          switch Parens.expr(arg) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, arg, braces)
+          | Nothing => doc
+          }
+        },
+        Doc.rparen,
+      })
+    | Some({pexp_desc: Pexp_tuple(args)}) =>
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(~sep=Doc.concat(list{Doc.comma, Doc.line}), List.map(expr => {
+                let doc = printExpressionWithComments(expr, cmtTbl)
+                switch Parens.expr(expr) {
+                | Parens.Parenthesized => addParens(doc)
+                | Braced(braces) => printBraces(doc, expr, braces)
+                | Nothing => doc
+                }
+              }, args)),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      })
+    | Some(arg) =>
+      let argDoc = {
+        let doc = printExpressionWithComments(arg, cmtTbl)
+        switch Parens.expr(arg) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, arg, braces)
+        | Nothing => doc
+        }
+      }
+
+      let shouldHug = ParsetreeViewer.isHuggableExpression(arg)
+      Doc.concat(list{
+        Doc.lparen,
+        if shouldHug {
+          argDoc
+        } else {
+          Doc.concat(list{
+            Doc.indent(Doc.concat(list{Doc.softLine, argDoc})),
+            Doc.trailingComma,
+            Doc.softLine,
+          })
+        },
+        Doc.rparen,
+      })
+    }
+
+    Doc.group(Doc.concat(list{variantName, args}))
+  | Pexp_record(rows, spreadExpr) =>
+    let spread = switch spreadExpr {
+    | None => Doc.nil
+    | Some(expr) =>
+      Doc.concat(list{
+        Doc.dotdotdot,
+        {
+          let doc = printExpressionWithComments(expr, cmtTbl)
+          switch Parens.expr(expr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, expr, braces)
+          | Nothing => doc
+          }
+        },
+        Doc.comma,
+        Doc.line,
+      })
+    }
+
+    /* If the record is written over multiple lines, break automatically
+     * `let x = {a: 1, b: 3}` -> same line, break when line-width exceeded
+     * `let x = {
+     *   a: 1,
+     *   b: 2,
+     *  }` -> record is written on multiple lines, break the group */
+    let forceBreak = e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum
+
+    let punningAllowed = switch (spreadExpr, rows) {
+    | (None, list{_}) => false /* disallow punning for single-element records */
+    | _ => true
+    }
+
+    Doc.breakableGroup(
+      ~forceBreak,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            spread,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+              List.map(row => printRecordRow(row, cmtTbl, punningAllowed), rows),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  | Pexp_extension(extension) =>
+    switch extension {
+    | (
+        {txt: "bs.obj" | "obj"},
+        PStr(list{{
+          pstr_loc: loc,
+          pstr_desc: Pstr_eval({pexp_desc: Pexp_record(rows, _)}, list{}),
+        }}),
+      ) =>
+      /* If the object is written over multiple lines, break automatically
+       * `let x = {"a": 1, "b": 3}` -> same line, break when line-width exceeded
+       * `let x = {
+       *   "a": 1,
+       *   "b": 2,
+       *  }` -> object is written on multiple lines, break the group */
+      let forceBreak = loc.loc_start.pos_lnum < loc.loc_end.pos_lnum
+
+      Doc.breakableGroup(
+        ~forceBreak,
+        Doc.concat(list{
+          Doc.lbrace,
+          Doc.indent(
+            Doc.concat(list{
+              Doc.softLine,
+              Doc.join(
+                ~sep=Doc.concat(list{Doc.text(","), Doc.line}),
+                List.map(row => printBsObjectRow(row, cmtTbl), rows),
+              ),
+            }),
+          ),
+          Doc.trailingComma,
+          Doc.softLine,
+          Doc.rbrace,
+        }),
+      )
+    | extension => printExtension(~atModuleLvl=false, extension, cmtTbl)
+    }
+  | Pexp_apply(_) =>
+    if ParsetreeViewer.isUnaryExpression(e) {
+      printUnaryExpression(e, cmtTbl)
+    } else if ParsetreeViewer.isTemplateLiteral(e) {
+      printTemplateLiteral(e, cmtTbl)
+    } else if ParsetreeViewer.isBinaryExpression(e) {
+      printBinaryExpression(e, cmtTbl)
+    } else {
+      printPexpApply(e, cmtTbl)
+    }
+  | Pexp_unreachable => Doc.dot
+  | Pexp_field(expr, longidentLoc) =>
+    let lhs = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.fieldExpr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{lhs, Doc.dot, printLidentPath(longidentLoc, cmtTbl)})
+  | Pexp_setfield(expr1, longidentLoc, expr2) =>
+    printSetFieldExpr(e.pexp_attributes, expr1, longidentLoc, expr2, e.pexp_loc, cmtTbl)
+  | Pexp_ifthenelse(_ifExpr, _thenExpr, _elseExpr) if ParsetreeViewer.isTernaryExpr(e) =>
+    let (parts, alternate) = ParsetreeViewer.collectTernaryParts(e)
+    let ternaryDoc = switch parts {
+    | list{(condition1, consequent1), ...rest} =>
+      Doc.group(
+        Doc.concat(list{
+          printTernaryOperand(condition1, cmtTbl),
+          Doc.indent(
+            Doc.concat(list{
+              Doc.line,
+              Doc.indent(
+                Doc.concat(list{Doc.text("? "), printTernaryOperand(consequent1, cmtTbl)}),
+              ),
+              Doc.concat(
+                List.map(
+                  ((condition, consequent)) =>
+                    Doc.concat(list{
+                      Doc.line,
+                      Doc.text(": "),
+                      printTernaryOperand(condition, cmtTbl),
+                      Doc.line,
+                      Doc.text("? "),
+                      printTernaryOperand(consequent, cmtTbl),
+                    }),
+                  rest,
+                ),
+              ),
+              Doc.line,
+              Doc.text(": "),
+              Doc.indent(printTernaryOperand(alternate, cmtTbl)),
+            }),
+          ),
+        }),
+      )
+    | _ => Doc.nil
+    }
+
+    let attrs = ParsetreeViewer.filterTernaryAttributes(e.pexp_attributes)
+    let needsParens = switch ParsetreeViewer.filterParsingAttrs(attrs) {
+    | list{} => false
+    | _ => true
+    }
+
+    Doc.concat(list{
+      printAttributes(attrs, cmtTbl),
+      if needsParens {
+        addParens(ternaryDoc)
+      } else {
+        ternaryDoc
+      },
+    })
+  | Pexp_ifthenelse(_ifExpr, _thenExpr, _elseExpr) =>
+    let (ifs, elseExpr) = ParsetreeViewer.collectIfExpressions(e)
+    printIfChain(e.pexp_attributes, ifs, elseExpr, cmtTbl)
+  | Pexp_while(expr1, expr2) =>
+    let condition = {
+      let doc = printExpressionWithComments(expr1, cmtTbl)
+      switch Parens.expr(expr1) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr1, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        Doc.text("while "),
+        if ParsetreeViewer.isBlockExpr(expr1) {
+          condition
+        } else {
+          Doc.group(Doc.ifBreaks(addParens(condition), condition))
+        },
+        Doc.space,
+        printExpressionBlock(~braces=true, expr2, cmtTbl),
+      }),
+    )
+  | Pexp_for(pattern, fromExpr, toExpr, directionFlag, body) =>
+    Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        Doc.text("for "),
+        printPattern(pattern, cmtTbl),
+        Doc.text(" in "),
+        {
+          let doc = printExpressionWithComments(fromExpr, cmtTbl)
+          switch Parens.expr(fromExpr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, fromExpr, braces)
+          | Nothing => doc
+          }
+        },
+        printDirectionFlag(directionFlag),
+        {
+          let doc = printExpressionWithComments(toExpr, cmtTbl)
+          switch Parens.expr(toExpr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, toExpr, braces)
+          | Nothing => doc
+          }
+        },
+        Doc.space,
+        printExpressionBlock(~braces=true, body, cmtTbl),
+      }),
+    )
+  | Pexp_constraint(
+      {pexp_desc: Pexp_pack(modExpr)},
+      {ptyp_desc: Ptyp_package(packageType), ptyp_loc},
+    ) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("module("),
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            printModExpr(modExpr, cmtTbl),
+            Doc.text(": "),
+            printComments(
+              printPackageType(~printModuleKeywordAndParens=false, packageType, cmtTbl),
+              cmtTbl,
+              ptyp_loc,
+            ),
+          }),
+        ),
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+
+  | Pexp_constraint(expr, typ) =>
+    let exprDoc = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.expr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{exprDoc, Doc.text(": "), printTypExpr(typ, cmtTbl)})
+  | Pexp_letmodule({txt: _modName}, _modExpr, _expr) =>
+    printExpressionBlock(~braces=true, e, cmtTbl)
+  | Pexp_letexception(_extensionConstructor, _expr) => printExpressionBlock(~braces=true, e, cmtTbl)
+  | Pexp_assert(expr) =>
+    let rhs = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.lazyOrAssertExprRhs(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{Doc.text("assert "), rhs})
+  | Pexp_lazy(expr) =>
+    let rhs = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.lazyOrAssertExprRhs(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.group(Doc.concat(list{Doc.text("lazy "), rhs}))
+  | Pexp_open(_overrideFlag, _longidentLoc, _expr) => printExpressionBlock(~braces=true, e, cmtTbl)
+  | Pexp_pack(modExpr) =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("module("),
+        Doc.indent(Doc.concat(list{Doc.softLine, printModExpr(modExpr, cmtTbl)})),
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+  | Pexp_sequence(_) => printExpressionBlock(~braces=true, e, cmtTbl)
+  | Pexp_let(_) => printExpressionBlock(~braces=true, e, cmtTbl)
+  | Pexp_fun(Nolabel, None, {ppat_desc: Ppat_var({txt: "__x"})}, {pexp_desc: Pexp_apply(_)}) =>
+    /* (__x) => f(a, __x, c) -----> f(a, _, c) */
+    printExpressionWithComments(ParsetreeViewer.rewriteUnderscoreApply(e), cmtTbl)
+  | Pexp_fun(_) | Pexp_newtype(_) =>
+    let (attrsOnArrow, parameters, returnExpr) = ParsetreeViewer.funExpr(e)
+    let (uncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(attrsOnArrow)
+
+    let (returnExpr, typConstraint) = switch returnExpr.pexp_desc {
+    | Pexp_constraint(expr, typ) => (
+        {
+          ...expr,
+          pexp_attributes: List.concat(list{expr.pexp_attributes, returnExpr.pexp_attributes}),
+        },
+        Some(typ),
+      )
+    | _ => (returnExpr, None)
+    }
+
+    let hasConstraint = switch typConstraint {
+    | Some(_) => true
+    | None => false
+    }
+    let parametersDoc = printExprFunParameters(
+      ~inCallback=NoCallback,
+      ~uncurried,
+      ~hasConstraint,
+      parameters,
+      cmtTbl,
+    )
+
+    let returnExprDoc = {
+      let (optBraces, _) = ParsetreeViewer.processBracesAttr(returnExpr)
+      let shouldInline = switch (returnExpr.pexp_desc, optBraces) {
+      | (_, Some(_)) => true
+      | (
+          Pexp_array(_)
+          | Pexp_tuple(_)
+          | Pexp_construct(_, Some(_))
+          | Pexp_record(_),
+          _,
+        ) => true
+      | _ => false
+      }
+
+      let shouldIndent = switch returnExpr.pexp_desc {
+      | Pexp_sequence(_)
+      | Pexp_let(_)
+      | Pexp_letmodule(_)
+      | Pexp_letexception(_)
+      | Pexp_open(_) => false
+      | _ => true
+      }
+
+      let returnDoc = {
+        let doc = printExpressionWithComments(returnExpr, cmtTbl)
+        switch Parens.expr(returnExpr) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, returnExpr, braces)
+        | Nothing => doc
+        }
+      }
+
+      if shouldInline {
+        Doc.concat(list{Doc.space, returnDoc})
+      } else {
+        Doc.group(
+          if shouldIndent {
+            Doc.indent(Doc.concat(list{Doc.line, returnDoc}))
+          } else {
+            Doc.concat(list{Doc.space, returnDoc})
+          },
+        )
+      }
+    }
+
+    let typConstraintDoc = switch typConstraint {
+    | Some(typ) =>
+      let typDoc = {
+        let doc = printTypExpr(typ, cmtTbl)
+        if Parens.arrowReturnTypExpr(typ) {
+          addParens(doc)
+        } else {
+          doc
+        }
+      }
+
+      Doc.concat(list{Doc.text(": "), typDoc})
+    | _ => Doc.nil
+    }
+
+    let attrs = printAttributes(attrs, cmtTbl)
+    Doc.group(
+      Doc.concat(list{attrs, parametersDoc, typConstraintDoc, Doc.text(" =>"), returnExprDoc}),
+    )
+  | Pexp_try(expr, cases) =>
+    let exprDoc = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.expr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{Doc.text("try "), exprDoc, Doc.text(" catch "), printCases(cases, cmtTbl)})
+  | Pexp_match(_, list{_, _}) if ParsetreeViewer.isIfLetExpr(e) =>
+    let (ifs, elseExpr) = ParsetreeViewer.collectIfExpressions(e)
+    printIfChain(e.pexp_attributes, ifs, elseExpr, cmtTbl)
+  | Pexp_match(expr, cases) =>
+    let exprDoc = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.expr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{Doc.text("switch "), exprDoc, Doc.space, printCases(cases, cmtTbl)})
+  | Pexp_function(cases) => Doc.concat(list{Doc.text("x => switch x "), printCases(cases, cmtTbl)})
+  | Pexp_coerce(expr, typOpt, typ) =>
+    let docExpr = printExpressionWithComments(expr, cmtTbl)
+    let docTyp = printTypExpr(typ, cmtTbl)
+    let ofType = switch typOpt {
+    | None => Doc.nil
+    | Some(typ1) => Doc.concat(list{Doc.text(": "), printTypExpr(typ1, cmtTbl)})
+    }
+
+    Doc.concat(list{Doc.lparen, docExpr, ofType, Doc.text(" :> "), docTyp, Doc.rparen})
+  | Pexp_send(parentExpr, label) =>
+    let parentDoc = {
+      let doc = printExpressionWithComments(parentExpr, cmtTbl)
+      switch Parens.unaryExprOperand(parentExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, parentExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    let member = {
+      let memberDoc = printComments(Doc.text(label.txt), cmtTbl, label.loc)
+      Doc.concat(list{Doc.text("\""), memberDoc, Doc.text("\"")})
+    }
+
+    Doc.group(Doc.concat(list{parentDoc, Doc.lbracket, member, Doc.rbracket}))
+  | Pexp_new(_) => Doc.text("Pexp_new not impemented in printer")
+  | Pexp_setinstvar(_) => Doc.text("Pexp_setinstvar not impemented in printer")
+  | Pexp_override(_) => Doc.text("Pexp_override not impemented in printer")
+  | Pexp_poly(_) => Doc.text("Pexp_poly not impemented in printer")
+  | Pexp_object(_) => Doc.text("Pexp_object not impemented in printer")
+  }
+
+  let shouldPrintItsOwnAttributes = switch e.pexp_desc {
+  | Pexp_apply(_)
+  | Pexp_fun(_)
+  | Pexp_newtype(_)
+  | Pexp_setfield(_)
+  | Pexp_ifthenelse(_) => true
+  | Pexp_match(_) if ParsetreeViewer.isIfLetExpr(e) => true
+  | Pexp_construct(_) if ParsetreeViewer.hasJsxAttribute(e.pexp_attributes) => true
+  | _ => false
+  }
+
+  switch e.pexp_attributes {
+  | list{} => printedExpression
+  | attrs if !shouldPrintItsOwnAttributes =>
+    Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), printedExpression}))
+  | _ => printedExpression
+  }
+}
+
+and printPexpFun = (~inCallback, e, cmtTbl) => {
+  let (attrsOnArrow, parameters, returnExpr) = ParsetreeViewer.funExpr(e)
+  let (uncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(attrsOnArrow)
+
+  let (returnExpr, typConstraint) = switch returnExpr.pexp_desc {
+  | Pexp_constraint(expr, typ) => (
+      {
+        ...expr,
+        pexp_attributes: List.concat(list{expr.pexp_attributes, returnExpr.pexp_attributes}),
+      },
+      Some(typ),
+    )
+  | _ => (returnExpr, None)
+  }
+
+  let parametersDoc = printExprFunParameters(
+    ~inCallback,
+    ~uncurried,
+    ~hasConstraint=switch typConstraint {
+    | Some(_) => true
+    | None => false
+    },
+    parameters,
+    cmtTbl,
+  )
+  let returnShouldIndent = switch returnExpr.pexp_desc {
+  | Pexp_sequence(_)
+  | Pexp_let(_)
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_open(_) => false
+  | _ => true
+  }
+
+  let returnExprDoc = {
+    let (optBraces, _) = ParsetreeViewer.processBracesAttr(returnExpr)
+    let shouldInline = switch (returnExpr.pexp_desc, optBraces) {
+    | (_, Some(_)) => true
+    | (
+        Pexp_array(_)
+        | Pexp_tuple(_)
+        | Pexp_construct(_, Some(_))
+        | Pexp_record(_),
+        _,
+      ) => true
+    | _ => false
+    }
+
+    let returnDoc = {
+      let doc = printExpressionWithComments(returnExpr, cmtTbl)
+      switch Parens.expr(returnExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, returnExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    if shouldInline {
+      Doc.concat(list{Doc.space, returnDoc})
+    } else {
+      Doc.group(
+        if returnShouldIndent {
+          Doc.concat(list{
+            Doc.indent(Doc.concat(list{Doc.line, returnDoc})),
+            switch inCallback {
+            | FitsOnOneLine | ArgumentsFitOnOneLine => Doc.softLine
+            | _ => Doc.nil
+            },
+          })
+        } else {
+          Doc.concat(list{Doc.space, returnDoc})
+        },
+      )
+    }
+  }
+
+  let typConstraintDoc = switch typConstraint {
+  | Some(typ) => Doc.concat(list{Doc.text(": "), printTypExpr(typ, cmtTbl)})
+  | _ => Doc.nil
+  }
+
+  Doc.concat(list{
+    printAttributes(attrs, cmtTbl),
+    parametersDoc,
+    typConstraintDoc,
+    Doc.text(" =>"),
+    returnExprDoc,
+  })
+}
+
+and printTernaryOperand = (expr, cmtTbl) => {
+  let doc = printExpressionWithComments(expr, cmtTbl)
+  switch Parens.ternaryOperand(expr) {
+  | Parens.Parenthesized => addParens(doc)
+  | Braced(braces) => printBraces(doc, expr, braces)
+  | Nothing => doc
+  }
+}
+
+and printSetFieldExpr = (attrs, lhs, longidentLoc, rhs, loc, cmtTbl) => {
+  let rhsDoc = {
+    let doc = printExpressionWithComments(rhs, cmtTbl)
+    switch Parens.setFieldExprRhs(rhs) {
+    | Parens.Parenthesized => addParens(doc)
+    | Braced(braces) => printBraces(doc, rhs, braces)
+    | Nothing => doc
+    }
+  }
+
+  let lhsDoc = {
+    let doc = printExpressionWithComments(lhs, cmtTbl)
+    switch Parens.fieldExpr(lhs) {
+    | Parens.Parenthesized => addParens(doc)
+    | Braced(braces) => printBraces(doc, lhs, braces)
+    | Nothing => doc
+    }
+  }
+
+  let shouldIndent = ParsetreeViewer.isBinaryExpression(rhs)
+  let doc = Doc.group(
+    Doc.concat(list{
+      lhsDoc,
+      Doc.dot,
+      printLidentPath(longidentLoc, cmtTbl),
+      Doc.text(" ="),
+      if shouldIndent {
+        Doc.group(Doc.indent(Doc.concat(list{Doc.line, rhsDoc})))
+      } else {
+        Doc.concat(list{Doc.space, rhsDoc})
+      },
+    }),
+  )
+  let doc = switch attrs {
+  | list{} => doc
+  | attrs => Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), doc}))
+  }
+
+  printComments(doc, cmtTbl, loc)
+}
+
+and printTemplateLiteral = (expr, cmtTbl) => {
+  let tag = ref("js")
+  let rec walkExpr = expr => {
+    open Parsetree
+    switch expr.pexp_desc {
+    | Pexp_apply(
+        {pexp_desc: Pexp_ident({txt: Longident.Lident("^")})},
+        list{(Nolabel, arg1), (Nolabel, arg2)},
+      ) =>
+      let lhs = walkExpr(arg1)
+      let rhs = walkExpr(arg2)
+      Doc.concat(list{lhs, rhs})
+    | Pexp_constant(Pconst_string(txt, Some(prefix))) =>
+      tag := prefix
+      printStringContents(txt)
+    | _ =>
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      Doc.group(Doc.concat(list{Doc.text("${"), Doc.indent(doc), Doc.rbrace}))
+    }
+  }
+
+  let content = walkExpr(expr)
+  Doc.concat(list{
+    if tag.contents == "js" {
+      Doc.nil
+    } else {
+      Doc.text(tag.contents)
+    },
+    Doc.text("`"),
+    content,
+    Doc.text("`"),
+  })
+}
+
+and printUnaryExpression = (expr, cmtTbl) => {
+  let printUnaryOperator = op =>
+    Doc.text(
+      switch op {
+      | "~+" => "+"
+      | "~+." => "+."
+      | "~-" => "-"
+      | "~-." => "-."
+      | "not" => "!"
+      | _ => assert false
+      },
+    )
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident(operator)})},
+      list{(Nolabel, operand)},
+    ) =>
+    let printedOperand = {
+      let doc = printExpressionWithComments(operand, cmtTbl)
+      switch Parens.unaryExprOperand(operand) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, operand, braces)
+      | Nothing => doc
+      }
+    }
+
+    let doc = Doc.concat(list{printUnaryOperator(operator), printedOperand})
+    printComments(doc, cmtTbl, expr.pexp_loc)
+  | _ => assert false
+  }
+}
+
+and printBinaryExpression = (expr: Parsetree.expression, cmtTbl) => {
+  let printBinaryOperator = (~inlineRhs, operator) => {
+    let operatorTxt = switch operator {
+    | "|." => "->"
+    | "^" => "++"
+    | "=" => "=="
+    | "==" => "==="
+    | "<>" => "!="
+    | "!=" => "!=="
+    | txt => txt
+    }
+
+    let spacingBeforeOperator = if operator == "|." {
+      Doc.softLine
+    } else if operator == "|>" {
+      Doc.line
+    } else {
+      Doc.space
+    }
+
+    let spacingAfterOperator = if operator == "|." {
+      Doc.nil
+    } else if operator == "|>" {
+      Doc.space
+    } else if inlineRhs {
+      Doc.space
+    } else {
+      Doc.line
+    }
+
+    Doc.concat(list{spacingBeforeOperator, Doc.text(operatorTxt), spacingAfterOperator})
+  }
+
+  let printOperand = (~isLhs, expr, parentOperator) => {
+    let rec flatten = (~isLhs, expr, parentOperator) =>
+      if ParsetreeViewer.isBinaryExpression(expr) {
+        switch expr {
+        | {
+            pexp_desc: Pexp_apply(
+              {pexp_desc: Pexp_ident({txt: Longident.Lident(operator)})},
+              list{(_, left), (_, right)},
+            ),
+          } =>
+          if (
+            ParsetreeViewer.flattenableOperators(parentOperator, operator) &&
+            !ParsetreeViewer.hasAttributes(expr.pexp_attributes)
+          ) {
+            let leftPrinted = flatten(~isLhs=true, left, operator)
+            let rightPrinted = {
+              let (_, rightAttrs) = ParsetreeViewer.partitionPrintableAttributes(
+                right.pexp_attributes,
+              )
+
+              let doc = printExpressionWithComments({...right, pexp_attributes: rightAttrs}, cmtTbl)
+
+              let doc = if Parens.flattenOperandRhs(parentOperator, right) {
+                Doc.concat(list{Doc.lparen, doc, Doc.rparen})
+              } else {
+                doc
+              }
+
+              let printableAttrs = ParsetreeViewer.filterPrintableAttributes(right.pexp_attributes)
+
+              let doc = Doc.concat(list{printAttributes(printableAttrs, cmtTbl), doc})
+              switch printableAttrs {
+              | list{} => doc
+              | _ => addParens(doc)
+              }
+            }
+
+            let doc = Doc.concat(list{
+              leftPrinted,
+              printBinaryOperator(~inlineRhs=false, operator),
+              rightPrinted,
+            })
+            let doc = if !isLhs && Parens.rhsBinaryExprOperand(operator, expr) {
+              Doc.concat(list{Doc.lparen, doc, Doc.rparen})
+            } else {
+              doc
+            }
+
+            printComments(doc, cmtTbl, expr.pexp_loc)
+          } else {
+            let doc = printExpressionWithComments({...expr, pexp_attributes: list{}}, cmtTbl)
+            let doc = if (
+              Parens.subBinaryExprOperand(parentOperator, operator) ||
+              (expr.pexp_attributes != list{} &&
+                (ParsetreeViewer.isBinaryExpression(expr) || ParsetreeViewer.isTernaryExpr(expr)))
+            ) {
+              Doc.concat(list{Doc.lparen, doc, Doc.rparen})
+            } else {
+              doc
+            }
+            Doc.concat(list{printAttributes(expr.pexp_attributes, cmtTbl), doc})
+          }
+        | _ => assert false
+        }
+      } else {
+        switch expr.pexp_desc {
+        | Pexp_apply(
+            {pexp_desc: Pexp_ident({txt: Longident.Lident("^"), loc})},
+            list{(Nolabel, _), (Nolabel, _)},
+          ) if loc.loc_ghost =>
+          let doc = printTemplateLiteral(expr, cmtTbl)
+          printComments(doc, cmtTbl, expr.Parsetree.pexp_loc)
+        | Pexp_setfield(lhs, field, rhs) =>
+          let doc = printSetFieldExpr(expr.pexp_attributes, lhs, field, rhs, expr.pexp_loc, cmtTbl)
+          if isLhs {
+            addParens(doc)
+          } else {
+            doc
+          }
+        | Pexp_apply(
+            {pexp_desc: Pexp_ident({txt: Longident.Lident("#=")})},
+            list{(Nolabel, lhs), (Nolabel, rhs)},
+          ) =>
+          let rhsDoc = printExpressionWithComments(rhs, cmtTbl)
+          let lhsDoc = printExpressionWithComments(lhs, cmtTbl)
+          /* TODO: unify indentation of "=" */
+          let shouldIndent = ParsetreeViewer.isBinaryExpression(rhs)
+          let doc = Doc.group(
+            Doc.concat(list{
+              lhsDoc,
+              Doc.text(" ="),
+              if shouldIndent {
+                Doc.group(Doc.indent(Doc.concat(list{Doc.line, rhsDoc})))
+              } else {
+                Doc.concat(list{Doc.space, rhsDoc})
+              },
+            }),
+          )
+          let doc = switch expr.pexp_attributes {
+          | list{} => doc
+          | attrs => Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), doc}))
+          }
+
+          if isLhs {
+            addParens(doc)
+          } else {
+            doc
+          }
+        | _ =>
+          let doc = printExpressionWithComments(expr, cmtTbl)
+          switch Parens.binaryExprOperand(~isLhs, expr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, expr, braces)
+          | Nothing => doc
+          }
+        }
+      }
+
+    flatten(~isLhs, expr, parentOperator)
+  }
+
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident(("|." | "|>") as op)})},
+      list{(Nolabel, lhs), (Nolabel, rhs)},
+    ) if !(ParsetreeViewer.isBinaryExpression(lhs) || ParsetreeViewer.isBinaryExpression(rhs)) =>
+    let lhsHasCommentBelow = hasCommentBelow(cmtTbl, lhs.pexp_loc)
+    let lhsDoc = printOperand(~isLhs=true, lhs, op)
+    let rhsDoc = printOperand(~isLhs=false, rhs, op)
+    Doc.group(
+      Doc.concat(list{
+        lhsDoc,
+        switch (lhsHasCommentBelow, op) {
+        | (true, "|.") => Doc.concat(list{Doc.softLine, Doc.text("->")})
+        | (false, "|.") => Doc.text("->")
+        | (true, "|>") => Doc.concat(list{Doc.line, Doc.text("|> ")})
+        | (false, "|>") => Doc.text(" |> ")
+        | _ => Doc.nil
+        },
+        rhsDoc,
+      }),
+    )
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident(operator)})},
+      list{(Nolabel, lhs), (Nolabel, rhs)},
+    ) =>
+    let right = {
+      let operatorWithRhs = {
+        let rhsDoc = printOperand(~isLhs=false, rhs, operator)
+        Doc.concat(list{
+          printBinaryOperator(~inlineRhs=ParsetreeViewer.shouldInlineRhsBinaryExpr(rhs), operator),
+          rhsDoc,
+        })
+      }
+      if ParsetreeViewer.shouldIndentBinaryExpr(expr) {
+        Doc.group(Doc.indent(operatorWithRhs))
+      } else {
+        operatorWithRhs
+      }
+    }
+
+    let doc = Doc.group(Doc.concat(list{printOperand(~isLhs=true, lhs, operator), right}))
+    Doc.group(
+      Doc.concat(list{
+        printAttributes(expr.pexp_attributes, cmtTbl),
+        switch Parens.binaryExpr({
+          ...expr,
+          pexp_attributes: List.filter(attr =>
+            switch attr {
+            | ({Location.txt: "ns.braces"}, _) => false
+            | _ => true
+            }
+          , expr.pexp_attributes),
+        }) {
+        | Braced(bracesLoc) => printBraces(doc, expr, bracesLoc)
+        | Parenthesized => addParens(doc)
+        | Nothing => doc
+        },
+      }),
+    )
+  | _ => Doc.nil
+  }
+}
+
+/* callExpr(arg1, arg2) */
+and printPexpApply = (expr, cmtTbl) =>
+  switch expr.pexp_desc {
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident("##")})},
+      list{(Nolabel, parentExpr), (Nolabel, memberExpr)},
+    ) =>
+    let parentDoc = {
+      let doc = printExpressionWithComments(parentExpr, cmtTbl)
+      switch Parens.unaryExprOperand(parentExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, parentExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    let member = {
+      let memberDoc = switch memberExpr.pexp_desc {
+      | Pexp_ident(lident) => printComments(printLongident(lident.txt), cmtTbl, memberExpr.pexp_loc)
+      | _ => printExpressionWithComments(memberExpr, cmtTbl)
+      }
+
+      Doc.concat(list{Doc.text("\""), memberDoc, Doc.text("\"")})
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        printAttributes(expr.pexp_attributes, cmtTbl),
+        parentDoc,
+        Doc.lbracket,
+        member,
+        Doc.rbracket,
+      }),
+    )
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Lident("#=")})},
+      list{(Nolabel, lhs), (Nolabel, rhs)},
+    ) =>
+    let rhsDoc = {
+      let doc = printExpressionWithComments(rhs, cmtTbl)
+      switch Parens.expr(rhs) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, rhs, braces)
+      | Nothing => doc
+      }
+    }
+
+    /* TODO: unify indentation of "=" */
+    let shouldIndent = !ParsetreeViewer.isBracedExpr(rhs) && ParsetreeViewer.isBinaryExpression(rhs)
+    let doc = Doc.group(
+      Doc.concat(list{
+        printExpressionWithComments(lhs, cmtTbl),
+        Doc.text(" ="),
+        if shouldIndent {
+          Doc.group(Doc.indent(Doc.concat(list{Doc.line, rhsDoc})))
+        } else {
+          Doc.concat(list{Doc.space, rhsDoc})
+        },
+      }),
+    )
+    switch expr.pexp_attributes {
+    | list{} => doc
+    | attrs => Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), doc}))
+    }
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Ldot(Lident("Array"), "get")})},
+      list{(Nolabel, parentExpr), (Nolabel, memberExpr)},
+    ) if !ParsetreeViewer.isRewrittenUnderscoreApplySugar(parentExpr) =>
+    /* Don't print the Array.get(_, 0) sugar a.k.a. (__x) => Array.get(__x, 0) as _[0] */
+    let member = {
+      let memberDoc = {
+        let doc = printExpressionWithComments(memberExpr, cmtTbl)
+        switch Parens.expr(memberExpr) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, memberExpr, braces)
+        | Nothing => doc
+        }
+      }
+
+      let shouldInline = switch memberExpr.pexp_desc {
+      | Pexp_constant(_) | Pexp_ident(_) => true
+      | _ => false
+      }
+
+      if shouldInline {
+        memberDoc
+      } else {
+        Doc.concat(list{Doc.indent(Doc.concat(list{Doc.softLine, memberDoc})), Doc.softLine})
+      }
+    }
+
+    let parentDoc = {
+      let doc = printExpressionWithComments(parentExpr, cmtTbl)
+      switch Parens.unaryExprOperand(parentExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, parentExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        printAttributes(expr.pexp_attributes, cmtTbl),
+        parentDoc,
+        Doc.lbracket,
+        member,
+        Doc.rbracket,
+      }),
+    )
+  | Pexp_apply(
+      {pexp_desc: Pexp_ident({txt: Longident.Ldot(Lident("Array"), "set")})},
+      list{(Nolabel, parentExpr), (Nolabel, memberExpr), (Nolabel, targetExpr)},
+    ) =>
+    let member = {
+      let memberDoc = {
+        let doc = printExpressionWithComments(memberExpr, cmtTbl)
+        switch Parens.expr(memberExpr) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, memberExpr, braces)
+        | Nothing => doc
+        }
+      }
+
+      let shouldInline = switch memberExpr.pexp_desc {
+      | Pexp_constant(_) | Pexp_ident(_) => true
+      | _ => false
+      }
+
+      if shouldInline {
+        memberDoc
+      } else {
+        Doc.concat(list{Doc.indent(Doc.concat(list{Doc.softLine, memberDoc})), Doc.softLine})
+      }
+    }
+
+    let shouldIndentTargetExpr = if ParsetreeViewer.isBracedExpr(targetExpr) {
+      false
+    } else {
+      ParsetreeViewer.isBinaryExpression(targetExpr) ||
+      switch targetExpr {
+      | {
+          pexp_attributes: list{({Location.txt: "ns.ternary"}, _)},
+          pexp_desc: Pexp_ifthenelse(ifExpr, _, _),
+        } =>
+        ParsetreeViewer.isBinaryExpression(ifExpr) ||
+        ParsetreeViewer.hasAttributes(ifExpr.pexp_attributes)
+      | {pexp_desc: Pexp_newtype(_)} => false
+      | e => ParsetreeViewer.hasAttributes(e.pexp_attributes) || ParsetreeViewer.isArrayAccess(e)
+      }
+    }
+
+    let targetExpr = {
+      let doc = printExpressionWithComments(targetExpr, cmtTbl)
+      switch Parens.expr(targetExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, targetExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    let parentDoc = {
+      let doc = printExpressionWithComments(parentExpr, cmtTbl)
+      switch Parens.unaryExprOperand(parentExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, parentExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        printAttributes(expr.pexp_attributes, cmtTbl),
+        parentDoc,
+        Doc.lbracket,
+        member,
+        Doc.rbracket,
+        Doc.text(" ="),
+        if shouldIndentTargetExpr {
+          Doc.indent(Doc.concat(list{Doc.line, targetExpr}))
+        } else {
+          Doc.concat(list{Doc.space, targetExpr})
+        },
+      }),
+    )
+  /* TODO: cleanup, are those branches even remotely performant? */
+  | Pexp_apply({pexp_desc: Pexp_ident(lident)}, args) if ParsetreeViewer.isJsxExpression(expr) =>
+    printJsxExpression(lident, args, cmtTbl)
+  | Pexp_apply(callExpr, args) =>
+    let args = List.map(((lbl, arg)) => (lbl, ParsetreeViewer.rewriteUnderscoreApply(arg)), args)
+
+    let (uncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(expr.pexp_attributes)
+
+    let callExprDoc = {
+      let doc = printExpressionWithComments(callExpr, cmtTbl)
+      switch Parens.callExpr(callExpr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, callExpr, braces)
+      | Nothing => doc
+      }
+    }
+
+    if ParsetreeViewer.requiresSpecialCallbackPrintingFirstArg(args) {
+      let argsDoc = printArgumentsWithCallbackInFirstPosition(~uncurried, args, cmtTbl)
+
+      Doc.concat(list{printAttributes(attrs, cmtTbl), callExprDoc, argsDoc})
+    } else if ParsetreeViewer.requiresSpecialCallbackPrintingLastArg(args) {
+      let argsDoc = printArgumentsWithCallbackInLastPosition(~uncurried, args, cmtTbl)
+
+      /*
+       * Fixes the following layout (the `[` and `]` should break):
+       *   [fn(x => {
+       *     let _ = x
+       *   }), fn(y => {
+       *     let _ = y
+       *   }), fn(z => {
+       *     let _ = z
+       *   })]
+       * See `Doc.willBreak documentation in interface file for more context.
+       * Context:
+       *  https://github.com/rescript-lang/syntax/issues/111
+       *  https://github.com/rescript-lang/syntax/issues/166
+       */
+      let maybeBreakParent = if Doc.willBreak(argsDoc) {
+        Doc.breakParent
+      } else {
+        Doc.nil
+      }
+
+      Doc.concat(list{maybeBreakParent, printAttributes(attrs, cmtTbl), callExprDoc, argsDoc})
+    } else {
+      let argsDoc = printArguments(~uncurried, args, cmtTbl)
+      Doc.concat(list{printAttributes(attrs, cmtTbl), callExprDoc, argsDoc})
+    }
+  | _ => assert false
+  }
+
+and printJsxExpression = (lident, args, cmtTbl) => {
+  let name = printJsxName(lident)
+  let (formattedProps, children) = printJsxProps(args, cmtTbl)
+  /* <div className="test" /> */
+  let isSelfClosing = switch children {
+  | Some({Parsetree.pexp_desc: Pexp_construct({txt: Longident.Lident("[]")}, None)}) => true
+  | _ => false
+  }
+
+  Doc.group(
+    Doc.concat(list{
+      Doc.group(
+        Doc.concat(list{
+          printComments(Doc.concat(list{Doc.lessThan, name}), cmtTbl, lident.Asttypes.loc),
+          formattedProps,
+          if isSelfClosing {
+            Doc.concat(list{Doc.line, Doc.text("/>")})
+          } else {
+            Doc.nil
+          },
+        }),
+      ),
+      if isSelfClosing {
+        Doc.nil
+      } else {
+        Doc.concat(list{
+          Doc.greaterThan,
+          Doc.indent(
+            Doc.concat(list{
+              Doc.line,
+              switch children {
+              | Some(childrenExpression) => printJsxChildren(childrenExpression, cmtTbl)
+              | None => Doc.nil
+              },
+            }),
+          ),
+          Doc.line,
+          Doc.text("</"),
+          name,
+          Doc.greaterThan,
+        })
+      },
+    }),
+  )
+}
+
+and printJsxFragment = (expr, cmtTbl) => {
+  let opening = Doc.text("<>")
+  let closing = Doc.text("</>")
+  /* let (children, _) = ParsetreeViewer.collectListExpressions expr in */
+  Doc.group(
+    Doc.concat(list{
+      opening,
+      switch expr.pexp_desc {
+      | Pexp_construct({txt: Longident.Lident("[]")}, None) => Doc.nil
+      | _ => Doc.indent(Doc.concat(list{Doc.line, printJsxChildren(expr, cmtTbl)}))
+      },
+      Doc.line,
+      closing,
+    }),
+  )
+}
+
+and printJsxChildren = (childrenExpr: Parsetree.expression, cmtTbl) =>
+  switch childrenExpr.pexp_desc {
+  | Pexp_construct({txt: Longident.Lident("::")}, _) =>
+    let (children, _) = ParsetreeViewer.collectListExpressions(childrenExpr)
+    Doc.group(Doc.join(~sep=Doc.line, List.map((expr: Parsetree.expression) => {
+          let leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, expr.pexp_loc)
+          let exprDoc = printExpressionWithComments(expr, cmtTbl)
+          switch Parens.jsxChildExpr(expr) {
+          | Parenthesized | Braced(_) =>
+            /* {(20: int)} make sure that we also protect the expression inside */
+            let innerDoc = if Parens.bracedExpr(expr) {
+              addParens(exprDoc)
+            } else {
+              exprDoc
+            }
+            if leadingLineCommentPresent {
+              addBraces(innerDoc)
+            } else {
+              Doc.concat(list{Doc.lbrace, innerDoc, Doc.rbrace})
+            }
+          | Nothing => exprDoc
+          }
+        }, children)))
+  | _ =>
+    let leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, childrenExpr.pexp_loc)
+    let exprDoc = printExpressionWithComments(childrenExpr, cmtTbl)
+    Doc.concat(list{
+      Doc.dotdotdot,
+      switch Parens.jsxChildExpr(childrenExpr) {
+      | Parenthesized | Braced(_) =>
+        let innerDoc = if Parens.bracedExpr(childrenExpr) {
+          addParens(exprDoc)
+        } else {
+          exprDoc
+        }
+        if leadingLineCommentPresent {
+          addBraces(innerDoc)
+        } else {
+          Doc.concat(list{Doc.lbrace, innerDoc, Doc.rbrace})
+        }
+      | Nothing => exprDoc
+      },
+    })
+  }
+
+and printJsxProps = (args, cmtTbl): (Doc.t, option<Parsetree.expression>) => {
+  let rec loop = (props, args) =>
+    switch args {
+    | list{} => (Doc.nil, None)
+    | list{
+        (Asttypes.Labelled("children"), children),
+        (
+          Asttypes.Nolabel,
+          {Parsetree.pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, None)},
+        ),
+      } =>
+      let formattedProps = Doc.indent(
+        switch props {
+        | list{} => Doc.nil
+        | props => Doc.concat(list{Doc.line, Doc.group(Doc.join(~sep=Doc.line, props |> List.rev))})
+        },
+      )
+      (formattedProps, Some(children))
+    | list{arg, ...args} =>
+      let propDoc = printJsxProp(arg, cmtTbl)
+      loop(list{propDoc, ...props}, args)
+    }
+
+  loop(list{}, args)
+}
+
+and printJsxProp = (arg, cmtTbl) =>
+  switch arg {
+  | (
+      (Asttypes.Labelled(lblTxt) | Optional(lblTxt)) as lbl,
+      {
+        Parsetree.pexp_attributes: list{({Location.txt: "ns.namedArgLoc", loc: argLoc}, _)},
+        pexp_desc: Pexp_ident({txt: Longident.Lident(ident)}),
+      },
+    ) if lblTxt == ident /* jsx punning */ =>
+    switch lbl {
+    | Nolabel => Doc.nil
+    | Labelled(_lbl) => printComments(printIdentLike(ident), cmtTbl, argLoc)
+    | Optional(_lbl) =>
+      let doc = Doc.concat(list{Doc.question, printIdentLike(ident)})
+      printComments(doc, cmtTbl, argLoc)
+    }
+  | (
+      (Asttypes.Labelled(lblTxt) | Optional(lblTxt)) as lbl,
+      {Parsetree.pexp_attributes: list{}, pexp_desc: Pexp_ident({txt: Longident.Lident(ident)})},
+    ) if lblTxt == ident /* jsx punning when printing from Reason */ =>
+    switch lbl {
+    | Nolabel => Doc.nil
+    | Labelled(_lbl) => printIdentLike(ident)
+    | Optional(_lbl) => Doc.concat(list{Doc.question, printIdentLike(ident)})
+    }
+  | (lbl, expr) =>
+    let (argLoc, expr) = switch expr.pexp_attributes {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ...attrs} => (
+        loc,
+        {...expr, pexp_attributes: attrs},
+      )
+    | _ => (Location.none, expr)
+    }
+
+    let lblDoc = switch lbl {
+    | Asttypes.Labelled(lbl) =>
+      let lbl = printComments(printIdentLike(lbl), cmtTbl, argLoc)
+      Doc.concat(list{lbl, Doc.equal})
+    | Asttypes.Optional(lbl) =>
+      let lbl = printComments(printIdentLike(lbl), cmtTbl, argLoc)
+      Doc.concat(list{lbl, Doc.equal, Doc.question})
+    | Nolabel => Doc.nil
+    }
+
+    let exprDoc = {
+      let leadingLineCommentPresent = hasLeadingLineComment(cmtTbl, expr.pexp_loc)
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.jsxPropExpr(expr) {
+      | Parenthesized | Braced(_) =>
+        /* {(20: int)} make sure that we also protect the expression inside */
+        let innerDoc = if Parens.bracedExpr(expr) {
+          addParens(doc)
+        } else {
+          doc
+        }
+        if leadingLineCommentPresent {
+          addBraces(innerDoc)
+        } else {
+          Doc.concat(list{Doc.lbrace, innerDoc, Doc.rbrace})
+        }
+      | _ => doc
+      }
+    }
+
+    let fullLoc = {...argLoc, loc_end: expr.pexp_loc.loc_end}
+    printComments(Doc.concat(list{lblDoc, exprDoc}), cmtTbl, fullLoc)
+  }
+
+/* div -> div.
+ * Navabar.createElement -> Navbar
+ * Staff.Users.createElement -> Staff.Users */
+and printJsxName = ({txt: lident}) => {
+  let rec flatten = (acc, lident) =>
+    switch lident {
+    | Longident.Lident(txt) => list{txt, ...acc}
+    | Ldot(lident, txt) =>
+      let acc = if txt == "createElement" {
+        acc
+      } else {
+        list{txt, ...acc}
+      }
+      flatten(acc, lident)
+    | _ => acc
+    }
+
+  switch lident {
+  | Longident.Lident(txt) => Doc.text(txt)
+  | _ as lident =>
+    let segments = flatten(list{}, lident)
+    Doc.join(~sep=Doc.dot, List.map(Doc.text, segments))
+  }
+}
+
+and printArgumentsWithCallbackInFirstPosition = (~uncurried, args, cmtTbl) => {
+  /* Because the same subtree gets printed twice, we need to copy the cmtTbl.
+   * consumed comments need to be marked not-consumed and reprinted…
+   * Cheng's different comment algorithm will solve this. */
+  let cmtTblCopy = CommentTable.copy(cmtTbl)
+  let (callback, printedArgs) = switch args {
+  | list{(lbl, expr), ...args} =>
+    let lblDoc = switch lbl {
+    | Asttypes.Nolabel => Doc.nil
+    | Asttypes.Labelled(txt) => Doc.concat(list{Doc.tilde, printIdentLike(txt), Doc.equal})
+    | Asttypes.Optional(txt) =>
+      Doc.concat(list{Doc.tilde, printIdentLike(txt), Doc.equal, Doc.question})
+    }
+
+    let callback = Doc.concat(list{lblDoc, printPexpFun(~inCallback=FitsOnOneLine, expr, cmtTbl)})
+    let callback = printComments(callback, cmtTbl, expr.pexp_loc)
+    let printedArgs = Doc.join(
+      ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+      List.map(arg => printArgument(arg, cmtTbl), args),
+    )
+
+    (callback, printedArgs)
+  | _ => assert false
+  }
+
+  /* Thing.map((arg1, arg2) => MyModuleBlah.toList(argument), foo) */
+  /* Thing.map((arg1, arg2) => {
+   *   MyModuleBlah.toList(argument)
+   * }, longArgumet, veryLooooongArgument)
+   */
+  let fitsOnOneLine = Doc.concat(list{
+    if uncurried {
+      Doc.text("(. ")
+    } else {
+      Doc.lparen
+    },
+    callback,
+    Doc.comma,
+    Doc.line,
+    printedArgs,
+    Doc.rparen,
+  })
+
+  /* Thing.map(
+   *   (param1, parm2) => doStuff(param1, parm2),
+   *   arg1,
+   *   arg2,
+   *   arg3,
+   * )
+   */
+  let breakAllArgs = printArguments(~uncurried, args, cmtTblCopy)
+
+  /* Sometimes one of the non-callback arguments will break.
+   * There might be a single line comment in there, or a multiline string etc.
+   * showDialog(
+   *   ~onConfirm={() => ()},
+   *   `
+   *   Do you really want to leave this workspace?
+   *   Some more text with detailed explanations...
+   *   `,
+   *   ~danger=true,
+   *   // comment   --> here a single line comment
+   *   ~confirmText="Yes, I am sure!",
+   *  )
+   * In this case, we always want the arguments broken over multiple lines,
+   * like a normal function call.
+   */
+  if Doc.willBreak(printedArgs) {
+    breakAllArgs
+  } else {
+    Doc.customLayout(list{fitsOnOneLine, breakAllArgs})
+  }
+}
+
+and printArgumentsWithCallbackInLastPosition = (~uncurried, args, cmtTbl) => {
+  /* Because the same subtree gets printed twice, we need to copy the cmtTbl.
+   * consumed comments need to be marked not-consumed and reprinted…
+   * Cheng's different comment algorithm will solve this. */
+  let cmtTblCopy = CommentTable.copy(cmtTbl)
+  let cmtTblCopy2 = CommentTable.copy(cmtTbl)
+  let rec loop = (acc, args) =>
+    switch args {
+    | list{} => (Doc.nil, Doc.nil, Doc.nil)
+    | list{(lbl, expr)} =>
+      let lblDoc = switch lbl {
+      | Asttypes.Nolabel => Doc.nil
+      | Asttypes.Labelled(txt) => Doc.concat(list{Doc.tilde, printIdentLike(txt), Doc.equal})
+      | Asttypes.Optional(txt) =>
+        Doc.concat(list{Doc.tilde, printIdentLike(txt), Doc.equal, Doc.question})
+      }
+
+      let callbackFitsOnOneLine = {
+        let pexpFunDoc = printPexpFun(~inCallback=FitsOnOneLine, expr, cmtTbl)
+        let doc = Doc.concat(list{lblDoc, pexpFunDoc})
+        printComments(doc, cmtTbl, expr.pexp_loc)
+      }
+
+      let callbackArgumentsFitsOnOneLine = {
+        let pexpFunDoc = printPexpFun(~inCallback=ArgumentsFitOnOneLine, expr, cmtTblCopy)
+        let doc = Doc.concat(list{lblDoc, pexpFunDoc})
+        printComments(doc, cmtTblCopy, expr.pexp_loc)
+      }
+
+      (Doc.concat(List.rev(acc)), callbackFitsOnOneLine, callbackArgumentsFitsOnOneLine)
+    | list{arg, ...args} =>
+      let argDoc = printArgument(arg, cmtTbl)
+      loop(list{Doc.line, Doc.comma, argDoc, ...acc}, args)
+    }
+
+  let (printedArgs, callback, callback2) = loop(list{}, args)
+
+  /* Thing.map(foo, (arg1, arg2) => MyModuleBlah.toList(argument)) */
+  let fitsOnOneLine = Doc.concat(list{
+    if uncurried {
+      Doc.text("(.")
+    } else {
+      Doc.lparen
+    },
+    printedArgs,
+    callback,
+    Doc.rparen,
+  })
+
+  /* Thing.map(longArgumet, veryLooooongArgument, (arg1, arg2) =>
+   *   MyModuleBlah.toList(argument)
+   * )
+   */
+  let arugmentsFitOnOneLine = Doc.concat(list{
+    if uncurried {
+      Doc.text("(.")
+    } else {
+      Doc.lparen
+    },
+    printedArgs,
+    Doc.breakableGroup(~forceBreak=true, callback2),
+    Doc.rparen,
+  })
+
+  /* Thing.map(
+   *   arg1,
+   *   arg2,
+   *   arg3,
+   *   (param1, parm2) => doStuff(param1, parm2)
+   * )
+   */
+  let breakAllArgs = printArguments(~uncurried, args, cmtTblCopy2)
+
+  /* Sometimes one of the non-callback arguments will break.
+   * There might be a single line comment in there, or a multiline string etc.
+   * showDialog(
+   *   `
+   *   Do you really want to leave this workspace?
+   *   Some more text with detailed explanations...
+   *   `,
+   *   ~danger=true,
+   *   // comment   --> here a single line comment
+   *   ~confirmText="Yes, I am sure!",
+   *   ~onConfirm={() => ()},
+   *  )
+   * In this case, we always want the arguments broken over multiple lines,
+   * like a normal function call.
+   */
+  if Doc.willBreak(printedArgs) {
+    breakAllArgs
+  } else {
+    Doc.customLayout(list{fitsOnOneLine, arugmentsFitOnOneLine, breakAllArgs})
+  }
+}
+
+and printArguments = (~uncurried, args: list<(Asttypes.arg_label, Parsetree.expression)>, cmtTbl) =>
+  switch args {
+  | list{(Nolabel, {pexp_desc: Pexp_construct({txt: Longident.Lident("()")}, _), pexp_loc: loc})} =>
+    /* See "parseCallExpr", ghost unit expression is used the implement
+     * arity zero vs arity one syntax.
+     * Related: https://github.com/rescript-lang/syntax/issues/138 */
+    switch (uncurried, loc.loc_ghost) {
+    | (true, true) => Doc.text("(.)") /* arity zero */
+    | (true, false) => Doc.text("(. ())") /* arity one */
+    | _ => Doc.text("()")
+    }
+  | list{(Nolabel, arg)} if ParsetreeViewer.isHuggableExpression(arg) =>
+    let argDoc = {
+      let doc = printExpressionWithComments(arg, cmtTbl)
+      switch Parens.expr(arg) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, arg, braces)
+      | Nothing => doc
+      }
+    }
+
+    Doc.concat(list{
+      if uncurried {
+        Doc.text("(. ")
+      } else {
+        Doc.lparen
+      },
+      argDoc,
+      Doc.rparen,
+    })
+  | args =>
+    Doc.group(
+      Doc.concat(list{
+        if uncurried {
+          Doc.text("(.")
+        } else {
+          Doc.lparen
+        },
+        Doc.indent(
+          Doc.concat(list{
+            if uncurried {
+              Doc.line
+            } else {
+              Doc.softLine
+            },
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(arg => printArgument(arg, cmtTbl), args),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+  }
+
+/*
+ * argument ::=
+ *   | _                            (* syntax sugar *)
+ *   | expr
+ *   | expr : type
+ *   | ~ label-name
+ *   | ~ label-name
+ *   | ~ label-name ?
+ *   | ~ label-name =   expr
+ *   | ~ label-name =   _           (* syntax sugar *)
+ *   | ~ label-name =   expr : type
+ *   | ~ label-name = ? expr
+ *   | ~ label-name = ? _           (* syntax sugar *)
+ *   | ~ label-name = ? expr : type */
+and printArgument = ((argLbl, arg), cmtTbl) =>
+  switch (argLbl, arg) {
+  /* ~a (punned) */
+  | (
+      Asttypes.Labelled(lbl),
+      {
+        pexp_desc: Pexp_ident({txt: Longident.Lident(name)}),
+        pexp_attributes: list{} | list{({Location.txt: "ns.namedArgLoc"}, _)},
+      } as argExpr,
+    ) if lbl == name && !ParsetreeViewer.isBracedExpr(argExpr) =>
+    let loc = switch arg.pexp_attributes {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._} => loc
+    | _ => arg.pexp_loc
+    }
+
+    let doc = Doc.concat(list{Doc.tilde, printIdentLike(lbl)})
+    printComments(doc, cmtTbl, loc)
+
+  /* ~a: int (punned) */
+  | (
+      Asttypes.Labelled(lbl),
+      {
+        pexp_desc: Pexp_constraint(
+          {pexp_desc: Pexp_ident({txt: Longident.Lident(name)})} as argExpr,
+          typ,
+        ),
+        pexp_loc,
+        pexp_attributes: (list{} | list{({Location.txt: "ns.namedArgLoc"}, _)}) as attrs,
+      },
+    ) if lbl == name && !ParsetreeViewer.isBracedExpr(argExpr) =>
+    let loc = switch attrs {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._} => {...loc, loc_end: pexp_loc.loc_end}
+    | _ => arg.pexp_loc
+    }
+
+    let doc = Doc.concat(list{
+      Doc.tilde,
+      printIdentLike(lbl),
+      Doc.text(": "),
+      printTypExpr(typ, cmtTbl),
+    })
+    printComments(doc, cmtTbl, loc)
+  /* ~a? (optional lbl punned) */
+  | (
+      Asttypes.Optional(lbl),
+      {
+        pexp_desc: Pexp_ident({txt: Longident.Lident(name)}),
+        pexp_attributes: list{} | list{({Location.txt: "ns.namedArgLoc"}, _)},
+      },
+    ) if lbl == name =>
+    let loc = switch arg.pexp_attributes {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._} => loc
+    | _ => arg.pexp_loc
+    }
+
+    let doc = Doc.concat(list{Doc.tilde, printIdentLike(lbl), Doc.question})
+    printComments(doc, cmtTbl, loc)
+  | (_lbl, expr) =>
+    let (argLoc, expr) = switch expr.pexp_attributes {
+    | list{({Location.txt: "ns.namedArgLoc", loc}, _), ...attrs} => (
+        loc,
+        {...expr, pexp_attributes: attrs},
+      )
+    | _ => (expr.pexp_loc, expr)
+    }
+
+    let printedLbl = switch argLbl {
+    | Asttypes.Nolabel => Doc.nil
+    | Asttypes.Labelled(lbl) =>
+      let doc = Doc.concat(list{Doc.tilde, printIdentLike(lbl), Doc.equal})
+      printComments(doc, cmtTbl, argLoc)
+    | Asttypes.Optional(lbl) =>
+      let doc = Doc.concat(list{Doc.tilde, printIdentLike(lbl), Doc.equal, Doc.question})
+      printComments(doc, cmtTbl, argLoc)
+    }
+
+    let printedExpr = {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.expr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    }
+
+    let loc = {...argLoc, loc_end: expr.pexp_loc.loc_end}
+    let doc = Doc.concat(list{printedLbl, printedExpr})
+    printComments(doc, cmtTbl, loc)
+  }
+
+and printCases = (cases: list<Parsetree.case>, cmtTbl) =>
+  Doc.breakableGroup(
+    ~forceBreak=true,
+    Doc.concat(list{
+      Doc.lbrace,
+      Doc.concat(list{
+        Doc.line,
+        printList(
+          ~getLoc=n => {...n.Parsetree.pc_lhs.ppat_loc, loc_end: n.pc_rhs.pexp_loc.loc_end},
+          ~print=printCase,
+          ~nodes=cases,
+          cmtTbl,
+        ),
+      }),
+      Doc.line,
+      Doc.rbrace,
+    }),
+  )
+
+and printCase = (case: Parsetree.case, cmtTbl) => {
+  let rhs = switch case.pc_rhs.pexp_desc {
+  | Pexp_let(_)
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_open(_)
+  | Pexp_sequence(_) =>
+    printExpressionBlock(~braces=ParsetreeViewer.isBracedExpr(case.pc_rhs), case.pc_rhs, cmtTbl)
+  | _ =>
+    let doc = printExpressionWithComments(case.pc_rhs, cmtTbl)
+    switch Parens.expr(case.pc_rhs) {
+    | Parenthesized => addParens(doc)
+    | _ => doc
+    }
+  }
+
+  let guard = switch case.pc_guard {
+  | None => Doc.nil
+  | Some(expr) =>
+    Doc.group(
+      Doc.concat(list{Doc.line, Doc.text("if "), printExpressionWithComments(expr, cmtTbl)}),
+    )
+  }
+
+  let shouldInlineRhs = switch case.pc_rhs.pexp_desc {
+  | Pexp_construct({txt: Longident.Lident("()" | "true" | "false")}, _)
+  | Pexp_constant(_)
+  | Pexp_ident(_) => true
+  | _ if ParsetreeViewer.isHuggableRhs(case.pc_rhs) => true
+  | _ => false
+  }
+
+  let shouldIndentPattern = switch case.pc_lhs.ppat_desc {
+  | Ppat_or(_) => false
+  | _ => true
+  }
+
+  let patternDoc = {
+    let doc = printPattern(case.pc_lhs, cmtTbl)
+    switch case.pc_lhs.ppat_desc {
+    | Ppat_constraint(_) => addParens(doc)
+    | _ => doc
+    }
+  }
+
+  let content = Doc.concat(list{
+    if shouldIndentPattern {
+      Doc.indent(patternDoc)
+    } else {
+      patternDoc
+    },
+    Doc.indent(guard),
+    Doc.text(" =>"),
+    Doc.indent(
+      Doc.concat(list{
+        if shouldInlineRhs {
+          Doc.space
+        } else {
+          Doc.line
+        },
+        rhs,
+      }),
+    ),
+  })
+  Doc.group(Doc.concat(list{Doc.text("| "), content}))
+}
+
+and printExprFunParameters = (~inCallback, ~uncurried, ~hasConstraint, parameters, cmtTbl) =>
+  switch parameters {
+  /* let f = _ => () */
+  | list{ParsetreeViewer.Parameter({
+      attrs: list{},
+      lbl: Asttypes.Nolabel,
+      defaultExpr: None,
+      pat: {Parsetree.ppat_desc: Ppat_any},
+    })} if !uncurried =>
+    if hasConstraint {
+      Doc.text("(_)")
+    } else {
+      Doc.text("_")
+    }
+  /* let f = a => () */
+  | list{ParsetreeViewer.Parameter({
+      attrs: list{},
+      lbl: Asttypes.Nolabel,
+      defaultExpr: None,
+      pat: {Parsetree.ppat_desc: Ppat_var(stringLoc)},
+    })} if !uncurried =>
+    let txtDoc = {
+      let var = printIdentLike(stringLoc.txt)
+      if hasConstraint {
+        addParens(var)
+      } else {
+        var
+      }
+    }
+
+    printComments(txtDoc, cmtTbl, stringLoc.loc)
+  /* let f = () => () */
+  | list{ParsetreeViewer.Parameter({
+      attrs: list{},
+      lbl: Asttypes.Nolabel,
+      defaultExpr: None,
+      pat: {ppat_desc: Ppat_construct({txt: Longident.Lident("()")}, None)},
+    })} if !uncurried =>
+    Doc.text("()")
+  /* let f = (~greeting, ~from as hometown, ~x=?) => () */
+  | parameters =>
+    let inCallback = switch inCallback {
+    | FitsOnOneLine => true
+    | _ => false
+    }
+
+    let lparen = if uncurried {
+      Doc.text("(. ")
+    } else {
+      Doc.lparen
+    }
+    let shouldHug = ParsetreeViewer.parametersShouldHug(parameters)
+    let printedParamaters = Doc.concat(list{
+      if shouldHug || inCallback {
+        Doc.nil
+      } else {
+        Doc.softLine
+      },
+      Doc.join(
+        ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+        List.map(p => printExpFunParameter(p, cmtTbl), parameters),
+      ),
+    })
+    Doc.group(
+      Doc.concat(list{
+        lparen,
+        if shouldHug || inCallback {
+          printedParamaters
+        } else {
+          Doc.concat(list{Doc.indent(printedParamaters), Doc.trailingComma, Doc.softLine})
+        },
+        Doc.rparen,
+      }),
+    )
+  }
+
+and printExpFunParameter = (parameter, cmtTbl) =>
+  switch parameter {
+  | ParsetreeViewer.NewTypes({attrs, locs: lbls}) =>
+    Doc.group(
+      Doc.concat(list{
+        printAttributes(attrs, cmtTbl),
+        Doc.text("type "),
+        Doc.join(
+          ~sep=Doc.space,
+          List.map(
+            lbl => printComments(printIdentLike(lbl.Asttypes.txt), cmtTbl, lbl.Asttypes.loc),
+            lbls,
+          ),
+        ),
+      }),
+    )
+  | Parameter({attrs, lbl, defaultExpr, pat: pattern}) =>
+    let (isUncurried, attrs) = ParsetreeViewer.processUncurriedAttribute(attrs)
+    let uncurried = if isUncurried {
+      Doc.concat(list{Doc.dot, Doc.space})
+    } else {
+      Doc.nil
+    }
+    let attrs = printAttributes(attrs, cmtTbl)
+    /* =defaultValue */
+    let defaultExprDoc = switch defaultExpr {
+    | Some(expr) => Doc.concat(list{Doc.text("="), printExpressionWithComments(expr, cmtTbl)})
+    | None => Doc.nil
+    }
+
+    /* ~from as hometown
+     * ~from                   ->  punning */
+    let labelWithPattern = switch (lbl, pattern) {
+    | (Asttypes.Nolabel, pattern) => printPattern(pattern, cmtTbl)
+    | (
+        Asttypes.Labelled(lbl) | Optional(lbl),
+        {
+          ppat_desc: Ppat_var(stringLoc),
+          ppat_attributes: list{} | list{({Location.txt: "ns.namedArgLoc"}, _)},
+        },
+      ) if lbl == stringLoc.txt =>
+      /* ~d */
+      Doc.concat(list{Doc.text("~"), printIdentLike(lbl)})
+    | (
+        Asttypes.Labelled(lbl) | Optional(lbl),
+        {
+          ppat_desc: Ppat_constraint({ppat_desc: Ppat_var({txt})}, typ),
+          ppat_attributes: list{} | list{({Location.txt: "ns.namedArgLoc"}, _)},
+        },
+      ) if lbl == txt =>
+      /* ~d: e */
+      Doc.concat(list{
+        Doc.text("~"),
+        printIdentLike(lbl),
+        Doc.text(": "),
+        printTypExpr(typ, cmtTbl),
+      })
+    | (Asttypes.Labelled(lbl) | Optional(lbl), pattern) =>
+      /* ~b as c */
+      Doc.concat(list{
+        Doc.text("~"),
+        printIdentLike(lbl),
+        Doc.text(" as "),
+        printPattern(pattern, cmtTbl),
+      })
+    }
+
+    let optionalLabelSuffix = switch (lbl, defaultExpr) {
+    | (Asttypes.Optional(_), None) => Doc.text("=?")
+    | _ => Doc.nil
+    }
+
+    let doc = Doc.group(
+      Doc.concat(list{uncurried, attrs, labelWithPattern, defaultExprDoc, optionalLabelSuffix}),
+    )
+    let cmtLoc = switch defaultExpr {
+    | None =>
+      switch pattern.ppat_attributes {
+      | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._} => {
+          ...loc,
+          loc_end: pattern.ppat_loc.loc_end,
+        }
+      | _ => pattern.ppat_loc
+      }
+    | Some(expr) =>
+      let startPos = switch pattern.ppat_attributes {
+      | list{({Location.txt: "ns.namedArgLoc", loc}, _), ..._} => loc.loc_start
+      | _ => pattern.ppat_loc.loc_start
+      }
+      {
+        ...pattern.ppat_loc,
+        loc_start: startPos,
+        loc_end: expr.pexp_loc.loc_end,
+      }
+    }
+
+    printComments(doc, cmtTbl, cmtLoc)
+  }
+
+and printExpressionBlock = (~braces, expr, cmtTbl) => {
+  let rec collectRows = (acc, expr) =>
+    switch expr.Parsetree.pexp_desc {
+    | Parsetree.Pexp_letmodule(modName, modExpr, expr2) =>
+      let name = {
+        let doc = Doc.text(modName.txt)
+        printComments(doc, cmtTbl, modName.loc)
+      }
+
+      let letModuleDoc = Doc.concat(list{
+        Doc.text("module "),
+        name,
+        Doc.text(" = "),
+        printModExpr(modExpr, cmtTbl),
+      })
+      let loc = {...expr.pexp_loc, loc_end: modExpr.pmod_loc.loc_end}
+      collectRows(list{(loc, letModuleDoc), ...acc}, expr2)
+    | Pexp_letexception(extensionConstructor, expr2) =>
+      let loc = {
+        let loc = {...expr.pexp_loc, loc_end: extensionConstructor.pext_loc.loc_end}
+        switch getFirstLeadingComment(cmtTbl, loc) {
+        | None => loc
+        | Some(comment) =>
+          let cmtLoc = Comment.loc(comment)
+          {...cmtLoc, loc_end: loc.loc_end}
+        }
+      }
+
+      let letExceptionDoc = printExceptionDef(extensionConstructor, cmtTbl)
+      collectRows(list{(loc, letExceptionDoc), ...acc}, expr2)
+    | Pexp_open(overrideFlag, longidentLoc, expr2) =>
+      let openDoc = Doc.concat(list{
+        Doc.text("open"),
+        printOverrideFlag(overrideFlag),
+        Doc.space,
+        printLongidentLocation(longidentLoc, cmtTbl),
+      })
+      let loc = {...expr.pexp_loc, loc_end: longidentLoc.loc.loc_end}
+      collectRows(list{(loc, openDoc), ...acc}, expr2)
+    | Pexp_sequence(expr1, expr2) =>
+      let exprDoc = {
+        let doc = printExpression(expr1, cmtTbl)
+        switch Parens.expr(expr1) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, expr1, braces)
+        | Nothing => doc
+        }
+      }
+
+      let loc = expr1.pexp_loc
+      collectRows(list{(loc, exprDoc), ...acc}, expr2)
+    | Pexp_let(recFlag, valueBindings, expr2) =>
+      let loc = {
+        let loc = switch (valueBindings, List.rev(valueBindings)) {
+        | (list{vb, ..._}, list{lastVb, ..._}) => {...vb.pvb_loc, loc_end: lastVb.pvb_loc.loc_end}
+        | _ => Location.none
+        }
+
+        switch getFirstLeadingComment(cmtTbl, loc) {
+        | None => loc
+        | Some(comment) =>
+          let cmtLoc = Comment.loc(comment)
+          {...cmtLoc, loc_end: loc.loc_end}
+        }
+      }
+
+      let recFlag = switch recFlag {
+      | Asttypes.Nonrecursive => Doc.nil
+      | Asttypes.Recursive => Doc.text("rec ")
+      }
+
+      let letDoc = printValueBindings(~recFlag, valueBindings, cmtTbl)
+      /* let () = {
+       *   let () = foo()
+       *   ()
+       * }
+       * We don't need to print the () on the last line of the block
+       */
+      switch expr2.pexp_desc {
+      | Pexp_construct({txt: Longident.Lident("()")}, _) => List.rev(list{(loc, letDoc), ...acc})
+      | _ => collectRows(list{(loc, letDoc), ...acc}, expr2)
+      }
+    | _ =>
+      let exprDoc = {
+        let doc = printExpression(expr, cmtTbl)
+        switch Parens.expr(expr) {
+        | Parens.Parenthesized => addParens(doc)
+        | Braced(braces) => printBraces(doc, expr, braces)
+        | Nothing => doc
+        }
+      }
+
+      List.rev(list{(expr.pexp_loc, exprDoc), ...acc})
+    }
+
+  let rows = collectRows(list{}, expr)
+  let block = printList(
+    ~getLoc=fst,
+    ~nodes=rows,
+    ~print=((_, doc), _) => doc,
+    ~forceBreak=true,
+    cmtTbl,
+  )
+
+  Doc.breakableGroup(
+    ~forceBreak=true,
+    if braces {
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(Doc.concat(list{Doc.line, block})),
+        Doc.line,
+        Doc.rbrace,
+      })
+    } else {
+      block
+    },
+  )
+}
+
+/*
+ * // user types:
+ * let f = (a, b) => { a + b }
+ *
+ * // printer: everything is on one line
+ * let f = (a, b) => { a + b }
+ *
+ * // user types: over multiple lines
+ * let f = (a, b) => {
+ *   a + b
+ * }
+ *
+ * // printer: over multiple lines
+ * let f = (a, b) => {
+ *   a + b
+ * }
+ */
+and printBraces = (doc, expr, bracesLoc) => {
+  let overMultipleLines = {
+    open Location
+    bracesLoc.loc_end.pos_lnum > bracesLoc.loc_start.pos_lnum
+  }
+
+  switch expr.Parsetree.pexp_desc {
+  | Pexp_letmodule(_)
+  | Pexp_letexception(_)
+  | Pexp_let(_)
+  | Pexp_open(_)
+  | Pexp_sequence(_) => /* already has braces */
+    doc
+  | _ =>
+    Doc.breakableGroup(
+      ~forceBreak=overMultipleLines,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            if Parens.bracedExpr(expr) {
+              addParens(doc)
+            } else {
+              doc
+            },
+          }),
+        ),
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  }
+}
+
+and printOverrideFlag = overrideFlag =>
+  switch overrideFlag {
+  | Asttypes.Override => Doc.text("!")
+  | Fresh => Doc.nil
+  }
+
+and printDirectionFlag = flag =>
+  switch flag {
+  | Asttypes.Downto => Doc.text(" downto ")
+  | Asttypes.Upto => Doc.text(" to ")
+  }
+
+and printRecordRow = ((lbl, expr), cmtTbl, punningAllowed) => {
+  let cmtLoc = {...lbl.loc, loc_end: expr.pexp_loc.loc_end}
+  let doc = Doc.group(
+    switch expr.pexp_desc {
+    | Pexp_ident({txt: Lident(key), loc: keyLoc})
+      if punningAllowed &&
+      (Longident.last(lbl.txt) == key &&
+      lbl.loc.loc_start.pos_cnum === keyLoc.loc_start.pos_cnum) =>
+      /* print punned field */
+      printLidentPath(lbl, cmtTbl)
+    | _ =>
+      Doc.concat(list{
+        printLidentPath(lbl, cmtTbl),
+        Doc.text(": "),
+        {
+          let doc = printExpressionWithComments(expr, cmtTbl)
+          switch Parens.expr(expr) {
+          | Parens.Parenthesized => addParens(doc)
+          | Braced(braces) => printBraces(doc, expr, braces)
+          | Nothing => doc
+          }
+        },
+      })
+    },
+  )
+  printComments(doc, cmtTbl, cmtLoc)
+}
+
+and printBsObjectRow = ((lbl, expr), cmtTbl) => {
+  let cmtLoc = {...lbl.loc, loc_end: expr.pexp_loc.loc_end}
+  let lblDoc = {
+    let doc = Doc.concat(list{Doc.text("\""), printLongident(lbl.txt), Doc.text("\"")})
+    printComments(doc, cmtTbl, lbl.loc)
+  }
+
+  let doc = Doc.concat(list{
+    lblDoc,
+    Doc.text(": "),
+    {
+      let doc = printExpressionWithComments(expr, cmtTbl)
+      switch Parens.expr(expr) {
+      | Parens.Parenthesized => addParens(doc)
+      | Braced(braces) => printBraces(doc, expr, braces)
+      | Nothing => doc
+      }
+    },
+  })
+  printComments(doc, cmtTbl, cmtLoc)
+}
+
+/* The optional loc indicates whether we need to print the attributes in
+ * relation to some location. In practise this means the following:
+ *  `@attr type t = string` -> on the same line, print on the same line
+ *  `@attr
+ *   type t = string` -> attr is on prev line, print the attributes
+ *   with a line break between, we respect the users' original layout */
+and printAttributes = (~loc=?, ~inline=false, attrs: Parsetree.attributes, cmtTbl) =>
+  switch ParsetreeViewer.filterParsingAttrs(attrs) {
+  | list{} => Doc.nil
+  | attrs =>
+    let lineBreak = switch loc {
+    | None => Doc.line
+    | Some(loc) =>
+      switch List.rev(attrs) {
+      | list{({loc: firstLoc}, _), ..._}
+        if loc.loc_start.pos_lnum > firstLoc.loc_end.pos_lnum => Doc.hardLine
+      | _ => Doc.line
+      }
+    }
+
+    Doc.concat(list{
+      Doc.group(Doc.join(~sep=Doc.line, List.map(attr => printAttribute(attr, cmtTbl), attrs))),
+      if inline {
+        Doc.space
+      } else {
+        lineBreak
+      },
+    })
+  }
+
+and printPayload = (payload: Parsetree.payload, cmtTbl) =>
+  switch payload {
+  | PStr(list{}) => Doc.nil
+  | PStr(list{{pstr_desc: Pstr_eval(expr, attrs)}}) =>
+    let exprDoc = printExpressionWithComments(expr, cmtTbl)
+    let needsParens = switch attrs {
+    | list{} => false
+    | _ => true
+    }
+    let shouldHug = ParsetreeViewer.isHuggableExpression(expr)
+    if shouldHug {
+      Doc.concat(list{
+        Doc.lparen,
+        printAttributes(attrs, cmtTbl),
+        if needsParens {
+          addParens(exprDoc)
+        } else {
+          exprDoc
+        },
+        Doc.rparen,
+      })
+    } else {
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            printAttributes(attrs, cmtTbl),
+            if needsParens {
+              addParens(exprDoc)
+            } else {
+              exprDoc
+            },
+          }),
+        ),
+        Doc.softLine,
+        Doc.rparen,
+      })
+    }
+  | PStr(list{{pstr_desc: Pstr_value(_recFlag, _bindings)} as si}) =>
+    addParens(printStructureItem(si, cmtTbl))
+  | PStr(structure) => addParens(printStructure(structure, cmtTbl))
+  | PTyp(typ) =>
+    Doc.concat(list{
+      Doc.lparen,
+      Doc.text(":"),
+      Doc.indent(Doc.concat(list{Doc.line, printTypExpr(typ, cmtTbl)})),
+      Doc.softLine,
+      Doc.rparen,
+    })
+  | PPat(pat, optExpr) =>
+    let whenDoc = switch optExpr {
+    | Some(expr) =>
+      Doc.concat(list{Doc.line, Doc.text("if "), printExpressionWithComments(expr, cmtTbl)})
+    | None => Doc.nil
+    }
+
+    Doc.concat(list{
+      Doc.lparen,
+      Doc.indent(
+        Doc.concat(list{Doc.softLine, Doc.text("? "), printPattern(pat, cmtTbl), whenDoc}),
+      ),
+      Doc.softLine,
+      Doc.rparen,
+    })
+  | PSig(signature) =>
+    Doc.concat(list{
+      Doc.lparen,
+      Doc.text(":"),
+      Doc.indent(Doc.concat(list{Doc.line, printSignature(signature, cmtTbl)})),
+      Doc.softLine,
+      Doc.rparen,
+    })
+  }
+
+and printAttribute = ((id, payload): Parsetree.attribute, cmtTbl) =>
+  Doc.group(
+    Doc.concat(list{
+      Doc.text("@"),
+      Doc.text(convertBsExternalAttribute(id.txt)),
+      printPayload(payload, cmtTbl),
+    }),
+  )
+
+and printModExpr = (modExpr, cmtTbl) => {
+  let doc = switch modExpr.pmod_desc {
+  | Pmod_ident(longidentLoc) => printLongidentLocation(longidentLoc, cmtTbl)
+  | Pmod_structure(list{}) =>
+    let shouldBreak = modExpr.pmod_loc.loc_start.pos_lnum < modExpr.pmod_loc.loc_end.pos_lnum
+
+    Doc.breakableGroup(
+      ~forceBreak=shouldBreak,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(Doc.concat(list{Doc.softLine, printCommentsInside(cmtTbl, modExpr.pmod_loc)})),
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  | Pmod_structure(structure) =>
+    Doc.breakableGroup(
+      ~forceBreak=true,
+      Doc.concat(list{
+        Doc.lbrace,
+        Doc.indent(Doc.concat(list{Doc.softLine, printStructure(structure, cmtTbl)})),
+        Doc.softLine,
+        Doc.rbrace,
+      }),
+    )
+  | Pmod_unpack(expr) =>
+    let shouldHug = switch expr.pexp_desc {
+    | Pexp_let(_) => true
+    | Pexp_constraint({pexp_desc: Pexp_let(_)}, {ptyp_desc: Ptyp_package(_packageType)}) => true
+    | _ => false
+    }
+
+    let (expr, moduleConstraint) = switch expr.pexp_desc {
+    | Pexp_constraint(expr, {ptyp_desc: Ptyp_package(packageType), ptyp_loc}) =>
+      let packageDoc = {
+        let doc = printPackageType(~printModuleKeywordAndParens=false, packageType, cmtTbl)
+        printComments(doc, cmtTbl, ptyp_loc)
+      }
+
+      let typeDoc = Doc.group(
+        Doc.concat(list{Doc.text(":"), Doc.indent(Doc.concat(list{Doc.line, packageDoc}))}),
+      )
+      (expr, typeDoc)
+    | _ => (expr, Doc.nil)
+    }
+
+    let unpackDoc = Doc.group(
+      Doc.concat(list{printExpressionWithComments(expr, cmtTbl), moduleConstraint}),
+    )
+    Doc.group(
+      Doc.concat(list{
+        Doc.text("unpack("),
+        if shouldHug {
+          unpackDoc
+        } else {
+          Doc.concat(list{Doc.indent(Doc.concat(list{Doc.softLine, unpackDoc})), Doc.softLine})
+        },
+        Doc.rparen,
+      }),
+    )
+  | Pmod_extension(extension) => printExtension(~atModuleLvl=false, extension, cmtTbl)
+  | Pmod_apply(_) =>
+    let (args, callExpr) = ParsetreeViewer.modExprApply(modExpr)
+    let isUnitSugar = switch args {
+    | list{{pmod_desc: Pmod_structure(list{})}} => true
+    | _ => false
+    }
+
+    let shouldHug = switch args {
+    | list{{pmod_desc: Pmod_structure(_)}} => true
+    | _ => false
+    }
+
+    Doc.group(
+      Doc.concat(list{
+        printModExpr(callExpr, cmtTbl),
+        if isUnitSugar {
+          printModApplyArg(@doesNotRaise List.hd(args), cmtTbl)
+        } else {
+          Doc.concat(list{
+            Doc.lparen,
+            if shouldHug {
+              printModApplyArg(@doesNotRaise List.hd(args), cmtTbl)
+            } else {
+              Doc.indent(
+                Doc.concat(list{
+                  Doc.softLine,
+                  Doc.join(
+                    ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+                    List.map(modArg => printModApplyArg(modArg, cmtTbl), args),
+                  ),
+                }),
+              )
+            },
+            if !shouldHug {
+              Doc.concat(list{Doc.trailingComma, Doc.softLine})
+            } else {
+              Doc.nil
+            },
+            Doc.rparen,
+          })
+        },
+      }),
+    )
+  | Pmod_constraint(modExpr, modType) =>
+    Doc.concat(list{printModExpr(modExpr, cmtTbl), Doc.text(": "), printModType(modType, cmtTbl)})
+  | Pmod_functor(_) => printModFunctor(modExpr, cmtTbl)
+  }
+
+  printComments(doc, cmtTbl, modExpr.pmod_loc)
+}
+
+and printModFunctor = (modExpr, cmtTbl) => {
+  let (parameters, returnModExpr) = ParsetreeViewer.modExprFunctor(modExpr)
+  /* let shouldInline = match returnModExpr.pmod_desc with */
+  /* | Pmod_structure _ | Pmod_ident _ -> true */
+  /* | Pmod_constraint ({pmod_desc = Pmod_structure _}, _) -> true */
+  /* | _ -> false */
+  /* in */
+  let (returnConstraint, returnModExpr) = switch returnModExpr.pmod_desc {
+  | Pmod_constraint(modExpr, modType) =>
+    let constraintDoc = {
+      let doc = printModType(modType, cmtTbl)
+      if Parens.modExprFunctorConstraint(modType) {
+        addParens(doc)
+      } else {
+        doc
+      }
+    }
+
+    let modConstraint = Doc.concat(list{Doc.text(": "), constraintDoc})
+    (modConstraint, printModExpr(modExpr, cmtTbl))
+  | _ => (Doc.nil, printModExpr(returnModExpr, cmtTbl))
+  }
+
+  let parametersDoc = switch parameters {
+  | list{(attrs, {txt: "*"}, None)} =>
+    Doc.group(Doc.concat(list{printAttributes(attrs, cmtTbl), Doc.text("()")}))
+  | list{(list{}, {txt: lbl}, None)} => Doc.text(lbl)
+  | parameters =>
+    Doc.group(
+      Doc.concat(list{
+        Doc.lparen,
+        Doc.indent(
+          Doc.concat(list{
+            Doc.softLine,
+            Doc.join(
+              ~sep=Doc.concat(list{Doc.comma, Doc.line}),
+              List.map(param => printModFunctorParam(param, cmtTbl), parameters),
+            ),
+          }),
+        ),
+        Doc.trailingComma,
+        Doc.softLine,
+        Doc.rparen,
+      }),
+    )
+  }
+
+  Doc.group(Doc.concat(list{parametersDoc, returnConstraint, Doc.text(" => "), returnModExpr}))
+}
+
+and printModFunctorParam = ((attrs, lbl, optModType), cmtTbl) => {
+  let cmtLoc = switch optModType {
+  | None => lbl.Asttypes.loc
+  | Some(modType) => {
+      ...lbl.loc,
+      loc_end: modType.Parsetree.pmty_loc.loc_end,
+    }
+  }
+
+  let attrs = printAttributes(attrs, cmtTbl)
+  let lblDoc = {
+    let doc = if lbl.txt == "*" {
+      Doc.text("()")
+    } else {
+      Doc.text(lbl.txt)
+    }
+    printComments(doc, cmtTbl, lbl.loc)
+  }
+
+  let doc = Doc.group(
+    Doc.concat(list{
+      attrs,
+      lblDoc,
+      switch optModType {
+      | None => Doc.nil
+      | Some(modType) => Doc.concat(list{Doc.text(": "), printModType(modType, cmtTbl)})
+      },
+    }),
+  )
+  printComments(doc, cmtTbl, cmtLoc)
+}
+
+and printModApplyArg = (modExpr, cmtTbl) =>
+  switch modExpr.pmod_desc {
+  | Pmod_structure(list{}) => Doc.text("()")
+  | _ => printModExpr(modExpr, cmtTbl)
+  }
+
+and printExceptionDef = (constr: Parsetree.extension_constructor, cmtTbl) => {
+  let kind = switch constr.pext_kind {
+  | Pext_rebind(longident) =>
+    Doc.indent(
+      Doc.concat(list{Doc.text(" ="), Doc.line, printLongidentLocation(longident, cmtTbl)}),
+    )
+  | Pext_decl(Pcstr_tuple(list{}), None) => Doc.nil
+  | Pext_decl(args, gadt) =>
+    let gadtDoc = switch gadt {
+    | Some(typ) => Doc.concat(list{Doc.text(": "), printTypExpr(typ, cmtTbl)})
+    | None => Doc.nil
+    }
+
+    Doc.concat(list{printConstructorArguments(~indent=false, args, cmtTbl), gadtDoc})
+  }
+
+  let name = printComments(Doc.text(constr.pext_name.txt), cmtTbl, constr.pext_name.loc)
+
+  let doc = Doc.group(
+    Doc.concat(list{
+      printAttributes(constr.pext_attributes, cmtTbl),
+      Doc.text("exception "),
+      name,
+      kind,
+    }),
+  )
+  printComments(doc, cmtTbl, constr.pext_loc)
+}
+
+and printExtensionConstructor = (constr: Parsetree.extension_constructor, cmtTbl, i) => {
+  let attrs = printAttributes(constr.pext_attributes, cmtTbl)
+  let bar = if i > 0 {
+    Doc.text("| ")
+  } else {
+    Doc.ifBreaks(Doc.text("| "), Doc.nil)
+  }
+
+  let kind = switch constr.pext_kind {
+  | Pext_rebind(longident) =>
+    Doc.indent(
+      Doc.concat(list{Doc.text(" ="), Doc.line, printLongidentLocation(longident, cmtTbl)}),
+    )
+  | Pext_decl(Pcstr_tuple(list{}), None) => Doc.nil
+  | Pext_decl(args, gadt) =>
+    let gadtDoc = switch gadt {
+    | Some(typ) => Doc.concat(list{Doc.text(": "), printTypExpr(typ, cmtTbl)})
+    | None => Doc.nil
+    }
+
+    Doc.concat(list{printConstructorArguments(~indent=false, args, cmtTbl), gadtDoc})
+  }
+
+  let name = printComments(Doc.text(constr.pext_name.txt), cmtTbl, constr.pext_name.loc)
+
+  Doc.concat(list{bar, Doc.group(Doc.concat(list{attrs, name, kind}))})
+}
+
+let printImplementation = (~width, s: Parsetree.structure, ~comments) => {
+  let cmtTbl = CommentTable.make()
+  CommentTable.walkStructure(s, cmtTbl, comments)
+  /* CommentTable.log cmtTbl; */
+  let doc = printStructure(s, cmtTbl)
+  /* Doc.debug doc; */
+  Doc.toString(~width, doc) ++ "\n"
+}
+
+let printInterface = (~width, s: Parsetree.signature, ~comments) => {
+  let cmtTbl = CommentTable.make()
+  CommentTable.walkSignature(s, cmtTbl, comments)
+  Doc.toString(~width, printSignature(s, cmtTbl)) ++ "\n"
+}
+
diff --git a/analysis/examples/larger-project/src/res_reporting.js b/analysis/examples/larger-project/src/res_reporting.js
new file mode 100644
index 0000000000..35b4650c02
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_reporting.js
@@ -0,0 +1,13 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+var Token;
+
+var Grammar;
+
+export {
+  Token ,
+  Grammar ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_reporting.res b/analysis/examples/larger-project/src/res_reporting.res
new file mode 100644
index 0000000000..45fd87e27a
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_reporting.res
@@ -0,0 +1,13 @@
+module Token = Res_token
+module Grammar = Res_grammar
+
+type problem =
+  | @live Unexpected(Token.t)
+  | @live Expected({token: Token.t, pos: Lexing.position, context: option<Grammar.t>})
+  | @live Message(string)
+  | @live Uident
+  | @live Lident
+  | @live Unbalanced(Token.t)
+
+type parseError = (Lexing.position, problem)
+
diff --git a/analysis/examples/larger-project/src/res_scanner.js b/analysis/examples/larger-project/src/res_scanner.js
new file mode 100644
index 0000000000..e4f255306e
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_scanner.js
@@ -0,0 +1,1502 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as P from "./P.js";
+import * as Caml from "rescript/lib/es6/caml.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as $$Buffer from "rescript/lib/es6/buffer.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Res_utf8 from "./res_utf8.js";
+import * as Res_token from "./res_token.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Res_comment from "./res_comment.js";
+import * as Res_diagnostics from "./res_diagnostics.js";
+
+function setDiamondMode(scanner) {
+  scanner.mode = {
+    hd: /* Diamond */1,
+    tl: scanner.mode
+  };
+  
+}
+
+function setJsxMode(scanner) {
+  scanner.mode = {
+    hd: /* Jsx */0,
+    tl: scanner.mode
+  };
+  
+}
+
+function popMode(scanner, mode) {
+  var match = scanner.mode;
+  if (match && match.hd === mode) {
+    scanner.mode = match.tl;
+    return ;
+  }
+  
+}
+
+function inDiamondMode(scanner) {
+  var match = scanner.mode;
+  if (match && match.hd) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function inJsxMode(scanner) {
+  var match = scanner.mode;
+  if (match && !match.hd) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function position(scanner) {
+  return {
+          pos_fname: scanner.filename,
+          pos_lnum: scanner.lnum,
+          pos_bol: scanner.lineOffset,
+          pos_cnum: scanner.offset
+        };
+}
+
+function _printDebug(startPos, endPos, scanner, token) {
+  Pervasives.print_string(scanner.src);
+  Pervasives.print_string($$String.make(startPos.pos_cnum, /* ' ' */32));
+  P.print_char(/* '^' */94);
+  var n = endPos.pos_cnum - startPos.pos_cnum | 0;
+  if (n !== 0) {
+    if (n !== 1) {
+      Pervasives.print_string($$String.make(n - 2 | 0, /* '-' */45));
+      P.print_char(/* '^' */94);
+    }
+    
+  } else if (token !== /* Eof */26) {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_scanner.res",
+            89,
+            6
+          ],
+          Error: new Error()
+        };
+  }
+  P.print_char(/* ' ' */32);
+  Pervasives.print_string(Res_token.toString(token));
+  P.print_char(/* ' ' */32);
+  Pervasives.print_int(startPos.pos_cnum);
+  P.print_char(/* '-' */45);
+  Pervasives.print_int(endPos.pos_cnum);
+  console.log("");
+  
+}
+
+function next(scanner) {
+  var nextOffset = scanner.offset + 1 | 0;
+  var match = scanner.ch;
+  if (match !== 10) {
+    
+  } else {
+    scanner.lineOffset = nextOffset;
+    scanner.lnum = scanner.lnum + 1 | 0;
+  }
+  if (nextOffset < scanner.src.length) {
+    scanner.offset = nextOffset;
+    scanner.ch = scanner.src.charCodeAt(scanner.offset);
+  } else {
+    scanner.offset = scanner.src.length;
+    scanner.ch = -1;
+  }
+  
+}
+
+function next2(scanner) {
+  next(scanner);
+  return next(scanner);
+}
+
+function next3(scanner) {
+  next(scanner);
+  next(scanner);
+  return next(scanner);
+}
+
+function peek(scanner) {
+  if ((scanner.offset + 1 | 0) < scanner.src.length) {
+    return scanner.src.charCodeAt(scanner.offset + 1 | 0);
+  } else {
+    return -1;
+  }
+}
+
+function peek2(scanner) {
+  if ((scanner.offset + 2 | 0) < scanner.src.length) {
+    return scanner.src.charCodeAt(scanner.offset + 2 | 0);
+  } else {
+    return -1;
+  }
+}
+
+function make(filename, src) {
+  return {
+          filename: filename,
+          src: src,
+          err: (function (param, param$1, param$2) {
+              
+            }),
+          ch: src === "" ? -1 : src.charCodeAt(0),
+          offset: 0,
+          lineOffset: 0,
+          lnum: 1,
+          mode: /* [] */0
+        };
+}
+
+function isWhitespace(ch) {
+  if (ch > 13 || ch < 9) {
+    return ch === 32;
+  } else {
+    return !(ch === 12 || ch === 11);
+  }
+}
+
+function skipWhitespace(scanner) {
+  while(true) {
+    if (!isWhitespace(scanner.ch)) {
+      return ;
+    }
+    next(scanner);
+    continue ;
+  };
+}
+
+function digitValue(ch) {
+  if (ch >= 65) {
+    if (ch >= 97) {
+      if (ch >= 103) {
+        return 16;
+      } else {
+        return (ch - /* 'a' */97 | 0) + 10 | 0;
+      }
+    } else if (ch >= 71) {
+      return 16;
+    } else {
+      return ((ch + 32 | 0) - /* 'a' */97 | 0) + 10 | 0;
+    }
+  } else if (ch > 57 || ch < 48) {
+    return 16;
+  } else {
+    return ch - 48 | 0;
+  }
+}
+
+function skipLowerCaseChars(scanner) {
+  while(true) {
+    var match = scanner.ch;
+    if (match > 122 || match < 97) {
+      return ;
+    }
+    next(scanner);
+    continue ;
+  };
+}
+
+function scanIdentifier(scanner) {
+  var startOff = scanner.offset;
+  var skipGoodChars = function (scanner) {
+    while(true) {
+      var match = scanner.ch;
+      if (match >= 65) {
+        if (match > 96 || match < 91) {
+          if (match >= 123) {
+            return ;
+          }
+          next(scanner);
+          continue ;
+        }
+        if (match !== 95) {
+          return ;
+        }
+        next(scanner);
+        continue ;
+      }
+      if (match >= 48) {
+        if (match >= 58) {
+          return ;
+        }
+        next(scanner);
+        continue ;
+      }
+      if (match !== 39) {
+        return ;
+      }
+      next(scanner);
+      continue ;
+    };
+  };
+  skipGoodChars(scanner);
+  var str = $$String.sub(scanner.src, startOff, scanner.offset - startOff | 0);
+  if (/* '{' */123 === scanner.ch && str === "list") {
+    next(scanner);
+    return Res_token.lookupKeyword("list{");
+  } else {
+    return Res_token.lookupKeyword(str);
+  }
+}
+
+function scanDigits(scanner, base) {
+  if (base <= 10) {
+    while(true) {
+      var match = scanner.ch;
+      if (match >= 58) {
+        if (match !== 95) {
+          return ;
+        }
+        next(scanner);
+        continue ;
+      }
+      if (match < 48) {
+        return ;
+      }
+      next(scanner);
+      continue ;
+    };
+  }
+  while(true) {
+    var match$1 = scanner.ch;
+    if (match$1 >= 71) {
+      if (match$1 >= 97) {
+        if (match$1 >= 103) {
+          return ;
+        }
+        next(scanner);
+        continue ;
+      }
+      if (match$1 !== 95) {
+        return ;
+      }
+      next(scanner);
+      continue ;
+    }
+    if (match$1 >= 58) {
+      if (match$1 < 65) {
+        return ;
+      }
+      next(scanner);
+      continue ;
+    }
+    if (match$1 < 48) {
+      return ;
+    }
+    next(scanner);
+    continue ;
+  };
+}
+
+function scanNumber(scanner) {
+  var startOff = scanner.offset;
+  var match = scanner.ch;
+  var base;
+  if (match !== 48) {
+    base = 10;
+  } else {
+    var match$1 = peek(scanner);
+    if (match$1 >= 89) {
+      if (match$1 !== 98) {
+        if (match$1 !== 111) {
+          if (match$1 !== 120) {
+            next(scanner);
+            base = 8;
+          } else {
+            next(scanner);
+            next(scanner);
+            base = 16;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          base = 8;
+        }
+      } else {
+        next(scanner);
+        next(scanner);
+        base = 2;
+      }
+    } else if (match$1 !== 66) {
+      if (match$1 !== 79) {
+        if (match$1 >= 88) {
+          next(scanner);
+          next(scanner);
+          base = 16;
+        } else {
+          next(scanner);
+          base = 8;
+        }
+      } else {
+        next(scanner);
+        next(scanner);
+        base = 8;
+      }
+    } else {
+      next(scanner);
+      next(scanner);
+      base = 2;
+    }
+  }
+  scanDigits(scanner, base);
+  var isFloat = /* '.' */46 === scanner.ch ? (next(scanner), scanDigits(scanner, base), true) : false;
+  var match$2 = scanner.ch;
+  var isFloat$1;
+  var exit = 0;
+  if (match$2 >= 81) {
+    if (match$2 !== 101 && match$2 !== 112) {
+      isFloat$1 = isFloat;
+    } else {
+      exit = 1;
+    }
+  } else if (match$2 !== 69 && match$2 < 80) {
+    isFloat$1 = isFloat;
+  } else {
+    exit = 1;
+  }
+  if (exit === 1) {
+    var match$3 = peek(scanner);
+    if (match$3 !== 43 && match$3 !== 45) {
+      next(scanner);
+    } else {
+      next(scanner);
+      next(scanner);
+    }
+    scanDigits(scanner, base);
+    isFloat$1 = true;
+  }
+  var literal = $$String.sub(scanner.src, startOff, scanner.offset - startOff | 0);
+  var ch = scanner.ch;
+  var suffix;
+  if (ch > 122 || ch < 103) {
+    if (ch > 90 || ch < 71) {
+      suffix = undefined;
+    } else {
+      next(scanner);
+      suffix = ch;
+    }
+  } else if (ch !== 110) {
+    next(scanner);
+    suffix = ch;
+  } else {
+    var msg = "Unsupported number type (nativeint). Did you mean `" + (literal + "`?");
+    var pos = position(scanner);
+    Curry._3(scanner.err, pos, pos, Res_diagnostics.message(msg));
+    next(scanner);
+    suffix = /* 'n' */110;
+  }
+  if (isFloat$1) {
+    return {
+            TAG: /* Float */2,
+            f: literal,
+            suffix: suffix
+          };
+  } else {
+    return {
+            TAG: /* Int */1,
+            i: literal,
+            suffix: suffix
+          };
+  }
+}
+
+function scanExoticIdentifier(scanner) {
+  next(scanner);
+  var buffer = $$Buffer.create(20);
+  var startPos = position(scanner);
+  var scan = function (_param) {
+    while(true) {
+      var ch = scanner.ch;
+      if (ch > 13 || ch < 10) {
+        if (ch === 34) {
+          return next(scanner);
+        }
+        
+      } else if (!(ch === 12 || ch === 11)) {
+        var endPos = position(scanner);
+        Curry._3(scanner.err, startPos, endPos, Res_diagnostics.message("A quoted identifier can't contain line breaks."));
+        return next(scanner);
+      }
+      if (ch === -1) {
+        var endPos$1 = position(scanner);
+        return Curry._3(scanner.err, startPos, endPos$1, Res_diagnostics.message("Did you forget a \" here?"));
+      }
+      $$Buffer.add_char(buffer, ch);
+      next(scanner);
+      _param = undefined;
+      continue ;
+    };
+  };
+  scan(undefined);
+  return {
+          TAG: /* Lident */4,
+          _0: $$Buffer.contents(buffer)
+        };
+}
+
+function scanStringEscapeSequence(startPos, scanner) {
+  var scan = function (n, base, max) {
+    var loop = function (_n, _x) {
+      while(true) {
+        var x = _x;
+        var n = _n;
+        if (n === 0) {
+          return x;
+        }
+        var d = digitValue(scanner.ch);
+        if (d >= base) {
+          var pos = position(scanner);
+          var msg = scanner.ch === -1 ? "unclosed escape sequence" : "unknown escape sequence";
+          Curry._3(scanner.err, startPos, pos, Res_diagnostics.message(msg));
+          return -1;
+        }
+        next(scanner);
+        _x = Math.imul(x, base) + d | 0;
+        _n = n - 1 | 0;
+        continue ;
+      };
+    };
+    var x = loop(n, 0);
+    if (!(x > max || 55296 <= x && x < 57344)) {
+      return ;
+    }
+    var pos = position(scanner);
+    return Curry._3(scanner.err, startPos, pos, Res_diagnostics.message("escape sequence is invalid unicode code point"));
+  };
+  var match = scanner.ch;
+  if (match >= 48) {
+    if (match < 92) {
+      if (match >= 58) {
+        return ;
+      } else {
+        return scan(3, 10, 255);
+      }
+    }
+    if (match >= 121) {
+      return ;
+    }
+    switch (match) {
+      case 111 :
+          next(scanner);
+          return scan(3, 8, 255);
+      case 92 :
+      case 98 :
+      case 110 :
+      case 114 :
+      case 116 :
+          return next(scanner);
+      case 117 :
+          next(scanner);
+          var match$1 = scanner.ch;
+          if (match$1 !== 123) {
+            return scan(4, 16, Res_utf8.max);
+          }
+          next(scanner);
+          var x = 0;
+          while((function () {
+                  var match = scanner.ch;
+                  if (match > 70 || match < 48) {
+                    return !(match > 102 || match < 97);
+                  } else {
+                    return match > 64 || match < 58;
+                  }
+                })()) {
+            x = (x << 4) + digitValue(scanner.ch) | 0;
+            next(scanner);
+          };
+          var match$2 = scanner.ch;
+          if (match$2 !== 125) {
+            return ;
+          } else {
+            return next(scanner);
+          }
+      case 93 :
+      case 94 :
+      case 95 :
+      case 96 :
+      case 97 :
+      case 99 :
+      case 100 :
+      case 101 :
+      case 102 :
+      case 103 :
+      case 104 :
+      case 105 :
+      case 106 :
+      case 107 :
+      case 108 :
+      case 109 :
+      case 112 :
+      case 113 :
+      case 115 :
+      case 118 :
+      case 119 :
+          return ;
+      case 120 :
+          next(scanner);
+          return scan(2, 16, 255);
+      
+    }
+  } else {
+    switch (match) {
+      case 33 :
+      case 35 :
+      case 36 :
+      case 37 :
+      case 38 :
+          return ;
+      case 32 :
+      case 34 :
+      case 39 :
+          return next(scanner);
+      default:
+        return ;
+    }
+  }
+}
+
+function scanString(scanner) {
+  var startPosWithQuote = position(scanner);
+  next(scanner);
+  var firstCharOffset = scanner.offset;
+  var scan = function (_param) {
+    while(true) {
+      var ch = scanner.ch;
+      if (ch !== 34) {
+        if (ch !== 92) {
+          if (ch === -1) {
+            var endPos = position(scanner);
+            Curry._3(scanner.err, startPosWithQuote, endPos, Res_diagnostics.unclosedString);
+            return $$String.sub(scanner.src, firstCharOffset, scanner.offset - firstCharOffset | 0);
+          }
+          next(scanner);
+          _param = undefined;
+          continue ;
+        }
+        var startPos = position(scanner);
+        next(scanner);
+        scanStringEscapeSequence(startPos, scanner);
+        _param = undefined;
+        continue ;
+      }
+      var lastCharOffset = scanner.offset;
+      next(scanner);
+      return $$String.sub(scanner.src, firstCharOffset, lastCharOffset - firstCharOffset | 0);
+    };
+  };
+  return {
+          TAG: /* String */3,
+          _0: scan(undefined)
+        };
+}
+
+function scanEscape(scanner) {
+  var offset = scanner.offset - 1 | 0;
+  var convertNumber = function (scanner, n, base) {
+    var x = 0;
+    for(var _for = n; _for >= 1; --_for){
+      var d = digitValue(scanner.ch);
+      x = Math.imul(x, base) + d | 0;
+      next(scanner);
+    }
+    var c = x;
+    if (Res_utf8.isValidCodePoint(c)) {
+      return c;
+    } else {
+      return Res_utf8.repl;
+    }
+  };
+  var ch = scanner.ch;
+  var codepoint;
+  if (ch >= 58) {
+    switch (ch) {
+      case 98 :
+          next(scanner);
+          codepoint = /* '\b' */8;
+          break;
+      case 110 :
+          next(scanner);
+          codepoint = /* '\n' */10;
+          break;
+      case 111 :
+          next(scanner);
+          codepoint = convertNumber(scanner, 3, 8);
+          break;
+      case 114 :
+          next(scanner);
+          codepoint = /* '\r' */13;
+          break;
+      case 116 :
+          next(scanner);
+          codepoint = /* '\t' */9;
+          break;
+      case 117 :
+          next(scanner);
+          var match = scanner.ch;
+          if (match !== 123) {
+            codepoint = convertNumber(scanner, 4, 16);
+          } else {
+            next(scanner);
+            var x = 0;
+            while((function () {
+                    var match = scanner.ch;
+                    if (match > 70 || match < 48) {
+                      return !(match > 102 || match < 97);
+                    } else {
+                      return match > 64 || match < 58;
+                    }
+                  })()) {
+              x = (x << 4) + digitValue(scanner.ch) | 0;
+              next(scanner);
+            };
+            var match$1 = scanner.ch;
+            if (match$1 !== 125) {
+              
+            } else {
+              next(scanner);
+            }
+            var c = x;
+            codepoint = Res_utf8.isValidCodePoint(c) ? c : Res_utf8.repl;
+          }
+          break;
+      case 99 :
+      case 100 :
+      case 101 :
+      case 102 :
+      case 103 :
+      case 104 :
+      case 105 :
+      case 106 :
+      case 107 :
+      case 108 :
+      case 109 :
+      case 112 :
+      case 113 :
+      case 115 :
+      case 118 :
+      case 119 :
+          next(scanner);
+          codepoint = ch;
+          break;
+      case 120 :
+          next(scanner);
+          codepoint = convertNumber(scanner, 2, 16);
+          break;
+      default:
+        next(scanner);
+        codepoint = ch;
+    }
+  } else if (ch >= 48) {
+    codepoint = convertNumber(scanner, 3, 10);
+  } else {
+    next(scanner);
+    codepoint = ch;
+  }
+  var contents = $$String.sub(scanner.src, offset, scanner.offset - offset | 0);
+  next(scanner);
+  return {
+          TAG: /* Codepoint */0,
+          c: codepoint,
+          original: contents
+        };
+}
+
+function scanSingleLineComment(scanner) {
+  var startOff = scanner.offset;
+  var startPos = position(scanner);
+  var skip = function (scanner) {
+    while(true) {
+      var ch = scanner.ch;
+      if (ch === 10) {
+        return ;
+      }
+      if (ch === 13) {
+        return ;
+      }
+      if (ch === -1) {
+        return ;
+      }
+      next(scanner);
+      continue ;
+    };
+  };
+  skip(scanner);
+  var endPos = position(scanner);
+  return {
+          TAG: /* Comment */6,
+          _0: Res_comment.makeSingleLineComment({
+                loc_start: startPos,
+                loc_end: endPos,
+                loc_ghost: false
+              }, $$String.sub(scanner.src, startOff, scanner.offset - startOff | 0))
+        };
+}
+
+function scanMultiLineComment(scanner) {
+  var contentStartOff = scanner.offset + 2 | 0;
+  var startPos = position(scanner);
+  var scan = function (_depth) {
+    while(true) {
+      var depth = _depth;
+      var match = scanner.ch;
+      var match$1 = peek(scanner);
+      if (match !== 42) {
+        if (match === 47 && match$1 === 42) {
+          next(scanner);
+          next(scanner);
+          _depth = depth + 1 | 0;
+          continue ;
+        }
+        
+      } else if (match$1 === 47) {
+        next(scanner);
+        next(scanner);
+        if (depth <= 1) {
+          return ;
+        }
+        _depth = depth - 1 | 0;
+        continue ;
+      }
+      if (match === -1) {
+        var endPos = position(scanner);
+        return Curry._3(scanner.err, startPos, endPos, Res_diagnostics.unclosedComment);
+      }
+      next(scanner);
+      continue ;
+    };
+  };
+  scan(0);
+  var length = (scanner.offset - 2 | 0) - contentStartOff | 0;
+  var length$1 = length < 0 ? 0 : length;
+  return {
+          TAG: /* Comment */6,
+          _0: Res_comment.makeMultiLineComment({
+                loc_start: startPos,
+                loc_end: position(scanner),
+                loc_ghost: false
+              }, $$String.sub(scanner.src, contentStartOff, length$1))
+        };
+}
+
+function scanTemplateLiteralToken(scanner) {
+  var startOff = scanner.offset;
+  if (scanner.ch === /* '}' */125) {
+    next(scanner);
+  }
+  var startPos = position(scanner);
+  var scan = function (_param) {
+    while(true) {
+      var ch = scanner.ch;
+      if (ch !== 36) {
+        if (ch !== 92) {
+          if (ch !== 96) {
+            if (ch === -1) {
+              var endPos = position(scanner);
+              Curry._3(scanner.err, startPos, endPos, Res_diagnostics.unclosedTemplate);
+              return {
+                      TAG: /* TemplateTail */7,
+                      _0: $$String.sub(scanner.src, startOff, Caml.caml_int_max((scanner.offset - 1 | 0) - startOff | 0, 0))
+                    };
+            }
+            next(scanner);
+            _param = undefined;
+            continue ;
+          }
+          next(scanner);
+          return {
+                  TAG: /* TemplateTail */7,
+                  _0: $$String.sub(scanner.src, startOff, (scanner.offset - 1 | 0) - startOff | 0)
+                };
+        }
+        var match = peek(scanner);
+        if (match >= 36) {
+          if (match > 95 || match < 37) {
+            if (match >= 97) {
+              next(scanner);
+              _param = undefined;
+              continue ;
+            }
+            next(scanner);
+            next(scanner);
+            _param = undefined;
+            continue ;
+          }
+          if (match !== 92) {
+            next(scanner);
+            _param = undefined;
+            continue ;
+          }
+          next(scanner);
+          next(scanner);
+          _param = undefined;
+          continue ;
+        }
+        if (match !== 10) {
+          if (match !== 13) {
+            next(scanner);
+            _param = undefined;
+            continue ;
+          }
+          next(scanner);
+          next(scanner);
+          _param = undefined;
+          continue ;
+        }
+        next(scanner);
+        next(scanner);
+        _param = undefined;
+        continue ;
+      }
+      var match$1 = peek(scanner);
+      if (match$1 !== 123) {
+        next(scanner);
+        _param = undefined;
+        continue ;
+      }
+      next(scanner);
+      next(scanner);
+      var contents = $$String.sub(scanner.src, startOff, (scanner.offset - 2 | 0) - startOff | 0);
+      return {
+              TAG: /* TemplatePart */8,
+              _0: contents
+            };
+    };
+  };
+  var token = scan(undefined);
+  var endPos = position(scanner);
+  return [
+          startPos,
+          endPos,
+          token
+        ];
+}
+
+function scan(scanner) {
+  skipWhitespace(scanner);
+  var startPos = position(scanner);
+  var ch = scanner.ch;
+  var token;
+  var exit = 0;
+  switch (ch) {
+    case 33 :
+        var match = peek(scanner);
+        var match$1 = peek2(scanner);
+        if (match !== 61) {
+          next(scanner);
+          token = /* Bang */7;
+        } else if (match$1 !== 61) {
+          next(scanner);
+          next(scanner);
+          token = /* BangEqual */70;
+        } else {
+          next3(scanner);
+          token = /* BangEqualEqual */71;
+        }
+        break;
+    case 34 :
+        token = scanString(scanner);
+        break;
+    case 35 :
+        var match$2 = peek(scanner);
+        if (match$2 !== 61) {
+          next(scanner);
+          token = /* Hash */44;
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* HashEqual */45;
+        }
+        break;
+    case 37 :
+        var match$3 = peek(scanner);
+        if (match$3 !== 37) {
+          next(scanner);
+          token = /* Percent */77;
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* PercentPercent */78;
+        }
+        break;
+    case 38 :
+        var match$4 = peek(scanner);
+        if (match$4 !== 38) {
+          next(scanner);
+          token = /* Band */69;
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* Land */67;
+        }
+        break;
+    case 39 :
+        var match$5 = peek(scanner);
+        var match$6 = peek2(scanner);
+        if (match$5 !== 92) {
+          if (match$6 !== 39) {
+            next(scanner);
+            var offset = scanner.offset;
+            var match$7 = Res_utf8.decodeCodePoint(scanner.offset, scanner.src, scanner.src.length);
+            var length = match$7[1];
+            for(var _for = 0; _for < length; ++_for){
+              next(scanner);
+            }
+            if (scanner.ch === /* '\'' */39) {
+              var contents = $$String.sub(scanner.src, offset, length);
+              next(scanner);
+              token = {
+                TAG: /* Codepoint */0,
+                c: match$7[0],
+                original: contents
+              };
+            } else {
+              scanner.ch = match$5;
+              scanner.offset = offset;
+              token = /* SingleQuote */13;
+            }
+          } else {
+            var offset$1 = scanner.offset + 1 | 0;
+            next3(scanner);
+            token = {
+              TAG: /* Codepoint */0,
+              c: match$5,
+              original: $$String.sub(scanner.src, offset$1, 1)
+            };
+          }
+        } else if (match$6 !== 34) {
+          next(scanner);
+          next(scanner);
+          token = scanEscape(scanner);
+        } else {
+          next(scanner);
+          token = /* SingleQuote */13;
+        }
+        break;
+    case 40 :
+        next(scanner);
+        token = /* Lparen */18;
+        break;
+    case 41 :
+        next(scanner);
+        token = /* Rparen */19;
+        break;
+    case 42 :
+        var match$8 = peek(scanner);
+        if (match$8 !== 42) {
+          if (match$8 !== 46) {
+            next(scanner);
+            token = /* Asterisk */31;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* AsteriskDot */32;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* Exponentiation */33;
+        }
+        break;
+    case 43 :
+        var match$9 = peek(scanner);
+        if (match$9 !== 43) {
+          if (match$9 !== 46) {
+            if (match$9 !== 61) {
+              next(scanner);
+              token = /* Plus */36;
+            } else {
+              next(scanner);
+              next(scanner);
+              token = /* PlusEqual */39;
+            }
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* PlusDot */37;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* PlusPlus */38;
+        }
+        break;
+    case 44 :
+        next(scanner);
+        token = /* Comma */25;
+        break;
+    case 45 :
+        var match$10 = peek(scanner);
+        if (match$10 !== 46) {
+          if (match$10 !== 62) {
+            next(scanner);
+            token = /* Minus */34;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* MinusGreater */58;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* MinusDot */35;
+        }
+        break;
+    case 46 :
+        var match$11 = peek(scanner);
+        var match$12 = peek2(scanner);
+        if (match$11 !== 46) {
+          next(scanner);
+          token = /* Dot */4;
+        } else if (match$12 !== 46) {
+          next(scanner);
+          next(scanner);
+          token = /* DotDot */5;
+        } else {
+          next3(scanner);
+          token = /* DotDotDot */6;
+        }
+        break;
+    case 47 :
+        var match$13 = peek(scanner);
+        switch (match$13) {
+          case 42 :
+              token = scanMultiLineComment(scanner);
+              break;
+          case 43 :
+          case 44 :
+          case 45 :
+              next(scanner);
+              token = /* Forwardslash */29;
+              break;
+          case 46 :
+              next(scanner);
+              next(scanner);
+              token = /* ForwardslashDot */30;
+              break;
+          case 47 :
+              next(scanner);
+              next(scanner);
+              token = scanSingleLineComment(scanner);
+              break;
+          default:
+            next(scanner);
+            token = /* Forwardslash */29;
+        }
+        break;
+    case 48 :
+    case 49 :
+    case 50 :
+    case 51 :
+    case 52 :
+    case 53 :
+    case 54 :
+    case 55 :
+    case 56 :
+    case 57 :
+        token = scanNumber(scanner);
+        break;
+    case 58 :
+        var match$14 = peek(scanner);
+        if (match$14 !== 61) {
+          if (match$14 !== 62) {
+            next(scanner);
+            token = /* Colon */24;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* ColonGreaterThan */40;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* ColonEqual */74;
+        }
+        break;
+    case 59 :
+        next(scanner);
+        token = /* Semicolon */8;
+        break;
+    case 60 :
+        if (inJsxMode(scanner)) {
+          next(scanner);
+          skipWhitespace(scanner);
+          var match$15 = scanner.ch;
+          if (match$15 !== 47) {
+            if (match$15 !== 61) {
+              token = /* LessThan */42;
+            } else {
+              next(scanner);
+              token = /* LessEqual */72;
+            }
+          } else {
+            next(scanner);
+            token = /* LessThanSlash */43;
+          }
+        } else {
+          var match$16 = peek(scanner);
+          if (match$16 !== 61) {
+            next(scanner);
+            token = /* LessThan */42;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* LessEqual */72;
+          }
+        }
+        break;
+    case 61 :
+        var match$17 = peek(scanner);
+        var match$18 = peek2(scanner);
+        if (match$17 !== 61) {
+          if (match$17 !== 62) {
+            next(scanner);
+            token = /* Equal */14;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* EqualGreater */57;
+          }
+        } else if (match$18 !== 61) {
+          next(scanner);
+          next(scanner);
+          token = /* EqualEqual */15;
+        } else {
+          next3(scanner);
+          token = /* EqualEqualEqual */16;
+        }
+        break;
+    case 62 :
+        var match$19 = peek(scanner);
+        if (match$19 !== 61 || inDiamondMode(scanner)) {
+          next(scanner);
+          token = /* GreaterThan */41;
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* GreaterEqual */73;
+        }
+        break;
+    case 63 :
+        next(scanner);
+        token = /* Question */49;
+        break;
+    case 64 :
+        var match$20 = peek(scanner);
+        if (match$20 !== 64) {
+          next(scanner);
+          token = /* At */75;
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* AtAt */76;
+        }
+        break;
+    case 91 :
+        next(scanner);
+        token = /* Lbracket */20;
+        break;
+    case 92 :
+        next(scanner);
+        token = scanExoticIdentifier(scanner);
+        break;
+    case 93 :
+        next(scanner);
+        token = /* Rbracket */21;
+        break;
+    case 36 :
+    case 94 :
+        exit = 1;
+        break;
+    case 95 :
+        var match$21 = peek(scanner);
+        if (match$21 >= 91) {
+          if (match$21 >= 97) {
+            if (match$21 >= 123) {
+              next(scanner);
+              token = /* Underscore */12;
+            } else {
+              token = scanIdentifier(scanner);
+            }
+          } else if (match$21 !== 95) {
+            next(scanner);
+            token = /* Underscore */12;
+          } else {
+            token = scanIdentifier(scanner);
+          }
+        } else if (match$21 >= 58) {
+          if (match$21 >= 65) {
+            token = scanIdentifier(scanner);
+          } else {
+            next(scanner);
+            token = /* Underscore */12;
+          }
+        } else if (match$21 >= 48) {
+          token = scanIdentifier(scanner);
+        } else {
+          next(scanner);
+          token = /* Underscore */12;
+        }
+        break;
+    case 96 :
+        next(scanner);
+        token = /* Backtick */80;
+        break;
+    case 65 :
+    case 66 :
+    case 67 :
+    case 68 :
+    case 69 :
+    case 70 :
+    case 71 :
+    case 72 :
+    case 73 :
+    case 74 :
+    case 75 :
+    case 76 :
+    case 77 :
+    case 78 :
+    case 79 :
+    case 80 :
+    case 81 :
+    case 82 :
+    case 83 :
+    case 84 :
+    case 85 :
+    case 86 :
+    case 87 :
+    case 88 :
+    case 89 :
+    case 90 :
+    case 97 :
+    case 98 :
+    case 99 :
+    case 100 :
+    case 101 :
+    case 102 :
+    case 103 :
+    case 104 :
+    case 105 :
+    case 106 :
+    case 107 :
+    case 108 :
+    case 109 :
+    case 110 :
+    case 111 :
+    case 112 :
+    case 113 :
+    case 114 :
+    case 115 :
+    case 116 :
+    case 117 :
+    case 118 :
+    case 119 :
+    case 120 :
+    case 121 :
+    case 122 :
+        token = scanIdentifier(scanner);
+        break;
+    case 123 :
+        next(scanner);
+        token = /* Lbrace */22;
+        break;
+    case 124 :
+        var match$22 = peek(scanner);
+        if (match$22 !== 62) {
+          if (match$22 !== 124) {
+            next(scanner);
+            token = /* Bar */17;
+          } else {
+            next(scanner);
+            next(scanner);
+            token = /* Lor */68;
+          }
+        } else {
+          next(scanner);
+          next(scanner);
+          token = /* BarGreater */81;
+        }
+        break;
+    case 125 :
+        next(scanner);
+        token = /* Rbrace */23;
+        break;
+    case 126 :
+        next(scanner);
+        token = /* Tilde */48;
+        break;
+    default:
+      exit = 1;
+  }
+  if (exit === 1) {
+    next(scanner);
+    if (ch === -1) {
+      token = /* Eof */26;
+    } else {
+      var endPos = position(scanner);
+      Curry._3(scanner.err, startPos, endPos, Res_diagnostics.unknownUchar(ch));
+      token = scan(scanner)[2];
+    }
+  }
+  var endPos$1 = position(scanner);
+  return [
+          startPos,
+          endPos$1,
+          token
+        ];
+}
+
+function reconsiderLessThan(scanner) {
+  skipWhitespace(scanner);
+  if (scanner.ch === /* '/' */47) {
+    next(scanner);
+    return /* LessThanSlash */43;
+  } else {
+    return /* LessThan */42;
+  }
+}
+
+function isBinaryOp(src, startCnum, endCnum) {
+  if (startCnum === 0) {
+    return false;
+  }
+  if (endCnum < 0) {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_scanner.res",
+            989,
+            4
+          ],
+          Error: new Error()
+        };
+  }
+  if (!(startCnum > 0 && startCnum < src.length)) {
+    throw {
+          RE_EXN_ID: "Assert_failure",
+          _1: [
+            "res_scanner.res",
+            990,
+            4
+          ],
+          Error: new Error()
+        };
+  }
+  var leftOk = isWhitespace(src.charCodeAt(startCnum - 1 | 0));
+  var rightOk = endCnum >= src.length || isWhitespace(src.charCodeAt(endCnum));
+  if (leftOk) {
+    return rightOk;
+  } else {
+    return false;
+  }
+}
+
+function tryAdvanceQuotedString(scanner) {
+  var scanContents = function (tag) {
+    while(true) {
+      var ch = scanner.ch;
+      if (ch !== 124) {
+        if (ch === -1) {
+          return ;
+        }
+        next(scanner);
+        continue ;
+      }
+      next(scanner);
+      var match = scanner.ch;
+      if (match >= 123) {
+        if (match === 125) {
+          return next(scanner);
+        }
+        continue ;
+      }
+      if (match >= 97) {
+        var startOff = scanner.offset;
+        skipLowerCaseChars(scanner);
+        var suffix = $$String.sub(scanner.src, startOff, scanner.offset - startOff | 0);
+        if (tag === suffix) {
+          if (scanner.ch === /* '}' */125) {
+            return next(scanner);
+          }
+          continue ;
+        }
+        continue ;
+      }
+      continue ;
+    };
+  };
+  var match = scanner.ch;
+  if (match >= 123) {
+    if (match !== 124) {
+      return ;
+    } else {
+      return scanContents("");
+    }
+  }
+  if (match < 97) {
+    return ;
+  }
+  var startOff = scanner.offset;
+  skipLowerCaseChars(scanner);
+  var tag = $$String.sub(scanner.src, startOff, scanner.offset - startOff | 0);
+  if (scanner.ch === /* '|' */124) {
+    return scanContents(tag);
+  }
+  
+}
+
+var Diagnostics;
+
+var Token;
+
+var $$Comment;
+
+var hackyEOFChar = -1;
+
+export {
+  Diagnostics ,
+  Token ,
+  $$Comment ,
+  hackyEOFChar ,
+  setDiamondMode ,
+  setJsxMode ,
+  popMode ,
+  inDiamondMode ,
+  inJsxMode ,
+  position ,
+  _printDebug ,
+  next ,
+  next2 ,
+  next3 ,
+  peek ,
+  peek2 ,
+  make ,
+  isWhitespace ,
+  skipWhitespace ,
+  digitValue ,
+  skipLowerCaseChars ,
+  scanIdentifier ,
+  scanDigits ,
+  scanNumber ,
+  scanExoticIdentifier ,
+  scanStringEscapeSequence ,
+  scanString ,
+  scanEscape ,
+  scanSingleLineComment ,
+  scanMultiLineComment ,
+  scanTemplateLiteralToken ,
+  scan ,
+  reconsiderLessThan ,
+  isBinaryOp ,
+  tryAdvanceQuotedString ,
+  
+}
+/* P Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_scanner.res b/analysis/examples/larger-project/src/res_scanner.res
new file mode 100644
index 0000000000..6b3b8ce34b
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_scanner.res
@@ -0,0 +1,1040 @@
+open P
+
+module Diagnostics = Res_diagnostics
+module Token = Res_token
+module Comment = Res_comment
+
+type mode = Jsx | Diamond
+
+/* We hide the implementation detail of the scanner reading character. Our char
+will also contain the special -1 value to indicate end-of-file. This isn't
+ideal; we should clean this up */
+let hackyEOFChar = Char.unsafe_chr(-1)
+type charEncoding = Char.t
+
+type t = {
+  filename: string,
+  src: string,
+  mutable err: (~startPos: Lexing.position, ~endPos: Lexing.position, Diagnostics.category) => unit,
+  mutable ch: charEncoding /* current character */,
+  mutable offset: int /* character offset */,
+  mutable lineOffset: int /* current line offset */,
+  mutable lnum: int /* current line number */,
+  mutable mode: list<mode>,
+}
+
+let setDiamondMode = scanner => scanner.mode = list{Diamond, ...scanner.mode}
+
+let setJsxMode = scanner => scanner.mode = list{Jsx, ...scanner.mode}
+
+let popMode = (scanner, mode) =>
+  switch scanner.mode {
+  | list{m, ...ms} if m == mode => scanner.mode = ms
+  | _ => ()
+  }
+
+let inDiamondMode = scanner =>
+  switch scanner.mode {
+  | list{Diamond, ..._} => true
+  | _ => false
+  }
+
+let inJsxMode = scanner =>
+  switch scanner.mode {
+  | list{Jsx, ..._} => true
+  | _ => false
+  }
+
+let position = scanner => {
+  open Lexing
+  {
+    pos_fname: scanner.filename,
+    /* line number */
+    pos_lnum: scanner.lnum,
+    /* offset of the beginning of the line (number
+     of characters between the beginning of the scanner and the beginning
+     of the line) */
+    pos_bol: scanner.lineOffset,
+    /* [pos_cnum] is the offset of the position (number of
+     characters between the beginning of the scanner and the position). */
+    pos_cnum: scanner.offset,
+  }
+}
+
+/* Small debugging util
+❯ echo 'let msg = "hello"' | ./lib/rescript.exe
+let msg = "hello"
+^-^ let 0-3
+let msg = "hello"
+    ^-^ msg 4-7
+let msg = "hello"
+        ^ = 8-9
+let msg = "hello"
+          ^-----^ string "hello" 10-17
+let msg = "hello"
+                  ^ eof 18-18
+let msg = "hello"
+*/
+@live
+let _printDebug = (~startPos, ~endPos, scanner, token) => {
+  open Lexing
+  print_string(scanner.src)
+  print_string((@doesNotRaise String.make)(startPos.pos_cnum, ' '))
+  print_char('^')
+  switch endPos.pos_cnum - startPos.pos_cnum {
+  | 0 =>
+    if token == Token.Eof {
+      ()
+    } else {
+      assert false
+    }
+  | 1 => ()
+  | n =>
+    print_string((@doesNotRaise String.make)(n - 2, '-'))
+    print_char('^')
+  }
+  print_char(' ')
+  print_string(Res_token.toString(token))
+  print_char(' ')
+  print_int(startPos.pos_cnum)
+  print_char('-')
+  print_int(endPos.pos_cnum)
+  print_endline("")
+}
+
+let next = scanner => {
+  let nextOffset = scanner.offset + 1
+  switch scanner.ch {
+  | '\n' =>
+    scanner.lineOffset = nextOffset
+    scanner.lnum = scanner.lnum + 1
+  /* What about CRLF (\r + \n) on windows?
+   * \r\n will always be terminated by a \n
+   * -> we can just bump the line count on \n */
+  | _ => ()
+  }
+  if nextOffset < String.length(scanner.src) {
+    scanner.offset = nextOffset
+    scanner.ch = String.unsafe_get(scanner.src, scanner.offset)
+  } else {
+    scanner.offset = String.length(scanner.src)
+    scanner.ch = hackyEOFChar
+  }
+}
+
+let next2 = scanner => {
+  next(scanner)
+  next(scanner)
+}
+
+let next3 = scanner => {
+  next(scanner)
+  next(scanner)
+  next(scanner)
+}
+
+let peek = scanner =>
+  if scanner.offset + 1 < String.length(scanner.src) {
+    String.unsafe_get(scanner.src, scanner.offset + 1)
+  } else {
+    hackyEOFChar
+  }
+
+let peek2 = scanner =>
+  if scanner.offset + 2 < String.length(scanner.src) {
+    String.unsafe_get(scanner.src, scanner.offset + 2)
+  } else {
+    hackyEOFChar
+  }
+
+let make = (~filename, src) => {
+  filename: filename,
+  src: src,
+  err: (~startPos as _, ~endPos as _, _) => (),
+  ch: if src == "" {
+    hackyEOFChar
+  } else {
+    String.unsafe_get(src, 0)
+  },
+  offset: 0,
+  lineOffset: 0,
+  lnum: 1,
+  mode: list{},
+}
+
+/* generic helpers */
+
+let isWhitespace = ch =>
+  switch ch {
+  | ' ' | '\t' | '\n' | '\r' => true
+  | _ => false
+  }
+
+let rec skipWhitespace = scanner =>
+  if isWhitespace(scanner.ch) {
+    next(scanner)
+    skipWhitespace(scanner)
+  }
+
+let digitValue = ch =>
+  switch ch {
+  | '0' .. '9' => Char.code(ch) - 48
+  | 'a' .. 'f' => Char.code(ch) - Char.code('a') + 10
+  | 'A' .. 'F' => Char.code(ch) + 32 - Char.code('a') + 10
+  | _ => 16
+  } /* larger than any legal value */
+
+let rec skipLowerCaseChars = scanner =>
+  switch scanner.ch {
+  | 'a' .. 'z' =>
+    next(scanner)
+    skipLowerCaseChars(scanner)
+  | _ => ()
+  }
+
+/* scanning helpers */
+
+let scanIdentifier = scanner => {
+  let startOff = scanner.offset
+  let rec skipGoodChars = scanner =>
+    switch scanner.ch {
+    | 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '\'' =>
+      next(scanner)
+      skipGoodChars(scanner)
+    | _ => ()
+    }
+
+  skipGoodChars(scanner)
+  let str = (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - startOff)
+  if '{' === scanner.ch && str == "list" {
+    next(scanner)
+    /* TODO: this isn't great */
+    Token.lookupKeyword("list{")
+  } else {
+    Token.lookupKeyword(str)
+  }
+}
+
+let scanDigits = (scanner, ~base) =>
+  if base <= 10 {
+    let rec loop = scanner =>
+      switch scanner.ch {
+      | '0' .. '9' | '_' =>
+        next(scanner)
+        loop(scanner)
+      | _ => ()
+      }
+    loop(scanner)
+  } else {
+    let rec loop = scanner =>
+      switch scanner.ch {
+      /* hex */
+      | '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' | '_' =>
+        next(scanner)
+        loop(scanner)
+      | _ => ()
+      }
+    loop(scanner)
+  }
+
+/* float: (0…9) { 0…9∣ _ } [. { 0…9∣ _ }] [(e∣ E) [+∣ -] (0…9) { 0…9∣ _ }] */
+let scanNumber = scanner => {
+  let startOff = scanner.offset
+
+  /* integer part */
+  let base = switch scanner.ch {
+  | '0' =>
+    switch peek(scanner) {
+    | 'x' | 'X' =>
+      next2(scanner)
+      16
+    | 'o' | 'O' =>
+      next2(scanner)
+      8
+    | 'b' | 'B' =>
+      next2(scanner)
+      2
+    | _ =>
+      next(scanner)
+      8
+    }
+  | _ => 10
+  }
+
+  scanDigits(scanner, ~base)
+
+  /*  */
+  let isFloat = if '.' === scanner.ch {
+    next(scanner)
+    scanDigits(scanner, ~base)
+    true
+  } else {
+    false
+  }
+
+  /* exponent part */
+  let isFloat = switch scanner.ch {
+  | 'e' | 'E' | 'p' | 'P' =>
+    switch peek(scanner) {
+    | '+' | '-' => next2(scanner)
+    | _ => next(scanner)
+    }
+    scanDigits(scanner, ~base)
+    true
+  | _ => isFloat
+  }
+
+  let literal = (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - startOff)
+
+  /* suffix */
+  let suffix = switch scanner.ch {
+  | 'n' =>
+    let msg = "Unsupported number type (nativeint). Did you mean `" ++ (literal ++ "`?")
+
+    let pos = position(scanner)
+    scanner.err(~startPos=pos, ~endPos=pos, Diagnostics.message(msg))
+    next(scanner)
+    Some('n')
+  | ('g' .. 'z' | 'G' .. 'Z') as ch =>
+    next(scanner)
+    Some(ch)
+  | _ => None
+  }
+
+  if isFloat {
+    Token.Float({f: literal, suffix: suffix})
+  } else {
+    Token.Int({i: literal, suffix: suffix})
+  }
+}
+
+let scanExoticIdentifier = scanner => {
+  /* TODO: are we disregarding the current char...? Should be a quote */
+  next(scanner)
+  let buffer = Buffer.create(20)
+  let startPos = position(scanner)
+
+  let rec scan = () =>
+    switch scanner.ch {
+    | '"' => next(scanner)
+    | '\n' | '\r' =>
+      /* line break */
+      let endPos = position(scanner)
+      scanner.err(
+        ~startPos,
+        ~endPos,
+        Diagnostics.message("A quoted identifier can't contain line breaks."),
+      )
+      next(scanner)
+    | ch if ch === hackyEOFChar =>
+      let endPos = position(scanner)
+      scanner.err(~startPos, ~endPos, Diagnostics.message("Did you forget a \" here?"))
+    | ch =>
+      Buffer.add_char(buffer, ch)
+      next(scanner)
+      scan()
+    }
+
+  scan()
+  /* TODO: do we really need to create a new buffer instead of substring once? */
+  Token.Lident(Buffer.contents(buffer))
+}
+
+let scanStringEscapeSequence = (~startPos, scanner) => {
+  let scan = (~n, ~base, ~max) => {
+    let rec loop = (n, x) =>
+      if n === 0 {
+        x
+      } else {
+        let d = digitValue(scanner.ch)
+        if d >= base {
+          let pos = position(scanner)
+          let msg = if scanner.ch === hackyEOFChar {
+            "unclosed escape sequence"
+          } else {
+            "unknown escape sequence"
+          }
+
+          scanner.err(~startPos, ~endPos=pos, Diagnostics.message(msg))
+          -1
+        } else {
+          let () = next(scanner)
+          loop(n - 1, x * base + d)
+        }
+      }
+
+    let x = loop(n, 0)
+    if x > max || (0xD800 <= x && x < 0xE000) {
+      let pos = position(scanner)
+      let msg = "escape sequence is invalid unicode code point"
+      scanner.err(~startPos, ~endPos=pos, Diagnostics.message(msg))
+    }
+  }
+
+  switch scanner.ch {
+  /* \ already consumed */
+  | 'n' | 't' | 'b' | 'r' | '\\' | ' ' | '\'' | '"' => next(scanner)
+  | '0' .. '9' =>
+    /* decimal */
+    scan(~n=3, ~base=10, ~max=255)
+  | 'o' =>
+    /* octal */
+    next(scanner)
+    scan(~n=3, ~base=8, ~max=255)
+  | 'x' =>
+    /* hex */
+    next(scanner)
+    scan(~n=2, ~base=16, ~max=255)
+  | 'u' =>
+    next(scanner)
+    switch scanner.ch {
+    | '{' =>
+      /* unicode code point escape sequence: '\u{7A}', one or more hex digits */
+      next(scanner)
+      let x = ref(0)
+      while (
+        switch scanner.ch {
+        | '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' => true
+        | _ => false
+        }
+      ) {
+        x := x.contents * 16 + digitValue(scanner.ch)
+        next(scanner)
+      }
+      /* consume '}' in '\u{7A}' */
+      switch scanner.ch {
+      | '}' => next(scanner)
+      | _ => ()
+      }
+    | _ => scan(~n=4, ~base=16, ~max=Res_utf8.max)
+    }
+  | _ => /* unknown escape sequence
+     * TODO: we should warn the user here. Let's not make it a hard error for now, for reason compat */
+    /*
+      let pos = position scanner in
+      let msg =
+        if ch == -1 then "unclosed escape sequence"
+        else "unknown escape sequence"
+      in
+      scanner.err ~startPos ~endPos:pos (Diagnostics.message msg)
+ */
+    ()
+  }
+}
+
+let scanString = scanner => {
+  /* assumption: we've just matched a quote */
+
+  let startPosWithQuote = position(scanner)
+  next(scanner)
+  let firstCharOffset = scanner.offset
+
+  let rec scan = () =>
+    switch scanner.ch {
+    | '"' =>
+      let lastCharOffset = scanner.offset
+      next(scanner)
+      (@doesNotRaise String.sub)(scanner.src, firstCharOffset, lastCharOffset - firstCharOffset)
+    | '\\' =>
+      let startPos = position(scanner)
+      next(scanner)
+      scanStringEscapeSequence(~startPos, scanner)
+      scan()
+    | ch if ch === hackyEOFChar =>
+      let endPos = position(scanner)
+      scanner.err(~startPos=startPosWithQuote, ~endPos, Diagnostics.unclosedString)
+      (@doesNotRaise String.sub)(scanner.src, firstCharOffset, scanner.offset - firstCharOffset)
+    | _ =>
+      next(scanner)
+      scan()
+    }
+
+  Token.String(scan())
+}
+
+let scanEscape = scanner => {
+  /* '\' consumed */
+  let offset = scanner.offset - 1
+  let convertNumber = (scanner, ~n, ~base) => {
+    let x = ref(0)
+    for _ in n downto 1 {
+      let d = digitValue(scanner.ch)
+      x := x.contents * base + d
+      next(scanner)
+    }
+    let c = x.contents
+    if Res_utf8.isValidCodePoint(c) {
+      Char.unsafe_chr(c)
+    } else {
+      Char.unsafe_chr(Res_utf8.repl)
+    }
+  }
+
+  let codepoint = switch scanner.ch {
+  | '0' .. '9' => convertNumber(scanner, ~n=3, ~base=10)
+  | 'b' =>
+    next(scanner)
+    '\b'
+  | 'n' =>
+    next(scanner)
+    '\n'
+  | 'r' =>
+    next(scanner)
+    '\r'
+  | 't' =>
+    next(scanner)
+    '\t'
+  | 'x' =>
+    next(scanner)
+    convertNumber(scanner, ~n=2, ~base=16)
+  | 'o' =>
+    next(scanner)
+    convertNumber(scanner, ~n=3, ~base=8)
+  | 'u' =>
+    next(scanner)
+    switch scanner.ch {
+    | '{' =>
+      /* unicode code point escape sequence: '\u{7A}', one or more hex digits */
+      next(scanner)
+      let x = ref(0)
+      while (
+        switch scanner.ch {
+        | '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' => true
+        | _ => false
+        }
+      ) {
+        x := x.contents * 16 + digitValue(scanner.ch)
+        next(scanner)
+      }
+      /* consume '}' in '\u{7A}' */
+      switch scanner.ch {
+      | '}' => next(scanner)
+      | _ => ()
+      }
+      let c = x.contents
+      if Res_utf8.isValidCodePoint(c) {
+        Char.unsafe_chr(c)
+      } else {
+        Char.unsafe_chr(Res_utf8.repl)
+      }
+    | _ =>
+      /* unicode escape sequence: '\u007A', exactly 4 hex digits */
+      convertNumber(scanner, ~n=4, ~base=16)
+    }
+  | ch =>
+    next(scanner)
+    ch
+  }
+
+  let contents = (@doesNotRaise String.sub)(scanner.src, offset, scanner.offset - offset)
+  next(scanner) /* Consume \' */
+  /* TODO: do we know it's \' ? */
+  Token.Codepoint({c: codepoint, original: contents})
+}
+
+let scanSingleLineComment = scanner => {
+  let startOff = scanner.offset
+  let startPos = position(scanner)
+  let rec skip = scanner =>
+    switch scanner.ch {
+    | '\n' | '\r' => ()
+    | ch if ch === hackyEOFChar => ()
+    | _ =>
+      next(scanner)
+      skip(scanner)
+    }
+
+  skip(scanner)
+  let endPos = position(scanner)
+  Token.Comment(
+    Comment.makeSingleLineComment(
+      ~loc={
+        open Location
+        {loc_start: startPos, loc_end: endPos, loc_ghost: false}
+      },
+      (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - startOff),
+    ),
+  )
+}
+
+let scanMultiLineComment = scanner => {
+  /* assumption: we're only ever using this helper in `scan` after detecting a comment */
+  let contentStartOff = scanner.offset + 2
+  let startPos = position(scanner)
+  let rec scan = (~depth) =>
+    /* invariant: depth > 0 right after this match. See assumption */
+    switch (scanner.ch, peek(scanner)) {
+    | ('/', '*') =>
+      next2(scanner)
+      scan(~depth=depth + 1)
+    | ('*', '/') =>
+      next2(scanner)
+      if depth > 1 {
+        scan(~depth=depth - 1)
+      }
+    | (ch, _) if ch === hackyEOFChar =>
+      let endPos = position(scanner)
+      scanner.err(~startPos, ~endPos, Diagnostics.unclosedComment)
+    | _ =>
+      next(scanner)
+      scan(~depth)
+    }
+
+  scan(~depth=0)
+  let length = scanner.offset - 2 - contentStartOff
+  let length = if length < 0 /* in case of EOF */ {
+    0
+  } else {
+    length
+  }
+  Token.Comment(
+    Comment.makeMultiLineComment(
+      ~loc={
+        open Location
+        {loc_start: startPos, loc_end: position(scanner), loc_ghost: false}
+      },
+      (@doesNotRaise String.sub)(scanner.src, contentStartOff, length),
+    ),
+  )
+}
+
+let scanTemplateLiteralToken = scanner => {
+  let startOff = scanner.offset
+
+  /* if starting } here, consume it */
+  if scanner.ch === '}' {
+    next(scanner)
+  }
+
+  let startPos = position(scanner)
+
+  let rec scan = () =>
+    switch scanner.ch {
+    | '`' =>
+      next(scanner)
+      Token.TemplateTail(
+        (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - 1 - startOff),
+      )
+    | '$' =>
+      switch peek(scanner) {
+      | '{' =>
+        next2(scanner)
+        let contents = (@doesNotRaise String.sub)(
+          scanner.src,
+          startOff,
+          scanner.offset - 2 - startOff,
+        )
+
+        Token.TemplatePart(contents)
+      | _ =>
+        next(scanner)
+        scan()
+      }
+    | '\\' =>
+      switch peek(scanner) {
+      | '`'
+      | '\\'
+      | '$'
+      | '\n'
+      | '\r' =>
+        /* line break */
+        next2(scanner)
+        scan()
+      | _ =>
+        next(scanner)
+        scan()
+      }
+    | ch if ch == hackyEOFChar =>
+      let endPos = position(scanner)
+      scanner.err(~startPos, ~endPos, Diagnostics.unclosedTemplate)
+      Token.TemplateTail(
+        (@doesNotRaise String.sub)(scanner.src, startOff, max(scanner.offset - 1 - startOff, 0)),
+      )
+    | _ =>
+      next(scanner)
+      scan()
+    }
+
+  let token = scan()
+  let endPos = position(scanner)
+  (startPos, endPos, token)
+}
+
+let rec scan = scanner => {
+  skipWhitespace(scanner)
+  let startPos = position(scanner)
+
+  let token = switch scanner.ch {
+  /* peeking 0 char */
+  | 'A' .. 'Z' | 'a' .. 'z' => scanIdentifier(scanner)
+  | '0' .. '9' => scanNumber(scanner)
+  | '`' =>
+    next(scanner)
+    Token.Backtick
+  | '~' =>
+    next(scanner)
+    Token.Tilde
+  | '?' =>
+    next(scanner)
+    Token.Question
+  | ';' =>
+    next(scanner)
+    Token.Semicolon
+  | '(' =>
+    next(scanner)
+    Token.Lparen
+  | ')' =>
+    next(scanner)
+    Token.Rparen
+  | '[' =>
+    next(scanner)
+    Token.Lbracket
+  | ']' =>
+    next(scanner)
+    Token.Rbracket
+  | '{' =>
+    next(scanner)
+    Token.Lbrace
+  | '}' =>
+    next(scanner)
+    Token.Rbrace
+  | ',' =>
+    next(scanner)
+    Token.Comma
+  | '"' => scanString(scanner)
+
+  /* peeking 1 char */
+  | '_' =>
+    switch peek(scanner) {
+    | 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' => scanIdentifier(scanner)
+    | _ =>
+      next(scanner)
+      Token.Underscore
+    }
+  | '#' =>
+    switch peek(scanner) {
+    | '=' =>
+      next2(scanner)
+      Token.HashEqual
+    | _ =>
+      next(scanner)
+      Token.Hash
+    }
+  | '*' =>
+    switch peek(scanner) {
+    | '*' =>
+      next2(scanner)
+      Token.Exponentiation
+    | '.' =>
+      next2(scanner)
+      Token.AsteriskDot
+    | _ =>
+      next(scanner)
+      Token.Asterisk
+    }
+  | '@' =>
+    switch peek(scanner) {
+    | '@' =>
+      next2(scanner)
+      Token.AtAt
+    | _ =>
+      next(scanner)
+      Token.At
+    }
+  | '%' =>
+    switch peek(scanner) {
+    | '%' =>
+      next2(scanner)
+      Token.PercentPercent
+    | _ =>
+      next(scanner)
+      Token.Percent
+    }
+  | '|' =>
+    switch peek(scanner) {
+    | '|' =>
+      next2(scanner)
+      Token.Lor
+    | '>' =>
+      next2(scanner)
+      Token.BarGreater
+    | _ =>
+      next(scanner)
+      Token.Bar
+    }
+  | '&' =>
+    switch peek(scanner) {
+    | '&' =>
+      next2(scanner)
+      Token.Land
+    | _ =>
+      next(scanner)
+      Token.Band
+    }
+  | ':' =>
+    switch peek(scanner) {
+    | '=' =>
+      next2(scanner)
+      Token.ColonEqual
+    | '>' =>
+      next2(scanner)
+      Token.ColonGreaterThan
+    | _ =>
+      next(scanner)
+      Token.Colon
+    }
+  | '\\' =>
+    next(scanner)
+    scanExoticIdentifier(scanner)
+  | '/' =>
+    switch peek(scanner) {
+    | '/' =>
+      next2(scanner)
+      scanSingleLineComment(scanner)
+    | '*' => scanMultiLineComment(scanner)
+    | '.' =>
+      next2(scanner)
+      Token.ForwardslashDot
+    | _ =>
+      next(scanner)
+      Token.Forwardslash
+    }
+  | '-' =>
+    switch peek(scanner) {
+    | '.' =>
+      next2(scanner)
+      Token.MinusDot
+    | '>' =>
+      next2(scanner)
+      Token.MinusGreater
+    | _ =>
+      next(scanner)
+      Token.Minus
+    }
+  | '+' =>
+    switch peek(scanner) {
+    | '.' =>
+      next2(scanner)
+      Token.PlusDot
+    | '+' =>
+      next2(scanner)
+      Token.PlusPlus
+    | '=' =>
+      next2(scanner)
+      Token.PlusEqual
+    | _ =>
+      next(scanner)
+      Token.Plus
+    }
+  | '>' =>
+    switch peek(scanner) {
+    | '=' if !inDiamondMode(scanner) =>
+      next2(scanner)
+      Token.GreaterEqual
+    | _ =>
+      next(scanner)
+      Token.GreaterThan
+    }
+  | '<' if !inJsxMode(scanner) =>
+    switch peek(scanner) {
+    | '=' =>
+      next2(scanner)
+      Token.LessEqual
+    | _ =>
+      next(scanner)
+      Token.LessThan
+    }
+  /* special handling for JSX < */
+  | '<' =>
+    /* Imagine the following: <div><
+     * < indicates the start of a new jsx-element, the parser expects
+     * the name of a new element after the <
+     * Example: <div> <div
+     * But what if we have a / here: example </ in  <div></div>
+     * This signals a closing element. To simulate the two-token lookahead,
+     * the </ is emitted as a single new token LessThanSlash */
+    next(scanner)
+    skipWhitespace(scanner)
+    switch scanner.ch {
+    | '/' =>
+      next(scanner)
+      Token.LessThanSlash
+    | '=' =>
+      next(scanner)
+      Token.LessEqual
+    | _ => Token.LessThan
+    }
+
+  /* peeking 2 chars */
+  | '.' =>
+    switch (peek(scanner), peek2(scanner)) {
+    | ('.', '.') =>
+      next3(scanner)
+      Token.DotDotDot
+    | ('.', _) =>
+      next2(scanner)
+      Token.DotDot
+    | _ =>
+      next(scanner)
+      Token.Dot
+    }
+  | '\'' =>
+    switch (peek(scanner), peek2(scanner)) {
+    | ('\\', '"') =>
+      /* careful with this one! We're next-ing _once_ (not twice),
+       then relying on matching on the quote */
+      next(scanner)
+      SingleQuote
+    | ('\\', _) =>
+      next2(scanner)
+      scanEscape(scanner)
+    | (ch, '\'') =>
+      let offset = scanner.offset + 1
+      next3(scanner)
+      Token.Codepoint({c: ch, original: (@doesNotRaise String.sub)(scanner.src, offset, 1)})
+    | (ch, _) =>
+      next(scanner)
+      let offset = scanner.offset
+      let (codepoint, length) = Res_utf8.decodeCodePoint(
+        scanner.offset,
+        scanner.src,
+        String.length(scanner.src),
+      )
+      for _ in 0 to length - 1 {
+        next(scanner)
+      }
+      if scanner.ch == '\'' {
+        let contents = (@doesNotRaise String.sub)(scanner.src, offset, length)
+        next(scanner)
+        Token.Codepoint({c: Obj.magic(codepoint), original: contents})
+      } else {
+        scanner.ch = ch
+        scanner.offset = offset
+        SingleQuote
+      }
+    }
+  | '!' =>
+    switch (peek(scanner), peek2(scanner)) {
+    | ('=', '=') =>
+      next3(scanner)
+      Token.BangEqualEqual
+    | ('=', _) =>
+      next2(scanner)
+      Token.BangEqual
+    | _ =>
+      next(scanner)
+      Token.Bang
+    }
+  | '=' =>
+    switch (peek(scanner), peek2(scanner)) {
+    | ('=', '=') =>
+      next3(scanner)
+      Token.EqualEqualEqual
+    | ('=', _) =>
+      next2(scanner)
+      Token.EqualEqual
+    | ('>', _) =>
+      next2(scanner)
+      Token.EqualGreater
+    | _ =>
+      next(scanner)
+      Token.Equal
+    }
+
+  /* special cases */
+  | ch if ch === hackyEOFChar =>
+    next(scanner)
+    Token.Eof
+  | ch =>
+    /* if we arrive here, we're dealing with an unknown character,
+     * report the error and continue scanning… */
+    next(scanner)
+    let endPos = position(scanner)
+    scanner.err(~startPos, ~endPos, Diagnostics.unknownUchar(ch))
+    let (_, _, token) = scan(scanner)
+    token
+  }
+
+  let endPos = position(scanner)
+  /* _printDebug ~startPos ~endPos scanner token; */
+  (startPos, endPos, token)
+}
+
+/* misc helpers used elsewhere */
+
+/* Imagine: <div> <Navbar /> <
+ * is `<` the start of a jsx-child? <div …
+ * or is it the start of a closing tag?  </div>
+ * reconsiderLessThan peeks at the next token and
+ * determines the correct token to disambiguate */
+let reconsiderLessThan = scanner => {
+  /* < consumed */
+  skipWhitespace(scanner)
+  if scanner.ch === '/' {
+    let () = next(scanner)
+    Token.LessThanSlash
+  } else {
+    Token.LessThan
+  }
+}
+
+/* If an operator has whitespace around both sides, it's a binary operator */
+/* TODO: this helper seems out of place */
+let isBinaryOp = (src, startCnum, endCnum) =>
+  if startCnum === 0 {
+    false
+  } else {
+    /* we're gonna put some assertions and invariant checks here because this is
+     used outside of the scanner's normal invariant assumptions */
+    assert (endCnum >= 0)
+    assert (startCnum > 0 && startCnum < String.length(src))
+    let leftOk = isWhitespace(String.unsafe_get(src, startCnum - 1))
+    /* we need some stronger confidence that endCnum is ok */
+    let rightOk = endCnum >= String.length(src) || isWhitespace(String.unsafe_get(src, endCnum))
+    leftOk && rightOk
+  }
+
+/* Assume `{` consumed, advances the scanner towards the ends of Reason quoted strings. (for conversion)
+ * In {| foo bar |} the scanner will be advanced until after the `|}` */
+let tryAdvanceQuotedString = scanner => {
+  let rec scanContents = tag =>
+    switch scanner.ch {
+    | '|' =>
+      next(scanner)
+      switch scanner.ch {
+      | 'a' .. 'z' =>
+        let startOff = scanner.offset
+        skipLowerCaseChars(scanner)
+        let suffix = (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - startOff)
+        if tag == suffix {
+          if scanner.ch == '}' {
+            next(scanner)
+          } else {
+            scanContents(tag)
+          }
+        } else {
+          scanContents(tag)
+        }
+      | '}' => next(scanner)
+      | _ => scanContents(tag)
+      }
+    | ch if ch === hackyEOFChar => /* TODO: why is this place checking EOF and not others? */
+      ()
+    | _ =>
+      next(scanner)
+      scanContents(tag)
+    }
+
+  switch scanner.ch {
+  | 'a' .. 'z' =>
+    let startOff = scanner.offset
+    skipLowerCaseChars(scanner)
+    let tag = (@doesNotRaise String.sub)(scanner.src, startOff, scanner.offset - startOff)
+    if scanner.ch == '|' {
+      scanContents(tag)
+    }
+  | '|' => scanContents("")
+  | _ => ()
+  }
+}
+
diff --git a/analysis/examples/larger-project/src/res_token.js b/analysis/examples/larger-project/src/res_token.js
new file mode 100644
index 0000000000..bb9bfcc0b6
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_token.js
@@ -0,0 +1,442 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Res_comment from "./res_comment.js";
+import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
+
+function precedence(x) {
+  if (typeof x !== "number") {
+    return 0;
+  }
+  if (x < 17) {
+    if (x !== 4) {
+      if (x >= 14) {
+        return 4;
+      } else {
+        return 0;
+      }
+    } else {
+      return 9;
+    }
+  }
+  if (x >= 46) {
+    if (x < 58) {
+      return 0;
+    }
+    switch (x) {
+      case /* MinusGreater */58 :
+          return 8;
+      case /* Land */67 :
+          return 3;
+      case /* Lor */68 :
+          return 2;
+      case /* ColonEqual */74 :
+          return 1;
+      case /* BangEqual */70 :
+      case /* BangEqualEqual */71 :
+      case /* LessEqual */72 :
+      case /* GreaterEqual */73 :
+      case /* BarGreater */81 :
+          return 4;
+      case /* External */59 :
+      case /* Typ */60 :
+      case /* Private */61 :
+      case /* Mutable */62 :
+      case /* Constraint */63 :
+      case /* Include */64 :
+      case /* Module */65 :
+      case /* Of */66 :
+      case /* Band */69 :
+      case /* At */75 :
+      case /* AtAt */76 :
+      case /* Percent */77 :
+      case /* PercentPercent */78 :
+      case /* List */79 :
+      case /* Backtick */80 :
+      case /* Try */82 :
+      case /* Import */83 :
+      case /* Export */84 :
+          return 0;
+      
+    }
+  } else {
+    if (x < 29) {
+      return 0;
+    }
+    switch (x) {
+      case /* Forwardslash */29 :
+      case /* ForwardslashDot */30 :
+      case /* Asterisk */31 :
+      case /* AsteriskDot */32 :
+          return 6;
+      case /* Exponentiation */33 :
+          return 7;
+      case /* Minus */34 :
+      case /* MinusDot */35 :
+      case /* Plus */36 :
+      case /* PlusDot */37 :
+      case /* PlusPlus */38 :
+          return 5;
+      case /* GreaterThan */41 :
+      case /* LessThan */42 :
+          return 4;
+      case /* PlusEqual */39 :
+      case /* ColonGreaterThan */40 :
+      case /* LessThanSlash */43 :
+      case /* Hash */44 :
+          return 0;
+      case /* HashEqual */45 :
+          return 1;
+      
+    }
+  }
+}
+
+function toString(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Open */0 :
+          return "open";
+      case /* True */1 :
+          return "true";
+      case /* False */2 :
+          return "false";
+      case /* As */3 :
+          return "as";
+      case /* Dot */4 :
+          return ".";
+      case /* DotDot */5 :
+          return "..";
+      case /* DotDotDot */6 :
+          return "...";
+      case /* Bang */7 :
+          return "!";
+      case /* Semicolon */8 :
+          return ";";
+      case /* Let */9 :
+          return "let";
+      case /* And */10 :
+          return "and";
+      case /* Rec */11 :
+          return "rec";
+      case /* Underscore */12 :
+          return "_";
+      case /* SingleQuote */13 :
+          return "'";
+      case /* Equal */14 :
+          return "=";
+      case /* EqualEqual */15 :
+          return "==";
+      case /* EqualEqualEqual */16 :
+          return "===";
+      case /* Bar */17 :
+          return "|";
+      case /* Lparen */18 :
+          return "(";
+      case /* Rparen */19 :
+          return ")";
+      case /* Lbracket */20 :
+          return "[";
+      case /* Rbracket */21 :
+          return "]";
+      case /* Lbrace */22 :
+          return "{";
+      case /* Rbrace */23 :
+          return "}";
+      case /* Colon */24 :
+          return ":";
+      case /* Comma */25 :
+          return ",";
+      case /* Eof */26 :
+          return "eof";
+      case /* Exception */27 :
+          return "exception";
+      case /* Backslash */28 :
+          return "\\";
+      case /* Forwardslash */29 :
+          return "/";
+      case /* ForwardslashDot */30 :
+          return "/.";
+      case /* Asterisk */31 :
+          return "*";
+      case /* AsteriskDot */32 :
+          return "*.";
+      case /* Exponentiation */33 :
+          return "**";
+      case /* Minus */34 :
+          return "-";
+      case /* MinusDot */35 :
+          return "-.";
+      case /* Plus */36 :
+          return "+";
+      case /* PlusDot */37 :
+          return "+.";
+      case /* PlusPlus */38 :
+          return "++";
+      case /* PlusEqual */39 :
+          return "+=";
+      case /* ColonGreaterThan */40 :
+          return ":>";
+      case /* GreaterThan */41 :
+          return ">";
+      case /* LessThan */42 :
+          return "<";
+      case /* LessThanSlash */43 :
+          return "</";
+      case /* Hash */44 :
+          return "#";
+      case /* HashEqual */45 :
+          return "#=";
+      case /* Assert */46 :
+          return "assert";
+      case /* Lazy */47 :
+          return "lazy";
+      case /* Tilde */48 :
+          return "tilde";
+      case /* Question */49 :
+          return "?";
+      case /* If */50 :
+          return "if";
+      case /* Else */51 :
+          return "else";
+      case /* For */52 :
+          return "for";
+      case /* In */53 :
+          return "in";
+      case /* While */54 :
+          return "while";
+      case /* Switch */55 :
+          return "switch";
+      case /* When */56 :
+          return "when";
+      case /* EqualGreater */57 :
+          return "=>";
+      case /* MinusGreater */58 :
+          return "->";
+      case /* External */59 :
+          return "external";
+      case /* Typ */60 :
+          return "type";
+      case /* Private */61 :
+          return "private";
+      case /* Mutable */62 :
+          return "mutable";
+      case /* Constraint */63 :
+          return "constraint";
+      case /* Include */64 :
+          return "include";
+      case /* Module */65 :
+          return "module";
+      case /* Of */66 :
+          return "of";
+      case /* Land */67 :
+          return "&&";
+      case /* Lor */68 :
+          return "||";
+      case /* Band */69 :
+          return "&";
+      case /* BangEqual */70 :
+          return "!=";
+      case /* BangEqualEqual */71 :
+          return "!==";
+      case /* LessEqual */72 :
+          return "<=";
+      case /* GreaterEqual */73 :
+          return ">=";
+      case /* ColonEqual */74 :
+          return ":=";
+      case /* At */75 :
+          return "@";
+      case /* AtAt */76 :
+          return "@@";
+      case /* Percent */77 :
+          return "%";
+      case /* PercentPercent */78 :
+          return "%%";
+      case /* List */79 :
+          return "list{";
+      case /* Backtick */80 :
+          return "`";
+      case /* BarGreater */81 :
+          return "|>";
+      case /* Try */82 :
+          return "try";
+      case /* Import */83 :
+          return "import";
+      case /* Export */84 :
+          return "export";
+      
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Codepoint */0 :
+          return "codepoint '" + (x.original + "'");
+      case /* Int */1 :
+          return "int " + x.i;
+      case /* Float */2 :
+          return "Float: " + x.f;
+      case /* String */3 :
+          return "string \"" + (x._0 + "\"");
+      case /* Lident */4 :
+      case /* Uident */5 :
+          return x._0;
+      case /* Comment */6 :
+          return "Comment" + Res_comment.toString(x._0);
+      case /* TemplateTail */7 :
+          return "TemplateTail(" + (x._0 + ")");
+      case /* TemplatePart */8 :
+          return x._0 + "${";
+      
+    }
+  }
+}
+
+function keywordTable(x) {
+  switch (x) {
+    case "and" :
+        return /* And */10;
+    case "as" :
+        return /* As */3;
+    case "assert" :
+        return /* Assert */46;
+    case "constraint" :
+        return /* Constraint */63;
+    case "else" :
+        return /* Else */51;
+    case "exception" :
+        return /* Exception */27;
+    case "export" :
+        return /* Export */84;
+    case "external" :
+        return /* External */59;
+    case "false" :
+        return /* False */2;
+    case "for" :
+        return /* For */52;
+    case "if" :
+        return /* If */50;
+    case "import" :
+        return /* Import */83;
+    case "in" :
+        return /* In */53;
+    case "include" :
+        return /* Include */64;
+    case "lazy" :
+        return /* Lazy */47;
+    case "let" :
+        return /* Let */9;
+    case "list{" :
+        return /* List */79;
+    case "module" :
+        return /* Module */65;
+    case "mutable" :
+        return /* Mutable */62;
+    case "of" :
+        return /* Of */66;
+    case "open" :
+        return /* Open */0;
+    case "private" :
+        return /* Private */61;
+    case "rec" :
+        return /* Rec */11;
+    case "switch" :
+        return /* Switch */55;
+    case "true" :
+        return /* True */1;
+    case "try" :
+        return /* Try */82;
+    case "type" :
+        return /* Typ */60;
+    case "when" :
+        return /* When */56;
+    case "while" :
+        return /* While */54;
+    default:
+      throw {
+            RE_EXN_ID: "Not_found",
+            Error: new Error()
+          };
+  }
+}
+
+function isKeyword(x) {
+  if (typeof x === "number") {
+    if (x >= 48) {
+      if (x >= 69) {
+        if (x !== 79) {
+          return x >= 82;
+        } else {
+          return true;
+        }
+      } else if (x >= 57) {
+        return x >= 59;
+      } else {
+        return x >= 50;
+      }
+    } else if (x > 45 || x < 12) {
+      return x > 8 || x < 4;
+    } else {
+      return x === 27;
+    }
+  } else {
+    return false;
+  }
+}
+
+function lookupKeyword(str) {
+  try {
+    return keywordTable(str);
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      var match = Caml_string.get(str, 0);
+      if (match > 90 || match < 65) {
+        return {
+                TAG: /* Lident */4,
+                _0: str
+              };
+      } else {
+        return {
+                TAG: /* Uident */5,
+                _0: str
+              };
+      }
+    }
+    throw exn;
+  }
+}
+
+function isKeywordTxt(str) {
+  try {
+    keywordTable(str);
+    return true;
+  }
+  catch (raw_exn){
+    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
+    if (exn.RE_EXN_ID === "Not_found") {
+      return false;
+    }
+    throw exn;
+  }
+}
+
+var $$Comment;
+
+var $$catch = {
+  TAG: /* Lident */4,
+  _0: "catch"
+};
+
+export {
+  $$Comment ,
+  precedence ,
+  toString ,
+  keywordTable ,
+  isKeyword ,
+  lookupKeyword ,
+  isKeywordTxt ,
+  $$catch ,
+  
+}
+/* Res_comment Not a pure module */
diff --git a/analysis/examples/larger-project/src/res_token.res b/analysis/examples/larger-project/src/res_token.res
new file mode 100644
index 0000000000..8528ea46f2
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_token.res
@@ -0,0 +1,309 @@
+module Comment = Res_comment
+
+type t =
+  | Open
+  | True
+  | False
+  | Codepoint({c: char, original: string})
+  | Int({i: string, suffix: option<char>})
+  | Float({f: string, suffix: option<char>})
+  | String(string)
+  | Lident(string)
+  | Uident(string)
+  | As
+  | Dot
+  | DotDot
+  | DotDotDot
+  | Bang
+  | Semicolon
+  | Let
+  | And
+  | Rec
+  | Underscore
+  | SingleQuote
+  | Equal
+  | EqualEqual
+  | EqualEqualEqual
+  | Bar
+  | Lparen
+  | Rparen
+  | Lbracket
+  | Rbracket
+  | Lbrace
+  | Rbrace
+  | Colon
+  | Comma
+  | Eof
+  | Exception
+  | @live Backslash
+  | Forwardslash
+  | ForwardslashDot
+  | Asterisk
+  | AsteriskDot
+  | Exponentiation
+  | Minus
+  | MinusDot
+  | Plus
+  | PlusDot
+  | PlusPlus
+  | PlusEqual
+  | ColonGreaterThan
+  | GreaterThan
+  | LessThan
+  | LessThanSlash
+  | Hash
+  | HashEqual
+  | Assert
+  | Lazy
+  | Tilde
+  | Question
+  | If
+  | Else
+  | For
+  | In
+  | While
+  | Switch
+  | When
+  | EqualGreater
+  | MinusGreater
+  | External
+  | Typ
+  | Private
+  | Mutable
+  | Constraint
+  | Include
+  | Module
+  | Of
+  | Land
+  | Lor
+  | Band /* Bitwise and: & */
+  | BangEqual
+  | BangEqualEqual
+  | LessEqual
+  | GreaterEqual
+  | ColonEqual
+  | At
+  | AtAt
+  | Percent
+  | PercentPercent
+  | Comment(Comment.t)
+  | List
+  | TemplateTail(string)
+  | TemplatePart(string)
+  | Backtick
+  | BarGreater
+  | Try
+  | Import
+  | Export
+
+let precedence = x =>
+  switch x {
+  | HashEqual | ColonEqual => 1
+  | Lor => 2
+  | Land => 3
+  | Equal
+  | EqualEqual
+  | EqualEqualEqual
+  | LessThan
+  | GreaterThan
+  | BangEqual
+  | BangEqualEqual
+  | LessEqual
+  | GreaterEqual
+  | BarGreater => 4
+  | Plus | PlusDot | Minus | MinusDot | PlusPlus => 5
+  | Asterisk | AsteriskDot | Forwardslash | ForwardslashDot => 6
+  | Exponentiation => 7
+  | MinusGreater => 8
+  | Dot => 9
+  | _ => 0
+  }
+
+let toString = x =>
+  switch x {
+  | Open => "open"
+  | True => "true"
+  | False => "false"
+  | Codepoint({original}) => "codepoint '" ++ (original ++ "'")
+  | String(s) => "string \"" ++ (s ++ "\"")
+  | Lident(str) => str
+  | Uident(str) => str
+  | Dot => "."
+  | DotDot => ".."
+  | DotDotDot => "..."
+  | Int({i}) => "int " ++ i
+  | Float({f}) => "Float: " ++ f
+  | Bang => "!"
+  | Semicolon => ";"
+  | Let => "let"
+  | And => "and"
+  | Rec => "rec"
+  | Underscore => "_"
+  | SingleQuote => "'"
+  | Equal => "="
+  | EqualEqual => "=="
+  | EqualEqualEqual => "==="
+  | Eof => "eof"
+  | Bar => "|"
+  | As => "as"
+  | Lparen => "("
+  | Rparen => ")"
+  | Lbracket => "["
+  | Rbracket => "]"
+  | Lbrace => "{"
+  | Rbrace => "}"
+  | ColonGreaterThan => ":>"
+  | Colon => ":"
+  | Comma => ","
+  | Minus => "-"
+  | MinusDot => "-."
+  | Plus => "+"
+  | PlusDot => "+."
+  | PlusPlus => "++"
+  | PlusEqual => "+="
+  | Backslash => "\\"
+  | Forwardslash => "/"
+  | ForwardslashDot => "/."
+  | Exception => "exception"
+  | Hash => "#"
+  | HashEqual => "#="
+  | GreaterThan => ">"
+  | LessThan => "<"
+  | LessThanSlash => "</"
+  | Asterisk => "*"
+  | AsteriskDot => "*."
+  | Exponentiation => "**"
+  | Assert => "assert"
+  | Lazy => "lazy"
+  | Tilde => "tilde"
+  | Question => "?"
+  | If => "if"
+  | Else => "else"
+  | For => "for"
+  | In => "in"
+  | While => "while"
+  | Switch => "switch"
+  | When => "when"
+  | EqualGreater => "=>"
+  | MinusGreater => "->"
+  | External => "external"
+  | Typ => "type"
+  | Private => "private"
+  | Constraint => "constraint"
+  | Mutable => "mutable"
+  | Include => "include"
+  | Module => "module"
+  | Of => "of"
+  | Lor => "||"
+  | Band => "&"
+  | Land => "&&"
+  | BangEqual => "!="
+  | BangEqualEqual => "!=="
+  | GreaterEqual => ">="
+  | LessEqual => "<="
+  | ColonEqual => ":="
+  | At => "@"
+  | AtAt => "@@"
+  | Percent => "%"
+  | PercentPercent => "%%"
+  | Comment(c) => "Comment" ++ Comment.toString(c)
+  | List => "list{"
+  | TemplatePart(text) => text ++ "${"
+  | TemplateTail(text) => "TemplateTail(" ++ (text ++ ")")
+  | Backtick => "`"
+  | BarGreater => "|>"
+  | Try => "try"
+  | Import => "import"
+  | Export => "export"
+  }
+
+@raises(Not_found)
+let keywordTable = x =>
+  switch x {
+  | "and" => And
+  | "as" => As
+  | "assert" => Assert
+  | "constraint" => Constraint
+  | "else" => Else
+  | "exception" => Exception
+  | "export" => Export
+  | "external" => External
+  | "false" => False
+  | "for" => For
+  | "if" => If
+  | "import" => Import
+  | "in" => In
+  | "include" => Include
+  | "lazy" => Lazy
+  | "let" => Let
+  | "list{" => List
+  | "module" => Module
+  | "mutable" => Mutable
+  | "of" => Of
+  | "open" => Open
+  | "private" => Private
+  | "rec" => Rec
+  | "switch" => Switch
+  | "true" => True
+  | "try" => Try
+  | "type" => Typ
+  | "when" => When
+  | "while" => While
+  | _ => raise(Not_found)
+  }
+
+let isKeyword = x =>
+  switch x {
+  | And
+  | As
+  | Assert
+  | Constraint
+  | Else
+  | Exception
+  | Export
+  | External
+  | False
+  | For
+  | If
+  | Import
+  | In
+  | Include
+  | Land
+  | Lazy
+  | Let
+  | List
+  | Lor
+  | Module
+  | Mutable
+  | Of
+  | Open
+  | Private
+  | Rec
+  | Switch
+  | True
+  | Try
+  | Typ
+  | When
+  | While => true
+  | _ => false
+  }
+
+let lookupKeyword = str =>
+  try keywordTable(str) catch {
+  | Not_found =>
+    switch @doesNotRaise
+    String.get(str, 0) {
+    | 'A' .. 'Z' => Uident(str)
+    | _ => Lident(str)
+    }
+  }
+
+let isKeywordTxt = str =>
+  try {
+    let _ = keywordTable(str)
+    true
+  } catch {
+  | Not_found => false
+  }
+
+let catch = Lident("catch")
diff --git a/analysis/examples/larger-project/src/res_utf8.js b/analysis/examples/larger-project/src/res_utf8.js
new file mode 100644
index 0000000000..e826abb1ab
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_utf8.js
@@ -0,0 +1,481 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Bytes from "rescript/lib/es6/bytes.js";
+
+var categoryTable = [
+  {
+    low: -1,
+    high: -1,
+    size: 1
+  },
+  {
+    low: 1,
+    high: -1,
+    size: 1
+  },
+  {
+    low: 128,
+    high: 191,
+    size: 2
+  },
+  {
+    low: 160,
+    high: 191,
+    size: 3
+  },
+  {
+    low: 128,
+    high: 191,
+    size: 3
+  },
+  {
+    low: 128,
+    high: 159,
+    size: 3
+  },
+  {
+    low: 144,
+    high: 191,
+    size: 4
+  },
+  {
+    low: 128,
+    high: 191,
+    size: 4
+  },
+  {
+    low: 128,
+    high: 143,
+    size: 4
+  }
+];
+
+var categories = [
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  3,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  5,
+  4,
+  4,
+  6,
+  7,
+  7,
+  7,
+  8,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0
+];
+
+function decodeCodePoint(i, s, len) {
+  if (len < 1) {
+    return [
+            65533,
+            1
+          ];
+  }
+  var first = s.charCodeAt(i);
+  if (first < 128) {
+    return [
+            first,
+            1
+          ];
+  }
+  var index = categories[first];
+  if (index === 0) {
+    return [
+            65533,
+            1
+          ];
+  }
+  var cat = categoryTable[index];
+  if (len < (i + cat.size | 0)) {
+    return [
+            65533,
+            1
+          ];
+  }
+  if (cat.size === 2) {
+    var c1 = s.charCodeAt(i + 1 | 0);
+    if (c1 < cat.low || cat.high < c1) {
+      return [
+              65533,
+              1
+            ];
+    }
+    var i1 = c1 & 63;
+    var i0 = ((first & 31) << 6);
+    var uc = i0 | i1;
+    return [
+            uc,
+            2
+          ];
+  }
+  if (cat.size === 3) {
+    var c1$1 = s.charCodeAt(i + 1 | 0);
+    var c2 = s.charCodeAt(i + 2 | 0);
+    if (c1$1 < cat.low || cat.high < c1$1 || c2 < 128 || 191 < c2) {
+      return [
+              65533,
+              1
+            ];
+    }
+    var i0$1 = ((first & 15) << 12);
+    var i1$1 = ((c1$1 & 63) << 6);
+    var i2 = c2 & 63;
+    var uc$1 = i0$1 | i1$1 | i2;
+    return [
+            uc$1,
+            3
+          ];
+  }
+  var c1$2 = s.charCodeAt(i + 1 | 0);
+  var c2$1 = s.charCodeAt(i + 2 | 0);
+  var c3 = s.charCodeAt(i + 3 | 0);
+  if (c1$2 < cat.low || cat.high < c1$2 || c2$1 < 128 || 191 < c2$1 || c3 < 128 || 191 < c3) {
+    return [
+            65533,
+            1
+          ];
+  }
+  var i1$2 = ((c1$2 & 63) << 12);
+  var i2$1 = ((c2$1 & 63) << 6);
+  var i3 = c3 & 63;
+  var i0$2 = ((first & 7) << 18);
+  var uc$2 = i0$2 | i3 | i2$1 | i1$2;
+  return [
+          uc$2,
+          4
+        ];
+}
+
+function encodeCodePoint(c) {
+  if (c <= 127) {
+    var bytes = [0];
+    bytes[0] = c;
+    return Bytes.unsafe_to_string(bytes);
+  }
+  if (c <= 2047) {
+    var bytes$1 = [
+      0,
+      0
+    ];
+    bytes$1[0] = 192 | (c >>> 6);
+    bytes$1[1] = 128 | c & 63;
+    return Bytes.unsafe_to_string(bytes$1);
+  }
+  if (c <= 65535) {
+    var bytes$2 = [
+      0,
+      0,
+      0
+    ];
+    bytes$2[0] = 224 | (c >>> 12);
+    bytes$2[1] = 128 | (c >>> 6) & 63;
+    bytes$2[2] = 128 | c & 63;
+    return Bytes.unsafe_to_string(bytes$2);
+  }
+  var bytes$3 = [
+    0,
+    0,
+    0,
+    0
+  ];
+  bytes$3[0] = 240 | (c >>> 18);
+  bytes$3[1] = 128 | (c >>> 12) & 63;
+  bytes$3[2] = 128 | (c >>> 6) & 63;
+  bytes$3[3] = 128 | c & 63;
+  return Bytes.unsafe_to_string(bytes$3);
+}
+
+function isValidCodePoint(c) {
+  if (0 <= c && c < 55296) {
+    return true;
+  } else if (57343 < c) {
+    return c <= 1114111;
+  } else {
+    return false;
+  }
+}
+
+var repl = 65533;
+
+var max = 1114111;
+
+var surrogateMin = 55296;
+
+var surrogateMax = 57343;
+
+var h2 = 192;
+
+var h3 = 224;
+
+var h4 = 240;
+
+var cont_mask = 63;
+
+var locb = 128;
+
+var hicb = 191;
+
+export {
+  repl ,
+  max ,
+  surrogateMin ,
+  surrogateMax ,
+  h2 ,
+  h3 ,
+  h4 ,
+  cont_mask ,
+  locb ,
+  hicb ,
+  categoryTable ,
+  categories ,
+  decodeCodePoint ,
+  encodeCodePoint ,
+  isValidCodePoint ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/larger-project/src/res_utf8.res b/analysis/examples/larger-project/src/res_utf8.res
new file mode 100644
index 0000000000..a31cceaffe
--- /dev/null
+++ b/analysis/examples/larger-project/src/res_utf8.res
@@ -0,0 +1,395 @@
+/* https://tools.ietf.org/html/rfc3629#section-10 */
+/* let bom = 0xFEFF */
+
+let repl = 0xFFFD
+
+/* let min = 0x0000 */
+let max = 0x10FFFF
+
+let surrogateMin = 0xD800
+let surrogateMax = 0xDFFF
+
+/*
+ * Char. number range  |        UTF-8 octet sequence
+ *       (hexadecimal)    |              (binary)
+ *    --------------------+---------------------------------------------
+ *    0000 0000-0000 007F | 0xxxxxxx
+ *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+let h2 = 0b1100_0000
+let h3 = 0b1110_0000
+let h4 = 0b1111_0000
+
+let cont_mask = 0b0011_1111
+
+type category = {
+  low: int,
+  high: int,
+  size: int,
+}
+
+let locb = 0b1000_0000
+let hicb = 0b1011_1111
+
+let categoryTable = [
+  /* 0 */ {low: -1, high: -1, size: 1} /* invalid */,
+  /* 1 */ {low: 1, high: -1, size: 1} /* ascii */,
+  /* 2 */ {low: locb, high: hicb, size: 2},
+  /* 3 */ {low: 0xA0, high: hicb, size: 3},
+  /* 4 */ {low: locb, high: hicb, size: 3},
+  /* 5 */ {low: locb, high: 0x9F, size: 3},
+  /* 6 */ {low: 0x90, high: hicb, size: 4},
+  /* 7 */ {low: locb, high: hicb, size: 4},
+  /* 8 */ {low: locb, high: 0x8F, size: 4},
+]
+
+let categories = [
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  /* surrogate range U+D800 - U+DFFFF = 55296 - 917503 */
+  0,
+  0,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  2,
+  3,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  4,
+  5,
+  4,
+  4,
+  6,
+  7,
+  7,
+  7,
+  8,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+  0,
+]
+
+let decodeCodePoint = (i, s, len) =>
+  if len < 1 {
+    (repl, 1)
+  } else {
+    let first = int_of_char(String.unsafe_get(s, i))
+    if first < 128 {
+      (first, 1)
+    } else {
+      let index = Array.unsafe_get(categories, first)
+      if index == 0 {
+        (repl, 1)
+      } else {
+        let cat = Array.unsafe_get(categoryTable, index)
+        if len < i + cat.size {
+          (repl, 1)
+        } else if cat.size === 2 {
+          let c1 = int_of_char(String.unsafe_get(s, i + 1))
+          if c1 < cat.low || cat.high < c1 {
+            (repl, 1)
+          } else {
+            let i1 = land(c1, 0b00111111)
+            let i0 = lsl(land(first, 0b00011111), 6)
+            let uc = lor(i0, i1)
+            (uc, 2)
+          }
+        } else if cat.size === 3 {
+          let c1 = int_of_char(String.unsafe_get(s, i + 1))
+          let c2 = int_of_char(String.unsafe_get(s, i + 2))
+          if c1 < cat.low || (cat.high < c1 || (c2 < locb || hicb < c2)) {
+            (repl, 1)
+          } else {
+            let i0 = lsl(land(first, 0b00001111), 12)
+            let i1 = lsl(land(c1, 0b00111111), 6)
+            let i2 = land(c2, 0b00111111)
+            let uc = lor(lor(i0, i1), i2)
+            (uc, 3)
+          }
+        } else {
+          let c1 = int_of_char(String.unsafe_get(s, i + 1))
+          let c2 = int_of_char(String.unsafe_get(s, i + 2))
+          let c3 = int_of_char(String.unsafe_get(s, i + 3))
+          if (
+            c1 < cat.low ||
+              (cat.high < c1 ||
+              (c2 < locb || (hicb < c2 || (c3 < locb || hicb < c3))))
+          ) {
+            (repl, 1)
+          } else {
+            let i1 = lsl(land(c1, 0x3f), 12)
+            let i2 = lsl(land(c2, 0x3f), 6)
+            let i3 = land(c3, 0x3f)
+            let i0 = lsl(land(first, 0x07), 18)
+            let uc = lor(lor(lor(i0, i3), i2), i1)
+            (uc, 4)
+          }
+        }
+      }
+    }
+  }
+
+let encodeCodePoint = c =>
+  if c <= 127 {
+    let bytes = (@doesNotRaise Bytes.create)(1)
+    Bytes.unsafe_set(bytes, 0, Char.unsafe_chr(c))
+    Bytes.unsafe_to_string(bytes)
+  } else if c <= 2047 {
+    let bytes = (@doesNotRaise Bytes.create)(2)
+    Bytes.unsafe_set(bytes, 0, Char.unsafe_chr(lor(h2, lsr(c, 6))))
+    Bytes.unsafe_set(bytes, 1, Char.unsafe_chr(lor(0b1000_0000, land(c, cont_mask))))
+    Bytes.unsafe_to_string(bytes)
+  } else if c <= 65535 {
+    let bytes = (@doesNotRaise Bytes.create)(3)
+    Bytes.unsafe_set(bytes, 0, Char.unsafe_chr(lor(h3, lsr(c, 12))))
+    Bytes.unsafe_set(bytes, 1, Char.unsafe_chr(lor(0b1000_0000, land(lsr(c, 6), cont_mask))))
+    Bytes.unsafe_set(bytes, 2, Char.unsafe_chr(lor(0b1000_0000, land(c, cont_mask))))
+    Bytes.unsafe_to_string(bytes)
+  } else {
+    /* if c <= max then */
+    let bytes = (@doesNotRaise Bytes.create)(4)
+    Bytes.unsafe_set(bytes, 0, Char.unsafe_chr(lor(h4, lsr(c, 18))))
+    Bytes.unsafe_set(bytes, 1, Char.unsafe_chr(lor(0b1000_0000, land(lsr(c, 12), cont_mask))))
+    Bytes.unsafe_set(bytes, 2, Char.unsafe_chr(lor(0b1000_0000, land(lsr(c, 6), cont_mask))))
+    Bytes.unsafe_set(bytes, 3, Char.unsafe_chr(lor(0b1000_0000, land(c, cont_mask))))
+    Bytes.unsafe_to_string(bytes)
+  }
+
+let isValidCodePoint = c => (0 <= c && c < surrogateMin) || (surrogateMax < c && c <= max)
+
diff --git a/analysis/examples/larger-project/src/syntaxerr.js b/analysis/examples/larger-project/src/syntaxerr.js
new file mode 100644
index 0000000000..67e55646d5
--- /dev/null
+++ b/analysis/examples/larger-project/src/syntaxerr.js
@@ -0,0 +1,76 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Printf from "./printf.js";
+import * as $$Location from "./location.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var $$Error = /* @__PURE__ */Caml_exceptions.create("Syntaxerr.Error");
+
+var Escape_error = /* @__PURE__ */Caml_exceptions.create("Syntaxerr.Escape_error");
+
+function prepare_error(x) {
+  switch (x.TAG | 0) {
+    case /* Unclosed */0 :
+        var closing = x._3;
+        var opening = x._1;
+        return Curry._1($$Location.errorf(x._2, {
+                        hd: Curry._1($$Location.errorf(x._0, undefined, undefined, "This '%s' might be unmatched"), opening),
+                        tl: /* [] */0
+                      }, Curry._2(Printf.sprintf("Syntax error: '%s' expected, the highlighted '%s' might be unmatched"), closing, opening), "Syntax error: '%s' expected"), closing);
+    case /* Expecting */1 :
+        return Curry._1($$Location.errorf(x._0, undefined, undefined, "Syntax error: %s expected."), x._1);
+    case /* Not_expecting */2 :
+        return Curry._1($$Location.errorf(x._0, undefined, undefined, "Syntax error: %s not expected."), x._1);
+    case /* Applicative_path */3 :
+        return $$Location.errorf(x._0, undefined, undefined, "Syntax error: applicative paths of the form F(X).t are not supported when the option -no-app-func is set.");
+    case /* Variable_in_scope */4 :
+        var $$var = x._1;
+        return Curry._2($$Location.errorf(x._0, undefined, undefined, "In this scoped type, variable '%s is reserved for the local type %s."), $$var, $$var);
+    case /* Other */5 :
+        return $$Location.errorf(x._0, undefined, undefined, "Syntax error");
+    case /* Ill_formed_ast */6 :
+        return Curry._1($$Location.errorf(x._0, undefined, undefined, "broken invariant in parsetree: %s"), x._1);
+    case /* Invalid_package_type */7 :
+        return Curry._1($$Location.errorf(x._0, undefined, undefined, "invalid package type: %s"), x._1);
+    
+  }
+}
+
+$$Location.register_error_of_exn(function (x) {
+      if (x.RE_EXN_ID === $$Error) {
+        return prepare_error(x._1);
+      }
+      
+    });
+
+function report_error(ppf, err) {
+  
+}
+
+function location_of_error(x) {
+  return x._0;
+}
+
+function ill_formed_ast(loc, s) {
+  throw {
+        RE_EXN_ID: $$Error,
+        _1: {
+          TAG: /* Ill_formed_ast */6,
+          _0: loc,
+          _1: s
+        },
+        Error: new Error()
+      };
+}
+
+export {
+  $$Error ,
+  Escape_error ,
+  prepare_error ,
+  report_error ,
+  location_of_error ,
+  ill_formed_ast ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/syntaxerr.res b/analysis/examples/larger-project/src/syntaxerr.res
new file mode 100644
index 0000000000..4d5e0426c8
--- /dev/null
+++ b/analysis/examples/larger-project/src/syntaxerr.res
@@ -0,0 +1,90 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/*  */
+/* Copyright 1997 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* Auxiliary type for reporting syntax errors */
+
+type error =
+  | Unclosed(Location.t, string, Location.t, string)
+  | Expecting(Location.t, string)
+  | Not_expecting(Location.t, string)
+  | Applicative_path(Location.t)
+  | Variable_in_scope(Location.t, string)
+  | Other(Location.t)
+  | Ill_formed_ast(Location.t, string)
+  | Invalid_package_type(Location.t, string)
+
+exception Error(error)
+exception Escape_error
+
+let prepare_error = x =>
+  switch x {
+  | Unclosed(opening_loc, opening, closing_loc, closing) =>
+    Location.errorf(
+      ~loc=closing_loc,
+      ~sub=list{Location.errorf(~loc=opening_loc, "This '%s' might be unmatched", opening)},
+      ~if_highlight=Printf.sprintf(
+        "Syntax error: '%s' expected, \
+                           the highlighted '%s' might be unmatched",
+        closing,
+        opening,
+      ),
+      "Syntax error: '%s' expected",
+      closing,
+    )
+
+  | Expecting(loc, nonterm) => Location.errorf(~loc, "Syntax error: %s expected.", nonterm)
+  | Not_expecting(loc, nonterm) => Location.errorf(~loc, "Syntax error: %s not expected.", nonterm)
+  | Applicative_path(loc) =>
+    Location.errorf(
+      ~loc,
+      "Syntax error: applicative paths of the form F(X).t \
+         are not supported when the option -no-app-func is set.",
+    )
+  | Variable_in_scope(loc, var) =>
+    Location.errorf(
+      ~loc,
+      "In this scoped type, variable '%s \
+         is reserved for the local type %s.",
+      var,
+      var,
+    )
+  | Other(loc) => Location.errorf(~loc, "Syntax error")
+  | Ill_formed_ast(loc, s) => Location.errorf(~loc, "broken invariant in parsetree: %s", s)
+  | Invalid_package_type(loc, s) => Location.errorf(~loc, "invalid package type: %s", s)
+  }
+
+let () = Location.register_error_of_exn(x =>
+  switch x {
+  | Error(err) => Some(prepare_error(err))
+  | _ => None
+  }
+)
+
+let report_error = (ppf, err) => ()
+
+let location_of_error = x =>
+  switch x {
+  | Unclosed(l, _, _, _)
+  | Applicative_path(l)
+  | Variable_in_scope(l, _)
+  | Other(l)
+  | Not_expecting(l, _)
+  | Ill_formed_ast(l, _)
+  | Invalid_package_type(l, _)
+  | Expecting(l, _) => l
+  }
+
+let ill_formed_ast = (loc, s) => raise(Error(Ill_formed_ast(loc, s)))
+
diff --git a/analysis/examples/larger-project/src/warnings.js b/analysis/examples/larger-project/src/warnings.js
new file mode 100644
index 0000000000..6265e1ff35
--- /dev/null
+++ b/analysis/examples/larger-project/src/warnings.js
@@ -0,0 +1,1357 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Arg from "rescript/lib/es6/arg.js";
+import * as Caml from "rescript/lib/es6/caml.js";
+import * as Char from "rescript/lib/es6/char.js";
+import * as List from "rescript/lib/es6/list.js";
+import * as Misc from "./misc.js";
+import * as $$Array from "rescript/lib/es6/array.js";
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Printf from "./printf.js";
+import * as $$String from "rescript/lib/es6/string.js";
+import * as Caml_array from "rescript/lib/es6/caml_array.js";
+import * as Pervasives from "rescript/lib/es6/pervasives.js";
+import * as Caml_string from "rescript/lib/es6/caml_string.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+function number(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Comment_start */0 :
+          return 1;
+      case /* Comment_not_end */1 :
+          return 2;
+      case /* Partial_application */2 :
+          return 5;
+      case /* Statement_type */3 :
+          return 10;
+      case /* Unused_match */4 :
+          return 11;
+      case /* Unused_pat */5 :
+          return 12;
+      case /* Illegal_backslash */6 :
+          return 14;
+      case /* Unerasable_optional_argument */7 :
+          return 16;
+      case /* Unused_argument */8 :
+          return 20;
+      case /* Nonreturning_statement */9 :
+          return 21;
+      case /* Useless_record_with */10 :
+          return 23;
+      case /* All_clauses_guarded */11 :
+          return 8;
+      case /* Wildcard_arg_to_constant_constr */12 :
+          return 28;
+      case /* Eol_in_string */13 :
+          return 29;
+      case /* Unused_rec_flag */14 :
+          return 39;
+      case /* Expect_tailcall */15 :
+          return 51;
+      case /* Fragile_literal_pattern */16 :
+          return 52;
+      case /* Unreachable_case */17 :
+          return 56;
+      case /* Assignment_to_non_mutable_value */18 :
+          return 59;
+      case /* Constraint_on_gadt */19 :
+          return 62;
+      
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Deprecated */0 :
+          return 3;
+      case /* Fragile_match */1 :
+          return 4;
+      case /* Labels_omitted */2 :
+          return 6;
+      case /* Method_override */3 :
+          return 7;
+      case /* Partial_match */4 :
+          return 8;
+      case /* Non_closed_record_pattern */5 :
+          return 9;
+      case /* Instance_variable_override */6 :
+          return 13;
+      case /* Implicit_public_methods */7 :
+          return 15;
+      case /* Undeclared_virtual_method */8 :
+          return 17;
+      case /* Not_principal */9 :
+          return 18;
+      case /* Without_principality */10 :
+          return 19;
+      case /* Preprocessor */11 :
+          return 22;
+      case /* Bad_module_name */12 :
+          return 24;
+      case /* Unused_var */13 :
+          return 26;
+      case /* Unused_var_strict */14 :
+          return 27;
+      case /* Duplicate_definitions */15 :
+          return 30;
+      case /* Multiple_definition */16 :
+          return 31;
+      case /* Unused_value_declaration */17 :
+          return 32;
+      case /* Unused_open */18 :
+          return 33;
+      case /* Unused_type_declaration */19 :
+          return 34;
+      case /* Unused_for_index */20 :
+          return 35;
+      case /* Unused_ancestor */21 :
+          return 36;
+      case /* Unused_constructor */22 :
+          return 37;
+      case /* Unused_extension */23 :
+          return 38;
+      case /* Name_out_of_scope */24 :
+          return 40;
+      case /* Ambiguous_name */25 :
+          return 41;
+      case /* Disambiguated_name */26 :
+          return 42;
+      case /* Nonoptional_label */27 :
+          return 43;
+      case /* Open_shadow_identifier */28 :
+          return 44;
+      case /* Open_shadow_label_constructor */29 :
+          return 45;
+      case /* Bad_env_variable */30 :
+          return 46;
+      case /* Attribute_payload */31 :
+          return 47;
+      case /* Eliminated_optional_arguments */32 :
+          return 48;
+      case /* No_cmi_file */33 :
+          return 49;
+      case /* Bad_docstring */34 :
+          return 50;
+      case /* Misplaced_attribute */35 :
+          return 53;
+      case /* Duplicated_attribute */36 :
+          return 54;
+      case /* Inlining_impossible */37 :
+          return 55;
+      case /* Ambiguous_pattern */38 :
+          return 57;
+      case /* No_cmx_file */39 :
+          return 58;
+      case /* Unused_module */40 :
+          return 60;
+      case /* Unboxable_type_in_prim_decl */41 :
+          return 61;
+      
+    }
+  }
+}
+
+function letter(x) {
+  switch (x) {
+    case 97 :
+        var loop = function (i) {
+          if (i === 0) {
+            return /* [] */0;
+          } else {
+            return {
+                    hd: i,
+                    tl: loop(i - 1 | 0)
+                  };
+          }
+        };
+        return loop(62);
+    case 99 :
+        return {
+                hd: 1,
+                tl: {
+                  hd: 2,
+                  tl: /* [] */0
+                }
+              };
+    case 100 :
+        return {
+                hd: 3,
+                tl: /* [] */0
+              };
+    case 101 :
+        return {
+                hd: 4,
+                tl: /* [] */0
+              };
+    case 102 :
+        return {
+                hd: 5,
+                tl: /* [] */0
+              };
+    case 107 :
+        return {
+                hd: 32,
+                tl: {
+                  hd: 33,
+                  tl: {
+                    hd: 34,
+                    tl: {
+                      hd: 35,
+                      tl: {
+                        hd: 36,
+                        tl: {
+                          hd: 37,
+                          tl: {
+                            hd: 38,
+                            tl: {
+                              hd: 39,
+                              tl: /* [] */0
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              };
+    case 108 :
+        return {
+                hd: 6,
+                tl: /* [] */0
+              };
+    case 109 :
+        return {
+                hd: 7,
+                tl: /* [] */0
+              };
+    case 112 :
+        return {
+                hd: 8,
+                tl: /* [] */0
+              };
+    case 114 :
+        return {
+                hd: 9,
+                tl: /* [] */0
+              };
+    case 115 :
+        return {
+                hd: 10,
+                tl: /* [] */0
+              };
+    case 117 :
+        return {
+                hd: 11,
+                tl: {
+                  hd: 12,
+                  tl: /* [] */0
+                }
+              };
+    case 118 :
+        return {
+                hd: 13,
+                tl: /* [] */0
+              };
+    case 98 :
+    case 103 :
+    case 104 :
+    case 105 :
+    case 106 :
+    case 110 :
+    case 111 :
+    case 113 :
+    case 116 :
+    case 119 :
+        return /* [] */0;
+    case 120 :
+        return {
+                hd: 14,
+                tl: {
+                  hd: 15,
+                  tl: {
+                    hd: 16,
+                    tl: {
+                      hd: 17,
+                      tl: {
+                        hd: 18,
+                        tl: {
+                          hd: 19,
+                          tl: {
+                            hd: 20,
+                            tl: {
+                              hd: 21,
+                              tl: {
+                                hd: 22,
+                                tl: {
+                                  hd: 23,
+                                  tl: {
+                                    hd: 24,
+                                    tl: {
+                                      hd: 30,
+                                      tl: /* [] */0
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              };
+    case 121 :
+        return {
+                hd: 26,
+                tl: /* [] */0
+              };
+    case 122 :
+        return {
+                hd: 27,
+                tl: /* [] */0
+              };
+    default:
+      throw {
+            RE_EXN_ID: "Assert_failure",
+            _1: [
+              "warnings.res",
+              204,
+              9
+            ],
+            Error: new Error()
+          };
+  }
+}
+
+var current = {
+  contents: {
+    active: Caml_array.make(63, true),
+    error: Caml_array.make(63, false)
+  }
+};
+
+var disabled = {
+  contents: false
+};
+
+function without_warnings(f) {
+  return Misc.protect_refs({
+              hd: /* R */{
+                _0: disabled,
+                _1: true
+              },
+              tl: /* [] */0
+            }, f);
+}
+
+function backup(param) {
+  return current.contents;
+}
+
+function restore(x) {
+  current.contents = x;
+  
+}
+
+function is_active(x) {
+  if (disabled.contents) {
+    return false;
+  } else {
+    return Caml_array.get(current.contents.active, number(x));
+  }
+}
+
+function is_error(x) {
+  if (disabled.contents) {
+    return false;
+  } else {
+    return Caml_array.get(current.contents.error, number(x));
+  }
+}
+
+function mk_lazy(f) {
+  var state = current.contents;
+  return {
+          LAZY_DONE: false,
+          VAL: (function () {
+              var prev = current.contents;
+              current.contents = state;
+              try {
+                var r = Curry._1(f, undefined);
+                current.contents = prev;
+                return r;
+              }
+              catch (exn){
+                current.contents = prev;
+                throw exn;
+              }
+            })
+        };
+}
+
+function parse_opt(error, active, flags, s) {
+  var set = function (i) {
+    return Caml_array.set(flags, i, true);
+  };
+  var clear = function (i) {
+    return Caml_array.set(flags, i, false);
+  };
+  var set_all = function (i) {
+    Caml_array.set(active, i, true);
+    return Caml_array.set(error, i, true);
+  };
+  var get_num = function (_n, _i) {
+    while(true) {
+      var i = _i;
+      var n = _n;
+      if (i >= s.length) {
+        return [
+                i,
+                n
+              ];
+      }
+      var match = Caml_string.get(s, i);
+      if (match > 57 || match < 48) {
+        return [
+                i,
+                n
+              ];
+      }
+      _i = i + 1 | 0;
+      _n = (Math.imul(10, n) + Caml_string.get(s, i) | 0) - /* '0' */48 | 0;
+      continue ;
+    };
+  };
+  var get_range = function (i) {
+    var match = get_num(0, i);
+    var n1 = match[1];
+    var i$1 = match[0];
+    if (!((i$1 + 2 | 0) < s.length && Caml_string.get(s, i$1) === /* '.' */46 && Caml_string.get(s, i$1 + 1 | 0) === /* '.' */46)) {
+      return [
+              i$1,
+              n1,
+              n1
+            ];
+    }
+    var match$1 = get_num(0, i$1 + 2 | 0);
+    var n2 = match$1[1];
+    if (n2 < n1) {
+      throw {
+            RE_EXN_ID: Arg.Bad,
+            _1: "Ill-formed list of warnings",
+            Error: new Error()
+          };
+    }
+    return [
+            match$1[0],
+            n1,
+            n2
+          ];
+  };
+  var loop = function (_i) {
+    while(true) {
+      var i = _i;
+      if (i >= s.length) {
+        return ;
+      }
+      var match = Caml_string.get(s, i);
+      if (match >= 65) {
+        if (match >= 97) {
+          if (match >= 123) {
+            throw {
+                  RE_EXN_ID: Arg.Bad,
+                  _1: "Ill-formed list of warnings",
+                  Error: new Error()
+                };
+          }
+          List.iter(clear, letter(Caml_string.get(s, i)));
+          _i = i + 1 | 0;
+          continue ;
+        }
+        if (match >= 91) {
+          throw {
+                RE_EXN_ID: Arg.Bad,
+                _1: "Ill-formed list of warnings",
+                Error: new Error()
+              };
+        }
+        List.iter(set, letter(Char.lowercase_ascii(Caml_string.get(s, i))));
+        _i = i + 1 | 0;
+        continue ;
+      }
+      if (match >= 46) {
+        if (match >= 64) {
+          return loop_letter_num(set_all, i + 1 | 0);
+        }
+        throw {
+              RE_EXN_ID: Arg.Bad,
+              _1: "Ill-formed list of warnings",
+              Error: new Error()
+            };
+      }
+      if (match >= 43) {
+        switch (match) {
+          case 43 :
+              return loop_letter_num(set, i + 1 | 0);
+          case 44 :
+              throw {
+                    RE_EXN_ID: Arg.Bad,
+                    _1: "Ill-formed list of warnings",
+                    Error: new Error()
+                  };
+          case 45 :
+              return loop_letter_num(clear, i + 1 | 0);
+          
+        }
+      } else {
+        throw {
+              RE_EXN_ID: Arg.Bad,
+              _1: "Ill-formed list of warnings",
+              Error: new Error()
+            };
+      }
+    };
+  };
+  var loop_letter_num = function (myset, i) {
+    if (i >= s.length) {
+      throw {
+            RE_EXN_ID: Arg.Bad,
+            _1: "Ill-formed list of warnings",
+            Error: new Error()
+          };
+    }
+    var match = Caml_string.get(s, i);
+    if (match >= 65) {
+      if (match >= 97) {
+        if (match >= 123) {
+          throw {
+                RE_EXN_ID: Arg.Bad,
+                _1: "Ill-formed list of warnings",
+                Error: new Error()
+              };
+        }
+        List.iter(myset, letter(Caml_string.get(s, i)));
+        return loop(i + 1 | 0);
+      }
+      if (match >= 91) {
+        throw {
+              RE_EXN_ID: Arg.Bad,
+              _1: "Ill-formed list of warnings",
+              Error: new Error()
+            };
+      }
+      List.iter(myset, letter(Char.lowercase_ascii(Caml_string.get(s, i))));
+      return loop(i + 1 | 0);
+    }
+    if (match > 57 || match < 48) {
+      throw {
+            RE_EXN_ID: Arg.Bad,
+            _1: "Ill-formed list of warnings",
+            Error: new Error()
+          };
+    }
+    var match$1 = get_range(i);
+    for(var n = match$1[1] ,n_finish = Caml.caml_int_min(match$1[2], 62); n <= n_finish; ++n){
+      Curry._1(myset, n);
+    }
+    return loop(match$1[0]);
+  };
+  return loop(0);
+}
+
+function parse_options(errflag, s) {
+  var error = $$Array.copy(current.contents.error);
+  var active = $$Array.copy(current.contents.active);
+  parse_opt(error, active, errflag ? error : active, s);
+  current.contents = {
+    active: active,
+    error: error
+  };
+  
+}
+
+var defaults_w = "+a-4-6-7-9-27-29-32..42-44-45-48-50-60";
+
+var defaults_warn_error = "-a+31";
+
+parse_options(false, defaults_w);
+
+parse_options(true, defaults_warn_error);
+
+function message(x) {
+  if (typeof x === "number") {
+    switch (x) {
+      case /* Comment_start */0 :
+          return "this is the start of a comment.";
+      case /* Comment_not_end */1 :
+          return "this is not the end of a comment.";
+      case /* Partial_application */2 :
+          return "this function application is partial,\nmaybe some arguments are missing.";
+      case /* Statement_type */3 :
+          return "this expression should have type unit.";
+      case /* Unused_match */4 :
+          return "this match case is unused.";
+      case /* Unused_pat */5 :
+          return "this sub-pattern is unused.";
+      case /* Illegal_backslash */6 :
+          return "illegal backslash escape in string.";
+      case /* Unerasable_optional_argument */7 :
+          return "this optional argument cannot be erased.";
+      case /* Unused_argument */8 :
+          return "this argument will not be used by the function.";
+      case /* Nonreturning_statement */9 :
+          return "this statement never returns (or has an unsound type.)";
+      case /* Useless_record_with */10 :
+          return "all the fields are explicitly listed in this record:\nthe 'with' clause is useless.";
+      case /* All_clauses_guarded */11 :
+          return "this pattern-matching is not exhaustive.\nAll clauses in this pattern-matching are guarded.";
+      case /* Wildcard_arg_to_constant_constr */12 :
+          return "wildcard pattern given as argument to a constant constructor";
+      case /* Eol_in_string */13 :
+          return "unescaped end-of-line in a string constant (non-portable code)";
+      case /* Unused_rec_flag */14 :
+          return "unused rec flag.";
+      case /* Expect_tailcall */15 :
+          return Printf.sprintf("expected tailcall");
+      case /* Fragile_literal_pattern */16 :
+          return Printf.sprintf("Code should not depend on the actual values of\nthis constructor's arguments. They are only for information\nand may change in future versions. (See manual section 8.5)");
+      case /* Unreachable_case */17 :
+          return "this match case is unreachable.\nConsider replacing it with a refutation case '<pat> -> .'";
+      case /* Assignment_to_non_mutable_value */18 :
+          return "A potential assignment to a non-mutable value was detected \nin this source file.  Such assignments may generate incorrect code \nwhen using Flambda.";
+      case /* Constraint_on_gadt */19 :
+          return "Type constraints do not apply to GADT cases of variant types.";
+      
+    }
+  } else {
+    switch (x.TAG | 0) {
+      case /* Deprecated */0 :
+          return "deprecated: " + Misc.normalise_eol(x._0);
+      case /* Fragile_match */1 :
+          var s = x._0;
+          if (s === "") {
+            return "this pattern-matching is fragile.";
+          } else {
+            return "this pattern-matching is fragile.\nIt will remain exhaustive when constructors are added to type " + (s + ".");
+          }
+      case /* Labels_omitted */2 :
+          var ls = x._0;
+          if (ls) {
+            if (ls.tl) {
+              return "labels " + ($$String.concat(", ", ls) + " were omitted in the application of this function.");
+            } else {
+              return "label " + (ls.hd + " was omitted in the application of this function.");
+            }
+          }
+          throw {
+                RE_EXN_ID: "Assert_failure",
+                _1: [
+                  "warnings.res",
+                  360,
+                  30
+                ],
+                Error: new Error()
+              };
+      case /* Method_override */3 :
+          var match = x._0;
+          if (match) {
+            var slist = match.tl;
+            var lab = match.hd;
+            if (slist) {
+              return $$String.concat(" ", {
+                          hd: "the following methods are overridden by the class",
+                          tl: {
+                            hd: lab,
+                            tl: {
+                              hd: ":\n ",
+                              tl: slist
+                            }
+                          }
+                        });
+            } else {
+              return "the method " + (lab + " is overridden.");
+            }
+          }
+          throw {
+                RE_EXN_ID: "Assert_failure",
+                _1: [
+                  "warnings.res",
+                  371,
+                  31
+                ],
+                Error: new Error()
+              };
+      case /* Partial_match */4 :
+          var s$1 = x._0;
+          if (s$1 === "") {
+            return "this pattern-matching is not exhaustive.";
+          } else {
+            return "this pattern-matching is not exhaustive.\nHere is an example of a case that is not matched:\n" + s$1;
+          }
+      case /* Non_closed_record_pattern */5 :
+          return "the following labels are not bound in this record pattern:\n" + (x._0 + "\nEither bind these labels explicitly or add '; _' to the pattern.");
+      case /* Instance_variable_override */6 :
+          var match$1 = x._0;
+          if (match$1) {
+            var slist$1 = match$1.tl;
+            var lab$1 = match$1.hd;
+            if (slist$1) {
+              return $$String.concat(" ", {
+                          hd: "the following instance variables are overridden by the class",
+                          tl: {
+                            hd: lab$1,
+                            tl: {
+                              hd: ":\n ",
+                              tl: slist$1
+                            }
+                          }
+                        }) + "\nThe behaviour changed in ocaml 3.10 (previous behaviour was hiding.)";
+            } else {
+              return "the instance variable " + (lab$1 + " is overridden.\nThe behaviour changed in ocaml 3.10 (previous behaviour was hiding.)");
+            }
+          }
+          throw {
+                RE_EXN_ID: "Assert_failure",
+                _1: [
+                  "warnings.res",
+                  393,
+                  42
+                ],
+                Error: new Error()
+              };
+      case /* Implicit_public_methods */7 :
+          return "the following private methods were made public implicitly:\n " + ($$String.concat(" ", x._0) + ".");
+      case /* Undeclared_virtual_method */8 :
+          return "the virtual method " + (x._0 + " is not declared.");
+      case /* Not_principal */9 :
+          return x._0 + " is not principal.";
+      case /* Without_principality */10 :
+          return x._0 + " without principality.";
+      case /* Preprocessor */11 :
+          return x._0;
+      case /* Bad_module_name */12 :
+          return "bad source file name: \"" + (x._0 + "\" is not a valid module name.");
+      case /* Unused_var */13 :
+      case /* Unused_var_strict */14 :
+          return "unused variable " + (x._0 + ".");
+      case /* Duplicate_definitions */15 :
+          return Curry._4(Printf.sprintf("the %s %s is defined in both types %s and %s."), x._0, x._1, x._2, x._3);
+      case /* Multiple_definition */16 :
+          return Curry._3(Printf.sprintf("files %s and %s both define a module named %s"), x._1, x._2, x._0);
+      case /* Unused_value_declaration */17 :
+          return "unused value " + (x._0 + ".");
+      case /* Unused_open */18 :
+          return "unused open " + (x._0 + ".");
+      case /* Unused_type_declaration */19 :
+          return "unused type " + (x._0 + ".");
+      case /* Unused_for_index */20 :
+          return "unused for-loop index " + (x._0 + ".");
+      case /* Unused_ancestor */21 :
+          return "unused ancestor variable " + (x._0 + ".");
+      case /* Unused_constructor */22 :
+          var s$2 = x._0;
+          if (x._1) {
+            return "constructor " + (s$2 + " is never used to build values.\n(However, this constructor appears in patterns.)");
+          } else if (x._2) {
+            return "constructor " + (s$2 + " is never used to build values.\nIts type is exported as a private type.");
+          } else {
+            return "unused constructor " + (s$2 + ".");
+          }
+      case /* Unused_extension */23 :
+          var kind = x._1 ? "exception" : "extension constructor";
+          var name = kind + (" " + x._0);
+          if (x._2) {
+            return name + " is never used to build values.\n(However, this constructor appears in patterns.)";
+          } else if (x._3) {
+            return name + " is never used to build values.\nIt is exported or rebound as a private extension.";
+          } else {
+            return "unused " + name;
+          }
+      case /* Name_out_of_scope */24 :
+          var slist$2 = x._1;
+          var ty = x._0;
+          if (slist$2 && !slist$2.tl && !x._2) {
+            return slist$2.hd + (" was selected from type " + (ty + ".\nIt is not visible in the current scope, and will not \nbe selected if the type becomes unknown."));
+          }
+          if (x._2) {
+            return "this record of type " + (ty + (" contains fields that are \nnot visible in the current scope: " + ($$String.concat(" ", slist$2) + ".\nThey will not be selected if the type becomes unknown.")));
+          }
+          throw {
+                RE_EXN_ID: "Assert_failure",
+                _1: [
+                  "warnings.res",
+                  457,
+                  38
+                ],
+                Error: new Error()
+              };
+          break;
+      case /* Ambiguous_name */25 :
+          var _slist = x._0;
+          if (_slist && !_slist.tl && !x._2) {
+            return _slist.hd + (" belongs to several types: " + ($$String.concat(" ", x._1) + "\nThe first one was selected. Please disambiguate if this is wrong."));
+          }
+          if (x._2) {
+            return "these field labels belong to several types: " + ($$String.concat(" ", x._1) + "\nThe first one was selected. Please disambiguate if this is wrong.");
+          }
+          throw {
+                RE_EXN_ID: "Assert_failure",
+                _1: [
+                  "warnings.res",
+                  473,
+                  35
+                ],
+                Error: new Error()
+              };
+          break;
+      case /* Disambiguated_name */26 :
+          return "this use of " + (x._0 + " relies on type-directed disambiguation,\nit will not compile with OCaml 4.00 or earlier.");
+      case /* Nonoptional_label */27 :
+          return "the label " + (x._0 + " is not optional.");
+      case /* Open_shadow_identifier */28 :
+          return Curry._2(Printf.sprintf("this open statement shadows the %s identifier %s (which is later used)"), x._0, x._1);
+      case /* Open_shadow_label_constructor */29 :
+          return Curry._2(Printf.sprintf("this open statement shadows the %s %s (which is later used)"), x._0, x._1);
+      case /* Bad_env_variable */30 :
+          return Curry._2(Printf.sprintf("illegal environment variable %s : %s"), x._0, x._1);
+      case /* Attribute_payload */31 :
+          return Curry._2(Printf.sprintf("illegal payload for attribute '%s'.\n%s"), x._0, x._1);
+      case /* Eliminated_optional_arguments */32 :
+          var sl = x._0;
+          return Curry._2(Printf.sprintf("implicit elimination of optional argument%s %s"), List.length(sl) === 1 ? "" : "s", $$String.concat(", ", sl));
+      case /* No_cmi_file */33 :
+          var msg = x._1;
+          var name$1 = x._0;
+          if (msg !== undefined) {
+            return Curry._2(Printf.sprintf("no valid cmi file was found in path for module %s. %s"), name$1, msg);
+          } else {
+            return "no cmi file was found in path for module " + name$1;
+          }
+      case /* Bad_docstring */34 :
+          if (x._0) {
+            return "unattached documentation comment (ignored)";
+          } else {
+            return "ambiguous documentation comment";
+          }
+      case /* Misplaced_attribute */35 :
+          return Curry._1(Printf.sprintf("the %S attribute cannot appear in this context"), x._0);
+      case /* Duplicated_attribute */36 :
+          return Curry._1(Printf.sprintf("the %S attribute is used more than once on this expression"), x._0);
+      case /* Inlining_impossible */37 :
+          return Curry._1(Printf.sprintf("Cannot inline: %s"), x._0);
+      case /* Ambiguous_pattern */38 :
+          var vars = List.sort($$String.compare, x._0);
+          var tmp;
+          if (vars) {
+            tmp = vars.tl ? "variables " + $$String.concat(",", vars) : "variable " + vars.hd;
+          } else {
+            throw {
+                  RE_EXN_ID: "Assert_failure",
+                  _1: [
+                    "warnings.res",
+                    535,
+                    18
+                  ],
+                  Error: new Error()
+                };
+          }
+          return Curry._1(Printf.sprintf("Ambiguous or-pattern variables under guard;\n%s may match different arguments. (See manual section 8.5)"), tmp);
+      case /* No_cmx_file */39 :
+          return Curry._1(Printf.sprintf("no cmx file was found in path for module %s, and its interface was not compiled with -opaque"), x._0);
+      case /* Unused_module */40 :
+          return "unused module " + (x._0 + ".");
+      case /* Unboxable_type_in_prim_decl */41 :
+          var t = x._0;
+          return Curry._2(Printf.sprintf("This primitive declaration uses type %s, which is unannotated and\nunboxable. The representation of such types may change in future\nversions. You should annotate the declaration of %s with [@@boxed]\nor [@@unboxed]."), t, t);
+      
+    }
+  }
+}
+
+function sub_locs(x) {
+  if (typeof x === "number" || x.TAG !== /* Deprecated */0) {
+    return /* [] */0;
+  } else {
+    return {
+            hd: [
+              x._1,
+              "Definition"
+            ],
+            tl: {
+              hd: [
+                x._2,
+                "Expected signature"
+              ],
+              tl: /* [] */0
+            }
+          };
+  }
+}
+
+var nerrors = {
+  contents: 0
+};
+
+function report(w) {
+  if (is_active(w)) {
+    if (is_error(w)) {
+      nerrors.contents = nerrors.contents + 1 | 0;
+    }
+    return {
+            NAME: "Active",
+            VAL: {
+              number: number(w),
+              message: message(w),
+              is_error: is_error(w),
+              sub_locs: sub_locs(w)
+            }
+          };
+  } else {
+    return "Inactive";
+  }
+}
+
+var Errors = /* @__PURE__ */Caml_exceptions.create("Warnings.Errors");
+
+function reset_fatal(param) {
+  nerrors.contents = 0;
+  
+}
+
+function check_fatal(param) {
+  if (nerrors.contents <= 0) {
+    return ;
+  }
+  nerrors.contents = 0;
+  throw {
+        RE_EXN_ID: Errors,
+        Error: new Error()
+      };
+}
+
+var descriptions = {
+  hd: [
+    1,
+    "Suspicious-looking start-of-comment mark."
+  ],
+  tl: {
+    hd: [
+      2,
+      "Suspicious-looking end-of-comment mark."
+    ],
+    tl: {
+      hd: [
+        3,
+        "Deprecated feature."
+      ],
+      tl: {
+        hd: [
+          4,
+          "Fragile pattern matching: matching that will remain complete even\n    if additional constructors are added to one of the variant types\n    matched."
+        ],
+        tl: {
+          hd: [
+            5,
+            "Partially applied function: expression whose result has function\n    type and is ignored."
+          ],
+          tl: {
+            hd: [
+              6,
+              "Label omitted in function application."
+            ],
+            tl: {
+              hd: [
+                7,
+                "Method overridden."
+              ],
+              tl: {
+                hd: [
+                  8,
+                  "Partial match: missing cases in pattern-matching."
+                ],
+                tl: {
+                  hd: [
+                    9,
+                    "Missing fields in a record pattern."
+                  ],
+                  tl: {
+                    hd: [
+                      10,
+                      "Expression on the left-hand side of a sequence that doesn't have type\n    \"unit\" (and that is not a function, see warning number 5)."
+                    ],
+                    tl: {
+                      hd: [
+                        11,
+                        "Redundant case in a pattern matching (unused match case)."
+                      ],
+                      tl: {
+                        hd: [
+                          12,
+                          "Redundant sub-pattern in a pattern-matching."
+                        ],
+                        tl: {
+                          hd: [
+                            13,
+                            "Instance variable overridden."
+                          ],
+                          tl: {
+                            hd: [
+                              14,
+                              "Illegal backslash escape in a string constant."
+                            ],
+                            tl: {
+                              hd: [
+                                15,
+                                "Private method made public implicitly."
+                              ],
+                              tl: {
+                                hd: [
+                                  16,
+                                  "Unerasable optional argument."
+                                ],
+                                tl: {
+                                  hd: [
+                                    17,
+                                    "Undeclared virtual method."
+                                  ],
+                                  tl: {
+                                    hd: [
+                                      18,
+                                      "Non-principal type."
+                                    ],
+                                    tl: {
+                                      hd: [
+                                        19,
+                                        "Type without principality."
+                                      ],
+                                      tl: {
+                                        hd: [
+                                          20,
+                                          "Unused function argument."
+                                        ],
+                                        tl: {
+                                          hd: [
+                                            21,
+                                            "Non-returning statement."
+                                          ],
+                                          tl: {
+                                            hd: [
+                                              22,
+                                              "Preprocessor warning."
+                                            ],
+                                            tl: {
+                                              hd: [
+                                                23,
+                                                "Useless record \"with\" clause."
+                                              ],
+                                              tl: {
+                                                hd: [
+                                                  24,
+                                                  "Bad module name: the source file name is not a valid OCaml module name."
+                                                ],
+                                                tl: {
+                                                  hd: [
+                                                    25,
+                                                    "Deprecated: now part of warning 8."
+                                                  ],
+                                                  tl: {
+                                                    hd: [
+                                                      26,
+                                                      "Suspicious unused variable: unused variable that is bound\n    with \"let\" or \"as\", and doesn't start with an underscore (\"_\")\n    character."
+                                                    ],
+                                                    tl: {
+                                                      hd: [
+                                                        27,
+                                                        "Innocuous unused variable: unused variable that is not bound with\n    \"let\" nor \"as\", and doesn't start with an underscore (\"_\")\n    character."
+                                                      ],
+                                                      tl: {
+                                                        hd: [
+                                                          28,
+                                                          "Wildcard pattern given as argument to a constant constructor."
+                                                        ],
+                                                        tl: {
+                                                          hd: [
+                                                            29,
+                                                            "Unescaped end-of-line in a string constant (non-portable code)."
+                                                          ],
+                                                          tl: {
+                                                            hd: [
+                                                              30,
+                                                              "Two labels or constructors of the same name are defined in two\n    mutually recursive types."
+                                                            ],
+                                                            tl: {
+                                                              hd: [
+                                                                31,
+                                                                "A module is linked twice in the same executable."
+                                                              ],
+                                                              tl: {
+                                                                hd: [
+                                                                  32,
+                                                                  "Unused value declaration."
+                                                                ],
+                                                                tl: {
+                                                                  hd: [
+                                                                    33,
+                                                                    "Unused open statement."
+                                                                  ],
+                                                                  tl: {
+                                                                    hd: [
+                                                                      34,
+                                                                      "Unused type declaration."
+                                                                    ],
+                                                                    tl: {
+                                                                      hd: [
+                                                                        35,
+                                                                        "Unused for-loop index."
+                                                                      ],
+                                                                      tl: {
+                                                                        hd: [
+                                                                          36,
+                                                                          "Unused ancestor variable."
+                                                                        ],
+                                                                        tl: {
+                                                                          hd: [
+                                                                            37,
+                                                                            "Unused constructor."
+                                                                          ],
+                                                                          tl: {
+                                                                            hd: [
+                                                                              38,
+                                                                              "Unused extension constructor."
+                                                                            ],
+                                                                            tl: {
+                                                                              hd: [
+                                                                                39,
+                                                                                "Unused rec flag."
+                                                                              ],
+                                                                              tl: {
+                                                                                hd: [
+                                                                                  40,
+                                                                                  "Constructor or label name used out of scope."
+                                                                                ],
+                                                                                tl: {
+                                                                                  hd: [
+                                                                                    41,
+                                                                                    "Ambiguous constructor or label name."
+                                                                                  ],
+                                                                                  tl: {
+                                                                                    hd: [
+                                                                                      42,
+                                                                                      "Disambiguated constructor or label name (compatibility warning)."
+                                                                                    ],
+                                                                                    tl: {
+                                                                                      hd: [
+                                                                                        43,
+                                                                                        "Nonoptional label applied as optional."
+                                                                                      ],
+                                                                                      tl: {
+                                                                                        hd: [
+                                                                                          44,
+                                                                                          "Open statement shadows an already defined identifier."
+                                                                                        ],
+                                                                                        tl: {
+                                                                                          hd: [
+                                                                                            45,
+                                                                                            "Open statement shadows an already defined label or constructor."
+                                                                                          ],
+                                                                                          tl: {
+                                                                                            hd: [
+                                                                                              46,
+                                                                                              "Error in environment variable."
+                                                                                            ],
+                                                                                            tl: {
+                                                                                              hd: [
+                                                                                                47,
+                                                                                                "Illegal attribute payload."
+                                                                                              ],
+                                                                                              tl: {
+                                                                                                hd: [
+                                                                                                  48,
+                                                                                                  "Implicit elimination of optional arguments."
+                                                                                                ],
+                                                                                                tl: {
+                                                                                                  hd: [
+                                                                                                    49,
+                                                                                                    "Absent cmi file when looking up module alias."
+                                                                                                  ],
+                                                                                                  tl: {
+                                                                                                    hd: [
+                                                                                                      50,
+                                                                                                      "Unexpected documentation comment."
+                                                                                                    ],
+                                                                                                    tl: {
+                                                                                                      hd: [
+                                                                                                        51,
+                                                                                                        "Warning on non-tail calls if @tailcall present."
+                                                                                                      ],
+                                                                                                      tl: {
+                                                                                                        hd: [
+                                                                                                          52,
+                                                                                                          "Fragile constant pattern."
+                                                                                                        ],
+                                                                                                        tl: {
+                                                                                                          hd: [
+                                                                                                            53,
+                                                                                                            "Attribute cannot appear in this context"
+                                                                                                          ],
+                                                                                                          tl: {
+                                                                                                            hd: [
+                                                                                                              54,
+                                                                                                              "Attribute used more than once on an expression"
+                                                                                                            ],
+                                                                                                            tl: {
+                                                                                                              hd: [
+                                                                                                                55,
+                                                                                                                "Inlining impossible"
+                                                                                                              ],
+                                                                                                              tl: {
+                                                                                                                hd: [
+                                                                                                                  56,
+                                                                                                                  "Unreachable case in a pattern-matching (based on type information)."
+                                                                                                                ],
+                                                                                                                tl: {
+                                                                                                                  hd: [
+                                                                                                                    57,
+                                                                                                                    "Ambiguous or-pattern variables under guard"
+                                                                                                                  ],
+                                                                                                                  tl: {
+                                                                                                                    hd: [
+                                                                                                                      58,
+                                                                                                                      "Missing cmx file"
+                                                                                                                    ],
+                                                                                                                    tl: {
+                                                                                                                      hd: [
+                                                                                                                        59,
+                                                                                                                        "Assignment to non-mutable value"
+                                                                                                                      ],
+                                                                                                                      tl: {
+                                                                                                                        hd: [
+                                                                                                                          60,
+                                                                                                                          "Unused module declaration"
+                                                                                                                        ],
+                                                                                                                        tl: {
+                                                                                                                          hd: [
+                                                                                                                            61,
+                                                                                                                            "Unboxable type in primitive declaration"
+                                                                                                                          ],
+                                                                                                                          tl: {
+                                                                                                                            hd: [
+                                                                                                                              62,
+                                                                                                                              "Type constraint on GADT type declaration"
+                                                                                                                            ],
+                                                                                                                            tl: /* [] */0
+                                                                                                                          }
+                                                                                                                        }
+                                                                                                                      }
+                                                                                                                    }
+                                                                                                                  }
+                                                                                                                }
+                                                                                                              }
+                                                                                                            }
+                                                                                                          }
+                                                                                                        }
+                                                                                                      }
+                                                                                                    }
+                                                                                                  }
+                                                                                                }
+                                                                                              }
+                                                                                            }
+                                                                                          }
+                                                                                        }
+                                                                                      }
+                                                                                    }
+                                                                                  }
+                                                                                }
+                                                                              }
+                                                                            }
+                                                                          }
+                                                                        }
+                                                                      }
+                                                                    }
+                                                                  }
+                                                                }
+                                                              }
+                                                            }
+                                                          }
+                                                        }
+                                                      }
+                                                    }
+                                                  }
+                                                }
+                                              }
+                                            }
+                                          }
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+};
+
+function help_warnings(param) {
+  List.iter((function (param) {
+          return Curry._2(Printf.printf("%3i %s\n"), param[0], param[1]);
+        }), descriptions);
+  console.log("  A all warnings");
+  for(var i = /* 'b' */98; i <= /* 'z' */122; ++i){
+    var c = Char.chr(i);
+    var l = letter(c);
+    if (l) {
+      if (l.tl) {
+        Curry._2(Printf.printf("  %c warnings %s.\n"), Char.uppercase_ascii(c), $$String.concat(", ", List.map((function (prim) {
+                        return String(prim);
+                      }), l)));
+      } else {
+        Curry._2(Printf.printf("  %c Alias for warning %i.\n"), Char.uppercase_ascii(c), l.hd);
+      }
+    }
+    
+  }
+  return Pervasives.exit(0);
+}
+
+var last_warning_number = 62;
+
+export {
+  number ,
+  last_warning_number ,
+  letter ,
+  current ,
+  disabled ,
+  without_warnings ,
+  backup ,
+  restore ,
+  is_active ,
+  is_error ,
+  mk_lazy ,
+  parse_opt ,
+  parse_options ,
+  defaults_w ,
+  defaults_warn_error ,
+  message ,
+  sub_locs ,
+  nerrors ,
+  report ,
+  Errors ,
+  reset_fatal ,
+  check_fatal ,
+  descriptions ,
+  help_warnings ,
+  
+}
+/*  Not a pure module */
diff --git a/analysis/examples/larger-project/src/warnings.res b/analysis/examples/larger-project/src/warnings.res
new file mode 100644
index 0000000000..12664097e4
--- /dev/null
+++ b/analysis/examples/larger-project/src/warnings.res
@@ -0,0 +1,722 @@
+/* ************************************************************************ */
+/*  */
+/* OCaml */
+/*  */
+/* Pierre Weis && Damien Doligez, INRIA Rocquencourt */
+/*  */
+/* Copyright 1998 Institut National de Recherche en Informatique et */
+/* en Automatique. */
+/*  */
+/* All rights reserved.  This file is distributed under the terms of */
+/* the GNU Lesser General Public License version 2.1, with the */
+/* special exception on linking described in the file LICENSE. */
+/*  */
+/* ************************************************************************ */
+
+/* When you change this, you need to update the documentation:
+   - man/ocamlc.m
+   - man/ocamlopt.m
+   - manual/manual/cmds/comp.etex
+   - manual/manual/cmds/native.etex
+*/
+
+type loc = {
+  loc_start: Lexing.position,
+  loc_end: Lexing.position,
+  loc_ghost: bool,
+}
+
+type t =
+  | Comment_start /* 1 */
+  | Comment_not_end /* 2 */
+  | Deprecated(string, loc, loc) /* 3 */
+  | Fragile_match(string) /* 4 */
+  | Partial_application /* 5 */
+  | Labels_omitted(list<string>) /* 6 */
+  | Method_override(list<string>) /* 7 */
+  | Partial_match(string) /* 8 */
+  | Non_closed_record_pattern(string) /* 9 */
+  | Statement_type /* 10 */
+  | Unused_match /* 11 */
+  | Unused_pat /* 12 */
+  | Instance_variable_override(list<string>) /* 13 */
+  | Illegal_backslash /* 14 */
+  | Implicit_public_methods(list<string>) /* 15 */
+  | Unerasable_optional_argument /* 16 */
+  | Undeclared_virtual_method(string) /* 17 */
+  | Not_principal(string) /* 18 */
+  | Without_principality(string) /* 19 */
+  | Unused_argument /* 20 */
+  | Nonreturning_statement /* 21 */
+  | Preprocessor(string) /* 22 */
+  | Useless_record_with /* 23 */
+  | Bad_module_name(string) /* 24 */
+  | All_clauses_guarded /* 8, used to be 25 */
+  | Unused_var(string) /* 26 */
+  | Unused_var_strict(string) /* 27 */
+  | Wildcard_arg_to_constant_constr /* 28 */
+  | Eol_in_string /* 29 */
+  | Duplicate_definitions(string, string, string, string) /* 30 */
+  | Multiple_definition(string, string, string) /* 31 */
+  | Unused_value_declaration(string) /* 32 */
+  | Unused_open(string) /* 33 */
+  | Unused_type_declaration(string) /* 34 */
+  | Unused_for_index(string) /* 35 */
+  | Unused_ancestor(string) /* 36 */
+  | Unused_constructor(string, bool, bool) /* 37 */
+  | Unused_extension(string, bool, bool, bool) /* 38 */
+  | Unused_rec_flag /* 39 */
+  | Name_out_of_scope(string, list<string>, bool) /* 40 */
+  | Ambiguous_name(list<string>, list<string>, bool) /* 41 */
+  | Disambiguated_name(string) /* 42 */
+  | Nonoptional_label(string) /* 43 */
+  | Open_shadow_identifier(string, string) /* 44 */
+  | Open_shadow_label_constructor(string, string) /* 45 */
+  | Bad_env_variable(string, string) /* 46 */
+  | Attribute_payload(string, string) /* 47 */
+  | Eliminated_optional_arguments(list<string>) /* 48 */
+  | No_cmi_file(string, option<string>) /* 49 */
+  | Bad_docstring(bool) /* 50 */
+  | Expect_tailcall /* 51 */
+  | Fragile_literal_pattern /* 52 */
+  | Misplaced_attribute(string) /* 53 */
+  | Duplicated_attribute(string) /* 54 */
+  | Inlining_impossible(string) /* 55 */
+  | Unreachable_case /* 56 */
+  | Ambiguous_pattern(list<string>) /* 57 */
+  | No_cmx_file(string) /* 58 */
+  | Assignment_to_non_mutable_value /* 59 */
+  | Unused_module(string) /* 60 */
+  | Unboxable_type_in_prim_decl(string) /* 61 */
+  | Constraint_on_gadt /* 62 */
+
+/* If you remove a warning, leave a hole in the numbering.  NEVER change
+   the numbers of existing warnings.
+   If you add a new warning, add it at the end with a new number;
+   do NOT reuse one of the holes.
+*/
+
+let number = x =>
+  switch x {
+  | Comment_start => 1
+  | Comment_not_end => 2
+  | Deprecated(_) => 3
+  | Fragile_match(_) => 4
+  | Partial_application => 5
+  | Labels_omitted(_) => 6
+  | Method_override(_) => 7
+  | Partial_match(_) => 8
+  | Non_closed_record_pattern(_) => 9
+  | Statement_type => 10
+  | Unused_match => 11
+  | Unused_pat => 12
+  | Instance_variable_override(_) => 13
+  | Illegal_backslash => 14
+  | Implicit_public_methods(_) => 15
+  | Unerasable_optional_argument => 16
+  | Undeclared_virtual_method(_) => 17
+  | Not_principal(_) => 18
+  | Without_principality(_) => 19
+  | Unused_argument => 20
+  | Nonreturning_statement => 21
+  | Preprocessor(_) => 22
+  | Useless_record_with => 23
+  | Bad_module_name(_) => 24
+  | All_clauses_guarded => 8 /* used to be 25 */
+  | Unused_var(_) => 26
+  | Unused_var_strict(_) => 27
+  | Wildcard_arg_to_constant_constr => 28
+  | Eol_in_string => 29
+  | Duplicate_definitions(_) => 30
+  | Multiple_definition(_) => 31
+  | Unused_value_declaration(_) => 32
+  | Unused_open(_) => 33
+  | Unused_type_declaration(_) => 34
+  | Unused_for_index(_) => 35
+  | Unused_ancestor(_) => 36
+  | Unused_constructor(_) => 37
+  | Unused_extension(_) => 38
+  | Unused_rec_flag => 39
+  | Name_out_of_scope(_) => 40
+  | Ambiguous_name(_) => 41
+  | Disambiguated_name(_) => 42
+  | Nonoptional_label(_) => 43
+  | Open_shadow_identifier(_) => 44
+  | Open_shadow_label_constructor(_) => 45
+  | Bad_env_variable(_) => 46
+  | Attribute_payload(_) => 47
+  | Eliminated_optional_arguments(_) => 48
+  | No_cmi_file(_) => 49
+  | Bad_docstring(_) => 50
+  | Expect_tailcall => 51
+  | Fragile_literal_pattern => 52
+  | Misplaced_attribute(_) => 53
+  | Duplicated_attribute(_) => 54
+  | Inlining_impossible(_) => 55
+  | Unreachable_case => 56
+  | Ambiguous_pattern(_) => 57
+  | No_cmx_file(_) => 58
+  | Assignment_to_non_mutable_value => 59
+  | Unused_module(_) => 60
+  | Unboxable_type_in_prim_decl(_) => 61
+  | Constraint_on_gadt => 62
+  }
+
+let last_warning_number = 62
+
+/* Must be the max number returned by the [number] function. */
+
+let letter = x =>
+  switch x {
+  | 'a' =>
+    let rec loop = i =>
+      if i == 0 {
+        list{}
+      } else {
+        list{i, ...loop(i - 1)}
+      }
+    loop(last_warning_number)
+  | 'b' => list{}
+  | 'c' => list{1, 2}
+  | 'd' => list{3}
+  | 'e' => list{4}
+  | 'f' => list{5}
+  | 'g' => list{}
+  | 'h' => list{}
+  | 'i' => list{}
+  | 'j' => list{}
+  | 'k' => list{32, 33, 34, 35, 36, 37, 38, 39}
+  | 'l' => list{6}
+  | 'm' => list{7}
+  | 'n' => list{}
+  | 'o' => list{}
+  | 'p' => list{8}
+  | 'q' => list{}
+  | 'r' => list{9}
+  | 's' => list{10}
+  | 't' => list{}
+  | 'u' => list{11, 12}
+  | 'v' => list{13}
+  | 'w' => list{}
+  | 'x' => list{14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 30}
+  | 'y' => list{26}
+  | 'z' => list{27}
+  | _ => assert false
+  }
+
+type state = {
+  active: array<bool>,
+  error: array<bool>,
+}
+
+let current = ref({
+  active: Array.make(last_warning_number + 1, true),
+  error: Array.make(last_warning_number + 1, false),
+})
+
+let disabled = ref(false)
+
+let without_warnings = f => Misc.protect_refs(list{Misc.R(disabled, true)}, f)
+
+let backup = () => current.contents
+
+let restore = x => current := x
+
+let is_active = x => !disabled.contents && current.contents.active[number(x)]
+let is_error = x => !disabled.contents && current.contents.error[number(x)]
+
+let mk_lazy = f => {
+  let state = backup()
+  lazy {
+    let prev = backup()
+    restore(state)
+    try {
+      let r = f()
+      restore(prev)
+      r
+    } catch {
+    | exn =>
+      restore(prev)
+      raise(exn)
+    }
+  }
+}
+
+let parse_opt = (error, active, flags, s) => {
+  let set = i => flags[i] = true
+  let clear = i => flags[i] = false
+  let set_all = i => {
+    active[i] = true
+    error[i] = true
+  }
+  let error = () => raise(Arg.Bad("Ill-formed list of warnings"))
+  let rec get_num = (n, i) =>
+    if i >= String.length(s) {
+      (i, n)
+    } else {
+      switch String.get(s, i) {
+      | '0' .. '9' => get_num(10 * n + Char.code(String.get(s, i)) - Char.code('0'), i + 1)
+      | _ => (i, n)
+      }
+    }
+
+  let get_range = i => {
+    let (i, n1) = get_num(0, i)
+    if i + 2 < String.length(s) && (String.get(s, i) == '.' && String.get(s, i + 1) == '.') {
+      let (i, n2) = get_num(0, i + 2)
+      if n2 < n1 {
+        error()
+      }
+      (i, n1, n2)
+    } else {
+      (i, n1, n1)
+    }
+  }
+
+  let rec loop = i =>
+    if i >= String.length(s) {
+      ()
+    } else {
+      switch String.get(s, i) {
+      | 'A' .. 'Z' =>
+        List.iter(set, letter(Char.lowercase_ascii(String.get(s, i))))
+        loop(i + 1)
+      | 'a' .. 'z' =>
+        List.iter(clear, letter(String.get(s, i)))
+        loop(i + 1)
+      | '+' => loop_letter_num(set, i + 1)
+      | '-' => loop_letter_num(clear, i + 1)
+      | '@' => loop_letter_num(set_all, i + 1)
+      | _ => error()
+      }
+    }
+  and loop_letter_num = (myset, i) =>
+    if i >= String.length(s) {
+      error()
+    } else {
+      switch String.get(s, i) {
+      | '0' .. '9' =>
+        let (i, n1, n2) = get_range(i)
+        for n in n1 to min(n2, last_warning_number) {
+          myset(n)
+        }
+        loop(i)
+      | 'A' .. 'Z' =>
+        List.iter(myset, letter(Char.lowercase_ascii(String.get(s, i))))
+        loop(i + 1)
+      | 'a' .. 'z' =>
+        List.iter(myset, letter(String.get(s, i)))
+        loop(i + 1)
+      | _ => error()
+      }
+    }
+
+  loop(0)
+}
+
+let parse_options = (errflag, s) => {
+  let error = Array.copy(current.contents.error)
+  let active = Array.copy(current.contents.active)
+  parse_opt(
+    error,
+    active,
+    if errflag {
+      error
+    } else {
+      active
+    },
+    s,
+  )
+  current := {error: error, active: active}
+}
+
+/* If you change these, don't forget to change them in man/ocamlc.m */
+let defaults_w = "+a-4-6-7-9-27-29-32..42-44-45-48-50-60"
+let defaults_warn_error = "-a+31"
+
+let () = parse_options(false, defaults_w)
+let () = parse_options(true, defaults_warn_error)
+
+let message = x =>
+  switch x {
+  | Comment_start => "this is the start of a comment."
+  | Comment_not_end => "this is not the end of a comment."
+  | Deprecated(s, _, _) =>
+    /* Reduce \r\n to \n:
+           - Prevents any \r characters being printed on Unix when processing
+             Windows sources
+           - Prevents \r\r\n being generated on Windows, which affects the
+             testsuite
+ */
+    "deprecated: " ++ Misc.normalise_eol(s)
+  | Fragile_match("") => "this pattern-matching is fragile."
+  | Fragile_match(s) =>
+    "this pattern-matching is fragile.\n\
+       It will remain exhaustive when constructors are added to type " ++
+    (s ++
+    ".")
+  | Partial_application => "this function application is partial,\n\
+       maybe some arguments are missing."
+  | Labels_omitted(list{}) => assert false
+  | Labels_omitted(list{l}) =>
+    "label " ++ (l ++ " was omitted in the application of this function.")
+  | Labels_omitted(ls) =>
+    "labels " ++ (String.concat(", ", ls) ++ " were omitted in the application of this function.")
+  | Method_override(list{lab}) => "the method " ++ (lab ++ " is overridden.")
+  | Method_override(list{cname, ...slist}) =>
+    String.concat(
+      " ",
+      list{"the following methods are overridden by the class", cname, ":\n ", ...slist},
+    )
+  | Method_override(list{}) => assert false
+  | Partial_match("") => "this pattern-matching is not exhaustive."
+  | Partial_match(s) =>
+    "this pattern-matching is not exhaustive.\n\
+       Here is an example of a case that is not matched:\n" ++
+    s
+  | Non_closed_record_pattern(s) =>
+    "the following labels are not bound in this record pattern:\n" ++
+    (s ++
+    "\nEither bind these labels explicitly or add '; _' to the pattern.")
+  | Statement_type => "this expression should have type unit."
+  | Unused_match => "this match case is unused."
+  | Unused_pat => "this sub-pattern is unused."
+  | Instance_variable_override(list{lab}) =>
+    "the instance variable " ++
+    (lab ++
+    (" is overridden.\n" ++ "The behaviour changed in ocaml 3.10 (previous behaviour was hiding.)"))
+  | Instance_variable_override(list{cname, ...slist}) =>
+    String.concat(
+      " ",
+      list{"the following instance variables are overridden by the class", cname, ":\n ", ...slist},
+    ) ++ "\nThe behaviour changed in ocaml 3.10 (previous behaviour was hiding.)"
+  | Instance_variable_override(list{}) => assert false
+  | Illegal_backslash => "illegal backslash escape in string."
+  | Implicit_public_methods(l) =>
+    "the following private methods were made public implicitly:\n " ++
+    (String.concat(" ", l) ++
+    ".")
+  | Unerasable_optional_argument => "this optional argument cannot be erased."
+  | Undeclared_virtual_method(m) => "the virtual method " ++ (m ++ " is not declared.")
+  | Not_principal(s) => s ++ " is not principal."
+  | Without_principality(s) => s ++ " without principality."
+  | Unused_argument => "this argument will not be used by the function."
+  | Nonreturning_statement => "this statement never returns (or has an unsound type.)"
+  | Preprocessor(s) => s
+  | Useless_record_with => "all the fields are explicitly listed in this record:\n\
+       the 'with' clause is useless."
+  | Bad_module_name(modname) =>
+    "bad source file name: \"" ++ (modname ++ "\" is not a valid module name.")
+  | All_clauses_guarded => "this pattern-matching is not exhaustive.\n\
+       All clauses in this pattern-matching are guarded."
+  | Unused_var(v) | Unused_var_strict(v) => "unused variable " ++ (v ++ ".")
+  | Wildcard_arg_to_constant_constr => "wildcard pattern given as argument to a constant constructor"
+  | Eol_in_string => "unescaped end-of-line in a string constant (non-portable code)"
+  | Duplicate_definitions(kind, cname, tc1, tc2) =>
+    Printf.sprintf("the %s %s is defined in both types %s and %s.", kind, cname, tc1, tc2)
+  | Multiple_definition(modname, file1, file2) =>
+    Printf.sprintf("files %s and %s both define a module named %s", file1, file2, modname)
+  | Unused_value_declaration(v) => "unused value " ++ (v ++ ".")
+  | Unused_open(s) => "unused open " ++ (s ++ ".")
+  | Unused_type_declaration(s) => "unused type " ++ (s ++ ".")
+  | Unused_for_index(s) => "unused for-loop index " ++ (s ++ ".")
+  | Unused_ancestor(s) => "unused ancestor variable " ++ (s ++ ".")
+  | Unused_constructor(s, false, false) => "unused constructor " ++ (s ++ ".")
+  | Unused_constructor(s, true, _) =>
+    "constructor " ++
+    (s ++
+    " is never used to build values.\n\
+        (However, this constructor appears in patterns.)")
+  | Unused_constructor(s, false, true) =>
+    "constructor " ++
+    (s ++
+    " is never used to build values.\n\
+        Its type is exported as a private type.")
+  | Unused_extension(s, is_exception, cu_pattern, cu_privatize) =>
+    let kind = if is_exception {
+      "exception"
+    } else {
+      "extension constructor"
+    }
+    let name = kind ++ (" " ++ s)
+    switch (cu_pattern, cu_privatize) {
+    | (false, false) => "unused " ++ name
+    | (true, _) =>
+      name ++ " is never used to build values.\n\
+           (However, this constructor appears in patterns.)"
+    | (false, true) =>
+      name ++ " is never used to build values.\n\
+            It is exported or rebound as a private extension."
+    }
+  | Unused_rec_flag => "unused rec flag."
+  | Name_out_of_scope(ty, list{nm}, false) =>
+    nm ++
+    (" was selected from type " ++
+    (ty ++ ".\nIt is not visible in the current scope, and will not \n\
+       be selected if the type becomes unknown."))
+  | Name_out_of_scope(_, _, false) => assert false
+  | Name_out_of_scope(ty, slist, true) =>
+    "this record of type " ++
+    (ty ++
+    (" contains fields that are \n\
+       not visible in the current scope: " ++
+    (String.concat(" ", slist) ++
+    ".\n\
+       They will not be selected if the type becomes unknown.")))
+  | Ambiguous_name(list{s}, tl, false) =>
+    s ++
+    (" belongs to several types: " ++
+    (String.concat(
+      " ",
+      tl,
+    ) ++ "\nThe first one was selected. Please disambiguate if this is wrong."))
+  | Ambiguous_name(_, _, false) => assert false
+  | Ambiguous_name(_slist, tl, true) =>
+    "these field labels belong to several types: " ++
+    (String.concat(" ", tl) ++
+    "\nThe first one was selected. Please disambiguate if this is wrong.")
+  | Disambiguated_name(s) =>
+    "this use of " ++
+    (s ++
+    " relies on type-directed disambiguation,\n\
+       it will not compile with OCaml 4.00 or earlier.")
+  | Nonoptional_label(s) => "the label " ++ (s ++ " is not optional.")
+  | Open_shadow_identifier(kind, s) =>
+    Printf.sprintf(
+      "this open statement shadows the %s identifier %s (which is later used)",
+      kind,
+      s,
+    )
+  | Open_shadow_label_constructor(kind, s) =>
+    Printf.sprintf("this open statement shadows the %s %s (which is later used)", kind, s)
+  | Bad_env_variable(var, s) => Printf.sprintf("illegal environment variable %s : %s", var, s)
+  | Attribute_payload(a, s) => Printf.sprintf("illegal payload for attribute '%s'.\n%s", a, s)
+  | Eliminated_optional_arguments(sl) =>
+    Printf.sprintf(
+      "implicit elimination of optional argument%s %s",
+      if List.length(sl) == 1 {
+        ""
+      } else {
+        "s"
+      },
+      String.concat(", ", sl),
+    )
+  | No_cmi_file(name, None) => "no cmi file was found in path for module " ++ name
+  | No_cmi_file(name, Some(msg)) =>
+    Printf.sprintf("no valid cmi file was found in path for module %s. %s", name, msg)
+  | Bad_docstring(unattached) =>
+    if unattached {
+      "unattached documentation comment (ignored)"
+    } else {
+      "ambiguous documentation comment"
+    }
+  | Expect_tailcall => Printf.sprintf("expected tailcall")
+  | Fragile_literal_pattern =>
+    Printf.sprintf(
+      "Code should not depend on the actual values of\n\
+         this constructor's arguments. They are only for information\n\
+         and may change in future versions. (See manual section 8.5)",
+    )
+  | Unreachable_case => "this match case is unreachable.\n\
+       Consider replacing it with a refutation case '<pat> -> .'"
+  | Misplaced_attribute(attr_name) =>
+    Printf.sprintf("the %S attribute cannot appear in this context", attr_name)
+  | Duplicated_attribute(attr_name) =>
+    Printf.sprintf(
+      "the %S attribute is used more than once on this \
+          expression",
+      attr_name,
+    )
+  | Inlining_impossible(reason) => Printf.sprintf("Cannot inline: %s", reason)
+  | Ambiguous_pattern(vars) =>
+    let msg = {
+      let vars = List.sort(String.compare, vars)
+      switch vars {
+      | list{} => assert false
+      | list{x} => "variable " ++ x
+      | list{_, ..._} => "variables " ++ String.concat(",", vars)
+      }
+    }
+    Printf.sprintf(
+      "Ambiguous or-pattern variables under guard;\n\
+         %s may match different arguments. (See manual section 8.5)",
+      msg,
+    )
+  | No_cmx_file(name) =>
+    Printf.sprintf(
+      "no cmx file was found in path for module %s, \
+         and its interface was not compiled with -opaque",
+      name,
+    )
+  | Assignment_to_non_mutable_value => "A potential assignment to a non-mutable value was detected \n\
+        in this source file.  Such assignments may generate incorrect code \n\
+        when using Flambda."
+  | Unused_module(s) => "unused module " ++ (s ++ ".")
+  | Unboxable_type_in_prim_decl(t) =>
+    Printf.sprintf(
+      "This primitive declaration uses type %s, which is unannotated and\n\
+         unboxable. The representation of such types may change in future\n\
+         versions. You should annotate the declaration of %s with [@@boxed]\n\
+         or [@@unboxed].",
+      t,
+      t,
+    )
+  | Constraint_on_gadt => "Type constraints do not apply to GADT cases of variant types."
+  }
+
+let sub_locs = x =>
+  switch x {
+  | Deprecated(_, def, use) => list{(def, "Definition"), (use, "Expected signature")}
+  | _ => list{}
+  }
+
+let nerrors = ref(0)
+
+type reporting_information = {
+  number: int,
+  message: string,
+  is_error: bool,
+  sub_locs: list<(loc, string)>,
+}
+
+let report = w =>
+  switch is_active(w) {
+  | false => #Inactive
+  | true =>
+    if is_error(w) {
+      incr(nerrors)
+    }
+    #Active({
+      number: number(w),
+      message: message(w),
+      is_error: is_error(w),
+      sub_locs: sub_locs(w),
+    })
+  }
+
+exception Errors
+
+let reset_fatal = () => nerrors := 0
+
+let check_fatal = () =>
+  if nerrors.contents > 0 {
+    nerrors := 0
+    raise(Errors)
+  }
+
+let descriptions = list{
+  (1, "Suspicious-looking start-of-comment mark."),
+  (2, "Suspicious-looking end-of-comment mark."),
+  (3, "Deprecated feature."),
+  (
+    4,
+    "Fragile pattern matching: matching that will remain complete even\n\
+   \    if additional constructors are added to one of the variant types\n\
+   \    matched.",
+  ),
+  (
+    5,
+    "Partially applied function: expression whose result has function\n\
+   \    type and is ignored.",
+  ),
+  (6, "Label omitted in function application."),
+  (7, "Method overridden."),
+  (8, "Partial match: missing cases in pattern-matching."),
+  (9, "Missing fields in a record pattern."),
+  (
+    10,
+    "Expression on the left-hand side of a sequence that doesn't have \
+      type\n\
+   \    \"unit\" (and that is not a function, see warning number 5).",
+  ),
+  (11, "Redundant case in a pattern matching (unused match case)."),
+  (12, "Redundant sub-pattern in a pattern-matching."),
+  (13, "Instance variable overridden."),
+  (14, "Illegal backslash escape in a string constant."),
+  (15, "Private method made public implicitly."),
+  (16, "Unerasable optional argument."),
+  (17, "Undeclared virtual method."),
+  (18, "Non-principal type."),
+  (19, "Type without principality."),
+  (20, "Unused function argument."),
+  (21, "Non-returning statement."),
+  (22, "Preprocessor warning."),
+  (23, "Useless record \"with\" clause."),
+  (
+    24,
+    "Bad module name: the source file name is not a valid OCaml module \
+        name.",
+  ),
+  (25, "Deprecated: now part of warning 8."),
+  (
+    26,
+    "Suspicious unused variable: unused variable that is bound\n\
+   \    with \"let\" or \"as\", and doesn't start with an underscore (\"_\")\n\
+   \    character.",
+  ),
+  (
+    27,
+    "Innocuous unused variable: unused variable that is not bound with\n\
+   \    \"let\" nor \"as\", and doesn't start with an underscore (\"_\")\n\
+   \    character.",
+  ),
+  (28, "Wildcard pattern given as argument to a constant constructor."),
+  (29, "Unescaped end-of-line in a string constant (non-portable code)."),
+  (
+    30,
+    "Two labels or constructors of the same name are defined in two\n\
+   \    mutually recursive types.",
+  ),
+  (31, "A module is linked twice in the same executable."),
+  (32, "Unused value declaration."),
+  (33, "Unused open statement."),
+  (34, "Unused type declaration."),
+  (35, "Unused for-loop index."),
+  (36, "Unused ancestor variable."),
+  (37, "Unused constructor."),
+  (38, "Unused extension constructor."),
+  (39, "Unused rec flag."),
+  (40, "Constructor or label name used out of scope."),
+  (41, "Ambiguous constructor or label name."),
+  (42, "Disambiguated constructor or label name (compatibility warning)."),
+  (43, "Nonoptional label applied as optional."),
+  (44, "Open statement shadows an already defined identifier."),
+  (45, "Open statement shadows an already defined label or constructor."),
+  (46, "Error in environment variable."),
+  (47, "Illegal attribute payload."),
+  (48, "Implicit elimination of optional arguments."),
+  (49, "Absent cmi file when looking up module alias."),
+  (50, "Unexpected documentation comment."),
+  (51, "Warning on non-tail calls if @tailcall present."),
+  (52, "Fragile constant pattern."),
+  (53, "Attribute cannot appear in this context"),
+  (54, "Attribute used more than once on an expression"),
+  (55, "Inlining impossible"),
+  (56, "Unreachable case in a pattern-matching (based on type information)."),
+  (57, "Ambiguous or-pattern variables under guard"),
+  (58, "Missing cmx file"),
+  (59, "Assignment to non-mutable value"),
+  (60, "Unused module declaration"),
+  (61, "Unboxable type in primitive declaration"),
+  (62, "Type constraint on GADT type declaration"),
+}
+
+let help_warnings = () => {
+  List.iter(((i, s)) => Printf.printf("%3i %s\n", i, s), descriptions)
+  print_endline("  A all warnings")
+  for i in Char.code('b') to Char.code('z') {
+    let c = Char.chr(i)
+    switch letter(c) {
+    | list{} => ()
+    | list{n} => Printf.printf("  %c Alias for warning %i.\n", Char.uppercase_ascii(c), n)
+    | l =>
+      Printf.printf(
+        "  %c warnings %s.\n",
+        Char.uppercase_ascii(c),
+        String.concat(", ", List.map(string_of_int, l)),
+      )
+    }
+  }
+  exit(0)
+}
+
diff --git a/analysis/examples/workspace-project/.gitignore b/analysis/examples/workspace-project/.gitignore
new file mode 100644
index 0000000000..f64998a524
--- /dev/null
+++ b/analysis/examples/workspace-project/.gitignore
@@ -0,0 +1,27 @@
+*.exe
+*.obj
+*.out
+*.compile
+*.native
+*.byte
+*.cmo
+*.annot
+*.cmi
+*.cmx
+*.cmt
+*.cmti
+*.cma
+*.a
+*.cmxa
+*.obj
+*~
+*.annot
+*.cmj
+*.bak
+lib
+*.mlast
+*.mliast
+.vscode
+.merlin
+.bsb.lock
+/node_modules/
diff --git a/analysis/examples/workspace-project/README.md b/analysis/examples/workspace-project/README.md
new file mode 100644
index 0000000000..510d030baf
--- /dev/null
+++ b/analysis/examples/workspace-project/README.md
@@ -0,0 +1,7 @@
+# ReScript workspaces example (monorepo)
+
+```
+npm install
+npm run build
+npm run start
+```
diff --git a/analysis/examples/workspace-project/app/bsconfig.json b/analysis/examples/workspace-project/app/bsconfig.json
new file mode 100644
index 0000000000..0b1f288e0e
--- /dev/null
+++ b/analysis/examples/workspace-project/app/bsconfig.json
@@ -0,0 +1,11 @@
+{
+  "name": "app",
+  "bsc-flags": ["-open Common"],
+  "bs-dependencies": ["common", "myplugin"],
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ]
+}
diff --git a/analysis/examples/workspace-project/app/package.json b/analysis/examples/workspace-project/app/package.json
new file mode 100644
index 0000000000..ed2ae7a672
--- /dev/null
+++ b/analysis/examples/workspace-project/app/package.json
@@ -0,0 +1,4 @@
+{
+	"name": "app",
+	"version": "0.1.0"
+}
\ No newline at end of file
diff --git a/analysis/examples/workspace-project/app/src/App.res b/analysis/examples/workspace-project/app/src/App.res
new file mode 100644
index 0000000000..fd6b0301df
--- /dev/null
+++ b/analysis/examples/workspace-project/app/src/App.res
@@ -0,0 +1 @@
+let _ = Myplugin.Promise.resolve(Utils.printError("Oh no!"))
diff --git a/analysis/examples/workspace-project/bsconfig.json b/analysis/examples/workspace-project/bsconfig.json
new file mode 100644
index 0000000000..6c11170e58
--- /dev/null
+++ b/analysis/examples/workspace-project/bsconfig.json
@@ -0,0 +1,12 @@
+{
+  "name": "workspace-project",
+  "version": "0.1.0",
+  "sources": [],
+  "package-specs": {
+    "module": "es6",
+    "in-source": true
+  },
+  "suffix": ".mjs",
+  "pinned-dependencies": ["common", "myplugin", "app"],
+  "bs-dependencies": ["common", "myplugin", "app"]
+}
diff --git a/analysis/examples/workspace-project/common/bsconfig.json b/analysis/examples/workspace-project/common/bsconfig.json
new file mode 100644
index 0000000000..4684ee44d5
--- /dev/null
+++ b/analysis/examples/workspace-project/common/bsconfig.json
@@ -0,0 +1,10 @@
+{
+  "name": "common",
+  "namespace": true,
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ]
+}
diff --git a/analysis/examples/workspace-project/common/package.json b/analysis/examples/workspace-project/common/package.json
new file mode 100644
index 0000000000..96d8796a92
--- /dev/null
+++ b/analysis/examples/workspace-project/common/package.json
@@ -0,0 +1,4 @@
+{
+	"name": "common",
+	"version": "0.1.0"
+}
\ No newline at end of file
diff --git a/analysis/examples/workspace-project/common/src/Utils.mjs b/analysis/examples/workspace-project/common/src/Utils.mjs
new file mode 100644
index 0000000000..db6ed63a96
--- /dev/null
+++ b/analysis/examples/workspace-project/common/src/Utils.mjs
@@ -0,0 +1,14 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+
+function printError(error) {
+  var errorMessage = "\u001b[31m" + error;
+  console.log(errorMessage);
+  
+}
+
+export {
+  printError ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/workspace-project/common/src/Utils.res b/analysis/examples/workspace-project/common/src/Utils.res
new file mode 100644
index 0000000000..4ead581095
--- /dev/null
+++ b/analysis/examples/workspace-project/common/src/Utils.res
@@ -0,0 +1,4 @@
+let printError = error => {
+  let errorMessage = `\u001b[31m${error}`
+  Js.log(errorMessage)
+}
diff --git a/analysis/examples/workspace-project/myplugin/bsconfig.json b/analysis/examples/workspace-project/myplugin/bsconfig.json
new file mode 100644
index 0000000000..8f7f2f18e0
--- /dev/null
+++ b/analysis/examples/workspace-project/myplugin/bsconfig.json
@@ -0,0 +1,10 @@
+{
+  "name": "myplugin",
+  "namespace": true,
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ]
+}
diff --git a/analysis/examples/workspace-project/myplugin/package.json b/analysis/examples/workspace-project/myplugin/package.json
new file mode 100644
index 0000000000..512360dcfa
--- /dev/null
+++ b/analysis/examples/workspace-project/myplugin/package.json
@@ -0,0 +1,4 @@
+{
+	"name": "myplugin",
+	"version": "0.1.0"
+}
\ No newline at end of file
diff --git a/analysis/examples/workspace-project/myplugin/src/Promise.mjs b/analysis/examples/workspace-project/myplugin/src/Promise.mjs
new file mode 100644
index 0000000000..a6e74917af
--- /dev/null
+++ b/analysis/examples/workspace-project/myplugin/src/Promise.mjs
@@ -0,0 +1,20 @@
+// Generated by ReScript, PLEASE EDIT WITH CARE
+
+import * as Curry from "rescript/lib/es6/curry.js";
+import * as Js_exn from "rescript/lib/es6/js_exn.js";
+import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
+
+var JsError = /* @__PURE__ */Caml_exceptions.create("Promise-Myplugin.JsError");
+
+function $$catch(promise, callback) {
+  return promise.catch(function (err) {
+              return Curry._1(callback, Js_exn.anyToExnInternal(err));
+            });
+}
+
+export {
+  JsError ,
+  $$catch ,
+  
+}
+/* No side effect */
diff --git a/analysis/examples/workspace-project/myplugin/src/Promise.res b/analysis/examples/workspace-project/myplugin/src/Promise.res
new file mode 100644
index 0000000000..6a72bfffd9
--- /dev/null
+++ b/analysis/examples/workspace-project/myplugin/src/Promise.res
@@ -0,0 +1,46 @@
+type t<+'a> = Js.Promise.t<'a>
+
+exception JsError(Js.Exn.t)
+external unsafeToJsExn: exn => Js.Exn.t = "%identity"
+
+@new
+external make: ((@uncurry (. 'a) => unit, (. 'e) => unit) => unit) => t<'a> = "Promise"
+
+@val @scope("Promise")
+external resolve: 'a => t<'a> = "resolve"
+
+@send external then: (t<'a>, @uncurry ('a => t<'b>)) => t<'b> = "then"
+
+@send
+external thenResolve: (t<'a>, @uncurry ('a => 'b)) => t<'b> = "then"
+
+@send external finally: (t<'a>, unit => unit) => t<'a> = "finally"
+
+@scope("Promise") @val
+external reject: exn => t<_> = "reject"
+
+@scope("Promise") @val
+external all: array<t<'a>> => t<array<'a>> = "all"
+
+@scope("Promise") @val
+external all2: ((t<'a>, t<'b>)) => t<('a, 'b)> = "all"
+
+@scope("Promise") @val
+external all3: ((t<'a>, t<'b>, t<'c>)) => t<('a, 'b, 'c)> = "all"
+
+@scope("Promise") @val
+external all4: ((t<'a>, t<'b>, t<'c>, t<'d>)) => t<('a, 'b, 'c, 'd)> = "all"
+
+@scope("Promise") @val
+external all5: ((t<'a>, t<'b>, t<'c>, t<'d>, t<'e>)) => t<('a, 'b, 'c, 'd, 'e)> = "all"
+
+@scope("Promise") @val
+external all6: ((t<'a>, t<'b>, t<'c>, t<'d>, t<'e>, t<'f>)) => t<('a, 'b, 'c, 'd, 'e, 'f)> = "all"
+
+@send
+external _catch: (t<'a>, @uncurry (exn => t<'a>)) => t<'a> = "catch"
+
+let catch = (promise, callback) => _catch(promise, err => callback(Js.Exn.anyToExnInternal(err)))
+
+@scope("Promise") @val
+external race: array<t<'a>> => t<'a> = "race"
diff --git a/analysis/examples/workspace-project/package-lock.json b/analysis/examples/workspace-project/package-lock.json
new file mode 100644
index 0000000000..b43a26c10d
--- /dev/null
+++ b/analysis/examples/workspace-project/package-lock.json
@@ -0,0 +1,14 @@
+{
+  "name": "workspace-project",
+  "version": "0.1.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "rescript": {
+      "version": "10.0.0-nightly.1",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.0.0-nightly.1.tgz",
+      "integrity": "sha512-hoFSGfxgQN0Vo4iYilseNcIGM40ztBroYWriOl+TdC8rXb64usbR/TqNYcacbWLn/cIydxwMCQBRA07BY1AoTA==",
+      "dev": true
+    }
+  }
+}
diff --git a/analysis/examples/workspace-project/package.json b/analysis/examples/workspace-project/package.json
new file mode 100644
index 0000000000..1a7f5508d1
--- /dev/null
+++ b/analysis/examples/workspace-project/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "workspace-project",
+  "private": true,
+  "version": "0.1.0",
+  "scripts": {
+    "clean": "rescript clean",
+    "build": "rescript build -with-deps",
+    "start": "node app/src/App.mjs",
+    "watch": "rescript build -w"
+  },
+  "keywords": [
+    "ReScript"
+  ],
+  "author": "",
+  "license": "MIT",
+  "devDependencies": {
+    "rescript": "^10.0.0-beta.3"
+  },
+  "workspaces": {
+    "packages": [
+      "app",
+      "common",
+      "myplugin"
+    ]
+  }
+}
diff --git a/analysis/reanalyze/dune b/analysis/reanalyze/dune
new file mode 100644
index 0000000000..ba31389588
--- /dev/null
+++ b/analysis/reanalyze/dune
@@ -0,0 +1 @@
+(dirs src)
diff --git a/analysis/reanalyze/src/Annotation.ml b/analysis/reanalyze/src/Annotation.ml
new file mode 100644
index 0000000000..3e6429fb4a
--- /dev/null
+++ b/analysis/reanalyze/src/Annotation.ml
@@ -0,0 +1,99 @@
+type attributePayload =
+  | BoolPayload of bool
+  | ConstructPayload of string
+  | FloatPayload of string
+  | IdentPayload of Longident.t
+  | IntPayload of string
+  | StringPayload of string
+  | TuplePayload of attributePayload list
+  | UnrecognizedPayload
+
+let tagIsGenType s = s = "genType" || s = "gentype"
+let tagIsGenTypeImport s = s = "genType.import" || s = "gentype.import"
+let tagIsGenTypeOpaque s = s = "genType.opaque" || s = "gentype.opaque"
+
+let tagIsOneOfTheGenTypeAnnotations s =
+  tagIsGenType s || tagIsGenTypeImport s || tagIsGenTypeOpaque s
+
+let rec getAttributePayload checkText (attributes : Typedtree.attributes) =
+  let rec fromExpr (expr : Parsetree.expression) =
+    match expr with
+    | {pexp_desc = Pexp_constant (Pconst_string (s, _))} ->
+      Some (StringPayload s)
+    | {pexp_desc = Pexp_constant (Pconst_integer (n, _))} -> Some (IntPayload n)
+    | {pexp_desc = Pexp_constant (Pconst_float (s, _))} -> Some (FloatPayload s)
+    | {
+     pexp_desc = Pexp_construct ({txt = Lident (("true" | "false") as s)}, _);
+     _;
+    } ->
+      Some (BoolPayload (s = "true"))
+    | {pexp_desc = Pexp_construct ({txt = Longident.Lident "[]"}, None)} -> None
+    | {pexp_desc = Pexp_construct ({txt = Longident.Lident "::"}, Some e)} ->
+      fromExpr e
+    | {pexp_desc = Pexp_construct ({txt}, _); _} ->
+      Some (ConstructPayload (txt |> Longident.flatten |> String.concat "."))
+    | {pexp_desc = Pexp_tuple exprs | Pexp_array exprs} ->
+      let payloads =
+        exprs |> List.rev
+        |> List.fold_left
+             (fun payloads expr ->
+               match expr |> fromExpr with
+               | Some payload -> payload :: payloads
+               | None -> payloads)
+             []
+      in
+      Some (TuplePayload payloads)
+    | {pexp_desc = Pexp_ident {txt}} -> Some (IdentPayload txt)
+    | _ -> None
+  in
+  match attributes with
+  | [] -> None
+  | ({Asttypes.txt}, payload) :: tl ->
+    if checkText txt then
+      match payload with
+      | PStr [] -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_eval (expr, _)} :: _) -> expr |> fromExpr
+      | PStr ({pstr_desc = Pstr_extension _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_value _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_primitive _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_type _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_typext _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_exception _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_module _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_recmodule _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_modtype _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_open _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_class _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_class_type _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_include _} :: _) -> Some UnrecognizedPayload
+      | PStr ({pstr_desc = Pstr_attribute _} :: _) -> Some UnrecognizedPayload
+      | PPat _ -> Some UnrecognizedPayload
+      | PSig _ -> Some UnrecognizedPayload
+      | PTyp _ -> Some UnrecognizedPayload
+    else getAttributePayload checkText tl
+
+let hasAttribute checkText (attributes : Typedtree.attributes) =
+  getAttributePayload checkText attributes <> None
+
+let isOcamlSuppressDeadWarning attributes =
+  match
+    attributes
+    |> getAttributePayload (fun x -> x = "ocaml.warning" || x = "warning")
+  with
+  | Some (StringPayload s) ->
+    let numeric =
+      match Str.search_forward (Str.regexp (Str.quote "-32")) s 0 with
+      | _ -> true
+      | exception Not_found -> false
+    in
+    let textual =
+      match
+        Str.search_forward
+          (Str.regexp (Str.quote "-unused-value-declaration"))
+          s 0
+      with
+      | _ -> true
+      | exception Not_found -> false
+    in
+    numeric || textual
+  | _ -> false
diff --git a/analysis/reanalyze/src/Arnold.ml b/analysis/reanalyze/src/Arnold.ml
new file mode 100644
index 0000000000..64f10b1f12
--- /dev/null
+++ b/analysis/reanalyze/src/Arnold.ml
@@ -0,0 +1,1421 @@
+let printPos ppf (pos : Lexing.position) =
+  let file = pos.Lexing.pos_fname in
+  let line = pos.Lexing.pos_lnum in
+  Format.fprintf ppf "@{<filename>%s@} @{<dim>%i@}"
+    (file |> Filename.basename)
+    line
+
+module StringSet = Set.Make (String)
+
+(** Type Definitions *)
+module FunctionName = struct
+  type t = string
+end
+
+module FunctionArgs = struct
+  type arg = {label: string; functionName: FunctionName.t}
+  type t = arg list
+
+  let empty = []
+  let argToString {label; functionName} = label ^ ":" ^ functionName
+
+  let toString functionArgs =
+    match functionArgs = [] with
+    | true -> ""
+    | false ->
+      "<" ^ (functionArgs |> List.map argToString |> String.concat ",") ^ ">"
+
+  let find (t : t) ~label =
+    match t |> List.find_opt (fun arg -> arg.label = label) with
+    | Some {functionName} -> Some functionName
+    | None -> None
+
+  let compareArg a1 a2 =
+    let n = compare a1.label a2.label in
+    if n <> 0 then n else compare a1.functionName a2.functionName
+
+  let rec compare l1 l2 =
+    match (l1, l2) with
+    | [], [] -> 0
+    | [], _ :: _ -> -1
+    | _ :: _, [] -> 1
+    | x1 :: l1, x2 :: l2 ->
+      let n = compareArg x1 x2 in
+      if n <> 0 then n else compare l1 l2
+end
+
+module FunctionCall = struct
+  type t = {functionName: FunctionName.t; functionArgs: FunctionArgs.t}
+
+  let substituteName ~sub name =
+    match sub |> FunctionArgs.find ~label:name with
+    | Some functionName -> functionName
+    | None -> name
+
+  let applySubstitution ~(sub : FunctionArgs.t) (t : t) =
+    if sub = [] then t
+    else
+      {
+        functionName = t.functionName |> substituteName ~sub;
+        functionArgs =
+          t.functionArgs
+          |> List.map (fun (arg : FunctionArgs.arg) ->
+                 {
+                   arg with
+                   functionName = arg.functionName |> substituteName ~sub;
+                 });
+      }
+
+  let noArgs functionName = {functionName; functionArgs = []}
+
+  let toString {functionName; functionArgs} =
+    functionName ^ FunctionArgs.toString functionArgs
+
+  let compare (x1 : t) x2 =
+    let n = compare x1.functionName x2.functionName in
+    if n <> 0 then n else FunctionArgs.compare x1.functionArgs x2.functionArgs
+end
+
+module FunctionCallSet = Set.Make (FunctionCall)
+
+module Stats = struct
+  let nCacheChecks = ref 0
+  let nCacheHits = ref 0
+  let nFiles = ref 0
+  let nFunctions = ref 0
+  let nHygieneErrors = ref 0
+  let nInfiniteLoops = ref 0
+  let nRecursiveBlocks = ref 0
+
+  let print ppf () =
+    Format.fprintf ppf "@[<v 2>@,@{<warning>Termination Analysis Stats@}@,";
+    Format.fprintf ppf "Files:@{<dim>%d@}@," !nFiles;
+    Format.fprintf ppf "Recursive Blocks:@{<dim>%d@}@," !nRecursiveBlocks;
+    Format.fprintf ppf "Functions:@{<dim>%d@}@," !nFunctions;
+    Format.fprintf ppf "Infinite Loops:@{<dim>%d@}@," !nInfiniteLoops;
+    Format.fprintf ppf "Hygiene Errors:@{<dim>%d@}@," !nHygieneErrors;
+    Format.fprintf ppf "Cache Hits:@{<dim>%d@}/@{<dim>%d@}@," !nCacheHits
+      !nCacheChecks;
+    Format.fprintf ppf "@]"
+
+  let dump ~ppf = Format.fprintf ppf "%a@." print ()
+  let newFile () = incr nFiles
+
+  let newRecursiveFunctions ~numFunctions =
+    incr nRecursiveBlocks;
+    nFunctions := !nFunctions + numFunctions
+
+  let logLoop () = incr nInfiniteLoops
+
+  let logCache ~functionCall ~hit ~loc =
+    incr nCacheChecks;
+    if hit then incr nCacheHits;
+    if !Common.Cli.debug then
+      Log_.warning ~forStats:false ~loc
+        (Termination
+           {
+             termination = TerminationAnalysisInternal;
+             message =
+               Format.asprintf "Cache %s for @{<info>%s@}"
+                 (match hit with
+                 | true -> "hit"
+                 | false -> "miss")
+                 (FunctionCall.toString functionCall);
+           })
+
+  let logResult ~functionCall ~loc ~resString =
+    if !Common.Cli.debug then
+      Log_.warning ~forStats:false ~loc
+        (Termination
+           {
+             termination = TerminationAnalysisInternal;
+             message =
+               Format.asprintf "@{<info>%s@} returns %s"
+                 (FunctionCall.toString functionCall)
+                 resString;
+           })
+
+  let logHygieneParametric ~functionName ~loc =
+    incr nHygieneErrors;
+    Log_.error ~loc
+      (Termination
+         {
+           termination = ErrorHygiene;
+           message =
+             Format.asprintf
+               "@{<error>%s@} cannot be analyzed directly as it is parametric"
+               functionName;
+         })
+
+  let logHygieneOnlyCallDirectly ~path ~loc =
+    incr nHygieneErrors;
+    Log_.error ~loc
+      (Termination
+         {
+           termination = ErrorHygiene;
+           message =
+             Format.asprintf
+               "@{<error>%s@} can only be called directly, or passed as \
+                labeled argument"
+               (Path.name path);
+         })
+
+  let logHygieneMustHaveNamedArgument ~label ~loc =
+    incr nHygieneErrors;
+    Log_.error ~loc
+      (Termination
+         {
+           termination = ErrorHygiene;
+           message =
+             Format.asprintf "Call must have named argument @{<error>%s@}" label;
+         })
+
+  let logHygieneNamedArgValue ~label ~loc =
+    incr nHygieneErrors;
+    Log_.error ~loc
+      (Termination
+         {
+           termination = ErrorHygiene;
+           message =
+             Format.asprintf
+               "Named argument @{<error>%s@} must be passed a recursive \
+                function"
+               label;
+         })
+
+  let logHygieneNoNestedLetRec ~loc =
+    incr nHygieneErrors;
+    Log_.error ~loc
+      (Termination
+         {
+           termination = ErrorHygiene;
+           message = Format.asprintf "Nested multiple let rec not supported yet";
+         })
+end
+
+module Progress = struct
+  type t = Progress | NoProgress
+
+  let toString progress =
+    match progress = Progress with
+    | true -> "Progress"
+    | false -> "NoProgress"
+end
+
+module Call = struct
+  type progressFunction = Path.t
+
+  type t =
+    | FunctionCall of FunctionCall.t
+    | ProgressFunction of progressFunction
+
+  let toString call =
+    match call with
+    | ProgressFunction progressFunction -> "+" ^ Path.name progressFunction
+    | FunctionCall functionCall -> FunctionCall.toString functionCall
+end
+
+module Trace = struct
+  type retOption = Rsome | Rnone
+
+  type t =
+    | Tcall of Call.t * Progress.t
+    | Tnondet of t list
+    | Toption of retOption
+    | Tseq of t list
+
+  let empty = Tseq []
+
+  let nd (t1 : t) (t2 : t) : t =
+    match (t1, t2) with
+    | Tnondet l1, Tnondet l2 -> Tnondet (l1 @ l2)
+    | _, Tnondet l2 -> Tnondet (t1 :: l2)
+    | Tnondet l1, _ -> Tnondet (l1 @ [t2])
+    | _ -> Tnondet [t1; t2]
+
+  let seq (t1 : t) (t2 : t) : t =
+    match (t1, t2) with
+    | Tseq l1, Tseq l2 -> Tseq (l1 @ l2)
+    | _, Tseq l2 -> Tseq (t1 :: l2)
+    | Tseq l1, _ -> Tseq (l1 @ [t2])
+    | _ -> Tseq [t1; t2]
+
+  let some = Toption Rsome
+  let none = Toption Rnone
+
+  let retOptionToString r =
+    match r = Rsome with
+    | true -> "Some"
+    | false -> "None"
+
+  let rec toString trace =
+    match trace with
+    | Tcall (ProgressFunction progressFunction, progress) ->
+      Path.name progressFunction ^ ":" ^ Progress.toString progress
+    | Tcall (FunctionCall functionCall, progress) ->
+      FunctionCall.toString functionCall ^ ":" ^ Progress.toString progress
+    | Tnondet traces ->
+      "[" ^ (traces |> List.map toString |> String.concat " || ") ^ "]"
+    | Toption retOption -> retOption |> retOptionToString
+    | Tseq traces -> (
+      let tracesNotEmpty = traces |> List.filter (( <> ) empty) in
+      match tracesNotEmpty with
+      | [] -> "_"
+      | [t] -> t |> toString
+      | _ :: _ -> tracesNotEmpty |> List.map toString |> String.concat "; ")
+end
+
+module Values : sig
+  type t
+
+  val getNone : t -> Progress.t option
+  val getSome : t -> Progress.t option
+  val nd : t -> t -> t
+  val none : progress:Progress.t -> t
+  val some : progress:Progress.t -> t
+  val toString : t -> string
+end = struct
+  type t = {none: Progress.t option; some: Progress.t option}
+
+  let getNone {none} = none
+  let getSome {some} = some
+
+  let toString x =
+    ((match x.some with
+     | None -> []
+     | Some p -> ["some: " ^ Progress.toString p])
+    @
+    match x.none with
+    | None -> []
+    | Some p -> ["none: " ^ Progress.toString p])
+    |> String.concat ", "
+
+  let none ~progress = {none = Some progress; some = None}
+  let some ~progress = {none = None; some = Some progress}
+
+  let nd (v1 : t) (v2 : t) : t =
+    let combine x y =
+      match (x, y) with
+      | Some progress1, Some progress2 ->
+        Some
+          (match progress1 = Progress.Progress && progress2 = Progress with
+          | true -> Progress.Progress
+          | false -> NoProgress)
+      | None, progressOpt | progressOpt, None -> progressOpt
+    in
+    let none = combine v1.none v2.none in
+    let some = combine v1.some v2.some in
+    {none; some}
+end
+
+module State = struct
+  type t = {progress: Progress.t; trace: Trace.t; valuesOpt: Values.t option}
+
+  let toString {progress; trace; valuesOpt} =
+    let progressStr =
+      match valuesOpt with
+      | None -> progress |> Progress.toString
+      | Some values -> "{" ^ (values |> Values.toString) ^ "}"
+    in
+    progressStr ^ " with trace " ^ Trace.toString trace
+
+  let init ?(progress = Progress.NoProgress) ?(trace = Trace.empty)
+      ?(valuesOpt = None) () =
+    {progress; trace; valuesOpt}
+
+  let seq s1 s2 =
+    let progress =
+      match s1.progress = Progress || s2.progress = Progress with
+      | true -> Progress.Progress
+      | false -> NoProgress
+    in
+    let trace = Trace.seq s1.trace s2.trace in
+    let valuesOpt = s2.valuesOpt in
+    {progress; trace; valuesOpt}
+
+  let sequence states =
+    match states with
+    | [] -> assert false
+    | s :: nextStates -> List.fold_left seq s nextStates
+
+  let nd s1 s2 =
+    let progress =
+      match s1.progress = Progress && s2.progress = Progress with
+      | true -> Progress.Progress
+      | false -> NoProgress
+    in
+    let trace = Trace.nd s1.trace s2.trace in
+    let valuesOpt =
+      match (s1.valuesOpt, s2.valuesOpt) with
+      | None, valuesOpt -> (
+        match s1.progress = Progress with
+        | true -> valuesOpt
+        | false -> None)
+      | valuesOpt, None -> (
+        match s2.progress = Progress with
+        | true -> valuesOpt
+        | false -> None)
+      | Some values1, Some values2 -> Some (Values.nd values1 values2)
+    in
+    {progress; trace; valuesOpt}
+
+  let nondet states =
+    match states with
+    | [] -> assert false
+    | s :: nextStates -> List.fold_left nd s nextStates
+
+  let unorderedSequence states = {(states |> sequence) with valuesOpt = None}
+
+  let none ~progress =
+    init ~progress ~trace:Trace.none
+      ~valuesOpt:(Some (Values.none ~progress))
+      ()
+
+  let some ~progress =
+    init ~progress ~trace:Trace.some
+      ~valuesOpt:(Some (Values.some ~progress))
+      ()
+end
+
+module Command = struct
+  type progress = Progress.t
+  type retOption = Trace.retOption
+
+  type t =
+    | Call of Call.t * Location.t
+    | ConstrOption of retOption
+    | Nondet of t list
+    | Nothing
+    | Sequence of t list
+    | SwitchOption of {
+        functionCall: FunctionCall.t;
+        loc: Location.t;
+        some: t;
+        none: t;
+      }
+    | UnorderedSequence of t list
+
+  let rec toString command =
+    match command with
+    | Call (call, _pos) -> call |> Call.toString
+    | ConstrOption r -> r |> Trace.retOptionToString
+    | Nondet commands ->
+      "[" ^ (commands |> List.map toString |> String.concat " || ") ^ "]"
+    | Nothing -> "_"
+    | Sequence commands -> commands |> List.map toString |> String.concat "; "
+    | SwitchOption {functionCall; some = cSome; none = cNone} ->
+      "switch "
+      ^ FunctionCall.toString functionCall
+      ^ " {some: " ^ toString cSome ^ ", none: " ^ toString cNone ^ "}"
+    | UnorderedSequence commands ->
+      "{" ^ (commands |> List.map toString |> String.concat ", ") ^ "}"
+
+  let nothing = Nothing
+
+  let nondet commands =
+    let rec loop commands =
+      match commands with
+      | [] -> nothing
+      | Nondet commands :: rest -> loop (commands @ rest)
+      | [command] -> command
+      | _ -> Nondet commands
+    in
+    loop commands
+
+  let sequence commands =
+    let rec loop acc commands =
+      match commands with
+      | [] -> List.rev acc
+      | Nothing :: cs when cs <> [] -> loop acc cs
+      | Sequence cs1 :: cs2 -> loop acc (cs1 @ cs2)
+      | c :: cs -> loop (c :: acc) cs
+    in
+    match loop [] commands with
+    | [c] -> c
+    | cs -> Sequence cs
+
+  let ( +++ ) c1 c2 = sequence [c1; c2]
+
+  let unorderedSequence commands =
+    let relevantCommands = commands |> List.filter (fun x -> x <> nothing) in
+    match relevantCommands with
+    | [] -> nothing
+    | [c] -> c
+    | _ :: _ :: _ -> UnorderedSequence relevantCommands
+end
+
+module Kind = struct
+  type t = entry list
+  and entry = {label: string; k: t}
+
+  let empty = ([] : t)
+
+  let hasLabel ~label (k : t) =
+    k |> List.exists (fun entry -> entry.label = label)
+
+  let rec entryToString {label; k} =
+    match k = [] with
+    | true -> label
+    | false -> label ^ ":" ^ (k |> toString)
+
+  and toString (kind : t) =
+    match kind = [] with
+    | true -> ""
+    | false ->
+      "<" ^ (kind |> List.map entryToString |> String.concat ", ") ^ ">"
+
+  let addLabelWithEmptyKind ~label kind =
+    if not (kind |> hasLabel ~label) then
+      {label; k = empty} :: kind |> List.sort compare
+    else kind
+end
+
+module FunctionTable = struct
+  type functionDefinition = {
+    mutable body: Command.t option;
+    mutable kind: Kind.t;
+  }
+
+  type t = (FunctionName.t, functionDefinition) Hashtbl.t
+
+  let create () : t = Hashtbl.create 1
+
+  let print ppf (tbl : t) =
+    Format.fprintf ppf "@[<v 2>@,@{<warning>Function Table@}";
+    let definitions =
+      Hashtbl.fold
+        (fun functionName {kind; body} definitions ->
+          (functionName, kind, body) :: definitions)
+        tbl []
+      |> List.sort (fun (fn1, _, _) (fn2, _, _) -> String.compare fn1 fn2)
+    in
+    definitions
+    |> List.iteri (fun i (functionName, kind, body) ->
+           Format.fprintf ppf "@,@{<dim>%d@} @{<info>%s%s@}: %s" (i + 1)
+             functionName (Kind.toString kind)
+             (match body with
+             | Some command -> Command.toString command
+             | None -> "None"));
+    Format.fprintf ppf "@]"
+
+  let dump tbl = Format.fprintf Format.std_formatter "%a@." print tbl
+  let initialFunctionDefinition () = {kind = Kind.empty; body = None}
+
+  let getFunctionDefinition ~functionName (tbl : t) =
+    try Hashtbl.find tbl functionName with Not_found -> assert false
+
+  let isInFunctionInTable ~functionTable path =
+    Hashtbl.mem functionTable (Path.name path)
+
+  let addFunction ~functionName (tbl : t) =
+    if Hashtbl.mem tbl functionName then assert false;
+    Hashtbl.replace tbl functionName (initialFunctionDefinition ())
+
+  let addLabelToKind ~functionName ~label (tbl : t) =
+    let functionDefinition = tbl |> getFunctionDefinition ~functionName in
+    functionDefinition.kind <-
+      functionDefinition.kind |> Kind.addLabelWithEmptyKind ~label
+
+  let addBody ~body ~functionName (tbl : t) =
+    let functionDefinition = tbl |> getFunctionDefinition ~functionName in
+    functionDefinition.body <- body
+
+  let functionGetKindOfLabel ~functionName ~label (tbl : t) =
+    match Hashtbl.find tbl functionName with
+    | {kind} -> (
+      match kind |> Kind.hasLabel ~label with
+      | true -> Some Kind.empty
+      | false -> None)
+    | exception Not_found -> None
+end
+
+module FindFunctionsCalled = struct
+  let traverseExpr ~callees =
+    let super = Tast_mapper.default in
+    let expr (self : Tast_mapper.mapper) (e : Typedtree.expression) =
+      (match e.exp_desc with
+      | Texp_apply ({exp_desc = Texp_ident (callee, _, _)}, _args) ->
+        let functionName = Path.name callee in
+        callees := !callees |> StringSet.add functionName
+      | _ -> ());
+      super.expr self e
+    in
+    {super with Tast_mapper.expr}
+
+  let findCallees (expression : Typedtree.expression) =
+    let isFunction =
+      match expression.exp_desc with
+      | Texp_function _ -> true
+      | _ -> false
+    in
+    let callees = ref StringSet.empty in
+    let traverseExpr = traverseExpr ~callees in
+    if isFunction then expression |> traverseExpr.expr traverseExpr |> ignore;
+    !callees
+end
+
+module ExtendFunctionTable = struct
+  (* Add functions passed a recursive function via a labeled argument,
+     and functions calling progress functions, to the function table. *)
+  let extractLabelledArgument ?(kindOpt = None)
+      (argOpt : Typedtree.expression option) =
+    match argOpt with
+    | Some {exp_desc = Texp_ident (path, {loc}, _)} -> Some (path, loc)
+    | Some
+        {
+          exp_desc =
+            Texp_let
+              ( Nonrecursive,
+                [
+                  {
+                    vb_pat = {pat_desc = Tpat_var (_, _)};
+                    vb_expr = {exp_desc = Texp_ident (path, {loc}, _)};
+                    vb_loc = {loc_ghost = true};
+                  };
+                ],
+                _ );
+        } ->
+      Some (path, loc)
+    | Some
+        {exp_desc = Texp_apply ({exp_desc = Texp_ident (path, {loc}, _)}, args)}
+      when kindOpt <> None ->
+      let checkArg ((argLabel : Asttypes.arg_label), _argOpt) =
+        match (argLabel, kindOpt) with
+        | (Labelled l | Optional l), Some kind ->
+          kind |> List.for_all (fun {Kind.label} -> label <> l)
+        | _ -> true
+      in
+      if args |> List.for_all checkArg then Some (path, loc) else None
+    | _ -> None
+
+  let traverseExpr ~functionTable ~progressFunctions ~valueBindingsTable =
+    let super = Tast_mapper.default in
+    let expr (self : Tast_mapper.mapper) (e : Typedtree.expression) =
+      (match e.exp_desc with
+      | Texp_ident (callee, _, _) -> (
+        let loc = e.exp_loc in
+        match Hashtbl.find_opt valueBindingsTable (Path.name callee) with
+        | None -> ()
+        | Some (id_pos, _, callees) ->
+          if
+            not
+              (StringSet.is_empty
+                 (StringSet.inter (Lazy.force callees) progressFunctions))
+          then
+            let functionName = Path.name callee in
+            if not (callee |> FunctionTable.isInFunctionInTable ~functionTable)
+            then (
+              functionTable |> FunctionTable.addFunction ~functionName;
+              if !Common.Cli.debug then
+                Log_.warning ~forStats:false ~loc
+                  (Termination
+                     {
+                       termination = TerminationAnalysisInternal;
+                       message =
+                         Format.asprintf
+                           "Extend Function Table with @{<info>%s@} (%a) as it \
+                            calls a progress function"
+                           functionName printPos id_pos;
+                     })))
+      | Texp_apply ({exp_desc = Texp_ident (callee, _, _)}, args)
+        when callee |> FunctionTable.isInFunctionInTable ~functionTable ->
+        let functionName = Path.name callee in
+        args
+        |> List.iter (fun ((argLabel : Asttypes.arg_label), argOpt) ->
+               match (argLabel, argOpt |> extractLabelledArgument) with
+               | Labelled label, Some (path, loc)
+                 when path |> FunctionTable.isInFunctionInTable ~functionTable
+                 ->
+                 functionTable
+                 |> FunctionTable.addLabelToKind ~functionName ~label;
+                 if !Common.Cli.debug then
+                   Log_.warning ~forStats:false ~loc
+                     (Termination
+                        {
+                          termination = TerminationAnalysisInternal;
+                          message =
+                            Format.asprintf
+                              "@{<info>%s@} is parametric \
+                               ~@{<info>%s@}=@{<info>%s@}"
+                              functionName label (Path.name path);
+                        })
+               | _ -> ())
+      | _ -> ());
+      super.expr self e
+    in
+    {super with Tast_mapper.expr}
+
+  let run ~functionTable ~progressFunctions ~valueBindingsTable
+      (expression : Typedtree.expression) =
+    let traverseExpr =
+      traverseExpr ~functionTable ~progressFunctions ~valueBindingsTable
+    in
+    expression |> traverseExpr.expr traverseExpr |> ignore
+end
+
+module CheckExpressionWellFormed = struct
+  let traverseExpr ~functionTable ~valueBindingsTable =
+    let super = Tast_mapper.default in
+    let checkIdent ~path ~loc =
+      if path |> FunctionTable.isInFunctionInTable ~functionTable then
+        Stats.logHygieneOnlyCallDirectly ~path ~loc
+    in
+    let expr (self : Tast_mapper.mapper) (e : Typedtree.expression) =
+      match e.exp_desc with
+      | Texp_ident (path, {loc}, _) ->
+        checkIdent ~path ~loc;
+        e
+      | Texp_apply ({exp_desc = Texp_ident (functionPath, _, _)}, args) ->
+        let functionName = Path.name functionPath in
+        args
+        |> List.iter (fun ((argLabel : Asttypes.arg_label), argOpt) ->
+               match argOpt |> ExtendFunctionTable.extractLabelledArgument with
+               | Some (path, loc) -> (
+                 match argLabel with
+                 | Labelled label -> (
+                   if
+                     functionTable
+                     |> FunctionTable.functionGetKindOfLabel ~functionName
+                          ~label
+                     <> None
+                   then ()
+                   else
+                     match Hashtbl.find_opt valueBindingsTable functionName with
+                     | Some (_pos, (body : Typedtree.expression), _)
+                       when path
+                            |> FunctionTable.isInFunctionInTable ~functionTable
+                       ->
+                       let inTable =
+                         functionPath
+                         |> FunctionTable.isInFunctionInTable ~functionTable
+                       in
+                       if not inTable then
+                         functionTable
+                         |> FunctionTable.addFunction ~functionName;
+                       functionTable
+                       |> FunctionTable.addLabelToKind ~functionName ~label;
+                       if !Common.Cli.debug then
+                         Log_.warning ~forStats:false ~loc:body.exp_loc
+                           (Termination
+                              {
+                                termination = TerminationAnalysisInternal;
+                                message =
+                                  Format.asprintf
+                                    "Extend Function Table with @{<info>%s@} \
+                                     as parametric ~@{<info>%s@}=@{<info>%s@}"
+                                    functionName label (Path.name path);
+                              })
+                     | _ -> checkIdent ~path ~loc)
+                 | Optional _ | Nolabel -> checkIdent ~path ~loc)
+               | _ -> ());
+        e
+      | _ -> super.expr self e
+    in
+    {super with Tast_mapper.expr}
+
+  let run ~functionTable ~valueBindingsTable (expression : Typedtree.expression)
+      =
+    let traverseExpr = traverseExpr ~functionTable ~valueBindingsTable in
+    expression |> traverseExpr.expr traverseExpr |> ignore
+end
+
+module Compile = struct
+  type ctx = {
+    currentFunctionName: FunctionName.t;
+    functionTable: FunctionTable.t;
+    innerRecursiveFunctions: (FunctionName.t, FunctionName.t) Hashtbl.t;
+    isProgressFunction: Path.t -> bool;
+  }
+
+  let rec expression ~ctx (expr : Typedtree.expression) =
+    let {currentFunctionName; functionTable; isProgressFunction} = ctx in
+    let loc = expr.exp_loc in
+    let notImplemented case =
+      Log_.error ~loc
+        (Termination
+           {termination = ErrorNotImplemented; message = Format.asprintf case})
+    in
+
+    match expr.exp_desc with
+    | Texp_ident _ -> Command.nothing
+    | Texp_apply
+        (({exp_desc = Texp_ident (calleeToRename, l, vd)} as expr), argsToExtend)
+      -> (
+      let callee, args =
+        match
+          Hashtbl.find_opt ctx.innerRecursiveFunctions
+            (Path.name calleeToRename)
+        with
+        | Some innerFunctionName ->
+          let innerFunctionDefinition =
+            functionTable
+            |> FunctionTable.getFunctionDefinition
+                 ~functionName:innerFunctionName
+          in
+          let argsFromKind =
+            innerFunctionDefinition.kind
+            |> List.map (fun (entry : Kind.entry) ->
+                   ( Asttypes.Labelled entry.label,
+                     Some
+                       {
+                         expr with
+                         exp_desc =
+                           Texp_ident
+                             (Path.Pident (Ident.create entry.label), l, vd);
+                       } ))
+          in
+          ( Path.Pident (Ident.create innerFunctionName),
+            argsFromKind @ argsToExtend )
+        | None -> (calleeToRename, argsToExtend)
+      in
+      if callee |> FunctionTable.isInFunctionInTable ~functionTable then
+        let functionName = Path.name callee in
+        let functionDefinition =
+          functionTable |> FunctionTable.getFunctionDefinition ~functionName
+        in
+        let exception ArgError in
+        let getFunctionArg {Kind.label} =
+          let argOpt =
+            args
+            |> List.find_opt (fun arg ->
+                   match arg with
+                   | Asttypes.Labelled s, Some _ -> s = label
+                   | _ -> false)
+          in
+          let argOpt =
+            match argOpt with
+            | Some (_, Some e) -> Some e
+            | _ -> None
+          in
+          let functionArg () =
+            match
+              argOpt
+              |> ExtendFunctionTable.extractLabelledArgument
+                   ~kindOpt:(Some functionDefinition.kind)
+            with
+            | None ->
+              Stats.logHygieneMustHaveNamedArgument ~label ~loc;
+              raise ArgError
+            | Some (path, _pos)
+              when path |> FunctionTable.isInFunctionInTable ~functionTable ->
+              let functionName = Path.name path in
+              {FunctionArgs.label; functionName}
+            | Some (path, _pos)
+              when functionTable
+                   |> FunctionTable.functionGetKindOfLabel
+                        ~functionName:currentFunctionName
+                        ~label:(Path.name path)
+                   = Some []
+                   (* TODO: when kinds are inferred, support and check non-empty kinds *)
+              ->
+              let functionName = Path.name path in
+              {FunctionArgs.label; functionName}
+            | _ ->
+              Stats.logHygieneNamedArgValue ~label ~loc;
+              raise ArgError
+              [@@raises ArgError]
+          in
+          functionArg ()
+            [@@raises ArgError]
+        in
+        let functionArgsOpt =
+          try Some (functionDefinition.kind |> List.map getFunctionArg)
+          with ArgError -> None
+        in
+        match functionArgsOpt with
+        | None -> Command.nothing
+        | Some functionArgs ->
+          Command.Call (FunctionCall {functionName; functionArgs}, loc)
+          |> evalArgs ~args ~ctx
+      else if callee |> isProgressFunction then
+        Command.Call (ProgressFunction callee, loc) |> evalArgs ~args ~ctx
+      else
+        match
+          functionTable
+          |> FunctionTable.functionGetKindOfLabel
+               ~functionName:currentFunctionName ~label:(Path.name callee)
+        with
+        | Some kind when kind = Kind.empty ->
+          Command.Call
+            (FunctionCall (Path.name callee |> FunctionCall.noArgs), loc)
+          |> evalArgs ~args ~ctx
+        | Some _kind ->
+          (* TODO when kinds are extended in future: check that args matches with kind
+             and create a function call with the appropriate arguments *)
+          assert false
+        | None -> expr |> expression ~ctx |> evalArgs ~args ~ctx)
+    | Texp_apply (expr, args) -> expr |> expression ~ctx |> evalArgs ~args ~ctx
+    | Texp_let
+        ( Recursive,
+          [{vb_pat = {pat_desc = Tpat_var (id, _); pat_loc}; vb_expr}],
+          inExpr ) ->
+      let oldFunctionName = Ident.name id in
+      let newFunctionName = currentFunctionName ^ "$" ^ oldFunctionName in
+      functionTable |> FunctionTable.addFunction ~functionName:newFunctionName;
+      let newFunctionDefinition =
+        functionTable
+        |> FunctionTable.getFunctionDefinition ~functionName:newFunctionName
+      in
+      let currentFunctionDefinition =
+        functionTable
+        |> FunctionTable.getFunctionDefinition ~functionName:currentFunctionName
+      in
+      newFunctionDefinition.kind <- currentFunctionDefinition.kind;
+      let newCtx = {ctx with currentFunctionName = newFunctionName} in
+      Hashtbl.replace ctx.innerRecursiveFunctions oldFunctionName
+        newFunctionName;
+      newFunctionDefinition.body <- Some (vb_expr |> expression ~ctx:newCtx);
+      if !Common.Cli.debug then
+        Log_.warning ~forStats:false ~loc:pat_loc
+          (Termination
+             {
+               termination = TerminationAnalysisInternal;
+               message =
+                 Format.asprintf "Adding recursive definition @{<info>%s@}"
+                   newFunctionName;
+             });
+      inExpr |> expression ~ctx
+    | Texp_let (recFlag, valueBindings, inExpr) ->
+      if recFlag = Recursive then Stats.logHygieneNoNestedLetRec ~loc;
+      let commands =
+        (valueBindings
+        |> List.map (fun (vb : Typedtree.value_binding) ->
+               vb.vb_expr |> expression ~ctx))
+        @ [inExpr |> expression ~ctx]
+      in
+      Command.sequence commands
+    | Texp_sequence (e1, e2) ->
+      let open Command in
+      expression ~ctx e1 +++ expression ~ctx e2
+    | Texp_ifthenelse (e1, e2, eOpt) ->
+      let c1 = e1 |> expression ~ctx in
+      let c2 = e2 |> expression ~ctx in
+      let c3 = eOpt |> expressionOpt ~ctx in
+      let open Command in
+      c1 +++ nondet [c2; c3]
+    | Texp_constant _ -> Command.nothing
+    | Texp_construct ({loc = {loc_ghost}}, {cstr_name}, expressions) -> (
+      let c =
+        expressions
+        |> List.map (fun e -> e |> expression ~ctx)
+        |> Command.unorderedSequence
+      in
+      match cstr_name with
+      | "Some" when loc_ghost = false ->
+        let open Command in
+        c +++ ConstrOption Rsome
+      | "None" when loc_ghost = false ->
+        let open Command in
+        c +++ ConstrOption Rnone
+      | _ -> c)
+    | Texp_function {cases} -> cases |> List.map (case ~ctx) |> Command.nondet
+    | Texp_match (e, casesOk, casesExn, _partial)
+      when not
+             (casesExn
+             |> List.map (fun (case : Typedtree.case) -> case.c_lhs.pat_desc)
+             != []) -> (
+      (* No exceptions *)
+      let cases = casesOk @ casesExn in
+      let cE = e |> expression ~ctx in
+      let cCases = cases |> List.map (case ~ctx) in
+      let fail () =
+        let open Command in
+        cE +++ nondet cCases
+      in
+      match (cE, cases) with
+      | ( Call (FunctionCall functionCall, loc),
+          [{c_lhs = pattern1}; {c_lhs = pattern2}] ) -> (
+        match (pattern1.pat_desc, pattern2.pat_desc) with
+        | ( Tpat_construct (_, {cstr_name = ("Some" | "None") as name1}, _),
+            Tpat_construct (_, {cstr_name = "Some" | "None"}, _) ) ->
+          let casesArr = Array.of_list cCases in
+          let some, none =
+            try
+              match name1 = "Some" with
+              | true -> (casesArr.(0), casesArr.(1))
+              | false -> (casesArr.(1), casesArr.(0))
+            with Invalid_argument _ -> (Nothing, Nothing)
+          in
+          Command.SwitchOption {functionCall; loc; some; none}
+        | _ -> fail ())
+      | _ -> fail ())
+    | Texp_match _ -> assert false (* exceptions *)
+    | Texp_field (e, _lid, _desc) -> e |> expression ~ctx
+    | Texp_record {fields; extended_expression} ->
+      extended_expression
+      :: (fields |> Array.to_list
+         |> List.map
+              (fun
+                ( _desc,
+                  (recordLabelDefinition : Typedtree.record_label_definition) )
+              ->
+                match recordLabelDefinition with
+                | Kept _typeExpr -> None
+                | Overridden (_loc, e) -> Some e))
+      |> List.map (expressionOpt ~ctx)
+      |> Command.unorderedSequence
+    | Texp_setfield (e1, _loc, _desc, e2) ->
+      [e1; e2] |> List.map (expression ~ctx) |> Command.unorderedSequence
+    | Texp_tuple expressions | Texp_array expressions ->
+      expressions |> List.map (expression ~ctx) |> Command.unorderedSequence
+    | Texp_assert _ -> Command.nothing
+    | Texp_try (e, cases) ->
+      let cE = e |> expression ~ctx in
+      let cCases = cases |> List.map (case ~ctx) |> Command.nondet in
+      let open Command in
+      cE +++ cCases
+    | Texp_variant (_label, eOpt) -> eOpt |> expressionOpt ~ctx
+    | Texp_while _ ->
+      notImplemented "Texp_while";
+      assert false
+    | Texp_for _ ->
+      notImplemented "Texp_for";
+      assert false
+    | Texp_send _ ->
+      notImplemented "Texp_send";
+      assert false
+    | Texp_new _ ->
+      notImplemented "Texp_new";
+      assert false
+    | Texp_instvar _ ->
+      notImplemented "Texp_instvar";
+      assert false
+    | Texp_setinstvar _ ->
+      notImplemented "Texp_setinstvar";
+      assert false
+    | Texp_override _ ->
+      notImplemented "Texp_override";
+      assert false
+    | Texp_letmodule _ ->
+      notImplemented "Texp_letmodule";
+      assert false
+    | Texp_letexception _ ->
+      notImplemented "Texp_letexception";
+      assert false
+    | Texp_lazy _ ->
+      notImplemented "Texp_lazy";
+      assert false
+    | Texp_object _ ->
+      notImplemented "Texp_letmodule";
+      assert false
+    | Texp_pack _ ->
+      notImplemented "Texp_pack";
+      assert false
+    | Texp_unreachable ->
+      notImplemented "Texp_unreachable";
+      assert false
+    | Texp_extension_constructor _ when true ->
+      notImplemented "Texp_extension_constructor";
+      assert false
+    | _ ->
+      (* ocaml 4.08: Texp_letop(_) | Texp_open(_) *)
+      notImplemented "Texp_letop(_) | Texp_open(_)";
+      assert false
+
+  and expressionOpt ~ctx eOpt =
+    match eOpt with
+    | None -> Command.nothing
+    | Some e -> e |> expression ~ctx
+
+  and evalArgs ~args ~ctx command =
+    (* Don't assume any evaluation order on the arguments *)
+    let commands =
+      args |> List.map (fun (_, eOpt) -> eOpt |> expressionOpt ~ctx)
+    in
+    let open Command in
+    unorderedSequence commands +++ command
+
+  and case : ctx:ctx -> Typedtree.case -> _ =
+   fun ~ctx {c_guard; c_rhs} ->
+    match c_guard with
+    | None -> c_rhs |> expression ~ctx
+    | Some e ->
+      let open Command in
+      expression ~ctx e +++ expression ~ctx c_rhs
+end
+
+module CallStack = struct
+  type frame = {frameNumber: int; pos: Lexing.position}
+  type t = {tbl: (FunctionCall.t, frame) Hashtbl.t; mutable size: int}
+
+  let create () = {tbl = Hashtbl.create 1; size = 0}
+
+  let toSet {tbl} =
+    Hashtbl.fold
+      (fun frame _i set -> FunctionCallSet.add frame set)
+      tbl FunctionCallSet.empty
+
+  let hasFunctionCall ~functionCall (t : t) = Hashtbl.mem t.tbl functionCall
+
+  let addFunctionCall ~functionCall ~pos (t : t) =
+    t.size <- t.size + 1;
+    Hashtbl.replace t.tbl functionCall {frameNumber = t.size; pos}
+
+  let removeFunctionCall ~functionCall (t : t) =
+    t.size <- t.size - 1;
+    Hashtbl.remove t.tbl functionCall
+
+  let print ppf (t : t) =
+    Format.fprintf ppf "  CallStack:";
+    let frames =
+      Hashtbl.fold
+        (fun functionCall {frameNumber; pos} frames ->
+          (functionCall, frameNumber, pos) :: frames)
+        t.tbl []
+      |> List.sort (fun (_, i1, _) (_, i2, _) -> i2 - i1)
+    in
+    frames
+    |> List.iter (fun ((functionCall : FunctionCall.t), i, pos) ->
+           Format.fprintf ppf "\n    @{<dim>%d@} %s (%a)" i
+             (FunctionCall.toString functionCall)
+             printPos pos)
+end
+
+module Eval = struct
+  type progress = Progress.t
+  type cache = (FunctionCall.t, State.t) Hashtbl.t
+
+  let createCache () : cache = Hashtbl.create 1
+
+  let lookupCache ~functionCall (cache : cache) =
+    Hashtbl.find_opt cache functionCall
+
+  let updateCache ~functionCall ~loc ~state (cache : cache) =
+    Stats.logResult ~functionCall ~resString:(state |> State.toString) ~loc;
+    if not (Hashtbl.mem cache functionCall) then
+      Hashtbl.replace cache functionCall state
+
+  let hasInfiniteLoop ~callStack ~functionCallToInstantiate ~functionCall ~loc
+      ~state =
+    if callStack |> CallStack.hasFunctionCall ~functionCall then (
+      if state.State.progress = NoProgress then (
+        Log_.error ~loc
+          (Termination
+             {
+               termination = ErrorTermination;
+               message =
+                 Format.asprintf "%a"
+                   (fun ppf () ->
+                     Format.fprintf ppf "Possible infinite loop when calling ";
+                     (match functionCallToInstantiate = functionCall with
+                     | true ->
+                       Format.fprintf ppf "@{<error>%s@}"
+                         (functionCallToInstantiate |> FunctionCall.toString)
+                     | false ->
+                       Format.fprintf ppf "@{<error>%s@} which is @{<error>%s@}"
+                         (functionCallToInstantiate |> FunctionCall.toString)
+                         (functionCall |> FunctionCall.toString));
+                     Format.fprintf ppf "@,%a" CallStack.print callStack)
+                   ();
+             });
+        Stats.logLoop ());
+      true)
+    else false
+
+  let rec runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
+      ~madeProgressOn ~loc ~state functionCallToInstantiate : State.t =
+    let pos = loc.Location.loc_start in
+    let functionCall =
+      functionCallToInstantiate
+      |> FunctionCall.applySubstitution ~sub:functionArgs
+    in
+    let functionName = functionCall.functionName in
+    let call = Call.FunctionCall functionCall in
+    let stateAfterCall =
+      match cache |> lookupCache ~functionCall with
+      | Some stateAfterCall ->
+        Stats.logCache ~functionCall ~hit:true ~loc;
+        {
+          stateAfterCall with
+          trace = Trace.Tcall (call, stateAfterCall.progress);
+        }
+      | None ->
+        if FunctionCallSet.mem functionCall madeProgressOn then
+          State.init ~progress:Progress ~trace:(Trace.Tcall (call, Progress)) ()
+        else if
+          hasInfiniteLoop ~callStack ~functionCallToInstantiate ~functionCall
+            ~loc ~state
+        then {state with trace = Trace.Tcall (call, state.progress)}
+        else (
+          Stats.logCache ~functionCall ~hit:false ~loc;
+          let functionDefinition =
+            functionTable |> FunctionTable.getFunctionDefinition ~functionName
+          in
+          callStack |> CallStack.addFunctionCall ~functionCall ~pos;
+          let body =
+            match functionDefinition.body with
+            | Some body -> body
+            | None -> assert false
+          in
+          let stateAfterCall =
+            body
+            |> run ~cache ~callStack ~functionArgs:functionCall.functionArgs
+                 ~functionTable ~madeProgressOn ~state:(State.init ())
+          in
+          cache |> updateCache ~functionCall ~loc ~state:stateAfterCall;
+          (* Invariant: run should restore the callStack *)
+          callStack |> CallStack.removeFunctionCall ~functionCall;
+          let trace = Trace.Tcall (call, stateAfterCall.progress) in
+          {stateAfterCall with trace})
+    in
+    State.seq state stateAfterCall
+
+  and run ~(cache : cache) ~callStack ~functionArgs ~functionTable
+      ~madeProgressOn ~state (command : Command.t) : State.t =
+    match command with
+    | Call (FunctionCall functionCall, loc) ->
+      functionCall
+      |> runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
+           ~madeProgressOn ~loc ~state
+    | Call ((ProgressFunction _ as call), _pos) ->
+      let state1 =
+        State.init ~progress:Progress ~trace:(Tcall (call, Progress)) ()
+      in
+      State.seq state state1
+    | ConstrOption r ->
+      let state1 =
+        match r = Rsome with
+        | true -> State.some ~progress:state.progress
+        | false -> State.none ~progress:state.progress
+      in
+      State.seq state state1
+    | Nothing ->
+      let state1 = State.init () in
+      State.seq state state1
+    | Sequence commands ->
+      (* if one command makes progress, then the sequence makes progress *)
+      let rec findFirstProgress ~callStack ~commands ~madeProgressOn ~state =
+        match commands with
+        | [] -> state
+        | c :: nextCommands ->
+          let state1 =
+            c
+            |> run ~cache ~callStack ~functionArgs ~functionTable
+                 ~madeProgressOn ~state
+          in
+          let madeProgressOn, callStack =
+            match state1.progress with
+            | Progress ->
+              (* look for infinite loops in the rest of the sequence, remembering what has made progress *)
+              ( FunctionCallSet.union madeProgressOn
+                  (callStack |> CallStack.toSet),
+                CallStack.create () )
+            | NoProgress -> (madeProgressOn, callStack)
+          in
+          findFirstProgress ~callStack ~commands:nextCommands ~madeProgressOn
+            ~state:state1
+      in
+      findFirstProgress ~callStack ~commands ~madeProgressOn ~state
+    | UnorderedSequence commands ->
+      let stateNoTrace = {state with trace = Trace.empty} in
+      (* the commands could be executed in any order: progess if any one does *)
+      let states =
+        commands
+        |> List.map (fun c ->
+               c
+               |> run ~cache ~callStack ~functionArgs ~functionTable
+                    ~madeProgressOn ~state:stateNoTrace)
+      in
+      State.seq state (states |> State.unorderedSequence)
+    | Nondet commands ->
+      let stateNoTrace = {state with trace = Trace.empty} in
+      (* the commands could be executed in any order: progess if any one does *)
+      let states =
+        commands
+        |> List.map (fun c ->
+               c
+               |> run ~cache ~callStack ~functionArgs ~functionTable
+                    ~madeProgressOn ~state:stateNoTrace)
+      in
+      State.seq state (states |> State.nondet)
+    | SwitchOption {functionCall; loc; some; none} -> (
+      let stateAfterCall =
+        functionCall
+        |> runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
+             ~madeProgressOn ~loc ~state
+      in
+      match stateAfterCall.valuesOpt with
+      | None ->
+        Command.nondet [some; none]
+        |> run ~cache ~callStack ~functionArgs ~functionTable ~madeProgressOn
+             ~state:stateAfterCall
+      | Some values ->
+        let runOpt c progressOpt =
+          match progressOpt with
+          | None -> State.init ~progress:Progress ()
+          | Some progress ->
+            c
+            |> run ~cache ~callStack ~functionArgs ~functionTable
+                 ~madeProgressOn ~state:(State.init ~progress ())
+        in
+        let stateNone = values |> Values.getNone |> runOpt none in
+        let stateSome = values |> Values.getSome |> runOpt some in
+        State.seq stateAfterCall (State.nondet [stateSome; stateNone]))
+
+  let analyzeFunction ~cache ~functionTable ~loc functionName =
+    if !Common.Cli.debug then
+      Log_.log "@[<v 2>@,@{<warning>Termination Analysis@} for @{<info>%s@}@]@."
+        functionName;
+    let pos = loc.Location.loc_start in
+    let callStack = CallStack.create () in
+    let functionArgs = FunctionArgs.empty in
+    let functionCall = FunctionCall.noArgs functionName in
+    callStack |> CallStack.addFunctionCall ~functionCall ~pos;
+    let functionDefinition =
+      functionTable |> FunctionTable.getFunctionDefinition ~functionName
+    in
+    if functionDefinition.kind <> Kind.empty then
+      Stats.logHygieneParametric ~functionName ~loc
+    else
+      let body =
+        match functionDefinition.body with
+        | Some body -> body
+        | None -> assert false
+      in
+      let state =
+        body
+        |> run ~cache ~callStack ~functionArgs ~functionTable
+             ~madeProgressOn:FunctionCallSet.empty ~state:(State.init ())
+      in
+      cache |> updateCache ~functionCall ~loc ~state
+end
+
+let progressFunctionsFromAttributes attributes =
+  let lidToString lid = lid |> Longident.flatten |> String.concat "." in
+  let isProgress = ( = ) "progress" in
+  if attributes |> Annotation.hasAttribute isProgress then
+    Some
+      (match attributes |> Annotation.getAttributePayload isProgress with
+      | None -> []
+      | Some (IdentPayload lid) -> [lidToString lid]
+      | Some (TuplePayload l) ->
+        l
+        |> List.filter_map (function
+             | Annotation.IdentPayload lid -> Some (lidToString lid)
+             | _ -> None)
+      | _ -> [])
+  else None
+
+let traverseAst ~valueBindingsTable =
+  let super = Tast_mapper.default in
+  let value_bindings (self : Tast_mapper.mapper) (recFlag, valueBindings) =
+    (* Update the table of value bindings for variables *)
+    valueBindings
+    |> List.iter (fun (vb : Typedtree.value_binding) ->
+           match vb.vb_pat.pat_desc with
+           | Tpat_var (id, {loc = {loc_start = pos}}) ->
+             let callees = lazy (FindFunctionsCalled.findCallees vb.vb_expr) in
+             Hashtbl.replace valueBindingsTable (Ident.name id)
+               (pos, vb.vb_expr, callees)
+           | _ -> ());
+    let progressFunctions, functionsToAnalyze =
+      if recFlag = Asttypes.Nonrecursive then (StringSet.empty, [])
+      else
+        let progressFunctions0, functionsToAnalyze0 =
+          valueBindings
+          |> List.fold_left
+               (fun (progressFunctions, functionsToAnalyze)
+                    (valueBinding : Typedtree.value_binding) ->
+                 match
+                   progressFunctionsFromAttributes valueBinding.vb_attributes
+                 with
+                 | None -> (progressFunctions, functionsToAnalyze)
+                 | Some newProgressFunctions ->
+                   ( StringSet.union
+                       (StringSet.of_list newProgressFunctions)
+                       progressFunctions,
+                     match valueBinding.vb_pat.pat_desc with
+                     | Tpat_var (id, _) ->
+                       (Ident.name id, valueBinding.vb_expr.exp_loc)
+                       :: functionsToAnalyze
+                     | _ -> functionsToAnalyze ))
+               (StringSet.empty, [])
+        in
+        (progressFunctions0, functionsToAnalyze0 |> List.rev)
+    in
+    if functionsToAnalyze <> [] then (
+      let functionTable = FunctionTable.create () in
+      let isProgressFunction path =
+        StringSet.mem (Path.name path) progressFunctions
+      in
+      let recursiveFunctions =
+        List.fold_left
+          (fun defs (valueBinding : Typedtree.value_binding) ->
+            match valueBinding.vb_pat.pat_desc with
+            | Tpat_var (id, _) -> Ident.name id :: defs
+            | _ -> defs)
+          [] valueBindings
+        |> List.rev
+      in
+      let recursiveDefinitions =
+        recursiveFunctions
+        |> List.fold_left
+             (fun acc functionName ->
+               match Hashtbl.find_opt valueBindingsTable functionName with
+               | Some (_pos, e, _set) -> (functionName, e) :: acc
+               | None -> acc)
+             []
+        |> List.rev
+      in
+      recursiveDefinitions
+      |> List.iter (fun (functionName, _body) ->
+             functionTable |> FunctionTable.addFunction ~functionName);
+      recursiveDefinitions
+      |> List.iter (fun (_, body) ->
+             body
+             |> ExtendFunctionTable.run ~functionTable ~progressFunctions
+                  ~valueBindingsTable);
+      recursiveDefinitions
+      |> List.iter (fun (_, body) ->
+             body
+             |> CheckExpressionWellFormed.run ~functionTable ~valueBindingsTable);
+      functionTable
+      |> Hashtbl.iter
+           (fun
+             functionName
+             (functionDefinition : FunctionTable.functionDefinition)
+           ->
+             if functionDefinition.body = None then
+               match Hashtbl.find_opt valueBindingsTable functionName with
+               | None -> ()
+               | Some (_pos, body, _) ->
+                 functionTable
+                 |> FunctionTable.addBody
+                      ~body:
+                        (Some
+                           (body
+                           |> Compile.expression
+                                ~ctx:
+                                  {
+                                    currentFunctionName = functionName;
+                                    functionTable;
+                                    innerRecursiveFunctions = Hashtbl.create 1;
+                                    isProgressFunction;
+                                  }))
+                      ~functionName);
+      if !Common.Cli.debug then FunctionTable.dump functionTable;
+      let cache = Eval.createCache () in
+      functionsToAnalyze
+      |> List.iter (fun (functionName, loc) ->
+             functionName |> Eval.analyzeFunction ~cache ~functionTable ~loc);
+      Stats.newRecursiveFunctions ~numFunctions:(Hashtbl.length functionTable));
+    valueBindings
+    |> List.iter (fun valueBinding ->
+           super.value_binding self valueBinding |> ignore);
+    (recFlag, valueBindings)
+  in
+  {super with Tast_mapper.value_bindings}
+
+let processStructure (structure : Typedtree.structure) =
+  Stats.newFile ();
+  let valueBindingsTable = Hashtbl.create 1 in
+  let traverseAst = traverseAst ~valueBindingsTable in
+  structure |> traverseAst.structure traverseAst |> ignore
+
+let processCmt (cmt_infos : Cmt_format.cmt_infos) =
+  match cmt_infos.cmt_annots with
+  | Interface _ -> ()
+  | Implementation structure -> processStructure structure
+  | _ -> ()
+
+let reportStats () = Stats.dump ~ppf:Format.std_formatter
diff --git a/analysis/reanalyze/src/Common.ml b/analysis/reanalyze/src/Common.ml
new file mode 100644
index 0000000000..e0e7e592bd
--- /dev/null
+++ b/analysis/reanalyze/src/Common.ml
@@ -0,0 +1,254 @@
+let currentSrc = ref ""
+let currentModule = ref ""
+let currentModuleName = ref ("" |> Name.create)
+let runConfig = RunConfig.runConfig
+
+(* Location printer: `filename:line: ' *)
+let posToString (pos : Lexing.position) =
+  let file = pos.Lexing.pos_fname in
+  let line = pos.Lexing.pos_lnum in
+  let col = pos.Lexing.pos_cnum - pos.Lexing.pos_bol in
+  (file |> Filename.basename)
+  ^ ":" ^ string_of_int line ^ ":" ^ string_of_int col
+
+module Cli = struct
+  let debug = ref false
+  let ci = ref false
+
+  (** The command was a -cmt variant (e.g. -exception-cmt) *)
+  let cmtCommand = ref false
+
+  let experimental = ref false
+  let json = ref false
+  let write = ref false
+
+  (* names to be considered live values *)
+  let liveNames = ref ([] : string list)
+
+  (* paths of files where all values are considered live *)
+
+  let livePaths = ref ([] : string list)
+
+  (* paths of files to exclude from analysis *)
+  let excludePaths = ref ([] : string list)
+end
+
+module StringSet = Set.Make (String)
+
+module LocSet = Set.Make (struct
+  include Location
+
+  let compare = compare
+end)
+
+module FileSet = Set.Make (String)
+
+module FileHash = struct
+  include Hashtbl.Make (struct
+    type t = string
+
+    let hash (x : t) = Hashtbl.hash x
+    let equal (x : t) y = x = y
+  end)
+end
+
+module FileReferences = struct
+  (* references across files *)
+  let table = (FileHash.create 256 : FileSet.t FileHash.t)
+
+  let findSet table key =
+    try FileHash.find table key with Not_found -> FileSet.empty
+
+  let add (locFrom : Location.t) (locTo : Location.t) =
+    let key = locFrom.loc_start.pos_fname in
+    let set = findSet table key in
+    FileHash.replace table key (FileSet.add locTo.loc_start.pos_fname set)
+
+  let addFile fileName =
+    let set = findSet table fileName in
+    FileHash.replace table fileName set
+
+  let exists fileName = FileHash.mem table fileName
+
+  let find fileName =
+    match FileHash.find_opt table fileName with
+    | Some set -> set
+    | None -> FileSet.empty
+
+  let iter f = FileHash.iter f table
+end
+
+module Path = struct
+  type t = Name.t list
+
+  let toName (path : t) =
+    path |> List.rev_map Name.toString |> String.concat "." |> Name.create
+
+  let toString path = path |> toName |> Name.toString
+
+  let withoutHead path =
+    match
+      path |> List.rev_map (fun n -> n |> Name.toInterface |> Name.toString)
+    with
+    | _ :: tl -> tl |> String.concat "."
+    | [] -> ""
+
+  let onOkPath ~whenContainsApply ~f path =
+    match path |> Path.flatten with
+    | `Ok (id, mods) -> f (Ident.name id :: mods |> String.concat ".")
+    | `Contains_apply -> whenContainsApply
+
+  let fromPathT path =
+    match path |> Path.flatten with
+    | `Ok (id, mods) -> Ident.name id :: mods |> List.rev_map Name.create
+    | `Contains_apply -> []
+
+  let moduleToImplementation path =
+    match path |> List.rev with
+    | moduleName :: rest ->
+      (moduleName |> Name.toImplementation) :: rest |> List.rev
+    | [] -> path
+
+  let moduleToInterface path =
+    match path |> List.rev with
+    | moduleName :: rest -> (moduleName |> Name.toInterface) :: rest |> List.rev
+    | [] -> path
+
+  let toModuleName ~isType path =
+    match path with
+    | _ :: tl when not isType -> tl |> toName
+    | _ :: _ :: tl when isType -> tl |> toName
+    | _ -> "" |> Name.create
+
+  let typeToInterface path =
+    match path with
+    | typeName :: rest -> (typeName |> Name.toInterface) :: rest
+    | [] -> path
+end
+
+module OptionalArgs = struct
+  type t = {
+    mutable count: int;
+    mutable unused: StringSet.t;
+    mutable alwaysUsed: StringSet.t;
+  }
+
+  let empty =
+    {unused = StringSet.empty; alwaysUsed = StringSet.empty; count = 0}
+
+  let fromList l =
+    {unused = StringSet.of_list l; alwaysUsed = StringSet.empty; count = 0}
+
+  let isEmpty x = StringSet.is_empty x.unused
+
+  let call ~argNames ~argNamesMaybe x =
+    let nameSet = argNames |> StringSet.of_list in
+    let nameSetMaybe = argNamesMaybe |> StringSet.of_list in
+    let nameSetAlways = StringSet.diff nameSet nameSetMaybe in
+    if x.count = 0 then x.alwaysUsed <- nameSetAlways
+    else x.alwaysUsed <- StringSet.inter nameSetAlways x.alwaysUsed;
+    argNames
+    |> List.iter (fun name -> x.unused <- StringSet.remove name x.unused);
+    x.count <- x.count + 1
+
+  let combine x y =
+    let unused = StringSet.inter x.unused y.unused in
+    x.unused <- unused;
+    y.unused <- unused;
+    let alwaysUsed = StringSet.inter x.alwaysUsed y.alwaysUsed in
+    x.alwaysUsed <- alwaysUsed;
+    y.alwaysUsed <- alwaysUsed
+
+  let iterUnused f x = StringSet.iter f x.unused
+  let iterAlwaysUsed f x = StringSet.iter (fun s -> f s x.count) x.alwaysUsed
+end
+
+module DeclKind = struct
+  type t =
+    | Exception
+    | RecordLabel
+    | VariantCase
+    | Value of {
+        isToplevel: bool;
+        mutable optionalArgs: OptionalArgs.t;
+        sideEffects: bool;
+      }
+
+  let isType dk =
+    match dk with
+    | RecordLabel | VariantCase -> true
+    | Exception | Value _ -> false
+
+  let toString dk =
+    match dk with
+    | Exception -> "Exception"
+    | RecordLabel -> "RecordLabel"
+    | VariantCase -> "VariantCase"
+    | Value _ -> "Value"
+end
+
+type posAdjustment = FirstVariant | OtherVariant | Nothing
+
+type decl = {
+  declKind: DeclKind.t;
+  moduleLoc: Location.t;
+  posAdjustment: posAdjustment;
+  path: Path.t;
+  pos: Lexing.position;
+  posEnd: Lexing.position;
+  posStart: Lexing.position;
+  mutable resolvedDead: bool option;
+  mutable report: bool;
+}
+
+type line = {mutable declarations: decl list; original: string}
+
+module ExnSet = Set.Make (Exn)
+
+type missingRaiseInfo = {
+  exnName: string;
+  exnTable: (Exn.t, LocSet.t) Hashtbl.t;
+  locFull: Location.t;
+  missingAnnotations: ExnSet.t;
+  raiseSet: ExnSet.t;
+}
+
+type severity = Warning | Error
+type deadOptional = WarningUnusedArgument | WarningRedundantOptionalArgument
+
+type termination =
+  | ErrorHygiene
+  | ErrorNotImplemented
+  | ErrorTermination
+  | TerminationAnalysisInternal
+
+type deadWarning =
+  | WarningDeadException
+  | WarningDeadType
+  | WarningDeadValue
+  | WarningDeadValueWithSideEffects
+  | IncorrectDeadAnnotation
+
+type lineAnnotation = (decl * line) option
+
+type description =
+  | Circular of {message: string}
+  | ExceptionAnalysis of {message: string}
+  | ExceptionAnalysisMissing of missingRaiseInfo
+  | DeadModule of {message: string}
+  | DeadOptional of {deadOptional: deadOptional; message: string}
+  | DeadWarning of {
+      deadWarning: deadWarning;
+      path: string;
+      message: string;
+      shouldWriteLineAnnotation: bool;
+      lineAnnotation: lineAnnotation;
+    }
+  | Termination of {termination: termination; message: string}
+
+type issue = {
+  name: string;
+  severity: severity;
+  loc: Location.t;
+  description: description;
+}
diff --git a/analysis/reanalyze/src/DeadCode.ml b/analysis/reanalyze/src/DeadCode.ml
new file mode 100644
index 0000000000..63323a88d2
--- /dev/null
+++ b/analysis/reanalyze/src/DeadCode.ml
@@ -0,0 +1,32 @@
+open DeadCommon
+
+let processSignature ~doValues ~doTypes (signature : Types.signature) =
+  signature
+  |> List.iter (fun sig_item ->
+         DeadValue.processSignatureItem ~doValues ~doTypes
+           ~moduleLoc:Location.none
+           ~path:[!Common.currentModuleName]
+           sig_item)
+
+let processCmt ~cmtFilePath (cmt_infos : Cmt_format.cmt_infos) =
+  (match cmt_infos.cmt_annots with
+  | Interface signature ->
+    ProcessDeadAnnotations.signature signature;
+    processSignature ~doValues:true ~doTypes:true signature.sig_type
+  | Implementation structure ->
+    let cmtiExists =
+      Sys.file_exists ((cmtFilePath |> Filename.remove_extension) ^ ".cmti")
+    in
+    ProcessDeadAnnotations.structure ~doGenType:(not cmtiExists) structure;
+    processSignature ~doValues:true ~doTypes:false structure.str_type;
+    let doExternals =
+      (* This is already handled at the interface level, avoid issues in inconsistent locations
+         https://github.com/BuckleScript/syntax/pull/54
+         Ideally, the handling should be less location-based, just like other language aspects. *)
+      false
+    in
+    DeadValue.processStructure ~doTypes:true ~doExternals
+      ~cmt_value_dependencies:cmt_infos.cmt_value_dependencies structure
+  | _ -> ());
+  DeadType.TypeDependencies.forceDelayedItems ();
+  DeadType.TypeDependencies.clear ()
diff --git a/analysis/reanalyze/src/DeadCommon.ml b/analysis/reanalyze/src/DeadCommon.ml
new file mode 100644
index 0000000000..b9cda3afbd
--- /dev/null
+++ b/analysis/reanalyze/src/DeadCommon.ml
@@ -0,0 +1,719 @@
+(* Adapted from https://github.com/LexiFi/dead_code_analyzer *)
+
+open Common
+
+module PosSet = Set.Make (struct
+  type t = Lexing.position
+
+  let compare = compare
+end)
+
+module Config = struct
+  (* Turn on type analysis *)
+  let analyzeTypes = ref true
+  let analyzeExternals = ref false
+  let reportUnderscore = false
+  let reportTypesDeadOnlyInInterface = false
+  let recursiveDebug = false
+  let warnOnCircularDependencies = false
+end
+
+module Current = struct
+  let bindings = ref PosSet.empty
+  let lastBinding = ref Location.none
+
+  (** max end position of a value reported dead *)
+  let maxValuePosEnd = ref Lexing.dummy_pos
+end
+
+let rec checkSub s1 s2 n =
+  n <= 0
+  || (try s1.[n] = s2.[n] with Invalid_argument _ -> false)
+     && checkSub s1 s2 (n - 1)
+
+let fileIsImplementationOf s1 s2 =
+  let n1 = String.length s1 and n2 = String.length s2 in
+  n2 = n1 + 1 && checkSub s1 s2 (n1 - 1)
+
+let liveAnnotation = "live"
+
+module PosHash = struct
+  include Hashtbl.Make (struct
+    type t = Lexing.position
+
+    let hash x =
+      let s = Filename.basename x.Lexing.pos_fname in
+      Hashtbl.hash (x.Lexing.pos_cnum, s)
+
+    let equal (x : t) y = x = y
+  end)
+
+  let findSet h k = try find h k with Not_found -> PosSet.empty
+
+  let addSet h k v =
+    let set = findSet h k in
+    replace h k (PosSet.add v set)
+end
+
+type decls = decl PosHash.t
+(** all exported declarations *)
+
+let decls = (PosHash.create 256 : decls)
+
+module ValueReferences = struct
+  (** all value references *)
+  let table = (PosHash.create 256 : PosSet.t PosHash.t)
+
+  let add posTo posFrom = PosHash.addSet table posTo posFrom
+  let find pos = PosHash.findSet table pos
+end
+
+module TypeReferences = struct
+  (** all type references *)
+  let table = (PosHash.create 256 : PosSet.t PosHash.t)
+
+  let add posTo posFrom = PosHash.addSet table posTo posFrom
+  let find pos = PosHash.findSet table pos
+end
+
+let declGetLoc decl =
+  let loc_start =
+    let offset =
+      WriteDeadAnnotations.offsetOfPosAdjustment decl.posAdjustment
+    in
+    let cnumWithOffset = decl.posStart.pos_cnum + offset in
+    if cnumWithOffset < decl.posEnd.pos_cnum then
+      {decl.posStart with pos_cnum = cnumWithOffset}
+    else decl.posStart
+  in
+  {Location.loc_start; loc_end = decl.posEnd; loc_ghost = false}
+
+let addValueReference ~addFileReference ~(locFrom : Location.t)
+    ~(locTo : Location.t) =
+  let lastBinding = !Current.lastBinding in
+  let locFrom =
+    match lastBinding = Location.none with
+    | true -> locFrom
+    | false -> lastBinding
+  in
+  if not locFrom.loc_ghost then (
+    if !Cli.debug then
+      Log_.item "addValueReference %s --> %s@."
+        (locFrom.loc_start |> posToString)
+        (locTo.loc_start |> posToString);
+    ValueReferences.add locTo.loc_start locFrom.loc_start;
+    if
+      addFileReference && (not locTo.loc_ghost) && (not locFrom.loc_ghost)
+      && locFrom.loc_start.pos_fname <> locTo.loc_start.pos_fname
+    then FileReferences.add locFrom locTo)
+
+let iterFilesFromRootsToLeaves iterFun =
+  (* For each file, the number of incoming references *)
+  let inverseReferences = (Hashtbl.create 1 : (string, int) Hashtbl.t) in
+  (* For each number of incoming references, the files *)
+  let referencesByNumber = (Hashtbl.create 1 : (int, FileSet.t) Hashtbl.t) in
+  let getNum fileName =
+    try Hashtbl.find inverseReferences fileName with Not_found -> 0
+  in
+  let getSet num =
+    try Hashtbl.find referencesByNumber num with Not_found -> FileSet.empty
+  in
+  let addIncomingEdge fileName =
+    let oldNum = getNum fileName in
+    let newNum = oldNum + 1 in
+    let oldSetAtNum = getSet oldNum in
+    let newSetAtNum = FileSet.remove fileName oldSetAtNum in
+    let oldSetAtNewNum = getSet newNum in
+    let newSetAtNewNum = FileSet.add fileName oldSetAtNewNum in
+    Hashtbl.replace inverseReferences fileName newNum;
+    Hashtbl.replace referencesByNumber oldNum newSetAtNum;
+    Hashtbl.replace referencesByNumber newNum newSetAtNewNum
+  in
+  let removeIncomingEdge fileName =
+    let oldNum = getNum fileName in
+    let newNum = oldNum - 1 in
+    let oldSetAtNum = getSet oldNum in
+    let newSetAtNum = FileSet.remove fileName oldSetAtNum in
+    let oldSetAtNewNum = getSet newNum in
+    let newSetAtNewNum = FileSet.add fileName oldSetAtNewNum in
+    Hashtbl.replace inverseReferences fileName newNum;
+    Hashtbl.replace referencesByNumber oldNum newSetAtNum;
+    Hashtbl.replace referencesByNumber newNum newSetAtNewNum
+  in
+  let addEdge fromFile toFile =
+    if FileReferences.exists fromFile then addIncomingEdge toFile
+  in
+  let removeEdge fromFile toFile =
+    if FileReferences.exists fromFile then removeIncomingEdge toFile
+  in
+  FileReferences.iter (fun fromFile set ->
+      if getNum fromFile = 0 then
+        Hashtbl.replace referencesByNumber 0 (FileSet.add fromFile (getSet 0));
+      set |> FileSet.iter (fun toFile -> addEdge fromFile toFile));
+  while getSet 0 <> FileSet.empty do
+    let filesWithNoIncomingReferences = getSet 0 in
+    Hashtbl.remove referencesByNumber 0;
+    filesWithNoIncomingReferences
+    |> FileSet.iter (fun fileName ->
+           iterFun fileName;
+           let references = FileReferences.find fileName in
+           references |> FileSet.iter (fun toFile -> removeEdge fileName toFile))
+  done;
+  (* Process any remaining items in case of circular references *)
+  referencesByNumber
+  |> Hashtbl.iter (fun _num set ->
+         if FileSet.is_empty set then ()
+         else
+           set
+           |> FileSet.iter (fun fileName ->
+                  let pos = {Lexing.dummy_pos with pos_fname = fileName} in
+                  let loc =
+                    {Location.none with loc_start = pos; loc_end = pos}
+                  in
+                  if Config.warnOnCircularDependencies then
+                    Log_.warning ~loc
+                      (Circular
+                         {
+                           message =
+                             Format.asprintf
+                               "Results for %s could be inaccurate because of \
+                                circular references"
+                               fileName;
+                         });
+                  iterFun fileName))
+
+(** Keep track of the location of values annotated @genType or @dead *)
+module ProcessDeadAnnotations = struct
+  type annotatedAs = GenType | Dead | Live
+
+  let positionsAnnotated = PosHash.create 1
+  let isAnnotatedDead pos = PosHash.find_opt positionsAnnotated pos = Some Dead
+
+  let isAnnotatedGenTypeOrLive pos =
+    match PosHash.find_opt positionsAnnotated pos with
+    | Some (Live | GenType) -> true
+    | Some Dead | None -> false
+
+  let isAnnotatedGenTypeOrDead pos =
+    match PosHash.find_opt positionsAnnotated pos with
+    | Some (Dead | GenType) -> true
+    | Some Live | None -> false
+
+  let annotateGenType (pos : Lexing.position) =
+    PosHash.replace positionsAnnotated pos GenType
+
+  let annotateDead (pos : Lexing.position) =
+    PosHash.replace positionsAnnotated pos Dead
+
+  let annotateLive (pos : Lexing.position) =
+    PosHash.replace positionsAnnotated pos Live
+
+  let processAttributes ~doGenType ~name ~pos attributes =
+    let getPayloadFun f = attributes |> Annotation.getAttributePayload f in
+    let getPayload (x : string) =
+      attributes |> Annotation.getAttributePayload (( = ) x)
+    in
+    if
+      doGenType
+      && getPayloadFun Annotation.tagIsOneOfTheGenTypeAnnotations <> None
+    then pos |> annotateGenType;
+    if getPayload WriteDeadAnnotations.deadAnnotation <> None then
+      pos |> annotateDead;
+    let nameIsInLiveNamesOrPaths () =
+      !Cli.liveNames |> List.mem name
+      ||
+      let fname =
+        match Filename.is_relative pos.pos_fname with
+        | true -> pos.pos_fname
+        | false -> Filename.concat (Sys.getcwd ()) pos.pos_fname
+      in
+      let fnameLen = String.length fname in
+      !Cli.livePaths
+      |> List.exists (fun prefix ->
+             String.length prefix <= fnameLen
+             &&
+             try String.sub fname 0 (String.length prefix) = prefix
+             with Invalid_argument _ -> false)
+    in
+    if getPayload liveAnnotation <> None || nameIsInLiveNamesOrPaths () then
+      pos |> annotateLive;
+    if attributes |> Annotation.isOcamlSuppressDeadWarning then
+      pos |> annotateLive
+
+  let collectExportLocations ~doGenType =
+    let super = Tast_mapper.default in
+    let currentlyDisableWarnings = ref false in
+    let value_binding self
+        ({vb_attributes; vb_pat} as value_binding : Typedtree.value_binding) =
+      (match vb_pat.pat_desc with
+      | Tpat_var (id, {loc = {loc_start = pos}})
+      | Tpat_alias ({pat_desc = Tpat_any}, id, {loc = {loc_start = pos}}) ->
+        if !currentlyDisableWarnings then pos |> annotateLive;
+        vb_attributes
+        |> processAttributes ~doGenType ~name:(id |> Ident.name) ~pos
+      | _ -> ());
+      super.value_binding self value_binding
+    in
+    let type_kind toplevelAttrs self (typeKind : Typedtree.type_kind) =
+      (match typeKind with
+      | Ttype_record labelDeclarations ->
+        labelDeclarations
+        |> List.iter
+             (fun ({ld_attributes; ld_loc} : Typedtree.label_declaration) ->
+               toplevelAttrs @ ld_attributes
+               |> processAttributes ~doGenType:false ~name:""
+                    ~pos:ld_loc.loc_start)
+      | Ttype_variant constructorDeclarations ->
+        constructorDeclarations
+        |> List.iter
+             (fun
+               ({cd_attributes; cd_loc; cd_args} :
+                 Typedtree.constructor_declaration)
+             ->
+               let _process_inline_records =
+                 match cd_args with
+                 | Cstr_record flds ->
+                   List.iter
+                     (fun ({ld_attributes; ld_loc} :
+                            Typedtree.label_declaration) ->
+                       toplevelAttrs @ cd_attributes @ ld_attributes
+                       |> processAttributes ~doGenType:false ~name:""
+                            ~pos:ld_loc.loc_start)
+                     flds
+                 | Cstr_tuple _ -> ()
+               in
+               toplevelAttrs @ cd_attributes
+               |> processAttributes ~doGenType:false ~name:""
+                    ~pos:cd_loc.loc_start)
+      | _ -> ());
+      super.type_kind self typeKind
+    in
+    let type_declaration self (typeDeclaration : Typedtree.type_declaration) =
+      let attributes = typeDeclaration.typ_attributes in
+      let _ = type_kind attributes self typeDeclaration.typ_kind in
+      typeDeclaration
+    in
+    let value_description self
+        ({val_attributes; val_id; val_val = {val_loc = {loc_start = pos}}} as
+         value_description :
+          Typedtree.value_description) =
+      if !currentlyDisableWarnings then pos |> annotateLive;
+      val_attributes
+      |> processAttributes ~doGenType ~name:(val_id |> Ident.name) ~pos;
+      super.value_description self value_description
+    in
+    let structure_item self (item : Typedtree.structure_item) =
+      (match item.str_desc with
+      | Tstr_attribute attribute
+        when [attribute] |> Annotation.isOcamlSuppressDeadWarning ->
+        currentlyDisableWarnings := true
+      | _ -> ());
+      super.structure_item self item
+    in
+    let structure self (structure : Typedtree.structure) =
+      let oldDisableWarnings = !currentlyDisableWarnings in
+      super.structure self structure |> ignore;
+      currentlyDisableWarnings := oldDisableWarnings;
+      structure
+    in
+    let signature_item self (item : Typedtree.signature_item) =
+      (match item.sig_desc with
+      | Tsig_attribute attribute
+        when [attribute] |> Annotation.isOcamlSuppressDeadWarning ->
+        currentlyDisableWarnings := true
+      | _ -> ());
+      super.signature_item self item
+    in
+    let signature self (signature : Typedtree.signature) =
+      let oldDisableWarnings = !currentlyDisableWarnings in
+      super.signature self signature |> ignore;
+      currentlyDisableWarnings := oldDisableWarnings;
+      signature
+    in
+    {
+      super with
+      signature;
+      signature_item;
+      structure;
+      structure_item;
+      type_declaration;
+      value_binding;
+      value_description;
+    }
+
+  let structure ~doGenType structure =
+    let collectExportLocations = collectExportLocations ~doGenType in
+    structure
+    |> collectExportLocations.structure collectExportLocations
+    |> ignore
+
+  let signature signature =
+    let collectExportLocations = collectExportLocations ~doGenType:true in
+    signature
+    |> collectExportLocations.signature collectExportLocations
+    |> ignore
+end
+
+let addDeclaration_ ?posEnd ?posStart ~declKind ~path ~(loc : Location.t)
+    ?(posAdjustment = Nothing) ~moduleLoc (name : Name.t) =
+  let pos = loc.loc_start in
+  let posStart =
+    match posStart with
+    | Some posStart -> posStart
+    | None -> pos
+  in
+  let posEnd =
+    match posEnd with
+    | Some posEnd -> posEnd
+    | None -> loc.loc_end
+  in
+  (* a .cmi file can contain locations from other files.
+     For instance:
+         module M : Set.S with type elt = int
+     will create value definitions whose location is in set.mli
+  *)
+  if
+    (not loc.loc_ghost)
+    && (!currentSrc = pos.pos_fname || !currentModule == "*include*")
+  then (
+    if !Cli.debug then
+      Log_.item "add%sDeclaration %s %s path:%s@."
+        (declKind |> DeclKind.toString)
+        (name |> Name.toString) (pos |> posToString) (path |> Path.toString);
+    let decl =
+      {
+        declKind;
+        moduleLoc;
+        posAdjustment;
+        path = name :: path;
+        pos;
+        posEnd;
+        posStart;
+        resolvedDead = None;
+        report = true;
+      }
+    in
+    PosHash.replace decls pos decl)
+
+let addValueDeclaration ?(isToplevel = true) ~(loc : Location.t) ~moduleLoc
+    ?(optionalArgs = OptionalArgs.empty) ~path ~sideEffects name =
+  name
+  |> addDeclaration_
+       ~declKind:(Value {isToplevel; optionalArgs; sideEffects})
+       ~loc ~moduleLoc ~path
+
+let emitWarning ~decl ~message deadWarning =
+  let loc = decl |> declGetLoc in
+  let isToplevelValueWithSideEffects decl =
+    match decl.declKind with
+    | Value {isToplevel; sideEffects} -> isToplevel && sideEffects
+    | _ -> false
+  in
+  let shouldWriteLineAnnotation =
+    (not (isToplevelValueWithSideEffects decl))
+    && Suppress.filter decl.pos
+    && deadWarning <> IncorrectDeadAnnotation
+  in
+  let lineAnnotation =
+    if shouldWriteLineAnnotation then
+      WriteDeadAnnotations.addLineAnnotation ~decl
+    else None
+  in
+  decl.path
+  |> Path.toModuleName ~isType:(decl.declKind |> DeclKind.isType)
+  |> DeadModules.checkModuleDead ~fileName:decl.pos.pos_fname;
+  Log_.warning ~loc
+    (DeadWarning
+       {
+         deadWarning;
+         path = Path.withoutHead decl.path;
+         message;
+         lineAnnotation;
+         shouldWriteLineAnnotation;
+       })
+
+module Decl = struct
+  let isValue decl =
+    match decl.declKind with
+    | Value _ (* | Exception *) -> true
+    | _ -> false
+
+  let isToplevelValueWithSideEffects decl =
+    match decl.declKind with
+    | Value {isToplevel; sideEffects} -> isToplevel && sideEffects
+    | _ -> false
+
+  let compareUsingDependencies ~orderedFiles
+      {
+        declKind = kind1;
+        path = _path1;
+        pos =
+          {
+            pos_fname = fname1;
+            pos_lnum = lnum1;
+            pos_bol = bol1;
+            pos_cnum = cnum1;
+          };
+      }
+      {
+        declKind = kind2;
+        path = _path2;
+        pos =
+          {
+            pos_fname = fname2;
+            pos_lnum = lnum2;
+            pos_bol = bol2;
+            pos_cnum = cnum2;
+          };
+      } =
+    let findPosition fn = Hashtbl.find orderedFiles fn [@@raises Not_found] in
+    (* From the root of the file dependency DAG to the leaves.
+       From the bottom of the file to the top. *)
+    let position1, position2 =
+      try (fname1 |> findPosition, fname2 |> findPosition)
+      with Not_found -> (0, 0)
+    in
+    compare
+      (position1, lnum2, bol2, cnum2, kind1)
+      (position2, lnum1, bol1, cnum1, kind2)
+
+  let compareForReporting
+      {
+        declKind = kind1;
+        pos =
+          {
+            pos_fname = fname1;
+            pos_lnum = lnum1;
+            pos_bol = bol1;
+            pos_cnum = cnum1;
+          };
+      }
+      {
+        declKind = kind2;
+        pos =
+          {
+            pos_fname = fname2;
+            pos_lnum = lnum2;
+            pos_bol = bol2;
+            pos_cnum = cnum2;
+          };
+      } =
+    compare
+      (fname1, lnum1, bol1, cnum1, kind1)
+      (fname2, lnum2, bol2, cnum2, kind2)
+
+  let isInsideReportedValue decl =
+    let fileHasChanged =
+      !Current.maxValuePosEnd.pos_fname <> decl.pos.pos_fname
+    in
+    let insideReportedValue =
+      decl |> isValue && (not fileHasChanged)
+      && !Current.maxValuePosEnd.pos_cnum > decl.pos.pos_cnum
+    in
+    if not insideReportedValue then
+      if decl |> isValue then
+        if
+          fileHasChanged
+          || decl.posEnd.pos_cnum > !Current.maxValuePosEnd.pos_cnum
+        then Current.maxValuePosEnd := decl.posEnd;
+    insideReportedValue
+
+  let report decl =
+    let insideReportedValue = decl |> isInsideReportedValue in
+    if decl.report then
+      let name, message =
+        match decl.declKind with
+        | Exception ->
+          (WarningDeadException, "is never raised or passed as value")
+        | Value {sideEffects} -> (
+          let noSideEffectsOrUnderscore =
+            (not sideEffects)
+            ||
+            match decl.path with
+            | hd :: _ -> hd |> Name.startsWithUnderscore
+            | [] -> false
+          in
+          ( (match not noSideEffectsOrUnderscore with
+            | true -> WarningDeadValueWithSideEffects
+            | false -> WarningDeadValue),
+            match decl.path with
+            | name :: _ when name |> Name.isUnderscore ->
+              "has no side effects and can be removed"
+            | _ -> (
+              "is never used"
+              ^
+              match not noSideEffectsOrUnderscore with
+              | true -> " and could have side effects"
+              | false -> "") ))
+        | RecordLabel ->
+          (WarningDeadType, "is a record label never used to read a value")
+        | VariantCase ->
+          (WarningDeadType, "is a variant case which is never constructed")
+      in
+      let hasRefBelow () =
+        let refs = ValueReferences.find decl.pos in
+        let refIsBelow (pos : Lexing.position) =
+          decl.pos.pos_fname <> pos.pos_fname
+          || decl.pos.pos_cnum < pos.pos_cnum
+             && (* not a function defined inside a function, e.g. not a callback *)
+             decl.posEnd.pos_cnum < pos.pos_cnum
+        in
+        refs |> PosSet.exists refIsBelow
+      in
+      let shouldEmitWarning =
+        (not insideReportedValue)
+        && (match decl.path with
+           | name :: _ when name |> Name.isUnderscore -> Config.reportUnderscore
+           | _ -> true)
+        && (runConfig.transitive || not (hasRefBelow ()))
+      in
+      if shouldEmitWarning then (
+        decl.path
+        |> Path.toModuleName ~isType:(decl.declKind |> DeclKind.isType)
+        |> DeadModules.checkModuleDead ~fileName:decl.pos.pos_fname;
+        emitWarning ~decl ~message name)
+end
+
+let declIsDead ~refs decl =
+  let liveRefs =
+    refs
+    |> PosSet.filter (fun p -> not (ProcessDeadAnnotations.isAnnotatedDead p))
+  in
+  liveRefs |> PosSet.cardinal = 0
+  && not (ProcessDeadAnnotations.isAnnotatedGenTypeOrLive decl.pos)
+
+let doReportDead pos = not (ProcessDeadAnnotations.isAnnotatedGenTypeOrDead pos)
+
+let rec resolveRecursiveRefs ~checkOptionalArg ~deadDeclarations ~level
+    ~orderedFiles ~refs ~refsBeingResolved decl : bool =
+  match decl.pos with
+  | _ when decl.resolvedDead <> None ->
+    if Config.recursiveDebug then
+      Log_.item "recursiveDebug %s [%d] already resolved@."
+        (decl.path |> Path.toString)
+        level;
+    decl.pos |> ProcessDeadAnnotations.isAnnotatedDead
+  | _ when PosSet.mem decl.pos !refsBeingResolved ->
+    if Config.recursiveDebug then
+      Log_.item "recursiveDebug %s [%d] is being resolved: assume dead@."
+        (decl.path |> Path.toString)
+        level;
+    true
+  | _ ->
+    if Config.recursiveDebug then
+      Log_.item "recursiveDebug resolving %s [%d]@."
+        (decl.path |> Path.toString)
+        level;
+    refsBeingResolved := PosSet.add decl.pos !refsBeingResolved;
+    let allDepsResolved = ref true in
+    let newRefs =
+      refs
+      |> PosSet.filter (fun pos ->
+             if pos = decl.pos then (
+               if Config.recursiveDebug then
+                 Log_.item "recursiveDebug %s ignoring reference to self@."
+                   (decl.path |> Path.toString);
+               false)
+             else
+               match PosHash.find_opt decls pos with
+               | None ->
+                 if Config.recursiveDebug then
+                   Log_.item "recursiveDebug can't find decl for %s@."
+                     (pos |> posToString);
+                 true
+               | Some xDecl ->
+                 let xRefs =
+                   match xDecl.declKind |> DeclKind.isType with
+                   | true -> TypeReferences.find pos
+                   | false -> ValueReferences.find pos
+                 in
+                 let xDeclIsDead =
+                   xDecl
+                   |> resolveRecursiveRefs ~checkOptionalArg ~deadDeclarations
+                        ~level:(level + 1) ~orderedFiles ~refs:xRefs
+                        ~refsBeingResolved
+                 in
+                 if xDecl.resolvedDead = None then allDepsResolved := false;
+                 not xDeclIsDead)
+    in
+    let isDead = decl |> declIsDead ~refs:newRefs in
+    let isResolved = (not isDead) || !allDepsResolved || level = 0 in
+    if isResolved then (
+      decl.resolvedDead <- Some isDead;
+      if isDead then (
+        decl.path
+        |> DeadModules.markDead
+             ~isType:(decl.declKind |> DeclKind.isType)
+             ~loc:decl.moduleLoc;
+        if not (decl.pos |> doReportDead) then decl.report <- false;
+        deadDeclarations := decl :: !deadDeclarations;
+        if not (Decl.isToplevelValueWithSideEffects decl) then
+          decl.pos |> ProcessDeadAnnotations.annotateDead)
+      else (
+        checkOptionalArg decl;
+        decl.path
+        |> DeadModules.markLive
+             ~isType:(decl.declKind |> DeclKind.isType)
+             ~loc:decl.moduleLoc;
+        if decl.pos |> ProcessDeadAnnotations.isAnnotatedDead then
+          emitWarning ~decl ~message:" is annotated @dead but is live"
+            IncorrectDeadAnnotation);
+      if !Cli.debug then
+        let refsString =
+          newRefs |> PosSet.elements |> List.map posToString
+          |> String.concat ", "
+        in
+        Log_.item "%s %s %s: %d references (%s) [%d]@."
+          (match isDead with
+          | true -> "Dead"
+          | false -> "Live")
+          (decl.declKind |> DeclKind.toString)
+          (decl.path |> Path.toString)
+          (newRefs |> PosSet.cardinal)
+          refsString level);
+    isDead
+
+let reportDead ~checkOptionalArg =
+  let iterDeclInOrder ~deadDeclarations ~orderedFiles decl =
+    let refs =
+      match decl |> Decl.isValue with
+      | true -> ValueReferences.find decl.pos
+      | false -> TypeReferences.find decl.pos
+    in
+    resolveRecursiveRefs ~checkOptionalArg ~deadDeclarations ~level:0
+      ~orderedFiles ~refsBeingResolved:(ref PosSet.empty) ~refs decl
+    |> ignore
+  in
+  if !Cli.debug then (
+    Log_.item "@.File References@.@.";
+    let fileList = ref [] in
+    FileReferences.iter (fun file files ->
+        fileList := (file, files) :: !fileList);
+    !fileList
+    |> List.sort (fun (f1, _) (f2, _) -> String.compare f1 f2)
+    |> List.iter (fun (file, files) ->
+           Log_.item "%s -->> %s@."
+             (file |> Filename.basename)
+             (files |> FileSet.elements |> List.map Filename.basename
+            |> String.concat ", ")));
+  let declarations =
+    PosHash.fold (fun _pos decl declarations -> decl :: declarations) decls []
+  in
+  let orderedFiles = Hashtbl.create 256 in
+  iterFilesFromRootsToLeaves
+    (let current = ref 0 in
+     fun fileName ->
+       incr current;
+       Hashtbl.add orderedFiles fileName !current);
+  let orderedDeclarations =
+    (* analyze in reverse order *)
+    declarations |> List.fast_sort (Decl.compareUsingDependencies ~orderedFiles)
+  in
+  let deadDeclarations = ref [] in
+  orderedDeclarations
+  |> List.iter (iterDeclInOrder ~orderedFiles ~deadDeclarations);
+  let sortedDeadDeclarations =
+    !deadDeclarations |> List.fast_sort Decl.compareForReporting
+  in
+  (* XXX *)
+  sortedDeadDeclarations |> List.iter Decl.report
diff --git a/analysis/reanalyze/src/DeadException.ml b/analysis/reanalyze/src/DeadException.ml
new file mode 100644
index 0000000000..023bee3f68
--- /dev/null
+++ b/analysis/reanalyze/src/DeadException.ml
@@ -0,0 +1,33 @@
+open DeadCommon
+open Common
+
+type item = {exceptionPath: Path.t; locFrom: Location.t}
+
+let delayedItems = ref []
+let declarations = Hashtbl.create 1
+
+let add ~path ~loc ~(strLoc : Location.t) name =
+  let exceptionPath = name :: path in
+  Hashtbl.add declarations exceptionPath loc;
+  name
+  |> addDeclaration_ ~posEnd:strLoc.loc_end ~posStart:strLoc.loc_start
+       ~declKind:Exception ~moduleLoc:(ModulePath.getCurrent ()).loc ~path ~loc
+
+let forceDelayedItems () =
+  let items = !delayedItems |> List.rev in
+  delayedItems := [];
+  items
+  |> List.iter (fun {exceptionPath; locFrom} ->
+         match Hashtbl.find_opt declarations exceptionPath with
+         | None -> ()
+         | Some locTo ->
+           addValueReference ~addFileReference:true ~locFrom ~locTo)
+
+let markAsUsed ~(locFrom : Location.t) ~(locTo : Location.t) path_ =
+  if locTo.loc_ghost then
+    (* Probably defined in another file, delay processing and check at the end *)
+    let exceptionPath =
+      path_ |> Path.fromPathT |> Path.moduleToImplementation
+    in
+    delayedItems := {exceptionPath; locFrom} :: !delayedItems
+  else addValueReference ~addFileReference:true ~locFrom ~locTo
diff --git a/analysis/reanalyze/src/DeadModules.ml b/analysis/reanalyze/src/DeadModules.ml
new file mode 100644
index 0000000000..572748bcfa
--- /dev/null
+++ b/analysis/reanalyze/src/DeadModules.ml
@@ -0,0 +1,44 @@
+let active () =
+  (* When transitive reporting is off, the only dead modules would be empty modules *)
+  RunConfig.runConfig.transitive
+
+let table = Hashtbl.create 1
+
+let markDead ~isType ~loc path =
+  if active () then
+    let moduleName = path |> Common.Path.toModuleName ~isType in
+    match Hashtbl.find_opt table moduleName with
+    | Some _ -> ()
+    | _ -> Hashtbl.replace table moduleName (false, loc)
+
+let markLive ~isType ~(loc : Location.t) path =
+  if active () then
+    let moduleName = path |> Common.Path.toModuleName ~isType in
+    match Hashtbl.find_opt table moduleName with
+    | None -> Hashtbl.replace table moduleName (true, loc)
+    | Some (false, loc) -> Hashtbl.replace table moduleName (true, loc)
+    | Some (true, _) -> ()
+
+let checkModuleDead ~fileName:pos_fname moduleName =
+  if active () then
+    match Hashtbl.find_opt table moduleName with
+    | Some (false, loc) ->
+      Hashtbl.remove table moduleName;
+      (* only report once *)
+      let loc =
+        if loc.loc_ghost then
+          let pos =
+            {Lexing.pos_fname; pos_lnum = 0; pos_bol = 0; pos_cnum = 0}
+          in
+          {Location.loc_start = pos; loc_end = pos; loc_ghost = false}
+        else loc
+      in
+      Log_.warning ~loc
+        (Common.DeadModule
+           {
+             message =
+               Format.asprintf "@{<info>%s@} %s"
+                 (moduleName |> Name.toInterface |> Name.toString)
+                 "is a dead module as all its items are dead.";
+           })
+    | _ -> ()
diff --git a/analysis/reanalyze/src/DeadOptionalArgs.ml b/analysis/reanalyze/src/DeadOptionalArgs.ml
new file mode 100644
index 0000000000..8a3c7e4b87
--- /dev/null
+++ b/analysis/reanalyze/src/DeadOptionalArgs.ml
@@ -0,0 +1,116 @@
+open DeadCommon
+open Common
+
+let active () = true
+
+type item = {
+  posTo: Lexing.position;
+  argNames: string list;
+  argNamesMaybe: string list;
+}
+
+let delayedItems = (ref [] : item list ref)
+let functionReferences = (ref [] : (Lexing.position * Lexing.position) list ref)
+
+let addFunctionReference ~(locFrom : Location.t) ~(locTo : Location.t) =
+  if active () then
+    let posTo = locTo.loc_start in
+    let posFrom = locFrom.loc_start in
+    let shouldAdd =
+      match PosHash.find_opt decls posTo with
+      | Some {declKind = Value {optionalArgs}} ->
+        not (OptionalArgs.isEmpty optionalArgs)
+      | _ -> false
+    in
+    if shouldAdd then (
+      if !Common.Cli.debug then
+        Log_.item "OptionalArgs.addFunctionReference %s %s@."
+          (posFrom |> posToString) (posTo |> posToString);
+      functionReferences := (posFrom, posTo) :: !functionReferences)
+
+let rec hasOptionalArgs (texpr : Types.type_expr) =
+  match texpr.desc with
+  | _ when not (active ()) -> false
+  | Tarrow (Optional _, _tFrom, _tTo, _) -> true
+  | Tarrow (_, _tFrom, tTo, _) -> hasOptionalArgs tTo
+  | Tlink t -> hasOptionalArgs t
+  | Tsubst t -> hasOptionalArgs t
+  | _ -> false
+
+let rec fromTypeExpr (texpr : Types.type_expr) =
+  match texpr.desc with
+  | _ when not (active ()) -> []
+  | Tarrow (Optional s, _tFrom, tTo, _) -> s :: fromTypeExpr tTo
+  | Tarrow (_, _tFrom, tTo, _) -> fromTypeExpr tTo
+  | Tlink t -> fromTypeExpr t
+  | Tsubst t -> fromTypeExpr t
+  | _ -> []
+
+let addReferences ~(locFrom : Location.t) ~(locTo : Location.t) ~path
+    (argNames, argNamesMaybe) =
+  if active () then (
+    let posTo = locTo.loc_start in
+    let posFrom = locFrom.loc_start in
+    delayedItems := {posTo; argNames; argNamesMaybe} :: !delayedItems;
+    if !Common.Cli.debug then
+      Log_.item
+        "DeadOptionalArgs.addReferences %s called with optional argNames:%s \
+         argNamesMaybe:%s %s@."
+        (path |> Path.fromPathT |> Path.toString)
+        (argNames |> String.concat ", ")
+        (argNamesMaybe |> String.concat ", ")
+        (posFrom |> posToString))
+
+let forceDelayedItems () =
+  let items = !delayedItems |> List.rev in
+  delayedItems := [];
+  items
+  |> List.iter (fun {posTo; argNames; argNamesMaybe} ->
+         match PosHash.find_opt decls posTo with
+         | Some {declKind = Value r} ->
+           r.optionalArgs |> OptionalArgs.call ~argNames ~argNamesMaybe
+         | _ -> ());
+  let fRefs = !functionReferences |> List.rev in
+  functionReferences := [];
+  fRefs
+  |> List.iter (fun (posFrom, posTo) ->
+         match
+           (PosHash.find_opt decls posFrom, PosHash.find_opt decls posTo)
+         with
+         | Some {declKind = Value rFrom}, Some {declKind = Value rTo} ->
+           OptionalArgs.combine rFrom.optionalArgs rTo.optionalArgs
+         | _ -> ())
+
+let check decl =
+  match decl with
+  | {declKind = Value {optionalArgs}}
+    when active ()
+         && not (ProcessDeadAnnotations.isAnnotatedGenTypeOrLive decl.pos) ->
+    optionalArgs
+    |> OptionalArgs.iterUnused (fun s ->
+           Log_.warning ~loc:(decl |> declGetLoc)
+             (DeadOptional
+                {
+                  deadOptional = WarningUnusedArgument;
+                  message =
+                    Format.asprintf
+                      "optional argument @{<info>%s@} of function @{<info>%s@} \
+                       is never used"
+                      s
+                      (decl.path |> Path.withoutHead);
+                }));
+    optionalArgs
+    |> OptionalArgs.iterAlwaysUsed (fun s nCalls ->
+           Log_.warning ~loc:(decl |> declGetLoc)
+             (DeadOptional
+                {
+                  deadOptional = WarningRedundantOptionalArgument;
+                  message =
+                    Format.asprintf
+                      "optional argument @{<info>%s@} of function @{<info>%s@} \
+                       is always supplied (%d calls)"
+                      s
+                      (decl.path |> Path.withoutHead)
+                      nCalls;
+                }))
+  | _ -> ()
diff --git a/analysis/reanalyze/src/DeadType.ml b/analysis/reanalyze/src/DeadType.ml
new file mode 100644
index 0000000000..d7e2383579
--- /dev/null
+++ b/analysis/reanalyze/src/DeadType.ml
@@ -0,0 +1,126 @@
+(* Adapted from https://github.com/LexiFi/dead_code_analyzer *)
+
+open Common
+open DeadCommon
+
+module TypeLabels = struct
+  (* map from type path (for record/variant label) to its location *)
+
+  let table = (Hashtbl.create 256 : (Path.t, Location.t) Hashtbl.t)
+  let add path loc = Hashtbl.replace table path loc
+  let find path = Hashtbl.find_opt table path
+end
+
+let addTypeReference ~posFrom ~posTo =
+  if !Common.Cli.debug then
+    Log_.item "addTypeReference %s --> %s@." (posFrom |> posToString)
+      (posTo |> posToString);
+  TypeReferences.add posTo posFrom
+
+module TypeDependencies = struct
+  let delayedItems = ref []
+  let add loc1 loc2 = delayedItems := (loc1, loc2) :: !delayedItems
+  let clear () = delayedItems := []
+
+  let processTypeDependency
+      ( ({loc_start = posTo; loc_ghost = ghost1} : Location.t),
+        ({loc_start = posFrom; loc_ghost = ghost2} : Location.t) ) =
+    if (not ghost1) && (not ghost2) && posTo <> posFrom then
+      addTypeReference ~posTo ~posFrom
+
+  let forceDelayedItems () = List.iter processTypeDependency !delayedItems
+end
+
+let extendTypeDependencies (loc1 : Location.t) (loc2 : Location.t) =
+  if loc1.loc_start <> loc2.loc_start then (
+    if !Common.Cli.debug then
+      Log_.item "extendTypeDependencies %s --> %s@."
+        (loc1.loc_start |> posToString)
+        (loc2.loc_start |> posToString);
+    TypeDependencies.add loc1 loc2)
+
+(* Type dependencies between Foo.re and Foo.rei *)
+let addTypeDependenciesAcrossFiles ~pathToType ~loc ~typeLabelName =
+  let isInterface = Filename.check_suffix !Common.currentSrc "i" in
+  if not isInterface then (
+    let path_1 = pathToType |> Path.moduleToInterface in
+    let path_2 = path_1 |> Path.typeToInterface in
+    let path1 = typeLabelName :: path_1 in
+    let path2 = typeLabelName :: path_2 in
+    match TypeLabels.find path1 with
+    | None -> (
+      match TypeLabels.find path2 with
+      | None -> ()
+      | Some loc2 ->
+        extendTypeDependencies loc loc2;
+        if not Config.reportTypesDeadOnlyInInterface then
+          extendTypeDependencies loc2 loc)
+    | Some loc1 ->
+      extendTypeDependencies loc loc1;
+      if not Config.reportTypesDeadOnlyInInterface then
+        extendTypeDependencies loc1 loc)
+  else
+    let path_1 = pathToType |> Path.moduleToImplementation in
+    let path1 = typeLabelName :: path_1 in
+    match TypeLabels.find path1 with
+    | None -> ()
+    | Some loc1 ->
+      extendTypeDependencies loc1 loc;
+      if not Config.reportTypesDeadOnlyInInterface then
+        extendTypeDependencies loc loc1
+
+(* Add type dependencies between implementation and interface in inner module *)
+let addTypeDependenciesInnerModule ~pathToType ~loc ~typeLabelName =
+  let path = typeLabelName :: pathToType in
+  match TypeLabels.find path with
+  | Some loc2 ->
+    extendTypeDependencies loc loc2;
+    if not Config.reportTypesDeadOnlyInInterface then
+      extendTypeDependencies loc2 loc
+  | None -> TypeLabels.add path loc
+
+let addDeclaration ~(typeId : Ident.t) ~(typeKind : Types.type_kind) =
+  let currentModulePath = ModulePath.getCurrent () in
+  let pathToType =
+    (typeId |> Ident.name |> Name.create)
+    :: (currentModulePath.path @ [!Common.currentModuleName])
+  in
+  let processTypeLabel ?(posAdjustment = Nothing) typeLabelName ~declKind
+      ~(loc : Location.t) =
+    addDeclaration_ ~declKind ~path:pathToType ~loc
+      ~moduleLoc:currentModulePath.loc ~posAdjustment typeLabelName;
+    addTypeDependenciesAcrossFiles ~pathToType ~loc ~typeLabelName;
+    addTypeDependenciesInnerModule ~pathToType ~loc ~typeLabelName;
+    TypeLabels.add (typeLabelName :: pathToType) loc
+  in
+  match typeKind with
+  | Type_record (l, _) ->
+    List.iter
+      (fun {Types.ld_id; ld_loc} ->
+        Ident.name ld_id |> Name.create
+        |> processTypeLabel ~declKind:RecordLabel ~loc:ld_loc)
+      l
+  | Type_variant decls ->
+    List.iteri
+      (fun i {Types.cd_id; cd_loc; cd_args} ->
+        let _handle_inline_records =
+          match cd_args with
+          | Cstr_record lbls ->
+            List.iter
+              (fun {Types.ld_id; ld_loc} ->
+                Ident.name cd_id ^ "." ^ Ident.name ld_id
+                |> Name.create
+                |> processTypeLabel ~declKind:RecordLabel ~loc:ld_loc)
+              lbls
+          | Cstr_tuple _ -> ()
+        in
+        let posAdjustment =
+          (* In Res the variant loc can include the | and spaces after it *)
+          if WriteDeadAnnotations.posLanguage cd_loc.loc_start = Res then
+            if i = 0 then FirstVariant else OtherVariant
+          else Nothing
+        in
+        Ident.name cd_id |> Name.create
+        |> processTypeLabel ~declKind:VariantCase ~loc:cd_loc ~posAdjustment)
+      decls
+  | _ -> ()
diff --git a/analysis/reanalyze/src/DeadValue.ml b/analysis/reanalyze/src/DeadValue.ml
new file mode 100644
index 0000000000..0a242b22a2
--- /dev/null
+++ b/analysis/reanalyze/src/DeadValue.ml
@@ -0,0 +1,387 @@
+(* Adapted from https://github.com/LexiFi/dead_code_analyzer *)
+
+open DeadCommon
+
+let checkAnyValueBindingWithNoSideEffects
+    ({vb_pat = {pat_desc}; vb_expr = expr; vb_loc = loc} :
+      Typedtree.value_binding) =
+  match pat_desc with
+  | Tpat_any when (not (SideEffects.checkExpr expr)) && not loc.loc_ghost ->
+    let name = "_" |> Name.create ~isInterface:false in
+    let currentModulePath = ModulePath.getCurrent () in
+    let path = currentModulePath.path @ [!Common.currentModuleName] in
+    name
+    |> addValueDeclaration ~path ~loc ~moduleLoc:currentModulePath.loc
+         ~sideEffects:false
+  | _ -> ()
+
+let collectValueBinding super self (vb : Typedtree.value_binding) =
+  let oldCurrentBindings = !Current.bindings in
+  let oldLastBinding = !Current.lastBinding in
+  checkAnyValueBindingWithNoSideEffects vb;
+  let loc =
+    match vb.vb_pat.pat_desc with
+    | Tpat_var (id, {loc = {loc_start; loc_ghost} as loc})
+    | Tpat_alias
+        ({pat_desc = Tpat_any}, id, {loc = {loc_start; loc_ghost} as loc})
+      when (not loc_ghost) && not vb.vb_loc.loc_ghost ->
+      let name = Ident.name id |> Name.create ~isInterface:false in
+      let optionalArgs =
+        vb.vb_expr.exp_type |> DeadOptionalArgs.fromTypeExpr
+        |> Common.OptionalArgs.fromList
+      in
+      let exists =
+        match PosHash.find_opt decls loc_start with
+        | Some {declKind = Value r} ->
+          r.optionalArgs <- optionalArgs;
+          true
+        | _ -> false
+      in
+      let currentModulePath = ModulePath.getCurrent () in
+      let path = currentModulePath.path @ [!Common.currentModuleName] in
+      let isFirstClassModule =
+        match vb.vb_expr.exp_type.desc with
+        | Tpackage _ -> true
+        | _ -> false
+      in
+      (if (not exists) && not isFirstClassModule then
+         (* This is never toplevel currently *)
+         let isToplevel = oldLastBinding = Location.none in
+         let sideEffects = SideEffects.checkExpr vb.vb_expr in
+         name
+         |> addValueDeclaration ~isToplevel ~loc
+              ~moduleLoc:currentModulePath.loc ~optionalArgs ~path ~sideEffects);
+      (match PosHash.find_opt decls loc_start with
+      | None -> ()
+      | Some decl ->
+        (* Value bindings contain the correct location for the entire declaration: update final position.
+           The previous value was taken from the signature, which only has positions for the id. *)
+        let declKind =
+          match decl.declKind with
+          | Value vk ->
+            Common.DeclKind.Value
+              {vk with sideEffects = SideEffects.checkExpr vb.vb_expr}
+          | dk -> dk
+        in
+        PosHash.replace decls loc_start
+          {
+            decl with
+            declKind;
+            posEnd = vb.vb_loc.loc_end;
+            posStart = vb.vb_loc.loc_start;
+          });
+      loc
+    | _ -> !Current.lastBinding
+  in
+  Current.bindings := PosSet.add loc.loc_start !Current.bindings;
+  Current.lastBinding := loc;
+  let r = super.Tast_mapper.value_binding self vb in
+  Current.bindings := oldCurrentBindings;
+  Current.lastBinding := oldLastBinding;
+  r
+
+let processOptionalArgs ~expType ~(locFrom : Location.t) ~locTo ~path args =
+  if expType |> DeadOptionalArgs.hasOptionalArgs then (
+    let supplied = ref [] in
+    let suppliedMaybe = ref [] in
+    args
+    |> List.iter (fun (lbl, arg) ->
+           let argIsSupplied =
+             match arg with
+             | Some
+                 {
+                   Typedtree.exp_desc =
+                     Texp_construct (_, {cstr_name = "Some"}, _);
+                 } ->
+               Some true
+             | Some
+                 {
+                   Typedtree.exp_desc =
+                     Texp_construct (_, {cstr_name = "None"}, _);
+                 } ->
+               Some false
+             | Some _ -> None
+             | None -> Some false
+           in
+           match lbl with
+           | Asttypes.Optional s when not locFrom.loc_ghost ->
+             if argIsSupplied <> Some false then supplied := s :: !supplied;
+             if argIsSupplied = None then suppliedMaybe := s :: !suppliedMaybe
+           | _ -> ());
+    (!supplied, !suppliedMaybe)
+    |> DeadOptionalArgs.addReferences ~locFrom ~locTo ~path)
+
+let rec collectExpr super self (e : Typedtree.expression) =
+  let locFrom = e.exp_loc in
+  (match e.exp_desc with
+  | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as locTo}) ->
+    (* if Path.name _path = "rc" then assert false; *)
+    if locFrom = locTo && _path |> Path.name = "emptyArray" then (
+      (* Work around lowercase jsx with no children producing an artifact `emptyArray`
+         which is called from its own location as many things are generated on the same location. *)
+      if !Common.Cli.debug then
+        Log_.item "addDummyReference %s --> %s@."
+          (Location.none.loc_start |> Common.posToString)
+          (locTo.loc_start |> Common.posToString);
+      ValueReferences.add locTo.loc_start Location.none.loc_start)
+    else addValueReference ~addFileReference:true ~locFrom ~locTo
+  | Texp_apply
+      ( {
+          exp_desc =
+            Texp_ident
+              (path, _, {Types.val_loc = {loc_ghost = false; _} as locTo});
+          exp_type;
+        },
+        args ) ->
+    args
+    |> processOptionalArgs ~expType:exp_type
+         ~locFrom:(locFrom : Location.t)
+         ~locTo ~path
+  | Texp_let
+      ( (* generated for functions with optional args *)
+        Nonrecursive,
+        [
+          {
+            vb_pat = {pat_desc = Tpat_var (idArg, _)};
+            vb_expr =
+              {
+                exp_desc =
+                  Texp_ident
+                    (path, _, {Types.val_loc = {loc_ghost = false; _} as locTo});
+                exp_type;
+              };
+          };
+        ],
+        {
+          exp_desc =
+            Texp_function
+              {
+                cases =
+                  [
+                    {
+                      c_lhs = {pat_desc = Tpat_var (etaArg, _)};
+                      c_rhs =
+                        {
+                          exp_desc =
+                            Texp_apply
+                              ({exp_desc = Texp_ident (idArg2, _, _)}, args);
+                        };
+                    };
+                  ];
+              };
+        } )
+    when Ident.name idArg = "arg"
+         && Ident.name etaArg = "eta"
+         && Path.name idArg2 = "arg" ->
+    args
+    |> processOptionalArgs ~expType:exp_type
+         ~locFrom:(locFrom : Location.t)
+         ~locTo ~path
+  | Texp_field
+      (_, _, {lbl_loc = {Location.loc_start = posTo; loc_ghost = false}; _}) ->
+    if !Config.analyzeTypes then
+      DeadType.addTypeReference ~posTo ~posFrom:locFrom.loc_start
+  | Texp_construct
+      ( _,
+        {cstr_loc = {Location.loc_start = posTo; loc_ghost} as locTo; cstr_tag},
+        _ ) ->
+    (match cstr_tag with
+    | Cstr_extension path -> path |> DeadException.markAsUsed ~locFrom ~locTo
+    | _ -> ());
+    if !Config.analyzeTypes && not loc_ghost then
+      DeadType.addTypeReference ~posTo ~posFrom:locFrom.loc_start
+  | Texp_record {fields} ->
+    fields
+    |> Array.iter (fun (_, record_label_definition) ->
+           match record_label_definition with
+           | Typedtree.Overridden (_, ({exp_loc} as e)) when exp_loc.loc_ghost
+             ->
+             (* Punned field in OCaml projects has ghost location in expression *)
+             let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in
+             collectExpr super self e |> ignore
+           | _ -> ())
+  | _ -> ());
+  super.Tast_mapper.expr self e
+
+(*
+  type k. is a locally abstract type
+  https://caml.inria.fr/pub/docs/manual-ocaml/locallyabstract.html
+  it is required because in ocaml >= 4.11 Typedtree.pattern and ADT is converted
+  in a GADT
+  https://github.com/ocaml/ocaml/commit/312253ce822c32740349e572498575cf2a82ee96
+  in short: all branches of pattern matches aren't the same type.
+  With this annotation we declare a new type for each branch to allow the
+  function to be typed.
+  *)
+let collectPattern : _ -> _ -> Typedtree.pattern -> Typedtree.pattern =
+ fun super self pat ->
+  let posFrom = pat.Typedtree.pat_loc.loc_start in
+  (match pat.pat_desc with
+  | Typedtree.Tpat_record (cases, _clodsedFlag) ->
+    cases
+    |> List.iter (fun (_loc, {Types.lbl_loc = {loc_start = posTo}}, _pat) ->
+           if !Config.analyzeTypes then
+             DeadType.addTypeReference ~posFrom ~posTo)
+  | _ -> ());
+  super.Tast_mapper.pat self pat
+
+let rec getSignature (moduleType : Types.module_type) =
+  match moduleType with
+  | Mty_signature signature -> signature
+  | Mty_functor (_, _mtParam, mt) -> getSignature mt
+  | _ -> []
+
+let rec processSignatureItem ~doTypes ~doValues ~moduleLoc ~path
+    (si : Types.signature_item) =
+  let oldModulePath = ModulePath.getCurrent () in
+  (match si with
+  | Sig_type (id, t, _) when doTypes ->
+    if !Config.analyzeTypes then
+      DeadType.addDeclaration ~typeId:id ~typeKind:t.type_kind
+  | Sig_value (id, {Types.val_loc = loc; val_kind = kind; val_type})
+    when doValues ->
+    if not loc.Location.loc_ghost then
+      let isPrimitive =
+        match kind with
+        | Val_prim _ -> true
+        | _ -> false
+      in
+      if (not isPrimitive) || !Config.analyzeExternals then
+        let optionalArgs =
+          val_type |> DeadOptionalArgs.fromTypeExpr
+          |> Common.OptionalArgs.fromList
+        in
+
+        (* if Ident.name id = "someValue" then
+           Printf.printf "XXX %s\n" (Ident.name id); *)
+        Ident.name id
+        |> Name.create ~isInterface:false
+        |> addValueDeclaration ~loc ~moduleLoc ~optionalArgs ~path
+             ~sideEffects:false
+  | Sig_module (id, {Types.md_type = moduleType; md_loc = moduleLoc}, _)
+  | Sig_modtype (id, {Types.mtd_type = Some moduleType; mtd_loc = moduleLoc}) ->
+    ModulePath.setCurrent
+      {
+        oldModulePath with
+        loc = moduleLoc;
+        path = (id |> Ident.name |> Name.create) :: oldModulePath.path;
+      };
+    let collect =
+      match si with
+      | Sig_modtype _ -> false
+      | _ -> true
+    in
+    if collect then
+      getSignature moduleType
+      |> List.iter
+           (processSignatureItem ~doTypes ~doValues ~moduleLoc
+              ~path:((id |> Ident.name |> Name.create) :: path))
+  | _ -> ());
+  ModulePath.setCurrent oldModulePath
+
+(* Traverse the AST *)
+let traverseStructure ~doTypes ~doExternals =
+  let super = Tast_mapper.default in
+  let expr self e = e |> collectExpr super self in
+  let pat self p = p |> collectPattern super self in
+  let value_binding self vb = vb |> collectValueBinding super self in
+  let structure_item self (structureItem : Typedtree.structure_item) =
+    let oldModulePath = ModulePath.getCurrent () in
+    (match structureItem.str_desc with
+    | Tstr_module {mb_expr; mb_id; mb_loc} -> (
+      let hasInterface =
+        match mb_expr.mod_desc with
+        | Tmod_constraint _ -> true
+        | _ -> false
+      in
+      ModulePath.setCurrent
+        {
+          oldModulePath with
+          loc = mb_loc;
+          path = (mb_id |> Ident.name |> Name.create) :: oldModulePath.path;
+        };
+      if hasInterface then
+        match mb_expr.mod_type with
+        | Mty_signature signature ->
+          signature
+          |> List.iter
+               (processSignatureItem ~doTypes ~doValues:false
+                  ~moduleLoc:mb_expr.mod_loc
+                  ~path:
+                    ((ModulePath.getCurrent ()).path
+                    @ [!Common.currentModuleName]))
+        | _ -> ())
+    | Tstr_primitive vd when doExternals && !Config.analyzeExternals ->
+      let currentModulePath = ModulePath.getCurrent () in
+      let path = currentModulePath.path @ [!Common.currentModuleName] in
+      let exists =
+        match PosHash.find_opt decls vd.val_loc.loc_start with
+        | Some {declKind = Value _} -> true
+        | _ -> false
+      in
+      let id = vd.val_id |> Ident.name in
+      Printf.printf "Primitive %s\n" id;
+      if
+        (not exists) && id <> "unsafe_expr"
+        (* see https://github.com/BuckleScript/bucklescript/issues/4532 *)
+      then
+        id
+        |> Name.create ~isInterface:false
+        |> addValueDeclaration ~path ~loc:vd.val_loc
+             ~moduleLoc:currentModulePath.loc ~sideEffects:false
+    | Tstr_type (_recFlag, typeDeclarations) when doTypes ->
+      if !Config.analyzeTypes then
+        typeDeclarations
+        |> List.iter (fun (typeDeclaration : Typedtree.type_declaration) ->
+               DeadType.addDeclaration ~typeId:typeDeclaration.typ_id
+                 ~typeKind:typeDeclaration.typ_type.type_kind)
+    | Tstr_include {incl_mod; incl_type} -> (
+      match incl_mod.mod_desc with
+      | Tmod_ident (_path, _lid) ->
+        let currentPath =
+          (ModulePath.getCurrent ()).path @ [!Common.currentModuleName]
+        in
+        incl_type
+        |> List.iter
+             (processSignatureItem ~doTypes
+                ~doValues:false (* TODO: also values? *)
+                ~moduleLoc:incl_mod.mod_loc ~path:currentPath)
+      | _ -> ())
+    | Tstr_exception {ext_id = id; ext_loc = loc} ->
+      let path =
+        (ModulePath.getCurrent ()).path @ [!Common.currentModuleName]
+      in
+      let name = id |> Ident.name |> Name.create in
+      name |> DeadException.add ~path ~loc ~strLoc:structureItem.str_loc
+    | _ -> ());
+    let result = super.structure_item self structureItem in
+    ModulePath.setCurrent oldModulePath;
+    result
+  in
+  {super with expr; pat; structure_item; value_binding}
+
+(* Merge a location's references to another one's *)
+let processValueDependency
+    ( ({
+         val_loc =
+           {loc_start = {pos_fname = fnTo} as posTo; loc_ghost = ghost1} as
+           locTo;
+       } :
+        Types.value_description),
+      ({
+         val_loc =
+           {loc_start = {pos_fname = fnFrom} as posFrom; loc_ghost = ghost2} as
+           locFrom;
+       } :
+        Types.value_description) ) =
+  if (not ghost1) && (not ghost2) && posTo <> posFrom then (
+    let addFileReference = fileIsImplementationOf fnTo fnFrom in
+    addValueReference ~addFileReference ~locFrom ~locTo;
+    DeadOptionalArgs.addFunctionReference ~locFrom ~locTo)
+
+let processStructure ~cmt_value_dependencies ~doTypes ~doExternals
+    (structure : Typedtree.structure) =
+  let traverseStructure = traverseStructure ~doTypes ~doExternals in
+  structure |> traverseStructure.structure traverseStructure |> ignore;
+  let valueDependencies = cmt_value_dependencies |> List.rev in
+  valueDependencies |> List.iter processValueDependency
diff --git a/analysis/reanalyze/src/EmitJson.ml b/analysis/reanalyze/src/EmitJson.ml
new file mode 100644
index 0000000000..c90e7f99cb
--- /dev/null
+++ b/analysis/reanalyze/src/EmitJson.ml
@@ -0,0 +1,27 @@
+let items = ref 0
+let start () = Printf.printf "["
+let finish () = Printf.printf "\n]\n"
+let emitClose () = "\n}"
+
+let emitItem ~ppf ~name ~kind ~file ~range ~message =
+  let open Format in
+  items := !items + 1;
+  let startLine, startCharacter, endLine, endCharacter = range in
+  fprintf ppf "%s{\n" (if !items = 1 then "\n" else ",\n");
+  fprintf ppf "  \"name\": \"%s\",\n" name;
+  fprintf ppf "  \"kind\": \"%s\",\n" kind;
+  fprintf ppf "  \"file\": \"%s\",\n" file;
+  fprintf ppf "  \"range\": [%d,%d,%d,%d],\n" startLine startCharacter endLine
+    endCharacter;
+  fprintf ppf "  \"message\": \"%s\"" message
+
+let locToPos (loc : Location.t) =
+  (loc.loc_start.pos_lnum - 1, loc.loc_start.pos_cnum - loc.loc_start.pos_bol)
+
+let emitAnnotate ~pos ~text ~action =
+  let line, character = pos in
+  Format.asprintf
+    ",\n\
+    \  \"annotate\": { \"line\": %d, \"character\": %d, \"text\": \"%s\", \
+     \"action\": \"%s\"}"
+    line character text action
diff --git a/analysis/reanalyze/src/Exception.ml b/analysis/reanalyze/src/Exception.ml
new file mode 100644
index 0000000000..95bade64a9
--- /dev/null
+++ b/analysis/reanalyze/src/Exception.ml
@@ -0,0 +1,490 @@
+let posToString = Common.posToString
+
+module LocSet = Common.LocSet
+
+module Values = struct
+  let valueBindingsTable =
+    (Hashtbl.create 15 : (string, (Name.t, Exceptions.t) Hashtbl.t) Hashtbl.t)
+
+  let currentFileTable = ref (Hashtbl.create 1)
+
+  let add ~name exceptions =
+    let path = (name |> Name.create) :: (ModulePath.getCurrent ()).path in
+    Hashtbl.replace !currentFileTable (path |> Common.Path.toName) exceptions
+
+  let getFromModule ~moduleName ~modulePath (path_ : Common.Path.t) =
+    let name = path_ @ modulePath |> Common.Path.toName in
+    match
+      Hashtbl.find_opt valueBindingsTable (String.capitalize_ascii moduleName)
+    with
+    | Some tbl -> Hashtbl.find_opt tbl name
+    | None -> (
+      match
+        Hashtbl.find_opt valueBindingsTable
+          (String.uncapitalize_ascii moduleName)
+      with
+      | Some tbl -> Hashtbl.find_opt tbl name
+      | None -> None)
+
+  let rec findLocal ~moduleName ~modulePath path =
+    match path |> getFromModule ~moduleName ~modulePath with
+    | Some exceptions -> Some exceptions
+    | None -> (
+      match modulePath with
+      | [] -> None
+      | _ :: restModulePath ->
+        path |> findLocal ~moduleName ~modulePath:restModulePath)
+
+  let findPath ~moduleName ~modulePath path =
+    let findExternal ~externalModuleName ~pathRev =
+      pathRev |> List.rev
+      |> getFromModule
+           ~moduleName:(externalModuleName |> Name.toString)
+           ~modulePath:[]
+    in
+    match path |> findLocal ~moduleName ~modulePath with
+    | None -> (
+      (* Search in another file *)
+      match path |> List.rev with
+      | externalModuleName :: pathRev -> (
+        match (findExternal ~externalModuleName ~pathRev, pathRev) with
+        | (Some _ as found), _ -> found
+        | None, externalModuleName2 :: pathRev2
+          when !Common.Cli.cmtCommand && pathRev2 <> [] ->
+          (* Simplistic namespace resolution for dune namespace: skip the root of the path *)
+          findExternal ~externalModuleName:externalModuleName2 ~pathRev:pathRev2
+        | None, _ -> None)
+      | [] -> None)
+    | Some exceptions -> Some exceptions
+
+  let newCmt () =
+    currentFileTable := Hashtbl.create 15;
+    Hashtbl.replace valueBindingsTable !Common.currentModule !currentFileTable
+end
+
+module Event = struct
+  type kind =
+    | Catches of t list (* with | E => ... *)
+    | Call of {callee: Common.Path.t; modulePath: Common.Path.t} (* foo() *)
+    | DoesNotRaise of
+        t list (* DoesNotRaise(events) where events come from an expression *)
+    | Raises  (** raise E *)
+
+  and t = {exceptions: Exceptions.t; kind: kind; loc: Location.t}
+
+  let rec print ppf event =
+    match event with
+    | {kind = Call {callee; modulePath}; exceptions; loc} ->
+      Format.fprintf ppf "%s Call(%s, modulePath:%s) %a@."
+        (loc.loc_start |> posToString)
+        (callee |> Common.Path.toString)
+        (modulePath |> Common.Path.toString)
+        (Exceptions.pp ~exnTable:None)
+        exceptions
+    | {kind = DoesNotRaise nestedEvents; loc} ->
+      Format.fprintf ppf "%s DoesNotRaise(%a)@."
+        (loc.loc_start |> posToString)
+        (fun ppf () ->
+          nestedEvents |> List.iter (fun e -> Format.fprintf ppf "%a " print e))
+        ()
+    | {kind = Raises; exceptions; loc} ->
+      Format.fprintf ppf "%s raises %a@."
+        (loc.loc_start |> posToString)
+        (Exceptions.pp ~exnTable:None)
+        exceptions
+    | {kind = Catches nestedEvents; exceptions; loc} ->
+      Format.fprintf ppf "%s Catches exceptions:%a nestedEvents:%a@."
+        (loc.loc_start |> posToString)
+        (Exceptions.pp ~exnTable:None)
+        exceptions
+        (fun ppf () ->
+          nestedEvents |> List.iter (fun e -> Format.fprintf ppf "%a " print e))
+        ()
+
+  let combine ~moduleName events =
+    if !Common.Cli.debug then (
+      Log_.item "@.";
+      Log_.item "Events combine: #events %d@." (events |> List.length));
+    let exnTable = Hashtbl.create 1 in
+    let extendExnTable exn loc =
+      match Hashtbl.find_opt exnTable exn with
+      | Some locSet -> Hashtbl.replace exnTable exn (LocSet.add loc locSet)
+      | None -> Hashtbl.replace exnTable exn (LocSet.add loc LocSet.empty)
+    in
+    let shrinkExnTable exn loc =
+      match Hashtbl.find_opt exnTable exn with
+      | Some locSet -> Hashtbl.replace exnTable exn (LocSet.remove loc locSet)
+      | None -> ()
+    in
+    let rec loop exnSet events =
+      match events with
+      | ({kind = Raises; exceptions; loc} as ev) :: rest ->
+        if !Common.Cli.debug then Log_.item "%a@." print ev;
+        exceptions |> Exceptions.iter (fun exn -> extendExnTable exn loc);
+        loop (Exceptions.union exnSet exceptions) rest
+      | ({kind = Call {callee; modulePath}; loc} as ev) :: rest ->
+        if !Common.Cli.debug then Log_.item "%a@." print ev;
+        let exceptions =
+          match callee |> Values.findPath ~moduleName ~modulePath with
+          | Some exceptions -> exceptions
+          | _ -> (
+            match ExnLib.find callee with
+            | Some exceptions -> exceptions
+            | None -> Exceptions.empty)
+        in
+        exceptions |> Exceptions.iter (fun exn -> extendExnTable exn loc);
+        loop (Exceptions.union exnSet exceptions) rest
+      | ({kind = DoesNotRaise nestedEvents; loc} as ev) :: rest ->
+        if !Common.Cli.debug then Log_.item "%a@." print ev;
+        let nestedExceptions = loop Exceptions.empty nestedEvents in
+        (if Exceptions.isEmpty nestedExceptions (* catch-all *) then
+           let name =
+             match nestedEvents with
+             | {kind = Call {callee}} :: _ -> callee |> Common.Path.toName
+             | _ -> "expression" |> Name.create
+           in
+           Log_.warning ~loc
+             (Common.ExceptionAnalysis
+                {
+                  message =
+                    Format.asprintf
+                      "@{<info>%s@} does not raise and is annotated with \
+                       redundant @doesNotRaise"
+                      (name |> Name.toString);
+                }));
+        loop exnSet rest
+      | ({kind = Catches nestedEvents; exceptions} as ev) :: rest ->
+        if !Common.Cli.debug then Log_.item "%a@." print ev;
+        if Exceptions.isEmpty exceptions then loop exnSet rest
+        else
+          let nestedExceptions = loop Exceptions.empty nestedEvents in
+          let newRaises = Exceptions.diff nestedExceptions exceptions in
+          exceptions
+          |> Exceptions.iter (fun exn ->
+                 nestedEvents
+                 |> List.iter (fun event -> shrinkExnTable exn event.loc));
+          loop (Exceptions.union exnSet newRaises) rest
+      | [] -> exnSet
+    in
+    let exnSet = loop Exceptions.empty events in
+    (exnSet, exnTable)
+end
+
+module Checks = struct
+  type check = {
+    events: Event.t list;
+    loc: Location.t;
+    locFull: Location.t;
+    moduleName: string;
+    exnName: string;
+    exceptions: Exceptions.t;
+  }
+
+  type t = check list
+
+  let checks = (ref [] : t ref)
+
+  let add ~events ~exceptions ~loc ?(locFull = loc) ~moduleName exnName =
+    checks := {events; exceptions; loc; locFull; moduleName; exnName} :: !checks
+
+  let doCheck {events; exceptions; loc; locFull; moduleName; exnName} =
+    let raiseSet, exnTable = events |> Event.combine ~moduleName in
+    let missingAnnotations = Exceptions.diff raiseSet exceptions in
+    let redundantAnnotations = Exceptions.diff exceptions raiseSet in
+    (if not (Exceptions.isEmpty missingAnnotations) then
+       let description =
+         Common.ExceptionAnalysisMissing
+           {exnName; exnTable; raiseSet; missingAnnotations; locFull}
+       in
+       Log_.warning ~loc description);
+    if not (Exceptions.isEmpty redundantAnnotations) then
+      Log_.warning ~loc
+        (Common.ExceptionAnalysis
+           {
+             message =
+               (let raisesDescription ppf () =
+                  if raiseSet |> Exceptions.isEmpty then
+                    Format.fprintf ppf "raises nothing"
+                  else
+                    Format.fprintf ppf "might raise %a"
+                      (Exceptions.pp ~exnTable:(Some exnTable))
+                      raiseSet
+                in
+                Format.asprintf
+                  "@{<info>%s@} %a and is annotated with redundant @raises(%a)"
+                  exnName raisesDescription ()
+                  (Exceptions.pp ~exnTable:None)
+                  redundantAnnotations);
+           })
+
+  let doChecks () = !checks |> List.rev |> List.iter doCheck
+end
+
+let traverseAst () =
+  ModulePath.init ();
+  let super = Tast_mapper.default in
+  let currentId = ref "" in
+  let currentEvents = ref [] in
+  let exceptionsOfPatterns patterns =
+    patterns
+    |> List.fold_left
+         (fun acc desc ->
+           match desc with
+           | Typedtree.Tpat_construct ({txt}, _, _) ->
+             Exceptions.add (Exn.fromLid txt) acc
+           | _ -> acc)
+         Exceptions.empty
+  in
+  let iterExpr self e = self.Tast_mapper.expr self e |> ignore in
+  let iterExprOpt self eo =
+    match eo with
+    | None -> ()
+    | Some e -> e |> iterExpr self
+  in
+  let iterPat self p = self.Tast_mapper.pat self p |> ignore in
+  let iterCases self cases =
+    cases
+    |> List.iter (fun case ->
+           case.Typedtree.c_lhs |> iterPat self;
+           case.c_guard |> iterExprOpt self;
+           case.c_rhs |> iterExpr self)
+  in
+  let isRaise s = s = "Pervasives.raise" || s = "Pervasives.raise_notrace" in
+  let raiseArgs args =
+    match args with
+    | [(_, Some {Typedtree.exp_desc = Texp_construct ({txt}, _, _)})] ->
+      [Exn.fromLid txt] |> Exceptions.fromList
+    | [(_, Some {Typedtree.exp_desc = Texp_ident _})] ->
+      [Exn.fromString "genericException"] |> Exceptions.fromList
+    | _ -> [Exn.fromString "TODO_from_raise1"] |> Exceptions.fromList
+  in
+  let doesNotRaise attributes =
+    attributes
+    |> Annotation.getAttributePayload (fun s ->
+           s = "doesNotRaise" || s = "doesnotraise" || s = "DoesNoRaise"
+           || s = "doesNotraise" || s = "doNotRaise" || s = "donotraise"
+           || s = "DoNoRaise" || s = "doNotraise")
+    <> None
+  in
+  let expr (self : Tast_mapper.mapper) (expr : Typedtree.expression) =
+    let loc = expr.exp_loc in
+    let isDoesNoRaise = expr.exp_attributes |> doesNotRaise in
+    let oldEvents = !currentEvents in
+    if isDoesNoRaise then currentEvents := [];
+    (match expr.exp_desc with
+    | Texp_ident (callee_, _, _) ->
+      let callee =
+        callee_ |> Common.Path.fromPathT |> ModulePath.resolveAlias
+      in
+      let calleeName = callee |> Common.Path.toName in
+      if calleeName |> Name.toString |> isRaise then
+        Log_.warning ~loc
+          (Common.ExceptionAnalysis
+             {
+               message =
+                 Format.asprintf
+                   "@{<info>%s@} can be analyzed only if called directly"
+                   (calleeName |> Name.toString);
+             });
+      currentEvents :=
+        {
+          Event.exceptions = Exceptions.empty;
+          loc;
+          kind = Call {callee; modulePath = (ModulePath.getCurrent ()).path};
+        }
+        :: !currentEvents
+    | Texp_apply
+        ( {exp_desc = Texp_ident (atat, _, _)},
+          [(_lbl1, Some {exp_desc = Texp_ident (callee, _, _)}); arg] )
+      when (* raise @@ Exn(...) *)
+           atat |> Path.name = "Pervasives.@@" && callee |> Path.name |> isRaise
+      ->
+      let exceptions = [arg] |> raiseArgs in
+      currentEvents := {Event.exceptions; loc; kind = Raises} :: !currentEvents;
+      arg |> snd |> iterExprOpt self
+    | Texp_apply
+        ( {exp_desc = Texp_ident (atat, _, _)},
+          [arg; (_lbl1, Some {exp_desc = Texp_ident (callee, _, _)})] )
+      when (*  Exn(...) |> raise *)
+           atat |> Path.name = "Pervasives.|>" && callee |> Path.name |> isRaise
+      ->
+      let exceptions = [arg] |> raiseArgs in
+      currentEvents := {Event.exceptions; loc; kind = Raises} :: !currentEvents;
+      arg |> snd |> iterExprOpt self
+    | Texp_apply (({exp_desc = Texp_ident (callee, _, _)} as e), args) ->
+      let calleeName = Path.name callee in
+      if calleeName |> isRaise then
+        let exceptions = args |> raiseArgs in
+        currentEvents :=
+          {Event.exceptions; loc; kind = Raises} :: !currentEvents
+      else e |> iterExpr self;
+      args |> List.iter (fun (_, eOpt) -> eOpt |> iterExprOpt self)
+    | Texp_match (e, casesOk, casesExn, partial) ->
+      let cases = casesOk @ casesExn in
+      let exceptionPatterns =
+        casesExn
+        |> List.map (fun (case : Typedtree.case) -> case.c_lhs.pat_desc)
+      in
+      let exceptions = exceptionPatterns |> exceptionsOfPatterns in
+      if exceptionPatterns <> [] then (
+        let oldEvents = !currentEvents in
+        currentEvents := [];
+        e |> iterExpr self;
+        currentEvents :=
+          {Event.exceptions; loc; kind = Catches !currentEvents} :: oldEvents)
+      else e |> iterExpr self;
+      cases |> iterCases self;
+      if partial = Partial then
+        currentEvents :=
+          {
+            Event.exceptions = [Exn.matchFailure] |> Exceptions.fromList;
+            loc;
+            kind = Raises;
+          }
+          :: !currentEvents
+    | Texp_try (e, cases) ->
+      let exceptions =
+        cases
+        |> List.map (fun case -> case.Typedtree.c_lhs.pat_desc)
+        |> exceptionsOfPatterns
+      in
+      let oldEvents = !currentEvents in
+      currentEvents := [];
+      e |> iterExpr self;
+      currentEvents :=
+        {Event.exceptions; loc; kind = Catches !currentEvents} :: oldEvents;
+      cases |> iterCases self
+    | _ -> super.expr self expr |> ignore);
+    (if isDoesNoRaise then
+       let nestedEvents = !currentEvents in
+       currentEvents :=
+         {
+           Event.exceptions = Exceptions.empty;
+           loc;
+           kind = DoesNotRaise nestedEvents;
+         }
+         :: oldEvents);
+    expr
+  in
+  let getExceptionsFromAnnotations attributes =
+    let raisesAnnotationPayload =
+      attributes
+      |> Annotation.getAttributePayload (fun s -> s = "raises" || s = "raise")
+    in
+    let rec getExceptions payload =
+      match payload with
+      | Annotation.StringPayload s -> [Exn.fromString s] |> Exceptions.fromList
+      | Annotation.ConstructPayload s when s <> "::" ->
+        [Exn.fromString s] |> Exceptions.fromList
+      | Annotation.IdentPayload s ->
+        [Exn.fromString (s |> Longident.flatten |> String.concat ".")]
+        |> Exceptions.fromList
+      | Annotation.TuplePayload tuple ->
+        tuple
+        |> List.map (fun payload ->
+               payload |> getExceptions |> Exceptions.toList)
+        |> List.concat |> Exceptions.fromList
+      | _ -> Exceptions.empty
+    in
+    match raisesAnnotationPayload with
+    | None -> Exceptions.empty
+    | Some payload -> payload |> getExceptions
+  in
+  let toplevelEval (self : Tast_mapper.mapper) (expr : Typedtree.expression)
+      attributes =
+    let oldId = !currentId in
+    let oldEvents = !currentEvents in
+    let name = "Toplevel expression" in
+    currentId := name;
+    currentEvents := [];
+    let moduleName = !Common.currentModule in
+    self.expr self expr |> ignore;
+    Checks.add ~events:!currentEvents
+      ~exceptions:(getExceptionsFromAnnotations attributes)
+      ~loc:expr.exp_loc ~moduleName name;
+    currentId := oldId;
+    currentEvents := oldEvents
+  in
+  let structure_item (self : Tast_mapper.mapper)
+      (structureItem : Typedtree.structure_item) =
+    let oldModulePath = ModulePath.getCurrent () in
+    (match structureItem.str_desc with
+    | Tstr_eval (expr, attributes) -> toplevelEval self expr attributes
+    | Tstr_module {mb_id; mb_loc} ->
+      ModulePath.setCurrent
+        {
+          oldModulePath with
+          loc = mb_loc;
+          path = (mb_id |> Ident.name |> Name.create) :: oldModulePath.path;
+        }
+    | _ -> ());
+    let result = super.structure_item self structureItem in
+    ModulePath.setCurrent oldModulePath;
+    (match structureItem.str_desc with
+    | Tstr_module {mb_id; mb_expr = {mod_desc = Tmod_ident (path_, _lid)}} ->
+      ModulePath.addAlias
+        ~name:(mb_id |> Ident.name |> Name.create)
+        ~path:(path_ |> Common.Path.fromPathT)
+    | _ -> ());
+    result
+  in
+  let value_binding (self : Tast_mapper.mapper) (vb : Typedtree.value_binding) =
+    let oldId = !currentId in
+    let oldEvents = !currentEvents in
+    let isFunction =
+      match vb.vb_expr.exp_desc with
+      | Texp_function _ -> true
+      | _ -> false
+    in
+    let isToplevel = !currentId = "" in
+    let processBinding name =
+      currentId := name;
+      currentEvents := [];
+      let exceptionsFromAnnotations =
+        getExceptionsFromAnnotations vb.vb_attributes
+      in
+      exceptionsFromAnnotations |> Values.add ~name;
+      let res = super.value_binding self vb in
+      let moduleName = !Common.currentModule in
+      let path = [name |> Name.create] in
+      let exceptions =
+        match
+          path
+          |> Values.findPath ~moduleName
+               ~modulePath:(ModulePath.getCurrent ()).path
+        with
+        | Some exceptions -> exceptions
+        | _ -> Exceptions.empty
+      in
+      Checks.add ~events:!currentEvents ~exceptions ~loc:vb.vb_pat.pat_loc
+        ~locFull:vb.vb_loc ~moduleName name;
+      currentId := oldId;
+      currentEvents := oldEvents;
+      res
+    in
+    match vb.vb_pat.pat_desc with
+    | Tpat_any when isToplevel && not vb.vb_loc.loc_ghost -> processBinding "_"
+    | Tpat_construct ({txt}, _, _)
+      when isToplevel && (not vb.vb_loc.loc_ghost)
+           && txt = Longident.Lident "()" ->
+      processBinding "()"
+    | Tpat_var (id, {loc = {loc_ghost}})
+      when (isFunction || isToplevel) && (not loc_ghost)
+           && not vb.vb_loc.loc_ghost ->
+      processBinding (id |> Ident.name)
+    | _ -> super.value_binding self vb
+  in
+  let open Tast_mapper in
+  {super with expr; value_binding; structure_item}
+
+let processStructure (structure : Typedtree.structure) =
+  let traverseAst = traverseAst () in
+  structure |> traverseAst.structure traverseAst |> ignore
+
+let processCmt (cmt_infos : Cmt_format.cmt_infos) =
+  match cmt_infos.cmt_annots with
+  | Interface _ -> ()
+  | Implementation structure ->
+    Values.newCmt ();
+    structure |> processStructure
+  | _ -> ()
diff --git a/analysis/reanalyze/src/Exceptions.ml b/analysis/reanalyze/src/Exceptions.ml
new file mode 100644
index 0000000000..06d4d5c187
--- /dev/null
+++ b/analysis/reanalyze/src/Exceptions.ml
@@ -0,0 +1,36 @@
+open Common
+
+type t = ExnSet.t
+
+let add = ExnSet.add
+let diff = ExnSet.diff
+let empty = ExnSet.empty
+let fromList = ExnSet.of_list
+let toList = ExnSet.elements
+let isEmpty = ExnSet.is_empty
+let iter = ExnSet.iter
+let union = ExnSet.union
+
+let pp ~exnTable ppf exceptions =
+  let isFirst = ref true in
+  let ppExn exn =
+    let separator = if !isFirst then "" else ", " in
+    isFirst := false;
+    let name = Exn.toString exn in
+    match exnTable with
+    | Some exnTable -> (
+      match Hashtbl.find_opt exnTable exn with
+      | Some locSet ->
+        let positions =
+          locSet |> Common.LocSet.elements
+          |> List.map (fun loc -> loc.Location.loc_start)
+        in
+        Format.fprintf ppf "%s@{<info>%s@} (@{<filename>%s@})" separator name
+          (positions |> List.map posToString |> String.concat " ")
+      | None -> Format.fprintf ppf "%s@{<info>%s@}" separator name)
+    | None -> Format.fprintf ppf "%s@{<info>%s@}" separator name
+  in
+  let isList = exceptions |> ExnSet.cardinal > 1 in
+  if isList then Format.fprintf ppf "[";
+  exceptions |> ExnSet.iter ppExn;
+  if isList then Format.fprintf ppf "]"
diff --git a/analysis/reanalyze/src/Exn.ml b/analysis/reanalyze/src/Exn.ml
new file mode 100644
index 0000000000..65f055d35d
--- /dev/null
+++ b/analysis/reanalyze/src/Exn.ml
@@ -0,0 +1,19 @@
+type t = string
+
+let compare = String.compare
+let decodeError = "DecodeError"
+let assertFailure = "Assert_failure"
+let divisionByZero = "Division_by_zero"
+let endOfFile = "End_of_file"
+let exit = "exit"
+let failure = "Failure"
+let invalidArgument = "Invalid_argument"
+let jsExnError = "Js.Exn.Error"
+let matchFailure = "Match_failure"
+let notFound = "Not_found"
+let sysError = "Sys_error"
+let fromLid lid = lid |> Longident.flatten |> String.concat "."
+let fromString s = s
+let toString s = s
+let yojsonJsonError = "Yojson.Json_error"
+let yojsonTypeError = "Yojson.Basic.Util.Type_error"
diff --git a/analysis/reanalyze/src/Exn.mli b/analysis/reanalyze/src/Exn.mli
new file mode 100644
index 0000000000..f591177819
--- /dev/null
+++ b/analysis/reanalyze/src/Exn.mli
@@ -0,0 +1,19 @@
+type t
+
+val compare : t -> t -> int
+val assertFailure : t
+val decodeError : t
+val divisionByZero : t
+val endOfFile : t
+val exit : t
+val failure : t
+val fromLid : Longident.t -> t
+val fromString : string -> t
+val invalidArgument : t
+val jsExnError : t
+val matchFailure : t
+val notFound : t
+val sysError : t
+val toString : t -> string
+val yojsonJsonError : t
+val yojsonTypeError : t
diff --git a/analysis/reanalyze/src/ExnLib.ml b/analysis/reanalyze/src/ExnLib.ml
new file mode 100644
index 0000000000..d0ddc5721c
--- /dev/null
+++ b/analysis/reanalyze/src/ExnLib.ml
@@ -0,0 +1,262 @@
+let raisesLibTable : (Name.t, Exceptions.t) Hashtbl.t =
+  let table = Hashtbl.create 15 in
+  let open Exn in
+  let array =
+    [
+      ("get", [invalidArgument]);
+      ("set", [invalidArgument]);
+      ("make", [invalidArgument]);
+      ("init", [invalidArgument]);
+      ("make_matrix", [invalidArgument]);
+      ("fill", [invalidArgument]);
+      ("blit", [invalidArgument]);
+      ("iter2", [invalidArgument]);
+      ("map2", [invalidArgument]);
+    ]
+  in
+  let beltArray = [("getExn", [assertFailure]); ("setExn", [assertFailure])] in
+  let beltList =
+    [("getExn", [notFound]); ("headExn", [notFound]); ("tailExn", [notFound])]
+  in
+  let beltMap = [("getExn", [notFound])] in
+  let beltMutableMap = beltMap in
+  let beltMutableQueue = [("peekExn", [notFound]); ("popExn", [notFound])] in
+  let beltMutableSet = [("getExn", [notFound])] in
+  let beltOption = [("getExn", [notFound])] in
+  let beltResult = [("getExn", [notFound])] in
+  let beltSet = [("getExn", [notFound])] in
+  let bsJson =
+    (* bs-json *)
+    [
+      ("bool", [decodeError]);
+      ("float", [decodeError]);
+      ("int", [decodeError]);
+      ("string", [decodeError]);
+      ("char", [decodeError]);
+      ("date", [decodeError]);
+      ("nullable", [decodeError]);
+      ("nullAs", [decodeError]);
+      ("array", [decodeError]);
+      ("list", [decodeError]);
+      ("pair", [decodeError]);
+      ("tuple2", [decodeError]);
+      ("tuple3", [decodeError]);
+      ("tuple4", [decodeError]);
+      ("dict", [decodeError]);
+      ("field", [decodeError]);
+      ("at", [decodeError; invalidArgument]);
+      ("oneOf", [decodeError]);
+      ("either", [decodeError]);
+    ]
+  in
+  let buffer =
+    [
+      ("sub", [invalidArgument]);
+      ("blit", [invalidArgument]);
+      ("nth", [invalidArgument]);
+      ("add_substitute", [notFound]);
+      ("add_channel", [endOfFile]);
+      ("truncate", [invalidArgument]);
+    ]
+  in
+  let bytes =
+    [
+      ("get", [invalidArgument]);
+      ("set", [invalidArgument]);
+      ("create", [invalidArgument]);
+      ("make", [invalidArgument]);
+      ("init", [invalidArgument]);
+      ("sub", [invalidArgument]);
+      ("sub_string", [invalidArgument]);
+      ("extend", [invalidArgument]);
+      ("fill", [invalidArgument]);
+      ("blit", [invalidArgument]);
+      ("blit_string", [invalidArgument]);
+      (* ("concat", [invalidArgument]), if longer than {!Sys.max_string_length}
+         ("cat", [invalidArgument]), if longer than {!Sys.max_string_length}
+         ("escaped", [invalidArgument]), if longer than {!Sys.max_string_length} *)
+      ("index", [notFound]);
+      ("rindex", [notFound]);
+      ("index_from", [invalidArgument; notFound]);
+      ("index_from_opt", [invalidArgument]);
+      ("rindex_from", [invalidArgument; notFound]);
+      ("rindex_from_opt", [invalidArgument]);
+      ("contains_from", [invalidArgument]);
+      ("rcontains_from", [invalidArgument]);
+    ]
+  in
+  let filename =
+    [
+      ("chop_extension", [invalidArgument]);
+      ("temp_file", [sysError]);
+      ("open_temp_file", [sysError]);
+    ]
+  in
+  let hashtbl = [("find", [notFound])] in
+  let list =
+    [
+      ("hd", [failure]);
+      ("tl", [failure]);
+      ("nth", [failure; invalidArgument]);
+      ("nth_opt", [invalidArgument]);
+      ("init", [invalidArgument]);
+      ("iter2", [invalidArgument]);
+      ("map2", [invalidArgument]);
+      ("fold_left2", [invalidArgument]);
+      ("fold_right2", [invalidArgument]);
+      ("for_all2", [invalidArgument]);
+      ("exists2", [invalidArgument]);
+      ("find", [notFound]);
+      ("assoc", [notFound]);
+      ("combine", [invalidArgument]);
+    ]
+  in
+  let string =
+    [
+      ("get", [invalidArgument]);
+      ("set", [invalidArgument]);
+      ("create", [invalidArgument]);
+      ("make", [invalidArgument]);
+      ("init", [invalidArgument]);
+      ("sub", [invalidArgument]);
+      ("fill", [invalidArgument]);
+      (* ("concat", [invalidArgument]), if longer than {!Sys.max_string_length}
+         ("escaped", [invalidArgument]), if longer than {!Sys.max_string_length} *)
+      ("index", [notFound]);
+      ("rindex", [notFound]);
+      ("index_from", [invalidArgument; notFound]);
+      ("index_from_opt", [invalidArgument]);
+      ("rindex_from", [invalidArgument; notFound]);
+      ("rindex_from_opt", [invalidArgument]);
+      ("contains_from", [invalidArgument]);
+      ("rcontains_from", [invalidArgument]);
+    ]
+  in
+  let stdlib =
+    [
+      ("invalid_arg", [invalidArgument]);
+      ("failwith", [failure]);
+      ("/", [divisionByZero]);
+      ("mod", [divisionByZero]);
+      ("char_of_int", [invalidArgument]);
+      ("bool_of_string", [invalidArgument]);
+      ("int_of_string", [failure]);
+      ("float_of_string", [failure]);
+      ("read_int", [failure]);
+      ("output", [invalidArgument]);
+      ("close_out", [sysError]);
+      ("input_char", [endOfFile]);
+      ("input_line", [endOfFile]);
+      ("input", [invalidArgument]);
+      ("really_input", [endOfFile; invalidArgument]);
+      ("really_input_string", [endOfFile]);
+      ("input_byte", [endOfFile]);
+      ("input_binary_int", [endOfFile]);
+      ("close_in", [sysError]);
+      ("exit", [exit]);
+    ]
+  in
+  let str =
+    [
+      ("search_forward", [notFound]);
+      ("search_backward", [notFound]);
+      ("matched_group", [notFound]);
+      ("group_beginning", [notFound; invalidArgument]);
+      ("group_end", [notFound; invalidArgument]);
+    ]
+  in
+  let yojsonBasic = [("from_string", [yojsonJsonError])] in
+  let yojsonBasicUtil =
+    [
+      ("member", [yojsonTypeError]);
+      ("to_assoc", [yojsonTypeError]);
+      ("to_bool", [yojsonTypeError]);
+      ("to_bool_option", [yojsonTypeError]);
+      ("to_float", [yojsonTypeError]);
+      ("to_float_option", [yojsonTypeError]);
+      ("to_int", [yojsonTypeError]);
+      ("to_list", [yojsonTypeError]);
+      ("to_number", [yojsonTypeError]);
+      ("to_number_option", [yojsonTypeError]);
+      ("to_string", [yojsonTypeError]);
+      ("to_string_option", [yojsonTypeError]);
+    ]
+  in
+  [
+    ("Array", array);
+    ("Belt.Array", beltArray);
+    ("Belt_Array", beltArray);
+    ("Belt.List", beltList);
+    ("Belt_List", beltList);
+    ("Belt.Map", beltMap);
+    ("Belt.Map.Int", beltMap);
+    ("Belt.Map.String", beltMap);
+    ("Belt_Map", beltMap);
+    ("Belt_Map.Int", beltMap);
+    ("Belt_Map.String", beltMap);
+    ("Belt_MapInt", beltMap);
+    ("Belt_MapString", beltMap);
+    ("Belt.MutableMap", beltMutableMap);
+    ("Belt.MutableMap.Int", beltMutableMap);
+    ("Belt.MutableMap.String", beltMutableMap);
+    ("Belt_MutableMap", beltMutableMap);
+    ("Belt_MutableMap.Int", beltMutableMap);
+    ("Belt_MutableMap.String", beltMutableMap);
+    ("Belt_MutableMapInt", beltMutableMap);
+    ("Belt_MutableMapString", beltMutableMap);
+    ("Belt.MutableQueue", beltMutableQueue);
+    ("Belt_MutableQueue", beltMutableQueue);
+    ("Belt.Option", beltOption);
+    ("Belt_Option", beltOption);
+    ("Belt.Result", beltResult);
+    ("Belt_Result", beltResult);
+    ("Belt.Set", beltSet);
+    ("Belt.Set.Int", beltSet);
+    ("Belt.Set.String", beltSet);
+    ("Belt_Set", beltSet);
+    ("Belt_Set.Int", beltSet);
+    ("Belt_Set.String", beltSet);
+    ("Belt_SetInt", beltSet);
+    ("Belt_SetString", beltSet);
+    ("Belt.MutableSet", beltMutableSet);
+    ("Belt.MutableSet.Int", beltMutableSet);
+    ("Belt.MutableSet.String", beltMutableSet);
+    ("MutableSet", beltMutableSet);
+    ("MutableSet.Int", beltMutableSet);
+    ("MutableSet.String", beltMutableSet);
+    ("Belt_MutableSetInt", beltMutableSet);
+    ("Belt_MutableSetString", beltMutableSet);
+    ("Buffer", buffer);
+    ("Bytes", bytes);
+    ("Char", [("chr", [invalidArgument])]);
+    ("Filename", filename);
+    ("Hashtbl", hashtbl);
+    ("Js.Json", [("parseExn", [jsExnError])]);
+    ("Json_decode", bsJson);
+    ("Json.Decode", bsJson);
+    ("List", list);
+    ("Pervasives", stdlib);
+    ("Stdlib", stdlib);
+    ("Stdlib.Array", array);
+    ("Stdlib.Buffer", buffer);
+    ("Stdlib.Bytes", bytes);
+    ("Stdlib.Filename", filename);
+    ("Stdlib.Hashtbl", hashtbl);
+    ("Stdlib.List", list);
+    ("Stdlib.Str", str);
+    ("Stdlib.String", string);
+    ("Str", str);
+    ("String", string);
+    ("Yojson.Basic", yojsonBasic);
+    ("Yojson.Basic.Util", yojsonBasicUtil);
+  ]
+  |> List.iter (fun (name, group) ->
+         group
+         |> List.iter (fun (s, e) ->
+                Hashtbl.add table
+                  (name ^ "." ^ s |> Name.create)
+                  (e |> Exceptions.fromList)));
+  table
+
+let find (path : Common.Path.t) =
+  Hashtbl.find_opt raisesLibTable (path |> Common.Path.toName)
diff --git a/analysis/reanalyze/src/FindSourceFile.ml b/analysis/reanalyze/src/FindSourceFile.ml
new file mode 100644
index 0000000000..3c92f8120c
--- /dev/null
+++ b/analysis/reanalyze/src/FindSourceFile.ml
@@ -0,0 +1,27 @@
+let rec interface items =
+  match items with
+  | {Typedtree.sig_loc} :: rest -> (
+    match not (Sys.file_exists sig_loc.loc_start.pos_fname) with
+    | true -> interface rest
+    | false -> Some sig_loc.loc_start.pos_fname)
+  | [] -> None
+
+let rec implementation items =
+  match items with
+  | {Typedtree.str_loc} :: rest -> (
+    match not (Sys.file_exists str_loc.loc_start.pos_fname) with
+    | true -> implementation rest
+    | false -> Some str_loc.loc_start.pos_fname)
+  | [] -> None
+
+let cmt cmt_annots =
+  match cmt_annots with
+  | Cmt_format.Interface signature ->
+    if !Common.Cli.debug && signature.sig_items = [] then
+      Log_.item "Interface %d@." (signature.sig_items |> List.length);
+    interface signature.sig_items
+  | Implementation structure ->
+    if !Common.Cli.debug && structure.str_items = [] then
+      Log_.item "Implementation %d@." (structure.str_items |> List.length);
+    implementation structure.str_items
+  | _ -> None
diff --git a/analysis/reanalyze/src/Issues.ml b/analysis/reanalyze/src/Issues.ml
new file mode 100644
index 0000000000..d1c1a3ca3f
--- /dev/null
+++ b/analysis/reanalyze/src/Issues.ml
@@ -0,0 +1,14 @@
+let errorHygiene = "Error Hygiene"
+let errorNotImplemented = "Error Not Implemented"
+let errorTermination = "Error Termination"
+let exceptionAnalysis = "Exception Analysis"
+let incorrectDeadAnnotation = "Incorrect Dead Annotation"
+let terminationAnalysisInternal = "Termination Analysis Internal"
+let warningDeadAnalysisCycle = "Warning Dead Analysis Cycle"
+let warningDeadException = "Warning Dead Exception"
+let warningDeadModule = "Warning Dead Module"
+let warningDeadType = "Warning Dead Type"
+let warningDeadValue = "Warning Dead Value"
+let warningDeadValueWithSideEffects = "Warning Dead Value With Side Effects"
+let warningRedundantOptionalArgument = "Warning Redundant Optional Argument"
+let warningUnusedArgument = "Warning Unused Argument"
diff --git a/analysis/reanalyze/src/Log_.ml b/analysis/reanalyze/src/Log_.ml
new file mode 100644
index 0000000000..2bb6aa0e9e
--- /dev/null
+++ b/analysis/reanalyze/src/Log_.ml
@@ -0,0 +1,254 @@
+open Common
+
+module Color = struct
+  let color_enabled = lazy (Unix.isatty Unix.stdout)
+  let forceColor = ref false
+  let get_color_enabled () = !forceColor || Lazy.force color_enabled
+
+  type color = Red | Yellow | Magenta | Cyan
+  type style = FG of color | Bold | Dim
+
+  let code_of_style = function
+    | FG Red -> "31"
+    | FG Yellow -> "33"
+    | FG Magenta -> "35"
+    | FG Cyan -> "36"
+    | Bold -> "1"
+    | Dim -> "2"
+
+  let getStringTag s =
+    match s with
+    | Format.String_tag s -> s
+    | _ -> ""
+
+  let style_of_tag s =
+    match s |> getStringTag with
+    | "error" -> [Bold; FG Red]
+    | "warning" -> [Bold; FG Magenta]
+    | "info" -> [Bold; FG Yellow]
+    | "dim" -> [Dim]
+    | "filename" -> [FG Cyan]
+    | _ -> []
+
+  let ansi_of_tag s =
+    let l = style_of_tag s in
+    let s = String.concat ";" (List.map code_of_style l) in
+    "\027[" ^ s ^ "m"
+
+  let reset_lit = "\027[0m"
+
+  let setOpenCloseTag openTag closeTag =
+    {
+      Format.mark_open_stag = openTag;
+      mark_close_stag = closeTag;
+      print_open_stag = (fun _ -> ());
+      print_close_stag = (fun _ -> ());
+    }
+
+  let color_functions =
+    setOpenCloseTag
+      (fun s -> if get_color_enabled () then ansi_of_tag s else "")
+      (fun _ -> if get_color_enabled () then reset_lit else "")
+
+  let setup () =
+    Format.pp_set_mark_tags Format.std_formatter true;
+    Format.pp_set_formatter_stag_functions Format.std_formatter color_functions;
+    if not (get_color_enabled ()) then Misc.Color.setup (Some Never);
+    (* Print a dummy thing once in the beginning, as otherwise flushing does not work. *)
+    Location.print_loc Format.str_formatter Location.none
+
+  let error ppf s = Format.fprintf ppf "@{<error>%s@}" s
+  let info ppf s = Format.fprintf ppf "@{<info>%s@}" s
+end
+
+module Loc = struct
+  let print_loc ppf (loc : Location.t) =
+    (* Change the range so it's on a single line.
+       In this way, the line number is clickable in vscode. *)
+    let startChar = loc.loc_start.pos_cnum - loc.loc_start.pos_bol in
+    let endChar = startChar + loc.loc_end.pos_cnum - loc.loc_start.pos_cnum in
+    let line = loc.loc_start.pos_lnum in
+    let processPos char (pos : Lexing.position) : Lexing.position =
+      {
+        pos_lnum = line;
+        pos_bol = 0;
+        pos_cnum = char;
+        pos_fname =
+          (let open Filename in
+           match is_implicit pos.pos_fname with
+           | _ when !Cli.ci -> basename pos.pos_fname
+           | true -> concat (Sys.getcwd ()) pos.pos_fname
+           | false -> pos.pos_fname);
+      }
+    in
+    Location.print_loc ppf
+      {
+        loc with
+        loc_start = loc.loc_start |> processPos startChar;
+        loc_end = loc.loc_end |> processPos endChar;
+      }
+
+  let print ppf (loc : Location.t) = Format.fprintf ppf "@[%a@]" print_loc loc
+end
+
+let log x = Format.fprintf Format.std_formatter x
+
+let item x =
+  Format.fprintf Format.std_formatter "  ";
+  Format.fprintf Format.std_formatter x
+
+let missingRaiseInfoToText {missingAnnotations; locFull} =
+  let missingTxt =
+    Format.asprintf "%a" (Exceptions.pp ~exnTable:None) missingAnnotations
+  in
+  if !Cli.json then
+    EmitJson.emitAnnotate ~action:"Add @raises annotation"
+      ~pos:(EmitJson.locToPos locFull)
+      ~text:(Format.asprintf "@raises(%s)\\n" missingTxt)
+  else ""
+
+let logAdditionalInfo ~(description : description) =
+  match description with
+  | DeadWarning {lineAnnotation; shouldWriteLineAnnotation} ->
+    if shouldWriteLineAnnotation then
+      WriteDeadAnnotations.lineAnnotationToString lineAnnotation
+    else ""
+  | ExceptionAnalysisMissing missingRaiseInfo ->
+    missingRaiseInfoToText missingRaiseInfo
+  | _ -> ""
+
+let missingRaiseInfoToMessage {exnTable; exnName; missingAnnotations; raiseSet}
+    =
+  let raisesTxt =
+    Format.asprintf "%a" (Exceptions.pp ~exnTable:(Some exnTable)) raiseSet
+  in
+  let missingTxt =
+    Format.asprintf "%a" (Exceptions.pp ~exnTable:None) missingAnnotations
+  in
+  Format.asprintf
+    "@{<info>%s@} might raise %s and is not annotated with @raises(%s)" exnName
+    raisesTxt missingTxt
+
+let descriptionToMessage (description : description) =
+  match description with
+  | Circular {message} -> message
+  | DeadModule {message} -> message
+  | DeadOptional {message} -> message
+  | DeadWarning {path; message} ->
+    Format.asprintf "@{<info>%s@} %s" path message
+  | ExceptionAnalysis {message} -> message
+  | ExceptionAnalysisMissing missingRaiseInfo ->
+    missingRaiseInfoToMessage missingRaiseInfo
+  | Termination {message} -> message
+
+let descriptionToName (description : description) =
+  match description with
+  | Circular _ -> Issues.warningDeadAnalysisCycle
+  | DeadModule _ -> Issues.warningDeadModule
+  | DeadOptional {deadOptional = WarningUnusedArgument} ->
+    Issues.warningUnusedArgument
+  | DeadOptional {deadOptional = WarningRedundantOptionalArgument} ->
+    Issues.warningRedundantOptionalArgument
+  | DeadWarning {deadWarning = WarningDeadException} ->
+    Issues.warningDeadException
+  | DeadWarning {deadWarning = WarningDeadType} -> Issues.warningDeadType
+  | DeadWarning {deadWarning = WarningDeadValue} -> Issues.warningDeadValue
+  | DeadWarning {deadWarning = WarningDeadValueWithSideEffects} ->
+    Issues.warningDeadValueWithSideEffects
+  | DeadWarning {deadWarning = IncorrectDeadAnnotation} ->
+    Issues.incorrectDeadAnnotation
+  | ExceptionAnalysis _ -> Issues.exceptionAnalysis
+  | ExceptionAnalysisMissing _ -> Issues.exceptionAnalysis
+  | Termination {termination = ErrorHygiene} -> Issues.errorHygiene
+  | Termination {termination = ErrorNotImplemented} ->
+    Issues.errorNotImplemented
+  | Termination {termination = ErrorTermination} -> Issues.errorTermination
+  | Termination {termination = TerminationAnalysisInternal} ->
+    Issues.terminationAnalysisInternal
+
+let logIssue ~(issue : issue) =
+  let open Format in
+  let loc = issue.loc in
+  if !Cli.json then
+    let file = Json.escape loc.loc_start.pos_fname in
+    let startLine = loc.loc_start.pos_lnum - 1 in
+    let startCharacter = loc.loc_start.pos_cnum - loc.loc_start.pos_bol in
+    let endLine = loc.loc_end.pos_lnum - 1 in
+    let endCharacter = loc.loc_end.pos_cnum - loc.loc_start.pos_bol in
+    let message = Json.escape (descriptionToMessage issue.description) in
+    Format.asprintf "%a%s%s"
+      (fun ppf () ->
+        EmitJson.emitItem ~ppf ~name:issue.name
+          ~kind:
+            (match issue.severity with
+            | Warning -> "warning"
+            | Error -> "error")
+          ~file
+          ~range:(startLine, startCharacter, endLine, endCharacter)
+          ~message)
+      ()
+      (logAdditionalInfo ~description:issue.description)
+      (if !Cli.json then EmitJson.emitClose () else "")
+  else
+    let color =
+      match issue.severity with
+      | Warning -> Color.info
+      | Error -> Color.error
+    in
+    asprintf "@.  %a@.  %a@.  %s%s@." color issue.name Loc.print issue.loc
+      (descriptionToMessage issue.description)
+      (logAdditionalInfo ~description:issue.description)
+
+module Stats = struct
+  let issues = ref []
+  let addIssue (issue : issue) = issues := issue :: !issues
+  let clear () = issues := []
+
+  let getSortedIssues () =
+    let counters2 = Hashtbl.create 1 in
+    !issues
+    |> List.iter (fun (issue : issue) ->
+           let counter =
+             match Hashtbl.find_opt counters2 issue.name with
+             | Some counter -> counter
+             | None ->
+               let counter = ref 0 in
+               Hashtbl.add counters2 issue.name counter;
+               counter
+           in
+           incr counter);
+    let issues, nIssues =
+      Hashtbl.fold
+        (fun name cnt (issues, nIssues) ->
+          ((name, cnt) :: issues, nIssues + !cnt))
+        counters2 ([], 0)
+    in
+    (issues |> List.sort (fun (n1, _) (n2, _) -> String.compare n1 n2), nIssues)
+
+  let report () =
+    !issues |> List.rev
+    |> List.iter (fun issue -> logIssue ~issue |> print_string);
+    let sortedIssues, nIssues = getSortedIssues () in
+    if not !Cli.json then (
+      if sortedIssues <> [] then item "@.";
+      item "Analysis reported %d issues%s@." nIssues
+        (match sortedIssues with
+        | [] -> ""
+        | _ :: _ ->
+          " ("
+          ^ (sortedIssues
+            |> List.map (fun (name, cnt) -> name ^ ":" ^ string_of_int !cnt)
+            |> String.concat ", ")
+          ^ ")"))
+end
+
+let logIssue ~forStats ~severity ~(loc : Location.t) description =
+  let name = descriptionToName description in
+  if Suppress.filter loc.loc_start then
+    if forStats then Stats.addIssue {name; severity; loc; description}
+
+let warning ?(forStats = true) ~loc description =
+  description |> logIssue ~severity:Warning ~forStats ~loc
+
+let error ~loc description =
+  description |> logIssue ~severity:Error ~forStats:true ~loc
diff --git a/analysis/reanalyze/src/ModulePath.ml b/analysis/reanalyze/src/ModulePath.ml
new file mode 100644
index 0000000000..1955da1810
--- /dev/null
+++ b/analysis/reanalyze/src/ModulePath.ml
@@ -0,0 +1,35 @@
+open Common
+module NameMap = Map.Make (Name)
+
+(* Keep track of the module path while traversing with Tast_mapper *)
+type t = {aliases: Path.t NameMap.t; loc: Location.t; path: Path.t}
+
+let initial = ({aliases = NameMap.empty; loc = Location.none; path = []} : t)
+let current = (ref initial : t ref)
+let init () = current := initial
+
+let normalizePath ~aliases path =
+  match path |> List.rev with
+  | name :: restRev when restRev <> [] -> (
+    match aliases |> NameMap.find_opt name with
+    | None -> path
+    | Some path1 ->
+      let newPath = List.rev (path1 @ restRev) in
+      if !Common.Cli.debug then
+        Log_.item "Resolve Alias: %s to %s@."
+          (path |> Common.Path.toString)
+          (newPath |> Common.Path.toString);
+      newPath)
+  | _ -> path
+
+let addAlias ~name ~path =
+  let aliases = !current.aliases in
+  let pathNormalized = path |> normalizePath ~aliases in
+  if !Common.Cli.debug then
+    Log_.item "Module Alias: %s = %s@." (name |> Name.toString)
+      (Path.toString pathNormalized);
+  current := {!current with aliases = NameMap.add name pathNormalized aliases}
+
+let resolveAlias path = path |> normalizePath ~aliases:!current.aliases
+let getCurrent () = !current
+let setCurrent p = current := p
diff --git a/analysis/reanalyze/src/Name.ml b/analysis/reanalyze/src/Name.ml
new file mode 100644
index 0000000000..2f9565410d
--- /dev/null
+++ b/analysis/reanalyze/src/Name.ml
@@ -0,0 +1,29 @@
+type t = string
+
+let compare = String.compare
+
+let create ?(isInterface = true) s =
+  match isInterface with
+  | true -> s
+  | false -> "+" ^ s
+
+let isInterface s = try s.[0] <> '+' with Invalid_argument _ -> false
+let isUnderscore s = s = "_" || s = "+_"
+
+let startsWithUnderscore s =
+  s |> String.length >= 2
+  &&
+  try s.[0] = '_' || (s.[0] = '+' && s.[1] = '_')
+  with Invalid_argument _ -> false
+
+let toInterface s =
+  match isInterface s with
+  | true -> s
+  | false -> (
+    try String.sub s 1 (String.length s - 1) with Invalid_argument _ -> s)
+
+let toImplementation s =
+  match isInterface s with
+  | true -> "+" ^ s
+  | false -> s
+let toString (s : t) = s
diff --git a/analysis/reanalyze/src/Name.mli b/analysis/reanalyze/src/Name.mli
new file mode 100644
index 0000000000..3f515e3065
--- /dev/null
+++ b/analysis/reanalyze/src/Name.mli
@@ -0,0 +1,9 @@
+type t
+
+val compare : t -> t -> int
+val create : ?isInterface:bool -> string -> t
+val isUnderscore : t -> bool
+val startsWithUnderscore : t -> bool
+val toImplementation : t -> t
+val toInterface : t -> t
+val toString : t -> string
diff --git a/analysis/reanalyze/src/Paths.ml b/analysis/reanalyze/src/Paths.ml
new file mode 100644
index 0000000000..45341f1042
--- /dev/null
+++ b/analysis/reanalyze/src/Paths.ml
@@ -0,0 +1,205 @@
+open Common
+module StringMap = Map_string
+
+let bsconfig = "bsconfig.json"
+let rescriptJson = "rescript.json"
+
+let readFile filename =
+  try
+    (* windows can't use open_in *)
+    let chan = open_in_bin filename in
+    let content = really_input_string chan (in_channel_length chan) in
+    close_in_noerr chan;
+    Some content
+  with _ -> None
+
+let rec findProjectRoot ~dir =
+  let rescriptJsonFile = Filename.concat dir rescriptJson in
+  let bsconfigFile = Filename.concat dir bsconfig in
+  if Sys.file_exists rescriptJsonFile || Sys.file_exists bsconfigFile then dir
+  else
+    let parent = dir |> Filename.dirname in
+    if parent = dir then (
+      prerr_endline
+        ("Error: cannot find project root containing " ^ rescriptJson ^ ".");
+      assert false)
+    else findProjectRoot ~dir:parent
+
+let setReScriptProjectRoot =
+  lazy
+    (runConfig.projectRoot <- findProjectRoot ~dir:(Sys.getcwd ());
+     runConfig.bsbProjectRoot <-
+       (match Sys.getenv_opt "BSB_PROJECT_ROOT" with
+       | None -> runConfig.projectRoot
+       | Some s -> s))
+
+module Config = struct
+  let readSuppress conf =
+    match Json.get "suppress" conf with
+    | Some (Array elements) ->
+      let names =
+        elements
+        |> List.filter_map (fun (x : Json.t) ->
+               match x with
+               | String s -> Some s
+               | _ -> None)
+      in
+      runConfig.suppress <- names @ runConfig.suppress
+    | _ -> ()
+
+  let readUnsuppress conf =
+    match Json.get "unsuppress" conf with
+    | Some (Array elements) ->
+      let names =
+        elements
+        |> List.filter_map (fun (x : Json.t) ->
+               match x with
+               | String s -> Some s
+               | _ -> None)
+      in
+      runConfig.unsuppress <- names @ runConfig.unsuppress
+    | _ -> ()
+
+  let readAnalysis conf =
+    match Json.get "analysis" conf with
+    | Some (Array elements) ->
+      elements
+      |> List.iter (fun (x : Json.t) ->
+             match x with
+             | String "all" -> RunConfig.all ()
+             | String "dce" -> RunConfig.dce ()
+             | String "exception" -> RunConfig.exception_ ()
+             | String "termination" -> RunConfig.termination ()
+             | _ -> ())
+    | _ ->
+      (* if no "analysis" specified, default to dce *)
+      RunConfig.dce ()
+
+  let readTransitive conf =
+    match Json.get "transitive" conf with
+    | Some True -> RunConfig.transitive true
+    | Some False -> RunConfig.transitive false
+    | _ -> ()
+
+  (* Read the config from rescript.json/bsconfig.json and apply it to runConfig and suppress and unsuppress *)
+  let processBsconfig () =
+    Lazy.force setReScriptProjectRoot;
+    let rescriptFile = Filename.concat runConfig.projectRoot rescriptJson in
+    let bsconfigFile = Filename.concat runConfig.projectRoot bsconfig in
+
+    let processText text =
+      match Json.parse text with
+      | None -> ()
+      | Some json -> (
+        match Json.get "reanalyze" json with
+        | Some conf ->
+          readSuppress conf;
+          readUnsuppress conf;
+          readAnalysis conf;
+          readTransitive conf
+        | None ->
+          (* if no "analysis" specified, default to dce *)
+          RunConfig.dce ())
+    in
+
+    match readFile rescriptFile with
+    | Some text -> processText text
+    | None -> (
+      match readFile bsconfigFile with
+      | Some text -> processText text
+      | None -> ())
+end
+
+(**
+  * Handle namespaces in cmt files.
+  * E.g. src/Module-Project.cmt becomes src/Module
+  *)
+let handleNamespace cmt =
+  let cutAfterDash s =
+    match String.index s '-' with
+    | n -> ( try String.sub s 0 n with Invalid_argument _ -> s)
+    | exception Not_found -> s
+  in
+  let noDir = Filename.basename cmt = cmt in
+  if noDir then cmt |> Filename.remove_extension |> cutAfterDash
+  else
+    let dir = cmt |> Filename.dirname in
+    let base =
+      cmt |> Filename.basename |> Filename.remove_extension |> cutAfterDash
+    in
+    Filename.concat dir base
+
+let getModuleName cmt = cmt |> handleNamespace |> Filename.basename
+
+let readDirsFromConfig ~configSources =
+  let dirs = ref [] in
+  let root = runConfig.projectRoot in
+  let rec processDir ~subdirs dir =
+    let absDir =
+      match dir = "" with
+      | true -> root
+      | false -> Filename.concat root dir
+    in
+    if Sys.file_exists absDir && Sys.is_directory absDir then (
+      dirs := dir :: !dirs;
+      if subdirs then
+        absDir |> Sys.readdir
+        |> Array.iter (fun d -> processDir ~subdirs (Filename.concat dir d)))
+  in
+  let rec processSourceItem (sourceItem : Ext_json_types.t) =
+    match sourceItem with
+    | Str {str} -> str |> processDir ~subdirs:false
+    | Obj {map} -> (
+      match StringMap.find_opt map "dir" with
+      | Some (Str {str}) ->
+        let subdirs =
+          match StringMap.find_opt map "subdirs" with
+          | Some (True _) -> true
+          | Some (False _) -> false
+          | _ -> false
+        in
+        str |> processDir ~subdirs
+      | _ -> ())
+    | Arr {content = arr} -> arr |> Array.iter processSourceItem
+    | _ -> ()
+  in
+  (match configSources with
+  | Some sourceItem -> processSourceItem sourceItem
+  | None -> ());
+  !dirs
+
+let readSourceDirs ~configSources =
+  let sourceDirs =
+    ["lib"; "bs"; ".sourcedirs.json"]
+    |> List.fold_left Filename.concat runConfig.bsbProjectRoot
+  in
+  let dirs = ref [] in
+  let readDirs json =
+    match json with
+    | Ext_json_types.Obj {map} -> (
+      match StringMap.find_opt map "dirs" with
+      | Some (Arr {content = arr}) ->
+        arr
+        |> Array.iter (fun x ->
+               match x with
+               | Ext_json_types.Str {str} -> dirs := str :: !dirs
+               | _ -> ());
+        ()
+      | _ -> ())
+    | _ -> ()
+  in
+  if sourceDirs |> Sys.file_exists then
+    let jsonOpt = sourceDirs |> Ext_json_parse.parse_json_from_file in
+    match jsonOpt with
+    | exception _ -> ()
+    | json ->
+      if runConfig.bsbProjectRoot <> runConfig.projectRoot then (
+        readDirs json;
+        dirs := readDirsFromConfig ~configSources)
+      else readDirs json
+  else (
+    if !Cli.debug then (
+      Log_.item "Warning: can't find source dirs: %s\n" sourceDirs;
+      Log_.item "Types for cross-references will not be found.\n");
+    dirs := readDirsFromConfig ~configSources);
+  !dirs
diff --git a/analysis/reanalyze/src/Reanalyze.ml b/analysis/reanalyze/src/Reanalyze.ml
new file mode 100644
index 0000000000..e52a9c1f46
--- /dev/null
+++ b/analysis/reanalyze/src/Reanalyze.ml
@@ -0,0 +1,222 @@
+open Common
+
+let loadCmtFile cmtFilePath =
+  let cmt_infos = Cmt_format.read_cmt cmtFilePath in
+  let excludePath sourceFile =
+    !Cli.excludePaths
+    |> List.exists (fun prefix_ ->
+           let prefix =
+             match Filename.is_relative sourceFile with
+             | true -> prefix_
+             | false -> Filename.concat (Sys.getcwd ()) prefix_
+           in
+           String.length prefix <= String.length sourceFile
+           &&
+           try String.sub sourceFile 0 (String.length prefix) = prefix
+           with Invalid_argument _ -> false)
+  in
+  match cmt_infos.cmt_annots |> FindSourceFile.cmt with
+  | Some sourceFile when not (excludePath sourceFile) ->
+    if !Cli.debug then
+      Log_.item "Scanning %s Source:%s@."
+        (match !Cli.ci && not (Filename.is_relative cmtFilePath) with
+        | true -> Filename.basename cmtFilePath
+        | false -> cmtFilePath)
+        (match !Cli.ci && not (Filename.is_relative sourceFile) with
+        | true -> sourceFile |> Filename.basename
+        | false -> sourceFile);
+    FileReferences.addFile sourceFile;
+    currentSrc := sourceFile;
+    currentModule := Paths.getModuleName sourceFile;
+    currentModuleName :=
+      !currentModule
+      |> Name.create ~isInterface:(Filename.check_suffix !currentSrc "i");
+    if runConfig.dce then cmt_infos |> DeadCode.processCmt ~cmtFilePath;
+    if runConfig.exception_ then cmt_infos |> Exception.processCmt;
+    if runConfig.termination then cmt_infos |> Arnold.processCmt
+  | _ -> ()
+
+let processCmtFiles ~cmtRoot =
+  let ( +++ ) = Filename.concat in
+  match cmtRoot with
+  | Some root ->
+    Cli.cmtCommand := true;
+    let rec walkSubDirs dir =
+      let absDir =
+        match dir = "" with
+        | true -> root
+        | false -> root +++ dir
+      in
+      let skipDir =
+        let base = Filename.basename dir in
+        base = "node_modules" || base = "_esy"
+      in
+      if (not skipDir) && Sys.file_exists absDir then
+        if Sys.is_directory absDir then
+          absDir |> Sys.readdir |> Array.iter (fun d -> walkSubDirs (dir +++ d))
+        else if
+          Filename.check_suffix absDir ".cmt"
+          || Filename.check_suffix absDir ".cmti"
+        then absDir |> loadCmtFile
+    in
+    walkSubDirs ""
+  | None ->
+    Lazy.force Paths.setReScriptProjectRoot;
+    let lib_bs = runConfig.projectRoot +++ ("lib" +++ "bs") in
+    let sourceDirs =
+      Paths.readSourceDirs ~configSources:None |> List.sort String.compare
+    in
+    sourceDirs
+    |> List.iter (fun sourceDir ->
+           let libBsSourceDir = Filename.concat lib_bs sourceDir in
+           let files =
+             match Sys.readdir libBsSourceDir |> Array.to_list with
+             | files -> files
+             | exception Sys_error _ -> []
+           in
+           let cmtFiles =
+             files
+             |> List.filter (fun x ->
+                    Filename.check_suffix x ".cmt"
+                    || Filename.check_suffix x ".cmti")
+           in
+           cmtFiles |> List.sort String.compare
+           |> List.iter (fun cmtFile ->
+                  let cmtFilePath = Filename.concat libBsSourceDir cmtFile in
+                  cmtFilePath |> loadCmtFile))
+
+let runAnalysis ~cmtRoot =
+  processCmtFiles ~cmtRoot;
+  if runConfig.dce then (
+    DeadException.forceDelayedItems ();
+    DeadOptionalArgs.forceDelayedItems ();
+    DeadCommon.reportDead ~checkOptionalArg:DeadOptionalArgs.check;
+    WriteDeadAnnotations.write ());
+  if runConfig.exception_ then Exception.Checks.doChecks ();
+  if runConfig.termination && !Common.Cli.debug then Arnold.reportStats ()
+
+let runAnalysisAndReport ~cmtRoot =
+  Log_.Color.setup ();
+  if !Common.Cli.json then EmitJson.start ();
+  runAnalysis ~cmtRoot;
+  Log_.Stats.report ();
+  Log_.Stats.clear ();
+  if !Common.Cli.json then EmitJson.finish ()
+
+let cli () =
+  let analysisKindSet = ref false in
+  let cmtRootRef = ref None in
+  let usage = "reanalyze version " ^ Version.version in
+  let versionAndExit () =
+    print_endline usage;
+    exit 0
+      [@@raises exit]
+  in
+  let rec setAll cmtRoot =
+    RunConfig.all ();
+    cmtRootRef := cmtRoot;
+    analysisKindSet := true
+  and setConfig () =
+    Paths.Config.processBsconfig ();
+    analysisKindSet := true
+  and setDCE cmtRoot =
+    RunConfig.dce ();
+    cmtRootRef := cmtRoot;
+    analysisKindSet := true
+  and setException cmtRoot =
+    RunConfig.exception_ ();
+    cmtRootRef := cmtRoot;
+    analysisKindSet := true
+  and setTermination cmtRoot =
+    RunConfig.termination ();
+    cmtRootRef := cmtRoot;
+    analysisKindSet := true
+  and speclist =
+    [
+      ("-all", Arg.Unit (fun () -> setAll None), "Run all the analyses.");
+      ( "-all-cmt",
+        String (fun s -> setAll (Some s)),
+        "root_path Run all the analyses for all the .cmt files under the root \
+         path" );
+      ("-ci", Unit (fun () -> Cli.ci := true), "Internal flag for use in CI");
+      ( "-config",
+        Unit setConfig,
+        "Read the analysis mode from rescript.json/bsconfig.json" );
+      ("-dce", Unit (fun () -> setDCE None), "Eperimental DCE");
+      ("-debug", Unit (fun () -> Cli.debug := true), "Print debug information");
+      ( "-dce-cmt",
+        String (fun s -> setDCE (Some s)),
+        "root_path Experimental DCE for all the .cmt files under the root path"
+      );
+      ( "-exception",
+        Unit (fun () -> setException None),
+        "Experimental exception analysis" );
+      ( "-exception-cmt",
+        String (fun s -> setException (Some s)),
+        "root_path Experimental exception analysis for all the .cmt files \
+         under the root path" );
+      ( "-exclude-paths",
+        String
+          (fun s ->
+            let paths = s |> String.split_on_char ',' in
+            Common.Cli.excludePaths := paths @ Common.Cli.excludePaths.contents),
+        "comma-separated-path-prefixes Exclude from analysis files whose path \
+         has a prefix in the list" );
+      ( "-experimental",
+        Set Common.Cli.experimental,
+        "Turn on experimental analyses (this option is currently unused)" );
+      ( "-externals",
+        Set DeadCommon.Config.analyzeExternals,
+        "Report on externals in dead code analysis" );
+      ("-json", Set Common.Cli.json, "Print reports in json format");
+      ( "-live-names",
+        String
+          (fun s ->
+            let names = s |> String.split_on_char ',' in
+            Common.Cli.liveNames := names @ Common.Cli.liveNames.contents),
+        "comma-separated-names Consider all values with the given names as live"
+      );
+      ( "-live-paths",
+        String
+          (fun s ->
+            let paths = s |> String.split_on_char ',' in
+            Common.Cli.livePaths := paths @ Common.Cli.livePaths.contents),
+        "comma-separated-path-prefixes Consider all values whose path has a \
+         prefix in the list as live" );
+      ( "-suppress",
+        String
+          (fun s ->
+            let names = s |> String.split_on_char ',' in
+            runConfig.suppress <- names @ runConfig.suppress),
+        "comma-separated-path-prefixes Don't report on files whose path has a \
+         prefix in the list" );
+      ( "-termination",
+        Unit (fun () -> setTermination None),
+        "Experimental termination analysis" );
+      ( "-termination-cmt",
+        String (fun s -> setTermination (Some s)),
+        "root_path Experimental termination analysis for all the .cmt files \
+         under the root path" );
+      ( "-unsuppress",
+        String
+          (fun s ->
+            let names = s |> String.split_on_char ',' in
+            runConfig.unsuppress <- names @ runConfig.unsuppress),
+        "comma-separated-path-prefixes Report on files whose path has a prefix \
+         in the list, overriding -suppress (no-op if -suppress is not \
+         specified)" );
+      ("-version", Unit versionAndExit, "Show version information and exit");
+      ("--version", Unit versionAndExit, "Show version information and exit");
+      ( "-write",
+        Set Common.Cli.write,
+        "Write @dead annotations directly in the source files" );
+    ]
+  in
+  Arg.parse speclist print_endline usage;
+  if !analysisKindSet = false then setConfig ();
+  let cmtRoot = !cmtRootRef in
+  runAnalysisAndReport ~cmtRoot
+[@@raises exit]
+
+module RunConfig = RunConfig
+module Log_ = Log_
diff --git a/analysis/reanalyze/src/RunConfig.ml b/analysis/reanalyze/src/RunConfig.ml
new file mode 100644
index 0000000000..3c33f79909
--- /dev/null
+++ b/analysis/reanalyze/src/RunConfig.ml
@@ -0,0 +1,33 @@
+type t = {
+  mutable bsbProjectRoot: string;
+  mutable dce: bool;
+  mutable exception_: bool;
+  mutable projectRoot: string;
+  mutable suppress: string list;
+  mutable termination: bool;
+  mutable transitive: bool;
+  mutable unsuppress: string list;
+}
+
+let runConfig =
+  {
+    bsbProjectRoot = "";
+    dce = false;
+    exception_ = false;
+    projectRoot = "";
+    suppress = [];
+    termination = false;
+    transitive = false;
+    unsuppress = [];
+  }
+
+let all () =
+  runConfig.dce <- true;
+  runConfig.exception_ <- true;
+  runConfig.termination <- true
+
+let dce () = runConfig.dce <- true
+let exception_ () = runConfig.exception_ <- true
+let termination () = runConfig.termination <- true
+
+let transitive b = runConfig.transitive <- b
diff --git a/analysis/reanalyze/src/SideEffects.ml b/analysis/reanalyze/src/SideEffects.ml
new file mode 100644
index 0000000000..b226b4988d
--- /dev/null
+++ b/analysis/reanalyze/src/SideEffects.ml
@@ -0,0 +1,89 @@
+let whiteListSideEffects =
+  [
+    "Pervasives./.";
+    "Pervasives.ref";
+    "Int64.mul";
+    "Int64.neg";
+    "Int64.sub";
+    "Int64.shift_left";
+    "Int64.one";
+    "String.length";
+  ]
+
+let whiteTableSideEffects =
+  lazy
+    (let tbl = Hashtbl.create 11 in
+     whiteListSideEffects |> List.iter (fun s -> Hashtbl.add tbl s ());
+     tbl)
+
+let pathIsWhitelistedForSideEffects path =
+  path
+  |> Common.Path.onOkPath ~whenContainsApply:false ~f:(fun s ->
+         Hashtbl.mem (Lazy.force whiteTableSideEffects) s)
+
+let rec exprNoSideEffects (expr : Typedtree.expression) =
+  match expr.exp_desc with
+  | Texp_ident _ | Texp_constant _ -> true
+  | Texp_construct (_, _, el) -> el |> List.for_all exprNoSideEffects
+  | Texp_function _ -> true
+  | Texp_apply ({exp_desc = Texp_ident (path, _, _)}, args)
+    when path |> pathIsWhitelistedForSideEffects ->
+    args |> List.for_all (fun (_, eo) -> eo |> exprOptNoSideEffects)
+  | Texp_apply _ -> false
+  | Texp_sequence (e1, e2) -> e1 |> exprNoSideEffects && e2 |> exprNoSideEffects
+  | Texp_let (_, vbs, e) ->
+    vbs
+    |> List.for_all (fun (vb : Typedtree.value_binding) ->
+           vb.vb_expr |> exprNoSideEffects)
+    && e |> exprNoSideEffects
+  | Texp_record {fields; extended_expression} ->
+    fields |> Array.for_all fieldNoSideEffects
+    && extended_expression |> exprOptNoSideEffects
+  | Texp_assert _ -> false
+  | Texp_match (e, casesOk, casesExn, partial) ->
+    let cases = casesOk @ casesExn in
+    partial = Total && e |> exprNoSideEffects
+    && cases |> List.for_all caseNoSideEffects
+  | Texp_letmodule _ -> false
+  | Texp_lazy e -> e |> exprNoSideEffects
+  | Texp_try (e, cases) ->
+    e |> exprNoSideEffects && cases |> List.for_all caseNoSideEffects
+  | Texp_tuple el -> el |> List.for_all exprNoSideEffects
+  | Texp_variant (_lbl, eo) -> eo |> exprOptNoSideEffects
+  | Texp_field (e, _lid, _ld) -> e |> exprNoSideEffects
+  | Texp_setfield _ -> false
+  | Texp_array el -> el |> List.for_all exprNoSideEffects
+  | Texp_ifthenelse (e1, e2, eo) ->
+    e1 |> exprNoSideEffects && e2 |> exprNoSideEffects
+    && eo |> exprOptNoSideEffects
+  | Texp_while (e1, e2) -> e1 |> exprNoSideEffects && e2 |> exprNoSideEffects
+  | Texp_for (_id, _pat, e1, e2, _dir, e3) ->
+    e1 |> exprNoSideEffects && e2 |> exprNoSideEffects
+    && e3 |> exprNoSideEffects
+  | Texp_send _ -> false
+  | Texp_new _ -> true
+  | Texp_instvar _ -> true
+  | Texp_setinstvar _ -> false
+  | Texp_override _ -> false
+  | Texp_letexception (_ec, e) -> e |> exprNoSideEffects
+  | Texp_object _ -> true
+  | Texp_pack _ -> false
+  | Texp_unreachable -> false
+  | Texp_extension_constructor _ when true -> true
+  | _ -> (* on ocaml 4.08: Texp_letop | Texp_open *) true
+
+and exprOptNoSideEffects eo =
+  match eo with
+  | None -> true
+  | Some e -> e |> exprNoSideEffects
+
+and fieldNoSideEffects ((_ld, rld) : _ * Typedtree.record_label_definition) =
+  match rld with
+  | Kept _typeExpr -> true
+  | Overridden (_lid, e) -> e |> exprNoSideEffects
+
+and caseNoSideEffects : Typedtree.case -> _ =
+ fun {c_guard; c_rhs} ->
+  c_guard |> exprOptNoSideEffects && c_rhs |> exprNoSideEffects
+
+let checkExpr e = not (exprNoSideEffects e)
diff --git a/analysis/reanalyze/src/Suppress.ml b/analysis/reanalyze/src/Suppress.ml
new file mode 100644
index 0000000000..dc9c521a5d
--- /dev/null
+++ b/analysis/reanalyze/src/Suppress.ml
@@ -0,0 +1,33 @@
+open Common
+
+let checkPrefix prefix_ =
+  let prefix =
+    match runConfig.projectRoot = "" with
+    | true -> prefix_
+    | false -> Filename.concat runConfig.projectRoot prefix_
+  in
+  let prefixLen = prefix |> String.length in
+  fun sourceDir ->
+    try String.sub sourceDir 0 prefixLen = prefix
+    with Invalid_argument _ -> false
+
+let suppressSourceDir =
+  lazy
+    (fun sourceDir ->
+      runConfig.suppress
+      |> List.exists (fun prefix -> checkPrefix prefix sourceDir))
+
+let unsuppressSourceDir =
+  lazy
+    (fun sourceDir ->
+      runConfig.unsuppress
+      |> List.exists (fun prefix -> checkPrefix prefix sourceDir))
+
+let posInSuppress (pos : Lexing.position) =
+  pos.pos_fname |> Lazy.force suppressSourceDir
+
+let posInUnsuppress (pos : Lexing.position) =
+  pos.pos_fname |> Lazy.force unsuppressSourceDir
+
+(** First suppress list, then override with unsuppress list *)
+let filter pos = (not (posInSuppress pos)) || posInUnsuppress pos
diff --git a/analysis/reanalyze/src/Version.ml b/analysis/reanalyze/src/Version.ml
new file mode 100644
index 0000000000..4361834b43
--- /dev/null
+++ b/analysis/reanalyze/src/Version.ml
@@ -0,0 +1,4 @@
+(* CREATED BY reanalyze/scripts/bump_version_module.js *)
+(* DO NOT MODIFY BY HAND, WILL BE AUTOMATICALLY UPDATED BY npm version *)
+
+let version = "2.22.0"
diff --git a/analysis/reanalyze/src/WriteDeadAnnotations.ml b/analysis/reanalyze/src/WriteDeadAnnotations.ml
new file mode 100644
index 0000000000..642bb3d875
--- /dev/null
+++ b/analysis/reanalyze/src/WriteDeadAnnotations.ml
@@ -0,0 +1,154 @@
+open Common
+
+type language = Ml | Res
+
+let posLanguage (pos : Lexing.position) =
+  if
+    Filename.check_suffix pos.pos_fname ".res"
+    || Filename.check_suffix pos.pos_fname ".resi"
+  then Res
+  else Ml
+
+let deadAnnotation = "dead"
+let annotateAtEnd ~pos =
+  match posLanguage pos with
+  | Res -> false
+  | Ml -> true
+
+let getPosAnnotation decl =
+  match annotateAtEnd ~pos:decl.pos with
+  | true -> decl.posEnd
+  | false -> decl.posStart
+
+let rec lineToString_ {original; declarations} =
+  match declarations with
+  | [] -> original
+  | ({declKind; path; pos} as decl) :: nextDeclarations ->
+    let language = posLanguage pos in
+    let annotationStr =
+      match language with
+      | Res ->
+        "@" ^ deadAnnotation ^ "(\"" ^ (path |> Path.withoutHead) ^ "\") "
+      | Ml ->
+        " " ^ "["
+        ^ (match declKind |> DeclKind.isType with
+          | true -> "@"
+          | false -> "@@")
+        ^ deadAnnotation ^ " \"" ^ (path |> Path.withoutHead) ^ "\"] "
+    in
+    let posAnnotation = decl |> getPosAnnotation in
+    let col = posAnnotation.pos_cnum - posAnnotation.pos_bol in
+    let originalLen = String.length original in
+    {
+      original =
+        (if String.length original >= col && col > 0 then
+           let original1, original2 =
+             try
+               ( String.sub original 0 col,
+                 String.sub original col (originalLen - col) )
+             with Invalid_argument _ -> (original, "")
+           in
+           if language = Res && declKind = VariantCase then
+             if
+               String.length original2 >= 2
+               && (String.sub [@doesNotRaise]) original2 0 2 = "| "
+             then
+               original1 ^ "| " ^ annotationStr
+               ^ (String.sub [@doesNotRaise]) original2 2
+                   (String.length original2 - 2)
+             else if
+               String.length original2 >= 1
+               && (String.sub [@doesNotRaise]) original2 0 1 = "|"
+             then
+               original1 ^ "|" ^ annotationStr
+               ^ (String.sub [@doesNotRaise]) original2 1
+                   (String.length original2 - 1)
+             else original1 ^ "| " ^ annotationStr ^ original2
+           else original1 ^ annotationStr ^ original2
+         else
+           match language = Ml with
+           | true -> original ^ annotationStr
+           | false -> annotationStr ^ original);
+      declarations = nextDeclarations;
+    }
+    |> lineToString_
+
+let lineToString {original; declarations} =
+  let declarations =
+    declarations
+    |> List.sort (fun decl1 decl2 ->
+           (getPosAnnotation decl2).pos_cnum - (getPosAnnotation decl1).pos_cnum)
+  in
+  lineToString_ {original; declarations}
+
+let currentFile = ref ""
+let currentFileLines = (ref [||] : line array ref)
+
+let readFile fileName =
+  let channel = open_in fileName in
+  let lines = ref [] in
+  let rec loop () =
+    let line = {original = input_line channel; declarations = []} in
+    lines := line :: !lines;
+    loop ()
+      [@@raises End_of_file]
+  in
+  try loop ()
+  with End_of_file ->
+    close_in_noerr channel;
+    !lines |> List.rev |> Array.of_list
+
+let writeFile fileName lines =
+  if fileName <> "" && !Cli.write then (
+    let channel = open_out fileName in
+    let lastLine = Array.length lines in
+    lines
+    |> Array.iteri (fun n line ->
+           output_string channel (line |> lineToString);
+           if n < lastLine - 1 then output_char channel '\n');
+    close_out_noerr channel)
+
+let offsetOfPosAdjustment = function
+  | FirstVariant | Nothing -> 0
+  | OtherVariant -> 2
+
+let getLineAnnotation ~decl ~line =
+  if !Cli.json then
+    let posAnnotation = decl |> getPosAnnotation in
+    let offset = decl.posAdjustment |> offsetOfPosAdjustment in
+    EmitJson.emitAnnotate
+      ~pos:
+        ( posAnnotation.pos_lnum - 1,
+          posAnnotation.pos_cnum - posAnnotation.pos_bol + offset )
+      ~text:
+        (if decl.posAdjustment = FirstVariant then
+           (* avoid syntax error *)
+           "| @dead "
+         else "@dead ")
+      ~action:"Suppress dead code warning"
+  else
+    Format.asprintf "@.  <-- line %d@.  %s" decl.pos.pos_lnum
+      (line |> lineToString)
+
+let cantFindLine () = if !Cli.json then "" else "\n  <-- Can't find line"
+
+let lineAnnotationToString = function
+  | None -> cantFindLine ()
+  | Some (decl, line) -> getLineAnnotation ~decl ~line
+
+let addLineAnnotation ~decl : lineAnnotation =
+  let fileName = decl.pos.pos_fname in
+  if Sys.file_exists fileName then (
+    if fileName <> !currentFile then (
+      writeFile !currentFile !currentFileLines;
+      currentFile := fileName;
+      currentFileLines := readFile fileName);
+    let indexInLines = (decl |> getPosAnnotation).pos_lnum - 1 in
+    match !currentFileLines.(indexInLines) with
+    | line ->
+      line.declarations <- decl :: line.declarations;
+      Some (decl, line)
+    | exception Invalid_argument _ -> None)
+  else None
+
+let write () = writeFile !currentFile !currentFileLines
diff --git a/analysis/reanalyze/src/dune b/analysis/reanalyze/src/dune
new file mode 100644
index 0000000000..e8b736446f
--- /dev/null
+++ b/analysis/reanalyze/src/dune
@@ -0,0 +1,5 @@
+(library
+ (name reanalyze)
+ (flags
+  (-w "+6+26+27+32+33+39"))
+ (libraries jsonlib ext ml str unix))
diff --git a/analysis/src/BuildSystem.ml b/analysis/src/BuildSystem.ml
new file mode 100644
index 0000000000..342acfb932
--- /dev/null
+++ b/analysis/src/BuildSystem.ml
@@ -0,0 +1,27 @@
+let namespacedName namespace name =
+  match namespace with
+  | None -> name
+  | Some namespace -> name ^ "-" ^ namespace
+
+let ( /+ ) = Filename.concat
+
+let getBsPlatformDir rootPath =
+  match !Cfg.isDocGenFromCompiler with
+  | false -> (
+    let result =
+      ModuleResolution.resolveNodeModulePath ~startPath:rootPath "rescript"
+    in
+    match result with
+    | Some path -> Some path
+    | None ->
+      let message = "rescript could not be found" in
+      Log.log message;
+      None)
+  | true -> Some rootPath
+
+let getLibBs root = Files.ifExists (root /+ "lib" /+ "bs")
+
+let getStdlib base =
+  match getBsPlatformDir base with
+  | None -> None
+  | Some bsPlatformDir -> Some (bsPlatformDir /+ "lib" /+ "ocaml")
diff --git a/analysis/src/Cache.ml b/analysis/src/Cache.ml
new file mode 100644
index 0000000000..5e7b3203fc
--- /dev/null
+++ b/analysis/src/Cache.ml
@@ -0,0 +1,41 @@
+open SharedTypes
+
+type cached = {
+  projectFiles: FileSet.t;
+  dependenciesFiles: FileSet.t;
+  pathsForModule: (file, paths) Hashtbl.t;
+}
+
+let writeCache filename (data : cached) =
+  let oc = open_out_bin filename in
+  Marshal.to_channel oc data [];
+  close_out oc
+
+let readCache filename =
+  if !Cfg.readProjectConfigCache && Sys.file_exists filename then
+    try
+      let ic = open_in_bin filename in
+      let data : cached = Marshal.from_channel ic in
+      close_in ic;
+      Some data
+    with _ -> None
+  else None
+
+let deleteCache filename = try Sys.remove filename with _ -> ()
+
+let targetFileFromLibBs libBs = Filename.concat libBs ".project-files-cache"
+
+let cacheProject (package : package) =
+  let cached =
+    {
+      projectFiles = package.projectFiles;
+      dependenciesFiles = package.dependenciesFiles;
+      pathsForModule = package.pathsForModule;
+    }
+  in
+  match BuildSystem.getLibBs package.rootPath with
+  | None -> print_endline "\"ERR\""
+  | Some libBs ->
+    let targetFile = targetFileFromLibBs libBs in
+    writeCache targetFile cached;
+    print_endline "\"OK\""
diff --git a/analysis/src/Cfg.ml b/analysis/src/Cfg.ml
new file mode 100644
index 0000000000..bd4166d5ab
--- /dev/null
+++ b/analysis/src/Cfg.ml
@@ -0,0 +1,19 @@
+let debugFollowCtxPath = ref false
+
+let isDocGenFromCompiler = ref false
+
+let inIncrementalTypecheckingMode =
+  ref
+    (try
+       match Sys.getenv "RESCRIPT_INCREMENTAL_TYPECHECKING" with
+       | "true" -> true
+       | _ -> false
+     with _ -> false)
+
+let readProjectConfigCache =
+  ref
+    (try
+       match Sys.getenv "RESCRIPT_PROJECT_CONFIG_CACHE" with
+       | "true" -> true
+       | _ -> false
+     with _ -> false)
diff --git a/analysis/src/Cmt.ml b/analysis/src/Cmt.ml
new file mode 100644
index 0000000000..a433d12908
--- /dev/null
+++ b/analysis/src/Cmt.ml
@@ -0,0 +1,53 @@
+open SharedTypes
+
+let fullForCmt ~moduleName ~package ~uri cmt =
+  match Shared.tryReadCmt cmt with
+  | None -> None
+  | Some infos ->
+    let file = ProcessCmt.fileForCmtInfos ~moduleName ~uri infos in
+    let extra = ProcessExtra.getExtra ~file ~infos in
+    Some {file; extra; package}
+
+let fullFromUri ~uri =
+  let path = Uri.toPath uri in
+  match Packages.getPackage ~uri with
+  | None -> None
+  | Some package -> (
+    let moduleName =
+      BuildSystem.namespacedName package.namespace (FindFiles.getName path)
+    in
+    let incremental =
+      if !Cfg.inIncrementalTypecheckingMode then
+        let incrementalCmtPath =
+          package.rootPath ^ "/lib/bs/___incremental" ^ "/" ^ moduleName
+          ^
+          match Files.classifySourceFile path with
+          | Resi -> ".cmti"
+          | _ -> ".cmt"
+        in
+        fullForCmt ~moduleName ~package ~uri incrementalCmtPath
+      else None
+    in
+    match incremental with
+    | Some cmtInfo ->
+      if Debug.verbose () then Printf.printf "[cmt] Found incremental cmt\n";
+      Some cmtInfo
+    | None -> (
+      match Hashtbl.find_opt package.pathsForModule moduleName with
+      | Some paths ->
+        let cmt = getCmtPath ~uri paths in
+        fullForCmt ~moduleName ~package ~uri cmt
+      | None ->
+        prerr_endline ("can't find module " ^ moduleName);
+        None))
+
+let fullsFromModule ~package ~moduleName =
+  if Hashtbl.mem package.pathsForModule moduleName then
+    let paths = Hashtbl.find package.pathsForModule moduleName in
+    let uris = getUris paths in
+    uris |> List.filter_map (fun uri -> fullFromUri ~uri)
+  else []
+
+let loadFullCmtFromPath ~path =
+  let uri = Uri.fromPath path in
+  fullFromUri ~uri
diff --git a/analysis/src/CodeActions.ml b/analysis/src/CodeActions.ml
new file mode 100644
index 0000000000..013395b3ab
--- /dev/null
+++ b/analysis/src/CodeActions.ml
@@ -0,0 +1,27 @@
+(* This is the return that's expected when resolving code actions *)
+type result = Protocol.codeAction list
+
+let stringifyCodeActions codeActions =
+  Printf.sprintf {|%s|}
+    (codeActions |> List.map Protocol.stringifyCodeAction |> Protocol.array)
+
+let make ~title ~kind ~uri ~newText ~range =
+  let uri = uri |> Uri.fromPath |> Uri.toString in
+  {
+    Protocol.title;
+    codeActionKind = kind;
+    edit =
+      {
+        documentChanges =
+          [
+            TextDocumentEdit
+              {
+                Protocol.textDocument = {version = None; uri};
+                edits = [{newText; range}];
+              };
+          ];
+      };
+  }
+
+let makeWithDocumentChanges ~title ~kind ~documentChanges =
+  {Protocol.title; codeActionKind = kind; edit = {documentChanges}}
diff --git a/analysis/src/Codemod.ml b/analysis/src/Codemod.ml
new file mode 100644
index 0000000000..61d7318cc8
--- /dev/null
+++ b/analysis/src/Codemod.ml
@@ -0,0 +1,47 @@
+type transformType = AddMissingCases
+
+let rec collectPatterns p =
+  match p.Parsetree.ppat_desc with
+  | Ppat_or (p1, p2) -> collectPatterns p1 @ [p2]
+  | _ -> [p]
+
+let transform ~path ~pos ~debug ~typ ~hint =
+  let structure, printExpr, _, _ = Xform.parseImplementation ~filename:path in
+  match typ with
+  | AddMissingCases -> (
+    let source = "let " ^ hint ^ " = ()" in
+    let {Res_driver.parsetree = hintStructure} =
+      Res_driver.parse_implementation_from_source ~for_printer:false
+        ~display_filename:"<none>" ~source
+    in
+    match hintStructure with
+    | [{pstr_desc = Pstr_value (_, [{pvb_pat = pattern}])}] -> (
+      let cases =
+        collectPatterns pattern
+        |> List.map (fun (p : Parsetree.pattern) ->
+               Ast_helper.Exp.case p (TypeUtils.Codegen.mkFailWithExp ()))
+      in
+      let result = ref None in
+      let mkIterator ~pos ~result =
+        let expr (iterator : Ast_iterator.iterator) (exp : Parsetree.expression)
+            =
+          match exp.pexp_desc with
+          | Pexp_match (e, existingCases)
+            when Pos.ofLexing exp.pexp_loc.loc_start = pos ->
+            result :=
+              Some {exp with pexp_desc = Pexp_match (e, existingCases @ cases)}
+          | _ -> Ast_iterator.default_iterator.expr iterator exp
+        in
+        {Ast_iterator.default_iterator with expr}
+      in
+      let iterator = mkIterator ~pos ~result in
+      iterator.structure iterator structure;
+      match !result with
+      | None ->
+        if debug then print_endline "Found no result";
+        exit 1
+      | Some switchExpr ->
+        printExpr ~range:(Xform.rangeOfLoc switchExpr.pexp_loc) switchExpr)
+    | _ ->
+      if debug then print_endline "Mismatch in expected structure";
+      exit 1)
diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml
new file mode 100644
index 0000000000..464b3fa53d
--- /dev/null
+++ b/analysis/src/Commands.ml
@@ -0,0 +1,508 @@
+let completion ~debug ~path ~pos ~currentFile =
+  let completions =
+    match
+      Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover:false
+    with
+    | None -> []
+    | Some (completions, full, _) ->
+      completions
+      |> List.map (CompletionBackEnd.completionToItem ~full)
+      |> List.map Protocol.stringifyCompletionItem
+  in
+  completions |> Protocol.array |> print_endline
+
+let completionResolve ~path ~modulePath =
+  (* We ignore the internal module path as of now because there's currently
+     no use case for it. But, if we wanted to move resolving documentation
+     for regular modules and not just file modules to the completionResolve
+     hook as well, it'd be easy to implement here. *)
+  let moduleName, _innerModulePath =
+    match modulePath |> String.split_on_char '.' with
+    | [moduleName] -> (moduleName, [])
+    | moduleName :: rest -> (moduleName, rest)
+    | [] -> raise (Failure "Invalid module path.")
+  in
+  let docstring =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None ->
+      if Debug.verbose () then
+        Printf.printf "[completion_resolve] Could not load cmt\n";
+      Protocol.null
+    | Some full -> (
+      match ProcessCmt.fileForModule ~package:full.package moduleName with
+      | None ->
+        if Debug.verbose () then
+          Printf.printf "[completion_resolve] Did not find file for module %s\n"
+            moduleName;
+        Protocol.null
+      | Some file ->
+        file.structure.docstring |> String.concat "\n\n"
+        |> Protocol.wrapInQuotes)
+  in
+  print_endline docstring
+
+let inlayhint ~path ~pos ~maxLength ~debug =
+  let result =
+    match Hint.inlay ~path ~pos ~maxLength ~debug with
+    | Some hints -> hints |> Protocol.array
+    | None -> Protocol.null
+  in
+  print_endline result
+
+let codeLens ~path ~debug =
+  let result =
+    match Hint.codeLens ~path ~debug with
+    | Some lens -> lens |> Protocol.array
+    | None -> Protocol.null
+  in
+  print_endline result
+
+let hover ~path ~pos ~currentFile ~debug ~supportsMarkdownLinks =
+  let result =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None -> Protocol.null
+    | Some full -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> (
+        if debug then
+          Printf.printf
+            "Nothing at that position. Now trying to use completion.\n";
+        match
+          Hover.getHoverViaCompletions ~debug ~path ~pos ~currentFile
+            ~forHover:true ~supportsMarkdownLinks
+        with
+        | None -> Protocol.null
+        | Some hover -> hover)
+      | Some locItem -> (
+        let isModule =
+          match locItem.locType with
+          | LModule _ | TopLevelModule _ -> true
+          | TypeDefinition _ | Typed _ | Constant _ -> false
+        in
+        let uriLocOpt = References.definitionForLocItem ~full locItem in
+        let skipZero =
+          match uriLocOpt with
+          | None -> false
+          | Some (_, loc) ->
+            let isInterface = full.file.uri |> Uri.isInterface in
+            let posIsZero {Lexing.pos_lnum; pos_bol; pos_cnum} =
+              (not isInterface) && pos_lnum = 1 && pos_cnum - pos_bol = 0
+            in
+            (* Skip if range is all zero, unless it's a module *)
+            (not isModule) && posIsZero loc.loc_start && posIsZero loc.loc_end
+        in
+        if skipZero then Protocol.null
+        else
+          let hoverText = Hover.newHover ~supportsMarkdownLinks ~full locItem in
+          match hoverText with
+          | None -> Protocol.null
+          | Some s -> Protocol.stringifyHover s))
+  in
+  print_endline result
+
+let signatureHelp ~path ~pos ~currentFile ~debug ~allowForConstructorPayloads =
+  let result =
+    match
+      SignatureHelp.signatureHelp ~path ~pos ~currentFile ~debug
+        ~allowForConstructorPayloads
+    with
+    | None ->
+      {Protocol.signatures = []; activeSignature = None; activeParameter = None}
+    | Some res -> res
+  in
+  print_endline (Protocol.stringifySignatureHelp result)
+
+let codeAction ~path ~startPos ~endPos ~currentFile ~debug =
+  Xform.extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug
+  |> CodeActions.stringifyCodeActions |> print_endline
+
+let definition ~path ~pos ~debug =
+  let locationOpt =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None -> None
+    | Some full -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> None
+      | Some locItem -> (
+        match References.definitionForLocItem ~full locItem with
+        | None -> None
+        | Some (uri, loc) when not loc.loc_ghost ->
+          let isInterface = full.file.uri |> Uri.isInterface in
+          let posIsZero {Lexing.pos_lnum; pos_bol; pos_cnum} =
+            (* range is zero *)
+            pos_lnum = 1 && pos_cnum - pos_bol = 0
+          in
+          let isModule =
+            match locItem.locType with
+            | LModule _ | TopLevelModule _ -> true
+            | TypeDefinition _ | Typed _ | Constant _ -> false
+          in
+          let skipLoc =
+            (not isModule) && (not isInterface) && posIsZero loc.loc_start
+            && posIsZero loc.loc_end
+          in
+          if skipLoc then None
+          else
+            Some
+              {
+                Protocol.uri = Files.canonicalizeUri uri;
+                range = Utils.cmtLocToRange loc;
+              }
+        | Some _ -> None))
+  in
+  print_endline
+    (match locationOpt with
+    | None -> Protocol.null
+    | Some location -> location |> Protocol.stringifyLocation)
+
+let typeDefinition ~path ~pos ~debug =
+  let maybeLocation =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None -> None
+    | Some full -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> None
+      | Some locItem -> (
+        match References.typeDefinitionForLocItem ~full locItem with
+        | None -> None
+        | Some (uri, loc) ->
+          Some
+            {
+              Protocol.uri = Files.canonicalizeUri uri;
+              range = Utils.cmtLocToRange loc;
+            }))
+  in
+  print_endline
+    (match maybeLocation with
+    | None -> Protocol.null
+    | Some location -> location |> Protocol.stringifyLocation)
+
+let references ~path ~pos ~debug =
+  let allLocs =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None -> []
+    | Some full -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> []
+      | Some locItem ->
+        let allReferences = References.allReferencesForLocItem ~full locItem in
+        allReferences
+        |> List.fold_left
+             (fun acc {References.uri = uri2; locOpt} ->
+               let loc =
+                 match locOpt with
+                 | Some loc -> loc
+                 | None -> Uri.toTopLevelLoc uri2
+               in
+               Protocol.stringifyLocation
+                 {uri = Uri.toString uri2; range = Utils.cmtLocToRange loc}
+               :: acc)
+             [])
+  in
+  print_endline
+    (if allLocs = [] then Protocol.null
+     else "[\n" ^ (allLocs |> String.concat ",\n") ^ "\n]")
+
+let rename ~path ~pos ~newName ~debug =
+  let result =
+    match Cmt.loadFullCmtFromPath ~path with
+    | None -> Protocol.null
+    | Some full -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> Protocol.null
+      | Some locItem ->
+        let allReferences = References.allReferencesForLocItem ~full locItem in
+        let referencesToToplevelModules =
+          allReferences
+          |> Utils.filterMap (fun {References.uri = uri2; locOpt} ->
+                 if locOpt = None then Some uri2 else None)
+        in
+        let referencesToItems =
+          allReferences
+          |> Utils.filterMap (function
+               | {References.uri = uri2; locOpt = Some loc} -> Some (uri2, loc)
+               | {locOpt = None} -> None)
+        in
+        let fileRenames =
+          referencesToToplevelModules
+          |> List.map (fun uri ->
+                 let path = Uri.toPath uri in
+                 let dir = Filename.dirname path in
+                 let newPath =
+                   Filename.concat dir (newName ^ Filename.extension path)
+                 in
+                 let newUri = Uri.fromPath newPath in
+                 Protocol.
+                   {
+                     oldUri = uri |> Uri.toString;
+                     newUri = newUri |> Uri.toString;
+                   })
+        in
+        let textDocumentEdits =
+          let module StringMap = Misc.StringMap in
+          let textEditsByUri =
+            referencesToItems
+            |> List.map (fun (uri, loc) -> (Uri.toString uri, loc))
+            |> List.fold_left
+                 (fun acc (uri, loc) ->
+                   let textEdit =
+                     Protocol.
+                       {range = Utils.cmtLocToRange loc; newText = newName}
+                   in
+                   match StringMap.find_opt uri acc with
+                   | None -> StringMap.add uri [textEdit] acc
+                   | Some prevEdits ->
+                     StringMap.add uri (textEdit :: prevEdits) acc)
+                 StringMap.empty
+          in
+          StringMap.fold
+            (fun uri edits acc ->
+              let textDocumentEdit =
+                Protocol.{textDocument = {uri; version = None}; edits}
+              in
+              textDocumentEdit :: acc)
+            textEditsByUri []
+        in
+        let fileRenamesString =
+          fileRenames |> List.map Protocol.stringifyRenameFile
+        in
+        let textDocumentEditsString =
+          textDocumentEdits |> List.map Protocol.stringifyTextDocumentEdit
+        in
+        "[\n"
+        ^ (fileRenamesString @ textDocumentEditsString |> String.concat ",\n")
+        ^ "\n]")
+  in
+  print_endline result
+
+let format ~path =
+  if Filename.check_suffix path ".res" then
+    let {Res_driver.parsetree = structure; comments; diagnostics} =
+      Res_driver.parsing_engine.parse_implementation ~for_printer:true
+        ~filename:path
+    in
+    if List.length diagnostics > 0 then ""
+    else
+      Res_printer.print_implementation
+        ~width:Res_multi_printer.default_print_width ~comments structure
+  else if Filename.check_suffix path ".resi" then
+    let {Res_driver.parsetree = signature; comments; diagnostics} =
+      Res_driver.parsing_engine.parse_interface ~for_printer:true ~filename:path
+    in
+    if List.length diagnostics > 0 then ""
+    else
+      Res_printer.print_interface ~width:Res_multi_printer.default_print_width
+        ~comments signature
+  else ""
+
+let diagnosticSyntax ~path =
+  print_endline (Diagnostics.document_syntax ~path |> Protocol.array)
+
+let test ~path =
+  Uri.stripPath := true;
+  match Files.readFile path with
+  | None -> assert false
+  | Some text ->
+    let lines = text |> String.split_on_char '\n' in
+    let processLine i line =
+      let createCurrentFile () =
+        let currentFile, cout =
+          Filename.open_temp_file "def" ("txt." ^ Filename.extension path)
+        in
+        let removeLineComment l =
+          let len = String.length l in
+          let rec loop i =
+            if i + 2 <= len && l.[i] = '/' && l.[i + 1] = '/' then Some (i + 2)
+            else if i + 2 < len && l.[i] = ' ' then loop (i + 1)
+            else None
+          in
+          match loop 0 with
+          | None -> l
+          | Some indexAfterComment ->
+            String.make indexAfterComment ' '
+            ^ String.sub l indexAfterComment (len - indexAfterComment)
+        in
+        lines
+        |> List.iteri (fun j l ->
+               let lineToOutput =
+                 if j == i - 1 then removeLineComment l else l
+               in
+               Printf.fprintf cout "%s\n" lineToOutput);
+        close_out cout;
+        currentFile
+      in
+      if Str.string_match (Str.regexp "^ *//[ ]*\\^") line 0 then
+        let matched = Str.matched_string line in
+        let len = line |> String.length in
+        let mlen = String.length matched in
+        let rest = String.sub line mlen (len - mlen) in
+        let line = i - 1 in
+        let col = mlen - 1 in
+        if mlen >= 3 then (
+          (match String.sub rest 0 3 with
+          | "db+" -> Log.verbose := true
+          | "db-" -> Log.verbose := false
+          | "dv+" -> Debug.debugLevel := Verbose
+          | "dv-" -> Debug.debugLevel := Off
+          | "in+" -> Cfg.inIncrementalTypecheckingMode := true
+          | "in-" -> Cfg.inIncrementalTypecheckingMode := false
+          | "ve+" -> (
+            let version = String.sub rest 3 (String.length rest - 3) in
+            let version = String.trim version in
+            if Debug.verbose () then
+              Printf.printf "Setting version: %s\n" version;
+            match String.split_on_char '.' version with
+            | [majorRaw; minorRaw] ->
+              let version = (int_of_string majorRaw, int_of_string minorRaw) in
+              Packages.overrideRescriptVersion := Some version
+            | _ -> ())
+          | "ve-" -> Packages.overrideRescriptVersion := None
+          | "def" ->
+            print_endline
+              ("Definition " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            definition ~path ~pos:(line, col) ~debug:true
+          | "com" ->
+            print_endline
+              ("Complete " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            let currentFile = createCurrentFile () in
+            completion ~debug:true ~path ~pos:(line, col) ~currentFile;
+            Sys.remove currentFile
+          | "cre" ->
+            let modulePath = String.sub rest 3 (String.length rest - 3) in
+            let modulePath = String.trim modulePath in
+            print_endline ("Completion resolve: " ^ modulePath);
+            completionResolve ~path ~modulePath
+          | "dce" ->
+            print_endline ("DCE " ^ path);
+            Reanalyze.RunConfig.runConfig.suppress <- ["src"];
+            Reanalyze.RunConfig.runConfig.unsuppress <-
+              [Filename.concat "src" "dce"];
+            DceCommand.command ()
+          | "doc" ->
+            print_endline ("DocumentSymbol " ^ path);
+            DocumentSymbol.command ~path
+          | "hig" ->
+            print_endline ("Highlight " ^ path);
+            SemanticTokens.command ~debug:true
+              ~emitter:(SemanticTokens.Token.createEmitter ())
+              ~path
+          | "hov" ->
+            print_endline
+              ("Hover " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            let currentFile = createCurrentFile () in
+            hover ~supportsMarkdownLinks:true ~path ~pos:(line, col)
+              ~currentFile ~debug:true;
+            Sys.remove currentFile
+          | "she" ->
+            print_endline
+              ("Signature help " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            let currentFile = createCurrentFile () in
+            signatureHelp ~path ~pos:(line, col) ~currentFile ~debug:true
+              ~allowForConstructorPayloads:true;
+            Sys.remove currentFile
+          | "int" ->
+            print_endline ("Create Interface " ^ path);
+            let cmiFile =
+              let open Filename in
+              let ( ++ ) = concat in
+              let name = chop_extension (basename path) ^ ".cmi" in
+              let dir = dirname path in
+              dir ++ parent_dir_name ++ "lib" ++ "bs" ++ "src" ++ name
+            in
+            Printf.printf "%s" (CreateInterface.command ~path ~cmiFile)
+          | "ref" ->
+            print_endline
+              ("References " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            references ~path ~pos:(line, col) ~debug:true
+          | "ren" ->
+            let newName = String.sub rest 4 (len - mlen - 4) in
+            let () =
+              print_endline
+                ("Rename " ^ path ^ " " ^ string_of_int line ^ ":"
+               ^ string_of_int col ^ " " ^ newName)
+            in
+            rename ~path ~pos:(line, col) ~newName ~debug:true
+          | "typ" ->
+            print_endline
+              ("TypeDefinition " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            typeDefinition ~path ~pos:(line, col) ~debug:true
+          | "xfm" ->
+            let currentFile = createCurrentFile () in
+            (* +2 is to ensure that the character ^ points to is what's considered the end of the selection. *)
+            let endCol = col + try String.index rest '^' + 2 with _ -> 0 in
+            let endPos = (line, endCol) in
+            let startPos = (line, col) in
+            if startPos = endPos then
+              print_endline
+                ("Xform " ^ path ^ " " ^ string_of_int line ^ ":"
+               ^ string_of_int col)
+            else
+              print_endline
+                ("Xform " ^ path ^ " start: " ^ Pos.toString startPos
+               ^ ", end: " ^ Pos.toString endPos);
+            let codeActions =
+              Xform.extractCodeActions ~path ~startPos ~endPos ~currentFile
+                ~debug:true
+            in
+            Sys.remove currentFile;
+            codeActions
+            |> List.iter (fun {Protocol.title; edit = {documentChanges}} ->
+                   Printf.printf "Hit: %s\n" title;
+                   documentChanges
+                   |> List.iter (fun dc ->
+                          match dc with
+                          | Protocol.TextDocumentEdit tde ->
+                            Printf.printf "\nTextDocumentEdit: %s\n"
+                              tde.textDocument.uri;
+
+                            tde.edits
+                            |> List.iter (fun {Protocol.range; newText} ->
+                                   let indent =
+                                     String.make range.start.character ' '
+                                   in
+                                   Printf.printf
+                                     "%s\nnewText:\n%s<--here\n%s%s\n"
+                                     (Protocol.stringifyRange range)
+                                     indent indent newText)
+                          | CreateFile cf ->
+                            Printf.printf "\nCreateFile: %s\n" cf.uri))
+          | "c-a" ->
+            let hint = String.sub rest 3 (String.length rest - 3) in
+            print_endline
+              ("Codemod AddMissingCases" ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            Codemod.transform ~path ~pos:(line, col) ~debug:true
+              ~typ:AddMissingCases ~hint
+            |> print_endline
+          | "dia" -> diagnosticSyntax ~path
+          | "hin" ->
+            (* Get all inlay Hint between line 1 and n.
+               Don't get the first line = 0.
+            *)
+            let line_start = 1 in
+            let line_end = 34 in
+            print_endline
+              ("Inlay Hint " ^ path ^ " " ^ string_of_int line_start ^ ":"
+             ^ string_of_int line_end);
+            inlayhint ~path ~pos:(line_start, line_end) ~maxLength:"25"
+              ~debug:false
+          | "cle" ->
+            print_endline ("Code Lens " ^ path);
+            codeLens ~path ~debug:false
+          | "ast" ->
+            print_endline
+              ("Dump AST " ^ path ^ " " ^ string_of_int line ^ ":"
+             ^ string_of_int col);
+            let currentFile = createCurrentFile () in
+            DumpAst.dump ~pos:(line, col) ~currentFile;
+            Sys.remove currentFile
+          | _ -> ());
+          print_newline ())
+    in
+    lines |> List.iteri processLine
diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml
new file mode 100644
index 0000000000..44798735ca
--- /dev/null
+++ b/analysis/src/CompletionBackEnd.ml
@@ -0,0 +1,2323 @@
+open SharedTypes
+
+let showConstructor {Constructor.cname = {txt}; args; res} =
+  txt
+  ^ (match args with
+    | Args [] -> ""
+    | InlineRecord fields ->
+      "({"
+      ^ (fields
+        |> List.map (fun (field : field) ->
+               Printf.sprintf "%s%s: %s" field.fname.txt
+                 (if field.optional then "?" else "")
+                 (Shared.typeToString
+                    (if field.optional then Utils.unwrapIfOption field.typ
+                     else field.typ)))
+        |> String.concat ", ")
+      ^ "})"
+    | Args args ->
+      "("
+      ^ (args
+        |> List.map (fun (typ, _) -> typ |> Shared.typeToString)
+        |> String.concat ", ")
+      ^ ")")
+  ^
+  match res with
+  | None -> ""
+  | Some typ -> "\n" ^ (typ |> Shared.typeToString)
+
+(* TODO: local opens *)
+let resolveOpens ~env opens ~package =
+  List.fold_left
+    (fun previous path ->
+      (* Finding an open, first trying to find it in previoulsly resolved opens *)
+      let rec loop prev =
+        match prev with
+        | [] -> (
+          match path with
+          | [] | [_] -> previous
+          | name :: path -> (
+            match ProcessCmt.fileForModule ~package name with
+            | None ->
+              Log.log ("Could not get module " ^ name);
+              previous (* TODO: warn? *)
+            | Some file -> (
+              match
+                ResolvePath.resolvePath ~env:(QueryEnv.fromFile file) ~package
+                  ~path
+              with
+              | None ->
+                Log.log ("Could not resolve in " ^ name);
+                previous
+              | Some (env, _placeholder) -> previous @ [env])))
+        | env :: rest -> (
+          match ResolvePath.resolvePath ~env ~package ~path with
+          | None -> loop rest
+          | Some (env, _placeholder) -> previous @ [env])
+      in
+      Log.log ("resolving open " ^ pathToString path);
+      match ResolvePath.resolvePath ~env ~package ~path with
+      | None ->
+        Log.log "Not local";
+        loop previous
+      | Some (env, _) ->
+        Log.log "Was local";
+        previous @ [env])
+    (* loop(previous) *)
+    [] opens
+
+let completionForExporteds iterExported getDeclared ~prefix ~exact ~env
+    ~namesUsed transformContents =
+  let res = ref [] in
+  iterExported (fun name stamp ->
+      (* Log.log("checking exported: " ++ name); *)
+      if Utils.checkName name ~prefix ~exact then
+        match getDeclared stamp with
+        | Some (declared : _ Declared.t)
+          when not (Hashtbl.mem namesUsed declared.name.txt) ->
+          Hashtbl.add namesUsed declared.name.txt ();
+          res :=
+            {
+              (Completion.create declared.name.txt ~env
+                 ~kind:(transformContents declared))
+              with
+              deprecated = declared.deprecated;
+              docstring = declared.docstring;
+            }
+            :: !res
+        | _ -> ());
+  !res
+
+let completionForExportedModules ~env ~prefix ~exact ~namesUsed =
+  completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Module)
+    (Stamps.findModule env.file.stamps) ~prefix ~exact ~env ~namesUsed
+    (fun declared ->
+      Completion.Module
+        {docstring = declared.docstring; module_ = declared.item})
+
+let completionForExportedValues ~env ~prefix ~exact ~namesUsed =
+  completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Value)
+    (Stamps.findValue env.file.stamps) ~prefix ~exact ~env ~namesUsed
+    (fun declared -> Completion.Value declared.item)
+
+let completionForExportedTypes ~env ~prefix ~exact ~namesUsed =
+  completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Type)
+    (Stamps.findType env.file.stamps) ~prefix ~exact ~env ~namesUsed
+    (fun declared -> Completion.Type declared.item)
+
+let completionsForExportedConstructors ~(env : QueryEnv.t) ~prefix ~exact
+    ~namesUsed =
+  let res = ref [] in
+  Exported.iter env.exported Exported.Type (fun _name stamp ->
+      match Stamps.findType env.file.stamps stamp with
+      | Some ({item = {kind = Type.Variant constructors}} as t) ->
+        res :=
+          (constructors
+          |> List.filter (fun c ->
+                 Utils.checkName c.Constructor.cname.txt ~prefix ~exact)
+          |> Utils.filterMap (fun c ->
+                 let name = c.Constructor.cname.txt in
+                 if not (Hashtbl.mem namesUsed name) then
+                   let () = Hashtbl.add namesUsed name () in
+                   Some
+                     (Completion.create name ~env ~docstring:c.docstring
+                        ?deprecated:c.deprecated
+                        ~kind:
+                          (Completion.Constructor
+                             (c, t.item.decl |> Shared.declToString t.name.txt)))
+                 else None))
+          @ !res
+      | _ -> ());
+  !res
+
+let completionForExportedFields ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed =
+  let res = ref [] in
+  Exported.iter env.exported Exported.Type (fun _name stamp ->
+      match Stamps.findType env.file.stamps stamp with
+      | Some ({item = {kind = Record fields}} as t) ->
+        res :=
+          (fields
+          |> List.filter (fun f -> Utils.checkName f.fname.txt ~prefix ~exact)
+          |> Utils.filterMap (fun f ->
+                 let name = f.fname.txt in
+                 if not (Hashtbl.mem namesUsed name) then
+                   let () = Hashtbl.add namesUsed name () in
+                   Some
+                     (Completion.create name ~env ~docstring:f.docstring
+                        ?deprecated:f.deprecated
+                        ~kind:
+                          (Completion.Field
+                             (f, t.item.decl |> Shared.declToString t.name.txt)))
+                 else None))
+          @ !res
+      | _ -> ());
+  !res
+
+let findModuleInScope ~env ~moduleName ~scope =
+  let modulesTable = Hashtbl.create 10 in
+  env.QueryEnv.file.stamps
+  |> Stamps.iterModules (fun _ declared ->
+         Hashtbl.replace modulesTable
+           (declared.name.txt, declared.extentLoc |> Loc.start)
+           declared);
+  let result = ref None in
+  let processModule name loc =
+    if name = moduleName && !result = None then
+      match Hashtbl.find_opt modulesTable (name, Loc.start loc) with
+      | Some declared -> result := Some declared
+      | None ->
+        Log.log
+          (Printf.sprintf "Module Not Found %s loc:%s\n" name (Loc.toString loc))
+  in
+  scope |> Scope.iterModulesBeforeFirstOpen processModule;
+  scope |> Scope.iterModulesAfterFirstOpen processModule;
+  !result
+
+let resolvePathFromStamps ~(env : QueryEnv.t) ~package ~scope ~moduleName ~path
+    =
+  (* Log.log("Finding from stamps " ++ name); *)
+  match findModuleInScope ~env ~moduleName ~scope with
+  | None -> None
+  | Some declared -> (
+    (* Log.log("found it"); *)
+    match ResolvePath.findInModule ~env declared.item path with
+    | None -> None
+    | Some res -> (
+      match res with
+      | `Local (env, name) -> Some (env, name)
+      | `Global (moduleName, fullPath) -> (
+        match ProcessCmt.fileForModule ~package moduleName with
+        | None -> None
+        | Some file ->
+          ResolvePath.resolvePath ~env:(QueryEnv.fromFile file) ~path:fullPath
+            ~package)))
+
+let resolveModuleWithOpens ~opens ~package ~moduleName =
+  let rec loop opens =
+    match opens with
+    | (env : QueryEnv.t) :: rest -> (
+      Log.log ("Looking for env in " ^ Uri.toString env.file.uri);
+      match ResolvePath.resolvePath ~env ~package ~path:[moduleName; ""] with
+      | Some (env, _) -> Some env
+      | None -> loop rest)
+    | [] -> None
+  in
+  loop opens
+
+let resolveFileModule ~moduleName ~package =
+  Log.log ("Getting module " ^ moduleName);
+  match ProcessCmt.fileForModule ~package moduleName with
+  | None -> None
+  | Some file ->
+    Log.log "got it";
+    let env = QueryEnv.fromFile file in
+    Some env
+
+let getEnvWithOpens ~scope ~(env : QueryEnv.t) ~package
+    ~(opens : QueryEnv.t list) ~moduleName (path : string list) =
+  (* TODO: handle interleaving of opens and local modules correctly *)
+  match resolvePathFromStamps ~env ~scope ~moduleName ~path ~package with
+  | Some x -> Some x
+  | None -> (
+    match resolveModuleWithOpens ~opens ~package ~moduleName with
+    | Some env -> ResolvePath.resolvePath ~env ~package ~path
+    | None -> (
+      match resolveFileModule ~moduleName ~package with
+      | None -> None
+      | Some env -> ResolvePath.resolvePath ~env ~package ~path))
+
+let rec expandTypeExpr ~env ~package typeExpr =
+  match typeExpr |> Shared.digConstructor with
+  | Some path -> (
+    match References.digConstructor ~env ~package path with
+    | None -> None
+    | Some (env, {item = {decl = {type_manifest = Some t}}}) ->
+      expandTypeExpr ~env ~package t
+    | Some (_, {docstring; item}) -> Some (docstring, item))
+  | None -> None
+
+let kindToDocumentation ~env ~full ~currentDocstring name
+    (kind : Completion.kind) =
+  let docsFromKind =
+    match kind with
+    | ObjLabel _ | Label _ | FileModule _ | Snippet _ | FollowContextPath _ ->
+      []
+    | Module {docstring} -> docstring
+    | Type {decl; name} ->
+      [decl |> Shared.declToString name |> Markdown.codeBlock]
+    | Value typ -> (
+      match expandTypeExpr ~env ~package:full.package typ with
+      | None -> []
+      | Some (docstrings, {decl; name; kind}) ->
+        docstrings
+        @ [
+            (match kind with
+            | Record _ | Tuple _ | Variant _ ->
+              Markdown.codeBlock (Shared.declToString name decl)
+            | _ -> "");
+          ])
+    | Field ({typ; optional; docstring}, s) ->
+      (* Handle optional fields. Checking for "?" is because sometimes optional
+         fields are prefixed with "?" when completing, and at that point we don't
+         need to _also_ add a "?" after the field name, as that looks weird. *)
+      docstring
+      @ [
+          Markdown.codeBlock
+            (if optional && Utils.startsWith name "?" = false then
+               name ^ "?: "
+               ^ (typ |> Utils.unwrapIfOption |> Shared.typeToString)
+             else name ^ ": " ^ (typ |> Shared.typeToString));
+          Markdown.codeBlock s;
+        ]
+    | Constructor (c, s) ->
+      [Markdown.codeBlock (showConstructor c); Markdown.codeBlock s]
+    | PolyvariantConstructor ({displayName; args}, s) ->
+      [
+        Markdown.codeBlock
+          ("#" ^ displayName
+          ^
+          match args with
+          | [] -> ""
+          | typeExprs ->
+            "("
+            ^ (typeExprs
+              |> List.map (fun typeExpr -> typeExpr |> Shared.typeToString)
+              |> String.concat ", ")
+            ^ ")");
+        Markdown.codeBlock s;
+      ]
+    | ExtractedType (extractedType, _) ->
+      [Markdown.codeBlock (TypeUtils.extractedTypeToString extractedType)]
+  in
+  currentDocstring @ docsFromKind
+  |> List.filter (fun s -> s <> "")
+  |> String.concat "\n\n"
+
+let kindToDetail name (kind : Completion.kind) =
+  match kind with
+  | Type {name} -> "type " ^ name
+  | Value typ -> typ |> Shared.typeToString
+  | ObjLabel typ -> typ |> Shared.typeToString
+  | Label typString -> typString
+  | Module _ -> "module " ^ name
+  | FileModule f -> "module " ^ f
+  | Field ({typ; optional}, _) ->
+    (* Handle optional fields. Checking for "?" is because sometimes optional
+       fields are prefixed with "?" when completing, and at that point we don't
+       need to _also_ add a "?" after the field name, as that looks weird. *)
+    if optional && Utils.startsWith name "?" = false then
+      typ |> Utils.unwrapIfOption |> Shared.typeToString
+    else typ |> Shared.typeToString
+  | Constructor (c, _) -> showConstructor c
+  | PolyvariantConstructor ({displayName; args}, _) -> (
+    "#" ^ displayName
+    ^
+    match args with
+    | [] -> ""
+    | typeExprs ->
+      "("
+      ^ (typeExprs
+        |> List.map (fun typeExpr -> typeExpr |> Shared.typeToString)
+        |> String.concat ", ")
+      ^ ")")
+  | Snippet s -> s
+  | FollowContextPath _ -> ""
+  | ExtractedType (extractedType, _) ->
+    TypeUtils.extractedTypeToString ~nameOnly:true extractedType
+
+let kindToData filePath (kind : Completion.kind) =
+  match kind with
+  | FileModule f -> Some [("modulePath", f); ("filePath", filePath)]
+  | _ -> None
+
+let findAllCompletions ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed
+    ~(completionContext : Completable.completionContext) =
+  Log.log ("findAllCompletions uri:" ^ Uri.toString env.file.uri);
+  match completionContext with
+  | Value ->
+    completionForExportedValues ~env ~prefix ~exact ~namesUsed
+    @ completionsForExportedConstructors ~env ~prefix ~exact ~namesUsed
+    @ completionForExportedModules ~env ~prefix ~exact ~namesUsed
+  | Type ->
+    completionForExportedTypes ~env ~prefix ~exact ~namesUsed
+    @ completionForExportedModules ~env ~prefix ~exact ~namesUsed
+  | Module -> completionForExportedModules ~env ~prefix ~exact ~namesUsed
+  | Field ->
+    completionForExportedFields ~env ~prefix ~exact ~namesUsed
+    @ completionForExportedModules ~env ~prefix ~exact ~namesUsed
+  | ValueOrField ->
+    completionForExportedValues ~env ~prefix ~exact ~namesUsed
+    @ completionForExportedFields ~env ~prefix ~exact ~namesUsed
+    @ completionForExportedModules ~env ~prefix ~exact ~namesUsed
+
+let processLocalValue name loc contextPath scope ~prefix ~exact ~env
+    ~(localTables : LocalTables.t) =
+  if Utils.checkName name ~prefix ~exact then
+    match Hashtbl.find_opt localTables.valueTable (name, Loc.start loc) with
+    | Some declared ->
+      if not (Hashtbl.mem localTables.namesUsed name) then (
+        Hashtbl.add localTables.namesUsed name ();
+        localTables.resultRev <-
+          {
+            (Completion.create declared.name.txt ~env ~kind:(Value declared.item))
+            with
+            deprecated = declared.deprecated;
+            docstring = declared.docstring;
+          }
+          :: localTables.resultRev)
+    | None ->
+      if !Cfg.debugFollowCtxPath then
+        Printf.printf "Completion Value Not Found %s loc:%s\n" name
+          (Loc.toString loc);
+      localTables.resultRev <-
+        Completion.create name ~env
+          ~kind:
+            (match contextPath with
+            | Some contextPath -> FollowContextPath (contextPath, scope)
+            | None ->
+              Value
+                (Ctype.newconstr
+                   (Path.Pident (Ident.create "Type Not Known"))
+                   []))
+        :: localTables.resultRev
+
+let processLocalConstructor name loc ~prefix ~exact ~env
+    ~(localTables : LocalTables.t) =
+  if Utils.checkName name ~prefix ~exact then
+    match
+      Hashtbl.find_opt localTables.constructorTable (name, Loc.start loc)
+    with
+    | Some declared ->
+      if not (Hashtbl.mem localTables.namesUsed name) then (
+        Hashtbl.add localTables.namesUsed name ();
+        localTables.resultRev <-
+          {
+            (Completion.create declared.name.txt ~env
+               ~kind:
+                 (Constructor
+                    ( declared.item,
+                      snd declared.item.typeDecl
+                      |> Shared.declToString (fst declared.item.typeDecl) )))
+            with
+            deprecated = declared.deprecated;
+            docstring = declared.docstring;
+          }
+          :: localTables.resultRev)
+    | None ->
+      Log.log
+        (Printf.sprintf "Completion Constructor Not Found %s loc:%s\n" name
+           (Loc.toString loc))
+
+let processLocalType name loc ~prefix ~exact ~env ~(localTables : LocalTables.t)
+    =
+  if Utils.checkName name ~prefix ~exact then
+    match Hashtbl.find_opt localTables.typesTable (name, Loc.start loc) with
+    | Some declared ->
+      if not (Hashtbl.mem localTables.namesUsed name) then (
+        Hashtbl.add localTables.namesUsed name ();
+        localTables.resultRev <-
+          {
+            (Completion.create declared.name.txt ~env ~kind:(Type declared.item))
+            with
+            deprecated = declared.deprecated;
+            docstring = declared.docstring;
+          }
+          :: localTables.resultRev)
+    | None ->
+      Log.log
+        (Printf.sprintf "Completion Type Not Found %s loc:%s\n" name
+           (Loc.toString loc))
+
+let processLocalModule name loc ~prefix ~exact ~env
+    ~(localTables : LocalTables.t) =
+  if Utils.checkName name ~prefix ~exact then
+    match Hashtbl.find_opt localTables.modulesTable (name, Loc.start loc) with
+    | Some declared ->
+      if not (Hashtbl.mem localTables.namesUsed name) then (
+        Hashtbl.add localTables.namesUsed name ();
+        localTables.resultRev <-
+          {
+            (Completion.create declared.name.txt ~env
+               ~kind:
+                 (Module
+                    {docstring = declared.docstring; module_ = declared.item}))
+            with
+            deprecated = declared.deprecated;
+            docstring = declared.docstring;
+          }
+          :: localTables.resultRev)
+    | None ->
+      Log.log
+        (Printf.sprintf "Completion Module Not Found %s loc:%s\n" name
+           (Loc.toString loc))
+
+let getItemsFromOpens ~opens ~localTables ~prefix ~exact ~completionContext =
+  opens
+  |> List.fold_left
+       (fun results env ->
+         let completionsFromThisOpen =
+           findAllCompletions ~env ~prefix ~exact
+             ~namesUsed:localTables.LocalTables.namesUsed ~completionContext
+         in
+         completionsFromThisOpen @ results)
+       []
+
+let findLocalCompletionsForValuesAndConstructors ~(localTables : LocalTables.t)
+    ~env ~prefix ~exact ~opens ~scope =
+  localTables |> LocalTables.populateValues ~env;
+  localTables |> LocalTables.populateConstructors ~env;
+  localTables |> LocalTables.populateModules ~env;
+  scope
+  |> Scope.iterValuesBeforeFirstOpen
+       (processLocalValue ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterConstructorsBeforeFirstOpen
+       (processLocalConstructor ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesBeforeFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+
+  let valuesFromOpens =
+    getItemsFromOpens ~opens ~localTables ~prefix ~exact
+      ~completionContext:Value
+  in
+
+  scope
+  |> Scope.iterValuesAfterFirstOpen
+       (processLocalValue ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterConstructorsAfterFirstOpen
+       (processLocalConstructor ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesAfterFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+  List.rev_append localTables.resultRev valuesFromOpens
+
+let findLocalCompletionsForValues ~(localTables : LocalTables.t) ~env ~prefix
+    ~exact ~opens ~scope =
+  localTables |> LocalTables.populateValues ~env;
+  localTables |> LocalTables.populateModules ~env;
+  scope
+  |> Scope.iterValuesBeforeFirstOpen
+       (processLocalValue ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesBeforeFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+
+  let valuesFromOpens =
+    getItemsFromOpens ~opens ~localTables ~prefix ~exact
+      ~completionContext:Value
+  in
+
+  scope
+  |> Scope.iterValuesAfterFirstOpen
+       (processLocalValue ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesAfterFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+  List.rev_append localTables.resultRev valuesFromOpens
+
+let findLocalCompletionsForTypes ~(localTables : LocalTables.t) ~env ~prefix
+    ~exact ~opens ~scope =
+  localTables |> LocalTables.populateTypes ~env;
+  localTables |> LocalTables.populateModules ~env;
+  scope
+  |> Scope.iterTypesBeforeFirstOpen
+       (processLocalType ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesBeforeFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+
+  let valuesFromOpens =
+    getItemsFromOpens ~opens ~localTables ~prefix ~exact ~completionContext:Type
+  in
+
+  scope
+  |> Scope.iterTypesAfterFirstOpen
+       (processLocalType ~prefix ~exact ~env ~localTables);
+  scope
+  |> Scope.iterModulesAfterFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+  List.rev_append localTables.resultRev valuesFromOpens
+
+let findLocalCompletionsForModules ~(localTables : LocalTables.t) ~env ~prefix
+    ~exact ~opens ~scope =
+  localTables |> LocalTables.populateModules ~env;
+  scope
+  |> Scope.iterModulesBeforeFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+
+  let valuesFromOpens =
+    getItemsFromOpens ~opens ~localTables ~prefix ~exact
+      ~completionContext:Module
+  in
+
+  scope
+  |> Scope.iterModulesAfterFirstOpen
+       (processLocalModule ~prefix ~exact ~env ~localTables);
+  List.rev_append localTables.resultRev valuesFromOpens
+
+let findLocalCompletionsWithOpens ~pos ~(env : QueryEnv.t) ~prefix ~exact ~opens
+    ~scope ~(completionContext : Completable.completionContext) =
+  (* TODO: handle arbitrary interleaving of opens and local bindings correctly *)
+  Log.log
+    ("findLocalCompletionsWithOpens uri:" ^ Uri.toString env.file.uri ^ " pos:"
+   ^ Pos.toString pos);
+  let localTables = LocalTables.create () in
+  match completionContext with
+  | Value | ValueOrField ->
+    findLocalCompletionsForValuesAndConstructors ~localTables ~env ~prefix
+      ~exact ~opens ~scope
+  | Type ->
+    findLocalCompletionsForTypes ~localTables ~env ~prefix ~exact ~opens ~scope
+  | Module ->
+    findLocalCompletionsForModules ~localTables ~env ~prefix ~exact ~opens
+      ~scope
+  | Field ->
+    (* There's no local completion for fields *)
+    []
+
+let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix
+    =
+  let exact = false in
+  let localCompletionsWithOpens =
+    let localTables = LocalTables.create () in
+    findLocalCompletionsForValues ~localTables ~env ~prefix ~exact ~opens ~scope
+  in
+  let fileModules =
+    allFiles |> FileSet.elements
+    |> Utils.filterMap (fun name ->
+           if
+             Utils.checkName name ~prefix ~exact
+             && not
+                  (* TODO complete the namespaced name too *)
+                  (Utils.fileNameHasUnallowedChars name)
+           then
+             Some
+               (Completion.create name ~env ~kind:(Completion.FileModule name))
+           else None)
+  in
+  localCompletionsWithOpens @ fileModules
+
+let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope
+    ~completionContext ~env path =
+  if debug then Printf.printf "Path %s\n" (path |> String.concat ".");
+  let allFiles = allFilesInPackage full.package in
+  match path with
+  | [] -> []
+  | [prefix] ->
+    let localCompletionsWithOpens =
+      findLocalCompletionsWithOpens ~pos ~env ~prefix ~exact ~opens ~scope
+        ~completionContext
+    in
+    let fileModules =
+      allFiles |> FileSet.elements
+      |> Utils.filterMap (fun name ->
+             if
+               Utils.checkName name ~prefix ~exact
+               && not
+                    (* TODO complete the namespaced name too *)
+                    (Utils.fileNameHasUnallowedChars name)
+             then
+               Some
+                 (Completion.create name ~env ~kind:(Completion.FileModule name))
+             else None)
+    in
+    localCompletionsWithOpens @ fileModules
+  | moduleName :: path -> (
+    Log.log ("Path " ^ pathToString path);
+    match
+      getEnvWithOpens ~scope ~env ~package:full.package ~opens ~moduleName path
+    with
+    | Some (env, prefix) ->
+      Log.log "Got the env";
+      let namesUsed = Hashtbl.create 10 in
+      findAllCompletions ~env ~prefix ~exact ~namesUsed ~completionContext
+    | None -> [])
+
+let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
+    ~scope path =
+  match
+    path
+    |> getCompletionsForPath ~debug ~completionContext:Type ~exact:true ~opens
+         ~full ~pos ~env ~scope
+  with
+  | {kind = Type {kind = Abstract (Some (p, _))}} :: _ ->
+    (* This case happens when what we're looking for is a type alias.
+       This is the case in newer rescript-react versions where
+       ReactDOM.domProps is an alias for JsxEvent.t. *)
+    let pathRev = p |> Utils.expandPath in
+    pathRev |> List.rev
+    |> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
+         ~scope
+  | {kind = Type {kind = Record fields}} :: _ -> Some fields
+  | _ -> None
+
+let mkItem ?data name ~kind ~detail ~deprecated ~docstring =
+  let docContent =
+    (match deprecated with
+    | None -> ""
+    | Some s -> "Deprecated: " ^ s ^ "\n\n")
+    ^
+    match docstring with
+    | [] -> ""
+    | _ :: _ -> docstring |> String.concat "\n"
+  in
+  let tags =
+    match deprecated with
+    | None -> []
+    | Some _ -> [1 (* deprecated *)]
+  in
+  Protocol.
+    {
+      label = name;
+      kind;
+      tags;
+      detail;
+      documentation =
+        (if docContent = "" then None
+         else Some {kind = "markdown"; value = docContent});
+      sortText = None;
+      insertText = None;
+      insertTextFormat = None;
+      filterText = None;
+      data;
+    }
+
+let completionToItem
+    {
+      Completion.name;
+      deprecated;
+      docstring;
+      kind;
+      sortText;
+      insertText;
+      insertTextFormat;
+      filterText;
+      detail;
+      env;
+    } ~full =
+  let item =
+    mkItem name
+      ?data:(kindToData (full.file.uri |> Uri.toPath) kind)
+      ~kind:(Completion.kindToInt kind)
+      ~deprecated
+      ~detail:
+        (match detail with
+        | None -> kindToDetail name kind
+        | Some detail -> detail)
+      ~docstring:
+        (match
+           kindToDocumentation ~currentDocstring:docstring ~full ~env name kind
+         with
+        | "" -> []
+        | docstring -> [docstring])
+  in
+  {item with sortText; insertText; insertTextFormat; filterText}
+
+let completionsGetTypeEnv = function
+  | {Completion.kind = Value typ; env} :: _ -> Some (typ, env)
+  | {Completion.kind = ObjLabel typ; env} :: _ -> Some (typ, env)
+  | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env)
+  | _ -> None
+
+type getCompletionsForContextPathMode = Regular | Pipe
+
+let completionsGetCompletionType ~full = function
+  | {Completion.kind = Value typ; env} :: _
+  | {Completion.kind = ObjLabel typ; env} :: _
+  | {Completion.kind = Field ({typ}, _); env} :: _ ->
+    typ
+    |> TypeUtils.extractType ~env ~package:full.package
+    |> Option.map (fun (typ, _) -> (typ, env))
+  | {Completion.kind = Type typ; env} :: _ -> (
+    match TypeUtils.extractTypeFromResolvedType typ ~env ~full with
+    | None -> None
+    | Some extractedType -> Some (extractedType, env))
+  | {Completion.kind = ExtractedType (typ, _); env} :: _ -> Some (typ, env)
+  | _ -> None
+
+let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos =
+  function
+  | {Completion.kind = Value typ; env} :: _
+  | {Completion.kind = ObjLabel typ; env} :: _
+  | {Completion.kind = Field ({typ}, _); env} :: _ ->
+    Some (TypeExpr typ, env)
+  | {Completion.kind = FollowContextPath (ctxPath, scope); env} :: _ ->
+    ctxPath
+    |> getCompletionsForContextPath ~debug ~full ~env ~exact:true ~opens
+         ~rawOpens ~pos ~scope
+    |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos
+  | {Completion.kind = Type typ; env} :: _ -> (
+    match TypeUtils.extractTypeFromResolvedType typ ~env ~full with
+    | None -> None
+    | Some extractedType -> Some (ExtractedType extractedType, env))
+  | {Completion.kind = ExtractedType (typ, _); env} :: _ ->
+    Some (ExtractedType typ, env)
+  | _ -> None
+
+and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens
+    ~rawOpens ~pos =
+  match completions with
+  | {Completion.kind = Value typ; env} :: _ -> Some (typ, env)
+  | {Completion.kind = ObjLabel typ; env} :: _ -> Some (typ, env)
+  | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env)
+  | {Completion.kind = FollowContextPath (ctxPath, scope); env} :: _ ->
+    ctxPath
+    |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+         ~exact:true ~scope
+    |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+  | _ -> None
+
+and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
+    ~scope ?(mode = Regular) contextPath =
+  if debug then
+    Printf.printf "ContextPath %s\n"
+      (Completable.contextPathToString contextPath);
+  let package = full.package in
+  match contextPath with
+  | CPString ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPString";
+    [Completion.create "dummy" ~env ~kind:(Completion.Value Predef.type_string)]
+  | CPBool ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPBool";
+    [Completion.create "dummy" ~env ~kind:(Completion.Value Predef.type_bool)]
+  | CPInt ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPInt";
+    [Completion.create "dummy" ~env ~kind:(Completion.Value Predef.type_int)]
+  | CPFloat ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPFloat";
+    [Completion.create "dummy" ~env ~kind:(Completion.Value Predef.type_float)]
+  | CPArray None ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPArray (no payload)";
+    [
+      Completion.create "array" ~env
+        ~kind:(Completion.Value (Ctype.newconstr Predef.path_array []));
+    ]
+  | CPArray (Some cp) -> (
+    if Debug.verbose () then
+      print_endline "[ctx_path]--> CPArray (with payload)";
+    match mode with
+    | Regular -> (
+      match
+        cp
+        |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+             ~exact:true ~scope
+        |> completionsGetCompletionType ~full
+      with
+      | None -> []
+      | Some (typ, env) ->
+        [
+          Completion.create "dummy" ~env
+            ~kind:
+              (Completion.ExtractedType (Tarray (env, ExtractedType typ), `Type));
+        ])
+    | Pipe ->
+      (* Pipe completion with array just needs to know that it's an array, not
+         what inner type it has. *)
+      [
+        Completion.create "dummy" ~env
+          ~kind:(Completion.Value (Ctype.newconstr Predef.path_array []));
+      ])
+  | CPOption cp -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPOption";
+    match
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetCompletionType ~full
+    with
+    | None -> []
+    | Some (typ, env) ->
+      [
+        Completion.create "dummy" ~env
+          ~kind:
+            (Completion.ExtractedType (Toption (env, ExtractedType typ), `Type));
+      ])
+  | CPAwait cp -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPAwait";
+    match
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetCompletionType ~full
+    with
+    | Some (Tpromise (env, typ), _env) ->
+      [Completion.create "dummy" ~env ~kind:(Completion.Value typ)]
+    | _ -> [])
+  | CPId {path; completionContext; loc} ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPId";
+    (* Looks up the type of an identifier.
+
+       Because of reasons we sometimes don't get enough type
+       information when looking up identifiers where the type
+       has type parameters. This in turn means less completions.
+
+       There's a heuristic below that tries to look up the type
+       of the ID in the usual way first. But if the type found
+       still has uninstantiated type parameters, we check the
+       location for the identifier from the compiler type artifacts.
+       That type usually has the type params instantiated, if they are.
+       This leads to better completion.
+
+       However, we only do it in incremental type checking mode,
+       because more type information is always available in that mode. *)
+    let useTvarLookup = !Cfg.inIncrementalTypecheckingMode in
+    let byPath =
+      path
+      |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
+           ~completionContext ~env ~scope
+    in
+    let hasTvars =
+      if useTvarLookup then
+        match byPath with
+        | [{kind = Value typ}] when TypeUtils.hasTvar typ -> true
+        | _ -> false
+      else false
+    in
+    let result =
+      if hasTvars then
+        let byLoc = TypeUtils.findTypeViaLoc loc ~full ~debug in
+        match (byLoc, byPath) with
+        | Some t, [({kind = Value _} as item)] -> [{item with kind = Value t}]
+        | _ -> byPath
+      else byPath
+    in
+    result
+  | CPApply (cp, labels) -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPApply";
+    match
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos
+    with
+    | Some ((TypeExpr typ | ExtractedType (Tfunction {typ})), env) -> (
+      let rec reconstructFunctionType args tRet =
+        match args with
+        | [] -> tRet
+        | (label, tArg) :: rest ->
+          let restType = reconstructFunctionType rest tRet in
+          {typ with desc = Tarrow (label, tArg, restType, Cok)}
+      in
+      let rec processApply args labels =
+        match (args, labels) with
+        | _, [] -> args
+        | _, label :: (_ :: _ as nextLabels) ->
+          (* compute the application of the first label, then the next ones *)
+          let args = processApply args [label] in
+          processApply args nextLabels
+        | (Asttypes.Nolabel, _) :: nextArgs, [Asttypes.Nolabel] -> nextArgs
+        | ((Labelled _, _) as arg) :: nextArgs, [Nolabel] ->
+          arg :: processApply nextArgs labels
+        | (Optional _, _) :: nextArgs, [Nolabel] -> processApply nextArgs labels
+        | ( (((Labelled s1 | Optional s1), _) as arg) :: nextArgs,
+            [(Labelled s2 | Optional s2)] ) ->
+          if s1 = s2 then nextArgs else arg :: processApply nextArgs labels
+        | ((Nolabel, _) as arg) :: nextArgs, [(Labelled _ | Optional _)] ->
+          arg :: processApply nextArgs labels
+        | [], [(Nolabel | Labelled _ | Optional _)] ->
+          (* should not happen, but just ignore extra arguments *) []
+      in
+      match TypeUtils.extractFunctionType ~env ~package typ with
+      | args, tRet when args <> [] ->
+        let args = processApply args labels in
+        let retType = reconstructFunctionType args tRet in
+        [Completion.create "dummy" ~env ~kind:(Completion.Value retType)]
+      | _ -> [])
+    | _ -> [])
+  | CPField (CPId {path; completionContext = Module}, fieldName) ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CPField: M.field";
+    (* M.field *)
+    path @ [fieldName]
+    |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
+         ~completionContext:Field ~env ~scope
+  | CPField (cp, fieldName) -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPField";
+    let completionsForCtxPath =
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+    in
+    let extracted =
+      match
+        completionsForCtxPath
+        |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos
+      with
+      | Some (TypeExpr typ, env) -> (
+        match typ |> TypeUtils.extractRecordType ~env ~package with
+        | Some (env, fields, typDecl) ->
+          Some
+            ( env,
+              fields,
+              typDecl.item.decl |> Shared.declToString typDecl.name.txt )
+        | None -> None)
+      | Some (ExtractedType typ, env) -> (
+        match typ with
+        | Trecord {fields} ->
+          Some (env, fields, typ |> TypeUtils.extractedTypeToString)
+        | _ -> None)
+      | None -> None
+    in
+    match extracted with
+    | None -> []
+    | Some (env, fields, recordAsString) ->
+      fields
+      |> Utils.filterMap (fun field ->
+             if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then
+               Some
+                 (Completion.create field.fname.txt ~env
+                    ?deprecated:field.deprecated ~docstring:field.docstring
+                    ~kind:(Completion.Field (field, recordAsString)))
+             else None))
+  | CPObj (cp, label) -> (
+    (* TODO: Also needs to support ExtractedType *)
+    if Debug.verbose () then print_endline "[ctx_path]--> CPObj";
+    match
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+    with
+    | Some (typ, env) -> (
+      match typ |> TypeUtils.extractObjectType ~env ~package with
+      | Some (env, tObj) ->
+        let rec getFields (texp : Types.type_expr) =
+          match texp.desc with
+          | Tfield (name, _, t1, t2) ->
+            let fields = t2 |> getFields in
+            (name, t1) :: fields
+          | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFields
+          | Tvar None -> []
+          | _ -> []
+        in
+        tObj |> getFields
+        |> Utils.filterMap (fun (field, typ) ->
+               if Utils.checkName field ~prefix:label ~exact then
+                 Some
+                   (Completion.create field ~env ~kind:(Completion.ObjLabel typ))
+               else None)
+      | None -> [])
+    | None -> [])
+  | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPPipe";
+    match
+      cp
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope ~mode:Pipe
+      |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+    with
+    | None -> []
+    | Some (typ, envFromCompletionItem) -> (
+      let env, typ =
+        typ
+        |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc
+      in
+      if debug then
+        if env <> envFromCompletionItem then
+          Printf.printf "CPPipe env:%s envFromCompletionItem:%s\n"
+            (QueryEnv.toString env)
+            (QueryEnv.toString envFromCompletionItem)
+        else Printf.printf "CPPipe env:%s\n" (QueryEnv.toString env);
+      let completionPath =
+        match typ with
+        | Builtin (builtin, _) ->
+          let {
+            arrayModulePath;
+            optionModulePath;
+            stringModulePath;
+            intModulePath;
+            floatModulePath;
+            promiseModulePath;
+            listModulePath;
+            resultModulePath;
+            regexpModulePath;
+          } =
+            package.builtInCompletionModules
+          in
+          Some
+            (match builtin with
+            | Array -> arrayModulePath
+            | Option -> optionModulePath
+            | String -> stringModulePath
+            | Int -> intModulePath
+            | Float -> floatModulePath
+            | Promise -> promiseModulePath
+            | List -> listModulePath
+            | Result -> resultModulePath
+            | RegExp -> regexpModulePath
+            | Lazy -> ["Lazy"]
+            | Char -> ["Char"])
+        | TypExpr t -> (
+          match t.Types.desc with
+          | Tconstr (path, _typeArgs, _)
+          | Tlink {desc = Tconstr (path, _typeArgs, _)}
+          | Tsubst {desc = Tconstr (path, _typeArgs, _)}
+          | Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) ->
+            if debug then Printf.printf "CPPipe type path:%s\n" (Path.name path);
+            TypeUtils.getPathRelativeToEnv ~debug ~env
+              ~envFromItem:envFromCompletionItem (Utils.expandPath path)
+          | _ -> None)
+      in
+      match completionPath with
+      | Some completionPath -> (
+        let completionPathMinusOpens =
+          TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package
+            completionPath
+          |> String.concat "."
+        in
+        let completionName name =
+          if completionPathMinusOpens = "" then name
+          else completionPathMinusOpens ^ "." ^ name
+        in
+        let completions =
+          completionPath @ [funNamePrefix]
+          |> getCompletionsForPath ~debug ~completionContext:Value ~exact:false
+               ~opens ~full ~pos ~env ~scope
+        in
+        let completions =
+          completions
+          |> List.map (fun (completion : Completion.t) ->
+                 {
+                   completion with
+                   name = completionName completion.name;
+                   env
+                   (* Restore original env for the completion after x->foo()... *);
+                 })
+        in
+        (* We add React element functions to the completion if we're in a JSX context *)
+        let forJsxCompletion =
+          if inJsx then
+            match typ with
+            | Builtin (Int, t) -> Some ("int", t)
+            | Builtin (Float, t) -> Some ("float", t)
+            | Builtin (String, t) -> Some ("string", t)
+            | Builtin (Array, t) -> Some ("array", t)
+            | _ -> None
+          else None
+        in
+        match forJsxCompletion with
+        | Some (builtinNameToComplete, typ)
+          when Utils.checkName builtinNameToComplete ~prefix:funNamePrefix
+                 ~exact:false ->
+          let name =
+            match package.genericJsxModule with
+            | None -> "React." ^ builtinNameToComplete
+            | Some g ->
+              g ^ "." ^ builtinNameToComplete
+              |> String.split_on_char '.'
+              |> TypeUtils.removeOpensFromCompletionPath ~rawOpens
+                   ~package:full.package
+              |> String.concat "."
+          in
+          [
+            Completion.create name ~includesSnippets:true ~kind:(Value typ) ~env
+              ~sortText:"A"
+              ~docstring:
+                [
+                  "Turns `" ^ builtinNameToComplete
+                  ^ "` into a JSX element so it can be used inside of JSX.";
+                ];
+          ]
+          @ completions
+        | _ -> completions)
+      | None -> []))
+  | CTuple ctxPaths ->
+    if Debug.verbose () then print_endline "[ctx_path]--> CTuple";
+    (* Turn a list of context paths into a list of type expressions. *)
+    let typeExrps =
+      ctxPaths
+      |> List.map (fun contextPath ->
+             contextPath
+             |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos
+                  ~env ~exact:true ~scope)
+      |> List.filter_map (fun completionItems ->
+             match completionItems with
+             | {Completion.kind = Value typ} :: _ -> Some typ
+             | _ -> None)
+    in
+    if List.length ctxPaths = List.length typeExrps then
+      [
+        Completion.create "dummy" ~env
+          ~kind:(Completion.Value (Ctype.newty (Ttuple typeExrps)));
+      ]
+    else []
+  | CJsxPropValue {pathToComponent; propName; emptyJsxPropNameHint} -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CJsxPropValue";
+    let findTypeOfValue path =
+      path
+      |> getCompletionsForPath ~debug ~completionContext:Value ~exact:true
+           ~opens ~full ~pos ~env ~scope
+      |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+    in
+    let lowercaseComponent =
+      match pathToComponent with
+      | [elName] when Char.lowercase_ascii elName.[0] = elName.[0] -> true
+      | _ -> false
+    in
+    (* TODO(env-stuff) Does this need to potentially be instantiated with type args too? *)
+    let labels =
+      if lowercaseComponent then
+        let rec digToTypeForCompletion path =
+          match
+            path
+            |> getCompletionsForPath ~debug ~completionContext:Type ~exact:true
+                 ~opens ~full ~pos ~env ~scope
+          with
+          | {kind = Type {kind = Abstract (Some (p, _))}} :: _ ->
+            (* This case happens when what we're looking for is a type alias.
+               This is the case in newer rescript-react versions where
+               ReactDOM.domProps is an alias for JsxEvent.t. *)
+            let pathRev = p |> Utils.expandPath in
+            pathRev |> List.rev |> digToTypeForCompletion
+          | {kind = Type {kind = Record fields}} :: _ ->
+            fields |> List.map (fun f -> (f.fname.txt, f.typ, env))
+          | _ -> []
+        in
+        TypeUtils.pathToElementProps package |> digToTypeForCompletion
+      else
+        CompletionJsx.getJsxLabels ~componentPath:pathToComponent
+          ~findTypeOfValue ~package
+    in
+    (* We have a heuristic that kicks in when completing empty prop expressions in the middle of a JSX element,
+       like <SomeComp firstProp=test second=<com> third=123 />.
+       The parser turns that broken JSX into: <SomeComp firstProp=test second=<com>third />, 123.
+
+       So, we use a heuristic that covers this scenario by picking up on the cursor being between
+       the prop name and the prop expression, and the prop expression being an ident that's a
+       _valid prop name_ for that JSX element.
+
+       This works because the ident itself will always be the next prop name (since that's what the
+       parser eats). So, we do a simple lookup of that hint here if it exists, to make sure the hint
+       is indeed a valid label for this JSX element. *)
+    let emptyJsxPropNameHintIsCorrect =
+      match emptyJsxPropNameHint with
+      | Some identName when identName != propName ->
+        labels
+        |> List.find_opt (fun (f, _, _) -> f = identName)
+        |> Option.is_some
+      | Some _ -> false
+      | None -> true
+    in
+    let targetLabel =
+      if emptyJsxPropNameHintIsCorrect then
+        labels |> List.find_opt (fun (f, _, _) -> f = propName)
+      else None
+    in
+    match targetLabel with
+    | None -> []
+    | Some (_, typ, env) ->
+      [
+        Completion.create "dummy" ~env
+          ~kind:(Completion.Value (Utils.unwrapIfOption typ));
+      ])
+  | CArgument {functionContextPath; argumentLabel} -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CArgument";
+    if Debug.verbose () then
+      Printf.printf "--> function argument: %s\n"
+        (match argumentLabel with
+        | Labelled n | Optional n -> n
+        | Unlabelled {argumentPosition} -> "$" ^ string_of_int argumentPosition);
+
+    let labels, env =
+      match
+        functionContextPath
+        |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+             ~exact:true ~scope
+        |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos
+      with
+      | Some ((TypeExpr typ | ExtractedType (Tfunction {typ})), env) ->
+        if Debug.verbose () then print_endline "--> found function type";
+        (typ |> TypeUtils.getArgs ~full ~env, env)
+      | _ ->
+        if Debug.verbose () then
+          print_endline "--> could not find function type";
+        ([], env)
+    in
+    let targetLabel =
+      labels
+      |> List.find_opt (fun (label, _) ->
+             match (argumentLabel, label) with
+             | ( Unlabelled {argumentPosition = pos1},
+                 Completable.Unlabelled {argumentPosition = pos2} ) ->
+               pos1 = pos2
+             | ( (Labelled name1 | Optional name1),
+                 (Labelled name2 | Optional name2) ) ->
+               name1 = name2
+             | _ -> false)
+    in
+    let expandOption =
+      match targetLabel with
+      | None | Some ((Unlabelled _ | Labelled _), _) -> false
+      | Some (Optional _, _) -> true
+    in
+    match targetLabel with
+    | None ->
+      if Debug.verbose () then
+        print_endline "--> could not look up function argument";
+      []
+    | Some (_, typ) ->
+      if Debug.verbose () then print_endline "--> found function argument!";
+      [
+        Completion.create "dummy" ~env
+          ~kind:
+            (Completion.Value
+               (if expandOption then Utils.unwrapIfOption typ else typ));
+      ])
+  | CPatternPath {rootCtxPath; nested} -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CPatternPath";
+    (* TODO(env-stuff) Get rid of innerType etc *)
+    match
+      rootCtxPath
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos
+    with
+    | Some (typ, env) -> (
+      match typ |> TypeUtils.resolveNestedPatternPath ~env ~full ~nested with
+      | Some (typ, env) ->
+        [Completion.create "dummy" ~env ~kind:(kindFromInnerType typ)]
+      | None -> [])
+    | None -> [])
+  | CTypeAtPos loc -> (
+    if Debug.verbose () then print_endline "[ctx_path]--> CTypeAtPos";
+    match TypeUtils.findTypeViaLoc loc ~full ~debug with
+    | None -> []
+    | Some typExpr -> [Completion.create "dummy" ~env ~kind:(Value typExpr)])
+
+let getOpens ~debug ~rawOpens ~package ~env =
+  if debug && rawOpens <> [] then
+    Printf.printf "%s\n"
+      ("Raw opens: "
+      ^ string_of_int (List.length rawOpens)
+      ^ " "
+      ^ String.concat " ... " (rawOpens |> List.map pathToString));
+  let packageOpens = package.opens in
+  if debug && packageOpens <> [] then
+    Printf.printf "%s\n"
+      ("Package opens "
+      ^ String.concat " " (packageOpens |> List.map (fun p -> p |> pathToString))
+      );
+  let resolvedOpens =
+    resolveOpens ~env (List.rev (rawOpens @ packageOpens)) ~package
+  in
+  if debug && resolvedOpens <> [] then
+    Printf.printf "%s\n"
+      ("Resolved opens "
+      ^ string_of_int (List.length resolvedOpens)
+      ^ " "
+      ^ String.concat " "
+          (resolvedOpens |> List.map (fun (e : QueryEnv.t) -> e.file.moduleName))
+      );
+  (* Last open takes priority *)
+  List.rev resolvedOpens
+
+let filterItems items ~prefix =
+  if prefix = "" then items
+  else
+    items
+    |> List.filter (fun (item : Completion.t) ->
+           Utils.startsWith item.name prefix)
+
+type completionMode = Pattern of Completable.patternMode | Expression
+
+let emptyCase ~mode num =
+  match mode with
+  | Expression -> "$" ^ string_of_int (num - 1)
+  | Pattern _ -> "${" ^ string_of_int num ^ ":_}"
+
+let printConstructorArgs ~mode ~asSnippet argsLen =
+  let args = ref [] in
+  for argNum = 1 to argsLen do
+    args :=
+      !args
+      @ [
+          (match (asSnippet, argsLen) with
+          | true, l when l > 1 -> Printf.sprintf "${%i:_}" argNum
+          | true, l when l > 0 -> emptyCase ~mode argNum
+          | _ -> "_");
+        ]
+  done;
+  if List.length !args > 0 then "(" ^ (!args |> String.concat ", ") ^ ")"
+  else ""
+
+let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens
+    ~full ~prefix ~completionContext ~mode (t : SharedTypes.completionType) =
+  let emptyCase = emptyCase ~mode in
+  let printConstructorArgs = printConstructorArgs ~mode in
+  let create = Completion.create ?typeArgContext in
+  match t with
+  | TtypeT {env; path} when mode = Expression ->
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> TtypeT (Expression)";
+    (* Find all values in the module with type t *)
+    let valueWithTypeT t =
+      match t.Types.desc with
+      | Tconstr (Pident {name = "t"}, [], _) -> true
+      | _ -> false
+    in
+    (* Find all functions in the module that returns type t *)
+    let rec fnReturnsTypeT t =
+      match t.Types.desc with
+      | Tlink t1
+      | Tsubst t1
+      | Tpoly (t1, [])
+      | Tconstr (Pident {name = "function$"}, [t1; _], _) ->
+        fnReturnsTypeT t1
+      | Tarrow _ -> (
+        match TypeUtils.extractFunctionType ~env ~package:full.package t with
+        | ( (Nolabel, {desc = Tconstr (Path.Pident {name = "t"}, _, _)}) :: _,
+            {desc = Tconstr (Path.Pident {name = "t"}, _, _)} ) ->
+          (* Filter out functions that take type t first. These are often
+             @send style functions that we don't want to have here because
+             they usually aren't meant to create a type t from scratch. *)
+          false
+        | _args, {desc = Tconstr (Path.Pident {name = "t"}, _, _)} -> true
+        | _ -> false)
+      | _ -> false
+    in
+    let getCompletionName exportedValueName =
+      let fnNname =
+        TypeUtils.getPathRelativeToEnv ~debug:false
+          ~env:(QueryEnv.fromFile full.file)
+          ~envFromItem:env (Utils.expandPath path)
+      in
+      match fnNname with
+      | None -> None
+      | Some base ->
+        let base =
+          TypeUtils.removeOpensFromCompletionPath ~rawOpens
+            ~package:full.package base
+        in
+        Some ((base |> String.concat ".") ^ "." ^ exportedValueName)
+    in
+    let getExportedValueCompletion name (declared : Types.type_expr Declared.t)
+        =
+      let typeExpr = declared.item in
+      if valueWithTypeT typeExpr then
+        getCompletionName name
+        |> Option.map (fun name ->
+               create name ~includesSnippets:true ~insertText:name
+                 ~kind:(Value typeExpr) ~env)
+      else if fnReturnsTypeT typeExpr then
+        getCompletionName name
+        |> Option.map (fun name ->
+               create
+                 (Printf.sprintf "%s()" name)
+                 ~includesSnippets:true ~insertText:(name ^ "($0)")
+                 ~kind:(Value typeExpr) ~env)
+      else None
+    in
+    let completionItems =
+      Hashtbl.fold
+        (fun name stamp all ->
+          match Stamps.findValue env.file.stamps stamp with
+          | None -> all
+          | Some declaredTypeExpr -> (
+            match getExportedValueCompletion name declaredTypeExpr with
+            | None -> all
+            | Some completion -> completion :: all))
+        env.exported.values_ []
+    in
+
+    (* Special casing for things where we want extra things in the completions *)
+    let completionItems =
+      match path with
+      | Pdot (Pdot (Pident m, "Re", _), "t", _) when Ident.name m = "Js" ->
+        (* regexps *)
+        create "%re()" ~insertText:"%re(\"/$0/g\")" ~includesSnippets:true
+          ~kind:(Label "Regular expression") ~env
+        :: completionItems
+      | _ -> completionItems
+    in
+    completionItems
+  | Tbool env ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tbool";
+    [
+      create "true" ~kind:(Label "bool") ~env;
+      create "false" ~kind:(Label "bool") ~env;
+    ]
+    |> filterItems ~prefix
+  | TtypeT {env; path} ->
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> TtypeT (Pattern)";
+    (* This is in patterns. Emit an alias/binding with the module name as a value name. *)
+    if prefix <> "" then []
+    else
+      let moduleName =
+        match path |> Utils.expandPath with
+        | _t :: moduleName :: _rest -> String.uncapitalize_ascii moduleName
+        | _ -> "value"
+      in
+      [
+        create moduleName ~kind:(Label moduleName) ~env
+          ~insertText:("${0:" ^ moduleName ^ "}")
+          ~includesSnippets:true;
+      ]
+  | Tvariant {env; constructors; variantDecl; variantName} ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tvariant";
+    constructors
+    |> List.map (fun (constructor : Constructor.t) ->
+           let numArgs =
+             match constructor.args with
+             | InlineRecord _ -> 1
+             | Args args -> List.length args
+           in
+           create ?deprecated:constructor.deprecated ~includesSnippets:true
+             (constructor.cname.txt
+             ^ printConstructorArgs numArgs ~asSnippet:false)
+             ~insertText:
+               (constructor.cname.txt
+               ^ printConstructorArgs numArgs ~asSnippet:true)
+             ~kind:
+               (Constructor
+                  (constructor, variantDecl |> Shared.declToString variantName))
+             ~env)
+    |> filterItems ~prefix
+  | Tpolyvariant {env; constructors; typeExpr} ->
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> Tpolyvariant";
+    constructors
+    |> List.map (fun (constructor : polyVariantConstructor) ->
+           create
+             ("#" ^ constructor.displayName
+             ^ printConstructorArgs
+                 (List.length constructor.args)
+                 ~asSnippet:false)
+             ~includesSnippets:true
+             ~insertText:
+               ((if Utils.startsWith prefix "#" then "" else "#")
+               ^ constructor.displayName
+               ^ printConstructorArgs
+                   (List.length constructor.args)
+                   ~asSnippet:true)
+             ~kind:
+               (PolyvariantConstructor
+                  (constructor, typeExpr |> Shared.typeToString))
+             ~env)
+    |> filterItems
+         ~prefix:(if Utils.startsWith prefix "#" then prefix else "#" ^ prefix)
+  | Toption (env, t) ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Toption";
+    let innerType =
+      match t with
+      | ExtractedType t -> Some (t, None)
+      | TypeExpr t -> t |> TypeUtils.extractType ~env ~package:full.package
+    in
+    let expandedCompletions =
+      match innerType with
+      | None -> []
+      | Some (innerType, _typeArgsContext) ->
+        innerType
+        |> completeTypedValue ~rawOpens ~full ~prefix ~completionContext ~mode
+        |> List.map (fun (c : Completion.t) ->
+               {
+                 c with
+                 name = "Some(" ^ c.name ^ ")";
+                 sortText = None;
+                 insertText =
+                   (match c.insertText with
+                   | None -> None
+                   | Some insertText -> Some ("Some(" ^ insertText ^ ")"));
+               })
+    in
+    let noneCase = Completion.create "None" ~kind:(kindFromInnerType t) ~env in
+    let someAnyCase =
+      create "Some(_)" ~includesSnippets:true ~kind:(kindFromInnerType t) ~env
+        ~insertText:(Printf.sprintf "Some(%s)" (emptyCase 1))
+    in
+    let completions =
+      match completionContext with
+      | Some (Completable.CameFromRecordField fieldName) ->
+        [
+          create
+            ("Some(" ^ fieldName ^ ")")
+            ~includesSnippets:true ~kind:(kindFromInnerType t) ~env
+            ~insertText:("Some(" ^ fieldName ^ ")$0");
+          someAnyCase;
+          noneCase;
+        ]
+      | _ -> [noneCase; someAnyCase]
+    in
+    completions @ expandedCompletions |> filterItems ~prefix
+  | Tresult {env; okType; errorType} ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tresult";
+    let okInnerType =
+      okType |> TypeUtils.extractType ~env ~package:full.package
+    in
+    let errorInnerType =
+      errorType |> TypeUtils.extractType ~env ~package:full.package
+    in
+    let expandedOkCompletions =
+      match okInnerType with
+      | None -> []
+      | Some (innerType, _) ->
+        innerType
+        |> completeTypedValue ~rawOpens ~full ~prefix ~completionContext ~mode
+        |> List.map (fun (c : Completion.t) ->
+               {
+                 c with
+                 name = "Ok(" ^ c.name ^ ")";
+                 sortText = None;
+                 insertText =
+                   (match c.insertText with
+                   | None -> None
+                   | Some insertText -> Some ("Ok(" ^ insertText ^ ")"));
+               })
+    in
+    let expandedErrorCompletions =
+      match errorInnerType with
+      | None -> []
+      | Some (innerType, _) ->
+        innerType
+        |> completeTypedValue ~rawOpens ~full ~prefix ~completionContext ~mode
+        |> List.map (fun (c : Completion.t) ->
+               {
+                 c with
+                 name = "Error(" ^ c.name ^ ")";
+                 sortText = None;
+                 insertText =
+                   (match c.insertText with
+                   | None -> None
+                   | Some insertText -> Some ("Error(" ^ insertText ^ ")"));
+               })
+    in
+    let okAnyCase =
+      create "Ok(_)" ~includesSnippets:true ~kind:(Value okType) ~env
+        ~insertText:(Printf.sprintf "Ok(%s)" (emptyCase 1))
+    in
+    let errorAnyCase =
+      create "Error(_)" ~includesSnippets:true ~kind:(Value errorType) ~env
+        ~insertText:(Printf.sprintf "Error(%s)" (emptyCase 1))
+    in
+    let completions =
+      match completionContext with
+      | Some (Completable.CameFromRecordField fieldName) ->
+        [
+          create
+            ("Ok(" ^ fieldName ^ ")")
+            ~includesSnippets:true ~kind:(Value okType) ~env
+            ~insertText:("Ok(" ^ fieldName ^ ")$0");
+          okAnyCase;
+          errorAnyCase;
+        ]
+      | _ -> [okAnyCase; errorAnyCase]
+    in
+    completions @ expandedOkCompletions @ expandedErrorCompletions
+    |> filterItems ~prefix
+  | Tuple (env, exprs, typ) ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tuple";
+    let numExprs = List.length exprs in
+    [
+      create
+        (printConstructorArgs numExprs ~asSnippet:false)
+        ~includesSnippets:true
+        ~insertText:(printConstructorArgs numExprs ~asSnippet:true)
+        ~kind:(Value typ) ~env;
+    ]
+  | Trecord {env; fields} as extractedType -> (
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Trecord";
+    (* As we're completing for a record, we'll need a hint (completionContext)
+       here to figure out whether we should complete for a record field, or
+       the record body itself. *)
+    match completionContext with
+    | Some (Completable.RecordField {seenFields}) ->
+      fields
+      |> List.filter (fun (field : field) ->
+             List.mem field.fname.txt seenFields = false)
+      |> List.map (fun (field : field) ->
+             match (field.optional, mode) with
+             | true, Pattern Destructuring ->
+               create ("?" ^ field.fname.txt) ?deprecated:field.deprecated
+                 ~docstring:
+                   [
+                     field.fname.txt
+                     ^ " is an optional field, and needs to be destructured \
+                        using '?'.";
+                   ]
+                 ~kind:
+                   (Field (field, TypeUtils.extractedTypeToString extractedType))
+                 ~env
+             | _ ->
+               create field.fname.txt ?deprecated:field.deprecated
+                 ~kind:
+                   (Field (field, TypeUtils.extractedTypeToString extractedType))
+                 ~env)
+      |> filterItems ~prefix
+    | _ ->
+      if prefix = "" then
+        [
+          create "{}" ~includesSnippets:true ~insertText:"{$0}" ~sortText:"A"
+            ~kind:
+              (ExtractedType
+                 ( extractedType,
+                   match mode with
+                   | Pattern _ -> `Type
+                   | Expression -> `Value ))
+            ~env;
+        ]
+      else [])
+  | TinlineRecord {env; fields} -> (
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> TinlineRecord";
+    match completionContext with
+    | Some (Completable.RecordField {seenFields}) ->
+      fields
+      |> List.filter (fun (field : field) ->
+             List.mem field.fname.txt seenFields = false)
+      |> List.map (fun (field : field) ->
+             create field.fname.txt ~kind:(Label "Inline record")
+               ?deprecated:field.deprecated ~env)
+      |> filterItems ~prefix
+    | _ ->
+      if prefix = "" then
+        [
+          create "{}" ~includesSnippets:true ~insertText:"{$0}" ~sortText:"A"
+            ~kind:(Label "Inline record") ~env;
+        ]
+      else [])
+  | Tarray (env, typ) ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tarray";
+    if prefix = "" then
+      [
+        create "[]" ~includesSnippets:true ~insertText:"[$0]" ~sortText:"A"
+          ~kind:
+            (match typ with
+            | ExtractedType typ ->
+              ExtractedType
+                ( typ,
+                  match mode with
+                  | Pattern _ -> `Type
+                  | Expression -> `Value )
+            | TypeExpr typ -> Value typ)
+          ~env;
+      ]
+    else []
+  | Tstring env ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tstring";
+    if prefix = "" then
+      [
+        create "\"\"" ~includesSnippets:true ~insertText:"\"$0\"" ~sortText:"A"
+          ~kind:(Value Predef.type_string) ~env;
+      ]
+    else []
+  | Tfunction {env; typ; args; returnType} when prefix = "" && mode = Expression
+    ->
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> Tfunction #1";
+    let shouldPrintAsUncurried = false in
+    let mkFnArgs ~asSnippet =
+      match args with
+      | [(Nolabel, argTyp)] when TypeUtils.typeIsUnit argTyp ->
+        if shouldPrintAsUncurried then "(. )" else "()"
+      | [(Nolabel, argTyp)] ->
+        let varName =
+          CompletionExpressions.prettyPrintFnTemplateArgName ~env ~full argTyp
+        in
+        let argsText = if asSnippet then "${1:" ^ varName ^ "}" else varName in
+        if shouldPrintAsUncurried then "(. " ^ argsText ^ ")" else argsText
+      | _ ->
+        let currentUnlabelledIndex = ref 0 in
+        let argsText =
+          args
+          |> List.map (fun ((label, typ) : typedFnArg) ->
+                 match label with
+                 | Optional name -> "~" ^ name ^ "=?"
+                 | Labelled name -> "~" ^ name
+                 | Nolabel ->
+                   if TypeUtils.typeIsUnit typ then "()"
+                   else (
+                     currentUnlabelledIndex := !currentUnlabelledIndex + 1;
+                     let num = !currentUnlabelledIndex in
+                     let varName =
+                       CompletionExpressions.prettyPrintFnTemplateArgName
+                         ~currentIndex:num ~env ~full typ
+                     in
+                     if asSnippet then
+                       "${" ^ string_of_int num ^ ":" ^ varName ^ "}"
+                     else varName))
+          |> String.concat ", "
+        in
+        "(" ^ if shouldPrintAsUncurried then ". " else "" ^ argsText ^ ")"
+    in
+    let isAsync =
+      match TypeUtils.extractType ~env ~package:full.package returnType with
+      | Some (Tpromise _, _) -> true
+      | _ -> false
+    in
+    let asyncPrefix = if isAsync then "async " else "" in
+    let functionBody, functionBodyInsertText =
+      match args with
+      | [(Nolabel, argTyp)] ->
+        let varName =
+          CompletionExpressions.prettyPrintFnTemplateArgName ~env ~full argTyp
+        in
+        ( (" => " ^ if varName = "()" then "{}" else varName),
+          " => ${0:" ^ varName ^ "}" )
+      | _ -> (" => {}", " => {${0:()}}")
+    in
+    [
+      create
+        (asyncPrefix ^ mkFnArgs ~asSnippet:false ^ functionBody)
+        ~includesSnippets:true
+        ~insertText:
+          (asyncPrefix ^ mkFnArgs ~asSnippet:true ^ functionBodyInsertText)
+        ~sortText:"A" ~kind:(Value typ) ~env;
+    ]
+  | Tfunction _ ->
+    if Debug.verbose () then
+      print_endline "[complete_typed_value]--> Tfunction #other";
+    []
+  | Texn env ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Texn";
+    [
+      create
+        (full.package.builtInCompletionModules.exnModulePath @ ["Error(error)"]
+        |> ident)
+        ~kind:(Label "Catches errors from JavaScript errors.")
+        ~docstring:
+          [
+            "Matches on a JavaScript error. Read more in the [documentation on \
+             catching JS \
+             exceptions](https://rescript-lang.org/docs/manual/latest/exception#catching-js-exceptions).";
+          ]
+        ~env;
+    ]
+  | Tpromise _ ->
+    if Debug.verbose () then print_endline "[complete_typed_value]--> Tpromise";
+    []
+
+module StringSet = Set.Make (String)
+
+let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable =
+  if debug then
+    Printf.printf "Completable: %s\n" (Completable.toString completable);
+  let package = full.package in
+  let rawOpens = Scope.getRawOpens scope in
+  let opens = getOpens ~debug ~rawOpens ~package ~env in
+  let allFiles = allFilesInPackage package in
+  let findTypeOfValue path =
+    path
+    |> getCompletionsForPath ~debug ~completionContext:Value ~exact:true ~opens
+         ~full ~pos ~env ~scope
+    |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+  in
+  match completable with
+  | Cnone -> []
+  | Cpath contextPath ->
+    contextPath
+    |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+         ~exact:forHover ~scope
+  | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> (
+    (* Lowercase JSX tag means builtin *)
+    let mkLabel (name, typString) =
+      Completion.create name ~kind:(Label typString) ~env
+    in
+    let keyLabels =
+      if Utils.startsWith "key" prefix then [mkLabel ("key", "string")] else []
+    in
+    (* We always try to look up completion from the actual domProps type first.
+       This works in JSXv4. For JSXv3, we have a backup hardcoded list of dom
+       labels we can use for completion. *)
+    let pathToElementProps = TypeUtils.pathToElementProps package in
+    if Debug.verbose () then
+      Printf.printf
+        "[completing-lowercase-jsx] Attempting to complete from type at %s\n"
+        (pathToElementProps |> String.concat ".");
+    let fromElementProps =
+      match
+        pathToElementProps
+        |> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
+             ~scope
+      with
+      | None -> None
+      | Some fields ->
+        Some
+          (fields
+          |> List.filter_map (fun (f : field) ->
+                 if
+                   Utils.startsWith f.fname.txt prefix
+                   && (forHover || not (List.mem f.fname.txt identsSeen))
+                 then
+                   Some
+                     ( f.fname.txt,
+                       Shared.typeToString (Utils.unwrapIfOption f.typ) )
+                 else None)
+          |> List.map mkLabel)
+    in
+    match fromElementProps with
+    | Some elementProps -> elementProps
+    | None ->
+      if debug then
+        Printf.printf
+          "[completing-lowercase-jsx] could not find element props to complete \
+           from.\n";
+      (CompletionJsx.domLabels
+      |> List.filter (fun (name, _t) ->
+             Utils.startsWith name prefix
+             && (forHover || not (List.mem name identsSeen)))
+      |> List.map mkLabel)
+      @ keyLabels)
+  | Cjsx (componentPath, prefix, identsSeen) ->
+    let labels =
+      CompletionJsx.getJsxLabels ~componentPath ~findTypeOfValue ~package
+    in
+    let mkLabel_ name typString =
+      Completion.create name ~kind:(Label typString) ~env
+    in
+    let mkLabel (name, typ, _env) =
+      mkLabel_ name (typ |> Shared.typeToString)
+    in
+    let keyLabels =
+      if Utils.startsWith "key" prefix then [mkLabel_ "key" "string"] else []
+    in
+    if labels = [] then []
+    else
+      (labels
+      |> List.filter (fun (name, _t, _env) ->
+             Utils.startsWith name prefix
+             && name <> "key"
+             && (forHover || not (List.mem name identsSeen)))
+      |> List.map mkLabel)
+      @ keyLabels
+  | CdecoratorPayload (JsxConfig {prefix; nested}) -> (
+    let mkField ~name ~primitive =
+      {
+        stamp = -1;
+        fname = {loc = Location.none; txt = name};
+        optional = true;
+        typ = Ctype.newconstr primitive [];
+        docstring = [];
+        deprecated = None;
+      }
+    in
+    let typ : completionType =
+      Trecord
+        {
+          env;
+          definition = `NameOnly "jsxConfig";
+          fields =
+            [
+              mkField ~name:"version" ~primitive:Predef.path_int;
+              mkField ~name:"module_" ~primitive:Predef.path_string;
+              mkField ~name:"mode" ~primitive:Predef.path_string;
+            ];
+        }
+    in
+    match typ |> TypeUtils.resolveNested ~env ~full ~nested with
+    | None -> []
+    | Some (typ, _env, completionContext, typeArgContext) ->
+      typ
+      |> completeTypedValue ?typeArgContext ~rawOpens ~mode:Expression ~full
+           ~prefix ~completionContext)
+  | CdecoratorPayload (ModuleWithImportAttributes {prefix; nested}) -> (
+    let mkField ~name ~primitive =
+      {
+        stamp = -1;
+        fname = {loc = Location.none; txt = name};
+        optional = true;
+        typ = Ctype.newconstr primitive [];
+        docstring = [];
+        deprecated = None;
+      }
+    in
+    let importAttributesConfig : completionType =
+      Trecord
+        {
+          env;
+          definition = `NameOnly "importAttributesConfig";
+          fields = [mkField ~name:"type_" ~primitive:Predef.path_string];
+        }
+    in
+    let rootConfig : completionType =
+      Trecord
+        {
+          env;
+          definition = `NameOnly "moduleConfig";
+          fields =
+            [
+              mkField ~name:"from" ~primitive:Predef.path_string;
+              mkField ~name:"with" ~primitive:Predef.path_string;
+            ];
+        }
+    in
+    let nested, typ =
+      match nested with
+      | NFollowRecordField {fieldName = "with"} :: rest ->
+        (rest, importAttributesConfig)
+      | _ -> (nested, rootConfig)
+    in
+    match typ |> TypeUtils.resolveNested ~env ~full ~nested with
+    | None -> []
+    | Some (typ, _env, completionContext, typeArgContext) ->
+      typ
+      |> completeTypedValue ?typeArgContext ~rawOpens ~mode:Expression ~full
+           ~prefix ~completionContext)
+  | CdecoratorPayload (Module prefix) ->
+    let packageJsonPath =
+      Utils.findPackageJson (full.package.rootPath |> Uri.fromPath)
+    in
+    let itemsFromPackageJson =
+      match packageJsonPath with
+      | None ->
+        if debug then
+          Printf.printf
+            "Did not find package.json, started looking (going upwards) from: %s\n"
+            full.package.rootPath;
+        []
+      | Some path -> (
+        match Files.readFile path with
+        | None ->
+          if debug then print_endline "Could not read package.json";
+          []
+        | Some s -> (
+          match Json.parse s with
+          | Some (Object items) ->
+            items
+            |> List.filter_map (fun (key, t) ->
+                   match (key, t) with
+                   | ("dependencies" | "devDependencies"), Json.Object o ->
+                     Some
+                       (o
+                       |> List.filter_map (fun (pkgName, _) ->
+                              match pkgName with
+                              | "rescript" -> None
+                              | pkgName -> Some pkgName))
+                   | _ -> None)
+            |> List.flatten
+          | _ ->
+            if debug then print_endline "Could not parse package.json";
+            []))
+    in
+    (* TODO: Resolve relatives? *)
+    let localItems =
+      try
+        let files =
+          Sys.readdir (Filename.dirname (env.file.uri |> Uri.toPath))
+          |> Array.to_list
+        in
+        (* Try to filter out compiled in source files *)
+        let resFiles =
+          StringSet.of_list
+            (files
+            |> List.filter_map (fun f ->
+                   if Filename.extension f = ".res" then
+                     Some (try Filename.chop_extension f with _ -> f)
+                   else None))
+        in
+        files
+        |> List.filter_map (fun fileName ->
+               let withoutExtension =
+                 try Filename.chop_extension fileName with _ -> fileName
+               in
+               if
+                 String.ends_with fileName ~suffix:package.suffix
+                 && resFiles |> StringSet.mem withoutExtension
+               then None
+               else
+                 match Filename.extension fileName with
+                 | ".res" | ".resi" | "" -> None
+                 | _ -> Some ("./" ^ fileName))
+        |> List.sort String.compare
+      with _ ->
+        if debug then print_endline "Could not read relative directory";
+        []
+    in
+    let items = itemsFromPackageJson @ localItems in
+    items
+    |> List.filter (fun name -> Utils.startsWith name prefix)
+    |> List.map (fun name ->
+           let isLocal = Utils.startsWith name "./" in
+           Completion.create name
+             ~kind:(Label (if isLocal then "Local file" else "Package"))
+             ~env)
+  | Cdecorator prefix ->
+    let mkDecorator (name, docstring, maybeInsertText) =
+      {
+        (Completion.create name ~includesSnippets:true ~kind:(Label "") ~env
+           ?insertText:maybeInsertText)
+        with
+        docstring;
+      }
+    in
+    let isTopLevel = String.starts_with ~prefix:"@" prefix in
+    let prefix =
+      if isTopLevel then String.sub prefix 1 (String.length prefix - 1)
+      else prefix
+    in
+    let decorators =
+      if isTopLevel then CompletionDecorators.toplevel
+      else CompletionDecorators.local
+    in
+    decorators
+    |> List.filter (fun (decorator, _, _) -> Utils.startsWith decorator prefix)
+    |> List.map (fun (decorator, maybeInsertText, doc) ->
+           let parts = String.split_on_char '.' prefix in
+           let len = String.length prefix in
+           let dec2 =
+             if List.length parts > 1 then
+               String.sub decorator len (String.length decorator - len)
+             else decorator
+           in
+           (dec2, doc, maybeInsertText))
+    |> List.map mkDecorator
+  | CnamedArg (cp, prefix, identsSeen) ->
+    let labels =
+      match
+        cp
+        |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+             ~exact:true ~scope
+        |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+      with
+      | Some (typ, _env) ->
+        if debug then
+          Printf.printf "Found type for function %s\n"
+            (typ |> Shared.typeToString);
+
+        typ
+        |> TypeUtils.getArgs ~full ~env
+        |> List.filter_map (fun arg ->
+               match arg with
+               | SharedTypes.Completable.Labelled name, a -> Some (name, a)
+               | Optional name, a -> Some (name, a)
+               | _ -> None)
+      | None -> []
+    in
+    let mkLabel (name, typ) =
+      Completion.create name ~kind:(Label (typ |> Shared.typeToString)) ~env
+    in
+    labels
+    |> List.filter (fun (name, _t) ->
+           Utils.startsWith name prefix
+           && (forHover || not (List.mem name identsSeen)))
+    |> List.map mkLabel
+  | Cpattern {contextPath; prefix; nested; fallback; patternMode} -> (
+    let fallbackOrEmpty ?items () =
+      match (fallback, items) with
+      | Some fallback, (None | Some []) ->
+        fallback |> processCompletable ~debug ~full ~scope ~env ~pos ~forHover
+      | _, Some items -> items
+      | None, None -> []
+    in
+    match
+      contextPath
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
+    with
+    | Some (typ, env) -> (
+      match
+        typ
+        |> TypeUtils.extractType ~env ~package:full.package
+        |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+               typ |> TypeUtils.resolveNested ?typeArgContext ~env ~full ~nested)
+      with
+      | None -> fallbackOrEmpty ()
+      | Some (typ, _env, completionContext, typeArgContext) ->
+        let items =
+          typ
+          |> completeTypedValue ?typeArgContext ~rawOpens
+               ~mode:(Pattern patternMode) ~full ~prefix ~completionContext
+        in
+        fallbackOrEmpty ~items ())
+    | None -> fallbackOrEmpty ())
+  | Cexpression {contextPath; prefix; nested} -> (
+    let isAmbigiousRecordBodyOrJsxWrap =
+      match (contextPath, nested) with
+      | CJsxPropValue _, [NRecordBody _] -> true
+      | _ -> false
+    in
+    if Debug.verbose () then
+      (* This happens in this scenario: `<SomeComponent someProp={<com>}`
+           Here, we don't know whether `{}` is just wraps for the type of
+           `someProp`, or if it's a record body where we want to complete
+            for the fields in the record. We need to look up what the type is
+           first before deciding what completions to show. So we do that here.*)
+      if isAmbigiousRecordBodyOrJsxWrap then
+        print_endline
+          "[process_completable]--> Cexpression special case: JSX prop value \
+           that might be record body or JSX wrap"
+      else print_endline "[process_completable]--> Cexpression";
+    (* Completions for local things like variables in scope, modules in the
+       project, etc. We only add completions when there's a prefix of some sort
+       we can filter on, since we know we're in some sort of context, and
+       therefore don't want to overwhelm the user with completion items. *)
+    let regularCompletions =
+      if prefix = "" then []
+      else
+        prefix
+        |> getComplementaryCompletionsForTypedValue ~opens ~allFiles ~env ~scope
+    in
+    match
+      contextPath
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:true ~scope
+      |> completionsGetCompletionType ~full
+    with
+    | None ->
+      if Debug.verbose () then
+        print_endline
+          "[process_completable]--> could not get completions for context path";
+      regularCompletions
+    | Some (typ, env) -> (
+      match typ |> TypeUtils.resolveNested ~env ~full ~nested with
+      | None ->
+        if Debug.verbose () then
+          print_endline
+            "[process_completable]--> could not resolve nested expression path";
+        if isAmbigiousRecordBodyOrJsxWrap then (
+          if Debug.verbose () then
+            print_endline
+              "[process_completable]--> case is ambigious Jsx prop vs record \
+               body case, complete also for the JSX prop value directly";
+          let itemsForRawJsxPropValue =
+            typ
+            |> completeTypedValue ~rawOpens ~mode:Expression ~full ~prefix
+                 ~completionContext:None
+          in
+          itemsForRawJsxPropValue @ regularCompletions)
+        else regularCompletions
+      | Some (typ, _env, completionContext, typeArgContext) -> (
+        if Debug.verbose () then
+          print_endline
+            "[process_completable]--> found type in nested expression \
+             completion";
+        (* Wrap the insert text in braces when we're completing the root of a
+           JSX prop value. *)
+        let wrapInsertTextInBraces =
+          if List.length nested > 0 then false
+          else
+            match contextPath with
+            | CJsxPropValue _ -> true
+            | _ -> false
+        in
+        let items =
+          typ
+          |> completeTypedValue ?typeArgContext ~rawOpens ~mode:Expression ~full
+               ~prefix ~completionContext
+          |> List.map (fun (c : Completion.t) ->
+                 if wrapInsertTextInBraces then
+                   {
+                     c with
+                     insertText =
+                       (match c.insertText with
+                       | None -> None
+                       | Some text -> Some ("{" ^ text ^ "}"));
+                   }
+                 else c)
+        in
+        match (prefix, completionContext) with
+        | "", _ -> items
+        | _, None ->
+          let items =
+            if List.length regularCompletions > 0 then
+              (* The client will occasionally sort the list of completions alphabetically, disregarding the order
+                 in which we send it. This fixes that by providing a sort text making the typed completions
+                 guaranteed to end up on top. *)
+              items
+              |> List.map (fun (c : Completion.t) ->
+                     {c with sortText = Some ("A" ^ " " ^ c.name)})
+            else items
+          in
+          items @ regularCompletions
+        | _ -> items)))
+  | CexhaustiveSwitch {contextPath; exprLoc} ->
+    let range = Utils.rangeOfLoc exprLoc in
+    let rescriptMajor, rescriptMinor = Packages.getReScriptVersion () in
+    let printFailwithStr num =
+      if (rescriptMajor = 11 && rescriptMinor >= 1) || rescriptMajor >= 12 then
+        "${" ^ string_of_int num ^ ":%todo}"
+      else "${" ^ string_of_int num ^ ":failwith(\"todo\")}"
+    in
+    let withExhaustiveItem ~cases ?(startIndex = 0) (c : Completion.t) =
+      (* We don't need to write out `switch` here since we know that's what the
+         user has already written. Just complete for the rest. *)
+      let newText =
+        c.name ^ " {\n"
+        ^ (cases
+          |> List.mapi (fun index caseText ->
+                 "| " ^ caseText ^ " => "
+                 ^ printFailwithStr (startIndex + index + 1))
+          |> String.concat "\n")
+        ^ "\n}"
+        |> Utils.indent range.start.character
+      in
+      [
+        c;
+        {
+          c with
+          name = c.name ^ " (exhaustive switch)";
+          filterText = Some c.name;
+          insertTextFormat = Some Snippet;
+          insertText = Some newText;
+          kind = Snippet "insert exhaustive switch for value";
+        };
+      ]
+    in
+    let completionsForContextPath =
+      contextPath
+      |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
+           ~exact:forHover ~scope
+    in
+    completionsForContextPath
+    |> List.map (fun (c : Completion.t) ->
+           match c.kind with
+           | Value typExpr -> (
+             match typExpr |> TypeUtils.extractType ~env:c.env ~package with
+             | Some (Tvariant v, _) ->
+               withExhaustiveItem c
+                 ~cases:
+                   (v.constructors
+                   |> List.map (fun (constructor : Constructor.t) ->
+                          constructor.cname.txt
+                          ^
+                          match constructor.args with
+                          | Args [] -> ""
+                          | _ -> "(_)"))
+             | Some (Tpolyvariant v, _) ->
+               withExhaustiveItem c
+                 ~cases:
+                   (v.constructors
+                   |> List.map (fun (constructor : polyVariantConstructor) ->
+                          "#" ^ constructor.displayName
+                          ^
+                          match constructor.args with
+                          | [] -> ""
+                          | _ -> "(_)"))
+             | Some (Toption (_env, _typ), _) ->
+               withExhaustiveItem c ~cases:["Some($1)"; "None"] ~startIndex:1
+             | Some (Tresult _, _) ->
+               withExhaustiveItem c ~cases:["Ok($1)"; "Error($1)"] ~startIndex:1
+             | Some (Tbool _, _) ->
+               withExhaustiveItem c ~cases:["true"; "false"]
+             | _ -> [c])
+           | _ -> [c])
+    |> List.flatten
+  | ChtmlElement {prefix} ->
+    CompletionJsx.htmlElements
+    |> List.filter_map (fun (elementName, description, deprecated) ->
+           if Utils.startsWith elementName prefix then
+             let name = "<" ^ elementName ^ ">" in
+             Some
+               (Completion.create name ~kind:(Label name) ~detail:description
+                  ~env ~docstring:[description] ~insertText:elementName
+                  ?deprecated:
+                    (match deprecated with
+                    | true -> Some "true"
+                    | false -> None))
+           else None)
+  | CextensionNode prefix ->
+    if Utils.startsWith "todo" prefix then
+      let detail =
+        "`%todo` is used to tell the compiler that some code still needs to be \
+         implemented."
+      in
+      [
+        Completion.create "todo" ~kind:(Label "todo") ~detail ~env
+          ~insertText:"todo";
+        Completion.create "todo (with payload)" ~includesSnippets:true
+          ~kind:(Label "todo")
+          ~detail:(detail ^ " With a payload.")
+          ~env ~insertText:"todo(\"${0:TODO}\")";
+      ]
+    else []
diff --git a/analysis/src/CompletionDecorators.ml b/analysis/src/CompletionDecorators.ml
new file mode 100644
index 0000000000..8319a5f4c9
--- /dev/null
+++ b/analysis/src/CompletionDecorators.ml
@@ -0,0 +1,295 @@
+let local =
+  [
+    ( "as",
+      Some "as(\"$0\")",
+      [
+        {|The `@as` decorator is commonly used on record types to alias record field names to a different JavaScript attribute name.
+
+This is useful to map to JavaScript attribute names that cannot be expressed in ReScript (such as keywords).
+
+It is also possible to map a ReScript record to a JavaScript array by passing indices to the `@as` decorator.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#as-decorator).|};
+      ] );
+    ( "dead",
+      None,
+      [
+        {|The `@dead` decorator is for reanalyze, a static analysis tool for ReScript that can do dead code analysis.
+
+`@dead` suppresses reporting on the value/type, but can also be used to force the analysis to consider a value as dead. Typically used to acknowledge cases of dead code you are not planning to address right now, but can be searched easily later.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#dead-decorator).
+
+> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
+      ] );
+    ( "deriving",
+      Some "deriving($0)",
+      [
+        {|When the `@deriving` decorator is applied to a record type, it expands the type into a factory function plus a set of getter/setter functions for its fields.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#deriving-decorator).|};
+      ] );
+    ( "deprecated",
+      Some "deprecated(\"$0\")",
+      [
+        {|The `@deprecated` decorator is used to add deprecation notes to types, values and submodules. The compiler and editor tooling will yield a warning whenever a deprecated entity is being used.
+
+Alternatively, use the `@@deprecated` decorator to add a deprecation warning to the file level.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#expression-deprecated-decorator).|};
+      ] );
+    ( "doesNotRaise",
+      None,
+      [
+        {|The `@doesNotRaise` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.
+
+`@doesNotRaise` is uses to override the analysis and state that an expression does not raise any exceptions,
+even though the analysis reports otherwise. This can happen for example in the case of array access where
+the analysis does not perform range checks but takes a conservative stance that any access
+could potentially raise.
+[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
+> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
+      ] );
+    ( "genType",
+      None,
+      [
+        {|The @genType decorator may be used to export ReScript values and types to JavaScript, and import JavaScript values and types into ReScript. It allows seamless integration of compiled ReScript modules in existing TypeScript, Flow, or plain JavaScript codebases, without loosing type information across different type systems.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#gentype-decorator).|};
+      ] );
+    ( "genType.as",
+      Some "genType.as(\"$0\")",
+      [
+        {|The @genType decorator may be used to export ReScript values and types to JavaScript, and import JavaScript values and types into ReScript. It allows seamless integration of compiled ReScript modules in existing TypeScript, Flow, or plain JavaScript codebases, without loosing type information across different type systems.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/docs/gentype/latest/usage).|};
+      ] );
+    ( "genType.import",
+      None,
+      [
+        {|The @genType decorator may be used to export ReScript values and types to JavaScript, and import JavaScript values and types into ReScript. It allows seamless integration of compiled ReScript modules in existing TypeScript, Flow, or plain JavaScript codebases, without loosing type information across different type systems.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/docs/gentype/latest/usage).|};
+      ] );
+    ( "genType.opaque",
+      None,
+      [
+        {|The @genType decorator may be used to export ReScript values and types to JavaScript, and import JavaScript values and types into ReScript. It allows seamless integration of compiled ReScript modules in existing TypeScript, Flow, or plain JavaScript codebases, without loosing type information across different type systems.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/docs/gentype/latest/usage).|};
+      ] );
+    ( "get",
+      None,
+      [
+        {|The `@get` decorator is used to bind to a property of an object.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#get-decorator).|};
+      ] );
+    ( "get_index",
+      None,
+      [
+        {|The `@get_index` decorator is used to access a dynamic property on an object, or an index of an array.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#get-index-decorator).|};
+      ] );
+    ( "inline",
+      None,
+      [
+        {|The `@inline` decorator tells the compiler to inline its value in every place the binding is being used, rather than use a variable.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#inline-decorator).|};
+      ] );
+    ( "int",
+      None,
+      [
+        {|The `@int` decorator can be used with polymorphic variants and the @as decorator on externals to modify the compiled JavaScript to use integers for the values instead of strings.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#int-decorator).|};
+      ] );
+    ( "live",
+      None,
+      [
+        {|The `@live` decorator is for reanalyze, a static analysis tool for ReScript that can do dead code analysis.
+
+`@live` tells the dead code analysis that the value should be considered live, even though it might appear to be dead. This is typically used in case of FFI where there are indirect ways to access values. It can be added to everything that could otherwise be considered unused by the dead code analysis - values, functions, arguments, records, individual record fields, and so on.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#live-decorator).
+
+Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
+      ] );
+    ( "meth",
+      None,
+      [
+        {|The `@meth` decorator is used to call a function on a JavaScript object, and avoid issues with currying.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#meth-decorator).|};
+      ] );
+    ( "module",
+      Some "module(\"$0\")",
+      [
+        {|The `@module` decorator is used to bind to a JavaScript module.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#module-decorator).|};
+      ] );
+    ( "new",
+      None,
+      [
+        {|
+The `@new` decorator is used whenever you need to bind to a JavaScript class constructor that requires the new keword for instantiation.|
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#new-decorator).|};
+      ] );
+    ( "obj",
+      None,
+      [
+        {|The `@obj` decorator is used to create functions that return JavaScript objects with properties that match the function's parameter labels.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#obj-decorator).|};
+      ] );
+    ( "raises",
+      Some "raises(\"$0\")",
+      [
+        {|The `@raises` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.
+
+`@raises` acknowledges that a function can raise exceptions that are not caught, and suppresses
+a warning in that case. Callers of the functions are then subjected to the same rule.
+Example `@raises(Exn)` or `@raises([E1, E2, E3])` for multiple exceptions.
+[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
+> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
+      ] );
+    ( "react.component",
+      None,
+      [
+        {|The `@react.component` decorator is used to annotate functions that are RescriptReact components.
+
+You will need this decorator whenever you want to use a ReScript / React component in ReScript JSX expressions.
+
+Note: The `@react.component` decorator requires the `jsx` config to be set in your `rescript.json`/`bsconfig.json` to enable the required React transformations.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#react-component-decorator).|};
+      ] );
+    ( "jsx.component",
+      None,
+      [
+        {|The `@jsx.component` decorator is used to annotate functions that are JSX components used with ReScript's [generic JSX transform](https://rescript-lang.org/docs/manual/latest/jsx#generic-jsx-transform-jsx-beyond-react-experimental).
+
+You will need this decorator whenever you want to use a JSX component in ReScript JSX expressions.|};
+      ] );
+    ( "return",
+      Some "return(${1:nullable})",
+      [
+        {|The `@return` decorator is used to control how `null` and `undefined` values are converted to option types in ReScript.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#return-decorator).|};
+      ] );
+    ( "scope",
+      Some "scope(\"$0\")",
+      [
+        {|The `@scope` decorator is used with other decorators such as `@val` and `@module` to declare a parent scope for the binding.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#scope-decorator).|};
+      ] );
+    ( "send",
+      None,
+      [
+        {|The `@send` decorator is used to bind to a method on an object or array.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#send-decorator).|};
+      ] );
+    ( "set",
+      None,
+      [
+        {|The `@set` decorator is used to set a property of an object.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#set-decorator).|};
+      ] );
+    ( "set_index",
+      None,
+      [
+        {|The `@set_index` decorator is used to set a dynamic property on an object, or an index of an array.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#set-index-decorator).|};
+      ] );
+    ( "string",
+      None,
+      [
+        {|The `@string` decorator can be used with polymorphic variants and the `@as` decorator on externals to modify the string values used for the variants in the compiled JavaScript.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#string-decorator).|};
+      ] );
+    ( "this",
+      None,
+      [
+        {|The `@this` decorator may be used to bind to an external callback function that require access to a this context.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#this-decorator).|};
+      ] );
+    ( "unboxed",
+      None,
+      [
+        {|The `@unboxed` decorator provides a way to unwrap variant constructors that have a single argument, or record objects that have a single field.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#unboxed-decorator).|};
+      ] );
+    ( "uncurry",
+      None,
+      [
+        {|The `@uncurry` decorator can be used to mark any callback argument within an external function as an uncurried function without the need for any explicit uncurried function syntax (`(.) => { ... }`).
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#uncurry-decorator).|};
+      ] );
+    ( "unwrap",
+      None,
+      [
+        {|The `@unwrap` decorator may be used when binding to external functions that accept multiple types for an argument.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#unwrap-decorator).|};
+      ] );
+    ( "val",
+      None,
+      [
+        {|The `@val` decorator allows you to bind to JavaScript values that are on the global scope.
+  
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#val-decorator).|};
+      ] );
+    ( "variadic",
+      None,
+      [
+        {|The `@variadic` decorator is used to model JavaScript functions that take a variable number of arguments, where all arguments are of the same type.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#variadic-decorator).|};
+      ] );
+  ]
+
+let toplevel =
+  [
+    ( "deprecated",
+      Some "deprecated(\"$0\")",
+      [
+        {|The `@@deprecated` decorator is used to add a deprecation note to the file-level of a module. The compiler and editor tooling will yield a warning whenever a deprecated file module is being used.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#module-deprecated-decorator).|};
+      ] );
+    ( "directive",
+      Some "directive(\"$0\")",
+      [
+        {|The `@@directive` decorator will output that string verbatim at the very top of the generated JavaScript file, before any imports.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#directive-decorator).|};
+      ] );
+    ( "warning",
+      Some "warning(\"$0\")",
+      [
+        {|The `@@warning` decorator is used to modify the enabled compiler warnings for the current module. See here for all available warning numbers.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#module-warning-decorator).
+         |};
+      ] );
+    ( "jsxConfig",
+      Some "jsxConfig({$0})",
+      [
+        {|The `@@jsxConfig` decorator is used to change the config for JSX on the fly.
+
+[Read more and see examples in the documentation](https://rescript-lang.org/docs/manual/latest/jsx#file-level-configuration).|};
+      ] );
+  ]
diff --git a/analysis/src/CompletionExpressions.ml b/analysis/src/CompletionExpressions.ml
new file mode 100644
index 0000000000..8b5b233e35
--- /dev/null
+++ b/analysis/src/CompletionExpressions.ml
@@ -0,0 +1,304 @@
+open SharedTypes
+
+let isExprHole exp =
+  match exp.Parsetree.pexp_desc with
+  | Pexp_extension ({txt = "rescript.exprhole"}, _) -> true
+  | _ -> false
+
+let isExprTuple expr =
+  match expr.Parsetree.pexp_desc with
+  | Pexp_tuple _ -> true
+  | _ -> false
+
+let rec traverseExpr (exp : Parsetree.expression) ~exprPath ~pos
+    ~firstCharBeforeCursorNoWhite =
+  let locHasCursor loc = loc |> CursorPosition.locHasCursor ~pos in
+  let someIfHasCursor v = if locHasCursor exp.pexp_loc then Some v else None in
+  match exp.pexp_desc with
+  | Pexp_ident {txt = Lident txt} when Utils.hasBraces exp.pexp_attributes ->
+    (* An ident with braces attribute corresponds to for example `{n}`.
+       Looks like a record but is parsed as an ident with braces. *)
+    someIfHasCursor (txt, [Completable.NRecordBody {seenFields = []}] @ exprPath)
+  | Pexp_ident {txt = Lident txt} -> someIfHasCursor (txt, exprPath)
+  | Pexp_construct ({txt = Lident "()"}, _) -> someIfHasCursor ("", exprPath)
+  | Pexp_construct ({txt = Lident txt}, None) -> someIfHasCursor (txt, exprPath)
+  | Pexp_variant (label, None) -> someIfHasCursor ("#" ^ label, exprPath)
+  | Pexp_array arrayPatterns -> (
+    let nextExprPath = [Completable.NArray] @ exprPath in
+    (* No fields but still has cursor = empty completion *)
+    if List.length arrayPatterns = 0 && locHasCursor exp.pexp_loc then
+      Some ("", nextExprPath)
+    else
+      let arrayItemWithCursor =
+        arrayPatterns
+        |> List.find_map (fun e ->
+               e
+               |> traverseExpr ~exprPath:nextExprPath
+                    ~firstCharBeforeCursorNoWhite ~pos)
+      in
+
+      match (arrayItemWithCursor, locHasCursor exp.pexp_loc) with
+      | Some arrayItemWithCursor, _ -> Some arrayItemWithCursor
+      | None, true when firstCharBeforeCursorNoWhite = Some ',' ->
+        (* No item had the cursor, but the entire expr still has the cursor (so
+           the cursor is in the array somewhere), and the first char before the
+           cursor is a comma = interpret as compleing for a new value (example:
+           `[None, <com>, None]`) *)
+        Some ("", nextExprPath)
+      | _ -> None)
+  | Pexp_tuple tupleItems when locHasCursor exp.pexp_loc ->
+    tupleItems
+    |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos
+         ~nextExprPath:(fun itemNum ->
+           [Completable.NTupleItem {itemNum}] @ exprPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [Completable.NTupleItem {itemNum = itemNum + 1}] @ exprPath)
+  | Pexp_record ([], _) ->
+    (* Empty fields means we're in a record body `{}`. Complete for the fields. *)
+    someIfHasCursor ("", [Completable.NRecordBody {seenFields = []}] @ exprPath)
+  | Pexp_record (fields, _) -> (
+    let fieldWithCursor = ref None in
+    let fieldWithExprHole = ref None in
+    fields
+    |> List.iter (fun (fname, exp) ->
+           match
+             ( fname.Location.txt,
+               exp.Parsetree.pexp_loc |> CursorPosition.classifyLoc ~pos )
+           with
+           | Longident.Lident fname, HasCursor ->
+             fieldWithCursor := Some (fname, exp)
+           | Lident fname, _ when isExprHole exp ->
+             fieldWithExprHole := Some (fname, exp)
+           | _ -> ());
+    let seenFields =
+      fields
+      |> List.filter_map (fun (fieldName, _f) ->
+             match fieldName with
+             | {Location.txt = Longident.Lident fieldName} -> Some fieldName
+             | _ -> None)
+    in
+    match (!fieldWithCursor, !fieldWithExprHole) with
+    | Some (fname, f), _ | None, Some (fname, f) -> (
+      match f.pexp_desc with
+      | Pexp_extension ({txt = "rescript.exprhole"}, _) ->
+        (* An expression hole means for example `{someField: <com>}`. We want to complete for the type of `someField`.  *)
+        someIfHasCursor
+          ("", [Completable.NFollowRecordField {fieldName = fname}] @ exprPath)
+      | Pexp_ident {txt = Lident txt} when fname = txt ->
+        (* This is a heuristic for catching writing field names. ReScript has punning for record fields, but the AST doesn't,
+           so punning is represented as the record field name and identifier being the same: {someField}. *)
+        someIfHasCursor (txt, [Completable.NRecordBody {seenFields}] @ exprPath)
+      | Pexp_ident {txt = Lident txt} ->
+        (* A var means `{someField: s}` or similar. Complete for identifiers or values. *)
+        someIfHasCursor (txt, exprPath)
+      | _ ->
+        f
+        |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos
+             ~exprPath:
+               ([Completable.NFollowRecordField {fieldName = fname}] @ exprPath)
+      )
+    | None, None -> (
+      if Debug.verbose () then (
+        Printf.printf "[traverse_expr] No field with cursor and no expr hole.\n";
+
+        match firstCharBeforeCursorNoWhite with
+        | None -> ()
+        | Some c ->
+          Printf.printf "[traverse_expr] firstCharBeforeCursorNoWhite: %c.\n" c);
+
+      (* Figure out if we're completing for a new field.
+         If the cursor is inside of the record body, but no field has the cursor,
+         and there's no pattern hole. Check the first char to the left of the cursor,
+         ignoring white space. If that's a comma or {, we assume you're completing for a new field,
+         since you're either between 2 fields (comma to the left) or at the start of the record ({). *)
+      match firstCharBeforeCursorNoWhite with
+      | Some (',' | '{') ->
+        someIfHasCursor ("", [Completable.NRecordBody {seenFields}] @ exprPath)
+      | _ -> None))
+  | Pexp_construct
+      ( {txt},
+        Some {pexp_loc; pexp_desc = Pexp_construct ({txt = Lident "()"}, _)} )
+    when locHasCursor pexp_loc ->
+    (* Empty payload with cursor, like: Test(<com>) *)
+    Some
+      ( "",
+        [
+          Completable.NVariantPayload
+            {constructorName = Utils.getUnqualifiedName txt; itemNum = 0};
+        ]
+        @ exprPath )
+  | Pexp_construct ({txt}, Some e)
+    when pos >= (e.pexp_loc |> Loc.end_)
+         && firstCharBeforeCursorNoWhite = Some ','
+         && isExprTuple e = false ->
+    (* Empty payload with trailing ',', like: Test(true, <com>) *)
+    Some
+      ( "",
+        [
+          Completable.NVariantPayload
+            {constructorName = Utils.getUnqualifiedName txt; itemNum = 1};
+        ]
+        @ exprPath )
+  | Pexp_construct ({txt}, Some {pexp_loc; pexp_desc = Pexp_tuple tupleItems})
+    when locHasCursor pexp_loc ->
+    tupleItems
+    |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos
+         ~nextExprPath:(fun itemNum ->
+           [
+             Completable.NVariantPayload
+               {constructorName = Utils.getUnqualifiedName txt; itemNum};
+           ]
+           @ exprPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [
+             Completable.NVariantPayload
+               {
+                 constructorName = Utils.getUnqualifiedName txt;
+                 itemNum = itemNum + 1;
+               };
+           ]
+           @ exprPath)
+  | Pexp_construct ({txt}, Some p) when locHasCursor exp.pexp_loc ->
+    p
+    |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos
+         ~exprPath:
+           ([
+              Completable.NVariantPayload
+                {constructorName = Utils.getUnqualifiedName txt; itemNum = 0};
+            ]
+           @ exprPath)
+  | Pexp_variant
+      (txt, Some {pexp_loc; pexp_desc = Pexp_construct ({txt = Lident "()"}, _)})
+    when locHasCursor pexp_loc ->
+    (* Empty payload with cursor, like: #test(<com>) *)
+    Some
+      ( "",
+        [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 0}]
+        @ exprPath )
+  | Pexp_variant (txt, Some e)
+    when pos >= (e.pexp_loc |> Loc.end_)
+         && firstCharBeforeCursorNoWhite = Some ','
+         && isExprTuple e = false ->
+    (* Empty payload with trailing ',', like: #test(true, <com>) *)
+    Some
+      ( "",
+        [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 1}]
+        @ exprPath )
+  | Pexp_variant (txt, Some {pexp_loc; pexp_desc = Pexp_tuple tupleItems})
+    when locHasCursor pexp_loc ->
+    tupleItems
+    |> traverseExprTupleItems ~firstCharBeforeCursorNoWhite ~pos
+         ~nextExprPath:(fun itemNum ->
+           [Completable.NPolyvariantPayload {constructorName = txt; itemNum}]
+           @ exprPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [
+             Completable.NPolyvariantPayload
+               {constructorName = txt; itemNum = itemNum + 1};
+           ]
+           @ exprPath)
+  | Pexp_variant (txt, Some p) when locHasCursor exp.pexp_loc ->
+    p
+    |> traverseExpr ~firstCharBeforeCursorNoWhite ~pos
+         ~exprPath:
+           ([
+              Completable.NPolyvariantPayload
+                {constructorName = txt; itemNum = 0};
+            ]
+           @ exprPath)
+  | _ -> None
+
+and traverseExprTupleItems tupleItems ~nextExprPath ~resultFromFoundItemNum ~pos
+    ~firstCharBeforeCursorNoWhite =
+  let itemNum = ref (-1) in
+  let itemWithCursor =
+    tupleItems
+    |> List.find_map (fun e ->
+           itemNum := !itemNum + 1;
+           e
+           |> traverseExpr ~exprPath:(nextExprPath !itemNum)
+                ~firstCharBeforeCursorNoWhite ~pos)
+  in
+  match (itemWithCursor, firstCharBeforeCursorNoWhite) with
+  | None, Some ',' ->
+    (* No tuple item has the cursor, but there's a comma before the cursor.
+       Figure out what arg we're trying to complete. Example: (true, <com>, None) *)
+    let posNum = ref (-1) in
+    tupleItems
+    |> List.iteri (fun index e ->
+           if pos >= Loc.start e.Parsetree.pexp_loc then posNum := index);
+    if !posNum > -1 then Some ("", resultFromFoundItemNum !posNum) else None
+  | v, _ -> v
+
+let prettyPrintFnTemplateArgName ?currentIndex ~env ~full
+    (argTyp : Types.type_expr) =
+  let indexText =
+    match currentIndex with
+    | None -> ""
+    | Some i -> string_of_int i
+  in
+  let defaultVarName = "v" ^ indexText in
+  let argTyp, suffix, _env =
+    TypeUtils.digToRelevantTemplateNameType ~env ~package:full.package argTyp
+  in
+  match argTyp |> TypeUtils.pathFromTypeExpr with
+  | None -> defaultVarName
+  | Some p -> (
+    let trailingElementsOfPath =
+      p |> Utils.expandPath |> List.rev |> Utils.lastElements
+    in
+    match trailingElementsOfPath with
+    | [] | ["t"] -> defaultVarName
+    | ["unit"] -> "()"
+    (* Special treatment for JsxEvent, since that's a common enough thing
+       used in event handlers. *)
+    | ["JsxEvent"; "synthetic"] -> "event"
+    | ["synthetic"] -> "event"
+    (* Ignore `t` types, and go for its module name instead. *)
+    | [someName; "t"] | [_; someName] | [someName] -> (
+      match someName with
+      | "string" | "int" | "float" | "array" | "option" | "bool" ->
+        defaultVarName
+      | someName when String.length someName < 30 ->
+        if someName = "synthetic" then
+          Printf.printf "synthetic! %s\n"
+            (trailingElementsOfPath |> SharedTypes.ident);
+        (* We cap how long the name can be, so we don't end up with super
+           long type names. *)
+        (someName |> Utils.lowercaseFirstChar) ^ suffix
+      | _ -> defaultVarName)
+    | _ -> defaultVarName)
+
+let completeConstructorPayload ~posBeforeCursor ~firstCharBeforeCursorNoWhite
+    (constructorLid : Longident.t Location.loc) expr =
+  match
+    traverseExpr expr ~exprPath:[] ~pos:posBeforeCursor
+      ~firstCharBeforeCursorNoWhite
+  with
+  | None -> None
+  | Some (prefix, nested) ->
+    (* The nested path must start with the constructor name found, plus
+       the target argument number for the constructor. We translate to
+       that here, because we need to account for multi arg constructors
+       being represented as tuples. *)
+    let nested =
+      match List.rev nested with
+      | Completable.NTupleItem {itemNum} :: rest ->
+        [
+          Completable.NVariantPayload
+            {constructorName = Longident.last constructorLid.txt; itemNum};
+        ]
+        @ rest
+      | nested ->
+        [
+          Completable.NVariantPayload
+            {constructorName = Longident.last constructorLid.txt; itemNum = 0};
+        ]
+        @ nested
+    in
+    let variantCtxPath =
+      Completable.CTypeAtPos
+        {constructorLid.loc with loc_start = constructorLid.loc.loc_end}
+    in
+    Some
+      (Completable.Cexpression {contextPath = variantCtxPath; prefix; nested})
diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml
new file mode 100644
index 0000000000..f5957a8ed4
--- /dev/null
+++ b/analysis/src/CompletionFrontEnd.ml
@@ -0,0 +1,1554 @@
+open SharedTypes
+
+let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
+    ~(contextPath : Completable.contextPath) ~posAfterFunExpr
+    ~firstCharBeforeCursorNoWhite ~charBeforeCursor ~isPipedExpr =
+  let fnHasCursor =
+    posAfterFunExpr <= posBeforeCursor && posBeforeCursor < endPos
+  in
+  let allNames =
+    List.fold_right
+      (fun arg allLabels ->
+        match arg with
+        | {label = Some labelled} -> labelled.name :: allLabels
+        | {label = None} -> allLabels)
+      args []
+  in
+  let unlabelledCount = ref (if isPipedExpr then 1 else 0) in
+  let someArgHadEmptyExprLoc = ref false in
+  let rec loop args =
+    match args with
+    | {label = Some labelled; exp} :: rest ->
+      if
+        labelled.posStart <= posBeforeCursor
+        && posBeforeCursor < labelled.posEnd
+      then (
+        if Debug.verbose () then
+          print_endline "[findArgCompletables] Completing named arg #2";
+        Some (Completable.CnamedArg (contextPath, labelled.name, allNames)))
+      else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
+        if Debug.verbose () then
+          print_endline
+            "[findArgCompletables] Completing in the assignment of labelled \
+             argument";
+        match
+          CompletionExpressions.traverseExpr exp ~exprPath:[]
+            ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
+        with
+        | None -> None
+        | Some (prefix, nested) ->
+          if Debug.verbose () then
+            print_endline
+              "[findArgCompletables] Completing for labelled argument value";
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CArgument
+                     {
+                       functionContextPath = contextPath;
+                       argumentLabel = Labelled labelled.name;
+                     };
+                 prefix;
+                 nested = List.rev nested;
+               }))
+      else if CompletionExpressions.isExprHole exp then (
+        if Debug.verbose () then
+          print_endline "[findArgCompletables] found exprhole";
+        Some
+          (Cexpression
+             {
+               contextPath =
+                 CArgument
+                   {
+                     functionContextPath = contextPath;
+                     argumentLabel = Labelled labelled.name;
+                   };
+               prefix = "";
+               nested = [];
+             }))
+      else loop rest
+    | {label = None; exp} :: rest ->
+      if Debug.verbose () then
+        Printf.printf "[findArgCompletable] unlabelled arg expr is: %s \n"
+          (DumpAst.printExprItem ~pos:posBeforeCursor ~indentation:0 exp);
+
+      (* Track whether there was an arg with an empty loc (indicates parser error)*)
+      if CursorPosition.locIsEmpty exp.pexp_loc ~pos:posBeforeCursor then
+        someArgHadEmptyExprLoc := true;
+
+      if Res_parsetree_viewer.is_template_literal exp then None
+      else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
+        if Debug.verbose () then
+          print_endline
+            "[findArgCompletables] Completing in an unlabelled argument";
+        match
+          CompletionExpressions.traverseExpr exp ~pos:posBeforeCursor
+            ~firstCharBeforeCursorNoWhite ~exprPath:[]
+        with
+        | None ->
+          if Debug.verbose () then
+            print_endline
+              "[findArgCompletables] found nothing when traversing expr";
+          None
+        | Some (prefix, nested) ->
+          if Debug.verbose () then
+            print_endline
+              "[findArgCompletables] completing for unlabelled argument #2";
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CArgument
+                     {
+                       functionContextPath = contextPath;
+                       argumentLabel =
+                         Unlabelled {argumentPosition = !unlabelledCount};
+                     };
+                 prefix;
+                 nested = List.rev nested;
+               }))
+      else if CompletionExpressions.isExprHole exp then (
+        if Debug.verbose () then
+          print_endline "[findArgCompletables] found an exprhole #2";
+        Some
+          (Cexpression
+             {
+               contextPath =
+                 CArgument
+                   {
+                     functionContextPath = contextPath;
+                     argumentLabel =
+                       Unlabelled {argumentPosition = !unlabelledCount};
+                   };
+               prefix = "";
+               nested = [];
+             }))
+      else (
+        unlabelledCount := !unlabelledCount + 1;
+        loop rest)
+    | [] ->
+      let hadEmptyExpLoc = !someArgHadEmptyExprLoc in
+      if fnHasCursor then (
+        if Debug.verbose () then
+          print_endline "[findArgCompletables] Function has cursor";
+        match charBeforeCursor with
+        | Some '~' ->
+          if Debug.verbose () then
+            print_endline "[findArgCompletables] '~' is before cursor";
+          Some (Completable.CnamedArg (contextPath, "", allNames))
+        | _ when hadEmptyExpLoc ->
+          (* Special case: `Console.log(arr->)`, completing on the pipe.
+             This match branch happens when the fn call has the cursor and:
+             - there's no argument label or expr that has the cursor
+             - there's an argument expression with an empty loc (indicates parser error)
+
+             In that case, it's safer to not complete for the unlabelled function
+             argument (which we do otherwise), and instead not complete and let the
+             completion engine move into the arguments one by one instead to check
+             for completions.
+
+             This can be handled in a more robust way in a future refactor of the
+             completion engine logic. *)
+          if Debug.verbose () then
+            print_endline
+              "[findArgCompletables] skipping completion in fn call because \
+               arg had empty loc";
+          None
+        | _
+          when firstCharBeforeCursorNoWhite = Some '('
+               || firstCharBeforeCursorNoWhite = Some ',' ->
+          (* Checks to ensure that completing for empty unlabelled arg makes
+             sense by checking what's left of the cursor. *)
+          if Debug.verbose () then
+            Printf.printf
+              "[findArgCompletables] Completing for unlabelled argument value \
+               because nothing matched and is not labelled argument name \
+               completion. isPipedExpr: %b\n"
+              isPipedExpr;
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CArgument
+                     {
+                       functionContextPath = contextPath;
+                       argumentLabel =
+                         Unlabelled {argumentPosition = !unlabelledCount};
+                     };
+                 prefix = "";
+                 nested = [];
+               })
+        | _ -> None)
+      else None
+  in
+  match args with
+  (* Special handling for empty fn calls, e.g. `let _ = someFn(<com>)` *)
+  | [
+   {label = None; exp = {pexp_desc = Pexp_construct ({txt = Lident "()"}, _)}};
+  ]
+    when fnHasCursor ->
+    if Debug.verbose () then
+      print_endline "[findArgCompletables] Completing for unit argument";
+    Some
+      (Completable.Cexpression
+         {
+           contextPath =
+             CArgument
+               {
+                 functionContextPath = contextPath;
+                 argumentLabel =
+                   Unlabelled
+                     {argumentPosition = (if isPipedExpr then 1 else 0)};
+               };
+           prefix = "";
+           nested = [];
+         })
+  | _ -> loop args
+
+let rec exprToContextPathInner (e : Parsetree.expression) =
+  match e.pexp_desc with
+  | Pexp_constant (Pconst_string _) -> Some Completable.CPString
+  | Pexp_constant (Pconst_integer _) -> Some CPInt
+  | Pexp_constant (Pconst_float _) -> Some CPFloat
+  | Pexp_construct ({txt = Lident ("true" | "false")}, None) -> Some CPBool
+  | Pexp_array exprs ->
+    Some
+      (CPArray
+         (match exprs with
+         | [] -> None
+         | exp :: _ -> exprToContextPath exp))
+  | Pexp_ident {txt = Lident ("|." | "|.u")} -> None
+  | Pexp_ident {txt; loc} ->
+    Some
+      (CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc})
+  | Pexp_field (e1, {txt = Lident name}) -> (
+    match exprToContextPath e1 with
+    | Some contextPath -> Some (CPField (contextPath, name))
+    | _ -> None)
+  | Pexp_field (_, {loc; txt = Ldot (lid, name)}) ->
+    (* Case x.M.field ignore the x part *)
+    Some
+      (CPField
+         ( CPId
+             {
+               path = Utils.flattenLongIdent lid;
+               completionContext = Module;
+               loc;
+             },
+           name ))
+  | Pexp_send (e1, {txt}) -> (
+    match exprToContextPath e1 with
+    | None -> None
+    | Some contexPath -> Some (CPObj (contexPath, txt)))
+  | Pexp_apply
+      ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+        [
+          (_, lhs);
+          (_, {pexp_desc = Pexp_apply (d, args); pexp_loc; pexp_attributes});
+        ] ) ->
+    (* Transform away pipe with apply call *)
+    exprToContextPath
+      {
+        pexp_desc = Pexp_apply (d, (Nolabel, lhs) :: args);
+        pexp_loc;
+        pexp_attributes;
+      }
+  | Pexp_apply
+      ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+        [(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes})]
+      ) ->
+    (* Transform away pipe with identifier *)
+    exprToContextPath
+      {
+        pexp_desc =
+          Pexp_apply
+            ( {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes},
+              [(Nolabel, lhs)] );
+        pexp_loc;
+        pexp_attributes;
+      }
+  | Pexp_apply (e1, args) -> (
+    match exprToContextPath e1 with
+    | None -> None
+    | Some contexPath -> Some (CPApply (contexPath, args |> List.map fst)))
+  | Pexp_tuple exprs ->
+    let exprsAsContextPaths = exprs |> List.filter_map exprToContextPath in
+    if List.length exprs = List.length exprsAsContextPaths then
+      Some (CTuple exprsAsContextPaths)
+    else None
+  | _ -> None
+
+and exprToContextPath (e : Parsetree.expression) =
+  match
+    ( Res_parsetree_viewer.has_await_attribute e.pexp_attributes,
+      exprToContextPathInner e )
+  with
+  | true, Some ctxPath -> Some (CPAwait ctxPath)
+  | false, Some ctxPath -> Some ctxPath
+  | _, None -> None
+
+let completePipeChain (exp : Parsetree.expression) =
+  (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe,
+     so it can be completed.
+     Example:
+      someArray->Js.Array2.filter(v => v > 10)->Js.Array2.map(v => v + 2)->
+        will complete as:
+      Js.Array2.map(someArray->Js.Array2.filter(v => v > 10), v => v + 2)->
+  *)
+  match exp.pexp_desc with
+  (* When the left side of the pipe we're completing is a function application.
+     Example: someArray->Js.Array2.map(v => v + 2)-> *)
+  | Pexp_apply
+      ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+        [_; (_, {pexp_desc = Pexp_apply (d, _)})] ) ->
+    exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc))
+    (* When the left side of the pipe we're completing is an identifier application.
+       Example: someArray->filterAllTheGoodStuff-> *)
+  | Pexp_apply
+      ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+        [_; (_, {pexp_desc = Pexp_ident _; pexp_loc})] ) ->
+    exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, pexp_loc))
+  | _ -> None
+
+let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
+    ?findThisExprLoc text =
+  let offsetNoWhite = Utils.skipWhite text (offset - 1) in
+  let posNoWhite =
+    let line, col = posCursor in
+    (line, max 0 col - offset + offsetNoWhite)
+  in
+  (* Identifies the first character before the cursor that's not white space.
+     Should be used very sparingly, but can be used to drive completion triggering
+     in scenarios where the parser eats things we'd need to complete.
+     Example: let {whatever,     <cursor>}, char is ','. *)
+  let firstCharBeforeCursorNoWhite =
+    if offsetNoWhite < String.length text && offsetNoWhite >= 0 then
+      Some text.[offsetNoWhite]
+    else None
+  in
+  let charAtCursor =
+    if offset < String.length text then text.[offset] else '\n'
+  in
+  let posBeforeCursor = Pos.posBeforeCursor posCursor in
+  let charBeforeCursor, blankAfterCursor =
+    match Pos.positionToOffset text posCursor with
+    | Some offset when offset > 0 -> (
+      let charBeforeCursor = text.[offset - 1] in
+      match charAtCursor with
+      | ' ' | '\t' | '\r' | '\n' ->
+        (Some charBeforeCursor, Some charBeforeCursor)
+      | _ -> (Some charBeforeCursor, None))
+    | _ -> (None, None)
+  in
+  let flattenLidCheckDot ?(jsx = true) (lid : Longident.t Location.loc) =
+    (* Flatten an identifier keeping track of whether the current cursor
+       is after a "." in the id followed by a blank character.
+       In that case, cut the path after ".". *)
+    let cutAtOffset =
+      let idStart = Loc.start lid.loc in
+      match blankAfterCursor with
+      | Some '.' ->
+        if fst posBeforeCursor = fst idStart then
+          Some (snd posBeforeCursor - snd idStart)
+        else None
+      | _ -> None
+    in
+    Utils.flattenLongIdent ~cutAtOffset ~jsx lid.txt
+  in
+
+  let currentCtxPath = ref None in
+  let processingFun = ref None in
+  let setCurrentCtxPath ctxPath =
+    if !Cfg.debugFollowCtxPath then
+      Printf.printf "setting current ctxPath: %s\n"
+        (Completable.contextPathToString ctxPath);
+    currentCtxPath := Some ctxPath
+  in
+  let resetCurrentCtxPath ctxPath =
+    (match (!currentCtxPath, ctxPath) with
+    | None, None -> ()
+    | _ ->
+      if !Cfg.debugFollowCtxPath then
+        Printf.printf "resetting current ctxPath to: %s\n"
+          (match ctxPath with
+          | None -> "None"
+          | Some ctxPath -> Completable.contextPathToString ctxPath));
+    currentCtxPath := ctxPath
+  in
+
+  let found = ref false in
+  let result = ref None in
+  let scope = ref (Scope.create ()) in
+  let setResultOpt x =
+    if !result = None then
+      match x with
+      | None ->
+        if Debug.verbose () then
+          print_endline
+            "[set_result] did not set new result because result already was set";
+        ()
+      | Some x ->
+        if Debug.verbose () then
+          Printf.printf "[set_result] set new result to %s\n"
+            (Completable.toString x);
+        result := Some (x, !scope)
+  in
+  let setResult x = setResultOpt (Some x) in
+  let scopeValueDescription (vd : Parsetree.value_description) =
+    scope :=
+      !scope |> Scope.addValue ~name:vd.pval_name.txt ~loc:vd.pval_name.loc
+  in
+  let rec scopePattern ?contextPath
+      ?(patternPath : Completable.nestedPath list = [])
+      (pat : Parsetree.pattern) =
+    let contextPathToSave =
+      match (contextPath, patternPath) with
+      | maybeContextPath, [] -> maybeContextPath
+      | Some contextPath, patternPath ->
+        Some
+          (Completable.CPatternPath
+             {rootCtxPath = contextPath; nested = List.rev patternPath})
+      | _ -> None
+    in
+    match pat.ppat_desc with
+    | Ppat_any -> ()
+    | Ppat_var {txt; loc} ->
+      scope :=
+        !scope |> Scope.addValue ~name:txt ~loc ?contextPath:contextPathToSave
+    | Ppat_alias (p, asA) ->
+      scopePattern p ~patternPath ?contextPath;
+      let ctxPath =
+        if contextPathToSave = None then
+          match p with
+          | {ppat_desc = Ppat_var {txt; loc}} ->
+            Some
+              (Completable.CPId {path = [txt]; completionContext = Value; loc})
+          | _ -> None
+        else None
+      in
+      scope :=
+        !scope |> Scope.addValue ~name:asA.txt ~loc:asA.loc ?contextPath:ctxPath
+    | Ppat_constant _ | Ppat_interval _ -> ()
+    | Ppat_tuple pl ->
+      pl
+      |> List.iteri (fun index p ->
+             scopePattern p
+               ~patternPath:(NTupleItem {itemNum = index} :: patternPath)
+               ?contextPath)
+    | Ppat_construct (_, None) -> ()
+    | Ppat_construct ({txt}, Some {ppat_desc = Ppat_tuple pl}) ->
+      pl
+      |> List.iteri (fun index p ->
+             scopePattern p
+               ~patternPath:
+                 (NVariantPayload
+                    {
+                      itemNum = index;
+                      constructorName = Utils.getUnqualifiedName txt;
+                    }
+                 :: patternPath)
+               ?contextPath)
+    | Ppat_construct ({txt}, Some p) ->
+      scopePattern
+        ~patternPath:
+          (NVariantPayload
+             {itemNum = 0; constructorName = Utils.getUnqualifiedName txt}
+          :: patternPath)
+        ?contextPath p
+    | Ppat_variant (_, None) -> ()
+    | Ppat_variant (txt, Some {ppat_desc = Ppat_tuple pl}) ->
+      pl
+      |> List.iteri (fun index p ->
+             scopePattern p
+               ~patternPath:
+                 (NPolyvariantPayload {itemNum = index; constructorName = txt}
+                 :: patternPath)
+               ?contextPath)
+    | Ppat_variant (txt, Some p) ->
+      scopePattern
+        ~patternPath:
+          (NPolyvariantPayload {itemNum = 0; constructorName = txt}
+          :: patternPath)
+        ?contextPath p
+    | Ppat_record (fields, _) ->
+      fields
+      |> List.iter (fun (fname, p) ->
+             match fname with
+             | {Location.txt = Longident.Lident fname} ->
+               scopePattern
+                 ~patternPath:
+                   (Completable.NFollowRecordField {fieldName = fname}
+                   :: patternPath)
+                 ?contextPath p
+             | _ -> ())
+    | Ppat_array pl ->
+      pl
+      |> List.iter
+           (scopePattern ~patternPath:(NArray :: patternPath) ?contextPath)
+    | Ppat_or (p1, _) -> scopePattern ~patternPath ?contextPath p1
+    | Ppat_constraint (p, coreType) ->
+      scopePattern ~patternPath
+        ?contextPath:(TypeUtils.contextPathFromCoreType coreType)
+        p
+    | Ppat_type _ -> ()
+    | Ppat_lazy p -> scopePattern ~patternPath ?contextPath p
+    | Ppat_unpack {txt; loc} ->
+      scope :=
+        !scope |> Scope.addValue ~name:txt ~loc ?contextPath:contextPathToSave
+    | Ppat_exception p -> scopePattern ~patternPath ?contextPath p
+    | Ppat_extension _ -> ()
+    | Ppat_open (_, p) -> scopePattern ~patternPath ?contextPath p
+  in
+  let locHasCursor = CursorPosition.locHasCursor ~pos:posBeforeCursor in
+  let locIsEmpty = CursorPosition.locIsEmpty ~pos:posBeforeCursor in
+  let completePattern ?contextPath (pat : Parsetree.pattern) =
+    match
+      ( pat
+        |> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
+             ~firstCharBeforeCursorNoWhite ~posBeforeCursor,
+        contextPath )
+    with
+    | Some (prefix, nestedPattern), Some ctxPath ->
+      if Debug.verbose () then
+        Printf.printf "[completePattern] found pattern that can be completed\n";
+      setResult
+        (Completable.Cpattern
+           {
+             contextPath = ctxPath;
+             prefix;
+             nested = List.rev nestedPattern;
+             fallback = None;
+             patternMode = Default;
+           })
+    | _ -> ()
+  in
+  let scopeValueBinding (vb : Parsetree.value_binding) =
+    let contextPath =
+      (* Pipe chains get special treatment here, because when assigning values
+         we want the return of the entire pipe chain as a function call, rather
+         than as a pipe completion call. *)
+      match completePipeChain vb.pvb_expr with
+      | Some (ctxPath, _) -> Some ctxPath
+      | None -> exprToContextPath vb.pvb_expr
+    in
+    scopePattern ?contextPath vb.pvb_pat
+  in
+  let scopeTypeKind (tk : Parsetree.type_kind) =
+    match tk with
+    | Ptype_variant constrDecls ->
+      constrDecls
+      |> List.iter (fun (cd : Parsetree.constructor_declaration) ->
+             scope :=
+               !scope
+               |> Scope.addConstructor ~name:cd.pcd_name.txt ~loc:cd.pcd_loc)
+    | Ptype_record labelDecls ->
+      labelDecls
+      |> List.iter (fun (ld : Parsetree.label_declaration) ->
+             scope :=
+               !scope |> Scope.addField ~name:ld.pld_name.txt ~loc:ld.pld_loc)
+    | _ -> ()
+  in
+  let scopeTypeDeclaration (td : Parsetree.type_declaration) =
+    scope :=
+      !scope |> Scope.addType ~name:td.ptype_name.txt ~loc:td.ptype_name.loc;
+    scopeTypeKind td.ptype_kind
+  in
+  let scopeModuleBinding (mb : Parsetree.module_binding) =
+    scope :=
+      !scope |> Scope.addModule ~name:mb.pmb_name.txt ~loc:mb.pmb_name.loc
+  in
+  let scopeModuleDeclaration (md : Parsetree.module_declaration) =
+    scope :=
+      !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc
+  in
+  let inJsxContext = ref false in
+  (* Identifies expressions where we can do typed pattern or expr completion. *)
+  let typedCompletionExpr (exp : Parsetree.expression) =
+    let debugTypedCompletionExpr = false in
+    if exp.pexp_loc |> CursorPosition.locHasCursor ~pos:posBeforeCursor then (
+      if Debug.verbose () && debugTypedCompletionExpr then
+        print_endline "[typedCompletionExpr] Has cursor";
+      match exp.pexp_desc with
+      (* No cases means there's no `|` yet in the switch *)
+      | Pexp_match (({pexp_desc = Pexp_ident _} as expr), []) ->
+        if Debug.verbose () && debugTypedCompletionExpr then
+          print_endline "[typedCompletionExpr] No cases, with ident";
+        if locHasCursor expr.pexp_loc then (
+          if Debug.verbose () && debugTypedCompletionExpr then
+            print_endline "[typedCompletionExpr] No cases - has cursor";
+          (* We can do exhaustive switch completion if this is an ident we can
+             complete from. *)
+          match exprToContextPath expr with
+          | None -> ()
+          | Some contextPath ->
+            setResult (CexhaustiveSwitch {contextPath; exprLoc = exp.pexp_loc}))
+      | Pexp_match (_expr, []) ->
+        (* switch x { } *)
+        if Debug.verbose () && debugTypedCompletionExpr then
+          print_endline "[typedCompletionExpr] No cases, rest";
+        ()
+      | Pexp_match (expr, [{pc_lhs; pc_rhs}])
+        when locHasCursor expr.pexp_loc
+             && CompletionExpressions.isExprHole pc_rhs
+             && CompletionPatterns.isPatternHole pc_lhs ->
+        (* switch x { | } when we're in the switch expr itself. *)
+        if Debug.verbose () && debugTypedCompletionExpr then
+          print_endline
+            "[typedCompletionExpr] No cases (expr and pat holes), rest";
+        ()
+      | Pexp_match
+          ( exp,
+            [
+              {
+                pc_lhs =
+                  {
+                    ppat_desc =
+                      Ppat_extension ({txt = "rescript.patternhole"}, _);
+                  };
+              };
+            ] ) -> (
+        (* A single case that's a pattern hole typically means `switch x { | }`. Complete as the pattern itself with nothing nested. *)
+        match exprToContextPath exp with
+        | None -> ()
+        | Some ctxPath ->
+          setResult
+            (Completable.Cpattern
+               {
+                 contextPath = ctxPath;
+                 nested = [];
+                 prefix = "";
+                 fallback = None;
+                 patternMode = Default;
+               }))
+      | Pexp_match (exp, cases) -> (
+        if Debug.verbose () && debugTypedCompletionExpr then
+          print_endline "[typedCompletionExpr] Has cases";
+        (* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another
+           broken parser case (`switch x { | true => () | <com> }` for example). *)
+        match exp |> exprToContextPath with
+        | None ->
+          if Debug.verbose () && debugTypedCompletionExpr then
+            print_endline "[typedCompletionExpr] Has cases - no ctx path"
+        | Some ctxPath -> (
+          if Debug.verbose () && debugTypedCompletionExpr then
+            print_endline "[typedCompletionExpr] Has cases - has ctx path";
+          let hasCaseWithCursor =
+            cases
+            |> List.find_opt (fun case ->
+                   locHasCursor case.Parsetree.pc_lhs.ppat_loc)
+            |> Option.is_some
+          in
+          let hasCaseWithEmptyLoc =
+            cases
+            |> List.find_opt (fun case ->
+                   locIsEmpty case.Parsetree.pc_lhs.ppat_loc)
+            |> Option.is_some
+          in
+          if Debug.verbose () && debugTypedCompletionExpr then
+            Printf.printf
+              "[typedCompletionExpr] Has cases - has ctx path - \
+               hasCaseWithEmptyLoc: %b, hasCaseWithCursor: %b\n"
+              hasCaseWithEmptyLoc hasCaseWithCursor;
+          match (hasCaseWithEmptyLoc, hasCaseWithCursor) with
+          | _, true ->
+            (* Always continue if there's a case with the cursor *)
+            ()
+          | true, false ->
+            (* If there's no case with the cursor, but a broken parser case, complete for the top level. *)
+            setResult
+              (Completable.Cpattern
+                 {
+                   contextPath = ctxPath;
+                   nested = [];
+                   prefix = "";
+                   fallback = None;
+                   patternMode = Default;
+                 })
+          | false, false -> ()))
+      | _ -> ())
+  in
+  let structure (iterator : Ast_iterator.iterator)
+      (structure : Parsetree.structure) =
+    let oldScope = !scope in
+    Ast_iterator.default_iterator.structure iterator structure;
+    scope := oldScope
+  in
+  let structure_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.structure_item) =
+    let processed = ref false in
+    (match item.pstr_desc with
+    | Pstr_open {popen_lid} ->
+      scope := !scope |> Scope.addOpen ~lid:popen_lid.txt
+    | Pstr_primitive vd -> scopeValueDescription vd
+    | Pstr_value (recFlag, bindings) ->
+      if recFlag = Recursive then bindings |> List.iter scopeValueBinding;
+      bindings |> List.iter (fun vb -> iterator.value_binding iterator vb);
+      if recFlag = Nonrecursive then bindings |> List.iter scopeValueBinding;
+      processed := true
+    | Pstr_type (recFlag, decls) ->
+      if recFlag = Recursive then decls |> List.iter scopeTypeDeclaration;
+      decls |> List.iter (fun td -> iterator.type_declaration iterator td);
+      if recFlag = Nonrecursive then decls |> List.iter scopeTypeDeclaration;
+      processed := true
+    | Pstr_module mb ->
+      iterator.module_binding iterator mb;
+      scopeModuleBinding mb;
+      processed := true
+    | Pstr_recmodule mbs ->
+      mbs |> List.iter scopeModuleBinding;
+      mbs |> List.iter (fun b -> iterator.module_binding iterator b);
+      processed := true
+    | _ -> ());
+    if not !processed then
+      Ast_iterator.default_iterator.structure_item iterator item
+  in
+  let value_binding (iterator : Ast_iterator.iterator)
+      (value_binding : Parsetree.value_binding) =
+    let oldInJsxContext = !inJsxContext in
+    if Utils.isJsxComponent value_binding then inJsxContext := true;
+    (match value_binding with
+    | {pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType)}; pvb_expr}
+      when locHasCursor pvb_expr.pexp_loc -> (
+      (* Expression with derivable type annotation.
+         E.g: let x: someRecord = {<com>} *)
+      match
+        ( TypeUtils.contextPathFromCoreType coreType,
+          pvb_expr
+          |> CompletionExpressions.traverseExpr ~exprPath:[]
+               ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite )
+      with
+      | Some ctxPath, Some (prefix, nested) ->
+        setResult
+          (Completable.Cexpression
+             {contextPath = ctxPath; prefix; nested = List.rev nested})
+      | _ -> ())
+    | {pvb_pat = {ppat_desc = Ppat_var {loc}}; pvb_expr}
+      when locHasCursor pvb_expr.pexp_loc -> (
+      (* Expression without a type annotation. We can complete this if this
+         has compiled previously and there's a type available for the identifier itself.
+         This is nice because the type is assigned even if the assignment isn't complete.
+
+         E.g: let x = {name: "name", <com>}, when `x` has compiled. *)
+      match
+        pvb_expr
+        |> CompletionExpressions.traverseExpr ~exprPath:[] ~pos:posBeforeCursor
+             ~firstCharBeforeCursorNoWhite
+      with
+      | Some (prefix, nested) ->
+        (* This completion should be low prio, so let any deeper completion
+           hit first, and only set this TypeAtPos completion if nothing else
+           here hit. *)
+        Ast_iterator.default_iterator.value_binding iterator value_binding;
+        setResult
+          (Completable.Cexpression
+             {contextPath = CTypeAtPos loc; prefix; nested = List.rev nested})
+      | _ -> ())
+    | {
+     pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType); ppat_loc};
+     pvb_expr;
+    }
+      when locHasCursor value_binding.pvb_loc
+           && locHasCursor ppat_loc = false
+           && locHasCursor pvb_expr.pexp_loc = false
+           && CompletionExpressions.isExprHole pvb_expr -> (
+      (* Expression with derivable type annotation, when the expression is empty (expr hole).
+         E.g: let x: someRecord = <com> *)
+      match TypeUtils.contextPathFromCoreType coreType with
+      | Some ctxPath ->
+        setResult
+          (Completable.Cexpression
+             {contextPath = ctxPath; prefix = ""; nested = []})
+      | _ -> ())
+    | {pvb_pat; pvb_expr} when locHasCursor pvb_pat.ppat_loc -> (
+      (* Completing a destructuring.
+         E.g: let {<com>} = someVar *)
+      match
+        ( pvb_pat
+          |> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
+               ~firstCharBeforeCursorNoWhite ~posBeforeCursor,
+          exprToContextPath pvb_expr )
+      with
+      | Some (prefix, nested), Some ctxPath ->
+        setResult
+          (Completable.Cpattern
+             {
+               contextPath = ctxPath;
+               prefix;
+               nested = List.rev nested;
+               fallback = None;
+               patternMode = Destructuring;
+             })
+      | _ -> ())
+    | _ -> ());
+    Ast_iterator.default_iterator.value_binding iterator value_binding;
+    inJsxContext := oldInJsxContext
+  in
+  let signature (iterator : Ast_iterator.iterator)
+      (signature : Parsetree.signature) =
+    let oldScope = !scope in
+    Ast_iterator.default_iterator.signature iterator signature;
+    scope := oldScope
+  in
+  let signature_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.signature_item) =
+    let processed = ref false in
+    (match item.psig_desc with
+    | Psig_open {popen_lid} ->
+      scope := !scope |> Scope.addOpen ~lid:popen_lid.txt
+    | Psig_value vd -> scopeValueDescription vd
+    | Psig_type (recFlag, decls) ->
+      if recFlag = Recursive then decls |> List.iter scopeTypeDeclaration;
+      decls |> List.iter (fun td -> iterator.type_declaration iterator td);
+      if recFlag = Nonrecursive then decls |> List.iter scopeTypeDeclaration;
+      processed := true
+    | Psig_module md ->
+      iterator.module_declaration iterator md;
+      scopeModuleDeclaration md;
+      processed := true
+    | Psig_recmodule mds ->
+      mds |> List.iter scopeModuleDeclaration;
+      mds |> List.iter (fun d -> iterator.module_declaration iterator d);
+      processed := true
+    | _ -> ());
+    if not !processed then
+      Ast_iterator.default_iterator.signature_item iterator item
+  in
+  let attribute (iterator : Ast_iterator.iterator)
+      ((id, payload) : Parsetree.attribute) =
+    (if String.length id.txt >= 4 && String.sub id.txt 0 4 = "res." then
+       (* skip: internal parser attribute *) ()
+     else if id.loc.loc_ghost then ()
+     else if id.loc |> Loc.hasPos ~pos:posBeforeCursor then
+       let posStart, posEnd = Loc.range id.loc in
+       match
+         (Pos.positionToOffset text posStart, Pos.positionToOffset text posEnd)
+       with
+       | Some offsetStart, Some offsetEnd ->
+         (* Can't trust the parser's location
+            E.g. @foo. let x... gives as label @foo.let *)
+         let label =
+           let rawLabel =
+             String.sub text offsetStart (offsetEnd - offsetStart)
+           in
+           let ( ++ ) x y =
+             match (x, y) with
+             | Some i1, Some i2 -> Some (min i1 i2)
+             | Some _, None -> x
+             | None, _ -> y
+           in
+           let label =
+             match
+               String.index_opt rawLabel ' '
+               ++ String.index_opt rawLabel '\t'
+               ++ String.index_opt rawLabel '\r'
+               ++ String.index_opt rawLabel '\n'
+             with
+             | None -> rawLabel
+             | Some i -> String.sub rawLabel 0 i
+           in
+           if label <> "" && label.[0] = '@' then
+             String.sub label 1 (String.length label - 1)
+           else label
+         in
+         found := true;
+         if debug then
+           Printf.printf "Attribute id:%s:%s label:%s\n" id.txt
+             (Loc.toString id.loc) label;
+         setResult (Completable.Cdecorator label)
+       | _ -> ()
+     else if id.txt = "module" then
+       match payload with
+       | PStr
+           [
+             {
+               pstr_desc =
+                 Pstr_eval
+                   ( {pexp_loc; pexp_desc = Pexp_constant (Pconst_string (s, _))},
+                     _ );
+             };
+           ]
+         when locHasCursor pexp_loc ->
+         if Debug.verbose () then
+           print_endline "[decoratorCompletion] Found @module";
+         setResult (Completable.CdecoratorPayload (Module s))
+       | PStr
+           [
+             {
+               pstr_desc =
+                 Pstr_eval
+                   ( {
+                       pexp_desc =
+                         Pexp_record (({txt = Lident "from"}, fromExpr) :: _, _);
+                     },
+                     _ );
+             };
+           ]
+         when locHasCursor fromExpr.pexp_loc
+              || locIsEmpty fromExpr.pexp_loc
+                 && CompletionExpressions.isExprHole fromExpr -> (
+         if Debug.verbose () then
+           print_endline
+             "[decoratorCompletion] Found @module with import attributes and \
+              cursor on \"from\"";
+         match
+           ( locHasCursor fromExpr.pexp_loc,
+             locIsEmpty fromExpr.pexp_loc,
+             CompletionExpressions.isExprHole fromExpr,
+             fromExpr )
+         with
+         | true, _, _, {pexp_desc = Pexp_constant (Pconst_string (s, _))} ->
+           if Debug.verbose () then
+             print_endline
+               "[decoratorCompletion] @module `from` payload was string";
+           setResult (Completable.CdecoratorPayload (Module s))
+         | false, true, true, _ ->
+           if Debug.verbose () then
+             print_endline
+               "[decoratorCompletion] @module `from` payload was expr hole";
+           setResult (Completable.CdecoratorPayload (Module ""))
+         | _ -> ())
+       | PStr [{pstr_desc = Pstr_eval (expr, _)}] -> (
+         if Debug.verbose () then
+           print_endline
+             "[decoratorCompletion] Found @module with non-string payload";
+         match
+           CompletionExpressions.traverseExpr expr ~exprPath:[]
+             ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
+         with
+         | None -> ()
+         | Some (prefix, nested) ->
+           if Debug.verbose () then
+             print_endline "[decoratorCompletion] Found @module record path";
+           setResult
+             (Completable.CdecoratorPayload
+                (ModuleWithImportAttributes {nested = List.rev nested; prefix}))
+         )
+       | _ -> ()
+     else if id.txt = "jsxConfig" then
+       match payload with
+       | PStr [{pstr_desc = Pstr_eval (expr, _)}] -> (
+         if Debug.verbose () then
+           print_endline "[decoratorCompletion] Found @jsxConfig";
+         match
+           CompletionExpressions.traverseExpr expr ~exprPath:[]
+             ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
+         with
+         | None -> ()
+         | Some (prefix, nested) ->
+           if Debug.verbose () then
+             print_endline "[decoratorCompletion] Found @jsxConfig path!";
+           setResult
+             (Completable.CdecoratorPayload
+                (JsxConfig {nested = List.rev nested; prefix})))
+       | _ -> ());
+    Ast_iterator.default_iterator.attribute iterator (id, payload)
+  in
+  let rec iterateFnArguments ~args ~iterator ~isPipe
+      (argCompletable : Completable.t option) =
+    match argCompletable with
+    | None -> (
+      match !currentCtxPath with
+      | None -> ()
+      | Some functionContextPath ->
+        let currentUnlabelledCount = ref (if isPipe then 1 else 0) in
+        args
+        |> List.iter (fun (arg : arg) ->
+               let previousCtxPath = !currentCtxPath in
+               setCurrentCtxPath
+                 (CArgument
+                    {
+                      functionContextPath;
+                      argumentLabel =
+                        (match arg with
+                        | {label = None} ->
+                          let current = !currentUnlabelledCount in
+                          currentUnlabelledCount := current + 1;
+                          Unlabelled {argumentPosition = current}
+                        | {label = Some {name; opt = true}} -> Optional name
+                        | {label = Some {name; opt = false}} -> Labelled name);
+                    });
+               expr iterator arg.exp;
+               resetCurrentCtxPath previousCtxPath))
+    | Some argCompletable -> setResult argCompletable
+  and iterateJsxProps ~iterator (props : CompletionJsx.jsxProps) =
+    props.props
+    |> List.iter (fun (prop : CompletionJsx.prop) ->
+           let previousCtxPath = !currentCtxPath in
+           setCurrentCtxPath
+             (CJsxPropValue
+                {
+                  pathToComponent =
+                    Utils.flattenLongIdent ~jsx:true props.compName.txt;
+                  propName = prop.name;
+                  emptyJsxPropNameHint = None;
+                });
+           expr iterator prop.exp;
+           resetCurrentCtxPath previousCtxPath)
+  and expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression) =
+    let oldInJsxContext = !inJsxContext in
+    let processed = ref false in
+    let setFound () =
+      found := true;
+      if debug then
+        Printf.printf "posCursor:[%s] posNoWhite:[%s] Found expr:%s\n"
+          (Pos.toString posCursor) (Pos.toString posNoWhite)
+          (Loc.toString expr.pexp_loc)
+    in
+    (match findThisExprLoc with
+    | Some loc when expr.pexp_loc = loc -> (
+      match exprToContextPath expr with
+      | None -> ()
+      | Some ctxPath -> setResult (Cpath ctxPath))
+    | _ -> ());
+    let setPipeResult ~(lhs : Parsetree.expression) ~id =
+      match completePipeChain lhs with
+      | None -> (
+        match exprToContextPath lhs with
+        | Some pipe ->
+          setResult
+            (Cpath
+               (CPPipe
+                  {
+                    contextPath = pipe;
+                    id;
+                    lhsLoc = lhs.pexp_loc;
+                    inJsx = !inJsxContext;
+                  }));
+          true
+        | None -> false)
+      | Some (pipe, lhsLoc) ->
+        setResult
+          (Cpath
+             (CPPipe {contextPath = pipe; id; lhsLoc; inJsx = !inJsxContext}));
+        true
+    in
+    typedCompletionExpr expr;
+    match expr.pexp_desc with
+    | Pexp_match (expr, cases)
+      when cases <> []
+           && locHasCursor expr.pexp_loc = false
+           && Option.is_none findThisExprLoc ->
+      if Debug.verbose () then
+        print_endline "[completionFrontend] Checking each case";
+      let ctxPath = exprToContextPath expr in
+      let oldCtxPath = !currentCtxPath in
+      cases
+      |> List.iter (fun (case : Parsetree.case) ->
+             let oldScope = !scope in
+             if
+               locHasCursor case.pc_rhs.pexp_loc = false
+               && locHasCursor case.pc_lhs.ppat_loc
+             then completePattern ?contextPath:ctxPath case.pc_lhs;
+             scopePattern ?contextPath:ctxPath case.pc_lhs;
+             Ast_iterator.default_iterator.case iterator case;
+             scope := oldScope);
+      resetCurrentCtxPath oldCtxPath
+    | Pexp_apply
+        ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u"); loc = opLoc}},
+          [
+            (_, lhs);
+            (_, {pexp_desc = Pexp_extension _; pexp_loc = {loc_ghost = true}});
+          ] )
+      when opLoc |> Loc.hasPos ~pos:posBeforeCursor ->
+      (* Case foo-> when the parser adds a ghost expression to the rhs
+         so the apply expression does not include the cursor *)
+      if setPipeResult ~lhs ~id:"" then setFound ()
+    | _ -> (
+      if expr.pexp_loc |> Loc.hasPos ~pos:posNoWhite && !result = None then (
+        setFound ();
+        match expr.pexp_desc with
+        | Pexp_extension ({txt}, _) -> setResult (CextensionNode txt)
+        | Pexp_constant _ -> setResult Cnone
+        | Pexp_ident lid ->
+          let lidPath = flattenLidCheckDot lid in
+          if debug then
+            Printf.printf "Pexp_ident %s:%s\n"
+              (lidPath |> String.concat ".")
+              (Loc.toString lid.loc);
+          if lid.loc |> Loc.hasPos ~pos:posBeforeCursor then
+            let isLikelyModulePath =
+              match lidPath with
+              | head :: _
+                when String.length head > 0
+                     && head.[0] == Char.uppercase_ascii head.[0] ->
+                true
+              | _ -> false
+            in
+            setResult
+              (Cpath
+                 (CPId
+                    {
+                      loc = lid.loc;
+                      path = lidPath;
+                      completionContext =
+                        (if
+                           isLikelyModulePath
+                           && expr |> Res_parsetree_viewer.is_braced_expr
+                         then ValueOrField
+                         else Value);
+                    }))
+        | Pexp_construct ({txt = Lident ("::" | "()")}, _) ->
+          (* Ignore list expressions, used in JSX, unit, and more *) ()
+        | Pexp_construct (lid, eOpt) -> (
+          let lidPath = flattenLidCheckDot lid in
+          if debug && lid.txt <> Lident "Function$" then
+            Printf.printf "Pexp_construct %s:%s %s\n"
+              (lidPath |> String.concat "\n")
+              (Loc.toString lid.loc)
+              (match eOpt with
+              | None -> "None"
+              | Some e -> Loc.toString e.pexp_loc);
+          if
+            eOpt = None && (not lid.loc.loc_ghost)
+            && lid.loc |> Loc.hasPos ~pos:posBeforeCursor
+          then
+            setResult
+              (Cpath
+                 (CPId
+                    {loc = lid.loc; path = lidPath; completionContext = Value}))
+          else
+            match eOpt with
+            | Some e when locHasCursor e.pexp_loc -> (
+              match
+                CompletionExpressions.completeConstructorPayload
+                  ~posBeforeCursor ~firstCharBeforeCursorNoWhite lid e
+              with
+              | Some result ->
+                (* Check if anything else more important completes before setting this completion. *)
+                Ast_iterator.default_iterator.expr iterator e;
+                setResult result
+              | None -> ())
+            | _ -> ())
+        | Pexp_field (e, fieldName) -> (
+          if debug then
+            Printf.printf "Pexp_field %s %s:%s\n" (Loc.toString e.pexp_loc)
+              (Utils.flattenLongIdent fieldName.txt |> String.concat ".")
+              (Loc.toString fieldName.loc);
+          if fieldName.loc |> Loc.hasPos ~pos:posBeforeCursor then
+            match fieldName.txt with
+            | Lident name -> (
+              match exprToContextPath e with
+              | Some contextPath ->
+                let contextPath = Completable.CPField (contextPath, name) in
+                setResult (Cpath contextPath)
+              | None -> ())
+            | Ldot (id, name) ->
+              (* Case x.M.field ignore the x part *)
+              let contextPath =
+                Completable.CPField
+                  ( CPId
+                      {
+                        loc = fieldName.loc;
+                        path = Utils.flattenLongIdent id;
+                        completionContext = Module;
+                      },
+                    if blankAfterCursor = Some '.' then
+                      (* x.M. field  --->  M. *) ""
+                    else if name = "_" then ""
+                    else name )
+              in
+              setResult (Cpath contextPath)
+            | Lapply _ -> ()
+          else if Loc.end_ e.pexp_loc = posBeforeCursor then
+            match exprToContextPath e with
+            | Some contextPath -> setResult (Cpath (CPField (contextPath, "")))
+            | None -> ())
+        | Pexp_apply ({pexp_desc = Pexp_ident compName}, args)
+          when Res_parsetree_viewer.is_jsx_expression expr ->
+          inJsxContext := true;
+          let jsxProps = CompletionJsx.extractJsxProps ~compName ~args in
+          let compNamePath = flattenLidCheckDot ~jsx:true compName in
+          if debug then
+            Printf.printf "JSX <%s:%s %s> _children:%s\n"
+              (compNamePath |> String.concat ".")
+              (Loc.toString compName.loc)
+              (jsxProps.props
+              |> List.map
+                   (fun ({name; posStart; posEnd; exp} : CompletionJsx.prop) ->
+                     Printf.sprintf "%s[%s->%s]=...%s" name
+                       (Pos.toString posStart) (Pos.toString posEnd)
+                       (Loc.toString exp.pexp_loc))
+              |> String.concat " ")
+              (match jsxProps.childrenStart with
+              | None -> "None"
+              | Some childrenPosStart -> Pos.toString childrenPosStart);
+          let jsxCompletable =
+            CompletionJsx.findJsxPropsCompletable ~jsxProps
+              ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
+              ~posAfterCompName:(Loc.end_ compName.loc)
+              ~firstCharBeforeCursorNoWhite ~charAtCursor
+          in
+          if jsxCompletable <> None then setResultOpt jsxCompletable
+          else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then
+            setResult
+              (match compNamePath with
+              | [prefix] when Char.lowercase_ascii prefix.[0] = prefix.[0] ->
+                ChtmlElement {prefix}
+              | _ ->
+                Cpath
+                  (CPId
+                     {
+                       loc = compName.loc;
+                       path = compNamePath;
+                       completionContext = Module;
+                     }))
+          else iterateJsxProps ~iterator jsxProps
+        | Pexp_apply
+            ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+              [
+                (_, lhs);
+                (_, {pexp_desc = Pexp_ident {txt = Longident.Lident id; loc}});
+              ] )
+          when loc |> Loc.hasPos ~pos:posBeforeCursor ->
+          if Debug.verbose () then print_endline "[expr_iter] Case foo->id";
+          setPipeResult ~lhs ~id |> ignore
+        | Pexp_apply
+            ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u"); loc = opLoc}},
+              [(_, lhs); _] )
+          when Loc.end_ opLoc = posCursor ->
+          if Debug.verbose () then print_endline "[expr_iter] Case foo->";
+          setPipeResult ~lhs ~id:"" |> ignore
+        | Pexp_apply
+            ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+              [_; (_, {pexp_desc = Pexp_apply (funExpr, args)})] )
+          when (* Normally named arg completion fires when the cursor is right after the expression.
+                  E.g in foo(~<---there
+                  But it should not fire in foo(~a)<---there *)
+               not
+                 (Loc.end_ expr.pexp_loc = posCursor
+                 && charBeforeCursor = Some ')') -> (
+          (* Complete fn argument values and named args when the fn call is piped. E.g. someVar->someFn(<com>). *)
+          if Debug.verbose () then
+            print_endline "[expr_iter] Complete fn arguments (piped)";
+          let args = extractExpApplyArgs ~args in
+          let funCtxPath = exprToContextPath funExpr in
+          let argCompletable =
+            match funCtxPath with
+            | Some contextPath ->
+              findArgCompletables ~contextPath ~args
+                ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
+                ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc)
+                ~charBeforeCursor ~isPipedExpr:true
+                ~firstCharBeforeCursorNoWhite
+            | None -> None
+          in
+          match argCompletable with
+          | None -> (
+            match funCtxPath with
+            | None -> ()
+            | Some funCtxPath ->
+              let oldCtxPath = !currentCtxPath in
+              setCurrentCtxPath funCtxPath;
+              argCompletable |> iterateFnArguments ~isPipe:true ~args ~iterator;
+              resetCurrentCtxPath oldCtxPath)
+          | Some argCompletable -> setResult argCompletable)
+        | Pexp_apply
+            ({pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}}, [_; _]) ->
+          (* Ignore any other pipe. *)
+          ()
+        | Pexp_apply (funExpr, args)
+          when not
+                 (Loc.end_ expr.pexp_loc = posCursor
+                 && charBeforeCursor = Some ')') -> (
+          (* Complete fn argument values and named args when the fn call is _not_ piped. E.g. someFn(<com>). *)
+          if Debug.verbose () then
+            print_endline "[expr_iter] Complete fn arguments (not piped)";
+          let args = extractExpApplyArgs ~args in
+          if debug then
+            Printf.printf "Pexp_apply ...%s (%s)\n"
+              (Loc.toString funExpr.pexp_loc)
+              (args
+              |> List.map (fun {label; exp} ->
+                     Printf.sprintf "%s...%s"
+                       (match label with
+                       | None -> ""
+                       | Some {name; opt; posStart; posEnd} ->
+                         "~" ^ name ^ Pos.toString posStart ^ "->"
+                         ^ Pos.toString posEnd ^ "="
+                         ^ if opt then "?" else "")
+                       (Loc.toString exp.pexp_loc))
+              |> String.concat ", ");
+
+          let funCtxPath = exprToContextPath funExpr in
+          let argCompletable =
+            match funCtxPath with
+            | Some contextPath ->
+              findArgCompletables ~contextPath ~args
+                ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
+                ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc)
+                ~charBeforeCursor ~isPipedExpr:false
+                ~firstCharBeforeCursorNoWhite
+            | None -> None
+          in
+          match argCompletable with
+          | None -> (
+            match funCtxPath with
+            | None -> ()
+            | Some funCtxPath ->
+              let oldCtxPath = !currentCtxPath in
+              setCurrentCtxPath funCtxPath;
+              argCompletable |> iterateFnArguments ~isPipe:false ~args ~iterator;
+              resetCurrentCtxPath oldCtxPath)
+          | Some argCompletable -> setResult argCompletable)
+        | Pexp_send (lhs, {txt; loc}) -> (
+          (* e["txt"]
+             If the string for txt is not closed, it could go over several lines.
+             Only take the first like to represent the label *)
+          let txtLines = txt |> String.split_on_char '\n' in
+          let label = List.hd txtLines in
+          let label =
+            if label <> "" && label.[String.length label - 1] = '\r' then
+              String.sub label 0 (String.length label - 1)
+            else label
+          in
+          let labelRange =
+            let l, c = Loc.start loc in
+            ((l, c + 1), (l, c + 1 + String.length label))
+          in
+          if debug then
+            Printf.printf "Pexp_send %s%s e:%s\n" label
+              (Range.toString labelRange)
+              (Loc.toString lhs.pexp_loc);
+          if
+            labelRange |> Range.hasPos ~pos:posBeforeCursor
+            || (label = "" && posCursor = fst labelRange)
+          then
+            match exprToContextPath lhs with
+            | Some contextPath -> setResult (Cpath (CPObj (contextPath, label)))
+            | None -> ())
+        | Pexp_fun (lbl, defaultExpOpt, pat, e) ->
+          let oldScope = !scope in
+          (match (!processingFun, !currentCtxPath) with
+          | None, Some ctxPath -> processingFun := Some (ctxPath, 0)
+          | _ -> ());
+          let argContextPath =
+            match !processingFun with
+            | None -> None
+            | Some (ctxPath, currentUnlabelledCount) ->
+              (processingFun :=
+                 match lbl with
+                 | Nolabel -> Some (ctxPath, currentUnlabelledCount + 1)
+                 | _ -> Some (ctxPath, currentUnlabelledCount));
+              if Debug.verbose () then
+                print_endline "[expr_iter] Completing for argument value";
+              Some
+                (Completable.CArgument
+                   {
+                     functionContextPath = ctxPath;
+                     argumentLabel =
+                       (match lbl with
+                       | Nolabel ->
+                         Unlabelled {argumentPosition = currentUnlabelledCount}
+                       | Optional name -> Optional name
+                       | Labelled name -> Labelled name);
+                   })
+          in
+          (match defaultExpOpt with
+          | None -> ()
+          | Some defaultExp -> iterator.expr iterator defaultExp);
+          if locHasCursor e.pexp_loc = false then
+            completePattern ?contextPath:argContextPath pat;
+          scopePattern ?contextPath:argContextPath pat;
+          iterator.pat iterator pat;
+          iterator.expr iterator e;
+          scope := oldScope;
+          processed := true
+        | Pexp_let (recFlag, bindings, e) ->
+          let oldScope = !scope in
+          if recFlag = Recursive then bindings |> List.iter scopeValueBinding;
+          bindings |> List.iter (fun vb -> iterator.value_binding iterator vb);
+          if recFlag = Nonrecursive then bindings |> List.iter scopeValueBinding;
+          iterator.expr iterator e;
+          scope := oldScope;
+          processed := true
+        | Pexp_letmodule (name, modExpr, modBody) ->
+          let oldScope = !scope in
+          iterator.location iterator name.loc;
+          iterator.module_expr iterator modExpr;
+          scope := !scope |> Scope.addModule ~name:name.txt ~loc:name.loc;
+          iterator.expr iterator modBody;
+          scope := oldScope;
+          processed := true
+        | Pexp_open (_, lid, e) ->
+          let oldScope = !scope in
+          iterator.location iterator lid.loc;
+          scope := !scope |> Scope.addOpen ~lid:lid.txt;
+          iterator.expr iterator e;
+          scope := oldScope;
+          processed := true
+        | _ -> ());
+      if not !processed then Ast_iterator.default_iterator.expr iterator expr;
+      inJsxContext := oldInJsxContext;
+      match expr.pexp_desc with
+      | Pexp_fun _ -> ()
+      | _ -> processingFun := None)
+  in
+  let typ (iterator : Ast_iterator.iterator) (core_type : Parsetree.core_type) =
+    if core_type.ptyp_loc |> Loc.hasPos ~pos:posNoWhite then (
+      found := true;
+      if debug then
+        Printf.printf "posCursor:[%s] posNoWhite:[%s] Found type:%s\n"
+          (Pos.toString posCursor) (Pos.toString posNoWhite)
+          (Loc.toString core_type.ptyp_loc);
+      match core_type.ptyp_desc with
+      | Ptyp_constr (lid, _args) ->
+        let lidPath = flattenLidCheckDot lid in
+        if debug then
+          Printf.printf "Ptyp_constr %s:%s\n"
+            (lidPath |> String.concat ".")
+            (Loc.toString lid.loc);
+        if lid.loc |> Loc.hasPos ~pos:posBeforeCursor then
+          setResult
+            (Cpath
+               (CPId {loc = lid.loc; path = lidPath; completionContext = Type}))
+      | _ -> ());
+    Ast_iterator.default_iterator.typ iterator core_type
+  in
+  let pat (iterator : Ast_iterator.iterator) (pat : Parsetree.pattern) =
+    if pat.ppat_loc |> Loc.hasPos ~pos:posNoWhite then (
+      found := true;
+      if debug then
+        Printf.printf "posCursor:[%s] posNoWhite:[%s] Found pattern:%s\n"
+          (Pos.toString posCursor) (Pos.toString posNoWhite)
+          (Loc.toString pat.ppat_loc);
+      (match pat.ppat_desc with
+      | Ppat_construct (lid, _) -> (
+        let lidPath = flattenLidCheckDot lid in
+        if debug then
+          Printf.printf "Ppat_construct %s:%s\n"
+            (lidPath |> String.concat ".")
+            (Loc.toString lid.loc);
+        let completion =
+          Completable.Cpath
+            (CPId {loc = lid.loc; path = lidPath; completionContext = Value})
+        in
+        match !result with
+        | Some (Completable.Cpattern p, scope) ->
+          result := Some (Cpattern {p with fallback = Some completion}, scope)
+        | _ -> setResult completion)
+      | _ -> ());
+      Ast_iterator.default_iterator.pat iterator pat)
+  in
+  let module_expr (iterator : Ast_iterator.iterator)
+      (me : Parsetree.module_expr) =
+    (match me.pmod_desc with
+    | Pmod_ident lid when lid.loc |> Loc.hasPos ~pos:posBeforeCursor ->
+      let lidPath = flattenLidCheckDot lid in
+      if debug then
+        Printf.printf "Pmod_ident %s:%s\n"
+          (lidPath |> String.concat ".")
+          (Loc.toString lid.loc);
+      found := true;
+      setResult
+        (Cpath
+           (CPId {loc = lid.loc; path = lidPath; completionContext = Module}))
+    | _ -> ());
+    Ast_iterator.default_iterator.module_expr iterator me
+  in
+  let module_type (iterator : Ast_iterator.iterator)
+      (mt : Parsetree.module_type) =
+    (match mt.pmty_desc with
+    | Pmty_ident lid when lid.loc |> Loc.hasPos ~pos:posBeforeCursor ->
+      let lidPath = flattenLidCheckDot lid in
+      if debug then
+        Printf.printf "Pmty_ident %s:%s\n"
+          (lidPath |> String.concat ".")
+          (Loc.toString lid.loc);
+      found := true;
+      setResult
+        (Cpath
+           (CPId {loc = lid.loc; path = lidPath; completionContext = Module}))
+    | _ -> ());
+    Ast_iterator.default_iterator.module_type iterator mt
+  in
+  let type_kind (iterator : Ast_iterator.iterator)
+      (type_kind : Parsetree.type_kind) =
+    (match type_kind with
+    | Ptype_variant [decl]
+      when decl.pcd_name.loc |> Loc.hasPos ~pos:posNoWhite
+           && decl.pcd_args = Pcstr_tuple [] ->
+      (* "type t = Pre" could signal the intent to complete variant "Prelude",
+         or the beginning of "Prefix.t" *)
+      if debug then
+        Printf.printf "Ptype_variant unary %s:%s\n" decl.pcd_name.txt
+          (Loc.toString decl.pcd_name.loc);
+      found := true;
+      setResult
+        (Cpath
+           (CPId
+              {
+                loc = decl.pcd_name.loc;
+                path = [decl.pcd_name.txt];
+                completionContext = Value;
+              }))
+    | _ -> ());
+    Ast_iterator.default_iterator.type_kind iterator type_kind
+  in
+
+  let lastScopeBeforeCursor = ref (Scope.create ()) in
+  let location (_iterator : Ast_iterator.iterator) (loc : Location.t) =
+    if Loc.end_ loc <= posCursor then lastScopeBeforeCursor := !scope
+  in
+
+  let iterator =
+    {
+      Ast_iterator.default_iterator with
+      attribute;
+      expr;
+      location;
+      module_expr;
+      module_type;
+      pat;
+      signature;
+      signature_item;
+      structure;
+      structure_item;
+      typ;
+      type_kind;
+      value_binding;
+    }
+  in
+
+  if Filename.check_suffix path ".res" then (
+    let parser =
+      Res_driver.parsing_engine.parse_implementation ~for_printer:false
+    in
+    let {Res_driver.parsetree = str} = parser ~filename:currentFile in
+    iterator.structure iterator str |> ignore;
+    if blankAfterCursor = Some ' ' || blankAfterCursor = Some '\n' then (
+      scope := !lastScopeBeforeCursor;
+      setResult
+        (Cpath
+           (CPId {loc = Location.none; path = [""]; completionContext = Value})));
+    if !found = false then if debug then Printf.printf "XXX Not found!\n";
+    !result)
+  else if Filename.check_suffix path ".resi" then (
+    let parser = Res_driver.parsing_engine.parse_interface ~for_printer:false in
+    let {Res_driver.parsetree = signature} = parser ~filename:currentFile in
+    iterator.signature iterator signature |> ignore;
+    if blankAfterCursor = Some ' ' || blankAfterCursor = Some '\n' then (
+      scope := !lastScopeBeforeCursor;
+      setResult
+        (Cpath
+           (CPId {loc = Location.none; path = [""]; completionContext = Type})));
+    if !found = false then if debug then Printf.printf "XXX Not found!\n";
+    !result)
+  else None
+
+let completionWithParser ~debug ~path ~posCursor ~currentFile ~text =
+  match Pos.positionToOffset text posCursor with
+  | Some offset ->
+    completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor text
+  | None -> None
+
+let findTypeOfExpressionAtLoc ~debug ~path ~posCursor ~currentFile loc =
+  let textOpt = Files.readFile currentFile in
+  match textOpt with
+  | None | Some "" -> None
+  | Some text -> (
+    match Pos.positionToOffset text posCursor with
+    | Some offset ->
+      completionWithParser1 ~findThisExprLoc:loc ~currentFile ~debug ~offset
+        ~path ~posCursor text
+    | None -> None)
diff --git a/analysis/src/CompletionJsx.ml b/analysis/src/CompletionJsx.ml
new file mode 100644
index 0000000000..e7368daff0
--- /dev/null
+++ b/analysis/src/CompletionJsx.ml
@@ -0,0 +1,1021 @@
+open SharedTypes
+
+let domLabels =
+  let bool = "bool" in
+  let float = "float" in
+  let int = "int" in
+  let string = "string" in
+  [
+    ("ariaDetails", string);
+    ("ariaDisabled", bool);
+    ("ariaHidden", bool);
+    ("ariaKeyshortcuts", string);
+    ("ariaLabel", string);
+    ("ariaRoledescription", string);
+    ("ariaExpanded", bool);
+    ("ariaLevel", int);
+    ("ariaModal", bool);
+    ("ariaMultiline", bool);
+    ("ariaMultiselectable", bool);
+    ("ariaPlaceholder", string);
+    ("ariaReadonly", bool);
+    ("ariaRequired", bool);
+    ("ariaSelected", bool);
+    ("ariaSort", string);
+    ("ariaValuemax", float);
+    ("ariaValuemin", float);
+    ("ariaValuenow", float);
+    ("ariaValuetext", string);
+    ("ariaAtomic", bool);
+    ("ariaBusy", bool);
+    ("ariaRelevant", string);
+    ("ariaGrabbed", bool);
+    ("ariaActivedescendant", string);
+    ("ariaColcount", int);
+    ("ariaColindex", int);
+    ("ariaColspan", int);
+    ("ariaControls", string);
+    ("ariaDescribedby", string);
+    ("ariaErrormessage", string);
+    ("ariaFlowto", string);
+    ("ariaLabelledby", string);
+    ("ariaOwns", string);
+    ("ariaPosinset", int);
+    ("ariaRowcount", int);
+    ("ariaRowindex", int);
+    ("ariaRowspan", int);
+    ("ariaSetsize", int);
+    ("defaultChecked", bool);
+    ("defaultValue", string);
+    ("accessKey", string);
+    ("className", string);
+    ("contentEditable", bool);
+    ("contextMenu", string);
+    ("dir", string);
+    ("draggable", bool);
+    ("hidden", bool);
+    ("id", string);
+    ("lang", string);
+    ("style", "style");
+    ("spellCheck", bool);
+    ("tabIndex", int);
+    ("title", string);
+    ("itemID", string);
+    ("itemProp", string);
+    ("itemRef", string);
+    ("itemScope", bool);
+    ("itemType", string);
+    ("accept", string);
+    ("acceptCharset", string);
+    ("action", string);
+    ("allowFullScreen", bool);
+    ("alt", string);
+    ("async", bool);
+    ("autoComplete", string);
+    ("autoCapitalize", string);
+    ("autoFocus", bool);
+    ("autoPlay", bool);
+    ("challenge", string);
+    ("charSet", string);
+    ("checked", bool);
+    ("cite", string);
+    ("crossOrigin", string);
+    ("cols", int);
+    ("colSpan", int);
+    ("content", string);
+    ("controls", bool);
+    ("coords", string);
+    ("data", string);
+    ("dateTime", string);
+    ("default", bool);
+    ("defer", bool);
+    ("disabled", bool);
+    ("download", string);
+    ("encType", string);
+    ("form", string);
+    ("formAction", string);
+    ("formTarget", string);
+    ("formMethod", string);
+    ("headers", string);
+    ("height", string);
+    ("high", int);
+    ("href", string);
+    ("hrefLang", string);
+    ("htmlFor", string);
+    ("httpEquiv", string);
+    ("icon", string);
+    ("inputMode", string);
+    ("integrity", string);
+    ("keyType", string);
+    ("label", string);
+    ("list", string);
+    ("loop", bool);
+    ("low", int);
+    ("manifest", string);
+    ("max", string);
+    ("maxLength", int);
+    ("media", string);
+    ("mediaGroup", string);
+    ("method", string);
+    ("min", string);
+    ("minLength", int);
+    ("multiple", bool);
+    ("muted", bool);
+    ("name", string);
+    ("nonce", string);
+    ("noValidate", bool);
+    ("open_", bool);
+    ("optimum", int);
+    ("pattern", string);
+    ("placeholder", string);
+    ("playsInline", bool);
+    ("poster", string);
+    ("preload", string);
+    ("radioGroup", string);
+    ("readOnly", bool);
+    ("rel", string);
+    ("required", bool);
+    ("reversed", bool);
+    ("rows", int);
+    ("rowSpan", int);
+    ("sandbox", string);
+    ("scope", string);
+    ("scoped", bool);
+    ("scrolling", string);
+    ("selected", bool);
+    ("shape", string);
+    ("size", int);
+    ("sizes", string);
+    ("span", int);
+    ("src", string);
+    ("srcDoc", string);
+    ("srcLang", string);
+    ("srcSet", string);
+    ("start", int);
+    ("step", float);
+    ("summary", string);
+    ("target", string);
+    ("type_", string);
+    ("useMap", string);
+    ("value", string);
+    ("width", string);
+    ("wrap", string);
+    ("onCopy", "ReactEvent.Clipboard.t => unit");
+    ("onCut", "ReactEvent.Clipboard.t => unit");
+    ("onPaste", "ReactEvent.Clipboard.t => unit");
+    ("onCompositionEnd", "ReactEvent.Composition.t => unit");
+    ("onCompositionStart", "ReactEvent.Composition.t => unit");
+    ("onCompositionUpdate", "ReactEvent.Composition.t => unit");
+    ("onKeyDown", "ReactEvent.Keyboard.t => unit");
+    ("onKeyPress", "ReactEvent.Keyboard.t => unit");
+    ("onKeyUp", "ReactEvent.Keyboard.t => unit");
+    ("onFocus", "ReactEvent.Focus.t => unit");
+    ("onBlur", "ReactEvent.Focus.t => unit");
+    ("onChange", "ReactEvent.Form.t => unit");
+    ("onInput", "ReactEvent.Form.t => unit");
+    ("onSubmit", "ReactEvent.Form.t => unit");
+    ("onInvalid", "ReactEvent.Form.t => unit");
+    ("onClick", "ReactEvent.Mouse.t => unit");
+    ("onContextMenu", "ReactEvent.Mouse.t => unit");
+    ("onDoubleClick", "ReactEvent.Mouse.t => unit");
+    ("onDrag", "ReactEvent.Mouse.t => unit");
+    ("onDragEnd", "ReactEvent.Mouse.t => unit");
+    ("onDragEnter", "ReactEvent.Mouse.t => unit");
+    ("onDragExit", "ReactEvent.Mouse.t => unit");
+    ("onDragLeave", "ReactEvent.Mouse.t => unit");
+    ("onDragOver", "ReactEvent.Mouse.t => unit");
+    ("onDragStart", "ReactEvent.Mouse.t => unit");
+    ("onDrop", "ReactEvent.Mouse.t => unit");
+    ("onMouseDown", "ReactEvent.Mouse.t => unit");
+    ("onMouseEnter", "ReactEvent.Mouse.t => unit");
+    ("onMouseLeave", "ReactEvent.Mouse.t => unit");
+    ("onMouseMove", "ReactEvent.Mouse.t => unit");
+    ("onMouseOut", "ReactEvent.Mouse.t => unit");
+    ("onMouseOver", "ReactEvent.Mouse.t => unit");
+    ("onMouseUp", "ReactEvent.Mouse.t => unit");
+    ("onSelect", "ReactEvent.Selection.t => unit");
+    ("onTouchCancel", "ReactEvent.Touch.t => unit");
+    ("onTouchEnd", "ReactEvent.Touch.t => unit");
+    ("onTouchMove", "ReactEvent.Touch.t => unit");
+    ("onTouchStart", "ReactEvent.Touch.t => unit");
+    ("onPointerOver", "ReactEvent.Pointer.t => unit");
+    ("onPointerEnter", "ReactEvent.Pointer.t => unit");
+    ("onPointerDown", "ReactEvent.Pointer.t => unit");
+    ("onPointerMove", "ReactEvent.Pointer.t => unit");
+    ("onPointerUp", "ReactEvent.Pointer.t => unit");
+    ("onPointerCancel", "ReactEvent.Pointer.t => unit");
+    ("onPointerOut", "ReactEvent.Pointer.t => unit");
+    ("onPointerLeave", "ReactEvent.Pointer.t => unit");
+    ("onGotPointerCapture", "ReactEvent.Pointer.t => unit");
+    ("onLostPointerCapture", "ReactEvent.Pointer.t => unit");
+    ("onScroll", "ReactEvent.UI.t => unit");
+    ("onWheel", "ReactEvent.Wheel.t => unit");
+    ("onAbort", "ReactEvent.Media.t => unit");
+    ("onCanPlay", "ReactEvent.Media.t => unit");
+    ("onCanPlayThrough", "ReactEvent.Media.t => unit");
+    ("onDurationChange", "ReactEvent.Media.t => unit");
+    ("onEmptied", "ReactEvent.Media.t => unit");
+    ("onEncrypetd", "ReactEvent.Media.t => unit");
+    ("onEnded", "ReactEvent.Media.t => unit");
+    ("onError", "ReactEvent.Media.t => unit");
+    ("onLoadedData", "ReactEvent.Media.t => unit");
+    ("onLoadedMetadata", "ReactEvent.Media.t => unit");
+    ("onLoadStart", "ReactEvent.Media.t => unit");
+    ("onPause", "ReactEvent.Media.t => unit");
+    ("onPlay", "ReactEvent.Media.t => unit");
+    ("onPlaying", "ReactEvent.Media.t => unit");
+    ("onProgress", "ReactEvent.Media.t => unit");
+    ("onRateChange", "ReactEvent.Media.t => unit");
+    ("onSeeked", "ReactEvent.Media.t => unit");
+    ("onSeeking", "ReactEvent.Media.t => unit");
+    ("onStalled", "ReactEvent.Media.t => unit");
+    ("onSuspend", "ReactEvent.Media.t => unit");
+    ("onTimeUpdate", "ReactEvent.Media.t => unit");
+    ("onVolumeChange", "ReactEvent.Media.t => unit");
+    ("onWaiting", "ReactEvent.Media.t => unit");
+    ("onAnimationStart", "ReactEvent.Animation.t => unit");
+    ("onAnimationEnd", "ReactEvent.Animation.t => unit");
+    ("onAnimationIteration", "ReactEvent.Animation.t => unit");
+    ("onTransitionEnd", "ReactEvent.Transition.t => unit");
+    ("accentHeight", string);
+    ("accumulate", string);
+    ("additive", string);
+    ("alignmentBaseline", string);
+    ("allowReorder", string);
+    ("alphabetic", string);
+    ("amplitude", string);
+    ("arabicForm", string);
+    ("ascent", string);
+    ("attributeName", string);
+    ("attributeType", string);
+    ("autoReverse", string);
+    ("azimuth", string);
+    ("baseFrequency", string);
+    ("baseProfile", string);
+    ("baselineShift", string);
+    ("bbox", string);
+    ("bias", string);
+    ("by", string);
+    ("calcMode", string);
+    ("capHeight", string);
+    ("clip", string);
+    ("clipPath", string);
+    ("clipPathUnits", string);
+    ("clipRule", string);
+    ("colorInterpolation", string);
+    ("colorInterpolationFilters", string);
+    ("colorProfile", string);
+    ("colorRendering", string);
+    ("contentScriptType", string);
+    ("contentStyleType", string);
+    ("cursor", string);
+    ("cx", string);
+    ("cy", string);
+    ("d", string);
+    ("decelerate", string);
+    ("descent", string);
+    ("diffuseConstant", string);
+    ("direction", string);
+    ("display", string);
+    ("divisor", string);
+    ("dominantBaseline", string);
+    ("dur", string);
+    ("dx", string);
+    ("dy", string);
+    ("edgeMode", string);
+    ("elevation", string);
+    ("enableBackground", string);
+    ("exponent", string);
+    ("externalResourcesRequired", string);
+    ("fill", string);
+    ("fillOpacity", string);
+    ("fillRule", string);
+    ("filter", string);
+    ("filterRes", string);
+    ("filterUnits", string);
+    ("floodColor", string);
+    ("floodOpacity", string);
+    ("focusable", string);
+    ("fontFamily", string);
+    ("fontSize", string);
+    ("fontSizeAdjust", string);
+    ("fontStretch", string);
+    ("fontStyle", string);
+    ("fontVariant", string);
+    ("fontWeight", string);
+    ("fomat", string);
+    ("from", string);
+    ("fx", string);
+    ("fy", string);
+    ("g1", string);
+    ("g2", string);
+    ("glyphName", string);
+    ("glyphOrientationHorizontal", string);
+    ("glyphOrientationVertical", string);
+    ("glyphRef", string);
+    ("gradientTransform", string);
+    ("gradientUnits", string);
+    ("hanging", string);
+    ("horizAdvX", string);
+    ("horizOriginX", string);
+    ("ideographic", string);
+    ("imageRendering", string);
+    ("in2", string);
+    ("intercept", string);
+    ("k", string);
+    ("k1", string);
+    ("k2", string);
+    ("k3", string);
+    ("k4", string);
+    ("kernelMatrix", string);
+    ("kernelUnitLength", string);
+    ("kerning", string);
+    ("keyPoints", string);
+    ("keySplines", string);
+    ("keyTimes", string);
+    ("lengthAdjust", string);
+    ("letterSpacing", string);
+    ("lightingColor", string);
+    ("limitingConeAngle", string);
+    ("local", string);
+    ("markerEnd", string);
+    ("markerHeight", string);
+    ("markerMid", string);
+    ("markerStart", string);
+    ("markerUnits", string);
+    ("markerWidth", string);
+    ("mask", string);
+    ("maskContentUnits", string);
+    ("maskUnits", string);
+    ("mathematical", string);
+    ("mode", string);
+    ("numOctaves", string);
+    ("offset", string);
+    ("opacity", string);
+    ("operator", string);
+    ("order", string);
+    ("orient", string);
+    ("orientation", string);
+    ("origin", string);
+    ("overflow", string);
+    ("overflowX", string);
+    ("overflowY", string);
+    ("overlinePosition", string);
+    ("overlineThickness", string);
+    ("paintOrder", string);
+    ("panose1", string);
+    ("pathLength", string);
+    ("patternContentUnits", string);
+    ("patternTransform", string);
+    ("patternUnits", string);
+    ("pointerEvents", string);
+    ("points", string);
+    ("pointsAtX", string);
+    ("pointsAtY", string);
+    ("pointsAtZ", string);
+    ("preserveAlpha", string);
+    ("preserveAspectRatio", string);
+    ("primitiveUnits", string);
+    ("r", string);
+    ("radius", string);
+    ("refX", string);
+    ("refY", string);
+    ("renderingIntent", string);
+    ("repeatCount", string);
+    ("repeatDur", string);
+    ("requiredExtensions", string);
+    ("requiredFeatures", string);
+    ("restart", string);
+    ("result", string);
+    ("rotate", string);
+    ("rx", string);
+    ("ry", string);
+    ("scale", string);
+    ("seed", string);
+    ("shapeRendering", string);
+    ("slope", string);
+    ("spacing", string);
+    ("specularConstant", string);
+    ("specularExponent", string);
+    ("speed", string);
+    ("spreadMethod", string);
+    ("startOffset", string);
+    ("stdDeviation", string);
+    ("stemh", string);
+    ("stemv", string);
+    ("stitchTiles", string);
+    ("stopColor", string);
+    ("stopOpacity", string);
+    ("strikethroughPosition", string);
+    ("strikethroughThickness", string);
+    (string, string);
+    ("stroke", string);
+    ("strokeDasharray", string);
+    ("strokeDashoffset", string);
+    ("strokeLinecap", string);
+    ("strokeLinejoin", string);
+    ("strokeMiterlimit", string);
+    ("strokeOpacity", string);
+    ("strokeWidth", string);
+    ("surfaceScale", string);
+    ("systemLanguage", string);
+    ("tableValues", string);
+    ("targetX", string);
+    ("targetY", string);
+    ("textAnchor", string);
+    ("textDecoration", string);
+    ("textLength", string);
+    ("textRendering", string);
+    ("transform", string);
+    ("u1", string);
+    ("u2", string);
+    ("underlinePosition", string);
+    ("underlineThickness", string);
+    ("unicode", string);
+    ("unicodeBidi", string);
+    ("unicodeRange", string);
+    ("unitsPerEm", string);
+    ("vAlphabetic", string);
+    ("vHanging", string);
+    ("vIdeographic", string);
+    ("vMathematical", string);
+    ("values", string);
+    ("vectorEffect", string);
+    ("version", string);
+    ("vertAdvX", string);
+    ("vertAdvY", string);
+    ("vertOriginX", string);
+    ("vertOriginY", string);
+    ("viewBox", string);
+    ("viewTarget", string);
+    ("visibility", string);
+    ("widths", string);
+    ("wordSpacing", string);
+    ("writingMode", string);
+    ("x", string);
+    ("x1", string);
+    ("x2", string);
+    ("xChannelSelector", string);
+    ("xHeight", string);
+    ("xlinkActuate", string);
+    ("xlinkArcrole", string);
+    ("xlinkHref", string);
+    ("xlinkRole", string);
+    ("xlinkShow", string);
+    ("xlinkTitle", string);
+    ("xlinkType", string);
+    ("xmlns", string);
+    ("xmlnsXlink", string);
+    ("xmlBase", string);
+    ("xmlLang", string);
+    ("xmlSpace", string);
+    ("y", string);
+    ("y1", string);
+    ("y2", string);
+    ("yChannelSelector", string);
+    ("z", string);
+    ("zoomAndPan", string);
+    ("about", string);
+    ("datatype", string);
+    ("inlist", string);
+    ("prefix", string);
+    ("property", string);
+    ("resource", string);
+    ("typeof", string);
+    ("vocab", string);
+    ("dangerouslySetInnerHTML", "{\"__html\": string}");
+    ("suppressContentEditableWarning", bool);
+  ]
+
+(* List and explanations taken from
+   https://www.tutorialrepublic.com/html-reference/html5-tags.php. *)
+let htmlElements =
+  [
+    ("a", "Defines a hyperlink.", false);
+    ("abbr", "Defines an abbreviated form of a longer word or phrase.", false);
+    ("acronym", "Defines an acronym. Use <abbr> instead.", true);
+    ("address", "Specifies the author's contact information.", false);
+    ( "applet",
+      "Embeds a Java applet (mini Java applications) on the page. Use <object> \
+       instead.",
+      true );
+    ("area", "Defines a specific area within an image map.", false);
+    ("article", "Defines an article.", false);
+    ("aside", "Defines some content loosely related to the page content.", false);
+    ("audio", "Embeds a sound, or an audio stream in an HTML document.", false);
+    ("b", "Displays text in a bold style.", false);
+    ("base", "Defines the base URL for all relative URLs in a document.", false);
+    ("basefont", "Specifies the base font for a page. Use CSS instead.", true);
+    ( "bdi",
+      "Represents text that is isolated from its surrounding for the purposes \
+       of bidirectional text formatting.",
+      false );
+    ("bdo", "Overrides the current text direction.", false);
+    ("big", "Displays text in a large size. Use CSS instead.", true);
+    ( "blockquote",
+      "Represents a section that is quoted from another source.",
+      false );
+    ("body", "Defines the document's body.", false);
+    ("br", "Produces a single line break.", false);
+    ("button", "Creates a clickable button.", false);
+    ( "canvas",
+      "Defines a region in the document, which can be used to draw graphics on \
+       the fly via scripting (usually JavaScript).",
+      false );
+    ("caption", "Defines the caption or title of the table.", false);
+    ("center", "Align contents in the center. Use CSS instead.", true);
+    ("cite", "Indicates a citation or reference to another source.", false);
+    ("code", "Specifies text as computer code.", false);
+    ( "col",
+      "Defines attribute values for one or more columns in a table.",
+      false );
+    ("colgroup", "Specifies attributes for multiple columns in a table.", false);
+    ( "data",
+      "Links a piece of content with a machine-readable translation.",
+      false );
+    ( "datalist",
+      "Represents a set of pre-defined options for an <input> element.",
+      false );
+    ( "dd",
+      "Specifies a description, or value for the term (<dt>) in a description \
+       list (<dl>).",
+      false );
+    ("del", "Represents text that has been deleted from the document.", false);
+    ( "details",
+      "Represents a widget from which the user can obtain additional \
+       information or controls on-demand.",
+      false );
+    ("dfn", "Specifies a definition.", false);
+    ("dialog", "Defines a dialog box or subwindow.", false);
+    ("dir", "Defines a directory list. Use <ul> instead.", true);
+    ("div", "Specifies a division or a section in a document.", false);
+    ("dl", "Defines a description list.", false);
+    ("dt", "Defines a term (an item) in a description list.", false);
+    ("em", "Defines emphasized text.", false);
+    ( "embed",
+      "Embeds external application, typically multimedia content like audio or \
+       video into an HTML document.",
+      false );
+    ("fieldset", "Specifies a set of related form fields.", false);
+    ("figcaption", "Defines a caption or legend for a figure.", false);
+    ("figure", "Represents a figure illustrated as part of the document.", false);
+    ("font", "Defines font, color, and size for text. Use CSS instead.", true);
+    ("footer", "Represents the footer of a document or a section.", false);
+    ("form", "Defines an HTML form for user input.", false);
+    ("frame", "Defines a single frame within a frameset.", true);
+    ("frameset", "Defines a collection of frames or other frameset.", true);
+    ( "head",
+      "Defines the head portion of the document that contains information \
+       about the document such as title.",
+      false );
+    ("header", "Represents the header of a document or a section.", false);
+    ("hgroup", "Defines a group of headings.", false);
+    ("h1", "Defines HTML headings.", false);
+    ("h2", "Defines HTML headings.", false);
+    ("h3", "Defines HTML headings.", false);
+    ("h4", "Defines HTML headings.", false);
+    ("h5", "Defines HTML headings.", false);
+    ("h6", "Defines HTML headings.", false);
+    ("hr", "Produce a horizontal line.", false);
+    ("html", "Defines the root of an HTML document.", false);
+    ("i", "Displays text in an italic style.", false);
+    ("iframe", "Displays a URL in an inline frame.", false);
+    ("img", "Represents an image.", false);
+    ("input", "Defines an input control.", false);
+    ( "ins",
+      "Defines a block of text that has been inserted into a document.",
+      false );
+    ("kbd", "Specifies text as keyboard input.", false);
+    ( "keygen",
+      "Represents a control for generating a public-private key pair.",
+      false );
+    ("label", "Defines a label for an <input> control.", false);
+    ("legend", "Defines a caption for a <fieldset> element.", false);
+    ("li", "Defines a list item.", false);
+    ( "link",
+      "Defines the relationship between the current document and an external \
+       resource.",
+      false );
+    ("main", "Represents the main or dominant content of the document.", false);
+    ("map", "Defines a client-side image-map.", false);
+    ("mark", "Represents text highlighted for reference purposes.", false);
+    ("menu", "Represents a list of commands.", false);
+    ( "menuitem",
+      "Defines a list (or menuitem) of commands that a user can perform.",
+      false );
+    ("meta", "Provides structured metadata about the document content.", false);
+    ("meter", "Represents a scalar measurement within a known range.", false);
+    ("nav", "Defines a section of navigation links.", false);
+    ( "noframes",
+      "Defines an alternate content that displays in browsers that do not \
+       support frames.",
+      true );
+    ( "noscript",
+      "Defines alternative content to display when the browser doesn't support \
+       scripting.",
+      false );
+    ("object", "Defines an embedded object.", false);
+    ("ol", "Defines an ordered list.", false);
+    ( "optgroup",
+      "Defines a group of related options in a selection list.",
+      false );
+    ("option", "Defines an option in a selection list.", false);
+    ("output", "Represents the result of a calculation.", false);
+    ("p", "Defines a paragraph.", false);
+    ("param", "Defines a parameter for an object or applet element.", false);
+    ("picture", "Defines a container for multiple image sources.", false);
+    ("pre", "Defines a block of preformatted text.", false);
+    ("progress", "Represents the completion progress of a task.", false);
+    ("q", "Defines a short inline quotation.", false);
+    ( "rp",
+      "Provides fall-back parenthesis for browsers that that don't support \
+       ruby annotations.",
+      false );
+    ( "rt",
+      "Defines the pronunciation of character presented in a ruby annotations.",
+      false );
+    ("ruby", "Represents a ruby annotation.", false);
+    ( "s",
+      "Represents contents that are no longer accurate or no longer relevant.",
+      false );
+    ("samp", "Specifies text as sample output from a computer program.", false);
+    ( "script",
+      "Places script in the document for client-side processing.",
+      false );
+    ( "section",
+      "Defines a section of a document, such as header, footer etc.",
+      false );
+    ("select", "Defines a selection list within a form.", false);
+    ("small", "Displays text in a smaller size.", false);
+    ( "source",
+      "Defines alternative media resources for the media elements like <audio> \
+       or <video>.",
+      false );
+    ("span", "Defines an inline styleless section in a document.", false);
+    ("strike", "Displays text in strikethrough style.", true);
+    ("strong", "Indicate strongly emphasized text.", false);
+    ( "style",
+      "Inserts style information (commonly CSS) into the head of a document.",
+      false );
+    ("sub", "Defines subscripted text.", false);
+    ("summary", "Defines a summary for the <details> element.", false);
+    ("sup", "Defines superscripted text.", false);
+    ( "svg",
+      "Embed SVG (Scalable Vector Graphics) content in an HTML document.",
+      false );
+    ("table", "Defines a data table.", false);
+    ( "tbody",
+      "Groups a set of rows defining the main body of the table data.",
+      false );
+    ("td", "Defines a cell in a table.", false);
+    ( "template",
+      "Defines the fragments of HTML that should be hidden when the page is \
+       loaded, but can be cloned and inserted in the document by JavaScript.",
+      false );
+    ("textarea", "Defines a multi-line text input control (text area).", false);
+    ( "tfoot",
+      "Groups a set of rows summarizing the columns of the table.",
+      false );
+    ("th", "Defines a header cell in a table.", false);
+    ( "thead",
+      "Groups a set of rows that describes the column labels of a table.",
+      false );
+    ("time", "Represents a time and/or date.", false);
+    ("title", "Defines a title for the document.", false);
+    ("tr", "Defines a row of cells in a table.", false);
+    ( "track",
+      "Defines text tracks for the media elements like <audio> or <video>.",
+      false );
+    ("tt", "Displays text in a teletype style.", true);
+    ("u", "Displays text with an underline.", false);
+    ("ul", "Defines an unordered list.", false);
+    ("var", "Defines a variable.", false);
+    ("video", "Embeds video content in an HTML document.", false);
+    ("wbr", "Represents a line break opportunity.", false);
+  ]
+
+let getJsxLabels ~componentPath ~findTypeOfValue ~package =
+  match componentPath @ ["make"] |> findTypeOfValue with
+  | Some (typ, make_env) ->
+    let rec getFieldsV3 (texp : Types.type_expr) =
+      match texp.desc with
+      | Tfield (name, _, t1, t2) ->
+        let fields = t2 |> getFieldsV3 in
+        if name = "children" then fields else (name, t1, make_env) :: fields
+      | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFieldsV3
+      | Tvar None -> []
+      | _ -> []
+    in
+    let getFieldsV4 ~path ~typeArgs =
+      match References.digConstructor ~env:make_env ~package path with
+      | Some
+          ( env,
+            {
+              item =
+                {
+                  decl =
+                    {
+                      type_kind = Type_record (labelDecls, _repr);
+                      type_params = typeParams;
+                    };
+                };
+            } ) ->
+        labelDecls
+        |> List.map (fun (ld : Types.label_declaration) ->
+               let name = Ident.name ld.ld_id in
+               let t =
+                 ld.ld_type |> TypeUtils.instantiateType ~typeParams ~typeArgs
+               in
+               (name, t, env))
+      | _ -> []
+    in
+    let rec getLabels (t : Types.type_expr) =
+      match t.desc with
+      | Tlink t1
+      | Tsubst t1
+      | Tpoly (t1, [])
+      | Tconstr (Pident {name = "function$"}, [t1; _], _) ->
+        getLabels t1
+      | Tarrow
+          ( Nolabel,
+            {
+              desc =
+                ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
+                | Tobject (tObj, _) );
+            },
+            _,
+            _ ) ->
+        (* JSX V3 *)
+        getFieldsV3 tObj
+      | Tconstr (p, [propsType], _) when Path.name p = "React.component" -> (
+        let rec getPropsType (t : Types.type_expr) =
+          match t.desc with
+          | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getPropsType t1
+          | Tconstr (path, typeArgs, _) when Path.last path = "props" ->
+            Some (path, typeArgs)
+          | _ -> None
+        in
+        match propsType |> getPropsType with
+        | Some (path, typeArgs) -> getFieldsV4 ~path ~typeArgs
+        | None -> [])
+      | Tarrow (Nolabel, {desc = Tconstr (path, typeArgs, _)}, _, _)
+        when Path.last path = "props" ->
+        (* JSX V4 *)
+        getFieldsV4 ~path ~typeArgs
+      | Tconstr
+          ( clPath,
+            [
+              {
+                desc =
+                  ( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
+                  | Tobject (tObj, _) );
+              };
+              _;
+            ],
+            _ )
+        when Path.name clPath = "React.componentLike" ->
+        (* JSX V3 external or interface *)
+        getFieldsV3 tObj
+      | Tconstr (clPath, [{desc = Tconstr (path, typeArgs, _)}; _], _)
+        when Path.name clPath = "React.componentLike"
+             && Path.last path = "props" ->
+        (* JSX V4 external or interface *)
+        getFieldsV4 ~path ~typeArgs
+      | Tarrow (Nolabel, typ, _, _) -> (
+        (* Component without the JSX PPX, like a make fn taking a hand-written
+           type props. *)
+        let rec digToConstr typ =
+          match typ.Types.desc with
+          | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> digToConstr t1
+          | Tconstr (path, typeArgs, _) when Path.last path = "props" ->
+            Some (path, typeArgs)
+          | _ -> None
+        in
+        match digToConstr typ with
+        | None -> []
+        | Some (path, typeArgs) -> getFieldsV4 ~path ~typeArgs)
+      | _ -> []
+    in
+    typ |> getLabels
+  | None -> []
+
+type prop = {
+  name: string;
+  posStart: int * int;
+  posEnd: int * int;
+  exp: Parsetree.expression;
+}
+
+type jsxProps = {
+  compName: Longident.t Location.loc;
+  props: prop list;
+  childrenStart: (int * int) option;
+}
+
+(** 
+<div muted= />
+
+This is a special case for JSX props, where the above code is parsed 
+as <div muted=//, a regexp literal. We leverage that fact to trigger completion
+for the JSX prop value.
+
+This code is safe because we also check that the location of the expression is broken,
+which only happens when the expression is a parse error/not complete.
+*)
+let isRegexpJsxHeuristicExpr expr =
+  match expr.Parsetree.pexp_desc with
+  | Pexp_extension
+      ( {txt = "re"},
+        PStr
+          [
+            {
+              pstr_desc =
+                Pstr_eval
+                  ({pexp_desc = Pexp_constant (Pconst_string ("//", _))}, _);
+            };
+          ] )
+    when expr.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) ->
+    true
+  | _ -> false
+
+let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
+    ~firstCharBeforeCursorNoWhite ~charAtCursor ~posAfterCompName =
+  let allLabels =
+    List.fold_right
+      (fun prop allLabels -> prop.name :: allLabels)
+      jsxProps.props []
+  in
+  let beforeChildrenStart =
+    match jsxProps.childrenStart with
+    | Some childrenPos -> posBeforeCursor < childrenPos
+    | None -> posBeforeCursor <= endPos
+  in
+  let rec loop props =
+    match props with
+    | prop :: rest ->
+      if prop.posStart <= posBeforeCursor && posBeforeCursor < prop.posEnd then (
+        if Debug.verbose () then
+          print_endline "[jsx_props_completable]--> Cursor on the prop name";
+
+        Some
+          (Completable.Cjsx
+             ( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
+               prop.name,
+               allLabels )))
+      else if
+        prop.posEnd <= posBeforeCursor
+        && posBeforeCursor < Loc.start prop.exp.pexp_loc
+      then (
+        if Debug.verbose () then
+          print_endline
+            "[jsx_props_completable]--> Cursor between the prop name and expr \
+             assigned";
+        match (firstCharBeforeCursorNoWhite, prop.exp) with
+        | Some '=', {pexp_desc = Pexp_ident {txt = Lident txt}} ->
+          if Debug.verbose () then
+            Printf.printf
+              "[jsx_props_completable]--> Heuristic for empty JSX prop expr \
+               completion.\n";
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CJsxPropValue
+                     {
+                       pathToComponent =
+                         Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
+                       propName = prop.name;
+                       emptyJsxPropNameHint = Some txt;
+                     };
+                 nested = [];
+                 prefix = "";
+               })
+        | _ -> None)
+      else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
+        if Debug.verbose () then
+          print_endline "[jsx_props_completable]--> Cursor on expr assigned";
+        match
+          CompletionExpressions.traverseExpr prop.exp ~exprPath:[]
+            ~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
+        with
+        | Some (prefix, nested) ->
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CJsxPropValue
+                     {
+                       pathToComponent =
+                         Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
+                       propName = prop.name;
+                       emptyJsxPropNameHint = None;
+                     };
+                 nested = List.rev nested;
+                 prefix;
+               })
+        | _ -> None)
+      else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then (
+        if Debug.verbose () then
+          print_endline "[jsx_props_completable]--> Loc is broken";
+        if
+          CompletionExpressions.isExprHole prop.exp
+          || isRegexpJsxHeuristicExpr prop.exp
+        then (
+          if Debug.verbose () then
+            print_endline
+              "[jsx_props_completable]--> Expr was expr hole or regexp literal \
+               heuristic";
+          Some
+            (Cexpression
+               {
+                 contextPath =
+                   CJsxPropValue
+                     {
+                       pathToComponent =
+                         Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
+                       propName = prop.name;
+                       emptyJsxPropNameHint = None;
+                     };
+                 prefix = "";
+                 nested = [];
+               }))
+        else None)
+      else if
+        rest = [] && beforeChildrenStart && charAtCursor = '>'
+        && firstCharBeforeCursorNoWhite = Some '='
+      then (
+        (* This is a special case for: <SomeComponent someProp=> (completing directly after the '=').
+           The completion comes at the end of the component, after the equals sign, but before any
+           children starts, and '>' marks that it's at the end of the component JSX.
+           This little heuristic makes sure we pick up this special case. *)
+        if Debug.verbose () then
+          print_endline
+            "[jsx_props_completable]--> Special case: last prop, '>' after \
+             cursor";
+        Some
+          (Cexpression
+             {
+               contextPath =
+                 CJsxPropValue
+                   {
+                     pathToComponent =
+                       Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
+                     propName = prop.name;
+                     emptyJsxPropNameHint = None;
+                   };
+               prefix = "";
+               nested = [];
+             }))
+      else loop rest
+    | [] ->
+      let afterCompName = posBeforeCursor >= posAfterCompName in
+      if afterCompName && beforeChildrenStart then (
+        if Debug.verbose () then
+          print_endline "[jsx_props_completable]--> Complete for JSX prop name";
+        Some
+          (Cjsx
+             ( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
+               "",
+               allLabels )))
+      else None
+  in
+  loop jsxProps.props
+
+let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
+  let thisCaseShouldNotHappen =
+    {
+      compName = Location.mknoloc (Longident.Lident "");
+      props = [];
+      childrenStart = None;
+    }
+  in
+  let rec processProps ~acc args =
+    match args with
+    | (Asttypes.Labelled "children", {Parsetree.pexp_loc}) :: _ ->
+      {
+        compName;
+        props = List.rev acc;
+        childrenStart =
+          (if pexp_loc.loc_ghost then None else Some (Loc.start pexp_loc));
+      }
+    | ((Labelled s | Optional s), (eProp : Parsetree.expression)) :: rest -> (
+      let namedArgLoc =
+        eProp.pexp_attributes
+        |> List.find_opt (fun ({Asttypes.txt}, _) -> txt = "res.namedArgLoc")
+      in
+      match namedArgLoc with
+      | Some ({loc}, _) ->
+        processProps
+          ~acc:
+            ({
+               name = s;
+               posStart = Loc.start loc;
+               posEnd = Loc.end_ loc;
+               exp = eProp;
+             }
+            :: acc)
+          rest
+      | None -> processProps ~acc rest)
+    | _ -> thisCaseShouldNotHappen
+  in
+  args |> processProps ~acc:[]
diff --git a/analysis/src/CompletionPatterns.ml b/analysis/src/CompletionPatterns.ml
new file mode 100644
index 0000000000..296e457c94
--- /dev/null
+++ b/analysis/src/CompletionPatterns.ml
@@ -0,0 +1,261 @@
+open SharedTypes
+
+let isPatternHole pat =
+  match pat.Parsetree.ppat_desc with
+  | Ppat_extension ({txt = "rescript.patternhole"}, _) -> true
+  | _ -> false
+
+let isPatternTuple pat =
+  match pat.Parsetree.ppat_desc with
+  | Ppat_tuple _ -> true
+  | _ -> false
+
+let rec traverseTupleItems tupleItems ~nextPatternPath ~resultFromFoundItemNum
+    ~locHasCursor ~firstCharBeforeCursorNoWhite ~posBeforeCursor =
+  let itemNum = ref (-1) in
+  let itemWithCursor =
+    tupleItems
+    |> List.find_map (fun pat ->
+           itemNum := !itemNum + 1;
+           pat
+           |> traversePattern ~patternPath:(nextPatternPath !itemNum)
+                ~locHasCursor ~firstCharBeforeCursorNoWhite ~posBeforeCursor)
+  in
+  match (itemWithCursor, firstCharBeforeCursorNoWhite) with
+  | None, Some ',' ->
+    (* No tuple item has the cursor, but there's a comma before the cursor.
+       Figure out what arg we're trying to complete. Example: (true, <com>, None) *)
+    let posNum = ref (-1) in
+    tupleItems
+    |> List.iteri (fun index pat ->
+           if posBeforeCursor >= Loc.start pat.Parsetree.ppat_loc then
+             posNum := index);
+    if !posNum > -1 then Some ("", resultFromFoundItemNum !posNum) else None
+  | v, _ -> v
+
+and traversePattern (pat : Parsetree.pattern) ~patternPath ~locHasCursor
+    ~firstCharBeforeCursorNoWhite ~posBeforeCursor =
+  let someIfHasCursor v debugId =
+    if locHasCursor pat.Parsetree.ppat_loc then (
+      if Debug.verbose () then
+        Printf.printf
+          "[traversePattern:someIfHasCursor] '%s' has cursor, returning \n"
+          debugId;
+      Some v)
+    else None
+  in
+  match pat.ppat_desc with
+  | Ppat_constant _ | Ppat_interval _ -> None
+  | Ppat_lazy p
+  | Ppat_constraint (p, _)
+  | Ppat_alias (p, _)
+  | Ppat_exception p
+  | Ppat_open (_, p) ->
+    p
+    |> traversePattern ~patternPath ~locHasCursor ~firstCharBeforeCursorNoWhite
+         ~posBeforeCursor
+  | Ppat_or (p1, p2) -> (
+    let orPatWithItem =
+      [p1; p2]
+      |> List.find_map (fun p ->
+             p
+             |> traversePattern ~patternPath ~locHasCursor
+                  ~firstCharBeforeCursorNoWhite ~posBeforeCursor)
+    in
+    match orPatWithItem with
+    | None when isPatternHole p1 || isPatternHole p2 ->
+      if Debug.verbose () then
+        Printf.printf
+          "[traversePattern] found or-pattern that was pattern hole\n";
+      Some ("", patternPath)
+    | v -> v)
+  | Ppat_any ->
+    (* We treat any `_` as an empty completion. This is mainly because we're
+       inserting `_` in snippets and automatically put the cursor there. So
+       letting it trigger an empty completion improves the ergonomics by a
+       lot. *)
+    someIfHasCursor ("", patternPath) "Ppat_any"
+  | Ppat_var {txt} -> someIfHasCursor (txt, patternPath) "Ppat_var"
+  | Ppat_construct ({txt = Lident "()"}, None) ->
+    (* switch s { | (<com>) }*)
+    someIfHasCursor
+      ("", patternPath @ [Completable.NTupleItem {itemNum = 0}])
+      "Ppat_construct()"
+  | Ppat_construct ({txt = Lident prefix}, None) ->
+    someIfHasCursor (prefix, patternPath) "Ppat_construct(Lident)"
+  | Ppat_variant (prefix, None) ->
+    someIfHasCursor ("#" ^ prefix, patternPath) "Ppat_variant"
+  | Ppat_array arrayPatterns ->
+    let nextPatternPath = [Completable.NArray] @ patternPath in
+    if List.length arrayPatterns = 0 && locHasCursor pat.ppat_loc then
+      Some ("", nextPatternPath)
+    else
+      arrayPatterns
+      |> List.find_map (fun pat ->
+             pat
+             |> traversePattern ~patternPath:nextPatternPath ~locHasCursor
+                  ~firstCharBeforeCursorNoWhite ~posBeforeCursor)
+  | Ppat_tuple tupleItems when locHasCursor pat.ppat_loc ->
+    tupleItems
+    |> traverseTupleItems ~firstCharBeforeCursorNoWhite ~posBeforeCursor
+         ~locHasCursor
+         ~nextPatternPath:(fun itemNum ->
+           [Completable.NTupleItem {itemNum}] @ patternPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [Completable.NTupleItem {itemNum = itemNum + 1}] @ patternPath)
+  | Ppat_record ([], _) ->
+    (* Empty fields means we're in a record body `{}`. Complete for the fields. *)
+    someIfHasCursor
+      ("", [Completable.NRecordBody {seenFields = []}] @ patternPath)
+      "Ppat_record(empty)"
+  | Ppat_record (fields, _) -> (
+    let fieldWithCursor = ref None in
+    let fieldWithPatHole = ref None in
+    fields
+    |> List.iter (fun (fname, f) ->
+           match
+             ( fname.Location.txt,
+               f.Parsetree.ppat_loc
+               |> CursorPosition.classifyLoc ~pos:posBeforeCursor )
+           with
+           | Longident.Lident fname, HasCursor ->
+             fieldWithCursor := Some (fname, f)
+           | Lident fname, _ when isPatternHole f ->
+             fieldWithPatHole := Some (fname, f)
+           | _ -> ());
+    let seenFields =
+      fields
+      |> List.filter_map (fun (fieldName, _f) ->
+             match fieldName with
+             | {Location.txt = Longident.Lident fieldName} -> Some fieldName
+             | _ -> None)
+    in
+    match (!fieldWithCursor, !fieldWithPatHole) with
+    | Some (fname, f), _ | None, Some (fname, f) -> (
+      match f.ppat_desc with
+      | Ppat_extension ({txt = "rescript.patternhole"}, _) ->
+        (* A pattern hole means for example `{someField: <com>}`. We want to complete for the type of `someField`.  *)
+        someIfHasCursor
+          ( "",
+            [Completable.NFollowRecordField {fieldName = fname}] @ patternPath
+          )
+          "patternhole"
+      | Ppat_var {txt} ->
+        (* A var means `{s}` or similar. Complete for fields. *)
+        someIfHasCursor
+          (txt, [Completable.NRecordBody {seenFields}] @ patternPath)
+          "Ppat_var #2"
+      | _ ->
+        f
+        |> traversePattern
+             ~patternPath:
+               ([Completable.NFollowRecordField {fieldName = fname}]
+               @ patternPath)
+             ~locHasCursor ~firstCharBeforeCursorNoWhite ~posBeforeCursor)
+    | None, None -> (
+      (* Figure out if we're completing for a new field.
+         If the cursor is inside of the record body, but no field has the cursor,
+         and there's no pattern hole. Check the first char to the left of the cursor,
+         ignoring white space. If that's a comma, we assume you're completing for a new field. *)
+      match firstCharBeforeCursorNoWhite with
+      | Some ',' ->
+        someIfHasCursor
+          ("", [Completable.NRecordBody {seenFields}] @ patternPath)
+          "firstCharBeforeCursorNoWhite:,"
+      | _ -> None))
+  | Ppat_construct
+      ( {txt},
+        Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)} )
+    when locHasCursor ppat_loc ->
+    (* Empty payload with cursor, like: Test(<com>) *)
+    Some
+      ( "",
+        [
+          Completable.NVariantPayload
+            {constructorName = Utils.getUnqualifiedName txt; itemNum = 0};
+        ]
+        @ patternPath )
+  | Ppat_construct ({txt}, Some pat)
+    when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
+         && firstCharBeforeCursorNoWhite = Some ','
+         && isPatternTuple pat = false ->
+    (* Empty payload with trailing ',', like: Test(true, <com>) *)
+    Some
+      ( "",
+        [
+          Completable.NVariantPayload
+            {constructorName = Utils.getUnqualifiedName txt; itemNum = 1};
+        ]
+        @ patternPath )
+  | Ppat_construct ({txt}, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
+    when locHasCursor ppat_loc ->
+    tupleItems
+    |> traverseTupleItems ~locHasCursor ~firstCharBeforeCursorNoWhite
+         ~posBeforeCursor
+         ~nextPatternPath:(fun itemNum ->
+           [
+             Completable.NVariantPayload
+               {constructorName = Utils.getUnqualifiedName txt; itemNum};
+           ]
+           @ patternPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [
+             Completable.NVariantPayload
+               {
+                 constructorName = Utils.getUnqualifiedName txt;
+                 itemNum = itemNum + 1;
+               };
+           ]
+           @ patternPath)
+  | Ppat_construct ({txt}, Some p) when locHasCursor pat.ppat_loc ->
+    p
+    |> traversePattern ~locHasCursor ~firstCharBeforeCursorNoWhite
+         ~posBeforeCursor
+         ~patternPath:
+           ([
+              Completable.NVariantPayload
+                {constructorName = Utils.getUnqualifiedName txt; itemNum = 0};
+            ]
+           @ patternPath)
+  | Ppat_variant
+      (txt, Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)})
+    when locHasCursor ppat_loc ->
+    (* Empty payload with cursor, like: #test(<com>) *)
+    Some
+      ( "",
+        [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 0}]
+        @ patternPath )
+  | Ppat_variant (txt, Some pat)
+    when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
+         && firstCharBeforeCursorNoWhite = Some ','
+         && isPatternTuple pat = false ->
+    (* Empty payload with trailing ',', like: #test(true, <com>) *)
+    Some
+      ( "",
+        [Completable.NPolyvariantPayload {constructorName = txt; itemNum = 1}]
+        @ patternPath )
+  | Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
+    when locHasCursor ppat_loc ->
+    tupleItems
+    |> traverseTupleItems ~locHasCursor ~firstCharBeforeCursorNoWhite
+         ~posBeforeCursor
+         ~nextPatternPath:(fun itemNum ->
+           [Completable.NPolyvariantPayload {constructorName = txt; itemNum}]
+           @ patternPath)
+         ~resultFromFoundItemNum:(fun itemNum ->
+           [
+             Completable.NPolyvariantPayload
+               {constructorName = txt; itemNum = itemNum + 1};
+           ]
+           @ patternPath)
+  | Ppat_variant (txt, Some p) when locHasCursor pat.ppat_loc ->
+    p
+    |> traversePattern ~locHasCursor ~firstCharBeforeCursorNoWhite
+         ~posBeforeCursor
+         ~patternPath:
+           ([
+              Completable.NPolyvariantPayload
+                {constructorName = txt; itemNum = 0};
+            ]
+           @ patternPath)
+  | _ -> None
diff --git a/analysis/src/Completions.ml b/analysis/src/Completions.ml
new file mode 100644
index 0000000000..42176bb3b0
--- /dev/null
+++ b/analysis/src/Completions.ml
@@ -0,0 +1,22 @@
+let getCompletions ~debug ~path ~pos ~currentFile ~forHover =
+  let textOpt = Files.readFile currentFile in
+  match textOpt with
+  | None | Some "" -> None
+  | Some text -> (
+    match
+      CompletionFrontEnd.completionWithParser ~debug ~path ~posCursor:pos
+        ~currentFile ~text
+    with
+    | None -> None
+    | Some (completable, scope) -> (
+      (* Only perform expensive ast operations if there are completables *)
+      match Cmt.loadFullCmtFromPath ~path with
+      | None -> None
+      | Some full ->
+        let env = SharedTypes.QueryEnv.fromFile full.file in
+        let completables =
+          completable
+          |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env
+               ~forHover
+        in
+        Some (completables, full, scope)))
diff --git a/analysis/src/CreateInterface.ml b/analysis/src/CreateInterface.ml
new file mode 100644
index 0000000000..9f1774b820
--- /dev/null
+++ b/analysis/src/CreateInterface.ml
@@ -0,0 +1,421 @@
+module SourceFileExtractor = struct
+  let create ~path =
+    match Files.readFile path with
+    | None -> [||]
+    | Some text -> text |> String.split_on_char '\n' |> Array.of_list
+
+  let extract lines ~posStart ~posEnd =
+    let lineStart, colStart = posStart in
+    let lineEnd, colEnd = posEnd in
+    let res = ref [] in
+    if lineStart < 0 || lineStart > lineEnd || lineEnd >= Array.length lines
+    then []
+    else (
+      for n = lineEnd downto lineStart do
+        let line = lines.(n) in
+        let len = String.length line in
+        if n = lineStart && n = lineEnd then (
+          if colStart >= 0 && colStart < colEnd && colEnd <= len then
+            let indent = String.make colStart ' ' in
+            res :=
+              (indent ^ String.sub line colStart (colEnd - colStart)) :: !res)
+        else if n = lineStart then (
+          if colStart >= 0 && colStart < len then
+            let indent = String.make colStart ' ' in
+            res := (indent ^ String.sub line colStart (len - colStart)) :: !res)
+        else if n = lineEnd then (
+          if colEnd > 0 && colEnd <= len then
+            res := String.sub line 0 colEnd :: !res)
+        else res := line :: !res
+      done;
+      !res)
+end
+
+module AttributesUtils : sig
+  type t
+
+  val make : string list -> t
+
+  val contains : string -> t -> bool
+
+  val toString : t -> string
+end = struct
+  type attribute = {line: int; offset: int; name: string}
+  type t = attribute list
+  type parseState = Search | Collect of int
+
+  let make lines =
+    let makeAttr lineIdx attrOffsetStart attrOffsetEnd line =
+      {
+        line = lineIdx;
+        offset = attrOffsetStart;
+        name = String.sub line attrOffsetStart (attrOffsetEnd - attrOffsetStart);
+      }
+    in
+    let res = ref [] in
+    lines
+    |> List.iteri (fun lineIdx line ->
+           let state = ref Search in
+           for i = 0 to String.length line - 1 do
+             let ch = line.[i] in
+             match (!state, ch) with
+             | Search, '@' -> state := Collect i
+             | Collect attrOffset, ' ' ->
+               res := makeAttr lineIdx attrOffset i line :: !res;
+               state := Search
+             | Search, _ | Collect _, _ -> ()
+           done;
+
+           match !state with
+           | Collect attrOffset ->
+             res :=
+               makeAttr lineIdx attrOffset (String.length line) line :: !res
+           | _ -> ());
+    !res |> List.rev
+
+  let contains attributeForSearch t =
+    t |> List.exists (fun {name} -> name = attributeForSearch)
+
+  let toString t =
+    match t with
+    | [] -> ""
+    | {line} :: _ ->
+      let prevLine = ref line in
+      let buffer = ref "" in
+      let res = ref [] in
+      t
+      |> List.iter (fun attr ->
+             let {line; offset; name} = attr in
+
+             if line <> !prevLine then (
+               res := !buffer :: !res;
+               buffer := "";
+               prevLine := line);
+
+             let indent = String.make (offset - String.length !buffer) ' ' in
+             buffer := !buffer ^ indent ^ name);
+      res := !buffer :: !res;
+      !res |> List.rev |> String.concat "\n"
+end
+
+let printSignature ~extractor ~signature =
+  let objectPropsToFun objTyp ~rhs ~makePropsType =
+    let propsTbl = Hashtbl.create 1 in
+    (* Process the object type of the make function, and map field names to types. *)
+    let rec processObjType typ =
+      match typ.Types.desc with
+      | Tfield (name, kind, {desc = Tlink t | Tsubst t | Tpoly (t, [])}, obj) ->
+        processObjType {typ with desc = Tfield (name, kind, t, obj)}
+      | Tfield (name, _kind, t, obj) ->
+        Hashtbl.add propsTbl name t;
+        processObjType obj
+      | Tnil -> ()
+      | _ -> (* should not happen *) assert false
+    in
+
+    processObjType objTyp;
+
+    (* Traverse the type of the makeProps function, and fill the prop types
+       by using the corresponding field in the object type of the make function *)
+    let rec fillPropsTypes makePropsType ~rhs =
+      match makePropsType.Types.desc with
+      | Tarrow (((Labelled lbl | Optional lbl) as argLbl), _, retT, c) -> (
+        match Hashtbl.find_opt propsTbl lbl with
+        | Some propT ->
+          {
+            makePropsType with
+            desc = Tarrow (argLbl, propT, fillPropsTypes retT ~rhs, c);
+          }
+        | None -> fillPropsTypes retT ~rhs)
+      | _ -> rhs
+    in
+
+    match objTyp.Types.desc with
+    | Tnil ->
+      (* component with zero props *)
+      {
+        objTyp with
+        desc =
+          Tarrow
+            ( Nolabel,
+              Ctype.newconstr (Path.Pident (Ident.create "unit")) [],
+              rhs,
+              Cok );
+      }
+    | _ -> fillPropsTypes makePropsType ~rhs
+  in
+
+  Printtyp.reset_names ();
+  let sigItemToString (item : Outcometree.out_sig_item) =
+    item |> Res_outcome_printer.print_out_sig_item_doc
+    |> Res_doc.to_string ~width:Res_multi_printer.default_print_width
+  in
+
+  let genSigStrForInlineAttr lines attributes id vd =
+    let divider = if List.length lines > 1 then "\n" else " " in
+
+    let sigStr =
+      sigItemToString
+        (Printtyp.tree_of_value_description id {vd with val_kind = Val_reg})
+    in
+
+    (attributes |> AttributesUtils.toString) ^ divider ^ sigStr ^ "\n"
+  in
+
+  let buf = Buffer.create 10 in
+
+  let rec getComponentTypeV3 (typ : Types.type_expr) =
+    let reactElement =
+      Ctype.newconstr (Pdot (Pident (Ident.create "React"), "element", 0)) []
+    in
+    match typ.desc with
+    | Tconstr (Pident {name = "function$"}, [typ; _], _) ->
+      getComponentTypeV3 typ
+    | Tarrow (_, {desc = Tobject (tObj, _)}, retType, _) -> Some (tObj, retType)
+    | Tconstr
+        ( Pdot (Pident {name = "React"}, "component", _),
+          [{desc = Tobject (tObj, _)}],
+          _ ) ->
+      Some (tObj, reactElement)
+    | Tconstr
+        ( Pdot (Pident {name = "React"}, "componentLike", _),
+          [{desc = Tobject (tObj, _)}; retType],
+          _ ) ->
+      Some (tObj, retType)
+    | _ -> None
+  in
+
+  let rec getComponentTypeV4 (typ : Types.type_expr) =
+    let reactElement =
+      Ctype.newconstr (Pdot (Pident (Ident.create "React"), "element", 0)) []
+    in
+    match typ.desc with
+    | Tconstr (Pident {name = "function$"}, [typ; _], _) ->
+      getComponentTypeV4 typ
+    | Tarrow (_, {desc = Tconstr (Path.Pident propsId, typeArgs, _)}, retType, _)
+      when Ident.name propsId = "props" ->
+      Some (typeArgs, retType)
+    | Tconstr
+        ( Pdot (Pident {name = "React"}, "component", _),
+          [{desc = Tconstr (Path.Pident propsId, typeArgs, _)}],
+          _ )
+      when Ident.name propsId = "props" ->
+      Some (typeArgs, reactElement)
+    | Tconstr
+        ( Pdot (Pident {name = "React"}, "componentLike", _),
+          [{desc = Tconstr (Path.Pident propsId, typeArgs, _)}; retType],
+          _ )
+      when Ident.name propsId = "props" ->
+      Some (typeArgs, retType)
+    | _ -> None
+  in
+
+  let rec processSignature ~indent (signature : Types.signature) : unit =
+    match signature with
+    | Sig_value
+        ( makePropsId (* makeProps *),
+          {val_loc = makePropsLoc; val_type = makePropsType} )
+      :: Sig_value (makeId (* make *), makeValueDesc)
+      :: rest
+      when Ident.name makePropsId = Ident.name makeId ^ "Props"
+           && ((* from implementation *) makePropsLoc.loc_ghost
+              || (* from interface *) makePropsLoc = makeValueDesc.val_loc)
+           && getComponentTypeV3 makeValueDesc.val_type <> None ->
+      (*
+        {"name": string} => retType  ~~>  (~name:string) => retType
+        React.component<{"name": string}>  ~~>  (~name:string) => React.element
+        React.componentLike<{"name": string}, retType>  ~~>  (~name:string) => retType
+      *)
+      let tObj, retType =
+        match getComponentTypeV3 makeValueDesc.val_type with
+        | None -> assert false
+        | Some (tObj, retType) -> (tObj, retType)
+      in
+      let funType = tObj |> objectPropsToFun ~rhs:retType ~makePropsType in
+      let newItemStr =
+        sigItemToString
+          (Printtyp.tree_of_value_description makeId
+             {makeValueDesc with val_type = funType})
+      in
+      Buffer.add_string buf (indent ^ "@react.component\n");
+      Buffer.add_string buf (indent ^ newItemStr ^ "\n");
+      processSignature ~indent rest
+    | Sig_type
+        ( propsId,
+          {
+            type_params;
+            type_kind = Type_record (labelDecls, recordRepresentation);
+          },
+          _ )
+      :: Sig_value (makeId (* make *), makeValueDesc)
+      :: rest
+      when Ident.name propsId = "props"
+           && getComponentTypeV4 makeValueDesc.val_type <> None ->
+      (* PPX V4 component declaration:
+         type props = {...}
+         let v = ...
+      *)
+      let newItemStr =
+        let typeArgs, retType =
+          match getComponentTypeV4 makeValueDesc.val_type with
+          | Some x -> x
+          | None -> assert false
+        in
+        let rec mkFunType (labelDecls : Types.label_declaration list) =
+          match labelDecls with
+          | [] -> retType
+          | labelDecl :: rest ->
+            let propType =
+              TypeUtils.instantiateType ~typeParams:type_params ~typeArgs
+                labelDecl.ld_type
+            in
+            let lblName = labelDecl.ld_id |> Ident.name in
+            let lbl =
+              let optLbls =
+                match recordRepresentation with
+                | Record_optional_labels optLbls -> optLbls
+                | _ -> []
+              in
+              if List.mem lblName optLbls then Asttypes.Optional lblName
+              else Labelled lblName
+            in
+            {retType with desc = Tarrow (lbl, propType, mkFunType rest, Cok)}
+        in
+        let funType =
+          if List.length labelDecls = 0 (* No props *) then
+            let tUnit =
+              Ctype.newconstr (Path.Pident (Ident.create "unit")) []
+            in
+            {retType with desc = Tarrow (Nolabel, tUnit, retType, Cok)}
+          else mkFunType labelDecls
+        in
+        sigItemToString
+          (Printtyp.tree_of_value_description makeId
+             {makeValueDesc with val_type = funType})
+      in
+      Buffer.add_string buf (indent ^ "@react.component\n");
+      Buffer.add_string buf (indent ^ newItemStr ^ "\n");
+      processSignature ~indent rest
+    | Sig_module (id, modDecl, recStatus) :: rest ->
+      let colonOrEquals =
+        match modDecl.md_type with
+        | Mty_alias _ -> " = "
+        | _ -> ": "
+      in
+      Buffer.add_string buf
+        (indent
+        ^ (match recStatus with
+          | Trec_not -> "module "
+          | Trec_first -> "module rec "
+          | Trec_next -> "and ")
+        ^ Ident.name id ^ colonOrEquals);
+      processModuleType ~indent modDecl.md_type;
+      Buffer.add_string buf "\n";
+      processSignature ~indent rest
+    | Sig_modtype (id, mtd) :: rest ->
+      let () =
+        match mtd.mtd_type with
+        | None ->
+          Buffer.add_string buf (indent ^ "module type " ^ Ident.name id ^ "\n")
+        | Some mt ->
+          Buffer.add_string buf (indent ^ "module type " ^ Ident.name id ^ " = ");
+          processModuleType ~indent mt;
+          Buffer.add_string buf "\n"
+      in
+      processSignature ~indent rest
+    | Sig_value (id, ({val_kind = Val_prim prim; val_loc} as vd)) :: items
+      when prim.prim_native_name <> "" && prim.prim_native_name.[0] = '\132' ->
+      (* Rescript primitive name, e.g. @val external ... *)
+      let lines =
+        let posStart, posEnd = Loc.range val_loc in
+        extractor |> SourceFileExtractor.extract ~posStart ~posEnd
+      in
+      let attributes = AttributesUtils.make lines in
+
+      if AttributesUtils.contains "@inline" attributes then
+        (* Generate type signature for @inline declaration *)
+        Buffer.add_string buf (genSigStrForInlineAttr lines attributes id vd)
+      else
+        (* Copy the external declaration verbatim from the implementation file *)
+        Buffer.add_string buf ((lines |> String.concat "\n") ^ "\n");
+
+      processSignature ~indent items
+    | Sig_value (id, vd) :: items ->
+      let newItemStr =
+        sigItemToString (Printtyp.tree_of_value_description id vd)
+      in
+      Buffer.add_string buf (indent ^ newItemStr ^ "\n");
+      processSignature ~indent items
+    | Sig_type (id, typeDecl, resStatus) :: items ->
+      let newItemStr =
+        sigItemToString
+          (Printtyp.tree_of_type_declaration id typeDecl resStatus)
+      in
+      Buffer.add_string buf (indent ^ newItemStr ^ "\n");
+      processSignature ~indent items
+    | Sig_typext (id, extConstr, extStatus) :: items ->
+      let newItemStr =
+        sigItemToString
+          (Printtyp.tree_of_extension_constructor id extConstr extStatus)
+      in
+      Buffer.add_string buf (indent ^ newItemStr ^ "\n");
+      processSignature ~indent items
+    | Sig_class _ :: items ->
+      (* not needed *)
+      processSignature ~indent items
+    | Sig_class_type _ :: items ->
+      (* not needed *)
+      processSignature ~indent items
+    | [] -> ()
+  and processModuleType ~indent (mt : Types.module_type) =
+    match mt with
+    | Mty_signature signature ->
+      Buffer.add_string buf "{\n";
+      processSignature ~indent:(indent ^ "  ") signature;
+      Buffer.add_string buf (indent ^ "}")
+    | Mty_functor _ ->
+      let rec collectFunctorArgs ~args (mt : Types.module_type) =
+        match mt with
+        | Mty_functor (id, None, mt) when Ident.name id = "*" ->
+          (* AST encoding of functor with no arguments *)
+          collectFunctorArgs ~args mt
+        | Mty_functor (id, mto, mt) ->
+          collectFunctorArgs ~args:((id, mto) :: args) mt
+        | mt -> (List.rev args, mt)
+      in
+      let args, retMt = collectFunctorArgs ~args:[] mt in
+      Buffer.add_string buf "(";
+      args
+      |> List.iter (fun (id, mto) ->
+             Buffer.add_string buf ("\n" ^ indent ^ "  ");
+             (match mto with
+             | None -> Buffer.add_string buf (Ident.name id)
+             | Some mt ->
+               Buffer.add_string buf (Ident.name id ^ ": ");
+               processModuleType ~indent:(indent ^ "  ") mt);
+             Buffer.add_string buf ",");
+      if args <> [] then Buffer.add_string buf ("\n" ^ indent);
+      Buffer.add_string buf (") =>\n" ^ indent);
+      processModuleType ~indent retMt
+    | Mty_ident path | Mty_alias (_, path) ->
+      let rec outIdentToString (ident : Outcometree.out_ident) =
+        match ident with
+        | Oide_ident s -> s
+        | Oide_dot (ident, s) -> outIdentToString ident ^ "." ^ s
+        | Oide_apply (call, arg) ->
+          outIdentToString call ^ "(" ^ outIdentToString arg ^ ")"
+      in
+      Buffer.add_string buf (outIdentToString (Printtyp.tree_of_path path))
+  in
+
+  processSignature ~indent:"" signature;
+  Buffer.contents buf
+
+let command ~path ~cmiFile =
+  match Shared.tryReadCmi cmiFile with
+  | Some cmi_info ->
+    (* For reading the config *)
+    let _ = Cmt.loadFullCmtFromPath ~path in
+    let extractor = SourceFileExtractor.create ~path in
+    printSignature ~extractor ~signature:cmi_info.cmi_sign
+  | None -> ""
diff --git a/analysis/src/DceCommand.ml b/analysis/src/DceCommand.ml
new file mode 100644
index 0000000000..8630277edd
--- /dev/null
+++ b/analysis/src/DceCommand.ml
@@ -0,0 +1,5 @@
+let command () =
+  Reanalyze.RunConfig.dce ();
+  Reanalyze.runAnalysis ~cmtRoot:None;
+  let issues = !Reanalyze.Log_.Stats.issues in
+  Printf.printf "issues:%d\n" (List.length issues)
diff --git a/analysis/src/Debug.ml b/analysis/src/Debug.ml
new file mode 100644
index 0000000000..d19d7dfa5a
--- /dev/null
+++ b/analysis/src/Debug.ml
@@ -0,0 +1,13 @@
+type debugLevel = Off | Regular | Verbose
+
+let debugLevel = ref Off
+
+let log s =
+  match !debugLevel with
+  | Regular | Verbose -> print_endline s
+  | Off -> ()
+
+let debugPrintEnv (env : SharedTypes.QueryEnv.t) =
+  env.pathRev @ [env.file.moduleName] |> List.rev |> String.concat "."
+
+let verbose () = !debugLevel = Verbose
diff --git a/analysis/src/Diagnostics.ml b/analysis/src/Diagnostics.ml
new file mode 100644
index 0000000000..0b30d0e332
--- /dev/null
+++ b/analysis/src/Diagnostics.ml
@@ -0,0 +1,34 @@
+let document_syntax ~path =
+  let get_diagnostics diagnostics =
+    diagnostics
+    |> List.map (fun diagnostic ->
+           let _, startline, startcol =
+             Location.get_pos_info (Res_diagnostics.get_start_pos diagnostic)
+           in
+           let _, endline, endcol =
+             Location.get_pos_info (Res_diagnostics.get_end_pos diagnostic)
+           in
+           Protocol.stringifyDiagnostic
+             {
+               range =
+                 {
+                   start = {line = startline - 1; character = startcol};
+                   end_ = {line = endline - 1; character = endcol};
+                 };
+               message = Res_diagnostics.explain diagnostic;
+               severity = 1;
+             })
+  in
+  if FindFiles.isImplementation path then
+    let parseImplementation =
+      Res_driver.parsing_engine.parse_implementation ~for_printer:false
+        ~filename:path
+    in
+    get_diagnostics parseImplementation.diagnostics
+  else if FindFiles.isInterface path then
+    let parseInterface =
+      Res_driver.parsing_engine.parse_interface ~for_printer:false
+        ~filename:path
+    in
+    get_diagnostics parseInterface.diagnostics
+  else []
diff --git a/analysis/src/DocumentSymbol.ml b/analysis/src/DocumentSymbol.ml
new file mode 100644
index 0000000000..44580f1e68
--- /dev/null
+++ b/analysis/src/DocumentSymbol.ml
@@ -0,0 +1,202 @@
+(* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol *)
+
+type kind =
+  | Module
+  | Property
+  | Constructor
+  | Function
+  | Variable
+  | Constant
+  | String
+  | Number
+  | EnumMember
+  | TypeParameter
+
+let kindNumber = function
+  | Module -> 2
+  | Property -> 7
+  | Constructor -> 9
+  | Function -> 12
+  | Variable -> 13
+  | Constant -> 14
+  | String -> 15
+  | Number -> 16
+  | EnumMember -> 22
+  | TypeParameter -> 26
+
+let command ~path =
+  let symbols = ref [] in
+  let addSymbol name loc kind =
+    if
+      (not loc.Location.loc_ghost)
+      && loc.loc_start.pos_cnum >= 0
+      && loc.loc_end.pos_cnum >= 0
+    then
+      let range = Utils.cmtLocToRange loc in
+      let symbol : Protocol.documentSymbolItem =
+        {name; range; kind = kindNumber kind; children = []}
+      in
+      symbols := symbol :: !symbols
+  in
+  let rec exprKind (exp : Parsetree.expression) =
+    match exp.pexp_desc with
+    | Pexp_fun _ -> Function
+    | Pexp_function _ -> Function
+    | Pexp_constraint (e, _) -> exprKind e
+    | Pexp_constant (Pconst_string _) -> String
+    | Pexp_constant (Pconst_float _ | Pconst_integer _) -> Number
+    | Pexp_constant _ -> Constant
+    | _ -> Variable
+  in
+  let processTypeKind (tk : Parsetree.type_kind) =
+    match tk with
+    | Ptype_variant constrDecls ->
+      constrDecls
+      |> List.iter (fun (cd : Parsetree.constructor_declaration) ->
+             addSymbol cd.pcd_name.txt cd.pcd_loc EnumMember)
+    | Ptype_record labelDecls ->
+      labelDecls
+      |> List.iter (fun (ld : Parsetree.label_declaration) ->
+             addSymbol ld.pld_name.txt ld.pld_loc Property)
+    | _ -> ()
+  in
+  let processTypeDeclaration (td : Parsetree.type_declaration) =
+    addSymbol td.ptype_name.txt td.ptype_loc TypeParameter;
+    processTypeKind td.ptype_kind
+  in
+  let processValueDescription (vd : Parsetree.value_description) =
+    addSymbol vd.pval_name.txt vd.pval_loc Variable
+  in
+  let processModuleBinding (mb : Parsetree.module_binding) =
+    addSymbol mb.pmb_name.txt mb.pmb_loc Module
+  in
+  let processModuleDeclaration (md : Parsetree.module_declaration) =
+    addSymbol md.pmd_name.txt md.pmd_loc Module
+  in
+  let processExtensionConstructor (et : Parsetree.extension_constructor) =
+    addSymbol et.pext_name.txt et.pext_loc Constructor
+  in
+  let value_binding (iterator : Ast_iterator.iterator)
+      (vb : Parsetree.value_binding) =
+    (match vb.pvb_pat.ppat_desc with
+    | Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) ->
+      addSymbol txt vb.pvb_loc (exprKind vb.pvb_expr)
+    | _ -> ());
+    Ast_iterator.default_iterator.value_binding iterator vb
+  in
+  let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
+    (match e.pexp_desc with
+    | Pexp_letmodule ({txt}, modExpr, _) ->
+      addSymbol txt {e.pexp_loc with loc_end = modExpr.pmod_loc.loc_end} Module
+    | Pexp_letexception (ec, _) -> processExtensionConstructor ec
+    | _ -> ());
+    Ast_iterator.default_iterator.expr iterator e
+  in
+  let structure_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.structure_item) =
+    (match item.pstr_desc with
+    | Pstr_value _ -> ()
+    | Pstr_primitive vd -> processValueDescription vd
+    | Pstr_type (_, typDecls) -> typDecls |> List.iter processTypeDeclaration
+    | Pstr_module mb -> processModuleBinding mb
+    | Pstr_recmodule mbs -> mbs |> List.iter processModuleBinding
+    | Pstr_exception ec -> processExtensionConstructor ec
+    | _ -> ());
+    Ast_iterator.default_iterator.structure_item iterator item
+  in
+  let signature_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.signature_item) =
+    (match item.psig_desc with
+    | Psig_value vd -> processValueDescription vd
+    | Psig_type (_, typDecls) -> typDecls |> List.iter processTypeDeclaration
+    | Psig_module md -> processModuleDeclaration md
+    | Psig_recmodule mds -> mds |> List.iter processModuleDeclaration
+    | Psig_exception ec -> processExtensionConstructor ec
+    | _ -> ());
+    Ast_iterator.default_iterator.signature_item iterator item
+  in
+  let module_expr (iterator : Ast_iterator.iterator)
+      (me : Parsetree.module_expr) =
+    match me.pmod_desc with
+    | Pmod_constraint (modExpr, _modTyp) ->
+      (* Don't double-list items in implementation and interface *)
+      Ast_iterator.default_iterator.module_expr iterator modExpr
+    | _ -> Ast_iterator.default_iterator.module_expr iterator me
+  in
+  let iterator =
+    {
+      Ast_iterator.default_iterator with
+      expr;
+      module_expr;
+      signature_item;
+      structure_item;
+      value_binding;
+    }
+  in
+
+  (if Filename.check_suffix path ".res" then
+     let parser =
+       Res_driver.parsing_engine.parse_implementation ~for_printer:false
+     in
+     let {Res_driver.parsetree = structure} = parser ~filename:path in
+     iterator.structure iterator structure |> ignore
+   else
+     let parser =
+       Res_driver.parsing_engine.parse_interface ~for_printer:false
+     in
+     let {Res_driver.parsetree = signature} = parser ~filename:path in
+     iterator.signature iterator signature |> ignore);
+  let isInside
+      ({
+         range =
+           {
+             start = {line = sl1; character = sc1};
+             end_ = {line = el1; character = ec1};
+           };
+       } :
+        Protocol.documentSymbolItem)
+      ({
+         range =
+           {
+             start = {line = sl2; character = sc2};
+             end_ = {line = el2; character = ec2};
+           };
+       } :
+        Protocol.documentSymbolItem) =
+    (sl1 > sl2 || (sl1 = sl2 && sc1 >= sc2))
+    && (el1 < el2 || (el1 = el2 && ec1 <= ec2))
+  in
+  let compareSymbol (s1 : Protocol.documentSymbolItem)
+      (s2 : Protocol.documentSymbolItem) =
+    let n = compare s1.range.start.line s2.range.start.line in
+    if n <> 0 then n
+    else
+      let n = compare s1.range.start.character s2.range.start.character in
+      if n <> 0 then n
+      else
+        let n = compare s1.range.end_.line s2.range.end_.line in
+        if n <> 0 then n
+        else compare s1.range.end_.character s2.range.end_.character
+  in
+  let rec addSymbolToChildren ~symbol children =
+    match children with
+    | [] -> [symbol]
+    | last :: rest ->
+      if isInside symbol last then
+        let newLast =
+          {last with children = last.children |> addSymbolToChildren ~symbol}
+        in
+        newLast :: rest
+      else symbol :: children
+  in
+  let rec addSortedSymbolsToChildren ~sortedSymbols children =
+    match sortedSymbols with
+    | [] -> children
+    | firstSymbol :: rest ->
+      children
+      |> addSymbolToChildren ~symbol:firstSymbol
+      |> addSortedSymbolsToChildren ~sortedSymbols:rest
+  in
+  let sortedSymbols = !symbols |> List.sort compareSymbol in
+  let symbolsWithChildren = [] |> addSortedSymbolsToChildren ~sortedSymbols in
+  print_endline (Protocol.stringifyDocumentSymbolItems symbolsWithChildren)
diff --git a/analysis/src/DumpAst.ml b/analysis/src/DumpAst.ml
new file mode 100644
index 0000000000..0515dc9fcd
--- /dev/null
+++ b/analysis/src/DumpAst.ml
@@ -0,0 +1,323 @@
+open SharedTypes
+(* This is intended to be a debug tool. It's by no means complete. Rather, you're encouraged to extend this with printing whatever types you need printing. *)
+
+let emptyLocDenom = "<x>"
+let hasCursorDenom = "<*>"
+let noCursorDenom = ""
+
+let printLocDenominator loc ~pos =
+  match loc |> CursorPosition.classifyLoc ~pos with
+  | EmptyLoc -> emptyLocDenom
+  | HasCursor -> hasCursorDenom
+  | NoCursor -> noCursorDenom
+
+let printLocDenominatorLoc loc ~pos =
+  match loc |> CursorPosition.classifyLocationLoc ~pos with
+  | CursorPosition.EmptyLoc -> emptyLocDenom
+  | HasCursor -> hasCursorDenom
+  | NoCursor -> noCursorDenom
+
+let printLocDenominatorPos pos ~posStart ~posEnd =
+  match CursorPosition.classifyPositions pos ~posStart ~posEnd with
+  | CursorPosition.EmptyLoc -> emptyLocDenom
+  | HasCursor -> hasCursorDenom
+  | NoCursor -> noCursorDenom
+
+let addIndentation indentation =
+  let rec indent str indentation =
+    if indentation < 1 then str else indent (str ^ "  ") (indentation - 1)
+  in
+  indent "" indentation
+
+let printAttributes attributes =
+  match List.length attributes with
+  | 0 -> ""
+  | _ ->
+    "["
+    ^ (attributes
+      |> List.map (fun ({Location.txt}, _payload) -> "@" ^ txt)
+      |> String.concat ",")
+    ^ "]"
+
+let printConstant const =
+  match const with
+  | Parsetree.Pconst_integer (s, _) -> "Pconst_integer(" ^ s ^ ")"
+  | Pconst_char c -> "Pconst_char(" ^ String.make 1 (Char.chr c) ^ ")"
+  | Pconst_string (s, delim) ->
+    let delim =
+      match delim with
+      | None -> ""
+      | Some delim -> delim ^ " "
+    in
+    "Pconst_string(" ^ delim ^ s ^ delim ^ ")"
+  | Pconst_float (s, _) -> "Pconst_float(" ^ s ^ ")"
+
+let printCoreType typ ~pos =
+  printAttributes typ.Parsetree.ptyp_attributes
+  ^ (typ.ptyp_loc |> printLocDenominator ~pos)
+  ^
+  match typ.ptyp_desc with
+  | Ptyp_any -> "Ptyp_any"
+  | Ptyp_var name -> "Ptyp_var(" ^ str name ^ ")"
+  | Ptyp_constr (lid, _types) ->
+    "Ptyp_constr("
+    ^ (lid |> printLocDenominatorLoc ~pos)
+    ^ (Utils.flattenLongIdent lid.txt |> ident |> str)
+    ^ ")"
+  | Ptyp_variant _ -> "Ptyp_variant(<unimplemented>)"
+  | _ -> "<unimplemented_ptyp_desc>"
+
+let rec printPattern pattern ~pos ~indentation =
+  printAttributes pattern.Parsetree.ppat_attributes
+  ^ (pattern.ppat_loc |> printLocDenominator ~pos)
+  ^
+  match pattern.Parsetree.ppat_desc with
+  | Ppat_or (pat1, pat2) ->
+    "Ppat_or(\n"
+    ^ addIndentation (indentation + 1)
+    ^ printPattern pat1 ~pos ~indentation:(indentation + 2)
+    ^ ",\n"
+    ^ addIndentation (indentation + 1)
+    ^ printPattern pat2 ~pos ~indentation:(indentation + 2)
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Ppat_extension (({txt} as loc), _) ->
+    "Ppat_extension(%" ^ (loc |> printLocDenominatorLoc ~pos) ^ txt ^ ")"
+  | Ppat_var ({txt} as loc) ->
+    "Ppat_var(" ^ (loc |> printLocDenominatorLoc ~pos) ^ txt ^ ")"
+  | Ppat_constant const -> "Ppat_constant(" ^ printConstant const ^ ")"
+  | Ppat_construct (({txt} as loc), maybePat) ->
+    "Ppat_construct("
+    ^ (loc |> printLocDenominatorLoc ~pos)
+    ^ (Utils.flattenLongIdent txt |> ident |> str)
+    ^ (match maybePat with
+      | None -> ""
+      | Some pat -> "," ^ printPattern pat ~pos ~indentation)
+    ^ ")"
+  | Ppat_variant (label, maybePat) ->
+    "Ppat_variant(" ^ str label
+    ^ (match maybePat with
+      | None -> ""
+      | Some pat -> "," ^ printPattern pat ~pos ~indentation)
+    ^ ")"
+  | Ppat_record (fields, _) ->
+    "Ppat_record(\n"
+    ^ addIndentation (indentation + 1)
+    ^ "fields:\n"
+    ^ (fields
+      |> List.map (fun ((Location.{txt} as loc), pat) ->
+             addIndentation (indentation + 2)
+             ^ (loc |> printLocDenominatorLoc ~pos)
+             ^ (Utils.flattenLongIdent txt |> ident |> str)
+             ^ ": "
+             ^ printPattern pat ~pos ~indentation:(indentation + 2))
+      |> String.concat "\n")
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Ppat_tuple patterns ->
+    "Ppat_tuple(\n"
+    ^ (patterns
+      |> List.map (fun pattern ->
+             addIndentation (indentation + 2)
+             ^ (pattern |> printPattern ~pos ~indentation:(indentation + 2)))
+      |> String.concat ",\n")
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Ppat_any -> "Ppat_any"
+  | Ppat_constraint (pattern, typ) ->
+    "Ppat_constraint(\n"
+    ^ addIndentation (indentation + 1)
+    ^ printCoreType typ ~pos ^ ",\n"
+    ^ addIndentation (indentation + 1)
+    ^ (pattern |> printPattern ~pos ~indentation:(indentation + 1))
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | v -> Printf.sprintf "<unimplemented_ppat_desc: %s>" (Utils.identifyPpat v)
+
+and printCase case ~pos ~indentation ~caseNum =
+  addIndentation indentation
+  ^ Printf.sprintf "case %i:\n" caseNum
+  ^ addIndentation (indentation + 1)
+  ^ "pattern"
+  ^ (case.Parsetree.pc_lhs.ppat_loc |> printLocDenominator ~pos)
+  ^ ":\n"
+  ^ addIndentation (indentation + 2)
+  ^ printPattern case.Parsetree.pc_lhs ~pos ~indentation
+  ^ "\n"
+  ^ addIndentation (indentation + 1)
+  ^ "expr"
+  ^ (case.Parsetree.pc_rhs.pexp_loc |> printLocDenominator ~pos)
+  ^ ":\n"
+  ^ addIndentation (indentation + 2)
+  ^ printExprItem case.pc_rhs ~pos ~indentation:(indentation + 2)
+
+and printExprItem expr ~pos ~indentation =
+  printAttributes expr.Parsetree.pexp_attributes
+  ^ (expr.pexp_loc |> printLocDenominator ~pos)
+  ^
+  match expr.Parsetree.pexp_desc with
+  | Pexp_array exprs ->
+    "Pexp_array(\n"
+    ^ addIndentation (indentation + 1)
+    ^ (exprs
+      |> List.map (fun expr ->
+             expr |> printExprItem ~pos ~indentation:(indentation + 1))
+      |> String.concat ("\n" ^ addIndentation (indentation + 1)))
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Pexp_match (matchExpr, cases) ->
+    "Pexp_match("
+    ^ printExprItem matchExpr ~pos ~indentation:0
+    ^ ")\n"
+    ^ (cases
+      |> List.mapi (fun caseNum case ->
+             printCase case ~pos ~caseNum:(caseNum + 1)
+               ~indentation:(indentation + 1))
+      |> String.concat "\n")
+  | Pexp_ident {txt} ->
+    "Pexp_ident:" ^ (Utils.flattenLongIdent txt |> SharedTypes.ident)
+  | Pexp_apply (expr, args) ->
+    let printLabel labelled ~pos =
+      match labelled with
+      | None -> "<unlabelled>"
+      | Some labelled ->
+        printLocDenominatorPos pos ~posStart:labelled.posStart
+          ~posEnd:labelled.posEnd
+        ^ "~"
+        ^ if labelled.opt then "?" else "" ^ labelled.name
+    in
+    let args = extractExpApplyArgs ~args in
+    "Pexp_apply(\n"
+    ^ addIndentation (indentation + 1)
+    ^ "expr:\n"
+    ^ addIndentation (indentation + 2)
+    ^ printExprItem expr ~pos ~indentation:(indentation + 2)
+    ^ "\n"
+    ^ addIndentation (indentation + 1)
+    ^ "args:\n"
+    ^ (args
+      |> List.map (fun arg ->
+             addIndentation (indentation + 2)
+             ^ printLabel arg.label ~pos ^ "=\n"
+             ^ addIndentation (indentation + 3)
+             ^ printExprItem arg.exp ~pos ~indentation:(indentation + 3))
+      |> String.concat ",\n")
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Pexp_constant constant -> "Pexp_constant(" ^ printConstant constant ^ ")"
+  | Pexp_construct (({txt} as loc), maybeExpr) ->
+    "Pexp_construct("
+    ^ (loc |> printLocDenominatorLoc ~pos)
+    ^ (Utils.flattenLongIdent txt |> ident |> str)
+    ^ (match maybeExpr with
+      | None -> ""
+      | Some expr -> ", " ^ printExprItem expr ~pos ~indentation)
+    ^ ")"
+  | Pexp_variant (label, maybeExpr) ->
+    "Pexp_variant(" ^ str label
+    ^ (match maybeExpr with
+      | None -> ""
+      | Some expr -> "," ^ printExprItem expr ~pos ~indentation)
+    ^ ")"
+  | Pexp_fun (arg, _maybeDefaultArgExpr, pattern, nextExpr) ->
+    "Pexp_fun(\n"
+    ^ addIndentation (indentation + 1)
+    ^ "arg: "
+    ^ (match arg with
+      | Nolabel -> "Nolabel"
+      | Labelled name -> "Labelled(" ^ name ^ ")"
+      | Optional name -> "Optional(" ^ name ^ ")")
+    ^ ",\n"
+    ^ addIndentation (indentation + 2)
+    ^ "pattern: "
+    ^ printPattern pattern ~pos ~indentation:(indentation + 2)
+    ^ ",\n"
+    ^ addIndentation (indentation + 1)
+    ^ "next expr:\n"
+    ^ addIndentation (indentation + 2)
+    ^ printExprItem nextExpr ~pos ~indentation:(indentation + 2)
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Pexp_extension (({txt} as loc), _) ->
+    "Pexp_extension(%" ^ (loc |> printLocDenominatorLoc ~pos) ^ txt ^ ")"
+  | Pexp_assert expr ->
+    "Pexp_assert(" ^ printExprItem expr ~pos ~indentation ^ ")"
+  | Pexp_field (exp, loc) ->
+    "Pexp_field("
+    ^ (loc |> printLocDenominatorLoc ~pos)
+    ^ printExprItem exp ~pos ~indentation
+    ^ ")"
+  | Pexp_record (fields, _) ->
+    "Pexp_record(\n"
+    ^ addIndentation (indentation + 1)
+    ^ "fields:\n"
+    ^ (fields
+      |> List.map (fun ((Location.{txt} as loc), expr) ->
+             addIndentation (indentation + 2)
+             ^ (loc |> printLocDenominatorLoc ~pos)
+             ^ (Utils.flattenLongIdent txt |> ident |> str)
+             ^ ": "
+             ^ printExprItem expr ~pos ~indentation:(indentation + 2))
+      |> String.concat "\n")
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | Pexp_tuple exprs ->
+    "Pexp_tuple(\n"
+    ^ (exprs
+      |> List.map (fun expr ->
+             addIndentation (indentation + 2)
+             ^ (expr |> printExprItem ~pos ~indentation:(indentation + 2)))
+      |> String.concat ",\n")
+    ^ "\n" ^ addIndentation indentation ^ ")"
+  | v -> Printf.sprintf "<unimplemented_pexp_desc: %s>" (Utils.identifyPexp v)
+
+let printValueBinding value ~pos ~indentation =
+  printAttributes value.Parsetree.pvb_attributes
+  ^ "value" ^ ":\n"
+  ^ addIndentation (indentation + 1)
+  ^ (value.pvb_pat |> printPattern ~pos ~indentation:(indentation + 1))
+  ^ "\n" ^ addIndentation indentation ^ "expr:\n"
+  ^ addIndentation (indentation + 1)
+  ^ printExprItem value.pvb_expr ~pos ~indentation:(indentation + 1)
+
+let printStructItem structItem ~pos ~source =
+  match structItem.Parsetree.pstr_loc |> CursorPosition.classifyLoc ~pos with
+  | HasCursor -> (
+    let startOffset =
+      match Pos.positionToOffset source (structItem.pstr_loc |> Loc.start) with
+      | None -> 0
+      | Some offset -> offset
+    in
+    let endOffset =
+      (* Include the next line of the source since that will hold the ast comment pointing to the position.
+         Caveat: this only works for single line sources with a comment on the next line. Will need to be
+         adapted if that's not the only use case.*)
+      let line, _col = structItem.pstr_loc |> Loc.end_ in
+      match Pos.positionToOffset source (line + 2, 0) with
+      | None -> 0
+      | Some offset -> offset
+    in
+
+    ("\nSource:\n// "
+    ^ String.sub source startOffset (endOffset - startOffset)
+    ^ "\n")
+    ^ printLocDenominator structItem.pstr_loc ~pos
+    ^
+    match structItem.pstr_desc with
+    | Pstr_eval (expr, _attributes) ->
+      "Pstr_eval(\n" ^ printExprItem expr ~pos ~indentation:1 ^ "\n)"
+    | Pstr_value (recFlag, values) ->
+      "Pstr_value(\n"
+      ^ (match recFlag with
+        | Recursive -> "  rec,\n"
+        | Nonrecursive -> "")
+      ^ (values
+        |> List.map (fun value ->
+               addIndentation 1 ^ printValueBinding value ~pos ~indentation:1)
+        |> String.concat ",\n")
+      ^ "\n)"
+    | _ -> "<structure_item_not_implemented>")
+  | _ -> ""
+
+let dump ~currentFile ~pos =
+  let {Res_driver.parsetree = structure; source} =
+    Res_driver.parsing_engine.parse_implementation ~for_printer:true
+      ~filename:currentFile
+  in
+
+  print_endline
+    (structure
+    |> List.map (fun structItem -> printStructItem structItem ~pos ~source)
+    |> String.concat "")
diff --git a/analysis/src/Files.ml b/analysis/src/Files.ml
new file mode 100644
index 0000000000..faefc23559
--- /dev/null
+++ b/analysis/src/Files.ml
@@ -0,0 +1,108 @@
+let split str string = Str.split (Str.regexp_string str) string
+
+let removeExtraDots path =
+  Str.global_replace (Str.regexp_string "/./") "/" path
+  |> Str.global_replace (Str.regexp {|^\./\.\./|}) "../"
+
+(* Win32 & MacOS are case-insensitive *)
+let pathEq =
+  if Sys.os_type = "Linux" then fun a b -> a = b
+  else fun a b -> String.lowercase_ascii a = String.lowercase_ascii b
+
+let pathStartsWith text prefix =
+  String.length prefix <= String.length text
+  && pathEq (String.sub text 0 (String.length prefix)) prefix
+
+let sliceToEnd str pos = String.sub str pos (String.length str - pos)
+
+let relpath base path =
+  if pathStartsWith path base then
+    let baselen = String.length base in
+    let rest = String.sub path baselen (String.length path - baselen) in
+    (if rest <> "" && rest.[0] = Filename.dir_sep.[0] then sliceToEnd rest 1
+     else rest)
+    |> removeExtraDots
+  else
+    let rec loop bp pp =
+      match (bp, pp) with
+      | "." :: ra, _ -> loop ra pp
+      | _, "." :: rb -> loop bp rb
+      | a :: ra, b :: rb when pathEq a b -> loop ra rb
+      | _ -> (bp, pp)
+    in
+    let base, path =
+      loop (split Filename.dir_sep base) (split Filename.dir_sep path)
+    in
+    String.concat Filename.dir_sep
+      ((match base with
+       | [] -> ["."]
+       | _ -> List.map (fun _ -> "..") base)
+      @ path)
+    |> removeExtraDots
+
+let maybeStat path =
+  try Some (Unix.stat path) with Unix.Unix_error (Unix.ENOENT, _, _) -> None
+
+let readFile filename =
+  try
+    (* windows can't use open_in *)
+    let chan = open_in_bin filename in
+    let content = really_input_string chan (in_channel_length chan) in
+    close_in_noerr chan;
+    Some content
+  with _ -> None
+
+let exists path =
+  match maybeStat path with
+  | None -> false
+  | Some _ -> true
+let ifExists path = if exists path then Some path else None
+
+let readDirectory dir =
+  match Unix.opendir dir with
+  | exception Unix.Unix_error (Unix.ENOENT, "opendir", _dir) -> []
+  | handle ->
+    let rec loop handle =
+      try
+        let name = Unix.readdir handle in
+        if name = Filename.current_dir_name || name = Filename.parent_dir_name
+        then loop handle
+        else name :: loop handle
+      with End_of_file ->
+        Unix.closedir handle;
+        []
+    in
+    loop handle
+
+let rec collectDirs path =
+  match maybeStat path with
+  | None -> []
+  | Some {Unix.st_kind = Unix.S_DIR} ->
+    path
+    :: (readDirectory path
+       |> List.map (fun name -> collectDirs (Filename.concat path name))
+       |> List.concat)
+  | _ -> []
+
+let rec collect ?(checkDir = fun _ -> true) path test =
+  match maybeStat path with
+  | None -> []
+  | Some {Unix.st_kind = Unix.S_DIR} ->
+    if checkDir path then
+      readDirectory path
+      |> List.map (fun name ->
+             collect ~checkDir (Filename.concat path name) test)
+      |> List.concat
+    else []
+  | _ -> if test path then [path] else []
+
+type classifiedFile = Res | Resi | Other
+
+let classifySourceFile path =
+  if Filename.check_suffix path ".res" && exists path then Res
+  else if Filename.check_suffix path ".resi" && exists path then Resi
+  else Other
+
+let canonicalizeUri uri =
+  let path = Uri.toPath uri in
+  path |> Unix.realpath |> Uri.fromPath |> Uri.toString
diff --git a/analysis/src/FindFiles.ml b/analysis/src/FindFiles.ml
new file mode 100644
index 0000000000..fc3556e0a0
--- /dev/null
+++ b/analysis/src/FindFiles.ml
@@ -0,0 +1,288 @@
+let ifDebug debug name fn v = if debug then Log.log (name ^ ": " ^ fn v)
+let ( /+ ) = Filename.concat
+let bind f x = Option.bind x f
+
+(* Returns a list of paths, relative to the provided `base` *)
+let getSourceDirectories ~includeDev ~baseDir config =
+  let rec handleItem current item =
+    match item with
+    | Json.Array contents ->
+      List.map (handleItem current) contents |> List.concat
+    | Json.String text -> [current /+ text]
+    | Json.Object _ -> (
+      let dir =
+        item |> Json.get "dir" |> bind Json.string
+        |> Option.value ~default:"Must specify directory"
+      in
+      let typ =
+        if includeDev then "lib"
+        else
+          item |> Json.get "type" |> bind Json.string
+          |> Option.value ~default:"lib"
+      in
+
+      if typ = "dev" then []
+      else
+        match item |> Json.get "subdirs" with
+        | None | Some Json.False -> [current /+ dir]
+        | Some Json.True ->
+          Files.collectDirs (baseDir /+ current /+ dir)
+          |> List.filter (fun name -> name <> Filename.current_dir_name)
+          |> List.map (Files.relpath baseDir)
+        | Some item -> (current /+ dir) :: handleItem (current /+ dir) item)
+    | _ -> failwith "Invalid subdirs entry"
+  in
+  match config |> Json.get "sources" with
+  | None -> []
+  | Some item -> handleItem "" item
+
+let isCompiledFile name =
+  Filename.check_suffix name ".cmt" || Filename.check_suffix name ".cmti"
+
+let isImplementation name =
+  Filename.check_suffix name ".re"
+  || Filename.check_suffix name ".res"
+  || Filename.check_suffix name ".ml"
+
+let isInterface name =
+  Filename.check_suffix name ".rei"
+  || Filename.check_suffix name ".resi"
+  || Filename.check_suffix name ".mli"
+
+let isSourceFile name = isImplementation name || isInterface name
+
+let compiledNameSpace name =
+  String.split_on_char '-' name
+  |> List.map String.capitalize_ascii
+  |> String.concat ""
+  (* Remove underscores??? Whyyy bucklescript, whyyyy *)
+  |> String.split_on_char '_'
+  |> String.concat ""
+
+let compiledBaseName ~namespace name =
+  Filename.chop_extension name
+  ^
+  match namespace with
+  | None -> ""
+  | Some n -> "-" ^ compiledNameSpace n
+
+let getName x =
+  Filename.basename x |> Filename.chop_extension |> String.capitalize_ascii
+
+let filterDuplicates cmts =
+  (* Remove .cmt's that have .cmti's *)
+  let intfs = Hashtbl.create 100 in
+  cmts
+  |> List.iter (fun path ->
+         if
+           Filename.check_suffix path ".rei"
+           || Filename.check_suffix path ".mli"
+           || Filename.check_suffix path ".cmti"
+         then Hashtbl.add intfs (getName path) true);
+  cmts
+  |> List.filter (fun path ->
+         not
+           ((Filename.check_suffix path ".re"
+            || Filename.check_suffix path ".ml"
+            || Filename.check_suffix path ".cmt")
+           && Hashtbl.mem intfs (getName path)))
+
+let nameSpaceToName n =
+  n
+  |> Str.split (Str.regexp "[-/@]")
+  |> List.map String.capitalize_ascii
+  |> String.concat ""
+
+let getNamespace config =
+  let ns = config |> Json.get "namespace" in
+  let fromString = ns |> bind Json.string in
+  let isNamespaced =
+    ns |> bind Json.bool |> Option.value ~default:(fromString |> Option.is_some)
+  in
+  let either x y = if x = None then y else x in
+  if isNamespaced then
+    let fromName = config |> Json.get "name" |> bind Json.string in
+    either fromString fromName |> Option.map nameSpaceToName
+  else None
+
+module StringSet = Set.Make (String)
+
+let getPublic config =
+  let public = config |> Json.get "public" in
+  match public with
+  | None -> None
+  | Some public -> (
+    match public |> Json.array with
+    | None -> None
+    | Some public ->
+      Some (public |> List.filter_map Json.string |> StringSet.of_list))
+
+let collectFiles directory =
+  let allFiles = Files.readDirectory directory in
+  let compileds = allFiles |> List.filter isCompiledFile |> filterDuplicates in
+  let sources = allFiles |> List.filter isSourceFile |> filterDuplicates in
+  compileds
+  |> Utils.filterMap (fun path ->
+         let modName = getName path in
+         let cmt = directory /+ path in
+         let resOpt =
+           Utils.find
+             (fun name ->
+               if getName name = modName then Some (directory /+ name) else None)
+             sources
+         in
+         match resOpt with
+         | None -> None
+         | Some res -> Some (modName, SharedTypes.Impl {cmt; res}))
+
+(* returns a list of (absolute path to cmt(i), relative path from base to source file) *)
+let findProjectFiles ~public ~namespace ~path ~sourceDirectories ~libBs =
+  let dirs =
+    sourceDirectories |> List.map (Filename.concat path) |> StringSet.of_list
+  in
+  let files =
+    dirs |> StringSet.elements
+    |> List.map (fun name -> Files.collect name isSourceFile)
+    |> List.concat |> StringSet.of_list
+  in
+  dirs
+  |> ifDebug true "Source directories" (fun s ->
+         s |> StringSet.elements |> List.map Utils.dumpPath |> String.concat " ");
+  files
+  |> ifDebug true "Source files" (fun s ->
+         s |> StringSet.elements |> List.map Utils.dumpPath |> String.concat " ");
+
+  let interfaces = Hashtbl.create 100 in
+  files
+  |> StringSet.iter (fun path ->
+         if isInterface path then Hashtbl.replace interfaces (getName path) path);
+
+  let normals =
+    files |> StringSet.elements
+    |> Utils.filterMap (fun file ->
+           if isImplementation file then (
+             let moduleName = getName file in
+             let resi = Hashtbl.find_opt interfaces moduleName in
+             Hashtbl.remove interfaces moduleName;
+             let base = compiledBaseName ~namespace (Files.relpath path file) in
+             match resi with
+             | Some resi ->
+               let cmti = (libBs /+ base) ^ ".cmti" in
+               let cmt = (libBs /+ base) ^ ".cmt" in
+               if Files.exists cmti then
+                 if Files.exists cmt then
+                   (* Log.log("Intf and impl " ++ cmti ++ " " ++ cmt) *)
+                   Some
+                     ( moduleName,
+                       SharedTypes.IntfAndImpl {cmti; resi; cmt; res = file} )
+                 else None
+               else (
+                 (* Log.log("Just intf " ++ cmti) *)
+                 Log.log ("Bad source file (no cmt/cmti/cmi) " ^ (libBs /+ base));
+                 None)
+             | None ->
+               let cmt = (libBs /+ base) ^ ".cmt" in
+               if Files.exists cmt then Some (moduleName, Impl {cmt; res = file})
+               else (
+                 Log.log ("Bad source file (no cmt/cmi) " ^ (libBs /+ base));
+                 None))
+           else None)
+  in
+  let result =
+    normals
+    |> List.filter_map (fun (name, paths) ->
+           let originalName = name in
+           let name =
+             match namespace with
+             | None -> name
+             | Some namespace -> name ^ "-" ^ namespace
+           in
+           match public with
+           | Some public ->
+             if public |> StringSet.mem originalName then Some (name, paths)
+             else None
+           | None -> Some (name, paths))
+  in
+  match namespace with
+  | None -> result
+  | Some namespace ->
+    let moduleName = nameSpaceToName namespace in
+    let cmt = (libBs /+ namespace) ^ ".cmt" in
+    Log.log ("adding namespace " ^ namespace ^ " : " ^ moduleName ^ " : " ^ cmt);
+    (moduleName, Namespace {cmt}) :: result
+
+let findDependencyFiles base config =
+  let deps =
+    config |> Json.get "bs-dependencies" |> bind Json.array
+    |> Option.value ~default:[]
+    |> List.filter_map Json.string
+  in
+  let devDeps =
+    config
+    |> Json.get "bs-dev-dependencies"
+    |> bind Json.array
+    |> Option.map (List.filter_map Json.string)
+    |> Option.value ~default:[]
+  in
+  let deps = deps @ devDeps in
+  Log.log ("Dependencies: " ^ String.concat " " deps);
+  let depFiles =
+    deps
+    |> List.map (fun name ->
+           let result =
+             Json.bind
+               (ModuleResolution.resolveNodeModulePath ~startPath:base name)
+               (fun path ->
+                 let rescriptJsonPath = path /+ "rescript.json" in
+                 let bsconfigJsonPath = path /+ "bsconfig.json" in
+
+                 let parseText text =
+                   match Json.parse text with
+                   | Some inner -> (
+                     let namespace = getNamespace inner in
+                     let sourceDirectories =
+                       getSourceDirectories ~includeDev:false ~baseDir:path
+                         inner
+                     in
+                     match BuildSystem.getLibBs path with
+                     | None -> None
+                     | Some libBs ->
+                       let compiledDirectories =
+                         sourceDirectories |> List.map (Filename.concat libBs)
+                       in
+                       let compiledDirectories =
+                         match namespace with
+                         | None -> compiledDirectories
+                         | Some _ -> libBs :: compiledDirectories
+                       in
+                       let projectFiles =
+                         findProjectFiles ~public:(getPublic inner) ~namespace
+                           ~path ~sourceDirectories ~libBs
+                       in
+                       Some (compiledDirectories, projectFiles))
+                   | None -> None
+                 in
+
+                 match Files.readFile rescriptJsonPath with
+                 | Some text -> parseText text
+                 | None -> (
+                   match Files.readFile bsconfigJsonPath with
+                   | Some text -> parseText text
+                   | None -> None))
+           in
+
+           match result with
+           | Some (files, directories) -> (files, directories)
+           | None ->
+             Log.log ("Skipping nonexistent dependency: " ^ name);
+             ([], []))
+  in
+  match BuildSystem.getStdlib base with
+  | None -> None
+  | Some stdlibDirectory ->
+    let compiledDirectories, projectFiles =
+      let files, directories = List.split depFiles in
+      (List.concat files, List.concat directories)
+    in
+    let allFiles = projectFiles @ collectFiles stdlibDirectory in
+    Some (compiledDirectories, allFiles)
diff --git a/analysis/src/Hint.ml b/analysis/src/Hint.ml
new file mode 100644
index 0000000000..8537a447d4
--- /dev/null
+++ b/analysis/src/Hint.ml
@@ -0,0 +1,178 @@
+open SharedTypes
+
+type inlayHintKind = Type
+let inlayKindToNumber = function
+  | Type -> 1
+
+let locItemToTypeHint ~full:{file; package} locItem =
+  match locItem.locType with
+  | Constant t ->
+    Some
+      (match t with
+      | Const_int _ -> "int"
+      | Const_char _ -> "char"
+      | Const_string _ -> "string"
+      | Const_float _ -> "float"
+      | Const_int32 _ -> "int32"
+      | Const_int64 _ -> "int64"
+      | Const_bigint _ -> "bigint")
+  | Typed (_, t, locKind) ->
+    let fromType typ =
+      typ |> Shared.typeToString
+      |> Str.global_replace (Str.regexp "[\r\n\t]") ""
+    in
+    Some
+      (match References.definedForLoc ~file ~package locKind with
+      | None -> fromType t
+      | Some (_, res) -> (
+        match res with
+        | `Declared -> fromType t
+        | `Constructor _ -> fromType t
+        | `Field -> fromType t))
+  | _ -> None
+
+let inlay ~path ~pos ~maxLength ~debug =
+  let maxlen = try Some (int_of_string maxLength) with Failure _ -> None in
+  let hints = ref [] in
+  let start_line, end_line = pos in
+  let push loc kind =
+    let range = Utils.cmtLocToRange loc in
+    if start_line <= range.end_.line && end_line >= range.start.line then
+      hints := (range, kind) :: !hints
+  in
+  let rec processPattern (pat : Parsetree.pattern) =
+    match pat.ppat_desc with
+    | Ppat_tuple pl -> pl |> List.iter processPattern
+    | Ppat_record (fields, _) ->
+      fields |> List.iter (fun (_, p) -> processPattern p)
+    | Ppat_array fields -> fields |> List.iter processPattern
+    | Ppat_var {loc} -> push loc Type
+    | _ -> ()
+  in
+  let value_binding (iterator : Ast_iterator.iterator)
+      (vb : Parsetree.value_binding) =
+    (match vb with
+    | {
+     pvb_pat = {ppat_desc = Ppat_var _};
+     pvb_expr =
+       {
+         pexp_desc =
+           ( Pexp_constant _ | Pexp_tuple _ | Pexp_record _ | Pexp_variant _
+           | Pexp_apply _ | Pexp_match _ | Pexp_construct _ | Pexp_ifthenelse _
+           | Pexp_array _ | Pexp_ident _ | Pexp_try _ | Pexp_lazy _
+           | Pexp_send _ | Pexp_field _ | Pexp_open _ );
+       };
+    } ->
+      push vb.pvb_pat.ppat_loc Type
+    | {pvb_pat = {ppat_desc = Ppat_tuple _}} -> processPattern vb.pvb_pat
+    | {pvb_pat = {ppat_desc = Ppat_record _}} -> processPattern vb.pvb_pat
+    | _ -> ());
+    Ast_iterator.default_iterator.value_binding iterator vb
+  in
+  let iterator = {Ast_iterator.default_iterator with value_binding} in
+  (if Files.classifySourceFile path = Res then
+     let parser =
+       Res_driver.parsing_engine.parse_implementation ~for_printer:false
+     in
+     let {Res_driver.parsetree = structure} = parser ~filename:path in
+     iterator.structure iterator structure |> ignore);
+  match Cmt.loadFullCmtFromPath ~path with
+  | None -> None
+  | Some full ->
+    let result =
+      !hints
+      |> List.filter_map (fun ((range : Protocol.range), hintKind) ->
+             match
+               References.getLocItem ~full
+                 ~pos:(range.start.line, range.start.character + 1)
+                 ~debug
+             with
+             | None -> None
+             | Some locItem -> (
+               let position : Protocol.position =
+                 {line = range.start.line; character = range.end_.character}
+               in
+               match locItemToTypeHint locItem ~full with
+               | Some label -> (
+                 let result =
+                   Protocol.stringifyHint
+                     {
+                       kind = inlayKindToNumber hintKind;
+                       position;
+                       paddingLeft = true;
+                       paddingRight = false;
+                       label = ": " ^ label;
+                     }
+                 in
+                 match maxlen with
+                 | Some value ->
+                   if String.length label > value then None else Some result
+                 | None -> Some result)
+               | None -> None))
+    in
+    Some result
+
+let codeLens ~path ~debug =
+  let lenses = ref [] in
+  let push loc =
+    let range = Utils.cmtLocToRange loc in
+    lenses := range :: !lenses
+  in
+  (* Code lenses are only emitted for functions right now. So look for value bindings that are functions,
+     and use the loc of the value binding itself so we can look up the full function type for our code lens. *)
+  let value_binding (iterator : Ast_iterator.iterator)
+      (vb : Parsetree.value_binding) =
+    (match vb with
+    | {
+     pvb_pat = {ppat_desc = Ppat_var _; ppat_loc};
+     pvb_expr =
+       {
+         pexp_desc =
+           Pexp_construct
+             ({txt = Lident "Function$"}, Some {pexp_desc = Pexp_fun _});
+       };
+    } ->
+      push ppat_loc
+    | _ -> ());
+    Ast_iterator.default_iterator.value_binding iterator vb
+  in
+  let iterator = {Ast_iterator.default_iterator with value_binding} in
+  (* We only print code lenses in implementation files. This is because they'd be redundant in interface files,
+     where the definition itself will be the same thing as what would've been printed in the code lens. *)
+  (if Files.classifySourceFile path = Res then
+     let parser =
+       Res_driver.parsing_engine.parse_implementation ~for_printer:false
+     in
+     let {Res_driver.parsetree = structure} = parser ~filename:path in
+     iterator.structure iterator structure |> ignore);
+  match Cmt.loadFullCmtFromPath ~path with
+  | None -> None
+  | Some full ->
+    let result =
+      !lenses
+      |> List.filter_map (fun (range : Protocol.range) ->
+             match
+               References.getLocItem ~full
+                 ~pos:(range.start.line, range.start.character + 1)
+                 ~debug
+             with
+             | Some {locType = Typed (_, typeExpr, _)} ->
+               Some
+                 (Protocol.stringifyCodeLens
+                    {
+                      range;
+                      command =
+                        Some
+                          {
+                            (* Code lenses can run commands. An empty command string means we just want the editor
+                               to print the text, not link to running a command. *)
+                            command = "";
+                            (* Print the type with a huge line width, because the code lens always prints on a
+                               single line in the editor. *)
+                            title =
+                              typeExpr |> Shared.typeToString ~lineWidth:400;
+                          };
+                    })
+             | _ -> None)
+    in
+    Some result
diff --git a/analysis/src/Hover.ml b/analysis/src/Hover.ml
new file mode 100644
index 0000000000..d01d49873e
--- /dev/null
+++ b/analysis/src/Hover.ml
@@ -0,0 +1,261 @@
+open SharedTypes
+
+let showModuleTopLevel ~docstring ~isType ~name (topLevel : Module.item list) =
+  let contents =
+    topLevel
+    |> List.map (fun item ->
+           match item.Module.kind with
+           (* TODO pretty print module contents *)
+           | Type ({decl}, recStatus) ->
+             "  " ^ (decl |> Shared.declToString ~recStatus item.name)
+           | Module _ -> "  module " ^ item.name
+           | Value typ ->
+             "  let " ^ item.name ^ ": " ^ (typ |> Shared.typeToString))
+    (* TODO indent *)
+    |> String.concat "\n"
+  in
+  let name = Utils.cutAfterDash name in
+  let full =
+    Markdown.codeBlock
+      ("module "
+      ^ (if isType then "type " ^ name ^ " = " else name ^ ": ")
+      ^ "{" ^ "\n" ^ contents ^ "\n}")
+  in
+  let doc =
+    match docstring with
+    | [] -> ""
+    | _ :: _ -> "\n" ^ (docstring |> String.concat "\n") ^ "\n"
+  in
+  Some (doc ^ full)
+
+let rec showModule ~docstring ~(file : File.t) ~package ~name
+    (declared : Module.t Declared.t option) =
+  match declared with
+  | None ->
+    showModuleTopLevel ~docstring ~isType:false ~name file.structure.items
+  | Some {item = Structure {items}; modulePath} ->
+    let isType =
+      match modulePath with
+      | ExportedModule {isType} -> isType
+      | _ -> false
+    in
+    showModuleTopLevel ~docstring ~isType ~name items
+  | Some ({item = Constraint (_moduleItem, moduleTypeItem)} as declared) ->
+    (* show the interface *)
+    showModule ~docstring ~file ~name ~package
+      (Some {declared with item = moduleTypeItem})
+  | Some ({item = Ident path} as declared) -> (
+    match References.resolveModuleReference ~file ~package declared with
+    | None -> Some ("Unable to resolve module reference " ^ Path.name path)
+    | Some (_, declared) -> showModule ~docstring ~file ~name ~package declared)
+
+type extractedType = {
+  name: string;
+  path: Path.t;
+  decl: Types.type_declaration;
+  env: SharedTypes.QueryEnv.t;
+  loc: Warnings.loc;
+}
+
+let findRelevantTypesFromType ~file ~package typ =
+  (* Expand definitions of types mentioned in typ.
+     If typ itself is a record or variant, search its body *)
+  let env = QueryEnv.fromFile file in
+  let envToSearch, typesToSearch =
+    match typ |> Shared.digConstructor with
+    | Some path -> (
+      let labelDeclarationsTypes lds =
+        lds |> List.map (fun (ld : Types.label_declaration) -> ld.ld_type)
+      in
+      match References.digConstructor ~env ~package path with
+      | None -> (env, [typ])
+      | Some (env1, {item = {decl}}) -> (
+        match decl.type_kind with
+        | Type_record (lds, _) -> (env1, typ :: (lds |> labelDeclarationsTypes))
+        | Type_variant cds ->
+          ( env1,
+            cds
+            |> List.map (fun (cd : Types.constructor_declaration) ->
+                   let fromArgs =
+                     match cd.cd_args with
+                     | Cstr_tuple ts -> ts
+                     | Cstr_record lds -> lds |> labelDeclarationsTypes
+                   in
+                   typ
+                   ::
+                   (match cd.cd_res with
+                   | None -> fromArgs
+                   | Some t -> t :: fromArgs))
+            |> List.flatten )
+        | _ -> (env, [typ])))
+    | None -> (env, [typ])
+  in
+  let fromConstructorPath ~env path =
+    match References.digConstructor ~env ~package path with
+    | None -> None
+    | Some (env, {name = {txt}; extentLoc; item = {decl}}) ->
+      if Utils.isUncurriedInternal path then None
+      else Some {name = txt; env; loc = extentLoc; decl; path}
+  in
+  let constructors = Shared.findTypeConstructors typesToSearch in
+  constructors |> List.filter_map (fromConstructorPath ~env:envToSearch)
+
+let expandTypes ~file ~package ~supportsMarkdownLinks typ =
+  findRelevantTypesFromType typ ~file ~package
+  |> List.map (fun {decl; env; loc; path} ->
+         let linkToTypeDefinitionStr =
+           if supportsMarkdownLinks then
+             Markdown.goToDefinitionText ~env ~pos:loc.Warnings.loc_start
+           else ""
+         in
+         Markdown.divider
+         ^ (if supportsMarkdownLinks then Markdown.spacing else "")
+         ^ Markdown.codeBlock
+             (decl
+             |> Shared.declToString ~printNameAsIs:true
+                  (SharedTypes.pathIdentToString path))
+         ^ linkToTypeDefinitionStr ^ "\n")
+
+(* Produces a hover with relevant types expanded in the main type being hovered. *)
+let hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ =
+  let typeString = Markdown.codeBlock (typ |> Shared.typeToString) in
+  typeString :: expandTypes ~file ~package ~supportsMarkdownLinks typ
+  |> String.concat "\n"
+
+(* Leverages autocomplete functionality to produce a hover for a position. This
+   makes it (most often) work with unsaved content. *)
+let getHoverViaCompletions ~debug ~path ~pos ~currentFile ~forHover
+    ~supportsMarkdownLinks =
+  match Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover with
+  | None -> None
+  | Some (completions, ({file; package} as full), scope) -> (
+    let rawOpens = Scope.getRawOpens scope in
+    match completions with
+    | {kind = Label typString; docstring} :: _ ->
+      let parts =
+        docstring
+        @ if typString = "" then [] else [Markdown.codeBlock typString]
+      in
+
+      Some (Protocol.stringifyHover (String.concat "\n\n" parts))
+    | {kind = Field _; env; docstring} :: _ -> (
+      let opens = CompletionBackEnd.getOpens ~debug ~rawOpens ~package ~env in
+      match
+        CompletionBackEnd.completionsGetTypeEnv2 ~debug ~full ~rawOpens ~opens
+          ~pos completions
+      with
+      | Some (typ, _env) ->
+        let typeString =
+          hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ
+        in
+        let parts = docstring @ [typeString] in
+        Some (Protocol.stringifyHover (String.concat "\n\n" parts))
+      | None -> None)
+    | {env} :: _ -> (
+      let opens = CompletionBackEnd.getOpens ~debug ~rawOpens ~package ~env in
+      match
+        CompletionBackEnd.completionsGetTypeEnv2 ~debug ~full ~rawOpens ~opens
+          ~pos completions
+      with
+      | Some (typ, _env) ->
+        let typeString =
+          hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ
+        in
+        Some (Protocol.stringifyHover typeString)
+      | None -> None)
+    | _ -> None)
+
+let newHover ~full:{file; package} ~supportsMarkdownLinks locItem =
+  match locItem.locType with
+  | TypeDefinition (name, decl, _stamp) -> (
+    let typeDef = Markdown.codeBlock (Shared.declToString name decl) in
+    match decl.type_manifest with
+    | None -> Some typeDef
+    | Some typ ->
+      Some
+        (typeDef :: expandTypes ~file ~package ~supportsMarkdownLinks typ
+        |> String.concat "\n"))
+  | LModule (Definition (stamp, _tip)) | LModule (LocalReference (stamp, _tip))
+    -> (
+    match Stamps.findModule file.stamps stamp with
+    | None -> None
+    | Some md -> (
+      match References.resolveModuleReference ~file ~package md with
+      | None -> None
+      | Some (file, declared) ->
+        let name, docstring =
+          match declared with
+          | Some d -> (d.name.txt, d.docstring)
+          | None -> (file.moduleName, file.structure.docstring)
+        in
+        showModule ~docstring ~name ~file declared ~package))
+  | LModule (GlobalReference (moduleName, path, tip)) -> (
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None -> None
+    | Some file -> (
+      let env = QueryEnv.fromFile file in
+      match References.exportedForTip ~env ~path ~package ~tip with
+      | None -> None
+      | Some (_env, _name, stamp) -> (
+        match Stamps.findModule file.stamps stamp with
+        | None -> None
+        | Some md -> (
+          match References.resolveModuleReference ~file ~package md with
+          | None -> None
+          | Some (file, declared) ->
+            let name, docstring =
+              match declared with
+              | Some d -> (d.name.txt, d.docstring)
+              | None -> (file.moduleName, file.structure.docstring)
+            in
+            showModule ~docstring ~name ~file ~package declared))))
+  | LModule NotFound -> None
+  | TopLevelModule name -> (
+    match ProcessCmt.fileForModule ~package name with
+    | None -> None
+    | Some file ->
+      showModule ~docstring:file.structure.docstring ~name:file.moduleName ~file
+        ~package None)
+  | Typed (_, _, Definition (_, (Field _ | Constructor _))) -> None
+  | Constant t ->
+    Some
+      (Markdown.codeBlock
+         (match t with
+         | Const_int _ -> "int"
+         | Const_char _ -> "char"
+         | Const_string _ -> "string"
+         | Const_float _ -> "float"
+         | Const_int32 _ -> "int32"
+         | Const_int64 _ -> "int64"
+         | Const_bigint _ -> "bigint"))
+  | Typed (_, t, locKind) ->
+    let fromType ~docstring typ =
+      ( hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ,
+        docstring )
+    in
+    let parts =
+      match References.definedForLoc ~file ~package locKind with
+      | None ->
+        let typeString, docstring = t |> fromType ~docstring:[] in
+        typeString :: docstring
+      | Some (docstring, res) -> (
+        match res with
+        | `Declared ->
+          let typeString, docstring = t |> fromType ~docstring in
+          typeString :: docstring
+        | `Constructor {cname = {txt}; args; docstring} ->
+          let typeString, docstring = t |> fromType ~docstring in
+          let argsString =
+            match args with
+            | InlineRecord _ | Args [] -> ""
+            | Args args ->
+              args
+              |> List.map (fun (t, _) -> Shared.typeToString t)
+              |> String.concat ", " |> Printf.sprintf "(%s)"
+          in
+          typeString :: Markdown.codeBlock (txt ^ argsString) :: docstring
+        | `Field ->
+          let typeString, docstring = t |> fromType ~docstring in
+          typeString :: docstring)
+    in
+    Some (String.concat Markdown.divider parts)
diff --git a/analysis/src/JsxHacks.ml b/analysis/src/JsxHacks.ml
new file mode 100644
index 0000000000..81ffe200a8
--- /dev/null
+++ b/analysis/src/JsxHacks.ml
@@ -0,0 +1,5 @@
+let pathIsFragment path = Path.name path = "ReasonReact.fragment"
+
+let primitiveIsFragment (vd : Typedtree.value_description) =
+  vd.val_name.txt = "fragment"
+  && vd.val_loc.loc_start.pos_fname |> Filename.basename = "ReasonReact.res"
diff --git a/analysis/src/Loc.ml b/analysis/src/Loc.ml
new file mode 100644
index 0000000000..a9e42979c7
--- /dev/null
+++ b/analysis/src/Loc.ml
@@ -0,0 +1,14 @@
+type t = Location.t
+
+let start (loc : t) = Pos.ofLexing loc.loc_start
+let end_ (loc : t) = Pos.ofLexing loc.loc_end
+let range loc : Range.t = (start loc, end_ loc)
+
+let toString (loc : t) =
+  (if loc.loc_ghost then "__ghost__" else "") ^ (loc |> range |> Range.toString)
+
+let hasPos ~pos loc = start loc <= pos && pos < end_ loc
+
+(** Allows the character after the end to be included. Ie when the cursor is at the 
+    end of the word, like `someIdentifier<cursor>`. Useful in some scenarios. *)
+let hasPosInclusiveEnd ~pos loc = start loc <= pos && pos <= end_ loc
diff --git a/analysis/src/LocalTables.ml b/analysis/src/LocalTables.ml
new file mode 100644
index 0000000000..5a5bdb0c2c
--- /dev/null
+++ b/analysis/src/LocalTables.ml
@@ -0,0 +1,51 @@
+open SharedTypes
+
+type 'a table = (string * (int * int), 'a Declared.t) Hashtbl.t
+type namesUsed = (string, unit) Hashtbl.t
+
+type t = {
+  namesUsed: namesUsed;
+  mutable resultRev: Completion.t list;
+  constructorTable: Constructor.t table;
+  modulesTable: Module.t table;
+  typesTable: Type.t table;
+  valueTable: Types.type_expr table;
+}
+
+let create () =
+  {
+    namesUsed = Hashtbl.create 1;
+    resultRev = [];
+    constructorTable = Hashtbl.create 1;
+    modulesTable = Hashtbl.create 1;
+    typesTable = Hashtbl.create 1;
+    valueTable = Hashtbl.create 1;
+  }
+
+let populateValues ~env localTables =
+  env.QueryEnv.file.stamps
+  |> Stamps.iterValues (fun _ declared ->
+         Hashtbl.replace localTables.valueTable
+           (declared.name.txt, declared.name.loc |> Loc.start)
+           declared)
+
+let populateConstructors ~env localTables =
+  env.QueryEnv.file.stamps
+  |> Stamps.iterConstructors (fun _ declared ->
+         Hashtbl.replace localTables.constructorTable
+           (declared.name.txt, declared.extentLoc |> Loc.start)
+           declared)
+
+let populateTypes ~env localTables =
+  env.QueryEnv.file.stamps
+  |> Stamps.iterTypes (fun _ declared ->
+         Hashtbl.replace localTables.typesTable
+           (declared.name.txt, declared.name.loc |> Loc.start)
+           declared)
+
+let populateModules ~env localTables =
+  env.QueryEnv.file.stamps
+  |> Stamps.iterModules (fun _ declared ->
+         Hashtbl.replace localTables.modulesTable
+           (declared.name.txt, declared.extentLoc |> Loc.start)
+           declared)
diff --git a/analysis/src/Log.ml b/analysis/src/Log.ml
new file mode 100644
index 0000000000..5d1fd823c9
--- /dev/null
+++ b/analysis/src/Log.ml
@@ -0,0 +1,2 @@
+let verbose = ref false
+let log msg = if !verbose then print_endline msg
diff --git a/analysis/src/Markdown.ml b/analysis/src/Markdown.ml
new file mode 100644
index 0000000000..0f82050fb0
--- /dev/null
+++ b/analysis/src/Markdown.ml
@@ -0,0 +1,23 @@
+let spacing = "\n```\n \n```\n"
+let codeBlock code = Printf.sprintf "```rescript\n%s\n```" code
+let divider = "\n---\n"
+
+type link = {startPos: Protocol.position; file: string; label: string}
+
+let linkToCommandArgs link =
+  Printf.sprintf "[\"%s\",%i,%i]" link.file link.startPos.line
+    link.startPos.character
+
+let makeGotoCommand link =
+  Printf.sprintf "[%s](command:rescript-vscode.go_to_location?%s)" link.label
+    (Uri.encodeURIComponent (linkToCommandArgs link))
+
+let goToDefinitionText ~env ~pos =
+  let startLine, startCol = Pos.ofLexing pos in
+  "\nGo to: "
+  ^ makeGotoCommand
+      {
+        label = "Type definition";
+        file = Uri.toString env.SharedTypes.QueryEnv.file.uri;
+        startPos = {line = startLine; character = startCol};
+      }
diff --git a/analysis/src/ModuleResolution.ml b/analysis/src/ModuleResolution.ml
new file mode 100644
index 0000000000..1982b9fe6e
--- /dev/null
+++ b/analysis/src/ModuleResolution.ml
@@ -0,0 +1,7 @@
+let ( /+ ) = Filename.concat
+
+let rec resolveNodeModulePath ~startPath name =
+  let path = startPath /+ "node_modules" /+ name in
+  if Files.exists path then Some path
+  else if Filename.dirname startPath = startPath then None
+  else resolveNodeModulePath ~startPath:(Filename.dirname startPath) name
diff --git a/analysis/src/Packages.ml b/analysis/src/Packages.ml
new file mode 100644
index 0000000000..675093636d
--- /dev/null
+++ b/analysis/src/Packages.ml
@@ -0,0 +1,254 @@
+open SharedTypes
+
+(* Creates the `pathsForModule` hashtbl, which maps a `moduleName` to it's `paths` (the ml/re, mli/rei, cmt, and cmti files) *)
+let makePathsForModule ~projectFilesAndPaths ~dependenciesFilesAndPaths =
+  let pathsForModule = Hashtbl.create 30 in
+  dependenciesFilesAndPaths
+  |> List.iter (fun (modName, paths) ->
+         Hashtbl.replace pathsForModule modName paths);
+  projectFilesAndPaths
+  |> List.iter (fun (modName, paths) ->
+         Hashtbl.replace pathsForModule modName paths);
+  pathsForModule
+
+let overrideRescriptVersion = ref None
+
+let getReScriptVersion () =
+  match !overrideRescriptVersion with
+  | Some overrideRescriptVersion -> overrideRescriptVersion
+  | None -> (
+    (* TODO: Include patch stuff when needed *)
+    let defaultVersion = (11, 0) in
+    try
+      let value = Sys.getenv "RESCRIPT_VERSION" in
+      let version =
+        match value |> String.split_on_char '.' with
+        | major :: minor :: _rest -> (
+          match (int_of_string_opt major, int_of_string_opt minor) with
+          | Some major, Some minor -> (major, minor)
+          | _ -> defaultVersion)
+        | _ -> defaultVersion
+      in
+      version
+    with Not_found -> defaultVersion)
+
+let newBsPackage ~rootPath =
+  let rescriptJson = Filename.concat rootPath "rescript.json" in
+  let bsconfigJson = Filename.concat rootPath "bsconfig.json" in
+
+  let parseRaw raw =
+    let libBs =
+      match !Cfg.isDocGenFromCompiler with
+      | true -> BuildSystem.getStdlib rootPath
+      | false -> BuildSystem.getLibBs rootPath
+    in
+    match Json.parse raw with
+    | Some config -> (
+      let namespace = FindFiles.getNamespace config in
+      let rescriptVersion = getReScriptVersion () in
+      let suffix =
+        match config |> Json.get "suffix" with
+        | Some (String suffix) -> suffix
+        | _ -> ".js"
+      in
+      let uncurried =
+        let ns = config |> Json.get "uncurried" in
+        match (rescriptVersion, ns) with
+        | (major, _), None when major >= 11 -> Some true
+        | _, ns -> Option.bind ns Json.bool
+      in
+      let genericJsxModule =
+        let jsxConfig = config |> Json.get "jsx" in
+        match jsxConfig with
+        | Some jsxConfig -> (
+          match jsxConfig |> Json.get "module" with
+          | Some (String m) when String.lowercase_ascii m <> "react" -> Some m
+          | _ -> None)
+        | None -> None
+      in
+      let uncurried = uncurried = Some true in
+      match libBs with
+      | None -> None
+      | Some libBs ->
+        let cached = Cache.readCache (Cache.targetFileFromLibBs libBs) in
+        let projectFiles, dependenciesFiles, pathsForModule =
+          match cached with
+          | Some cached ->
+            ( cached.projectFiles,
+              cached.dependenciesFiles,
+              cached.pathsForModule )
+          | None ->
+            let dependenciesFilesAndPaths =
+              match FindFiles.findDependencyFiles rootPath config with
+              | None -> []
+              | Some (_dependencyDirectories, dependenciesFilesAndPaths) ->
+                dependenciesFilesAndPaths
+            in
+            let sourceDirectories =
+              FindFiles.getSourceDirectories ~includeDev:true ~baseDir:rootPath
+                config
+            in
+            let projectFilesAndPaths =
+              FindFiles.findProjectFiles
+                ~public:(FindFiles.getPublic config)
+                ~namespace ~path:rootPath ~sourceDirectories ~libBs
+            in
+            let pathsForModule =
+              makePathsForModule ~projectFilesAndPaths
+                ~dependenciesFilesAndPaths
+            in
+            let projectFiles =
+              projectFilesAndPaths |> List.map fst |> FileSet.of_list
+            in
+            let dependenciesFiles =
+              dependenciesFilesAndPaths |> List.map fst |> FileSet.of_list
+            in
+            (projectFiles, dependenciesFiles, pathsForModule)
+        in
+        Some
+          (let opens_from_namespace =
+             match namespace with
+             | None -> []
+             | Some namespace ->
+               let cmt = Filename.concat libBs namespace ^ ".cmt" in
+               Hashtbl.replace pathsForModule namespace (Namespace {cmt});
+               let path = [FindFiles.nameSpaceToName namespace] in
+               [path]
+           in
+           let opens_from_bsc_flags =
+             let bind f x = Option.bind x f in
+             match Json.get "bsc-flags" config |> bind Json.array with
+             | Some l ->
+               List.fold_left
+                 (fun opens item ->
+                   match item |> Json.string with
+                   | None -> opens
+                   | Some s -> (
+                     let parts = String.split_on_char ' ' s in
+                     match parts with
+                     | "-open" :: name :: _ ->
+                       let path = name |> String.split_on_char '.' in
+                       path :: opens
+                     | _ -> opens))
+                 [] l
+             | None -> []
+           in
+           let opens =
+             ["Pervasives"; "JsxModules"] :: opens_from_namespace
+             |> List.rev_append opens_from_bsc_flags
+             |> List.map (fun path -> path @ ["place holder"])
+           in
+           {
+             genericJsxModule;
+             suffix;
+             rescriptVersion;
+             rootPath;
+             projectFiles;
+             dependenciesFiles;
+             pathsForModule;
+             opens;
+             namespace;
+             builtInCompletionModules =
+               (if
+                  opens_from_bsc_flags
+                  |> List.find_opt (fun opn ->
+                         match opn with
+                         | ["RescriptCore"] -> true
+                         | _ -> false)
+                  |> Option.is_some
+                then
+                  {
+                    arrayModulePath = ["Array"];
+                    optionModulePath = ["Option"];
+                    stringModulePath = ["String"];
+                    intModulePath = ["Int"];
+                    floatModulePath = ["Float"];
+                    promiseModulePath = ["Promise"];
+                    listModulePath = ["List"];
+                    resultModulePath = ["Result"];
+                    exnModulePath = ["Exn"];
+                    regexpModulePath = ["RegExp"];
+                  }
+                else if
+                  opens_from_bsc_flags
+                  |> List.find_opt (fun opn ->
+                         match opn with
+                         | ["Belt"] -> true
+                         | _ -> false)
+                  |> Option.is_some
+                then
+                  {
+                    arrayModulePath = ["Array"];
+                    optionModulePath = ["Option"];
+                    stringModulePath = ["Js"; "String2"];
+                    intModulePath = ["Int"];
+                    floatModulePath = ["Float"];
+                    promiseModulePath = ["Js"; "Promise"];
+                    listModulePath = ["List"];
+                    resultModulePath = ["Result"];
+                    exnModulePath = ["Js"; "Exn"];
+                    regexpModulePath = ["Js"; "Re"];
+                  }
+                else
+                  {
+                    arrayModulePath = ["Js"; "Array2"];
+                    optionModulePath = ["Belt"; "Option"];
+                    stringModulePath = ["Js"; "String2"];
+                    intModulePath = ["Belt"; "Int"];
+                    floatModulePath = ["Belt"; "Float"];
+                    promiseModulePath = ["Js"; "Promise"];
+                    listModulePath = ["Belt"; "List"];
+                    resultModulePath = ["Belt"; "Result"];
+                    exnModulePath = ["Js"; "Exn"];
+                    regexpModulePath = ["Js"; "Re"];
+                  });
+             uncurried;
+           }))
+    | None -> None
+  in
+
+  match Files.readFile rescriptJson with
+  | Some raw -> parseRaw raw
+  | None -> (
+    Log.log ("Unable to read " ^ rescriptJson);
+    match Files.readFile bsconfigJson with
+    | Some raw -> parseRaw raw
+    | None ->
+      Log.log ("Unable to read " ^ bsconfigJson);
+      None)
+
+let findRoot ~uri packagesByRoot =
+  let path = Uri.toPath uri in
+  let rec loop path =
+    if path = "/" then None
+    else if Hashtbl.mem packagesByRoot path then Some (`Root path)
+    else if
+      Files.exists (Filename.concat path "rescript.json")
+      || Files.exists (Filename.concat path "bsconfig.json")
+    then Some (`Bs path)
+    else
+      let parent = Filename.dirname path in
+      if parent = path then (* reached root *) None else loop parent
+  in
+  loop (if Sys.is_directory path then path else Filename.dirname path)
+
+let getPackage ~uri =
+  let open SharedTypes in
+  if Hashtbl.mem state.rootForUri uri then
+    Some (Hashtbl.find state.packagesByRoot (Hashtbl.find state.rootForUri uri))
+  else
+    match findRoot ~uri state.packagesByRoot with
+    | None ->
+      Log.log "No root directory found";
+      None
+    | Some (`Root rootPath) ->
+      Hashtbl.replace state.rootForUri uri rootPath;
+      Some
+        (Hashtbl.find state.packagesByRoot (Hashtbl.find state.rootForUri uri))
+    | Some (`Bs rootPath) -> (
+      match newBsPackage ~rootPath with
+      | None -> None
+      | Some package ->
+        Hashtbl.replace state.rootForUri uri package.rootPath;
+        Hashtbl.replace state.packagesByRoot package.rootPath package;
+        Some package)
diff --git a/analysis/src/Pos.ml b/analysis/src/Pos.ml
new file mode 100644
index 0000000000..081c575ea9
--- /dev/null
+++ b/analysis/src/Pos.ml
@@ -0,0 +1,28 @@
+type t = int * int
+
+let ofLexing {Lexing.pos_lnum; pos_cnum; pos_bol} =
+  (pos_lnum - 1, pos_cnum - pos_bol)
+
+let toString (loc, col) = Printf.sprintf "%d:%d" loc col
+
+let offsetOfLine text line =
+  let ln = String.length text in
+  let rec loop i lno =
+    if i >= ln then None
+    else
+      match text.[i] with
+      | '\n' -> if lno = line - 1 then Some (i + 1) else loop (i + 1) (lno + 1)
+      | _ -> loop (i + 1) lno
+  in
+  match line with
+  | 0 -> Some 0
+  | _ -> loop 0 0
+
+let positionToOffset text (line, character) =
+  match offsetOfLine text line with
+  | None -> None
+  | Some bol ->
+    if bol + character <= String.length text then Some (bol + character)
+    else None
+
+let posBeforeCursor pos = (fst pos, max 0 (snd pos - 1))
diff --git a/analysis/src/PrintType.ml b/analysis/src/PrintType.ml
new file mode 100644
index 0000000000..3234d11b45
--- /dev/null
+++ b/analysis/src/PrintType.ml
@@ -0,0 +1,11 @@
+let printExpr ?(lineWidth = 60) typ =
+  Printtyp.reset_names ();
+  Printtyp.reset_and_mark_loops typ;
+  Res_doc.to_string ~width:lineWidth
+    (Res_outcome_printer.print_out_type_doc (Printtyp.tree_of_typexp false typ))
+
+let printDecl ?printNameAsIs ~recStatus name decl =
+  Printtyp.reset_names ();
+  Res_doc.to_string ~width:60
+    (Res_outcome_printer.print_out_sig_item_doc ?print_name_as_is:printNameAsIs
+       (Printtyp.tree_of_type_declaration (Ident.create name) decl recStatus))
diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml
new file mode 100644
index 0000000000..60ba88c21b
--- /dev/null
+++ b/analysis/src/ProcessAttributes.ml
@@ -0,0 +1,50 @@
+open SharedTypes
+
+(* TODO should I hang on to location? *)
+let rec findDocAttribute attributes =
+  let open Parsetree in
+  match attributes with
+  | [] -> None
+  | ( {Asttypes.txt = "ocaml.doc" | "ocaml.text" | "ns.doc" | "res.doc"},
+      PStr
+        [
+          {
+            pstr_desc =
+              Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (doc, _))}, _);
+          };
+        ] )
+    :: _ ->
+    Some doc
+  | _ :: rest -> findDocAttribute rest
+
+let rec findDeprecatedAttribute attributes =
+  let open Parsetree in
+  match attributes with
+  | [] -> None
+  | ( {Asttypes.txt = "deprecated"},
+      PStr
+        [
+          {
+            pstr_desc =
+              Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (msg, _))}, _);
+          };
+        ] )
+    :: _ ->
+    Some msg
+  | ({Asttypes.txt = "deprecated"}, _) :: _ -> Some ""
+  | _ :: rest -> findDeprecatedAttribute rest
+
+let newDeclared ~item ~extent ~name ~stamp ~modulePath isExported attributes =
+  {
+    Declared.name;
+    stamp;
+    extentLoc = extent;
+    isExported;
+    modulePath;
+    deprecated = findDeprecatedAttribute attributes;
+    docstring =
+      (match findDocAttribute attributes with
+      | None -> []
+      | Some d -> [d]);
+    item;
+  }
diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml
new file mode 100644
index 0000000000..b60ded55dc
--- /dev/null
+++ b/analysis/src/ProcessCmt.ml
@@ -0,0 +1,683 @@
+open SharedTypes
+
+let isModuleType (declared : Module.t Declared.t) =
+  match declared.modulePath with
+  | ExportedModule {isType} -> isType
+  | _ -> false
+
+let addDeclared ~(name : string Location.loc) ~extent ~stamp ~(env : Env.t)
+    ~item attributes addExported addStamp =
+  let isExported = addExported name.txt stamp in
+  let declared =
+    ProcessAttributes.newDeclared ~item ~extent ~name ~stamp
+      ~modulePath:env.modulePath isExported attributes
+  in
+  addStamp env.stamps stamp declared;
+  declared
+
+let attrsToDocstring attrs =
+  match ProcessAttributes.findDocAttribute attrs with
+  | None -> []
+  | Some docstring -> [docstring]
+
+let mapRecordField {Types.ld_id; ld_type; ld_attributes} =
+  let astamp = Ident.binding_time ld_id in
+  let name = Ident.name ld_id in
+  {
+    stamp = astamp;
+    fname = Location.mknoloc name;
+    typ = ld_type;
+    optional = Res_parsetree_viewer.has_optional_attribute ld_attributes;
+    docstring =
+      (match ProcessAttributes.findDocAttribute ld_attributes with
+      | None -> []
+      | Some docstring -> [docstring]);
+    deprecated = ProcessAttributes.findDeprecatedAttribute ld_attributes;
+  }
+
+let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t)
+    (item : Types.signature_item) =
+  match item with
+  | Sig_value (ident, {val_type; val_attributes; val_loc = loc}) ->
+    let item = val_type in
+    let stamp = Ident.binding_time ident in
+    let oldDeclared = Stamps.findValue env.stamps stamp in
+    let declared =
+      addDeclared
+        ~name:(Location.mkloc (Ident.name ident) loc)
+        ~extent:loc ~stamp ~env ~item val_attributes
+        (Exported.add exported Exported.Value)
+        Stamps.addValue
+    in
+    let declared =
+      (* When an id is shadowed, a module constraint without the doc comment is created.
+         Here the existing doc comment is restored. See https://github.com/rescript-lang/rescript-vscode/issues/621 *)
+      match oldDeclared with
+      | Some oldDeclared when declared.docstring = [] ->
+        let newDeclared = {declared with docstring = oldDeclared.docstring} in
+        Stamps.addValue env.stamps stamp newDeclared;
+        newDeclared
+      | _ -> declared
+    in
+    [
+      {
+        Module.kind = Module.Value declared.item;
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Sig_type
+      ( ident,
+        ({type_loc; type_kind; type_manifest; type_attributes} as decl),
+        recStatus ) ->
+    let declared =
+      let name = Location.mknoloc (Ident.name ident) in
+      addDeclared ~extent:type_loc
+        ~item:
+          {
+            Type.decl;
+            attributes = type_attributes;
+            name = name.txt;
+            kind =
+              (match type_kind with
+              | Type_abstract -> (
+                match type_manifest with
+                | Some {desc = Tconstr (path, args, _)} ->
+                  Abstract (Some (path, args))
+                | Some {desc = Ttuple items} -> Tuple items
+                (* TODO dig *)
+                | _ -> Abstract None)
+              | Type_open -> Open
+              | Type_variant constructors ->
+                Variant
+                  (constructors
+                  |> List.map
+                       (fun
+                         {Types.cd_loc; cd_id; cd_args; cd_res; cd_attributes}
+                       ->
+                         let name = Ident.name cd_id in
+                         let stamp = Ident.binding_time cd_id in
+                         let item =
+                           {
+                             Constructor.stamp;
+                             cname = Location.mknoloc name;
+                             args =
+                               (match cd_args with
+                               | Cstr_tuple args ->
+                                 Args
+                                   (args
+                                   |> List.map (fun t -> (t, Location.none)))
+                               | Cstr_record fields ->
+                                 InlineRecord (fields |> List.map mapRecordField));
+                             res = cd_res;
+                             typeDecl = (name, decl);
+                             docstring = attrsToDocstring cd_attributes;
+                             deprecated =
+                               ProcessAttributes.findDeprecatedAttribute
+                                 cd_attributes;
+                           }
+                         in
+                         let declared =
+                           ProcessAttributes.newDeclared ~item ~extent:cd_loc
+                             ~name:(Location.mknoloc name)
+                             ~stamp (* TODO maybe this needs another child *)
+                             ~modulePath:env.modulePath true cd_attributes
+                         in
+                         Stamps.addConstructor env.stamps stamp declared;
+                         item))
+              | Type_record (fields, _) ->
+                Record (fields |> List.map mapRecordField));
+          }
+        ~name ~stamp:(Ident.binding_time ident) ~env type_attributes
+        (Exported.add exported Exported.Type)
+        Stamps.addType
+    in
+    [
+      {
+        Module.kind = Type (declared.item, recStatus);
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Sig_module (ident, {md_type; md_attributes; md_loc}, _) ->
+    let name = Ident.name ident in
+    let declared =
+      addDeclared ~extent:md_loc
+        ~item:(forTypeModule ~name ~env md_type)
+        ~name:(Location.mkloc name md_loc)
+        ~stamp:(Ident.binding_time ident) ~env md_attributes
+        (Exported.add exported Exported.Module)
+        Stamps.addModule
+    in
+    [
+      {
+        Module.kind =
+          Module {type_ = declared.item; isModuleType = isModuleType declared};
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | _ -> []
+
+and forTypeSignature ~name ~env signature =
+  let exported = Exported.init () in
+  let items =
+    List.fold_right
+      (fun item items -> forTypeSignatureItem ~env ~exported item @ items)
+      signature []
+  in
+  {Module.name; docstring = []; exported; items; deprecated = None}
+
+and forTypeModule ~name ~env moduleType =
+  match moduleType with
+  | Types.Mty_ident path -> Ident path
+  | Mty_alias (_ (* 402 *), path) -> Ident path
+  | Mty_signature signature -> Structure (forTypeSignature ~name ~env signature)
+  | Mty_functor (_argIdent, _argType, resultType) ->
+    forTypeModule ~name ~env resultType
+
+let getModuleTypePath mod_desc =
+  match mod_desc with
+  | Typedtree.Tmty_ident (path, _) | Tmty_alias (path, _) -> Some path
+  | Tmty_signature _ | Tmty_functor _ | Tmty_with _ | Tmty_typeof _ -> None
+
+let forTypeDeclaration ~env ~(exported : Exported.t)
+    {
+      Typedtree.typ_id;
+      typ_loc;
+      typ_name = name;
+      typ_attributes;
+      typ_type;
+      typ_kind;
+      typ_manifest;
+    } ~recStatus =
+  let stamp = Ident.binding_time typ_id in
+  let declared =
+    addDeclared ~extent:typ_loc
+      ~item:
+        {
+          Type.decl = typ_type;
+          attributes = typ_attributes;
+          name = name.txt;
+          kind =
+            (match typ_kind with
+            | Ttype_abstract -> (
+              match typ_manifest with
+              | Some {ctyp_desc = Ttyp_constr (path, _lident, args)} ->
+                Abstract
+                  (Some (path, args |> List.map (fun t -> t.Typedtree.ctyp_type)))
+              | Some {ctyp_desc = Ttyp_tuple items} ->
+                Tuple (items |> List.map (fun t -> t.Typedtree.ctyp_type))
+              (* TODO dig *)
+              | _ -> Abstract None)
+            | Ttype_open -> Open
+            | Ttype_variant constructors ->
+              Variant
+                (constructors
+                |> List.map
+                     (fun
+                       {
+                         Typedtree.cd_id;
+                         cd_name = cname;
+                         cd_args;
+                         cd_res;
+                         cd_attributes;
+                         cd_loc;
+                       }
+                     ->
+                       let stamp = Ident.binding_time cd_id in
+                       let item =
+                         {
+                           Constructor.stamp;
+                           cname;
+                           deprecated =
+                             ProcessAttributes.findDeprecatedAttribute
+                               cd_attributes;
+                           args =
+                             (match cd_args with
+                             | Cstr_tuple args ->
+                               Args
+                                 (args
+                                 |> List.map (fun t ->
+                                        (t.Typedtree.ctyp_type, t.ctyp_loc)))
+                             | Cstr_record fields ->
+                               InlineRecord
+                                 (fields
+                                 |> List.map
+                                      (fun (f : Typedtree.label_declaration) ->
+                                        let astamp =
+                                          Ident.binding_time f.ld_id
+                                        in
+                                        let name = Ident.name f.ld_id in
+                                        {
+                                          stamp = astamp;
+                                          fname = Location.mknoloc name;
+                                          typ = f.ld_type.ctyp_type;
+                                          optional =
+                                            Res_parsetree_viewer
+                                            .has_optional_attribute
+                                              f.ld_attributes;
+                                          docstring =
+                                            (match
+                                               ProcessAttributes
+                                               .findDocAttribute f.ld_attributes
+                                             with
+                                            | None -> []
+                                            | Some docstring -> [docstring]);
+                                          deprecated =
+                                            ProcessAttributes
+                                            .findDeprecatedAttribute
+                                              f.ld_attributes;
+                                        })));
+                           res =
+                             (match cd_res with
+                             | None -> None
+                             | Some t -> Some t.ctyp_type);
+                           typeDecl = (name.txt, typ_type);
+                           docstring = attrsToDocstring cd_attributes;
+                         }
+                       in
+                       let declared =
+                         ProcessAttributes.newDeclared ~item ~extent:cd_loc
+                           ~name:cname ~stamp ~modulePath:env.modulePath true
+                           cd_attributes
+                       in
+                       Stamps.addConstructor env.stamps stamp declared;
+                       item))
+            | Ttype_record fields ->
+              Record
+                (fields
+                |> List.map
+                     (fun
+                       {
+                         Typedtree.ld_id;
+                         ld_name = fname;
+                         ld_type = {ctyp_type};
+                         ld_attributes;
+                       }
+                     ->
+                       let fstamp = Ident.binding_time ld_id in
+                       {
+                         stamp = fstamp;
+                         fname;
+                         typ = ctyp_type;
+                         optional =
+                           Res_parsetree_viewer.has_optional_attribute
+                             ld_attributes;
+                         docstring = attrsToDocstring ld_attributes;
+                         deprecated =
+                           ProcessAttributes.findDeprecatedAttribute
+                             ld_attributes;
+                       })));
+        }
+      ~name ~stamp ~env typ_attributes
+      (Exported.add exported Exported.Type)
+      Stamps.addType
+  in
+  {
+    Module.kind = Module.Type (declared.item, recStatus);
+    name = declared.name.txt;
+    docstring = declared.docstring;
+    deprecated = declared.deprecated;
+    loc = declared.extentLoc;
+  }
+
+let rec forSignatureItem ~env ~(exported : Exported.t)
+    (item : Typedtree.signature_item) =
+  match item.sig_desc with
+  | Tsig_value {val_id; val_loc; val_name = name; val_desc; val_attributes} ->
+    let declared =
+      addDeclared ~name
+        ~stamp:(Ident.binding_time val_id)
+        ~extent:val_loc ~item:val_desc.ctyp_type ~env val_attributes
+        (Exported.add exported Exported.Value)
+        Stamps.addValue
+    in
+    [
+      {
+        Module.kind = Module.Value declared.item;
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Tsig_type (recFlag, decls) ->
+    decls
+    |> List.mapi (fun i decl ->
+           let recStatus =
+             match recFlag with
+             | Recursive when i = 0 -> Types.Trec_first
+             | Nonrecursive when i = 0 -> Types.Trec_not
+             | _ -> Types.Trec_next
+           in
+           decl |> forTypeDeclaration ~env ~exported ~recStatus)
+  | Tsig_module
+      {md_id; md_attributes; md_loc; md_name = name; md_type = {mty_type}} ->
+    let item =
+      forTypeModule ~name:name.txt
+        ~env:(env |> Env.addModule ~name:name.txt)
+        mty_type
+    in
+    let declared =
+      addDeclared ~item ~name ~extent:md_loc ~stamp:(Ident.binding_time md_id)
+        ~env md_attributes
+        (Exported.add exported Exported.Module)
+        Stamps.addModule
+    in
+    [
+      {
+        Module.kind =
+          Module {type_ = declared.item; isModuleType = isModuleType declared};
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Tsig_recmodule modDecls ->
+    modDecls
+    |> List.map (fun modDecl ->
+           forSignatureItem ~env ~exported
+             {item with sig_desc = Tsig_module modDecl})
+    |> List.flatten
+  | Tsig_include {incl_mod; incl_type} ->
+    let env =
+      match getModuleTypePath incl_mod.mty_desc with
+      | None -> env
+      | Some path ->
+        {env with modulePath = IncludedModule (path, env.modulePath)}
+    in
+    let topLevel =
+      List.fold_right
+        (fun item items -> forTypeSignatureItem ~env ~exported item @ items)
+        incl_type []
+    in
+    topLevel
+  (* TODO: process other things here *)
+  | _ -> []
+
+let forSignature ~name ~env sigItems =
+  let exported = Exported.init () in
+  let items =
+    sigItems |> List.map (forSignatureItem ~env ~exported) |> List.flatten
+  in
+  let attributes =
+    match sigItems with
+    | {sig_desc = Tsig_attribute attribute} :: _ -> [attribute]
+    | _ -> []
+  in
+  let docstring = attrsToDocstring attributes in
+  let deprecated = ProcessAttributes.findDeprecatedAttribute attributes in
+  {Module.name; docstring; exported; items; deprecated}
+
+let forTreeModuleType ~name ~env {Typedtree.mty_desc} =
+  match mty_desc with
+  | Tmty_ident _ -> None
+  | Tmty_signature {sig_items} ->
+    let contents = forSignature ~name ~env sig_items in
+    Some (Module.Structure contents)
+  | _ -> None
+
+let rec getModulePath mod_desc =
+  match mod_desc with
+  | Typedtree.Tmod_ident (path, _lident) -> Some path
+  | Tmod_structure _ -> None
+  | Tmod_functor (_ident, _argName, _maybeType, _resultExpr) -> None
+  | Tmod_apply (functor_, _arg, _coercion) -> getModulePath functor_.mod_desc
+  | Tmod_unpack (_expr, _moduleType) -> None
+  | Tmod_constraint (expr, _typ, _constraint, _coercion) ->
+    getModulePath expr.mod_desc
+
+let rec forStructureItem ~env ~(exported : Exported.t) item =
+  match item.Typedtree.str_desc with
+  | Tstr_value (_isRec, bindings) ->
+    let items = ref [] in
+    let rec handlePattern attributes pat =
+      match pat.Typedtree.pat_desc with
+      | Tpat_var (ident, name)
+      | Tpat_alias (_, ident, name) (* let x : t = ... *) ->
+        let item = pat.pat_type in
+        let declared =
+          addDeclared ~name ~stamp:(Ident.binding_time ident) ~env
+            ~extent:pat.pat_loc ~item attributes
+            (Exported.add exported Exported.Value)
+            Stamps.addValue
+        in
+        items :=
+          {
+            Module.kind = Module.Value declared.item;
+            name = declared.name.txt;
+            docstring = declared.docstring;
+            deprecated = declared.deprecated;
+            loc = declared.extentLoc;
+          }
+          :: !items
+      | Tpat_tuple pats | Tpat_array pats | Tpat_construct (_, _, pats) ->
+        pats |> List.iter (fun p -> handlePattern [] p)
+      | Tpat_or (p, _, _) -> handlePattern [] p
+      | Tpat_record (items, _) ->
+        items |> List.iter (fun (_, _, p) -> handlePattern [] p)
+      | Tpat_lazy p -> handlePattern [] p
+      | Tpat_variant (_, Some p, _) -> handlePattern [] p
+      | Tpat_variant (_, None, _) | Tpat_any | Tpat_constant _ -> ()
+    in
+    List.iter
+      (fun {Typedtree.vb_pat; vb_attributes} ->
+        handlePattern vb_attributes vb_pat)
+      bindings;
+    !items
+  | Tstr_module
+      {mb_id; mb_attributes; mb_loc; mb_name = name; mb_expr = {mod_desc}}
+    when not
+           (String.length name.txt >= 6
+           && (String.sub name.txt 0 6 = "local_") [@doesNotRaise])
+         (* %%private generates a dummy module called local_... *) ->
+    let item = forModule ~env mod_desc name.txt in
+    let declared =
+      addDeclared ~item ~name ~extent:mb_loc ~stamp:(Ident.binding_time mb_id)
+        ~env mb_attributes
+        (Exported.add exported Exported.Module)
+        Stamps.addModule
+    in
+    [
+      {
+        Module.kind =
+          Module {type_ = declared.item; isModuleType = isModuleType declared};
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Tstr_recmodule modDecls ->
+    modDecls
+    |> List.map (fun modDecl ->
+           forStructureItem ~env ~exported
+             {item with str_desc = Tstr_module modDecl})
+    |> List.flatten
+  | Tstr_modtype
+      {
+        mtd_name = name;
+        mtd_id;
+        mtd_attributes;
+        mtd_type = Some {mty_type = modType};
+        mtd_loc;
+      } ->
+    let env = env |> Env.addModuleType ~name:name.txt in
+    let modTypeItem = forTypeModule ~name:name.txt ~env modType in
+    let declared =
+      addDeclared ~item:modTypeItem ~name ~extent:mtd_loc
+        ~stamp:(Ident.binding_time mtd_id)
+        ~env mtd_attributes
+        (Exported.add exported Exported.Module)
+        Stamps.addModule
+    in
+    [
+      {
+        Module.kind =
+          Module {type_ = declared.item; isModuleType = isModuleType declared};
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Tstr_include {incl_mod; incl_type} ->
+    let env =
+      match getModulePath incl_mod.mod_desc with
+      | None -> env
+      | Some path ->
+        {env with modulePath = IncludedModule (path, env.modulePath)}
+    in
+    let topLevel =
+      List.fold_right
+        (fun item items -> forTypeSignatureItem ~env ~exported item @ items)
+        incl_type []
+    in
+    topLevel
+  | Tstr_primitive vd when JsxHacks.primitiveIsFragment vd = false ->
+    let declared =
+      addDeclared ~extent:vd.val_loc ~item:vd.val_val.val_type ~name:vd.val_name
+        ~stamp:(Ident.binding_time vd.val_id)
+        ~env vd.val_attributes
+        (Exported.add exported Exported.Value)
+        Stamps.addValue
+    in
+    [
+      {
+        Module.kind = Value declared.item;
+        name = declared.name.txt;
+        docstring = declared.docstring;
+        deprecated = declared.deprecated;
+        loc = declared.extentLoc;
+      };
+    ]
+  | Tstr_type (recFlag, decls) ->
+    decls
+    |> List.mapi (fun i decl ->
+           let recStatus =
+             match recFlag with
+             | Recursive when i = 0 -> Types.Trec_first
+             | Nonrecursive when i = 0 -> Types.Trec_not
+             | _ -> Types.Trec_next
+           in
+           decl |> forTypeDeclaration ~env ~exported ~recStatus)
+  | _ -> []
+
+and forModule ~env mod_desc moduleName =
+  match mod_desc with
+  | Tmod_ident (path, _lident) -> Ident path
+  | Tmod_structure structure ->
+    let env = env |> Env.addModule ~name:moduleName in
+    let contents = forStructure ~name:moduleName ~env structure.str_items in
+    Structure contents
+  | Tmod_functor (ident, argName, maybeType, resultExpr) ->
+    (match maybeType with
+    | None -> ()
+    | Some t -> (
+      match forTreeModuleType ~name:argName.txt ~env t with
+      | None -> ()
+      | Some kind ->
+        let stamp = Ident.binding_time ident in
+        let declared =
+          ProcessAttributes.newDeclared ~item:kind ~name:argName
+            ~extent:t.Typedtree.mty_loc ~stamp ~modulePath:NotVisible false []
+        in
+        Stamps.addModule env.stamps stamp declared));
+    forModule ~env resultExpr.mod_desc moduleName
+  | Tmod_apply (functor_, _arg, _coercion) ->
+    forModule ~env functor_.mod_desc moduleName
+  | Tmod_unpack (_expr, moduleType) ->
+    let env = env |> Env.addModule ~name:moduleName in
+    forTypeModule ~name:moduleName ~env moduleType
+  | Tmod_constraint (expr, typ, _constraint, _coercion) ->
+    (* TODO do this better I think *)
+    let modKind = forModule ~env expr.mod_desc moduleName in
+    let env = env |> Env.addModule ~name:moduleName in
+    let modTypeKind = forTypeModule ~name:moduleName ~env typ in
+    Constraint (modKind, modTypeKind)
+
+and forStructure ~name ~env strItems =
+  let exported = Exported.init () in
+  let items =
+    List.fold_right
+      (fun item results -> forStructureItem ~env ~exported item @ results)
+      strItems []
+  in
+  let attributes =
+    strItems
+    |> List.filter_map (fun (struc : Typedtree.structure_item) ->
+           match struc with
+           | {str_desc = Tstr_attribute attr} -> Some attr
+           | _ -> None)
+  in
+  let docstring = attrsToDocstring attributes in
+  let deprecated = ProcessAttributes.findDeprecatedAttribute attributes in
+  {Module.name; docstring; exported; items; deprecated}
+
+let fileForCmtInfos ~moduleName ~uri
+    ({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) =
+  let env =
+    {Env.stamps = Stamps.init (); modulePath = File (uri, moduleName)}
+  in
+  match cmt_annots with
+  | Partial_implementation parts ->
+    let items =
+      parts |> Array.to_list
+      |> Utils.filterMap (fun p ->
+             match (p : Cmt_format.binary_part) with
+             | Partial_structure str -> Some str.str_items
+             | Partial_structure_item str -> Some [str]
+             | _ -> None)
+      |> List.concat
+    in
+    let structure = forStructure ~name:moduleName ~env items in
+    {File.uri; moduleName = cmt_modname; stamps = env.stamps; structure}
+  | Partial_interface parts ->
+    let items =
+      parts |> Array.to_list
+      |> Utils.filterMap (fun (p : Cmt_format.binary_part) ->
+             match p with
+             | Partial_signature str -> Some str.sig_items
+             | Partial_signature_item str -> Some [str]
+             | _ -> None)
+      |> List.concat
+    in
+    let structure = forSignature ~name:moduleName ~env items in
+    {uri; moduleName = cmt_modname; stamps = env.stamps; structure}
+  | Implementation structure ->
+    let structure = forStructure ~name:moduleName ~env structure.str_items in
+    {uri; moduleName = cmt_modname; stamps = env.stamps; structure}
+  | Interface signature ->
+    let structure = forSignature ~name:moduleName ~env signature.sig_items in
+    {uri; moduleName = cmt_modname; stamps = env.stamps; structure}
+  | _ -> File.create moduleName uri
+
+let fileForCmt ~moduleName ~cmt ~uri =
+  match Hashtbl.find_opt state.cmtCache cmt with
+  | Some file -> Some file
+  | None -> (
+    match Shared.tryReadCmt cmt with
+    | None -> None
+    | Some infos ->
+      let file = fileForCmtInfos ~moduleName ~uri infos in
+      Hashtbl.replace state.cmtCache cmt file;
+      Some file)
+
+let fileForModule moduleName ~package =
+  match Hashtbl.find_opt package.pathsForModule moduleName with
+  | Some paths ->
+    let uri = getUri paths in
+    let cmt = getCmtPath ~uri paths in
+    Log.log ("fileForModule " ^ showPaths paths);
+    fileForCmt ~cmt ~moduleName ~uri
+  | None ->
+    Log.log ("No path for module " ^ moduleName);
+    None
diff --git a/analysis/src/ProcessExtra.ml b/analysis/src/ProcessExtra.ml
new file mode 100644
index 0000000000..e153c76cba
--- /dev/null
+++ b/analysis/src/ProcessExtra.ml
@@ -0,0 +1,429 @@
+open SharedTypes
+
+let addLocItem extra loc locType =
+  if not loc.Warnings.loc_ghost then
+    extra.locItems <- {loc; locType} :: extra.locItems
+
+let addReference ~extra stamp loc =
+  Hashtbl.replace extra.internalReferences stamp
+    (loc
+    ::
+    (if Hashtbl.mem extra.internalReferences stamp then
+       Hashtbl.find extra.internalReferences stamp
+     else []))
+
+let extraForFile ~(file : File.t) =
+  let extra = initExtra () in
+  file.stamps
+  |> Stamps.iterModules (fun stamp (d : Module.t Declared.t) ->
+         addLocItem extra d.name.loc (LModule (Definition (stamp, Module)));
+         addReference ~extra stamp d.name.loc);
+  file.stamps
+  |> Stamps.iterValues (fun stamp (d : Types.type_expr Declared.t) ->
+         addLocItem extra d.name.loc
+           (Typed (d.name.txt, d.item, Definition (stamp, Value)));
+         addReference ~extra stamp d.name.loc);
+  file.stamps
+  |> Stamps.iterTypes (fun stamp (d : Type.t Declared.t) ->
+         addLocItem extra d.name.loc
+           (TypeDefinition (d.name.txt, d.item.Type.decl, stamp));
+         addReference ~extra stamp d.name.loc;
+         match d.item.Type.kind with
+         | Record labels ->
+           labels
+           |> List.iter (fun {stamp; fname; typ} ->
+                  addReference ~extra stamp fname.loc;
+                  addLocItem extra fname.loc
+                    (Typed
+                       (d.name.txt, typ, Definition (d.stamp, Field fname.txt))))
+         | Variant constructors ->
+           constructors
+           |> List.iter (fun {Constructor.stamp; cname} ->
+                  addReference ~extra stamp cname.loc;
+                  let t =
+                    {
+                      Types.id = 0;
+                      level = 0;
+                      desc =
+                        Tconstr
+                          ( Path.Pident
+                              {Ident.stamp; name = d.name.txt; flags = 0},
+                            [],
+                            ref Types.Mnil );
+                    }
+                  in
+                  addLocItem extra cname.loc
+                    (Typed
+                       ( d.name.txt,
+                         t,
+                         Definition (d.stamp, Constructor cname.txt) )))
+         | _ -> ());
+  extra
+
+let addExternalReference ~extra moduleName path tip loc =
+  (* TODO need to follow the path, and be able to load the files to follow module references... *)
+  Hashtbl.replace extra.externalReferences moduleName
+    ((path, tip, loc)
+    ::
+    (if Hashtbl.mem extra.externalReferences moduleName then
+       Hashtbl.find extra.externalReferences moduleName
+     else []))
+
+let addFileReference ~extra moduleName loc =
+  let newLocs =
+    match Hashtbl.find_opt extra.fileReferences moduleName with
+    | Some oldLocs -> LocationSet.add loc oldLocs
+    | None -> LocationSet.singleton loc
+  in
+  Hashtbl.replace extra.fileReferences moduleName newLocs
+
+let handleConstructor txt =
+  match txt with
+  | Longident.Lident name -> name
+  | Ldot (_left, name) -> name
+  | Lapply (_, _) -> assert false
+
+let rec lidIsComplex (lid : Longident.t) =
+  match lid with
+  | Lapply _ -> true
+  | Ldot (lid, _) -> lidIsComplex lid
+  | _ -> false
+
+let extraForStructureItems ~(iterator : Tast_iterator.iterator)
+    (items : Typedtree.structure_item list) =
+  items |> List.iter (iterator.structure_item iterator)
+
+let extraForSignatureItems ~(iterator : Tast_iterator.iterator)
+    (items : Typedtree.signature_item list) =
+  items |> List.iter (iterator.signature_item iterator)
+
+let extraForCmt ~(iterator : Tast_iterator.iterator)
+    ({cmt_annots} : Cmt_format.cmt_infos) =
+  let extraForParts parts =
+    parts
+    |> Array.iter (fun part ->
+           match part with
+           | Cmt_format.Partial_signature str -> iterator.signature iterator str
+           | Partial_signature_item str -> iterator.signature_item iterator str
+           | Partial_expression expression -> iterator.expr iterator expression
+           | Partial_pattern pattern -> iterator.pat iterator pattern
+           | Partial_class_expr _ -> ()
+           | Partial_module_type module_type ->
+             iterator.module_type iterator module_type
+           | Partial_structure _ | Partial_structure_item _ -> ())
+  in
+  match cmt_annots with
+  | Implementation structure ->
+    extraForStructureItems ~iterator structure.str_items
+  | Partial_implementation parts ->
+    let items =
+      parts |> Array.to_list
+      |> Utils.filterMap (fun (p : Cmt_format.binary_part) ->
+             match p with
+             | Partial_structure str -> Some str.str_items
+             | Partial_structure_item str -> Some [str]
+             (* | Partial_expression(exp) => Some([ str]) *)
+             | _ -> None)
+      |> List.concat
+    in
+    extraForStructureItems ~iterator items;
+    extraForParts parts
+  | Interface signature -> extraForSignatureItems ~iterator signature.sig_items
+  | Partial_interface parts ->
+    let items =
+      parts |> Array.to_list
+      |> Utils.filterMap (fun (p : Cmt_format.binary_part) ->
+             match p with
+             | Partial_signature s -> Some s.sig_items
+             | Partial_signature_item str -> Some [str]
+             | _ -> None)
+      |> List.concat
+    in
+    extraForSignatureItems ~iterator items;
+    extraForParts parts
+  | _ -> extraForStructureItems ~iterator []
+
+let addForPath ~env ~extra path lident loc typ tip =
+  let identName = Longident.last lident in
+  let identLoc = Utils.endOfLocation loc (String.length identName) in
+  let locType =
+    match ResolvePath.fromCompilerPath ~env path with
+    | Stamp stamp ->
+      addReference ~extra stamp identLoc;
+      LocalReference (stamp, tip)
+    | NotFound -> NotFound
+    | Global (moduleName, path) ->
+      addExternalReference ~extra moduleName path tip identLoc;
+      GlobalReference (moduleName, path, tip)
+    | Exported (env, name) -> (
+      match
+        match tip with
+        | Type -> Exported.find env.exported Exported.Type name
+        | _ -> Exported.find env.exported Exported.Value name
+      with
+      | Some stamp ->
+        addReference ~extra stamp identLoc;
+        LocalReference (stamp, tip)
+      | None -> NotFound)
+    | GlobalMod _ -> NotFound
+  in
+  addLocItem extra loc (Typed (identName, typ, locType))
+
+let addForPathParent ~env ~extra path loc =
+  let locType =
+    match ResolvePath.fromCompilerPath ~env path with
+    | GlobalMod moduleName ->
+      addFileReference ~extra moduleName loc;
+      TopLevelModule moduleName
+    | Stamp stamp ->
+      addReference ~extra stamp loc;
+      LModule (LocalReference (stamp, Module))
+    | NotFound -> LModule NotFound
+    | Global (moduleName, path) ->
+      addExternalReference ~extra moduleName path Module loc;
+      LModule (GlobalReference (moduleName, path, Module))
+    | Exported (env, name) -> (
+      match Exported.find env.exported Exported.Module name with
+      | Some stamp ->
+        addReference ~extra stamp loc;
+        LModule (LocalReference (stamp, Module))
+      | None -> LModule NotFound)
+  in
+  addLocItem extra loc locType
+
+let getTypeAtPath ~env path =
+  match ResolvePath.fromCompilerPath ~env path with
+  | GlobalMod _ -> `Not_found
+  | Global (moduleName, path) -> `Global (moduleName, path)
+  | NotFound -> `Not_found
+  | Exported (env, name) -> (
+    match Exported.find env.exported Exported.Type name with
+    | None -> `Not_found
+    | Some stamp -> (
+      let declaredType = Stamps.findType env.file.stamps stamp in
+      match declaredType with
+      | Some declaredType -> `Local declaredType
+      | None -> `Not_found))
+  | Stamp stamp -> (
+    let declaredType = Stamps.findType env.file.stamps stamp in
+    match declaredType with
+    | Some declaredType -> `Local declaredType
+    | None -> `Not_found)
+
+let addForField ~env ~extra ~recordType ~fieldType {Asttypes.txt; loc} =
+  match (Shared.dig recordType).desc with
+  | Tconstr (path, _args, _memo) ->
+    let t = getTypeAtPath ~env path in
+    let name = handleConstructor txt in
+    let nameLoc = Utils.endOfLocation loc (String.length name) in
+    let locType =
+      match t with
+      | `Local {stamp; item = {kind = Record fields}} -> (
+        match fields |> List.find_opt (fun f -> f.fname.txt = name) with
+        | Some {stamp = astamp} ->
+          addReference ~extra astamp nameLoc;
+          LocalReference (stamp, Field name)
+        | None -> NotFound)
+      | `Global (moduleName, path) ->
+        addExternalReference ~extra moduleName path (Field name) nameLoc;
+        GlobalReference (moduleName, path, Field name)
+      | _ -> NotFound
+    in
+    addLocItem extra nameLoc (Typed (name, fieldType, locType))
+  | _ -> ()
+
+let addForRecord ~env ~extra ~recordType items =
+  match (Shared.dig recordType).desc with
+  | Tconstr (path, _args, _memo) ->
+    let t = getTypeAtPath ~env path in
+    items
+    |> List.iter (fun ({Asttypes.txt; loc}, _, _) ->
+           (* let name = Longident.last(txt); *)
+           let name = handleConstructor txt in
+           let nameLoc = Utils.endOfLocation loc (String.length name) in
+           let locType =
+             match t with
+             | `Local {stamp; item = {kind = Record fields}} -> (
+               match fields |> List.find_opt (fun f -> f.fname.txt = name) with
+               | Some {stamp = astamp} ->
+                 addReference ~extra astamp nameLoc;
+                 LocalReference (stamp, Field name)
+               | None -> NotFound)
+             | `Global (moduleName, path) ->
+               addExternalReference ~extra moduleName path (Field name) nameLoc;
+               GlobalReference (moduleName, path, Field name)
+             | _ -> NotFound
+           in
+           addLocItem extra nameLoc (Typed (name, recordType, locType)))
+  | _ -> ()
+
+let addForConstructor ~env ~extra constructorType {Asttypes.txt; loc}
+    {Types.cstr_name} =
+  match (Shared.dig constructorType).desc with
+  | Tconstr (path, _args, _memo) ->
+    let name = handleConstructor txt in
+    let nameLoc = Utils.endOfLocation loc (String.length name) in
+    let t = getTypeAtPath ~env path in
+    let locType =
+      match t with
+      | `Local {stamp; item = {kind = Variant constructors}} -> (
+        match
+          constructors
+          |> List.find_opt (fun c -> c.Constructor.cname.txt = cstr_name)
+        with
+        | Some {stamp = cstamp} ->
+          addReference ~extra cstamp nameLoc;
+          LocalReference (stamp, Constructor name)
+        | None -> NotFound)
+      | `Global (moduleName, path) ->
+        addExternalReference ~extra moduleName path (Constructor name) nameLoc;
+        GlobalReference (moduleName, path, Constructor name)
+      | _ -> NotFound
+    in
+    addLocItem extra nameLoc (Typed (name, constructorType, locType))
+  | _ -> ()
+
+let rec addForLongident ~env ~extra top (path : Path.t) (txt : Longident.t) loc
+    =
+  if (not loc.Location.loc_ghost) && not (lidIsComplex txt) then (
+    let idLength = String.length (String.concat "." (Longident.flatten txt)) in
+    let reportedLength = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum in
+    let isPpx = idLength <> reportedLength in
+    if isPpx then
+      match top with
+      | Some (t, tip) -> addForPath ~env ~extra path txt loc t tip
+      | None -> addForPathParent ~env ~extra path loc
+    else
+      let l = Utils.endOfLocation loc (String.length (Longident.last txt)) in
+      (match top with
+      | Some (t, tip) -> addForPath ~env ~extra path txt l t tip
+      | None -> addForPathParent ~env ~extra path l);
+      match (path, txt) with
+      | Pdot (pinner, _pname, _), Ldot (inner, name) ->
+        addForLongident ~env ~extra None pinner inner
+          (Utils.chopLocationEnd loc (String.length name + 1))
+      | Pident _, Lident _ -> ()
+      | _ -> ())
+
+let rec handle_module_expr ~env ~extra expr =
+  match expr with
+  | Typedtree.Tmod_constraint (expr, _, _, _) ->
+    handle_module_expr ~env ~extra expr.mod_desc
+  | Tmod_ident (path, {txt; loc}) ->
+    if not (lidIsComplex txt) then
+      Log.log ("Ident!! " ^ String.concat "." (Longident.flatten txt));
+    addForLongident ~env ~extra None path txt loc
+  | Tmod_functor (_ident, _argName, _maybeType, resultExpr) ->
+    handle_module_expr ~env ~extra resultExpr.mod_desc
+  | Tmod_apply (obj, arg, _) ->
+    handle_module_expr ~env ~extra obj.mod_desc;
+    handle_module_expr ~env ~extra arg.mod_desc
+  | _ -> ()
+
+let structure_item ~env ~extra (iter : Tast_iterator.iterator) item =
+  (match item.Typedtree.str_desc with
+  | Tstr_include {incl_mod = expr} ->
+    handle_module_expr ~env ~extra expr.mod_desc
+  | Tstr_module {mb_expr} -> handle_module_expr ~env ~extra mb_expr.mod_desc
+  | Tstr_open {open_path; open_txt = {txt; loc}} ->
+    (* Log.log("Have an open here"); *)
+    addForLongident ~env ~extra None open_path txt loc
+  | _ -> ());
+  Tast_iterator.default_iterator.structure_item iter item
+
+let signature_item ~(file : File.t) ~extra (iter : Tast_iterator.iterator) item
+    =
+  (match item.Typedtree.sig_desc with
+  | Tsig_value {val_id; val_loc; val_name = name; val_desc; val_attributes} ->
+    let stamp = Ident.binding_time val_id in
+    if Stamps.findValue file.stamps stamp = None then (
+      let declared =
+        ProcessAttributes.newDeclared ~name ~stamp ~extent:val_loc
+          ~modulePath:NotVisible ~item:val_desc.ctyp_type false val_attributes
+      in
+      Stamps.addValue file.stamps stamp declared;
+      addReference ~extra stamp name.loc;
+      addLocItem extra name.loc
+        (Typed (name.txt, val_desc.ctyp_type, Definition (stamp, Value))))
+  | _ -> ());
+  Tast_iterator.default_iterator.signature_item iter item
+
+let typ ~env ~extra (iter : Tast_iterator.iterator) (item : Typedtree.core_type)
+    =
+  (match item.ctyp_desc with
+  | Ttyp_constr (path, {txt; loc}, _args) ->
+    addForLongident ~env ~extra (Some (item.ctyp_type, Type)) path txt loc
+  | _ -> ());
+  Tast_iterator.default_iterator.typ iter item
+
+let pat ~(file : File.t) ~env ~extra (iter : Tast_iterator.iterator)
+    (pattern : Typedtree.pattern) =
+  let addForPattern stamp name =
+    if Stamps.findValue file.stamps stamp = None then (
+      let declared =
+        ProcessAttributes.newDeclared ~name ~stamp ~modulePath:NotVisible
+          ~extent:pattern.pat_loc ~item:pattern.pat_type false
+          pattern.pat_attributes
+      in
+      Stamps.addValue file.stamps stamp declared;
+      addReference ~extra stamp name.loc;
+      addLocItem extra name.loc
+        (Typed (name.txt, pattern.pat_type, Definition (stamp, Value))))
+  in
+  (* Log.log("Entering pattern " ++ Utils.showLocation(pat_loc)); *)
+  (match pattern.pat_desc with
+  | Tpat_record (items, _) ->
+    addForRecord ~env ~extra ~recordType:pattern.pat_type items
+  | Tpat_construct (lident, constructor, _) ->
+    addForConstructor ~env ~extra pattern.pat_type lident constructor
+  | Tpat_alias (_inner, ident, name) ->
+    let stamp = Ident.binding_time ident in
+    addForPattern stamp name
+  | Tpat_var (ident, name) ->
+    (* Log.log("Pattern " ++ name.txt); *)
+    let stamp = Ident.binding_time ident in
+    addForPattern stamp name
+  | _ -> ());
+  Tast_iterator.default_iterator.pat iter pattern
+
+let expr ~env ~(extra : extra) (iter : Tast_iterator.iterator)
+    (expression : Typedtree.expression) =
+  (match expression.exp_desc with
+  | Texp_ident (path, {txt; loc}, _) when not (JsxHacks.pathIsFragment path) ->
+    addForLongident ~env ~extra (Some (expression.exp_type, Value)) path txt loc
+  | Texp_record {fields} ->
+    addForRecord ~env ~extra ~recordType:expression.exp_type
+      (fields |> Array.to_list
+      |> Utils.filterMap (fun (desc, item) ->
+             match item with
+             | Typedtree.Overridden (loc, _) -> Some (loc, desc, ())
+             | _ -> None))
+  | Texp_constant constant ->
+    addLocItem extra expression.exp_loc (Constant constant)
+  (* Skip unit and list literals *)
+  | Texp_construct ({txt = Lident ("()" | "::"); loc}, _, _args)
+    when loc.loc_end.pos_cnum - loc.loc_start.pos_cnum <> 2 ->
+    ()
+  | Texp_construct (lident, constructor, _args) ->
+    addForConstructor ~env ~extra expression.exp_type lident constructor
+  | Texp_field (inner, lident, _label_description) ->
+    addForField ~env ~extra ~recordType:inner.exp_type
+      ~fieldType:expression.exp_type lident
+  | _ -> ());
+  Tast_iterator.default_iterator.expr iter expression
+
+let getExtra ~file ~infos =
+  let extra = extraForFile ~file in
+  let env = QueryEnv.fromFile file in
+  let iterator =
+    {
+      Tast_iterator.default_iterator with
+      expr = expr ~env ~extra;
+      pat = pat ~env ~extra ~file;
+      signature_item = signature_item ~file ~extra;
+      structure_item = structure_item ~env ~extra;
+      typ = typ ~env ~extra;
+    }
+  in
+  extraForCmt ~iterator infos;
+  extra
diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml
new file mode 100644
index 0000000000..8297b3505c
--- /dev/null
+++ b/analysis/src/Protocol.ml
@@ -0,0 +1,361 @@
+type position = {line: int; character: int}
+type range = {start: position; end_: position}
+type markupContent = {kind: string; value: string}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command *)
+type command = {title: string; command: string}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeLens *)
+type codeLens = {range: range; command: command option}
+
+type inlayHint = {
+  position: position;
+  label: string;
+  kind: int;
+  paddingLeft: bool;
+  paddingRight: bool;
+}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#parameterInformation *)
+type parameterInformation = {label: int * int; documentation: markupContent}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureInformation *)
+type signatureInformation = {
+  label: string;
+  parameters: parameterInformation list;
+  documentation: markupContent option;
+}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureHelp *)
+type signatureHelp = {
+  signatures: signatureInformation list;
+  activeSignature: int option;
+  activeParameter: int option;
+}
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#insertTextFormat *)
+type insertTextFormat = Snippet
+
+let insertTextFormatToInt f =
+  match f with
+  | Snippet -> 2
+
+type completionItem = {
+  label: string;
+  kind: int;
+  tags: int list;
+  detail: string;
+  sortText: string option;
+  filterText: string option;
+  insertTextFormat: insertTextFormat option;
+  insertText: string option;
+  documentation: markupContent option;
+  data: (string * string) list option;
+}
+
+type location = {uri: string; range: range}
+type documentSymbolItem = {
+  name: string;
+  kind: int;
+  range: range;
+  children: documentSymbolItem list;
+}
+type renameFile = {oldUri: string; newUri: string}
+type textEdit = {range: range; newText: string}
+
+type diagnostic = {range: range; message: string; severity: int}
+
+type optionalVersionedTextDocumentIdentifier = {
+  version: int option;
+  uri: string;
+}
+
+type textDocumentEdit = {
+  textDocument: optionalVersionedTextDocumentIdentifier;
+  edits: textEdit list;
+}
+
+type createFileOptions = {overwrite: bool option; ignoreIfExists: bool option}
+type createFile = {uri: string; options: createFileOptions option}
+
+type documentChange =
+  | TextDocumentEdit of textDocumentEdit
+  | CreateFile of createFile
+
+type codeActionEdit = {documentChanges: documentChange list}
+type codeActionKind = RefactorRewrite
+
+type codeAction = {
+  title: string;
+  codeActionKind: codeActionKind;
+  edit: codeActionEdit;
+}
+
+let wrapInQuotes s = "\"" ^ Json.escape s ^ "\""
+
+let null = "null"
+let array l = "[" ^ String.concat ", " l ^ "]"
+
+let stringifyPosition p =
+  Printf.sprintf {|{"line": %i, "character": %i}|} p.line p.character
+
+let stringifyRange r =
+  Printf.sprintf {|{"start": %s, "end": %s}|}
+    (stringifyPosition r.start)
+    (stringifyPosition r.end_)
+
+let stringifyMarkupContent (m : markupContent) =
+  Printf.sprintf {|{"kind": %s, "value": %s}|} (wrapInQuotes m.kind)
+    (wrapInQuotes m.value)
+
+(** None values are not emitted in the output. *)
+let stringifyObject ?(startOnNewline = false) ?(indentation = 1) properties =
+  let indentationStr = String.make (indentation * 2) ' ' in
+  (if startOnNewline then "\n" ^ indentationStr else "")
+  ^ {|{
+|}
+  ^ (properties
+    |> List.filter_map (fun (key, value) ->
+           match value with
+           | None -> None
+           | Some v ->
+             Some (Printf.sprintf {|%s  "%s": %s|} indentationStr key v))
+    |> String.concat ",\n")
+  ^ "\n" ^ indentationStr ^ "}"
+
+let optWrapInQuotes s =
+  match s with
+  | None -> None
+  | Some s -> Some (wrapInQuotes s)
+
+let stringifyCompletionItem c =
+  stringifyObject
+    [
+      ("label", Some (wrapInQuotes c.label));
+      ("kind", Some (string_of_int c.kind));
+      ("tags", Some (c.tags |> List.map string_of_int |> array));
+      ("detail", Some (wrapInQuotes c.detail));
+      ( "documentation",
+        Some
+          (match c.documentation with
+          | None -> null
+          | Some doc -> stringifyMarkupContent doc) );
+      ("sortText", optWrapInQuotes c.sortText);
+      ("filterText", optWrapInQuotes c.filterText);
+      ("insertText", optWrapInQuotes c.insertText);
+      ( "insertTextFormat",
+        match c.insertTextFormat with
+        | None -> None
+        | Some insertTextFormat ->
+          Some (Printf.sprintf "%i" (insertTextFormatToInt insertTextFormat)) );
+      ( "data",
+        match c.data with
+        | None -> None
+        | Some fields ->
+          Some
+            (fields
+            |> List.map (fun (key, value) -> (key, Some (wrapInQuotes value)))
+            |> stringifyObject ~indentation:2) );
+    ]
+
+let stringifyHover value =
+  Printf.sprintf {|{"contents": %s}|}
+    (stringifyMarkupContent {kind = "markdown"; value})
+
+let stringifyLocation (h : location) =
+  Printf.sprintf {|{"uri": %s, "range": %s}|} (wrapInQuotes h.uri)
+    (stringifyRange h.range)
+
+let stringifyDocumentSymbolItems items =
+  let buf = Buffer.create 10 in
+  let stringifyName name = Printf.sprintf "\"%s\"" (Json.escape name) in
+  let stringifyKind kind = string_of_int kind in
+  let emitStr = Buffer.add_string buf in
+  let emitSep () = emitStr ",\n" in
+  let rec emitItem ~indent item =
+    let openBrace = Printf.sprintf "%s{\n" indent in
+    let closeBrace = Printf.sprintf "\n%s}" indent in
+    let indent = indent ^ "  " in
+    let emitField name s =
+      emitStr (Printf.sprintf "%s\"%s\": %s" indent name s)
+    in
+    emitStr openBrace;
+    emitField "name" (stringifyName item.name);
+    emitSep ();
+    emitField "kind" (stringifyKind item.kind);
+    emitSep ();
+    emitField "range" (stringifyRange item.range);
+    emitSep ();
+    emitField "selectionRange" (stringifyRange item.range);
+    if item.children <> [] then (
+      emitSep ();
+      emitField "children" "[\n";
+      emitBody ~indent (List.rev item.children);
+      emitStr "]");
+    emitStr closeBrace
+  and emitBody ~indent items =
+    match items with
+    | [] -> ()
+    | item :: rest ->
+      emitItem ~indent item;
+      if rest <> [] then emitSep ();
+      emitBody ~indent rest
+  in
+  let indent = "" in
+  emitStr "[\n";
+  emitBody ~indent (List.rev items);
+  emitStr "\n]";
+  Buffer.contents buf
+
+let stringifyRenameFile {oldUri; newUri} =
+  Printf.sprintf {|{
+  "kind": "rename",
+  "oldUri": %s,
+  "newUri": %s
+}|}
+    (wrapInQuotes oldUri) (wrapInQuotes newUri)
+
+let stringifyTextEdit (te : textEdit) =
+  Printf.sprintf {|{
+  "range": %s,
+  "newText": %s
+  }|}
+    (stringifyRange te.range) (wrapInQuotes te.newText)
+
+let stringifyoptionalVersionedTextDocumentIdentifier td =
+  Printf.sprintf {|{
+  "version": %s,
+  "uri": %s
+  }|}
+    (match td.version with
+    | None -> null
+    | Some v -> string_of_int v)
+    (wrapInQuotes td.uri)
+
+let stringifyTextDocumentEdit tde =
+  Printf.sprintf {|{
+  "textDocument": %s,
+  "edits": %s
+  }|}
+    (stringifyoptionalVersionedTextDocumentIdentifier tde.textDocument)
+    (tde.edits |> List.map stringifyTextEdit |> array)
+
+let stringifyCreateFile cf =
+  stringifyObject
+    [
+      ("kind", Some (wrapInQuotes "create"));
+      ("uri", Some (wrapInQuotes cf.uri));
+      ( "options",
+        match cf.options with
+        | None -> None
+        | Some options ->
+          Some
+            (stringifyObject
+               [
+                 ( "overwrite",
+                   match options.overwrite with
+                   | None -> None
+                   | Some ov -> Some (string_of_bool ov) );
+                 ( "ignoreIfExists",
+                   match options.ignoreIfExists with
+                   | None -> None
+                   | Some i -> Some (string_of_bool i) );
+               ]) );
+    ]
+
+let stringifyDocumentChange dc =
+  match dc with
+  | TextDocumentEdit tde -> stringifyTextDocumentEdit tde
+  | CreateFile cf -> stringifyCreateFile cf
+
+let codeActionKindToString kind =
+  match kind with
+  | RefactorRewrite -> "refactor.rewrite"
+
+let stringifyCodeActionEdit cae =
+  Printf.sprintf {|{"documentChanges": %s}|}
+    (cae.documentChanges |> List.map stringifyDocumentChange |> array)
+
+let stringifyCodeAction ca =
+  Printf.sprintf {|{"title": %s, "kind": %s, "edit": %s}|}
+    (wrapInQuotes ca.title)
+    (wrapInQuotes (codeActionKindToString ca.codeActionKind))
+    (ca.edit |> stringifyCodeActionEdit)
+
+let stringifyHint hint =
+  Printf.sprintf
+    {|{
+    "position": %s,
+    "label": %s,
+    "kind": %i,
+    "paddingLeft": %b,
+    "paddingRight": %b
+}|}
+    (stringifyPosition hint.position)
+    (wrapInQuotes hint.label) hint.kind hint.paddingLeft hint.paddingRight
+
+let stringifyCommand (command : command) =
+  Printf.sprintf {|{"title": %s, "command": %s}|}
+    (wrapInQuotes command.title)
+    (wrapInQuotes command.command)
+
+let stringifyCodeLens (codeLens : codeLens) =
+  Printf.sprintf
+    {|{
+        "range": %s,
+        "command": %s
+    }|}
+    (stringifyRange codeLens.range)
+    (match codeLens.command with
+    | None -> ""
+    | Some command -> stringifyCommand command)
+
+let stringifyParameterInformation (parameterInformation : parameterInformation)
+    =
+  Printf.sprintf {|{"label": %s, "documentation": %s}|}
+    (let line, chr = parameterInformation.label in
+     "[" ^ string_of_int line ^ ", " ^ string_of_int chr ^ "]")
+    (stringifyMarkupContent parameterInformation.documentation)
+
+let stringifySignatureInformation (signatureInformation : signatureInformation)
+    =
+  Printf.sprintf
+    {|{
+    "label": %s,
+    "parameters": %s%s
+  }|}
+    (wrapInQuotes signatureInformation.label)
+    (signatureInformation.parameters
+    |> List.map stringifyParameterInformation
+    |> array)
+    (match signatureInformation.documentation with
+    | None -> ""
+    | Some docs ->
+      Printf.sprintf ",\n    \"documentation\": %s"
+        (stringifyMarkupContent docs))
+
+let stringifySignatureHelp (signatureHelp : signatureHelp) =
+  Printf.sprintf
+    {|{
+  "signatures": %s,
+  "activeSignature": %s,
+  "activeParameter": %s
+}|}
+    (signatureHelp.signatures |> List.map stringifySignatureInformation |> array)
+    (match signatureHelp.activeSignature with
+    | None -> null
+    | Some activeSignature -> activeSignature |> string_of_int)
+    (match signatureHelp.activeParameter with
+    | None -> null
+    | Some activeParameter -> activeParameter |> string_of_int)
+
+(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *)
+let stringifyDiagnostic d =
+  Printf.sprintf
+    {|{
+  "range": %s,
+  "message": %s,
+  "severity": %d,
+  "source": "ReScript"
+}|}
+    (stringifyRange d.range) (wrapInQuotes d.message) d.severity
diff --git a/analysis/src/Range.ml b/analysis/src/Range.ml
new file mode 100644
index 0000000000..a743490757
--- /dev/null
+++ b/analysis/src/Range.ml
@@ -0,0 +1,6 @@
+type t = Pos.t * Pos.t
+
+let toString ((posStart, posEnd) : t) =
+  Printf.sprintf "[%s->%s]" (Pos.toString posStart) (Pos.toString posEnd)
+
+let hasPos ~pos ((posStart, posEnd) : t) = posStart <= pos && pos < posEnd
diff --git a/analysis/src/References.ml b/analysis/src/References.ml
new file mode 100644
index 0000000000..e047a2ba18
--- /dev/null
+++ b/analysis/src/References.ml
@@ -0,0 +1,573 @@
+open SharedTypes
+
+let debugReferences = ref true
+let maybeLog m = if !debugReferences then Log.log ("[ref] " ^ m)
+
+let checkPos (line, char)
+    {Location.loc_start = {pos_lnum; pos_bol; pos_cnum}; loc_end} =
+  if line < pos_lnum || (line = pos_lnum && char < pos_cnum - pos_bol) then
+    false
+  else if
+    line > loc_end.pos_lnum
+    || (line = loc_end.pos_lnum && char > loc_end.pos_cnum - loc_end.pos_bol)
+  then false
+  else true
+
+let locItemsForPos ~extra pos =
+  extra.locItems |> List.filter (fun {loc; locType = _} -> checkPos pos loc)
+
+let lineColToCmtLoc ~pos:(line, col) = (line + 1, col)
+
+let getLocItem ~full ~pos ~debug =
+  let log n msg = if debug then Printf.printf "getLocItem #%d: %s\n" n msg in
+  let pos = lineColToCmtLoc ~pos in
+  let locItems = locItemsForPos ~extra:full.extra pos in
+  if !Log.verbose then
+    print_endline
+      ("locItems:\n  "
+      ^ (locItems |> List.map locItemToString |> String.concat "\n  "));
+  let nameOf li =
+    match li.locType with
+    | Typed (n, _, _) -> n
+    | _ -> "NotFound"
+  in
+  match locItems with
+  | li1 :: li2 :: li3 :: ({locType = Typed ("makeProps", _, _)} as li4) :: _
+    when full.file.uri |> Uri.isInterface ->
+    log 1 "heuristic for makeProps in interface files";
+    if debug then
+      Printf.printf "n1:%s n2:%s n3:%s\n" (nameOf li1) (nameOf li2) (nameOf li3);
+    Some li4
+  | [
+   {locType = Constant _};
+   ({locType = Typed ("createDOMElementVariadic", _, _)} as li2);
+  ] ->
+    log 3 "heuristic for <div>";
+    Some li2
+  | {locType = Typed ("makeProps", _, _)}
+    :: ({locType = Typed ("make", _, _)} as li2)
+    :: _ ->
+    log 4
+      "heuristic for </Comp> within fragments: take make as makeProps does not \
+       work\n\
+       the type is not great but jump to definition works";
+    Some li2
+  | [
+   ({locType = Typed (_, _, LocalReference _)} as li1);
+   ({locType = Typed (_, _, _)} as li2);
+  ]
+    when li1.loc = li2.loc ->
+    log 5
+      "heuristic for JSX and compiler combined:\n\
+       ~x becomes Props#x\n\
+       heuristic for: [Props, x], give loc of `x`";
+    if debug then Printf.printf "n1:%s n2:%s\n" (nameOf li1) (nameOf li2);
+    Some li2
+  | [
+   ({locType = Typed (_, _, LocalReference _)} as li1);
+   ({locType = Typed (_, _, GlobalReference ("Js_OO", ["unsafe_downgrade"], _))}
+    as li2);
+   li3;
+  ]
+  (* For older compiler 9.0 or earlier *)
+    when li1.loc = li2.loc && li2.loc = li3.loc ->
+    (* Not currently testable on 9.1.4 *)
+    log 6
+      "heuristic for JSX and compiler combined:\n\
+       ~x becomes Js_OO.unsafe_downgrade(Props)#x\n\
+       heuristic for: [Props, unsafe_downgrade, x], give loc of `x`";
+    Some li3
+  | [
+   ({locType = Typed (_, _, LocalReference (_, Value))} as li1);
+   ({locType = Typed (_, _, Definition (_, Value))} as li2);
+  ] ->
+    log 7
+      "heuristic for JSX on type-annotated labeled (~arg:t):\n\
+       (~arg:t) becomes Props#arg\n\
+       Props has the location range of arg:t\n\
+       arg has the location range of arg\n\
+       heuristic for: [Props, arg], give loc of `arg`";
+    if debug then Printf.printf "n1:%s n2:%s\n" (nameOf li1) (nameOf li2);
+    Some li2
+  | [li1; li2; li3] when li1.loc = li2.loc && li2.loc = li3.loc ->
+    (* Not currently testable on 9.1.4 *)
+    log 8
+      "heuristic for JSX with at most one child\n\
+       heuristic for: [makeProps, make, createElement], give the loc of `make` ";
+    Some li2
+  | [li1; li2; li3; li4]
+    when li1.loc = li2.loc && li2.loc = li3.loc && li3.loc = li4.loc ->
+    log 9
+      "heuristic for JSX variadic, e.g. <C> {x} {y} </C>\n\
+       heuristic for: [React.null, makeProps, make, createElementVariadic], \
+       give the loc of `make`";
+    if debug then
+      Printf.printf "n1:%s n2:%s n3:%s n4:%s\n" (nameOf li1) (nameOf li2)
+        (nameOf li3) (nameOf li4);
+    Some li3
+  | {locType = Typed (_, {desc = Tconstr (path, _, _)}, _)} :: li :: _
+    when Utils.isUncurriedInternal path ->
+    Some li
+  | li :: _ -> Some li
+  | _ -> None
+
+let declaredForTip ~(stamps : Stamps.t) stamp (tip : Tip.t) =
+  match tip with
+  | Value ->
+    Stamps.findValue stamps stamp
+    |> Option.map (fun x -> {x with Declared.item = ()})
+  | Field _ | Constructor _ | Type ->
+    Stamps.findType stamps stamp
+    |> Option.map (fun x -> {x with Declared.item = ()})
+  | Module ->
+    Stamps.findModule stamps stamp
+    |> Option.map (fun x -> {x with Declared.item = ()})
+
+let getField (file : File.t) stamp name =
+  match Stamps.findType file.stamps stamp with
+  | None -> None
+  | Some {item = {kind}} -> (
+    match kind with
+    | Record fields -> fields |> List.find_opt (fun f -> f.fname.txt = name)
+    | _ -> None)
+
+let getConstructor (file : File.t) stamp name =
+  match Stamps.findType file.stamps stamp with
+  | None -> None
+  | Some {item = {kind}} -> (
+    match kind with
+    | Variant constructors -> (
+      match
+        constructors
+        |> List.find_opt (fun const -> const.Constructor.cname.txt = name)
+      with
+      | None -> None
+      | Some const -> Some const)
+    | _ -> None)
+
+let exportedForTip ~env ~path ~package ~(tip : Tip.t) =
+  match ResolvePath.resolvePath ~env ~path ~package with
+  | None ->
+    Log.log ("Cannot resolve path " ^ pathToString path);
+    None
+  | Some (env, name) -> (
+    let kind =
+      match tip with
+      | Value -> Exported.Value
+      | Field _ | Constructor _ | Type -> Exported.Type
+      | Module -> Exported.Module
+    in
+    match Exported.find env.exported kind name with
+    | None ->
+      Log.log ("Exported not found for tip " ^ name ^ " > " ^ Tip.toString tip);
+      None
+    | Some stamp -> Some (env, name, stamp))
+
+let definedForLoc ~file ~package locKind =
+  let inner ~file stamp (tip : Tip.t) =
+    match tip with
+    | Constructor name -> (
+      match getConstructor file stamp name with
+      | None -> None
+      | Some constructor ->
+        Some (constructor.docstring, `Constructor constructor))
+    | Field name ->
+      Some
+        ( (match getField file stamp name with
+          | None -> []
+          | Some field -> field.docstring),
+          `Field )
+    | _ -> (
+      maybeLog
+        ("Trying for declared " ^ Tip.toString tip ^ " " ^ string_of_int stamp
+       ^ " in file " ^ Uri.toString file.uri);
+      match declaredForTip ~stamps:file.stamps stamp tip with
+      | None -> None
+      | Some declared -> Some (declared.docstring, `Declared))
+  in
+  match locKind with
+  | NotFound -> None
+  | LocalReference (stamp, tip) | Definition (stamp, tip) ->
+    inner ~file stamp tip
+  | GlobalReference (moduleName, path, tip) -> (
+    maybeLog ("Getting global " ^ moduleName);
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None ->
+      Log.log ("Cannot get module " ^ moduleName);
+      None
+    | Some file -> (
+      let env = QueryEnv.fromFile file in
+      match exportedForTip ~env ~path ~package ~tip with
+      | None -> None
+      | Some (env, name, stamp) -> (
+        maybeLog ("Getting for " ^ string_of_int stamp ^ " in " ^ name);
+        match inner ~file:env.file stamp tip with
+        | None ->
+          Log.log "could not get defined";
+          None
+        | Some res ->
+          maybeLog "Yes!! got it";
+          Some res)))
+
+(** Find alternative declaration: from res in case of interface, or from resi in case of implementation  *)
+let alternateDeclared ~(file : File.t) ~package (declared : _ Declared.t) tip =
+  match Hashtbl.find_opt package.pathsForModule file.moduleName with
+  | None -> None
+  | Some paths -> (
+    match paths with
+    | IntfAndImpl {resi; res} -> (
+      maybeLog
+        ("alternateDeclared for " ^ file.moduleName ^ " has both resi and res");
+      let alternateUri = if Uri.isInterface file.uri then res else resi in
+      match Cmt.fullFromUri ~uri:(Uri.fromPath alternateUri) with
+      | None -> None
+      | Some {file; extra} -> (
+        let env = QueryEnv.fromFile file in
+        let path = ModulePath.toPath declared.modulePath declared.name.txt in
+        maybeLog ("find declared for path " ^ pathToString path);
+        let declaredOpt =
+          match exportedForTip ~env ~path ~package ~tip with
+          | None -> None
+          | Some (_env, _name, stamp) ->
+            declaredForTip ~stamps:file.stamps stamp tip
+        in
+        match declaredOpt with
+        | None -> None
+        | Some declared -> Some (file, extra, declared)))
+    | _ ->
+      maybeLog ("alternateDeclared for " ^ file.moduleName ^ " not found");
+
+      None)
+
+let rec resolveModuleReference ?(pathsSeen = []) ~file ~package
+    (declared : Module.t Declared.t) =
+  match declared.item with
+  | Structure _ -> Some (file, Some declared)
+  | Constraint (_moduleItem, moduleTypeItem) ->
+    resolveModuleReference ~pathsSeen ~file ~package
+      {declared with item = moduleTypeItem}
+  | Ident path -> (
+    let env = QueryEnv.fromFile file in
+    match ResolvePath.fromCompilerPath ~env path with
+    | NotFound -> None
+    | Exported (env, name) -> (
+      match Exported.find env.exported Exported.Module name with
+      | None -> None
+      | Some stamp -> (
+        match Stamps.findModule env.file.stamps stamp with
+        | None -> None
+        | Some md -> Some (env.file, Some md)))
+    | Global (moduleName, path) -> (
+      match ProcessCmt.fileForModule ~package moduleName with
+      | None -> None
+      | Some file -> (
+        let env = QueryEnv.fromFile file in
+        match ResolvePath.resolvePath ~env ~package ~path with
+        | None -> None
+        | Some (env, name) -> (
+          match Exported.find env.exported Exported.Module name with
+          | None -> None
+          | Some stamp -> (
+            match Stamps.findModule env.file.stamps stamp with
+            | None -> None
+            | Some md -> Some (env.file, Some md)))))
+    | Stamp stamp -> (
+      match Stamps.findModule file.stamps stamp with
+      | None -> None
+      | Some ({item = Ident path} as md) when not (List.mem path pathsSeen) ->
+        (* avoid possible infinite loops *)
+        resolveModuleReference ~file ~package ~pathsSeen:(path :: pathsSeen) md
+      | Some md -> Some (file, Some md))
+    | GlobalMod name -> (
+      match ProcessCmt.fileForModule ~package name with
+      | None -> None
+      | Some file -> Some (file, None)))
+
+let validateLoc (loc : Location.t) (backup : Location.t) =
+  if loc.loc_start.pos_cnum = -1 then
+    if backup.loc_start.pos_cnum = -1 then
+      {
+        Location.loc_ghost = true;
+        loc_start = {pos_cnum = 0; pos_lnum = 1; pos_bol = 0; pos_fname = ""};
+        loc_end = {pos_cnum = 0; pos_lnum = 1; pos_bol = 0; pos_fname = ""};
+      }
+    else backup
+  else loc
+
+let resolveModuleDefinition ~(file : File.t) ~package stamp =
+  match Stamps.findModule file.stamps stamp with
+  | None -> None
+  | Some md -> (
+    match resolveModuleReference ~file ~package md with
+    | None -> None
+    | Some (file, declared) ->
+      let loc =
+        match declared with
+        | None -> Uri.toTopLevelLoc file.uri
+        | Some declared -> validateLoc declared.name.loc declared.extentLoc
+      in
+      Some (file.uri, loc))
+
+let definition ~file ~package stamp (tip : Tip.t) =
+  match tip with
+  | Constructor name -> (
+    match getConstructor file stamp name with
+    | None -> None
+    | Some constructor -> Some (file.uri, constructor.cname.loc))
+  | Field name -> (
+    match getField file stamp name with
+    | None -> None
+    | Some field -> Some (file.uri, field.fname.loc))
+  | Module -> resolveModuleDefinition ~file ~package stamp
+  | _ -> (
+    match declaredForTip ~stamps:file.stamps stamp tip with
+    | None -> None
+    | Some declared ->
+      let fileImpl, declaredImpl =
+        match alternateDeclared ~package ~file declared tip with
+        | Some (fileImpl, _extra, declaredImpl) when Uri.isInterface file.uri ->
+          (fileImpl, declaredImpl)
+        | _ -> (file, declared)
+      in
+      let loc = validateLoc declaredImpl.name.loc declaredImpl.extentLoc in
+      let env = QueryEnv.fromFile fileImpl in
+      let uri =
+        ResolvePath.getSourceUri ~env ~package declaredImpl.modulePath
+      in
+      maybeLog ("Inner uri " ^ Uri.toString uri);
+      Some (uri, loc))
+
+let definitionForLocItem ~full:{file; package} locItem =
+  match locItem.locType with
+  | Typed (_, _, Definition (stamp, tip)) -> (
+    maybeLog
+      ("Typed Definition stamp:" ^ string_of_int stamp ^ " tip:"
+     ^ Tip.toString tip);
+    match declaredForTip ~stamps:file.stamps stamp tip with
+    | None -> None
+    | Some declared ->
+      maybeLog ("Declared " ^ declared.name.txt);
+      if declared.isExported then (
+        maybeLog ("exported, looking for alternate " ^ file.moduleName);
+        match alternateDeclared ~package ~file declared tip with
+        | None -> None
+        | Some (file, _extra, declared) ->
+          let loc = validateLoc declared.name.loc declared.extentLoc in
+          Some (file.uri, loc))
+      else None)
+  | Typed (_, _, NotFound)
+  | LModule (NotFound | Definition (_, _))
+  | TypeDefinition (_, _, _)
+  | Constant _ ->
+    None
+  | TopLevelModule name -> (
+    maybeLog ("Toplevel " ^ name);
+    match Hashtbl.find_opt package.pathsForModule name with
+    | None -> None
+    | Some paths ->
+      let uri = getUri paths in
+      Some (uri, Uri.toTopLevelLoc uri))
+  | LModule (LocalReference (stamp, tip))
+  | Typed (_, _, LocalReference (stamp, tip)) ->
+    maybeLog ("Local defn " ^ Tip.toString tip);
+    definition ~file ~package stamp tip
+  | LModule (GlobalReference (moduleName, path, tip))
+  | Typed (_, _, GlobalReference (moduleName, path, tip)) -> (
+    maybeLog
+      ("Typed GlobalReference moduleName:" ^ moduleName ^ " path:"
+     ^ pathToString path ^ " tip:" ^ Tip.toString tip);
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None -> None
+    | Some file -> (
+      let env = QueryEnv.fromFile file in
+      match exportedForTip ~env ~path ~package ~tip with
+      | None -> None
+      | Some (env, _name, stamp) ->
+        (* oooh wht do I do if the stamp is inside a pseudo-file? *)
+        maybeLog ("Got stamp " ^ string_of_int stamp);
+        definition ~file:env.file ~package stamp tip))
+
+let digConstructor ~env ~package path =
+  match ResolvePath.resolveFromCompilerPath ~env ~package path with
+  | NotFound -> None
+  | Stamp stamp -> (
+    match Stamps.findType env.file.stamps stamp with
+    | None -> None
+    | Some t -> Some (env, t))
+  | Exported (env, name) -> (
+    match Exported.find env.exported Exported.Type name with
+    | None -> None
+    | Some stamp -> (
+      match Stamps.findType env.file.stamps stamp with
+      | None -> None
+      | Some t -> Some (env, t)))
+  | _ -> None
+
+let typeDefinitionForLocItem ~full:{file; package} locItem =
+  match locItem.locType with
+  | Constant _ | TopLevelModule _ | LModule _ -> None
+  | TypeDefinition _ -> Some (file.uri, locItem.loc)
+  | Typed (_, typ, _) -> (
+    let env = QueryEnv.fromFile file in
+    match Shared.digConstructor typ with
+    | None -> None
+    | Some path -> (
+      match digConstructor ~env ~package path with
+      | Some (env, declared) -> Some (env.file.uri, declared.item.decl.type_loc)
+      | None -> None))
+
+let isVisible (declared : _ Declared.t) =
+  declared.isExported
+  &&
+  let rec loop (v : ModulePath.t) =
+    match v with
+    | File _ -> true
+    | NotVisible -> false
+    | IncludedModule (_, inner) -> loop inner
+    | ExportedModule {modulePath = inner} -> loop inner
+  in
+  loop declared.modulePath
+
+type references = {
+  uri: Uri.t;
+  locOpt: Location.t option; (* None: reference to a toplevel module *)
+}
+
+let forLocalStamp ~full:{file; extra; package} stamp (tip : Tip.t) =
+  let env = QueryEnv.fromFile file in
+  match
+    match tip with
+    | Constructor name ->
+      getConstructor file stamp name
+      |> Option.map (fun x -> x.Constructor.stamp)
+    | Field name -> getField file stamp name |> Option.map (fun x -> x.stamp)
+    | _ -> Some stamp
+  with
+  | None -> []
+  | Some localStamp -> (
+    match Hashtbl.find_opt extra.internalReferences localStamp with
+    | None -> []
+    | Some locs ->
+      maybeLog ("Checking externals: " ^ string_of_int stamp);
+      let externals =
+        match declaredForTip ~stamps:env.file.stamps stamp tip with
+        | None -> []
+        | Some declared ->
+          if isVisible declared then (
+            let alternativeReferences =
+              match alternateDeclared ~package ~file declared tip with
+              | None -> []
+              | Some (file, extra, {stamp}) -> (
+                match
+                  match tip with
+                  | Constructor name ->
+                    getConstructor file stamp name
+                    |> Option.map (fun x -> x.Constructor.stamp)
+                  | Field name ->
+                    getField file stamp name |> Option.map (fun x -> x.stamp)
+                  | _ -> Some stamp
+                with
+                | None -> []
+                | Some localStamp -> (
+                  match
+                    Hashtbl.find_opt extra.internalReferences localStamp
+                  with
+                  | None -> []
+                  | Some locs ->
+                    locs
+                    |> List.map (fun loc -> {uri = file.uri; locOpt = Some loc})
+                  ))
+              (* if this file has a corresponding interface or implementation file
+                 also find the references in that file *)
+            in
+            let path =
+              ModulePath.toPath declared.modulePath declared.name.txt
+            in
+            maybeLog ("Now checking path " ^ pathToString path);
+            let thisModuleName = file.moduleName in
+            let externals =
+              package.projectFiles |> FileSet.elements
+              |> List.filter (fun name -> name <> file.moduleName)
+              |> List.map (fun moduleName ->
+                     Cmt.fullsFromModule ~package ~moduleName
+                     |> List.map (fun {file; extra} ->
+                            match
+                              Hashtbl.find_opt extra.externalReferences
+                                thisModuleName
+                            with
+                            | None -> []
+                            | Some refs ->
+                              let locs =
+                                refs
+                                |> Utils.filterMap (fun (p, t, locs) ->
+                                       if p = path && t = tip then Some locs
+                                       else None)
+                              in
+                              locs
+                              |> List.map (fun loc ->
+                                     {uri = file.uri; locOpt = Some loc})))
+              |> List.concat |> List.concat
+            in
+            alternativeReferences @ externals)
+          else (
+            maybeLog "Not visible";
+            [])
+      in
+      List.append
+        (locs |> List.map (fun loc -> {uri = file.uri; locOpt = Some loc}))
+        externals)
+
+let allReferencesForLocItem ~full:({file; package} as full) locItem =
+  match locItem.locType with
+  | TopLevelModule moduleName ->
+    let otherModulesReferences =
+      package.projectFiles |> FileSet.elements
+      |> Utils.filterMap (fun name ->
+             match ProcessCmt.fileForModule ~package name with
+             | None -> None
+             | Some file -> Cmt.fullFromUri ~uri:file.uri)
+      |> List.map (fun full ->
+             match Hashtbl.find_opt full.extra.fileReferences moduleName with
+             | None -> []
+             | Some locs ->
+               locs |> LocationSet.elements
+               |> List.map (fun loc ->
+                      {
+                        uri = Uri.fromPath loc.Location.loc_start.pos_fname;
+                        locOpt = Some loc;
+                      }))
+      |> List.flatten
+    in
+    let targetModuleReferences =
+      match Hashtbl.find_opt package.pathsForModule moduleName with
+      | None -> []
+      | Some paths ->
+        let moduleSrcToRef src = {uri = Uri.fromPath src; locOpt = None} in
+        getSrc paths |> List.map moduleSrcToRef
+    in
+    List.append targetModuleReferences otherModulesReferences
+  | Typed (_, _, NotFound) | LModule NotFound | Constant _ -> []
+  | TypeDefinition (_, _, stamp) -> forLocalStamp ~full stamp Type
+  | Typed (_, _, (LocalReference (stamp, tip) | Definition (stamp, tip)))
+  | LModule (LocalReference (stamp, tip) | Definition (stamp, tip)) ->
+    maybeLog
+      ("Finding references for " ^ Uri.toString file.uri ^ " and stamp "
+     ^ string_of_int stamp ^ " and tip " ^ Tip.toString tip);
+    forLocalStamp ~full stamp tip
+  | LModule (GlobalReference (moduleName, path, tip))
+  | Typed (_, _, GlobalReference (moduleName, path, tip)) -> (
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None -> []
+    | Some file -> (
+      let env = QueryEnv.fromFile file in
+      match exportedForTip ~env ~path ~package ~tip with
+      | None -> []
+      | Some (env, _name, stamp) -> (
+        match Cmt.fullFromUri ~uri:env.file.uri with
+        | None -> []
+        | Some full ->
+          maybeLog
+            ("Finding references for (global) " ^ Uri.toString env.file.uri
+           ^ " and stamp " ^ string_of_int stamp ^ " and tip "
+           ^ Tip.toString tip);
+          forLocalStamp ~full stamp tip)))
diff --git a/analysis/src/ResolvePath.ml b/analysis/src/ResolvePath.ml
new file mode 100644
index 0000000000..877e273fe8
--- /dev/null
+++ b/analysis/src/ResolvePath.ml
@@ -0,0 +1,148 @@
+open SharedTypes
+
+type resolution =
+  | Exported of QueryEnv.t * filePath
+  | Global of filePath * filePath list
+  | GlobalMod of filePath
+  | NotFound
+  | Stamp of int
+
+let rec joinPaths modulePath path =
+  match modulePath with
+  | Path.Pident ident -> (ident.stamp, ident.name, path)
+  | Papply (fnPath, _argPath) -> joinPaths fnPath path
+  | Pdot (inner, name, _) -> joinPaths inner (name :: path)
+
+let rec makePath ~(env : QueryEnv.t) modulePath =
+  match modulePath with
+  | Path.Pident ident when ident.stamp == 0 -> GlobalMod ident.name
+  | Pident ident -> Stamp ident.stamp
+  | Papply (fnPath, _argPath) -> makePath ~env fnPath
+  | Pdot (inner, name, _) -> (
+    match joinPaths inner [name] with
+    | 0, moduleName, path -> Global (moduleName, path)
+    | stamp, _moduleName, path -> (
+      let res =
+        match Stamps.findModule env.file.stamps stamp with
+        | None -> None
+        | Some {item = kind} -> findInModule ~env kind path
+      in
+      match res with
+      | None -> NotFound
+      | Some (`Local (env, name)) -> Exported (env, name)
+      | Some (`Global (moduleName, fullPath)) -> Global (moduleName, fullPath)))
+
+and resolvePathInner ~(env : QueryEnv.t) ~path =
+  match path with
+  | [] -> None
+  | [name] -> Some (`Local (env, name))
+  | subName :: subPath -> (
+    match Exported.find env.exported Exported.Module subName with
+    | None -> None
+    | Some stamp -> (
+      match Stamps.findModule env.file.stamps stamp with
+      | None -> None
+      | Some {item} -> findInModule ~env item subPath))
+
+and findInModule ~(env : QueryEnv.t) module_ path =
+  match module_ with
+  | Structure structure ->
+    resolvePathInner ~env:(QueryEnv.enterStructure env structure) ~path
+  | Constraint (_, module1) -> findInModule ~env module1 path
+  | Ident modulePath -> (
+    let stamp, moduleName, fullPath = joinPaths modulePath path in
+    if stamp = 0 then Some (`Global (moduleName, fullPath))
+    else
+      match Stamps.findModule env.file.stamps stamp with
+      | None -> None
+      | Some {item} -> findInModule ~env item fullPath)
+
+let rec resolvePath ~env ~path ~package =
+  Log.log ("resolvePath path:" ^ pathToString path);
+  match resolvePathInner ~env ~path with
+  | None -> None
+  | Some result -> (
+    match result with
+    | `Local (env, name) -> Some (env, name)
+    | `Global (moduleName, fullPath) -> (
+      Log.log
+        ("resolvePath Global path:" ^ pathToString fullPath ^ " module:"
+       ^ moduleName);
+      match ProcessCmt.fileForModule ~package moduleName with
+      | None -> None
+      | Some file ->
+        resolvePath ~env:(QueryEnv.fromFile file) ~path:fullPath ~package))
+
+let fromCompilerPath ~(env : QueryEnv.t) path : resolution =
+  match makePath ~env path with
+  | Stamp stamp -> Stamp stamp
+  | GlobalMod name -> GlobalMod name
+  | NotFound -> NotFound
+  | Exported (env, name) -> Exported (env, name)
+  | Global (moduleName, fullPath) -> Global (moduleName, fullPath)
+
+let resolveModuleFromCompilerPath ~env ~package path =
+  match fromCompilerPath ~env path with
+  | Global (moduleName, path) -> (
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None -> None
+    | Some file -> (
+      let env = QueryEnv.fromFile file in
+      match resolvePath ~env ~package ~path with
+      | None -> None
+      | Some (env, name) -> (
+        match Exported.find env.exported Exported.Module name with
+        | None -> None
+        | Some stamp -> (
+          match Stamps.findModule env.file.stamps stamp with
+          | None -> None
+          | Some declared -> Some (env, Some declared)))))
+  | Stamp stamp -> (
+    match Stamps.findModule env.file.stamps stamp with
+    | None -> None
+    | Some declared -> Some (env, Some declared))
+  | GlobalMod moduleName -> (
+    match ProcessCmt.fileForModule ~package moduleName with
+    | None -> None
+    | Some file ->
+      let env = QueryEnv.fromFile file in
+      Some (env, None))
+  | NotFound -> None
+  | Exported (env, name) -> (
+    match Exported.find env.exported Exported.Module name with
+    | None -> None
+    | Some stamp -> (
+      match Stamps.findModule env.file.stamps stamp with
+      | None -> None
+      | Some declared -> Some (env, Some declared)))
+
+let resolveFromCompilerPath ~env ~package path =
+  match fromCompilerPath ~env path with
+  | Global (moduleName, path) -> (
+    let res =
+      match ProcessCmt.fileForModule ~package moduleName with
+      | None -> None
+      | Some file ->
+        let env = QueryEnv.fromFile file in
+        resolvePath ~env ~package ~path
+    in
+    match res with
+    | None -> NotFound
+    | Some (env, name) -> Exported (env, name))
+  | Stamp stamp -> Stamp stamp
+  | GlobalMod _ -> NotFound
+  | NotFound -> NotFound
+  | Exported (env, name) -> Exported (env, name)
+
+let rec getSourceUri ~(env : QueryEnv.t) ~package (path : ModulePath.t) =
+  match path with
+  | File (uri, _moduleName) -> uri
+  | NotVisible -> env.file.uri
+  | IncludedModule (path, inner) -> (
+    Log.log "INCLUDED MODULE";
+    match resolveModuleFromCompilerPath ~env ~package path with
+    | None ->
+      Log.log "NOT FOUND";
+      getSourceUri ~env ~package inner
+    | Some (env, _declared) -> env.file.uri)
+  | ExportedModule {modulePath = inner} -> getSourceUri ~env ~package inner
diff --git a/analysis/src/Scope.ml b/analysis/src/Scope.ml
new file mode 100644
index 0000000000..0e092d2a43
--- /dev/null
+++ b/analysis/src/Scope.ml
@@ -0,0 +1,136 @@
+type item = SharedTypes.ScopeTypes.item
+
+type t = item list
+
+open SharedTypes.ScopeTypes
+
+let itemToString item =
+  let str s = if s = "" then "\"\"" else s in
+  let list l = "[" ^ (l |> List.map str |> String.concat ", ") ^ "]" in
+  match item with
+  | Constructor (s, loc) -> "Constructor " ^ s ^ " " ^ Loc.toString loc
+  | Field (s, loc) -> "Field " ^ s ^ " " ^ Loc.toString loc
+  | Open sl -> "Open " ^ list sl
+  | Module (s, loc) -> "Module " ^ s ^ " " ^ Loc.toString loc
+  | Value (s, loc, _, _) -> "Value " ^ s ^ " " ^ Loc.toString loc
+  | Type (s, loc) -> "Type " ^ s ^ " " ^ Loc.toString loc
+[@@live]
+
+let create () : t = []
+let addConstructor ~name ~loc x = Constructor (name, loc) :: x
+let addField ~name ~loc x = Field (name, loc) :: x
+let addModule ~name ~loc x = Module (name, loc) :: x
+let addOpen ~lid x = Open (Utils.flattenLongIdent lid @ ["place holder"]) :: x
+let addValue ~name ~loc ?contextPath x =
+  let showDebug = !Cfg.debugFollowCtxPath in
+  (if showDebug then
+     match contextPath with
+     | None -> Printf.printf "adding value '%s', no ctxPath\n" name
+     | Some contextPath ->
+       if showDebug then
+         Printf.printf "adding value '%s' with ctxPath: %s\n" name
+           (SharedTypes.Completable.contextPathToString contextPath));
+  Value (name, loc, contextPath, x) :: x
+let addType ~name ~loc x = Type (name, loc) :: x
+
+let iterValuesBeforeFirstOpen f x =
+  let rec loop items =
+    match items with
+    | Value (s, loc, contextPath, scope) :: rest ->
+      f s loc contextPath scope;
+      loop rest
+    | Open _ :: _ -> ()
+    | _ :: rest -> loop rest
+    | [] -> ()
+  in
+  loop x
+
+let iterValuesAfterFirstOpen f x =
+  let rec loop foundOpen items =
+    match items with
+    | Value (s, loc, contextPath, scope) :: rest ->
+      if foundOpen then f s loc contextPath scope;
+      loop foundOpen rest
+    | Open _ :: rest -> loop true rest
+    | _ :: rest -> loop foundOpen rest
+    | [] -> ()
+  in
+  loop false x
+
+let iterConstructorsBeforeFirstOpen f x =
+  let rec loop items =
+    match items with
+    | Constructor (s, loc) :: rest ->
+      f s loc;
+      loop rest
+    | Open _ :: _ -> ()
+    | _ :: rest -> loop rest
+    | [] -> ()
+  in
+  loop x
+
+let iterConstructorsAfterFirstOpen f x =
+  let rec loop foundOpen items =
+    match items with
+    | Constructor (s, loc) :: rest ->
+      if foundOpen then f s loc;
+      loop foundOpen rest
+    | Open _ :: rest -> loop true rest
+    | _ :: rest -> loop foundOpen rest
+    | [] -> ()
+  in
+  loop false x
+
+let iterTypesBeforeFirstOpen f x =
+  let rec loop items =
+    match items with
+    | Type (s, loc) :: rest ->
+      f s loc;
+      loop rest
+    | Open _ :: _ -> ()
+    | _ :: rest -> loop rest
+    | [] -> ()
+  in
+  loop x
+
+let iterTypesAfterFirstOpen f x =
+  let rec loop foundOpen items =
+    match items with
+    | Type (s, loc) :: rest ->
+      if foundOpen then f s loc;
+      loop foundOpen rest
+    | Open _ :: rest -> loop true rest
+    | _ :: rest -> loop foundOpen rest
+    | [] -> ()
+  in
+  loop false x
+
+let iterModulesBeforeFirstOpen f x =
+  let rec loop items =
+    match items with
+    | Module (s, loc) :: rest ->
+      f s loc;
+      loop rest
+    | Open _ :: _ -> ()
+    | _ :: rest -> loop rest
+    | [] -> ()
+  in
+  loop x
+
+let iterModulesAfterFirstOpen f x =
+  let rec loop foundOpen items =
+    match items with
+    | Module (s, loc) :: rest ->
+      if foundOpen then f s loc;
+      loop foundOpen rest
+    | Open _ :: rest -> loop true rest
+    | _ :: rest -> loop foundOpen rest
+    | [] -> ()
+  in
+  loop false x
+
+let getRawOpens x =
+  x
+  |> Utils.filterMap (function
+       | Open path -> Some path
+       | _ -> None)
diff --git a/analysis/src/SemanticTokens.ml b/analysis/src/SemanticTokens.ml
new file mode 100644
index 0000000000..58564aa1fc
--- /dev/null
+++ b/analysis/src/SemanticTokens.ml
@@ -0,0 +1,465 @@
+(*
+   Generally speaking, semantic highlighting here takes care of categorizing identifiers,
+   since the kind of an identifier is highly context-specific and hard to catch with a grammar.
+
+   The big exception is labels, whose location is not represented in the AST
+   E.g. function definition such as (~foo as _) =>, application (~foo=3) and prop <div foo=3>.
+   Labels are handled in the grammar, not here.
+   Punned labels such as (~foo) => are both labels and identifiers. They are overridden here.
+
+   There are 2 cases where the grammar and semantic highlighting work jointly.
+   The styles emitted in the grammar and here need to be kept in sync.
+   1) For jsx angled brackets, the grammar handles basic cases such as />
+      whose location is not in the AST.
+      Instead < and > are handled here. Those would be difficult to disambiguate in a grammar.
+   2) Most operators are handled in the grammar. Except < and > are handled here.
+      The reason is again that < and > would be difficult do disambiguate in a grammar.
+*)
+
+module Token = struct
+  (* This needs to stay synced with the same legend in `server.ts` *)
+  (* See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens *)
+  type tokenType =
+    | Operator  (** < and > *)
+    | Variable  (** let x = *)
+    | Type  (** type t = *)
+    | JsxTag  (** the < and > in <div> *)
+    | Namespace  (** module M = *)
+    | EnumMember  (** variant A or poly variant #A *)
+    | Property  (** {x:...} *)
+    | JsxLowercase  (** div in <div> *)
+
+  let tokenTypeToString = function
+    | Operator -> "0"
+    | Variable -> "1"
+    | Type -> "2"
+    | JsxTag -> "3"
+    | Namespace -> "4"
+    | EnumMember -> "5"
+    | Property -> "6"
+    | JsxLowercase -> "7"
+
+  let tokenTypeDebug = function
+    | Operator -> "Operator"
+    | Variable -> "Variable"
+    | Type -> "Type"
+    | JsxTag -> "JsxTag"
+    | Namespace -> "Namespace"
+    | EnumMember -> "EnumMember"
+    | Property -> "Property"
+    | JsxLowercase -> "JsxLowercase"
+
+  let tokenModifiersString = "0" (* None at the moment *)
+
+  type token = int * int * int * tokenType
+
+  type emitter = {
+    mutable tokens: token list;
+    mutable lastLine: int;
+    mutable lastChar: int;
+  }
+
+  let createEmitter () = {tokens = []; lastLine = 0; lastChar = 0}
+
+  let add ~line ~char ~length ~type_ e =
+    e.tokens <- (line, char, length, type_) :: e.tokens
+
+  let emitToken buf (line, char, length, type_) e =
+    let deltaLine = line - e.lastLine in
+    let deltaChar = if deltaLine = 0 then char - e.lastChar else char in
+    e.lastLine <- line;
+    e.lastChar <- char;
+    if Buffer.length buf > 0 then Buffer.add_char buf ',';
+    if
+      deltaLine >= 0 && deltaChar >= 0 && length >= 0
+      (* Defensive programming *)
+    then
+      Buffer.add_string buf
+        (string_of_int deltaLine ^ "," ^ string_of_int deltaChar ^ ","
+       ^ string_of_int length ^ "," ^ tokenTypeToString type_ ^ ","
+       ^ tokenModifiersString)
+
+  let emit e =
+    let sortedTokens =
+      e.tokens
+      |> List.sort (fun (l1, c1, _, _) (l2, c2, _, _) ->
+             if l1 = l2 then compare c1 c2 else compare l1 l2)
+    in
+    let buf = Buffer.create 1 in
+    sortedTokens |> List.iter (fun t -> e |> emitToken buf t);
+    Buffer.contents buf
+end
+
+let isLowercaseId id =
+  id <> ""
+  &&
+  let c = id.[0] in
+  c == '_' || (c >= 'a' && c <= 'z')
+
+let isUppercaseId id =
+  id <> ""
+  &&
+  let c = id.[0] in
+  c >= 'A' && c <= 'Z'
+
+let emitFromRange (posStart, posEnd) ~type_ emitter =
+  let length =
+    if fst posStart = fst posEnd then snd posEnd - snd posStart else 0
+  in
+  if length > 0 then
+    emitter
+    |> Token.add ~line:(fst posStart) ~char:(snd posStart) ~length ~type_
+
+let emitFromLoc ~loc ~type_ emitter =
+  emitter |> emitFromRange (Loc.range loc) ~type_
+
+let emitLongident ?(backwards = false) ?(jsx = false)
+    ?(lowerCaseToken = if jsx then Token.JsxLowercase else Token.Variable)
+    ?(upperCaseToken = Token.Namespace) ?(lastToken = None) ?(posEnd = None)
+    ~pos ~lid ~debug emitter =
+  let rec flatten acc lid =
+    match lid with
+    | Longident.Lident txt -> txt :: acc
+    | Ldot (lid, txt) ->
+      let acc = if jsx && txt = "createElement" then acc else txt :: acc in
+      flatten acc lid
+    | _ -> acc
+  in
+  let rec loop pos segments =
+    match segments with
+    | [id] when isUppercaseId id || isLowercaseId id ->
+      let type_ =
+        match lastToken with
+        | Some type_ -> type_
+        | None -> if isUppercaseId id then upperCaseToken else lowerCaseToken
+      in
+      let posAfter = (fst pos, snd pos + String.length id) in
+      let posEnd, lenMismatch =
+        (* There could be a length mismatch when ids are quoted
+           e.g. variable /"true" or object field {"x":...} *)
+        match posEnd with
+        | Some posEnd -> (posEnd, posEnd <> posAfter)
+        | None -> (posAfter, false)
+      in
+      if debug then
+        Printf.printf "Lident: %s %s%s %s\n" id (Pos.toString pos)
+          (if lenMismatch then "->" ^ Pos.toString posEnd else "")
+          (Token.tokenTypeDebug type_);
+      emitter |> emitFromRange (pos, posEnd) ~type_
+    | id :: segments when isUppercaseId id || isLowercaseId id ->
+      let type_ = if isUppercaseId id then upperCaseToken else lowerCaseToken in
+      if debug then
+        Printf.printf "Ldot: %s %s %s\n" id (Pos.toString pos)
+          (Token.tokenTypeDebug type_);
+      let length = String.length id in
+      emitter |> emitFromRange (pos, (fst pos, snd pos + length)) ~type_;
+      loop (fst pos, snd pos + length + 1) segments
+    | _ -> ()
+  in
+  let segments = flatten [] lid in
+  if backwards then (
+    let totalLength = segments |> String.concat "." |> String.length in
+    if snd pos >= totalLength then
+      loop (fst pos, snd pos - totalLength) segments)
+  else loop pos segments
+
+let emitVariable ~id ~debug ~loc emitter =
+  if debug then Printf.printf "Variable: %s %s\n" id (Loc.toString loc);
+  emitter |> emitFromLoc ~loc ~type_:Variable
+
+let emitJsxOpen ~lid ~debug ~(loc : Location.t) emitter =
+  if not loc.loc_ghost then
+    emitter |> emitLongident ~pos:(Loc.start loc) ~lid ~jsx:true ~debug
+
+let emitJsxClose ~lid ~debug ~pos emitter =
+  emitter |> emitLongident ~backwards:true ~pos ~lid ~jsx:true ~debug
+
+let emitJsxTag ~debug ~name ~pos emitter =
+  if debug then Printf.printf "JsxTag %s: %s\n" name (Pos.toString pos);
+  emitter |> emitFromRange (pos, (fst pos, snd pos + 1)) ~type_:Token.JsxTag
+
+let emitType ~lid ~debug ~(loc : Location.t) emitter =
+  if not loc.loc_ghost then
+    emitter
+    |> emitLongident ~lowerCaseToken:Token.Type ~pos:(Loc.start loc) ~lid ~debug
+
+let emitRecordLabel ~(label : Longident.t Location.loc) ~debug emitter =
+  if not label.loc.loc_ghost then
+    emitter
+    |> emitLongident ~lowerCaseToken:Token.Property ~pos:(Loc.start label.loc)
+         ~posEnd:(Some (Loc.end_ label.loc))
+         ~lid:label.txt ~debug
+
+let emitVariant ~(name : Longident.t Location.loc) ~debug emitter =
+  if not name.loc.loc_ghost then
+    emitter
+    |> emitLongident ~lastToken:(Some Token.EnumMember)
+         ~pos:(Loc.start name.loc) ~lid:name.txt ~debug
+
+let command ~debug ~emitter ~path =
+  let processTypeArg (coreType : Parsetree.core_type) =
+    if debug then Printf.printf "TypeArg: %s\n" (Loc.toString coreType.ptyp_loc)
+  in
+  let typ (iterator : Ast_iterator.iterator) (coreType : Parsetree.core_type) =
+    match coreType.ptyp_desc with
+    | Ptyp_constr ({txt = lid; loc}, args) ->
+      emitter |> emitType ~lid ~debug ~loc;
+      args |> List.iter processTypeArg;
+      Ast_iterator.default_iterator.typ iterator coreType
+    | _ -> Ast_iterator.default_iterator.typ iterator coreType
+  in
+  let type_declaration (iterator : Ast_iterator.iterator)
+      (tydecl : Parsetree.type_declaration) =
+    emitter
+    |> emitType ~lid:(Lident tydecl.ptype_name.txt) ~debug
+         ~loc:tydecl.ptype_name.loc;
+    Ast_iterator.default_iterator.type_declaration iterator tydecl
+  in
+  let pat (iterator : Ast_iterator.iterator) (p : Parsetree.pattern) =
+    match p.ppat_desc with
+    | Ppat_var {txt = id} ->
+      if isLowercaseId id then
+        emitter |> emitVariable ~id ~debug ~loc:p.ppat_loc;
+      Ast_iterator.default_iterator.pat iterator p
+    | Ppat_construct ({txt = Lident ("true" | "false")}, _) ->
+      (* Don't emit true or false *)
+      Ast_iterator.default_iterator.pat iterator p
+    | Ppat_record (cases, _) ->
+      cases
+      |> List.iter (fun (label, _) -> emitter |> emitRecordLabel ~label ~debug);
+      Ast_iterator.default_iterator.pat iterator p
+    | Ppat_construct (name, _) ->
+      emitter |> emitVariant ~name ~debug;
+      Ast_iterator.default_iterator.pat iterator p
+    | Ppat_type {txt = lid; loc} ->
+      emitter |> emitType ~lid ~debug ~loc;
+      Ast_iterator.default_iterator.pat iterator p
+    | _ -> Ast_iterator.default_iterator.pat iterator p
+  in
+  let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
+    match e.pexp_desc with
+    | Pexp_ident {txt = lid; loc} ->
+      if lid <> Lident "not" then
+        if not loc.loc_ghost then
+          emitter
+          |> emitLongident ~pos:(Loc.start loc)
+               ~posEnd:(Some (Loc.end_ loc))
+               ~lid ~debug;
+      Ast_iterator.default_iterator.expr iterator e
+    | Pexp_apply ({pexp_desc = Pexp_ident lident; pexp_loc}, args)
+      when Res_parsetree_viewer.is_jsx_expression e ->
+      (*
+         Angled brackets:
+          - These are handled in the grammar:  <>  </>  </  />
+          - Here we handle `<` and `>`
+
+         Component names:
+          - handled like other Longitent.t, except lowercase id is marked Token.JsxLowercase
+      *)
+      emitter (* --> <div... *)
+      |> emitJsxTag ~debug ~name:"<"
+           ~pos:
+             (let pos = Loc.start e.pexp_loc in
+              (fst pos, snd pos - 1 (* the AST skips the loc of < somehow *)));
+      emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:pexp_loc;
+
+      let posOfGreatherthanAfterProps =
+        let rec loop = function
+          | (Asttypes.Labelled "children", {Parsetree.pexp_loc}) :: _ ->
+            Loc.start pexp_loc
+          | _ :: args -> loop args
+          | [] -> (* should not happen *) (-1, -1)
+        in
+
+        loop args
+      in
+      let posOfFinalGreatherthan =
+        let pos = Loc.end_ e.pexp_loc in
+        (fst pos, snd pos - 1)
+      in
+      let selfClosing =
+        fst posOfGreatherthanAfterProps == fst posOfFinalGreatherthan
+        && snd posOfGreatherthanAfterProps + 1 == snd posOfFinalGreatherthan
+        (* there's an off-by one somehow in the AST *)
+      in
+      (if not selfClosing then
+         let lineStart, colStart = Loc.start pexp_loc in
+         let lineEnd, colEnd = Loc.end_ pexp_loc in
+         let length = if lineStart = lineEnd then colEnd - colStart else 0 in
+         let lineEndWhole, colEndWhole = Loc.end_ e.pexp_loc in
+         if length > 0 && colEndWhole > length then (
+           emitter
+           |> emitJsxClose ~debug ~lid:lident.txt
+                ~pos:(lineEndWhole, colEndWhole - 1);
+           emitter (* <foo ...props > <-- *)
+           |> emitJsxTag ~debug ~name:">" ~pos:posOfGreatherthanAfterProps;
+           emitter (* <foo> ... </foo> <-- *)
+           |> emitJsxTag ~debug ~name:">" ~pos:posOfFinalGreatherthan));
+
+      args |> List.iter (fun (_lbl, arg) -> iterator.expr iterator arg)
+    | Pexp_apply
+        ( {
+            pexp_desc =
+              Pexp_ident {txt = Longident.Lident (("<" | ">") as op); loc};
+          },
+          [_; _] ) ->
+      if debug then
+        Printf.printf "Binary operator %s %s\n" op (Loc.toString loc);
+      emitter |> emitFromLoc ~loc ~type_:Operator;
+      Ast_iterator.default_iterator.expr iterator e
+    | Pexp_record (cases, _) ->
+      cases
+      |> List.filter_map (fun ((label : Longident.t Location.loc), _) ->
+             match label.txt with
+             | Longident.Lident s when not (Utils.isFirstCharUppercase s) ->
+               Some label
+             | _ -> None)
+      |> List.iter (fun label -> emitter |> emitRecordLabel ~label ~debug);
+      Ast_iterator.default_iterator.expr iterator e
+    | Pexp_field (_, label) | Pexp_setfield (_, label, _) ->
+      emitter |> emitRecordLabel ~label ~debug;
+      Ast_iterator.default_iterator.expr iterator e
+    | Pexp_construct ({txt = Lident ("true" | "false")}, _) ->
+      (* Don't emit true or false *)
+      Ast_iterator.default_iterator.expr iterator e
+    | Pexp_construct (name, _) ->
+      emitter |> emitVariant ~name ~debug;
+      Ast_iterator.default_iterator.expr iterator e
+    | _ -> Ast_iterator.default_iterator.expr iterator e
+  in
+  let module_expr (iterator : Ast_iterator.iterator)
+      (me : Parsetree.module_expr) =
+    match me.pmod_desc with
+    | Pmod_ident {txt = lid; loc} ->
+      if not loc.loc_ghost then
+        emitter |> emitLongident ~pos:(Loc.start loc) ~lid ~debug;
+      Ast_iterator.default_iterator.module_expr iterator me
+    | _ -> Ast_iterator.default_iterator.module_expr iterator me
+  in
+  let module_binding (iterator : Ast_iterator.iterator)
+      (mb : Parsetree.module_binding) =
+    if not mb.pmb_name.loc.loc_ghost then
+      emitter
+      |> emitLongident
+           ~pos:(Loc.start mb.pmb_name.loc)
+           ~lid:(Longident.Lident mb.pmb_name.txt) ~debug;
+    Ast_iterator.default_iterator.module_binding iterator mb
+  in
+  let module_declaration (iterator : Ast_iterator.iterator)
+      (md : Parsetree.module_declaration) =
+    if not md.pmd_name.loc.loc_ghost then
+      emitter
+      |> emitLongident
+           ~pos:(Loc.start md.pmd_name.loc)
+           ~lid:(Longident.Lident md.pmd_name.txt) ~debug;
+    Ast_iterator.default_iterator.module_declaration iterator md
+  in
+  let module_type (iterator : Ast_iterator.iterator)
+      (mt : Parsetree.module_type) =
+    match mt.pmty_desc with
+    | Pmty_ident {txt = lid; loc} ->
+      if not loc.loc_ghost then
+        emitter
+        |> emitLongident ~upperCaseToken:Token.Type ~pos:(Loc.start loc) ~lid
+             ~debug;
+      Ast_iterator.default_iterator.module_type iterator mt
+    | _ -> Ast_iterator.default_iterator.module_type iterator mt
+  in
+  let module_type_declaration (iterator : Ast_iterator.iterator)
+      (mtd : Parsetree.module_type_declaration) =
+    if not mtd.pmtd_name.loc.loc_ghost then
+      emitter
+      |> emitLongident ~upperCaseToken:Token.Type
+           ~pos:(Loc.start mtd.pmtd_name.loc)
+           ~lid:(Longident.Lident mtd.pmtd_name.txt) ~debug;
+    Ast_iterator.default_iterator.module_type_declaration iterator mtd
+  in
+  let open_description (iterator : Ast_iterator.iterator)
+      (od : Parsetree.open_description) =
+    if not od.popen_lid.loc.loc_ghost then
+      emitter
+      |> emitLongident
+           ~pos:(Loc.start od.popen_lid.loc)
+           ~lid:od.popen_lid.txt ~debug;
+    Ast_iterator.default_iterator.open_description iterator od
+  in
+  let label_declaration (iterator : Ast_iterator.iterator)
+      (ld : Parsetree.label_declaration) =
+    emitter
+    |> emitRecordLabel
+         ~label:{loc = ld.pld_name.loc; txt = Longident.Lident ld.pld_name.txt}
+         ~debug;
+    Ast_iterator.default_iterator.label_declaration iterator ld
+  in
+  let constructor_declaration (iterator : Ast_iterator.iterator)
+      (cd : Parsetree.constructor_declaration) =
+    emitter
+    |> emitVariant
+         ~name:{loc = cd.pcd_name.loc; txt = Longident.Lident cd.pcd_name.txt}
+         ~debug;
+    Ast_iterator.default_iterator.constructor_declaration iterator cd
+  in
+
+  let structure_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.structure_item) =
+    (match item.pstr_desc with
+    | Pstr_primitive {pval_name = {txt = id; loc}} ->
+      emitter |> emitVariable ~id ~debug ~loc
+    | _ -> ());
+    Ast_iterator.default_iterator.structure_item iterator item
+  in
+
+  let signature_item (iterator : Ast_iterator.iterator)
+      (item : Parsetree.signature_item) =
+    (match item.psig_desc with
+    | Psig_value {pval_name = {txt = id; loc}} ->
+      emitter |> emitVariable ~id ~debug ~loc
+    | _ -> ());
+    Ast_iterator.default_iterator.signature_item iterator item
+  in
+
+  let iterator =
+    {
+      Ast_iterator.default_iterator with
+      constructor_declaration;
+      expr;
+      label_declaration;
+      module_declaration;
+      module_binding;
+      module_expr;
+      module_type;
+      module_type_declaration;
+      open_description;
+      pat;
+      typ;
+      type_declaration;
+      structure_item;
+      signature_item;
+    }
+  in
+
+  if Files.classifySourceFile path = Res then (
+    let parser =
+      Res_driver.parsing_engine.parse_implementation ~for_printer:false
+    in
+    let {Res_driver.parsetree = structure; diagnostics} =
+      parser ~filename:path
+    in
+    if debug then
+      Printf.printf "structure items:%d diagnostics:%d \n"
+        (List.length structure) (List.length diagnostics);
+    iterator.structure iterator structure |> ignore)
+  else
+    let parser = Res_driver.parsing_engine.parse_interface ~for_printer:false in
+    let {Res_driver.parsetree = signature; diagnostics} =
+      parser ~filename:path
+    in
+    if debug then
+      Printf.printf "signature items:%d diagnostics:%d \n"
+        (List.length signature) (List.length diagnostics);
+    iterator.signature iterator signature |> ignore
+
+let semanticTokens ~currentFile =
+  let emitter = Token.createEmitter () in
+  command ~emitter ~debug:false ~path:currentFile;
+  Printf.printf "{\"data\":[%s]}" (Token.emit emitter)
diff --git a/analysis/src/Shared.ml b/analysis/src/Shared.ml
new file mode 100644
index 0000000000..18aac6043d
--- /dev/null
+++ b/analysis/src/Shared.ml
@@ -0,0 +1,80 @@
+let tryReadCmt cmt =
+  if not (Files.exists cmt) then (
+    Log.log ("Cmt file does not exist " ^ cmt);
+    None)
+  else
+    match Cmt_format.read_cmt cmt with
+    | exception Cmi_format.Error err ->
+      Log.log
+        ("Failed to load " ^ cmt ^ " as a cmt w/ ocaml version " ^ "406"
+       ^ ", error: "
+        ^
+        (Cmi_format.report_error Format.str_formatter err;
+         Format.flush_str_formatter ()));
+      None
+    | exception err ->
+      Log.log
+        ("Invalid cmt format " ^ cmt
+       ^ " - probably wrong ocaml version, expected " ^ Config.version ^ " : "
+       ^ Printexc.to_string err);
+      None
+    | x -> Some x
+
+let tryReadCmi cmi =
+  if not (Files.exists cmi) then None
+  else
+    match Cmt_format.read_cmi cmi with
+    | exception _ ->
+      Log.log ("Failed to load " ^ cmi);
+      None
+    | x -> Some x
+
+let rec dig (te : Types.type_expr) =
+  match te.desc with
+  | Tlink inner -> dig inner
+  | Tsubst inner -> dig inner
+  | Tpoly (inner, _) -> dig inner
+  | _ -> te
+
+let digConstructor te =
+  match (dig te).desc with
+  | Tconstr (path, _args, _memo) -> Some path
+  | _ -> None
+
+let findTypeConstructors (tel : Types.type_expr list) =
+  let paths = ref [] in
+  let addPath path =
+    if not (List.exists (Path.same path) !paths) then paths := path :: !paths
+  in
+  let rec loop (te : Types.type_expr) =
+    match te.desc with
+    | Tlink te1 | Tsubst te1 | Tpoly (te1, _) -> loop te1
+    | Tconstr (path, args, _) ->
+      addPath path;
+      args |> List.iter loop
+    | Tarrow (_, te1, te2, _) ->
+      loop te1;
+      loop te2
+    | Ttuple tel -> tel |> List.iter loop
+    | Tnil | Tvar _ | Tobject _ | Tfield _ | Tvariant _ | Tunivar _ | Tpackage _
+      ->
+      ()
+  in
+  tel |> List.iter loop;
+  !paths |> List.rev
+
+let declToString ?printNameAsIs ?(recStatus = Types.Trec_not) name t =
+  PrintType.printDecl ?printNameAsIs ~recStatus name t
+
+let cacheTypeToString = ref false
+let typeTbl = Hashtbl.create 1
+
+let typeToString ?lineWidth (t : Types.type_expr) =
+  match
+    if !cacheTypeToString then Hashtbl.find_opt typeTbl (t.id, t) else None
+  with
+  | None ->
+    let s = PrintType.printExpr ?lineWidth t in
+    Hashtbl.replace typeTbl (t.id, t) s;
+    s
+  | Some s -> s
diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml
new file mode 100644
index 0000000000..aa3dffb1e9
--- /dev/null
+++ b/analysis/src/SharedTypes.ml
@@ -0,0 +1,914 @@
+let str s = if s = "" then "\"\"" else s
+let list l = "[" ^ (l |> List.map str |> String.concat ", ") ^ "]"
+let ident l = l |> List.map str |> String.concat "."
+
+type path = string list
+
+type typedFnArg = Asttypes.arg_label * Types.type_expr
+
+let pathToString (path : path) = path |> String.concat "."
+
+module ModulePath = struct
+  type t =
+    | File of Uri.t * string
+    | NotVisible
+    | IncludedModule of Path.t * t
+    | ExportedModule of {name: string; modulePath: t; isType: bool}
+
+  let toPath modulePath tipName : path =
+    let rec loop modulePath current =
+      match modulePath with
+      | File _ -> current
+      | IncludedModule (_, inner) -> loop inner current
+      | ExportedModule {name; modulePath = inner} -> loop inner (name :: current)
+      | NotVisible -> current
+    in
+    loop modulePath [tipName]
+end
+
+type field = {
+  stamp: int;
+  fname: string Location.loc;
+  typ: Types.type_expr;
+  optional: bool;
+  docstring: string list;
+  deprecated: string option;
+}
+
+type constructorArgs =
+  | InlineRecord of field list
+  | Args of (Types.type_expr * Location.t) list
+
+module Constructor = struct
+  type t = {
+    stamp: int;
+    cname: string Location.loc;
+    args: constructorArgs;
+    res: Types.type_expr option;
+    typeDecl: string * Types.type_declaration;
+    docstring: string list;
+    deprecated: string option;
+  }
+end
+
+module Type = struct
+  type kind =
+    | Abstract of (Path.t * Types.type_expr list) option
+    | Open
+    | Tuple of Types.type_expr list
+    | Record of field list
+    | Variant of Constructor.t list
+
+  type t = {
+    kind: kind;
+    decl: Types.type_declaration;
+    name: string;
+    attributes: Parsetree.attributes;
+  }
+end
+
+module Exported = struct
+  type namedStampMap = (string, int) Hashtbl.t
+
+  type t = {
+    types_: namedStampMap;
+    values_: namedStampMap;
+    modules_: namedStampMap;
+  }
+
+  type kind = Type | Value | Module
+
+  let init () =
+    {
+      types_ = Hashtbl.create 10;
+      values_ = Hashtbl.create 10;
+      modules_ = Hashtbl.create 10;
+    }
+
+  let add t kind name x =
+    let tbl =
+      match kind with
+      | Type -> t.types_
+      | Value -> t.values_
+      | Module -> t.modules_
+    in
+    if Hashtbl.mem tbl name then false
+    else
+      let () = Hashtbl.add tbl name x in
+      true
+
+  let find t kind name =
+    let tbl =
+      match kind with
+      | Type -> t.types_
+      | Value -> t.values_
+      | Module -> t.modules_
+    in
+    Hashtbl.find_opt tbl name
+
+  let iter t kind f =
+    let tbl =
+      match kind with
+      | Type -> t.types_
+      | Value -> t.values_
+      | Module -> t.modules_
+    in
+    Hashtbl.iter f tbl
+end
+
+module Module = struct
+  type kind =
+    | Value of Types.type_expr
+    | Type of Type.t * Types.rec_status
+    | Module of {type_: t; isModuleType: bool}
+
+  and item = {
+    kind: kind;
+    name: string;
+    loc: Location.t;
+    docstring: string list;
+    deprecated: string option;
+  }
+
+  and structure = {
+    name: string;
+    docstring: string list;
+    exported: Exported.t;
+    items: item list;
+    deprecated: string option;
+  }
+
+  and t = Ident of Path.t | Structure of structure | Constraint of t * t
+end
+
+module Declared = struct
+  type 'item t = {
+    name: string Location.loc;
+    extentLoc: Location.t;
+    stamp: int;
+    modulePath: ModulePath.t;
+    isExported: bool;
+    deprecated: string option;
+    docstring: string list;
+    item: 'item;
+  }
+end
+
+module Stamps : sig
+  type t
+
+  val addConstructor : t -> int -> Constructor.t Declared.t -> unit
+  val addModule : t -> int -> Module.t Declared.t -> unit
+  val addType : t -> int -> Type.t Declared.t -> unit
+  val addValue : t -> int -> Types.type_expr Declared.t -> unit
+  val findModule : t -> int -> Module.t Declared.t option
+  val findType : t -> int -> Type.t Declared.t option
+  val findValue : t -> int -> Types.type_expr Declared.t option
+  val init : unit -> t
+  val iterConstructors : (int -> Constructor.t Declared.t -> unit) -> t -> unit
+  val iterModules : (int -> Module.t Declared.t -> unit) -> t -> unit
+  val iterTypes : (int -> Type.t Declared.t -> unit) -> t -> unit
+  val iterValues : (int -> Types.type_expr Declared.t -> unit) -> t -> unit
+end = struct
+  type 't stampMap = (int, 't Declared.t) Hashtbl.t
+
+  type kind =
+    | KType of Type.t Declared.t
+    | KValue of Types.type_expr Declared.t
+    | KModule of Module.t Declared.t
+    | KConstructor of Constructor.t Declared.t
+
+  type t = (int, kind) Hashtbl.t
+
+  let init () = Hashtbl.create 10
+
+  let addConstructor (stamps : t) stamp declared =
+    Hashtbl.add stamps stamp (KConstructor declared)
+
+  let addModule stamps stamp declared =
+    Hashtbl.add stamps stamp (KModule declared)
+
+  let addType stamps stamp declared = Hashtbl.add stamps stamp (KType declared)
+
+  let addValue stamps stamp declared =
+    Hashtbl.add stamps stamp (KValue declared)
+
+  let findModule stamps stamp =
+    match Hashtbl.find_opt stamps stamp with
+    | Some (KModule declared) -> Some declared
+    | _ -> None
+
+  let findType stamps stamp =
+    match Hashtbl.find_opt stamps stamp with
+    | Some (KType declared) -> Some declared
+    | _ -> None
+
+  let findValue stamps stamp =
+    match Hashtbl.find_opt stamps stamp with
+    | Some (KValue declared) -> Some declared
+    | _ -> None
+
+  let iterModules f stamps =
+    Hashtbl.iter
+      (fun stamp d ->
+        match d with
+        | KModule d -> f stamp d
+        | _ -> ())
+      stamps
+
+  let iterTypes f stamps =
+    Hashtbl.iter
+      (fun stamp d ->
+        match d with
+        | KType d -> f stamp d
+        | _ -> ())
+      stamps
+
+  let iterValues f stamps =
+    Hashtbl.iter
+      (fun stamp d ->
+        match d with
+        | KValue d -> f stamp d
+        | _ -> ())
+      stamps
+
+  let iterConstructors f stamps =
+    Hashtbl.iter
+      (fun stamp d ->
+        match d with
+        | KConstructor d -> f stamp d
+        | _ -> ())
+      stamps
+end
+
+module File = struct
+  type t = {
+    uri: Uri.t;
+    stamps: Stamps.t;
+    moduleName: string;
+    structure: Module.structure;
+  }
+
+  let create moduleName uri =
+    {
+      uri;
+      stamps = Stamps.init ();
+      moduleName;
+      structure =
+        {
+          name = moduleName;
+          docstring = [];
+          exported = Exported.init ();
+          items = [];
+          deprecated = None;
+        };
+    }
+end
+
+module QueryEnv : sig
+  type t = private {
+    file: File.t;
+    exported: Exported.t;
+    pathRev: path;
+    parent: t option;
+  }
+  val fromFile : File.t -> t
+  val enterStructure : t -> Module.structure -> t
+
+  (* Express a path starting from the module represented by the env.
+     E.g. the env is at A.B.C and the path is D.
+     The result is A.B.C.D if D is inside C.
+     Or A.B.D or A.D or D if it's in one of its parents. *)
+  val pathFromEnv : t -> path -> bool * path
+
+  val toString : t -> string
+end = struct
+  type t = {file: File.t; exported: Exported.t; pathRev: path; parent: t option}
+
+  let toString {file; pathRev} =
+    file.moduleName :: List.rev pathRev |> String.concat "."
+
+  let fromFile (file : File.t) =
+    {file; exported = file.structure.exported; pathRev = []; parent = None}
+
+  (* Prune a path and find a parent environment that contains the module name *)
+  let rec prunePath pathRev env name =
+    if Exported.find env.exported Module name <> None then (true, pathRev)
+    else
+      match (pathRev, env.parent) with
+      | _ :: rest, Some env -> prunePath rest env name
+      | _ -> (false, [])
+
+  let pathFromEnv env path =
+    match path with
+    | [] -> (true, env.pathRev |> List.rev)
+    | name :: _ ->
+      let found, prunedPathRev = prunePath env.pathRev env name in
+      (found, List.rev_append prunedPathRev path)
+
+  let enterStructure env (structure : Module.structure) =
+    let name = structure.name in
+    let pathRev = name :: snd (prunePath env.pathRev env name) in
+    {env with exported = structure.exported; pathRev; parent = Some env}
+end
+
+type typeArgContext = {
+  env: QueryEnv.t;
+  typeArgs: Types.type_expr list;
+  typeParams: Types.type_expr list;
+}
+
+type polyVariantConstructor = {
+  name: string;
+  displayName: string;
+  args: Types.type_expr list;
+}
+
+(* TODO(env-stuff) All envs for bool string etc can be removed. *)
+type innerType = TypeExpr of Types.type_expr | ExtractedType of completionType
+and completionType =
+  | Tuple of QueryEnv.t * Types.type_expr list * Types.type_expr
+  | Texn of QueryEnv.t
+  | Tpromise of QueryEnv.t * Types.type_expr
+  | Toption of QueryEnv.t * innerType
+  | Tresult of {
+      env: QueryEnv.t;
+      okType: Types.type_expr;
+      errorType: Types.type_expr;
+    }
+  | Tbool of QueryEnv.t
+  | Tarray of QueryEnv.t * innerType
+  | Tstring of QueryEnv.t
+  | TtypeT of {env: QueryEnv.t; path: Path.t}
+  | Tvariant of {
+      env: QueryEnv.t;
+      constructors: Constructor.t list;
+      variantDecl: Types.type_declaration;
+      variantName: string;
+    }
+  | Tpolyvariant of {
+      env: QueryEnv.t;
+      constructors: polyVariantConstructor list;
+      typeExpr: Types.type_expr;
+    }
+  | Trecord of {
+      env: QueryEnv.t;
+      fields: field list;
+      definition:
+        [ `NameOnly of string
+          (** When we only have the name, like when pulling the record from a declared type. *)
+        | `TypeExpr of Types.type_expr
+          (** When we have the full type expr from the compiler. *) ];
+    }
+  | TinlineRecord of {env: QueryEnv.t; fields: field list}
+  | Tfunction of {
+      env: QueryEnv.t;
+      args: typedFnArg list;
+      typ: Types.type_expr;
+      uncurried: bool;
+      returnType: Types.type_expr;
+    }
+
+module Env = struct
+  type t = {stamps: Stamps.t; modulePath: ModulePath.t}
+  let addExportedModule ~name ~isType env =
+    {
+      env with
+      modulePath = ExportedModule {name; modulePath = env.modulePath; isType};
+    }
+  let addModule ~name env = env |> addExportedModule ~name ~isType:false
+  let addModuleType ~name env = env |> addExportedModule ~name ~isType:true
+end
+
+type filePath = string
+
+type paths =
+  | Impl of {cmt: filePath; res: filePath}
+  | Namespace of {cmt: filePath}
+  | IntfAndImpl of {
+      cmti: filePath;
+      resi: filePath;
+      cmt: filePath;
+      res: filePath;
+    }
+
+let showPaths paths =
+  match paths with
+  | Impl {cmt; res} ->
+    Printf.sprintf "Impl cmt:%s res:%s" (Utils.dumpPath cmt)
+      (Utils.dumpPath res)
+  | Namespace {cmt} -> Printf.sprintf "Namespace cmt:%s" (Utils.dumpPath cmt)
+  | IntfAndImpl {cmti; resi; cmt; res} ->
+    Printf.sprintf "IntfAndImpl cmti:%s resi:%s cmt:%s res:%s"
+      (Utils.dumpPath cmti) (Utils.dumpPath resi) (Utils.dumpPath cmt)
+      (Utils.dumpPath res)
+
+let getSrc p =
+  match p with
+  | Impl {res} -> [res]
+  | Namespace _ -> []
+  | IntfAndImpl {resi; res} -> [resi; res]
+
+let getUri p =
+  match p with
+  | Impl {res} -> Uri.fromPath res
+  | Namespace {cmt} -> Uri.fromPath cmt
+  | IntfAndImpl {resi} -> Uri.fromPath resi
+
+let getUris p =
+  match p with
+  | Impl {res} -> [Uri.fromPath res]
+  | Namespace {cmt} -> [Uri.fromPath cmt]
+  | IntfAndImpl {res; resi} -> [Uri.fromPath res; Uri.fromPath resi]
+
+let getCmtPath ~uri p =
+  match p with
+  | Impl {cmt} -> cmt
+  | Namespace {cmt} -> cmt
+  | IntfAndImpl {cmti; cmt} ->
+    let interface = Utils.endsWith (Uri.toPath uri) "i" in
+    if interface then cmti else cmt
+
+module Tip = struct
+  type t = Value | Type | Field of string | Constructor of string | Module
+
+  let toString tip =
+    match tip with
+    | Value -> "Value"
+    | Type -> "Type"
+    | Field f -> "Field(" ^ f ^ ")"
+    | Constructor a -> "Constructor(" ^ a ^ ")"
+    | Module -> "Module"
+end
+
+let rec pathIdentToString (p : Path.t) =
+  match p with
+  | Pident {name} -> name
+  | Pdot (nextPath, id, _) ->
+    Printf.sprintf "%s.%s" (pathIdentToString nextPath) id
+  | Papply _ -> ""
+
+type locKind =
+  | LocalReference of int * Tip.t
+  | GlobalReference of string * string list * Tip.t
+  | NotFound
+  | Definition of int * Tip.t
+
+type locType =
+  | Typed of string * Types.type_expr * locKind
+  | Constant of Asttypes.constant
+  | LModule of locKind
+  | TopLevelModule of string
+  | TypeDefinition of string * Types.type_declaration * int
+
+type locItem = {loc: Location.t; locType: locType}
+
+module LocationSet = Set.Make (struct
+  include Location
+
+  let compare loc1 loc2 = compare loc2 loc1
+
+  (* polymorphic compare should be OK *)
+end)
+
+type extra = {
+  internalReferences: (int, Location.t list) Hashtbl.t;
+  externalReferences:
+    (string, (string list * Tip.t * Location.t) list) Hashtbl.t;
+  fileReferences: (string, LocationSet.t) Hashtbl.t;
+  mutable locItems: locItem list;
+}
+
+type file = string
+
+module FileSet = Set.Make (String)
+
+type builtInCompletionModules = {
+  arrayModulePath: string list;
+  optionModulePath: string list;
+  stringModulePath: string list;
+  intModulePath: string list;
+  floatModulePath: string list;
+  promiseModulePath: string list;
+  listModulePath: string list;
+  resultModulePath: string list;
+  exnModulePath: string list;
+  regexpModulePath: string list;
+}
+
+type package = {
+  genericJsxModule: string option;
+  suffix: string;
+  rootPath: filePath;
+  projectFiles: FileSet.t;
+  dependenciesFiles: FileSet.t;
+  pathsForModule: (file, paths) Hashtbl.t;
+  namespace: string option;
+  builtInCompletionModules: builtInCompletionModules;
+  opens: path list;
+  uncurried: bool;
+  rescriptVersion: int * int;
+}
+
+let allFilesInPackage package =
+  FileSet.union package.projectFiles package.dependenciesFiles
+
+type full = {extra: extra; file: File.t; package: package}
+
+let initExtra () =
+  {
+    internalReferences = Hashtbl.create 10;
+    externalReferences = Hashtbl.create 10;
+    fileReferences = Hashtbl.create 10;
+    locItems = [];
+  }
+
+type state = {
+  packagesByRoot: (string, package) Hashtbl.t;
+  rootForUri: (Uri.t, string) Hashtbl.t;
+  cmtCache: (filePath, File.t) Hashtbl.t;
+}
+
+(* There's only one state, so it can as well be global *)
+let state =
+  {
+    packagesByRoot = Hashtbl.create 1;
+    rootForUri = Hashtbl.create 30;
+    cmtCache = Hashtbl.create 30;
+  }
+
+let locKindToString = function
+  | LocalReference (_, tip) -> "(LocalReference " ^ Tip.toString tip ^ ")"
+  | GlobalReference _ -> "GlobalReference"
+  | NotFound -> "NotFound"
+  | Definition (_, tip) -> "(Definition " ^ Tip.toString tip ^ ")"
+
+let locTypeToString = function
+  | Typed (name, e, locKind) ->
+    "Typed " ^ name ^ " " ^ Shared.typeToString e ^ " "
+    ^ locKindToString locKind
+  | Constant _ -> "Constant"
+  | LModule locKind -> "LModule " ^ locKindToString locKind
+  | TopLevelModule _ -> "TopLevelModule"
+  | TypeDefinition _ -> "TypeDefinition"
+
+let locItemToString {loc = {Location.loc_start; loc_end}; locType} =
+  let pos1 = Utils.cmtPosToPosition loc_start in
+  let pos2 = Utils.cmtPosToPosition loc_end in
+  Printf.sprintf "%d:%d-%d:%d %s" pos1.line pos1.character pos2.line
+    pos2.character (locTypeToString locType)
+
+(* needed for debugging *)
+let _ = locItemToString
+
+module Completable = struct
+  (* Completion context *)
+  type completionContext = Type | Value | Module | Field | ValueOrField
+
+  type argumentLabel =
+    | Unlabelled of {argumentPosition: int}
+    | Labelled of string
+    | Optional of string
+
+  (** Additional context for nested completion where needed. *)
+  type nestedContext =
+    | RecordField of {seenFields: string list}
+        (** Completing for a record field, and we already saw the following fields... *)
+    | CameFromRecordField of string
+        (** We just came from this field (we leverage use this for better
+            completion names etc) *)
+
+  type nestedPath =
+    | NTupleItem of {itemNum: int}
+    | NFollowRecordField of {fieldName: string}
+    | NRecordBody of {seenFields: string list}
+    | NVariantPayload of {constructorName: string; itemNum: int}
+    | NPolyvariantPayload of {constructorName: string; itemNum: int}
+    | NArray
+
+  let nestedPathToString p =
+    match p with
+    | NTupleItem {itemNum} -> "tuple($" ^ string_of_int itemNum ^ ")"
+    | NFollowRecordField {fieldName} -> "recordField(" ^ fieldName ^ ")"
+    | NRecordBody _ -> "recordBody"
+    | NVariantPayload {constructorName; itemNum} ->
+      "variantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum ^ ")"
+    | NPolyvariantPayload {constructorName; itemNum} ->
+      "polyvariantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum
+      ^ ")"
+    | NArray -> "array"
+
+  type contextPath =
+    | CPString
+    | CPArray of contextPath option
+    | CPInt
+    | CPFloat
+    | CPBool
+    | CPOption of contextPath
+    | CPApply of contextPath * Asttypes.arg_label list
+    | CPId of {
+        path: string list;
+        completionContext: completionContext;
+        loc: Location.t;
+      }
+    | CPField of contextPath * string
+    | CPObj of contextPath * string
+    | CPAwait of contextPath
+    | CPPipe of {
+        contextPath: contextPath;
+        id: string;
+        inJsx: bool;  (** Whether this pipe was found in a JSX context. *)
+        lhsLoc: Location.t;
+            (** The loc item for the left hand side of the pipe. *)
+      }
+    | CTuple of contextPath list
+    | CArgument of {
+        functionContextPath: contextPath;
+        argumentLabel: argumentLabel;
+      }
+    | CJsxPropValue of {
+        pathToComponent: string list;
+        propName: string;
+        emptyJsxPropNameHint: string option;
+            (* This helps handle a special case in JSX prop completion. More info where this is used. *)
+      }
+    | CPatternPath of {rootCtxPath: contextPath; nested: nestedPath list}
+    | CTypeAtPos of Location.t
+        (** A position holding something that might have a *compiled* type. *)
+
+  type patternMode = Default | Destructuring
+
+  type decoratorPayload =
+    | Module of string
+    | ModuleWithImportAttributes of {nested: nestedPath list; prefix: string}
+    | JsxConfig of {nested: nestedPath list; prefix: string}
+
+  type t =
+    | Cdecorator of string  (** e.g. @module *)
+    | CdecoratorPayload of decoratorPayload
+    | CextensionNode of string  (** e.g. %todo *)
+    | CnamedArg of contextPath * string * string list
+        (** e.g. (..., "label", ["l1", "l2"]) for ...(...~l1...~l2...~label...) *)
+    | Cnone  (** e.g. don't complete inside strings *)
+    | Cpath of contextPath
+    | Cjsx of string list * string * string list
+        (** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for <M.Comp id1=... id2=... ... id *)
+    | Cexpression of {
+        contextPath: contextPath;
+        nested: nestedPath list;
+        prefix: string;
+      }
+    | Cpattern of {
+        contextPath: contextPath;
+        nested: nestedPath list;
+        prefix: string;
+        patternMode: patternMode;
+        fallback: t option;
+      }
+    | CexhaustiveSwitch of {contextPath: contextPath; exprLoc: Location.t}
+    | ChtmlElement of {prefix: string}
+
+  let completionContextToString = function
+    | Value -> "Value"
+    | Type -> "Type"
+    | Module -> "Module"
+    | Field -> "Field"
+    | ValueOrField -> "ValueOrField"
+
+  let rec contextPathToString = function
+    | CPString -> "string"
+    | CPInt -> "int"
+    | CPFloat -> "float"
+    | CPBool -> "bool"
+    | CPAwait ctxPath -> "await " ^ contextPathToString ctxPath
+    | CPOption ctxPath -> "option<" ^ contextPathToString ctxPath ^ ">"
+    | CPApply (cp, labels) ->
+      contextPathToString cp ^ "("
+      ^ (labels
+        |> List.map (function
+             | Asttypes.Nolabel -> "Nolabel"
+             | Labelled s -> "~" ^ s
+             | Optional s -> "?" ^ s)
+        |> String.concat ", ")
+      ^ ")"
+    | CPArray (Some ctxPath) -> "array<" ^ contextPathToString ctxPath ^ ">"
+    | CPArray None -> "array"
+    | CPId {path; completionContext} ->
+      completionContextToString completionContext ^ list path
+    | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s
+    | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]"
+    | CPPipe {contextPath; id; inJsx} ->
+      contextPathToString contextPath
+      ^ "->" ^ id
+      ^ if inJsx then " <<jsx>>" else ""
+    | CTuple ctxPaths ->
+      "CTuple("
+      ^ (ctxPaths |> List.map contextPathToString |> String.concat ", ")
+      ^ ")"
+    | CArgument {functionContextPath; argumentLabel} ->
+      "CArgument "
+      ^ contextPathToString functionContextPath
+      ^ "("
+      ^ (match argumentLabel with
+        | Unlabelled {argumentPosition} -> "$" ^ string_of_int argumentPosition
+        | Labelled name -> "~" ^ name
+        | Optional name -> "~" ^ name ^ "=?")
+      ^ ")"
+    | CJsxPropValue {pathToComponent; propName} ->
+      "CJsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName
+    | CPatternPath {rootCtxPath; nested} ->
+      "CPatternPath("
+      ^ contextPathToString rootCtxPath
+      ^ ")" ^ "->"
+      ^ (nested
+        |> List.map (fun nestedPath -> nestedPathToString nestedPath)
+        |> String.concat "->")
+    | CTypeAtPos _loc -> "CTypeAtPos()"
+
+  let toString = function
+    | Cpath cp -> "Cpath " ^ contextPathToString cp
+    | Cdecorator s -> "Cdecorator(" ^ str s ^ ")"
+    | CextensionNode s -> "CextensionNode(" ^ str s ^ ")"
+    | CdecoratorPayload (Module s) -> "CdecoratorPayload(module=" ^ s ^ ")"
+    | CdecoratorPayload (ModuleWithImportAttributes _) ->
+      "CdecoratorPayload(moduleWithImportAttributes)"
+    | CdecoratorPayload (JsxConfig _) -> "JsxConfig"
+    | CnamedArg (cp, s, sl2) ->
+      "CnamedArg("
+      ^ (cp |> contextPathToString)
+      ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")"
+    | Cnone -> "Cnone"
+    | Cjsx (sl1, s, sl2) ->
+      "Cjsx(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")"
+    | Cpattern {contextPath; nested; prefix} -> (
+      "Cpattern "
+      ^ contextPathToString contextPath
+      ^ (if prefix = "" then "" else "=" ^ prefix)
+      ^
+      match nested with
+      | [] -> ""
+      | nestedPaths ->
+        "->"
+        ^ (nestedPaths
+          |> List.map (fun nestedPath -> nestedPathToString nestedPath)
+          |> String.concat ", "))
+    | Cexpression {contextPath; nested; prefix} -> (
+      "Cexpression "
+      ^ contextPathToString contextPath
+      ^ (if prefix = "" then "" else "=" ^ prefix)
+      ^
+      match nested with
+      | [] -> ""
+      | nestedPaths ->
+        "->"
+        ^ (nestedPaths
+          |> List.map (fun nestedPath -> nestedPathToString nestedPath)
+          |> String.concat ", "))
+    | CexhaustiveSwitch {contextPath} ->
+      "CexhaustiveSwitch " ^ contextPathToString contextPath
+    | ChtmlElement {prefix} -> "ChtmlElement <" ^ prefix
+end
+
+module ScopeTypes = struct
+  type item =
+    | Constructor of string * Location.t
+    | Field of string * Location.t
+    | Module of string * Location.t
+    | Open of string list
+    | Type of string * Location.t
+    | Value of string * Location.t * Completable.contextPath option * item list
+end
+
+module Completion = struct
+  type kind =
+    | Module of {docstring: string list; module_: Module.t}
+    | Value of Types.type_expr
+    | ObjLabel of Types.type_expr
+    | Label of string
+    | Type of Type.t
+    | Constructor of Constructor.t * string
+    | PolyvariantConstructor of polyVariantConstructor * string
+    | Field of field * string
+    | FileModule of string
+    | Snippet of string
+    | ExtractedType of completionType * [`Value | `Type]
+    | FollowContextPath of Completable.contextPath * ScopeTypes.item list
+
+  type t = {
+    name: string;
+    sortText: string option;
+    insertText: string option;
+    filterText: string option;
+    insertTextFormat: Protocol.insertTextFormat option;
+    env: QueryEnv.t;
+    deprecated: string option;
+    docstring: string list;
+    kind: kind;
+    detail: string option;
+    typeArgContext: typeArgContext option;
+    data: (string * string) list option;
+  }
+
+  let create ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind
+      ~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name =
+    {
+      name;
+      env;
+      deprecated;
+      docstring;
+      kind;
+      sortText;
+      insertText;
+      insertTextFormat =
+        (if includesSnippets then Some Protocol.Snippet else None);
+      filterText;
+      detail;
+      typeArgContext;
+      data;
+    }
+
+  (* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *)
+  (* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind *)
+  let kindToInt kind =
+    match kind with
+    | Module _ -> 9
+    | FileModule _ -> 9
+    | Constructor (_, _) | PolyvariantConstructor (_, _) -> 4
+    | ObjLabel _ -> 4
+    | Label _ -> 4
+    | Field (_, _) -> 5
+    | Type _ | ExtractedType (_, `Type) -> 22
+    | Value _ | ExtractedType (_, `Value) -> 12
+    | Snippet _ | FollowContextPath _ -> 15
+end
+
+let kindFromInnerType (t : innerType) =
+  match t with
+  | ExtractedType extractedType ->
+    Completion.ExtractedType (extractedType, `Value)
+  | TypeExpr typ -> Value typ
+
+module CursorPosition = struct
+  type t = NoCursor | HasCursor | EmptyLoc
+
+  let classifyLoc loc ~pos =
+    if loc |> Loc.hasPos ~pos then HasCursor
+    else if loc |> Loc.end_ = (Location.none |> Loc.end_) then EmptyLoc
+    else NoCursor
+
+  let classifyLocationLoc (loc : 'a Location.loc) ~pos =
+    if Loc.start loc.Location.loc <= pos && pos <= Loc.end_ loc.loc then
+      HasCursor
+    else if loc.loc |> Loc.end_ = (Location.none |> Loc.end_) then EmptyLoc
+    else NoCursor
+
+  let classifyPositions pos ~posStart ~posEnd =
+    if posStart <= pos && pos <= posEnd then HasCursor
+    else if posEnd = (Location.none |> Loc.end_) then EmptyLoc
+    else NoCursor
+
+  let locHasCursor loc ~pos = loc |> classifyLoc ~pos = HasCursor
+
+  let locIsEmpty loc ~pos = loc |> classifyLoc ~pos = EmptyLoc
+end
+
+type labelled = {
+  name: string;
+  opt: bool;
+  posStart: int * int;
+  posEnd: int * int;
+}
+
+type label = labelled option
+type arg = {label: label; exp: Parsetree.expression}
+
+let extractExpApplyArgs ~args =
+  let rec processArgs ~acc args =
+    match args with
+    | (((Asttypes.Labelled s | Optional s) as label), (e : Parsetree.expression))
+      :: rest -> (
+      let namedArgLoc =
+        e.pexp_attributes
+        |> List.find_opt (fun ({Asttypes.txt}, _) -> txt = "res.namedArgLoc")
+      in
+      match namedArgLoc with
+      | Some ({loc}, _) ->
+        let labelled =
+          {
+            name = s;
+            opt =
+              (match label with
+              | Optional _ -> true
+              | _ -> false);
+            posStart = Loc.start loc;
+            posEnd = Loc.end_ loc;
+          }
+        in
+        processArgs ~acc:({label = Some labelled; exp = e} :: acc) rest
+      | None -> processArgs ~acc rest)
+    | (Asttypes.Nolabel, (e : Parsetree.expression)) :: rest ->
+      if e.pexp_loc.loc_ghost then processArgs ~acc rest
+      else processArgs ~acc:({label = None; exp = e} :: acc) rest
+    | [] -> List.rev acc
+  in
+  args |> processArgs ~acc:[]
diff --git a/analysis/src/SignatureHelp.ml b/analysis/src/SignatureHelp.ml
new file mode 100644
index 0000000000..c2e148de87
--- /dev/null
+++ b/analysis/src/SignatureHelp.ml
@@ -0,0 +1,768 @@
+open SharedTypes
+type cursorAtArg = Unlabelled of int | Labelled of string
+
+(* Produces the doc string shown below the signature help for each parameter. *)
+let docsForLabel typeExpr ~file ~package ~supportsMarkdownLinks =
+  let types = Hover.findRelevantTypesFromType ~file ~package typeExpr in
+  let typeNames = types |> List.map (fun {Hover.name} -> name) in
+  let typeDefinitions =
+    types
+    |> List.map (fun {Hover.decl; name; env; loc; path} ->
+           let linkToTypeDefinitionStr =
+             if supportsMarkdownLinks then
+               Markdown.goToDefinitionText ~env ~pos:loc.Warnings.loc_start
+             else ""
+           in
+           (* Since printing the whole name via its path can get quite long, and
+              we're short on space for the signature help, we'll only print the
+              fully "qualified" type name if we must (ie if several types we're
+              displaying have the same name). *)
+           let multipleTypesHaveThisName =
+             typeNames
+             |> List.filter (fun typeName -> typeName = name)
+             |> List.length > 1
+           in
+           let typeName =
+             if multipleTypesHaveThisName then
+               path |> SharedTypes.pathIdentToString
+             else name
+           in
+           Markdown.codeBlock
+             (Shared.declToString ~printNameAsIs:true typeName decl)
+           ^ linkToTypeDefinitionStr)
+  in
+  typeDefinitions |> String.concat "\n"
+
+let findFunctionType ~currentFile ~debug ~path ~pos =
+  (* Start by looking at the typed info at the loc of the fn *)
+  match Cmt.loadFullCmtFromPath ~path with
+  | None -> None
+  | Some full -> (
+    let {file; package} = full in
+    let env = QueryEnv.fromFile file in
+    let fnFromLocItem =
+      match References.getLocItem ~full ~pos ~debug:false with
+      | Some {locType = Typed (_, typeExpr, locKind)} -> (
+        let docstring =
+          match References.definedForLoc ~file ~package locKind with
+          | None -> []
+          | Some (docstring, _) -> docstring
+        in
+        if Debug.verbose () then
+          Printf.printf "[sig_help_fn] Found loc item: %s.\n"
+            (Shared.typeToString typeExpr);
+        match
+          TypeUtils.extractFunctionType2 ~env ~package:full.package typeExpr
+        with
+        | args, _tRet, _ when args <> [] ->
+          Some (args, docstring, typeExpr, package, env, file)
+        | _ -> None)
+      | None ->
+        if Debug.verbose () then
+          Printf.printf "[sig_help_fn] Found no loc item.\n";
+        None
+      | Some _ ->
+        if Debug.verbose () then
+          Printf.printf
+            "[sig_help_fn] Found loc item, but not what was expected.\n";
+        None
+    in
+    match fnFromLocItem with
+    | Some fnFromLocItem -> Some fnFromLocItem
+    | None -> (
+      (* If nothing was found there, try using the unsaved completion engine *)
+      let completables =
+        let textOpt = Files.readFile currentFile in
+        match textOpt with
+        | None | Some "" -> None
+        | Some text -> (
+          (* Leverage the completion functionality to pull out the type of the identifier doing the function application.
+             This lets us leverage all of the smart work done in completions to find the correct type in many cases even
+             for files not saved yet. *)
+          match
+            CompletionFrontEnd.completionWithParser ~debug ~path ~posCursor:pos
+              ~currentFile ~text
+          with
+          | None -> None
+          | Some (completable, scope) ->
+            Some
+              ( completable
+                |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope
+                     ~env ~forHover:true,
+                env,
+                package,
+                file ))
+      in
+      match completables with
+      | Some ({kind = Value type_expr; docstring} :: _, env, package, file) ->
+        let args, _, _ =
+          TypeUtils.extractFunctionType2 type_expr ~env ~package
+        in
+        Some (args, docstring, type_expr, package, env, file)
+      | _ -> None))
+
+(* Extracts all parameters from a parsed function signature *)
+let extractParameters ~signature ~typeStrForParser ~labelPrefixLen =
+  match signature with
+  | [
+   ( {
+       Parsetree.psig_desc =
+         Psig_value {pval_type = {ptyp_desc = Ptyp_arrow _} as expr};
+     }
+   | {
+       psig_desc =
+         Psig_value
+           {
+             pval_type =
+               {
+                 ptyp_desc =
+                   Ptyp_constr
+                     ( {txt = Lident "function$"},
+                       [({ptyp_desc = Ptyp_arrow _} as expr); _] );
+               };
+           };
+     } );
+  ] ->
+    let rec extractParams expr params =
+      match expr with
+      | {
+       (* Gotcha: functions with multiple arugments are modelled as a series of single argument functions. *)
+       Parsetree.ptyp_desc =
+         Ptyp_arrow (argumentLabel, argumentTypeExpr, nextFunctionExpr);
+       ptyp_loc;
+      } ->
+        let startOffset =
+          ptyp_loc |> Loc.start
+          |> Pos.positionToOffset typeStrForParser
+          |> Option.get
+        in
+        let endOffset =
+          argumentTypeExpr.ptyp_loc |> Loc.end_
+          |> Pos.positionToOffset typeStrForParser
+          |> Option.get
+        in
+        (* The AST locations does not account for "=?" of optional arguments, so add that to the offset here if needed. *)
+        let endOffset =
+          match argumentLabel with
+          | Asttypes.Optional _ -> endOffset + 2
+          | _ -> endOffset
+        in
+        extractParams nextFunctionExpr
+          (params
+          @ [
+              ( argumentLabel,
+                (* Remove the label prefix offset here, since we're not showing
+                   that to the end user. *)
+                startOffset - labelPrefixLen,
+                endOffset - labelPrefixLen );
+            ])
+      | _ -> params
+    in
+    extractParams expr []
+  | _ -> []
+
+(* Finds what parameter is active, if any *)
+let findActiveParameter ~argAtCursor ~args =
+  match argAtCursor with
+  | None -> (
+    (* If a function only has one, unlabelled argument, we can safely assume that's active whenever we're in the signature help for that function,
+       even if we technically didn't find anything at the cursor (which we don't for empty expressions). *)
+    match args with
+    | [(Asttypes.Nolabel, _)] -> Some 0
+    | _ -> None)
+  | Some (Unlabelled unlabelledArgumentIndex) ->
+    let index = ref 0 in
+    args
+    |> List.find_map (fun (label, _) ->
+           match label with
+           | Asttypes.Nolabel when !index = unlabelledArgumentIndex ->
+             Some !index
+           | _ ->
+             index := !index + 1;
+             None)
+  | Some (Labelled name) ->
+    let index = ref 0 in
+    args
+    |> List.find_map (fun (label, _) ->
+           match label with
+           | (Asttypes.Labelled labelName | Optional labelName)
+             when labelName = name ->
+             Some !index
+           | _ ->
+             index := !index + 1;
+             None)
+
+type constructorInfo = {
+  docstring: string list;
+  name: string;
+  args: constructorArgs;
+}
+
+let findConstructorArgs ~full ~env ~constructorName loc =
+  match
+    References.getLocItem ~debug:false ~full
+      ~pos:(Pos.ofLexing loc.Location.loc_end)
+  with
+  | None -> None
+  | Some {locType = Typed (_, typExpr, _)} -> (
+    match TypeUtils.extractType ~env ~package:full.package typExpr with
+    | Some ((Toption (_, TypeExpr t) as extractedType), _) -> (
+      match constructorName with
+      | "Some" ->
+        Some
+          {
+            name = "Some";
+            docstring =
+              [
+                Markdown.codeBlock
+                  (TypeUtils.extractedTypeToString extractedType);
+              ];
+            args = Args [(t, Location.none)];
+          }
+      | _ -> None)
+    | Some ((Tresult {okType; errorType} as extractedType), _) -> (
+      match constructorName with
+      | "Ok" ->
+        Some
+          {
+            name = "Ok";
+            docstring =
+              [
+                Markdown.codeBlock
+                  (TypeUtils.extractedTypeToString extractedType);
+              ];
+            args = Args [(okType, Location.none)];
+          }
+      | "Error" ->
+        Some
+          {
+            name = "Error";
+            docstring =
+              [
+                Markdown.codeBlock
+                  (TypeUtils.extractedTypeToString extractedType);
+              ];
+            args = Args [(errorType, Location.none)];
+          }
+      | _ -> None)
+    | Some (Tvariant {constructors}, _) ->
+      constructors
+      |> List.find_opt (fun (c : Constructor.t) ->
+             c.cname.txt = constructorName)
+      |> Option.map (fun (c : Constructor.t) ->
+             {docstring = c.docstring; name = c.cname.txt; args = c.args})
+    | _ -> None)
+  | _ -> None
+
+let signatureHelp ~path ~pos ~currentFile ~debug ~allowForConstructorPayloads =
+  let textOpt = Files.readFile currentFile in
+  match textOpt with
+  | None | Some "" -> None
+  | Some text -> (
+    match Pos.positionToOffset text pos with
+    | None -> None
+    | Some offset -> (
+      let posBeforeCursor = Pos.posBeforeCursor pos in
+      let offsetNoWhite = Utils.skipWhite text (offset - 1) in
+      let firstCharBeforeCursorNoWhite =
+        if offsetNoWhite < String.length text && offsetNoWhite >= 0 then
+          Some text.[offsetNoWhite]
+        else None
+      in
+      let locHasCursor loc =
+        loc |> CursorPosition.locHasCursor ~pos:posBeforeCursor
+      in
+      let supportsMarkdownLinks = true in
+      let result = ref None in
+      let printThing thg =
+        match thg with
+        | `ConstructorExpr _ -> "Constructor(expr)"
+        | `ConstructorPat _ -> "Constructor(pat)"
+        | `FunctionCall _ -> "FunctionCall"
+      in
+      let setResult (loc, thing) =
+        match (thing, allowForConstructorPayloads) with
+        | (`ConstructorExpr _ | `ConstructorPat _), false -> ()
+        | _ -> (
+          match !result with
+          | None ->
+            if Debug.verbose () then
+              Printf.printf "[sig_help_result] Setting because had none\n";
+            result := Some (loc, thing)
+          | Some (currentLoc, currentThing)
+            when Pos.ofLexing loc.Location.loc_start
+                 > Pos.ofLexing currentLoc.Location.loc_start ->
+            result := Some (loc, thing);
+
+            if Debug.verbose () then
+              Printf.printf
+                "[sig_help_result] Setting because loc of %s > then existing \
+                 of %s\n"
+                (printThing thing) (printThing currentThing)
+          | Some (_, currentThing) ->
+            if Debug.verbose () then
+              Printf.printf
+                "[sig_help_result] Doing nothing because loc of %s < then \
+                 existing of %s\n"
+                (printThing thing) (printThing currentThing))
+      in
+      let searchForArgWithCursor ~isPipeExpr ~args =
+        let extractedArgs = extractExpApplyArgs ~args in
+        let argAtCursor =
+          let firstArgIndex = if isPipeExpr then 1 else 0 in
+          let unlabelledArgCount = ref firstArgIndex in
+          let lastUnlabelledArgBeforeCursor = ref firstArgIndex in
+          let argAtCursor_ =
+            extractedArgs
+            |> List.find_map (fun arg ->
+                   match arg.label with
+                   | None ->
+                     let currentUnlabelledArgCount = !unlabelledArgCount in
+                     unlabelledArgCount := currentUnlabelledArgCount + 1;
+                     (* An argument without a label is just the expression, so we can use that. *)
+                     if locHasCursor arg.exp.pexp_loc then
+                       Some (Unlabelled currentUnlabelledArgCount)
+                     else (
+                       (* If this unlabelled arg doesn't have the cursor, record
+                          it as the last seen unlabelled arg before the
+                          cursor.*)
+                       if posBeforeCursor >= (arg.exp.pexp_loc |> Loc.start)
+                       then
+                         lastUnlabelledArgBeforeCursor :=
+                           currentUnlabelledArgCount;
+                       None)
+                   | Some {name; posStart; posEnd} -> (
+                     (* Check for the label identifier itself having the cursor *)
+                     match
+                       pos |> CursorPosition.classifyPositions ~posStart ~posEnd
+                     with
+                     | HasCursor -> Some (Labelled name)
+                     | NoCursor | EmptyLoc -> (
+                       (* If we're not in the label, check the exp. Either the exp
+                          exists and has the cursor. Or the exp is a parser recovery
+                          node, in which case we assume that the parser recovery
+                          indicates that the cursor was here. *)
+                       match
+                         ( arg.exp.pexp_desc,
+                           arg.exp.pexp_loc
+                           |> CursorPosition.classifyLoc ~pos:posBeforeCursor )
+                       with
+                       | Pexp_extension ({txt = "rescript.exprhole"}, _), _
+                       | _, HasCursor ->
+                         Some (Labelled name)
+                       | _ -> None)))
+          in
+
+          match argAtCursor_ with
+          | None ->
+            Some
+              (Unlabelled
+                 (!lastUnlabelledArgBeforeCursor
+                 +
+                 if firstCharBeforeCursorNoWhite = Some ',' then 1
+                   (* If we found no argument with the cursor, we might still be
+                      able to complete for an unlabelled argument, if the char
+                      before the cursor is ',', like: `someFn(123, <com>)`
+                      complete for argument 2, or: `someFn(123, <com>, true)`
+                      complete for argument 2 as well. Adding 1 here accounts
+                      for the comma telling us that the users intent is to fill
+                      in the next argument. *)
+                 else 0))
+          | v -> v
+        in
+        (argAtCursor, extractedArgs)
+      in
+      let expr (iterator : Ast_iterator.iterator) (expr : Parsetree.expression)
+          =
+        (match expr with
+        (* Handle pipes, like someVar->someFunc(... *)
+        | {
+         pexp_desc =
+           Pexp_apply
+             ( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
+               [
+                 _;
+                 ( _,
+                   {
+                     pexp_desc =
+                       Pexp_apply (({pexp_desc = Pexp_ident _} as exp), args);
+                     pexp_loc;
+                   } );
+               ] );
+        }
+          when locHasCursor pexp_loc ->
+          let argAtCursor, extractedArgs =
+            searchForArgWithCursor ~isPipeExpr:true ~args
+          in
+          setResult
+            (exp.pexp_loc, `FunctionCall (argAtCursor, exp, extractedArgs))
+        (* Look for applying idents, like someIdent(...) *)
+        | {
+         pexp_desc = Pexp_apply (({pexp_desc = Pexp_ident _} as exp), args);
+         pexp_loc;
+        }
+          when locHasCursor pexp_loc ->
+          let argAtCursor, extractedArgs =
+            searchForArgWithCursor ~isPipeExpr:false ~args
+          in
+          setResult
+            (exp.pexp_loc, `FunctionCall (argAtCursor, exp, extractedArgs))
+        | {pexp_desc = Pexp_construct (lid, Some payloadExp); pexp_loc}
+          when locHasCursor payloadExp.pexp_loc
+               || CompletionExpressions.isExprHole payloadExp
+                  && locHasCursor pexp_loc ->
+          (* Constructor payloads *)
+          setResult (lid.loc, `ConstructorExpr (lid, payloadExp))
+        | _ -> ());
+        Ast_iterator.default_iterator.expr iterator expr
+      in
+      let pat (iterator : Ast_iterator.iterator) (pat : Parsetree.pattern) =
+        (match pat with
+        | {ppat_desc = Ppat_construct (lid, Some payloadPat)}
+          when locHasCursor payloadPat.ppat_loc ->
+          (* Constructor payloads *)
+          setResult (lid.loc, `ConstructorPat (lid, payloadPat))
+        | _ -> ());
+        Ast_iterator.default_iterator.pat iterator pat
+      in
+      let iterator = {Ast_iterator.default_iterator with expr; pat} in
+      let parser =
+        Res_driver.parsing_engine.parse_implementation ~for_printer:false
+      in
+      let {Res_driver.parsetree = structure} = parser ~filename:currentFile in
+      iterator.structure iterator structure |> ignore;
+      (* Handle function application, if found *)
+      match !result with
+      | Some (_, `FunctionCall (argAtCursor, exp, _extractedArgs)) -> (
+        (* Not looking for the cursor position after this, but rather the target function expression's loc. *)
+        let pos = exp.pexp_loc |> Loc.end_ in
+        match findFunctionType ~currentFile ~debug ~path ~pos with
+        | Some (args, docstring, type_expr, package, _env, file) ->
+          if debug then
+            Printf.printf "argAtCursor: %s\n"
+              (match argAtCursor with
+              | None -> "none"
+              | Some (Labelled name) -> "~" ^ name
+              | Some (Unlabelled index) ->
+                "unlabelled<" ^ string_of_int index ^ ">");
+
+          (* The LS protocol wants us to send both the full type signature (label) that the end user sees as the signature help, and all parameters in that label
+             in the form of a list of start/end character offsets. We leverage the parser to figure the offsets out by parsing the label, and extract the
+             offsets from the parser. *)
+
+          (* A full let binding with the type text is needed for the parser to be able to parse it.  *)
+          let labelPrefix = "let fn: " in
+          let labelPrefixLen = String.length labelPrefix in
+          let fnTypeStr = Shared.typeToString type_expr in
+          let typeStrForParser = labelPrefix ^ fnTypeStr in
+          let {Res_driver.parsetree = signature} =
+            Res_driver.parse_interface_from_source ~for_printer:false
+              ~display_filename:"<missing-file>" ~source:typeStrForParser
+          in
+
+          let parameters =
+            extractParameters ~signature ~typeStrForParser ~labelPrefixLen
+          in
+          if debug then
+            Printf.printf "extracted params: \n%s\n"
+              (parameters
+              |> List.map (fun (_, start, end_) ->
+                     String.sub fnTypeStr start (end_ - start))
+              |> list);
+
+          (* Figure out the active parameter *)
+          let activeParameter = findActiveParameter ~argAtCursor ~args in
+
+          let paramUnlabelledArgCount = ref 0 in
+          Some
+            {
+              Protocol.signatures =
+                [
+                  {
+                    label = fnTypeStr;
+                    parameters =
+                      parameters
+                      |> List.map (fun (argLabel, start, end_) ->
+                             let paramArgCount = !paramUnlabelledArgCount in
+                             paramUnlabelledArgCount := paramArgCount + 1;
+                             let unlabelledArgCount = ref 0 in
+                             {
+                               Protocol.label = (start, end_);
+                               documentation =
+                                 (match
+                                    args
+                                    |> List.find_opt (fun (lbl, _) ->
+                                           let argCount = !unlabelledArgCount in
+                                           unlabelledArgCount := argCount + 1;
+                                           match (lbl, argLabel) with
+                                           | ( Asttypes.Optional l1,
+                                               Asttypes.Optional l2 )
+                                             when l1 = l2 ->
+                                             true
+                                           | Labelled l1, Labelled l2
+                                             when l1 = l2 ->
+                                             true
+                                           | Nolabel, Nolabel
+                                             when paramArgCount = argCount ->
+                                             true
+                                           | _ -> false)
+                                  with
+                                 | None ->
+                                   {Protocol.kind = "markdown"; value = ""}
+                                 | Some (_, labelTypExpr) ->
+                                   {
+                                     Protocol.kind = "markdown";
+                                     value =
+                                       docsForLabel ~supportsMarkdownLinks ~file
+                                         ~package labelTypExpr;
+                                   });
+                             });
+                    documentation =
+                      (match List.nth_opt docstring 0 with
+                      | None -> None
+                      | Some docs ->
+                        Some {Protocol.kind = "markdown"; value = docs});
+                  };
+                ];
+              activeSignature = Some 0;
+              activeParameter =
+                (match activeParameter with
+                | None -> Some (-1)
+                | activeParameter -> activeParameter);
+            }
+        | _ -> None)
+      | Some (_, ((`ConstructorExpr (lid, _) | `ConstructorPat (lid, _)) as cs))
+        -> (
+        if Debug.verbose () then
+          Printf.printf "[signature_help] Found constructor!\n";
+        match Cmt.loadFullCmtFromPath ~path with
+        | None ->
+          if Debug.verbose () then
+            Printf.printf "[signature_help] Could not load cmt\n";
+          None
+        | Some full -> (
+          let {file} = full in
+          let env = QueryEnv.fromFile file in
+          let constructorName = Longident.last lid.txt in
+          match
+            findConstructorArgs ~full ~env ~constructorName
+              {lid.loc with loc_start = lid.loc.loc_end}
+          with
+          | None ->
+            if Debug.verbose () then
+              Printf.printf "[signature_help] Did not find constructor '%s'\n"
+                constructorName;
+            None
+          | Some constructor ->
+            let argParts =
+              match constructor.args with
+              | Args [] -> None
+              | InlineRecord fields ->
+                let offset = ref 0 in
+                Some
+                  (`InlineRecord
+                    (fields
+                    |> List.map (fun (field : field) ->
+                           let startOffset = !offset in
+                           let argText =
+                             Printf.sprintf "%s%s: %s" field.fname.txt
+                               (if field.optional then "?" else "")
+                               (Shared.typeToString
+                                  (if field.optional then
+                                     Utils.unwrapIfOption field.typ
+                                   else field.typ))
+                           in
+                           let endOffset =
+                             startOffset + String.length argText
+                           in
+                           offset := endOffset + String.length ", ";
+                           (argText, field, (startOffset, endOffset)))))
+              | Args [(typ, _)] ->
+                Some
+                  (`SingleArg
+                    ( typ |> Shared.typeToString,
+                      docsForLabel ~file:full.file ~package:full.package
+                        ~supportsMarkdownLinks typ ))
+              | Args args ->
+                let offset = ref 0 in
+                Some
+                  (`TupleArg
+                    (args
+                    |> List.map (fun (typ, _) ->
+                           let startOffset = !offset in
+                           let argText = typ |> Shared.typeToString in
+                           let endOffset =
+                             startOffset + String.length argText
+                           in
+                           offset := endOffset + String.length ", ";
+                           ( argText,
+                             docsForLabel ~file:full.file ~package:full.package
+                               ~supportsMarkdownLinks typ,
+                             (startOffset, endOffset) ))))
+            in
+            let label =
+              constructor.name ^ "("
+              ^ (match argParts with
+                | None -> ""
+                | Some (`InlineRecord fields) ->
+                  "{"
+                  ^ (fields
+                    |> List.map (fun (argText, _, _) -> argText)
+                    |> String.concat ", ")
+                  ^ "}"
+                | Some (`SingleArg (arg, _)) -> arg
+                | Some (`TupleArg items) ->
+                  items
+                  |> List.map (fun (argText, _, _) -> argText)
+                  |> String.concat ", ")
+              ^ ")"
+            in
+            let activeParameter =
+              match cs with
+              | `ConstructorExpr (_, {pexp_desc = Pexp_tuple items}) -> (
+                let idx = ref 0 in
+                let tupleItemWithCursor =
+                  items
+                  |> List.find_map (fun (item : Parsetree.expression) ->
+                         let currentIndex = !idx in
+                         idx := currentIndex + 1;
+                         if locHasCursor item.pexp_loc then Some currentIndex
+                         else None)
+                in
+                match tupleItemWithCursor with
+                | None -> -1
+                | Some i -> i)
+              | `ConstructorExpr (_, {pexp_desc = Pexp_record (fields, _)}) -> (
+                let fieldNameWithCursor =
+                  fields
+                  |> List.find_map
+                       (fun
+                         (({loc; txt}, expr) :
+                           Longident.t Location.loc * Parsetree.expression)
+                       ->
+                         if
+                           posBeforeCursor >= Pos.ofLexing loc.loc_start
+                           && posBeforeCursor
+                              <= Pos.ofLexing expr.pexp_loc.loc_end
+                         then Some (Longident.last txt)
+                         else None)
+                in
+                match (fieldNameWithCursor, argParts) with
+                | Some fieldName, Some (`InlineRecord fields) ->
+                  let idx = ref 0 in
+                  let fieldIndex = ref (-1) in
+                  fields
+                  |> List.iter (fun (_, field, _) ->
+                         idx := !idx + 1;
+                         let currentIndex = !idx in
+                         if fieldName = field.fname.txt then
+                           fieldIndex := currentIndex
+                         else ());
+                  !fieldIndex
+                | _ -> -1)
+              | `ConstructorExpr (_, expr) when locHasCursor expr.pexp_loc -> 0
+              | `ConstructorPat (_, {ppat_desc = Ppat_tuple items}) -> (
+                let idx = ref 0 in
+                let tupleItemWithCursor =
+                  items
+                  |> List.find_map (fun (item : Parsetree.pattern) ->
+                         let currentIndex = !idx in
+                         idx := currentIndex + 1;
+                         if locHasCursor item.ppat_loc then Some currentIndex
+                         else None)
+                in
+                match tupleItemWithCursor with
+                | None -> -1
+                | Some i -> i)
+              | `ConstructorPat (_, {ppat_desc = Ppat_record (fields, _)}) -> (
+                let fieldNameWithCursor =
+                  fields
+                  |> List.find_map
+                       (fun
+                         (({loc; txt}, pat) :
+                           Longident.t Location.loc * Parsetree.pattern)
+                       ->
+                         if
+                           posBeforeCursor >= Pos.ofLexing loc.loc_start
+                           && posBeforeCursor
+                              <= Pos.ofLexing pat.ppat_loc.loc_end
+                         then Some (Longident.last txt)
+                         else None)
+                in
+                match (fieldNameWithCursor, argParts) with
+                | Some fieldName, Some (`InlineRecord fields) ->
+                  let idx = ref 0 in
+                  let fieldIndex = ref (-1) in
+                  fields
+                  |> List.iter (fun (_, field, _) ->
+                         idx := !idx + 1;
+                         let currentIndex = !idx in
+                         if fieldName = field.fname.txt then
+                           fieldIndex := currentIndex
+                         else ());
+                  !fieldIndex
+                | _ -> -1)
+              | `ConstructorPat (_, pat) when locHasCursor pat.ppat_loc -> 0
+              | _ -> -1
+            in
+
+            let constructorNameLength = String.length constructor.name in
+            let params =
+              match argParts with
+              | None -> []
+              | Some (`SingleArg (_, docstring)) ->
+                [
+                  {
+                    Protocol.label =
+                      (constructorNameLength + 1, String.length label - 1);
+                    documentation =
+                      {Protocol.kind = "markdown"; value = docstring};
+                  };
+                ]
+              | Some (`InlineRecord fields) ->
+                (* Account for leading '({' *)
+                let baseOffset = constructorNameLength + 2 in
+                {
+                  Protocol.label = (0, 0);
+                  documentation = {Protocol.kind = "markdown"; value = ""};
+                }
+                :: (fields
+                   |> List.map (fun (_, (field : field), (start, end_)) ->
+                          {
+                            Protocol.label =
+                              (baseOffset + start, baseOffset + end_);
+                            documentation =
+                              {
+                                Protocol.kind = "markdown";
+                                value = field.docstring |> String.concat "\n";
+                              };
+                          }))
+              | Some (`TupleArg items) ->
+                (* Account for leading '(' *)
+                let baseOffset = constructorNameLength + 1 in
+                items
+                |> List.map (fun (_, docstring, (start, end_)) ->
+                       {
+                         Protocol.label = (baseOffset + start, baseOffset + end_);
+                         documentation =
+                           {Protocol.kind = "markdown"; value = docstring};
+                       })
+            in
+            Some
+              {
+                Protocol.signatures =
+                  [
+                    {
+                      label;
+                      parameters = params;
+                      documentation =
+                        (match List.nth_opt constructor.docstring 0 with
+                        | None -> None
+                        | Some docs ->
+                          Some {Protocol.kind = "markdown"; value = docs});
+                    };
+                  ];
+                activeSignature = Some 0;
+                activeParameter = Some activeParameter;
+              }))
+      | _ -> None))
diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml
new file mode 100644
index 0000000000..2f3dbe08dd
--- /dev/null
+++ b/analysis/src/TypeUtils.ml
@@ -0,0 +1,1124 @@
+open SharedTypes
+
+let debugLogTypeArgContext {env; typeArgs; typeParams} =
+  Printf.sprintf "Type arg context. env: %s, typeArgs: %s, typeParams: %s\n"
+    (Debug.debugPrintEnv env)
+    (typeArgs |> List.map Shared.typeToString |> String.concat ", ")
+    (typeParams |> List.map Shared.typeToString |> String.concat ", ")
+
+(** Checks whether this type has any uninstantiated type parameters. *)
+let rec hasTvar (ty : Types.type_expr) : bool =
+  match ty.desc with
+  | Tvar _ -> true
+  | Tarrow (_, ty1, ty2, _) -> hasTvar ty1 || hasTvar ty2
+  | Ttuple tyl -> List.exists hasTvar tyl
+  | Tconstr (_, tyl, _) -> List.exists hasTvar tyl
+  | Tobject (ty, _) -> hasTvar ty
+  | Tfield (_, _, ty1, ty2) -> hasTvar ty1 || hasTvar ty2
+  | Tnil -> false
+  | Tlink ty -> hasTvar ty
+  | Tsubst ty -> hasTvar ty
+  | Tvariant {row_fields; _} ->
+    List.exists
+      (function
+        | _, Types.Rpresent (Some ty) -> hasTvar ty
+        | _, Reither (_, tyl, _, _) -> List.exists hasTvar tyl
+        | _ -> false)
+      row_fields
+  | Tunivar _ -> true
+  | Tpoly (ty, tyl) -> hasTvar ty || List.exists hasTvar tyl
+  | Tpackage (_, _, tyl) -> List.exists hasTvar tyl
+
+let findTypeViaLoc ~full ~debug (loc : Location.t) =
+  match References.getLocItem ~full ~pos:(Pos.ofLexing loc.loc_end) ~debug with
+  | Some {locType = Typed (_, typExpr, _)} -> Some typExpr
+  | _ -> None
+
+let rec pathFromTypeExpr (t : Types.type_expr) =
+  match t.desc with
+  | Tconstr (Pident {name = "function$"}, [t; _], _) -> pathFromTypeExpr t
+  | Tconstr (path, _typeArgs, _)
+  | Tlink {desc = Tconstr (path, _typeArgs, _)}
+  | Tsubst {desc = Tconstr (path, _typeArgs, _)}
+  | Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) ->
+    Some path
+  | _ -> None
+
+let printRecordFromFields ?name (fields : field list) =
+  (match name with
+  | None -> ""
+  | Some name -> "type " ^ name ^ " = ")
+  ^ "{"
+  ^ (fields
+    |> List.map (fun f -> f.fname.txt ^ ": " ^ Shared.typeToString f.typ)
+    |> String.concat ", ")
+  ^ "}"
+
+let rec extractedTypeToString ?(nameOnly = false) ?(inner = false) = function
+  | Tuple (_, _, typ) | Tpolyvariant {typeExpr = typ} | Tfunction {typ} ->
+    if inner then
+      try typ |> pathFromTypeExpr |> Option.get |> SharedTypes.pathIdentToString
+      with _ -> ""
+    else Shared.typeToString typ
+  | Trecord {definition; fields} ->
+    let name =
+      match definition with
+      | `TypeExpr typ -> (
+        try
+          typ |> pathFromTypeExpr |> Option.get |> SharedTypes.pathIdentToString
+        with _ -> "")
+      | `NameOnly name -> name
+    in
+    if inner || nameOnly then name else printRecordFromFields ~name fields
+  | Tbool _ -> "bool"
+  | Tstring _ -> "string"
+  | TtypeT _ -> "type t"
+  | Tarray (_, TypeExpr innerTyp) ->
+    "array<" ^ Shared.typeToString innerTyp ^ ">"
+  | Tarray (_, ExtractedType innerTyp) ->
+    "array<" ^ extractedTypeToString ~inner:true innerTyp ^ ">"
+  | Toption (_, TypeExpr innerTyp) ->
+    "option<" ^ Shared.typeToString innerTyp ^ ">"
+  | Tresult {okType; errorType} ->
+    "result<" ^ Shared.typeToString okType ^ ", "
+    ^ Shared.typeToString errorType
+    ^ ">"
+  | Toption (_, ExtractedType innerTyp) ->
+    "option<" ^ extractedTypeToString ~inner:true innerTyp ^ ">"
+  | Tpromise (_, innerTyp) -> "promise<" ^ Shared.typeToString innerTyp ^ ">"
+  | Tvariant {variantDecl; variantName} ->
+    if inner || nameOnly then variantName
+    else Shared.declToString variantName variantDecl
+  | TinlineRecord {fields} -> printRecordFromFields fields
+  | Texn _ -> "exn"
+
+let getExtractedType maybeRes =
+  match maybeRes with
+  | None -> None
+  | Some (extractedType, _) -> Some extractedType
+
+let instantiateType ~typeParams ~typeArgs (t : Types.type_expr) =
+  if typeParams = [] || typeArgs = [] then t
+  else
+    let rec applySub tp ta t =
+      match (tp, ta) with
+      | t1 :: tRest1, t2 :: tRest2 ->
+        if t1 = t then t2 else applySub tRest1 tRest2 t
+      | [], _ | _, [] -> t
+    in
+    let rec loop (t : Types.type_expr) =
+      match t.desc with
+      | Tlink t -> loop t
+      | Tvar _ -> applySub typeParams typeArgs t
+      | Tunivar _ -> t
+      | Tconstr (path, args, memo) ->
+        {t with desc = Tconstr (path, args |> List.map loop, memo)}
+      | Tsubst t -> loop t
+      | Tvariant rd -> {t with desc = Tvariant (rowDesc rd)}
+      | Tnil -> t
+      | Tarrow (lbl, t1, t2, c) ->
+        {t with desc = Tarrow (lbl, loop t1, loop t2, c)}
+      | Ttuple tl -> {t with desc = Ttuple (tl |> List.map loop)}
+      | Tobject (t, r) -> {t with desc = Tobject (loop t, r)}
+      | Tfield (n, k, t1, t2) -> {t with desc = Tfield (n, k, loop t1, loop t2)}
+      | Tpoly (t, []) -> loop t
+      | Tpoly (t, tl) -> {t with desc = Tpoly (loop t, tl |> List.map loop)}
+      | Tpackage (p, l, tl) ->
+        {t with desc = Tpackage (p, l, tl |> List.map loop)}
+    and rowDesc (rd : Types.row_desc) =
+      let row_fields =
+        rd.row_fields |> List.map (fun (l, rf) -> (l, rowField rf))
+      in
+      let row_more = loop rd.row_more in
+      let row_name =
+        match rd.row_name with
+        | None -> None
+        | Some (p, tl) -> Some (p, tl |> List.map loop)
+      in
+      {rd with row_fields; row_more; row_name}
+    and rowField (rf : Types.row_field) =
+      match rf with
+      | Rpresent None -> rf
+      | Rpresent (Some t) -> Rpresent (Some (loop t))
+      | Reither (b1, tl, b2, r) -> Reither (b1, tl |> List.map loop, b2, r)
+      | Rabsent -> Rabsent
+    in
+    loop t
+
+let instantiateType2 ?(typeArgContext : typeArgContext option)
+    (t : Types.type_expr) =
+  match typeArgContext with
+  | None | Some {typeArgs = []} | Some {typeParams = []} -> t
+  | Some {typeArgs; typeParams} ->
+    let rec applySub tp ta name =
+      match (tp, ta) with
+      | {Types.desc = Tvar (Some varName)} :: tRest1, t2 :: tRest2 ->
+        if varName = name then t2 else applySub tRest1 tRest2 name
+      | _ :: tRest1, _ :: tRest2 -> applySub tRest1 tRest2 name
+      | [], _ | _, [] -> t
+    in
+
+    let rec loop (t : Types.type_expr) =
+      match t.desc with
+      | Tlink t -> loop t
+      | Tvar (Some name) -> applySub typeParams typeArgs name
+      | Tvar _ -> t
+      | Tunivar _ -> t
+      | Tconstr (path, args, memo) ->
+        {t with desc = Tconstr (path, args |> List.map loop, memo)}
+      | Tsubst t -> loop t
+      | Tvariant rd -> {t with desc = Tvariant (rowDesc rd)}
+      | Tnil -> t
+      | Tarrow (lbl, t1, t2, c) ->
+        {t with desc = Tarrow (lbl, loop t1, loop t2, c)}
+      | Ttuple tl -> {t with desc = Ttuple (tl |> List.map loop)}
+      | Tobject (t, r) -> {t with desc = Tobject (loop t, r)}
+      | Tfield (n, k, t1, t2) -> {t with desc = Tfield (n, k, loop t1, loop t2)}
+      | Tpoly (t, []) -> loop t
+      | Tpoly (t, tl) -> {t with desc = Tpoly (loop t, tl |> List.map loop)}
+      | Tpackage (p, l, tl) ->
+        {t with desc = Tpackage (p, l, tl |> List.map loop)}
+    and rowDesc (rd : Types.row_desc) =
+      let row_fields =
+        rd.row_fields |> List.map (fun (l, rf) -> (l, rowField rf))
+      in
+      let row_more = loop rd.row_more in
+      let row_name =
+        match rd.row_name with
+        | None -> None
+        | Some (p, tl) -> Some (p, tl |> List.map loop)
+      in
+      {rd with row_fields; row_more; row_name}
+    and rowField (rf : Types.row_field) =
+      match rf with
+      | Rpresent None -> rf
+      | Rpresent (Some t) -> Rpresent (Some (loop t))
+      | Reither (b1, tl, b2, r) -> Reither (b1, tl |> List.map loop, b2, r)
+      | Rabsent -> Rabsent
+    in
+    loop t
+
+let rec extractRecordType ~env ~package (t : Types.type_expr) =
+  match t.desc with
+  | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractRecordType ~env ~package t1
+  | Tconstr (path, typeArgs, _) -> (
+    match References.digConstructor ~env ~package path with
+    | Some (env, ({item = {kind = Record fields}} as typ)) ->
+      let typeParams = typ.item.decl.type_params in
+      let fields =
+        fields
+        |> List.map (fun field ->
+               let fieldTyp =
+                 field.typ |> instantiateType ~typeParams ~typeArgs
+               in
+               {field with typ = fieldTyp})
+      in
+      Some (env, fields, typ)
+    | Some
+        ( env,
+          {item = {decl = {type_manifest = Some t1; type_params = typeParams}}}
+        ) ->
+      let t1 = t1 |> instantiateType ~typeParams ~typeArgs in
+      extractRecordType ~env ~package t1
+    | _ -> None)
+  | _ -> None
+
+let rec extractObjectType ~env ~package (t : Types.type_expr) =
+  match t.desc with
+  | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractObjectType ~env ~package t1
+  | Tobject (tObj, _) -> Some (env, tObj)
+  | Tconstr (path, typeArgs, _) -> (
+    match References.digConstructor ~env ~package path with
+    | Some
+        ( env,
+          {item = {decl = {type_manifest = Some t1; type_params = typeParams}}}
+        ) ->
+      let t1 = t1 |> instantiateType ~typeParams ~typeArgs in
+      extractObjectType ~env ~package t1
+    | _ -> None)
+  | _ -> None
+
+let rec extractFunctionType ~env ~package typ =
+  let rec loop ~env acc (t : Types.type_expr) =
+    match t.desc with
+    | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ~env acc t1
+    | Tarrow (label, tArg, tRet, _) -> loop ~env ((label, tArg) :: acc) tRet
+    | Tconstr (Pident {name = "function$"}, [t; _], _) ->
+      extractFunctionType ~env ~package t
+    | Tconstr (path, typeArgs, _) -> (
+      match References.digConstructor ~env ~package path with
+      | Some
+          ( env,
+            {
+              item = {decl = {type_manifest = Some t1; type_params = typeParams}};
+            } ) ->
+        let t1 = t1 |> instantiateType ~typeParams ~typeArgs in
+        loop ~env acc t1
+      | _ -> (List.rev acc, t))
+    | _ -> (List.rev acc, t)
+  in
+  loop ~env [] typ
+
+let maybeSetTypeArgCtx ?typeArgContextFromTypeManifest ~typeParams ~typeArgs env
+    =
+  match typeArgContextFromTypeManifest with
+  | Some typeArgContextFromTypeManifest -> Some typeArgContextFromTypeManifest
+  | None ->
+    let typeArgContext =
+      if List.length typeParams > 0 then Some {env; typeParams; typeArgs}
+      else None
+    in
+    (match typeArgContext with
+    | None -> ()
+    | Some typeArgContext ->
+      if Debug.verbose () then
+        Printf.printf "[#type_arg_ctx]--> setting new type arg ctx: %s"
+          (debugLogTypeArgContext typeArgContext));
+    typeArgContext
+
+(* TODO(env-stuff) Maybe this could be removed entirely if we can guarantee that we don't have to look up functions from in here. *)
+let rec extractFunctionType2 ?typeArgContext ~env ~package typ =
+  let rec loop ?typeArgContext ~env acc (t : Types.type_expr) =
+    match t.desc with
+    | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ?typeArgContext ~env acc t1
+    | Tarrow (label, tArg, tRet, _) ->
+      loop ?typeArgContext ~env ((label, tArg) :: acc) tRet
+    | Tconstr (Pident {name = "function$"}, [t; _], _) ->
+      extractFunctionType2 ?typeArgContext ~env ~package t
+    | Tconstr (path, typeArgs, _) -> (
+      match References.digConstructor ~env ~package path with
+      | Some
+          ( env,
+            {
+              item = {decl = {type_manifest = Some t1; type_params = typeParams}};
+            } ) ->
+        let typeArgContext = maybeSetTypeArgCtx ~typeParams ~typeArgs env in
+        loop ?typeArgContext ~env acc t1
+      | _ -> (List.rev acc, t, typeArgContext))
+    | _ -> (List.rev acc, t, typeArgContext)
+  in
+  loop ?typeArgContext ~env [] typ
+
+let rec extractType ?(printOpeningDebug = true)
+    ?(typeArgContext : typeArgContext option)
+    ?(typeArgContextFromTypeManifest : typeArgContext option) ~env ~package
+    (t : Types.type_expr) =
+  let maybeSetTypeArgCtx = maybeSetTypeArgCtx ?typeArgContextFromTypeManifest in
+  if Debug.verbose () && printOpeningDebug then
+    Printf.printf
+      "[extract_type]--> starting extraction of type: %s, in env: %s. Has type \
+       arg ctx: %b\n"
+      (Shared.typeToString t) (Debug.debugPrintEnv env)
+      (Option.is_some typeArgContext);
+  (match typeArgContext with
+  | None -> ()
+  | Some typeArgContext ->
+    if Debug.verbose () && printOpeningDebug then
+      Printf.printf "[extract_type]--> %s"
+        (debugLogTypeArgContext typeArgContext));
+  let instantiateType = instantiateType2 in
+  match t.desc with
+  | Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
+    extractType ?typeArgContext ~printOpeningDebug:false ~env ~package t1
+  | Tconstr (Path.Pident {name = "option"}, [payloadTypeExpr], _) ->
+    Some (Toption (env, TypeExpr payloadTypeExpr), typeArgContext)
+  | Tconstr (Path.Pident {name = "promise"}, [payloadTypeExpr], _) ->
+    Some (Tpromise (env, payloadTypeExpr), typeArgContext)
+  | Tconstr (Path.Pident {name = "array"}, [payloadTypeExpr], _) ->
+    Some (Tarray (env, TypeExpr payloadTypeExpr), typeArgContext)
+  | Tconstr (Path.Pident {name = "result"}, [okType; errorType], _) ->
+    Some (Tresult {env; okType; errorType}, typeArgContext)
+  | Tconstr (Path.Pident {name = "bool"}, [], _) ->
+    Some (Tbool env, typeArgContext)
+  | Tconstr (Path.Pident {name = "string"}, [], _) ->
+    Some (Tstring env, typeArgContext)
+  | Tconstr (Path.Pident {name = "exn"}, [], _) ->
+    Some (Texn env, typeArgContext)
+  | Tconstr (Pident {name = "function$"}, [t; _], _) -> (
+    match extractFunctionType2 ?typeArgContext t ~env ~package with
+    | args, tRet, typeArgContext when args <> [] ->
+      Some
+        ( Tfunction {env; args; typ = t; uncurried = true; returnType = tRet},
+          typeArgContext )
+    | _args, _tRet, _typeArgContext -> None)
+  | Tarrow _ -> (
+    match extractFunctionType2 ?typeArgContext t ~env ~package with
+    | args, tRet, typeArgContext when args <> [] ->
+      Some
+        ( Tfunction {env; args; typ = t; uncurried = false; returnType = tRet},
+          typeArgContext )
+    | _args, _tRet, _typeArgContext -> None)
+  | Tconstr (path, typeArgs, _) -> (
+    if Debug.verbose () then
+      Printf.printf "[extract_type]--> digging for type %s in %s\n"
+        (Path.name path) (Debug.debugPrintEnv env);
+    match References.digConstructor ~env ~package path with
+    | Some
+        ( envFromDeclaration,
+          {item = {decl = {type_manifest = Some t1; type_params}}} ) ->
+      if Debug.verbose () then
+        print_endline "[extract_type]--> found type manifest";
+
+      (* Type manifests inherit the last type args ctx that wasn't for a type manifest.
+         This is because the manifest itself doesn't have type args and an env that can
+         be used to instantiate. *)
+      let typeArgContext =
+        maybeSetTypeArgCtx ~typeParams:type_params ~typeArgs env
+      in
+      t1
+      |> extractType ?typeArgContextFromTypeManifest:typeArgContext
+           ~env:envFromDeclaration ~package
+    | Some (envFromItem, {name; item = {decl; kind = Type.Variant constructors}})
+      ->
+      if Debug.verbose () then print_endline "[extract_type]--> found variant";
+      let typeArgContext =
+        maybeSetTypeArgCtx ~typeParams:decl.type_params ~typeArgs env
+      in
+      Some
+        ( Tvariant
+            {
+              env = envFromItem;
+              constructors;
+              variantName = name.txt;
+              variantDecl = decl;
+            },
+          typeArgContext )
+    | Some (envFromDeclaration, {item = {kind = Record fields; decl}}) ->
+      if Debug.verbose () then print_endline "[extract_type]--> found record";
+      (* Need to create a new type arg context here because we're sending along a type expr that might have type vars. *)
+      let typeArgContext =
+        maybeSetTypeArgCtx ~typeParams:decl.type_params ~typeArgs env
+      in
+      Some
+        ( Trecord {env = envFromDeclaration; fields; definition = `TypeExpr t},
+          typeArgContext )
+    | Some (envFromDeclaration, {item = {name = "t"; decl = {type_params}}}) ->
+      let typeArgContext =
+        maybeSetTypeArgCtx ~typeParams:type_params ~typeArgs env
+      in
+      Some (TtypeT {env = envFromDeclaration; path}, typeArgContext)
+    | None ->
+      if Debug.verbose () then
+        print_endline "[extract_type]--> found nothing when digging";
+      None
+    | _ ->
+      if Debug.verbose () then
+        print_endline "[extract_type]--> found something else when digging";
+      None)
+  | Ttuple expressions -> Some (Tuple (env, expressions, t), typeArgContext)
+  | Tvariant {row_fields} ->
+    let constructors =
+      row_fields
+      |> List.map (fun (label, field) ->
+             {
+               name = label;
+               displayName = Utils.printMaybeExoticIdent ~allowUident:true label;
+               args =
+                 (* Multiple arguments are represented as a Ttuple, while a single argument is just the type expression itself. *)
+                 (match field with
+                 | Types.Rpresent (Some typeExpr) -> (
+                   match typeExpr.desc with
+                   | Ttuple args -> args
+                   | _ -> [typeExpr])
+                 | _ -> []);
+             })
+    in
+    Some (Tpolyvariant {env; constructors; typeExpr = t}, typeArgContext)
+  | Tvar (Some varName) -> (
+    if Debug.verbose () then
+      Printf.printf
+        "[extract_type]--> found type variable: '%s. Trying to instantiate %s"
+        varName
+        (match typeArgContext with
+        | None -> "with no type args ctx\n"
+        | Some typeArgContext ->
+          Printf.sprintf "with %s" (debugLogTypeArgContext typeArgContext));
+
+    let instantiated = t |> instantiateType ?typeArgContext in
+    let rec extractInstantiated t =
+      match t.Types.desc with
+      | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractInstantiated t1
+      | _ -> t
+    in
+    match extractInstantiated instantiated with
+    | {desc = Tvar _} ->
+      if Debug.verbose () then
+        Printf.printf "[extract_type]--> could not instantiate '%s. Skipping.\n"
+          varName;
+      None
+    | _ ->
+      if Debug.verbose () then
+        Printf.printf
+          "[extract_type]--> SUCCEEDED instantiation, new type is: %s\n"
+          (Shared.typeToString instantiated);
+
+      (* Use the env from instantiation if we managed to instantiate the type param *)
+      let nextEnv =
+        match typeArgContext with
+        | Some {env} -> env
+        | None -> env
+      in
+      instantiated |> extractType ?typeArgContext ~env:nextEnv ~package)
+  | _ ->
+    if Debug.verbose () then print_endline "[extract_type]--> miss";
+    None
+
+let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug =
+  match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with
+  | Some {locType = Typed (_, typExpr, _)} -> (
+    match extractFunctionType ~env ~package:full.package typExpr with
+    | args, tRet when args <> [] -> Some tRet
+    | _ -> None)
+  | _ -> None
+
+type builtinType =
+  | Array
+  | Option
+  | String
+  | Int
+  | Float
+  | Promise
+  | List
+  | Result
+  | Lazy
+  | Char
+  | RegExp
+
+type pipeCompletionType =
+  | Builtin of builtinType * Types.type_expr
+  | TypExpr of Types.type_expr
+
+let getBuiltinFromTypePath path =
+  match path with
+  | Path.Pident _ -> (
+    match Path.name path with
+    | "array" -> Some Array
+    | "option" -> Some Option
+    | "string" -> Some String
+    | "int" -> Some Int
+    | "float" -> Some Float
+    | "promise" -> Some Promise
+    | "list" -> Some List
+    | "result" -> Some Result
+    | "lazy_t" -> Some Lazy
+    | "char" -> Some Char
+    | _ -> None)
+  | Pdot (Pdot (Pident m, "Re", _), "t", _) when Ident.name m = "Js" ->
+    Some RegExp
+  | Pdot (Pident id, "result", _) when Ident.name id = "Pervasives" ->
+    Some Result
+  | _ -> None
+
+let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "")
+    (t : Types.type_expr) =
+  match t.desc with
+  | Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
+    digToRelevantTemplateNameType ~suffix ~env ~package t1
+  | Tconstr (Path.Pident {name = "option"}, [t1], _) ->
+    digToRelevantTemplateNameType ~suffix ~env ~package t1
+  | Tconstr (Path.Pident {name = "array"}, [t1], _) ->
+    digToRelevantTemplateNameType ~suffix:"s" ~env ~package t1
+  | Tconstr (path, _, _) -> (
+    match References.digConstructor ~env ~package path with
+    | Some (env, {item = {decl = {type_manifest = Some typ}}}) ->
+      digToRelevantTemplateNameType ~suffix ~env ~package typ
+    | _ -> (t, suffix, env))
+  | _ -> (t, suffix, env)
+
+let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full
+    (t : Types.type_expr) =
+  let builtin =
+    match t |> pathFromTypeExpr with
+    | Some path -> path |> getBuiltinFromTypePath
+    | None -> None
+  in
+  match builtin with
+  | Some builtin -> (env, Builtin (builtin, t))
+  | None -> (
+    (* If the type we're completing on is a type parameter, we won't be able to
+       do completion unless we know what that type parameter is compiled as.
+       This attempts to look up the compiled type for that type parameter by
+       looking for compiled information at the loc of that expression. *)
+    let typFromLoc =
+      match t with
+      | {Types.desc = Tvar _} -> (
+        match findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false with
+        | None -> None
+        | Some typFromLoc -> Some typFromLoc)
+      | _ -> None
+    in
+    match typFromLoc with
+    | Some typFromLoc ->
+      typFromLoc |> resolveTypeForPipeCompletion ~lhsLoc ~env ~package ~full
+    | None ->
+      let rec digToRelevantType ~env ~package (t : Types.type_expr) =
+        match t.desc with
+        | Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
+          digToRelevantType ~env ~package t1
+        (* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *)
+        | Tconstr (path, _, _) when path |> Path.last = "t" -> (env, TypExpr t)
+        | Tconstr (path, _, _) -> (
+          match References.digConstructor ~env ~package path with
+          | Some (env, {item = {decl = {type_manifest = Some typ}}}) ->
+            digToRelevantType ~env ~package typ
+          | _ -> (env, TypExpr t))
+        | _ -> (env, TypExpr t)
+      in
+      digToRelevantType ~env ~package t)
+
+let extractTypeFromResolvedType (typ : Type.t) ~env ~full =
+  match typ.kind with
+  | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items)))
+  | Record fields ->
+    Some (Trecord {env; fields; definition = `NameOnly typ.name})
+  | Variant constructors ->
+    Some
+      (Tvariant
+         {env; constructors; variantName = typ.name; variantDecl = typ.decl})
+  | Abstract _ | Open -> (
+    match typ.decl.type_manifest with
+    | None -> None
+    | Some t -> t |> extractType ~env ~package:full.package |> getExtractedType)
+
+(** The context we just came from as we resolve the nested structure. *)
+type ctx = Rfield of string  (** A record field of name *)
+
+let rec resolveNested ?typeArgContext ~env ~full ~nested ?ctx
+    (typ : completionType) =
+  let extractType = extractType ?typeArgContext in
+  if Debug.verbose () then
+    Printf.printf
+      "[nested]--> running nested in env: %s. Has type arg ctx: %b\n"
+      (Debug.debugPrintEnv env)
+      (Option.is_some typeArgContext);
+  (match typeArgContext with
+  | None -> ()
+  | Some typeArgContext ->
+    if Debug.verbose () then
+      Printf.printf "[nested]--> %s" (debugLogTypeArgContext typeArgContext));
+  match nested with
+  | [] ->
+    if Debug.verbose () then
+      print_endline "[nested]--> reached end of pattern, returning type";
+    Some
+      ( typ,
+        env,
+        (match ctx with
+        | None -> None
+        | Some (Rfield fieldName) ->
+          Some (Completable.CameFromRecordField fieldName)),
+        typeArgContext )
+  | patternPath :: nested -> (
+    match (patternPath, typ) with
+    | Completable.NTupleItem {itemNum}, Tuple (env, tupleItems, _) -> (
+      if Debug.verbose () then
+        print_endline "[nested]--> trying to move into tuple";
+      match List.nth_opt tupleItems itemNum with
+      | None ->
+        if Debug.verbose () then
+          print_endline "[nested]--> tuple element not found";
+        None
+      | Some typ ->
+        typ
+        |> extractType ~env ~package:full.package
+        |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+               typ |> resolveNested ?typeArgContext ~env ~full ~nested))
+    | ( NFollowRecordField {fieldName},
+        (TinlineRecord {env; fields} | Trecord {env; fields}) ) -> (
+      if Debug.verbose () then
+        print_endline "[nested]--> trying to move into record field";
+      match
+        fields
+        |> List.find_opt (fun (field : field) -> field.fname.txt = fieldName)
+      with
+      | None ->
+        if Debug.verbose () then
+          print_endline "[nested]--> did not find record field";
+        None
+      | Some {typ; optional} ->
+        if Debug.verbose () then
+          print_endline "[nested]--> found record field type";
+        let typ = if optional then Utils.unwrapIfOption typ else typ in
+
+        if Debug.verbose () then
+          Printf.printf "[nested]--> extracting from type %s in env %s\n"
+            (Shared.typeToString typ) (Debug.debugPrintEnv env);
+        typ
+        |> extractType ~env ~package:full.package
+        |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+               typ
+               |> resolveNested ?typeArgContext ~ctx:(Rfield fieldName) ~env
+                    ~full ~nested))
+    | NRecordBody {seenFields}, Trecord {env; definition = `TypeExpr typeExpr}
+      ->
+      typeExpr
+      |> extractType ~env ~package:full.package
+      |> Option.map (fun (typ, typeArgContext) ->
+             ( typ,
+               env,
+               Some (Completable.RecordField {seenFields}),
+               typeArgContext ))
+    | ( NRecordBody {seenFields},
+        (Trecord {env; definition = `NameOnly _} as extractedType) ) ->
+      Some
+        ( extractedType,
+          env,
+          Some (Completable.RecordField {seenFields}),
+          typeArgContext )
+    | NRecordBody {seenFields}, TinlineRecord {env; fields} ->
+      Some
+        ( TinlineRecord {fields; env},
+          env,
+          Some (Completable.RecordField {seenFields}),
+          typeArgContext )
+    | ( NVariantPayload {constructorName = "Some"; itemNum = 0},
+        Toption (env, ExtractedType typ) ) ->
+      if Debug.verbose () then
+        print_endline "[nested]--> moving into option Some";
+      typ |> resolveNested ~env ~full ~nested
+    | ( NVariantPayload {constructorName = "Some"; itemNum = 0},
+        Toption (env, TypeExpr typ) ) ->
+      if Debug.verbose () then
+        print_endline "[nested]--> moving into option Some";
+      typ
+      |> extractType ~env ~package:full.package
+      |> Utils.Option.flatMap (fun (t, typeArgContext) ->
+             t |> resolveNested ?typeArgContext ~env ~full ~nested)
+    | NVariantPayload {constructorName = "Ok"; itemNum = 0}, Tresult {okType} ->
+      if Debug.verbose () then print_endline "[nested]--> moving into result Ok";
+      okType
+      |> extractType ~env ~package:full.package
+      |> Utils.Option.flatMap (fun (t, typeArgContext) ->
+             t |> resolveNested ?typeArgContext ~env ~full ~nested)
+    | ( NVariantPayload {constructorName = "Error"; itemNum = 0},
+        Tresult {errorType} ) ->
+      if Debug.verbose () then
+        print_endline "[nested]--> moving into result Error";
+      errorType
+      |> extractType ~env ~package:full.package
+      |> Utils.Option.flatMap (fun (t, typeArgContext) ->
+             t |> resolveNested ?typeArgContext ~env ~full ~nested)
+    | NVariantPayload {constructorName; itemNum}, Tvariant {env; constructors}
+      -> (
+      if Debug.verbose () then
+        Printf.printf
+          "[nested]--> trying to move into variant payload $%i of constructor \
+           '%s'\n"
+          itemNum constructorName;
+      match
+        constructors
+        |> List.find_opt (fun (c : Constructor.t) ->
+               c.cname.txt = constructorName)
+      with
+      | Some {args = Args args} -> (
+        if Debug.verbose () then
+          print_endline "[nested]--> found constructor (Args type)";
+        match List.nth_opt args itemNum with
+        | None ->
+          if Debug.verbose () then
+            print_endline "[nested]--> did not find relevant args num";
+          None
+        | Some (typ, _) ->
+          if Debug.verbose () then
+            Printf.printf "[nested]--> found arg of type: %s\n"
+              (Shared.typeToString typ);
+
+          typ
+          |> extractType ~env ~package:full.package
+          |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+                 if Debug.verbose () then
+                   Printf.printf
+                     "[nested]--> extracted %s, continuing descent of %i items\n"
+                     (extractedTypeToString typ)
+                     (List.length nested);
+                 typ |> resolveNested ?typeArgContext ~env ~full ~nested))
+      | Some {args = InlineRecord fields} when itemNum = 0 ->
+        if Debug.verbose () then
+          print_endline "[nested]--> found constructor (inline record)";
+        TinlineRecord {env; fields} |> resolveNested ~env ~full ~nested
+      | _ -> None)
+    | ( NPolyvariantPayload {constructorName; itemNum},
+        Tpolyvariant {env; constructors} ) -> (
+      match
+        constructors
+        |> List.find_opt (fun (c : polyVariantConstructor) ->
+               c.name = constructorName)
+      with
+      | None -> None
+      | Some constructor -> (
+        match List.nth_opt constructor.args itemNum with
+        | None -> None
+        | Some typ ->
+          typ
+          |> extractType ~env ~package:full.package
+          |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+                 typ |> resolveNested ?typeArgContext ~env ~full ~nested)))
+    | NArray, Tarray (env, ExtractedType typ) ->
+      typ |> resolveNested ~env ~full ~nested
+    | NArray, Tarray (env, TypeExpr typ) ->
+      typ
+      |> extractType ~env ~package:full.package
+      |> Utils.Option.flatMap (fun (typ, typeArgContext) ->
+             typ |> resolveNested ?typeArgContext ~env ~full ~nested)
+    | _ -> None)
+
+let findTypeOfRecordField fields ~fieldName =
+  match
+    fields |> List.find_opt (fun (field : field) -> field.fname.txt = fieldName)
+  with
+  | None -> None
+  | Some {typ; optional} ->
+    let typ = if optional then Utils.unwrapIfOption typ else typ in
+    Some typ
+
+let findTypeOfConstructorArg constructors ~constructorName ~payloadNum ~env =
+  match
+    constructors
+    |> List.find_opt (fun (c : Constructor.t) -> c.cname.txt = constructorName)
+  with
+  | Some {args = Args args} -> (
+    match List.nth_opt args payloadNum with
+    | None -> None
+    | Some (typ, _) -> Some (TypeExpr typ))
+  | Some {args = InlineRecord fields} when payloadNum = 0 ->
+    Some (ExtractedType (TinlineRecord {env; fields}))
+  | _ -> None
+
+let findTypeOfPolyvariantArg constructors ~constructorName ~payloadNum =
+  match
+    constructors
+    |> List.find_opt (fun (c : polyVariantConstructor) ->
+           c.name = constructorName)
+  with
+  | Some {args} -> (
+    match List.nth_opt args payloadNum with
+    | None -> None
+    | Some typ -> Some typ)
+  | None -> None
+
+let rec resolveNestedPatternPath (typ : innerType) ~env ~full ~nested =
+  if Debug.verbose () then print_endline "[nested_pattern_path]";
+  let t =
+    match typ with
+    | TypeExpr t ->
+      t |> extractType ~env ~package:full.package |> getExtractedType
+    | ExtractedType t -> Some t
+  in
+  match nested with
+  | [] -> None
+  | [finalPatternPath] -> (
+    match t with
+    | None -> None
+    | Some completionType -> (
+      match (finalPatternPath, completionType) with
+      | ( Completable.NFollowRecordField {fieldName},
+          (TinlineRecord {fields} | Trecord {fields}) ) -> (
+        match fields |> findTypeOfRecordField ~fieldName with
+        | None -> None
+        | Some typ -> Some (TypeExpr typ, env))
+      | NTupleItem {itemNum}, Tuple (env, tupleItems, _) -> (
+        match List.nth_opt tupleItems itemNum with
+        | None -> None
+        | Some typ -> Some (TypeExpr typ, env))
+      | NVariantPayload {constructorName; itemNum}, Tvariant {env; constructors}
+        -> (
+        match
+          constructors
+          |> findTypeOfConstructorArg ~constructorName ~payloadNum:itemNum ~env
+        with
+        | Some typ -> Some (typ, env)
+        | None -> None)
+      | ( NPolyvariantPayload {constructorName; itemNum},
+          Tpolyvariant {env; constructors} ) -> (
+        match
+          constructors
+          |> findTypeOfPolyvariantArg ~constructorName ~payloadNum:itemNum
+        with
+        | Some typ -> Some (TypeExpr typ, env)
+        | None -> None)
+      | ( NVariantPayload {constructorName = "Some"; itemNum = 0},
+          Toption (env, typ) ) ->
+        Some (typ, env)
+      | ( NVariantPayload {constructorName = "Ok"; itemNum = 0},
+          Tresult {env; okType} ) ->
+        Some (TypeExpr okType, env)
+      | ( NVariantPayload {constructorName = "Error"; itemNum = 0},
+          Tresult {env; errorType} ) ->
+        Some (TypeExpr errorType, env)
+      | NArray, Tarray (env, typ) -> Some (typ, env)
+      | _ -> None))
+  | patternPath :: nested -> (
+    match t with
+    | None -> None
+    | Some completionType -> (
+      match (patternPath, completionType) with
+      | ( Completable.NFollowRecordField {fieldName},
+          (TinlineRecord {env; fields} | Trecord {env; fields}) ) -> (
+        match fields |> findTypeOfRecordField ~fieldName with
+        | None -> None
+        | Some typ ->
+          typ
+          |> extractType ~env ~package:full.package
+          |> getExtractedType
+          |> Utils.Option.flatMap (fun typ ->
+                 ExtractedType typ
+                 |> resolveNestedPatternPath ~env ~full ~nested))
+      | NTupleItem {itemNum}, Tuple (env, tupleItems, _) -> (
+        match List.nth_opt tupleItems itemNum with
+        | None -> None
+        | Some typ ->
+          typ
+          |> extractType ~env ~package:full.package
+          |> getExtractedType
+          |> Utils.Option.flatMap (fun typ ->
+                 ExtractedType typ
+                 |> resolveNestedPatternPath ~env ~full ~nested))
+      | NVariantPayload {constructorName; itemNum}, Tvariant {env; constructors}
+        -> (
+        match
+          constructors
+          |> findTypeOfConstructorArg ~constructorName ~payloadNum:itemNum ~env
+        with
+        | Some typ -> typ |> resolveNestedPatternPath ~env ~full ~nested
+        | None -> None)
+      | ( NPolyvariantPayload {constructorName; itemNum},
+          Tpolyvariant {env; constructors} ) -> (
+        match
+          constructors
+          |> findTypeOfPolyvariantArg ~constructorName ~payloadNum:itemNum
+        with
+        | Some typ ->
+          TypeExpr typ |> resolveNestedPatternPath ~env ~full ~nested
+        | None -> None)
+      | ( NVariantPayload {constructorName = "Some"; itemNum = 0},
+          Toption (env, typ) ) ->
+        typ |> resolveNestedPatternPath ~env ~full ~nested
+      | ( NVariantPayload {constructorName = "Ok"; itemNum = 0},
+          Tresult {env; okType} ) ->
+        TypeExpr okType |> resolveNestedPatternPath ~env ~full ~nested
+      | ( NVariantPayload {constructorName = "Error"; itemNum = 0},
+          Tresult {env; errorType} ) ->
+        TypeExpr errorType |> resolveNestedPatternPath ~env ~full ~nested
+      | NArray, Tarray (env, typ) ->
+        typ |> resolveNestedPatternPath ~env ~full ~nested
+      | _ -> None))
+
+let getArgs ~env (t : Types.type_expr) ~full =
+  let rec getArgsLoop ~env (t : Types.type_expr) ~full ~currentArgumentPosition
+      =
+    match t.desc with
+    | Tlink t1
+    | Tsubst t1
+    | Tpoly (t1, [])
+    | Tconstr (Pident {name = "function$"}, [t1; _], _) ->
+      getArgsLoop ~full ~env ~currentArgumentPosition t1
+    | Tarrow (Labelled l, tArg, tRet, _) ->
+      (SharedTypes.Completable.Labelled l, tArg)
+      :: getArgsLoop ~full ~env ~currentArgumentPosition tRet
+    | Tarrow (Optional l, tArg, tRet, _) ->
+      (Optional l, tArg) :: getArgsLoop ~full ~env ~currentArgumentPosition tRet
+    | Tarrow (Nolabel, tArg, tRet, _) ->
+      (Unlabelled {argumentPosition = currentArgumentPosition}, tArg)
+      :: getArgsLoop ~full ~env
+           ~currentArgumentPosition:(currentArgumentPosition + 1)
+           tRet
+    | Tconstr (path, typeArgs, _) -> (
+      match References.digConstructor ~env ~package:full.package path with
+      | Some
+          ( env,
+            {
+              item = {decl = {type_manifest = Some t1; type_params = typeParams}};
+            } ) ->
+        let t1 = t1 |> instantiateType ~typeParams ~typeArgs in
+        getArgsLoop ~full ~env ~currentArgumentPosition t1
+      | _ -> [])
+    | _ -> []
+  in
+  t |> getArgsLoop ~env ~full ~currentArgumentPosition:0
+
+let typeIsUnit (typ : Types.type_expr) =
+  match typ.desc with
+  | Tconstr (Pident id, _typeArgs, _)
+  | Tlink {desc = Tconstr (Pident id, _typeArgs, _)}
+  | Tsubst {desc = Tconstr (Pident id, _typeArgs, _)}
+  | Tpoly ({desc = Tconstr (Pident id, _typeArgs, _)}, [])
+    when Ident.name id = "unit" ->
+    true
+  | _ -> false
+
+let rec contextPathFromCoreType (coreType : Parsetree.core_type) =
+  match coreType.ptyp_desc with
+  | Ptyp_constr ({txt = Lident "option"}, [innerTyp]) ->
+    innerTyp |> contextPathFromCoreType
+    |> Option.map (fun innerTyp -> Completable.CPOption innerTyp)
+  | Ptyp_constr ({txt = Lident "array"}, [innerTyp]) ->
+    Some (Completable.CPArray (innerTyp |> contextPathFromCoreType))
+  | Ptyp_constr (lid, _) ->
+    Some
+      (CPId
+         {
+           path = lid.txt |> Utils.flattenLongIdent;
+           completionContext = Type;
+           loc = lid.loc;
+         })
+  | _ -> None
+
+let unwrapCompletionTypeIfOption (t : SharedTypes.completionType) =
+  match t with
+  | Toption (_, ExtractedType unwrapped) -> unwrapped
+  | _ -> t
+
+module Codegen = struct
+  let mkFailWithExp () =
+    Ast_helper.Exp.apply
+      (Ast_helper.Exp.ident {txt = Lident "failwith"; loc = Location.none})
+      [(Nolabel, Ast_helper.Exp.constant (Pconst_string ("TODO", None)))]
+
+  let mkConstructPat ?payload name =
+    Ast_helper.Pat.construct
+      {Asttypes.txt = Longident.Lident name; loc = Location.none}
+      payload
+
+  let mkTagPat ?payload name = Ast_helper.Pat.variant name payload
+
+  let any () = Ast_helper.Pat.any ()
+
+  let rec extractedTypeToExhaustivePatterns ~env ~full extractedType =
+    match extractedType with
+    | Tvariant v ->
+      Some
+        (v.constructors
+        |> List.map (fun (c : SharedTypes.Constructor.t) ->
+               mkConstructPat
+                 ?payload:
+                   (match c.args with
+                   | Args [] -> None
+                   | _ -> Some (any ()))
+                 c.cname.txt))
+    | Tpolyvariant v ->
+      Some
+        (v.constructors
+        |> List.map (fun (c : SharedTypes.polyVariantConstructor) ->
+               mkTagPat
+                 ?payload:
+                   (match c.args with
+                   | [] -> None
+                   | _ -> Some (any ()))
+                 c.displayName))
+    | Toption (_, innerType) ->
+      let extractedType =
+        match innerType with
+        | ExtractedType t -> Some t
+        | TypeExpr t ->
+          extractType t ~env ~package:full.package |> getExtractedType
+      in
+      let expandedBranches =
+        match extractedType with
+        | None -> []
+        | Some extractedType -> (
+          match extractedTypeToExhaustivePatterns ~env ~full extractedType with
+          | None -> []
+          | Some patterns -> patterns)
+      in
+      Some
+        ([
+           mkConstructPat "None";
+           mkConstructPat ~payload:(Ast_helper.Pat.any ()) "Some";
+         ]
+        @ (expandedBranches
+          |> List.map (fun (pat : Parsetree.pattern) ->
+                 mkConstructPat ~payload:pat "Some")))
+    | Tresult {okType; errorType} ->
+      let extractedOkType =
+        okType |> extractType ~env ~package:full.package |> getExtractedType
+      in
+      let extractedErrorType =
+        errorType |> extractType ~env ~package:full.package |> getExtractedType
+      in
+      let expandedOkBranches =
+        match extractedOkType with
+        | None -> []
+        | Some extractedType -> (
+          match extractedTypeToExhaustivePatterns ~env ~full extractedType with
+          | None -> []
+          | Some patterns -> patterns)
+      in
+      let expandedErrorBranches =
+        match extractedErrorType with
+        | None -> []
+        | Some extractedType -> (
+          match extractedTypeToExhaustivePatterns ~env ~full extractedType with
+          | None -> []
+          | Some patterns -> patterns)
+      in
+      Some
+        ((expandedOkBranches
+         |> List.map (fun (pat : Parsetree.pattern) ->
+                mkConstructPat ~payload:pat "Ok"))
+        @ (expandedErrorBranches
+          |> List.map (fun (pat : Parsetree.pattern) ->
+                 mkConstructPat ~payload:pat "Error")))
+    | Tbool _ -> Some [mkConstructPat "true"; mkConstructPat "false"]
+    | _ -> None
+
+  let extractedTypeToExhaustiveCases ~env ~full extractedType =
+    let patterns = extractedTypeToExhaustivePatterns ~env ~full extractedType in
+
+    match patterns with
+    | None -> None
+    | Some patterns ->
+      Some
+        (patterns
+        |> List.map (fun (pat : Parsetree.pattern) ->
+               Ast_helper.Exp.case pat (mkFailWithExp ())))
+end
+
+let getPathRelativeToEnv ~debug ~(env : QueryEnv.t) ~envFromItem path =
+  match path with
+  | _ :: pathRev ->
+    (* type path is relative to the completion environment
+       express it from the root of the file *)
+    let found, pathFromEnv =
+      QueryEnv.pathFromEnv envFromItem (List.rev pathRev)
+    in
+    if debug then
+      Printf.printf "CPPipe pathFromEnv:%s found:%b\n"
+        (pathFromEnv |> String.concat ".")
+        found;
+    if pathFromEnv = [] then None
+    else if
+      env.file.moduleName <> envFromItem.file.moduleName && found
+      (* If the module names are different, then one needs to qualify the path.
+         But only if the path belongs to the env from completion *)
+    then Some (envFromItem.file.moduleName :: pathFromEnv)
+    else Some pathFromEnv
+  | _ -> None
+
+let removeOpensFromCompletionPath ~rawOpens ~package completionPath =
+  let rec removeRawOpen rawOpen modulePath =
+    match (rawOpen, modulePath) with
+    | [_], _ -> Some modulePath
+    | s :: inner, first :: restPath when s = first ->
+      removeRawOpen inner restPath
+    | _ -> None
+  in
+  let rec removeRawOpens rawOpens modulePath =
+    match rawOpens with
+    | rawOpen :: restOpens -> (
+      let newModulePath = removeRawOpens restOpens modulePath in
+      match removeRawOpen rawOpen newModulePath with
+      | None -> newModulePath
+      | Some mp -> mp)
+    | [] -> modulePath
+  in
+  let completionPathMinusOpens =
+    completionPath |> Utils.flattenAnyNamespaceInPath
+    |> removeRawOpens package.opens
+    |> removeRawOpens rawOpens
+  in
+  completionPathMinusOpens
+
+let pathToElementProps package =
+  match package.genericJsxModule with
+  | None -> ["ReactDOM"; "domProps"]
+  | Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"]
diff --git a/analysis/src/Uri.ml b/analysis/src/Uri.ml
new file mode 100644
index 0000000000..4b1d67f543
--- /dev/null
+++ b/analysis/src/Uri.ml
@@ -0,0 +1,58 @@
+type t = {path: string; uri: string}
+
+let stripPath = ref false (* for use in tests *)
+
+let pathToUri path =
+  if Sys.os_type = "Unix" then "file://" ^ path
+  else
+    "file://"
+    ^ (Str.global_replace (Str.regexp_string "\\") "/" path
+      |> Str.substitute_first (Str.regexp "^\\([a-zA-Z]\\):") (fun text ->
+             let name = Str.matched_group 1 text in
+             "/" ^ String.lowercase_ascii name ^ "%3A"))
+
+let fromPath path = {path; uri = pathToUri path}
+let isInterface {path} = Filename.check_suffix path "i"
+let toPath {path} = path
+
+let toTopLevelLoc {path} =
+  let topPos =
+    {Lexing.pos_fname = path; pos_lnum = 1; pos_bol = 0; pos_cnum = 0}
+  in
+  {Location.loc_start = topPos; Location.loc_end = topPos; loc_ghost = false}
+
+let toString {uri} = if !stripPath then Filename.basename uri else uri
+
+(* Light weight, hopefully-enough-for-the-purpose fn to encode URI components.
+   Built to handle the reserved characters listed in
+   https://en.wikipedia.org/wiki/Percent-encoding. Note that this function is not
+   general purpose, rather it's currently only for URL encoding the argument list
+   passed to command links in markdown. *)
+let encodeURIComponent text =
+  let ln = String.length text in
+  let buf = Buffer.create ln in
+  let rec loop i =
+    if i < ln then (
+      (match text.[i] with
+      | '"' -> Buffer.add_string buf "%22"
+      | '\'' -> Buffer.add_string buf "%22"
+      | ':' -> Buffer.add_string buf "%3A"
+      | ';' -> Buffer.add_string buf "%3B"
+      | '/' -> Buffer.add_string buf "%2F"
+      | '\\' -> Buffer.add_string buf "%5C"
+      | ',' -> Buffer.add_string buf "%2C"
+      | '&' -> Buffer.add_string buf "%26"
+      | '[' -> Buffer.add_string buf "%5B"
+      | ']' -> Buffer.add_string buf "%5D"
+      | '#' -> Buffer.add_string buf "%23"
+      | '$' -> Buffer.add_string buf "%24"
+      | '+' -> Buffer.add_string buf "%2B"
+      | '=' -> Buffer.add_string buf "%3D"
+      | '?' -> Buffer.add_string buf "%3F"
+      | '@' -> Buffer.add_string buf "%40"
+      | '%' -> Buffer.add_string buf "%25"
+      | c -> Buffer.add_char buf c);
+      loop (i + 1))
+  in
+  loop 0;
+  Buffer.contents buf
diff --git a/analysis/src/Uri.mli b/analysis/src/Uri.mli
new file mode 100644
index 0000000000..b6e94692bd
--- /dev/null
+++ b/analysis/src/Uri.mli
@@ -0,0 +1,9 @@
+type t
+
+val fromPath : string -> t
+val isInterface : t -> bool
+val stripPath : bool ref
+val toPath : t -> string
+val toString : t -> string
+val toTopLevelLoc : t -> Location.t
+val encodeURIComponent : string -> string
diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml
new file mode 100644
index 0000000000..d136c181ae
--- /dev/null
+++ b/analysis/src/Utils.ml
@@ -0,0 +1,275 @@
+(**
+ * `startsWith(string, prefix)`
+ * true if the string starts with the prefix
+ *)
+let startsWith s prefix =
+  if prefix = "" then true
+  else
+    let p = String.length prefix in
+    p <= String.length s && String.sub s 0 p = prefix
+
+let endsWith s suffix =
+  if suffix = "" then true
+  else
+    let p = String.length suffix in
+    let l = String.length s in
+    p <= String.length s && String.sub s (l - p) p = suffix
+
+let isFirstCharUppercase s =
+  String.length s > 0 && Char.equal s.[0] (Char.uppercase_ascii s.[0])
+
+let cmtPosToPosition {Lexing.pos_lnum; pos_cnum; pos_bol} =
+  Protocol.{line = pos_lnum - 1; character = pos_cnum - pos_bol}
+
+let cmtLocToRange {Location.loc_start; loc_end} =
+  Protocol.{start = cmtPosToPosition loc_start; end_ = cmtPosToPosition loc_end}
+
+let endOfLocation loc length =
+  let open Location in
+  {
+    loc with
+    loc_start = {loc.loc_end with pos_cnum = loc.loc_end.pos_cnum - length};
+  }
+
+let chopLocationEnd loc length =
+  let open Location in
+  {
+    loc with
+    loc_end = {loc.loc_end with pos_cnum = loc.loc_end.pos_cnum - length};
+  }
+
+(** An optional List.find *)
+let rec find fn items =
+  match items with
+  | [] -> None
+  | one :: rest -> (
+    match fn one with
+    | None -> find fn rest
+    | Some x -> Some x)
+
+let filterMap f =
+  let rec aux accu = function
+    | [] -> List.rev accu
+    | x :: l -> (
+      match f x with
+      | None -> aux accu l
+      | Some v -> aux (v :: accu) l)
+  in
+  aux []
+
+let dumpPath path = Str.global_replace (Str.regexp_string "\\") "/" path
+let isUncurriedInternal path = startsWith (Path.name path) "Js.Fn.arity"
+
+let flattenLongIdent ?(jsx = false) ?(cutAtOffset = None) lid =
+  let extendPath s path =
+    match path with
+    | "" :: _ -> path
+    | _ -> s :: path
+  in
+  let rec loop lid =
+    match lid with
+    | Longident.Lident txt -> ([txt], String.length txt)
+    | Ldot (lid, txt) ->
+      let path, offset = loop lid in
+      if Some offset = cutAtOffset then (extendPath "" path, offset + 1)
+      else if jsx && txt = "createElement" then (path, offset)
+      else if txt = "_" then (extendPath "" path, offset + 1)
+      else (extendPath txt path, offset + 1 + String.length txt)
+    | Lapply _ -> ([], 0)
+  in
+  let path, _ = loop lid in
+  List.rev path
+
+let identifyPexp pexp =
+  match pexp with
+  | Parsetree.Pexp_ident _ -> "Pexp_ident"
+  | Pexp_constant _ -> "Pexp_constant"
+  | Pexp_let _ -> "Pexp_let"
+  | Pexp_function _ -> "Pexp_function"
+  | Pexp_fun _ -> "Pexp_fun"
+  | Pexp_apply _ -> "Pexp_apply"
+  | Pexp_match _ -> "Pexp_match"
+  | Pexp_try _ -> "Pexp_try"
+  | Pexp_tuple _ -> "Pexp_tuple"
+  | Pexp_construct _ -> "Pexp_construct"
+  | Pexp_variant _ -> "Pexp_variant"
+  | Pexp_record _ -> "Pexp_record"
+  | Pexp_field _ -> "Pexp_field"
+  | Pexp_setfield _ -> "Pexp_setfield"
+  | Pexp_array _ -> "Pexp_array"
+  | Pexp_ifthenelse _ -> "Pexp_ifthenelse"
+  | Pexp_sequence _ -> "Pexp_sequence"
+  | Pexp_while _ -> "Pexp_while"
+  | Pexp_for _ -> "Pexp_for"
+  | Pexp_constraint _ -> "Pexp_constraint"
+  | Pexp_coerce _ -> "Pexp_coerce"
+  | Pexp_send _ -> "Pexp_send"
+  | Pexp_new _ -> "Pexp_new"
+  | Pexp_setinstvar _ -> "Pexp_setinstvar"
+  | Pexp_override _ -> "Pexp_override"
+  | Pexp_letmodule _ -> "Pexp_letmodule"
+  | Pexp_letexception _ -> "Pexp_letexception"
+  | Pexp_assert _ -> "Pexp_assert"
+  | Pexp_lazy _ -> "Pexp_lazy"
+  | Pexp_poly _ -> "Pexp_poly"
+  | Pexp_object _ -> "Pexp_object"
+  | Pexp_newtype _ -> "Pexp_newtype"
+  | Pexp_pack _ -> "Pexp_pack"
+  | Pexp_extension _ -> "Pexp_extension"
+  | Pexp_open _ -> "Pexp_open"
+  | Pexp_unreachable -> "Pexp_unreachable"
+
+let identifyPpat pat =
+  match pat with
+  | Parsetree.Ppat_any -> "Ppat_any"
+  | Ppat_var _ -> "Ppat_var"
+  | Ppat_alias _ -> "Ppat_alias"
+  | Ppat_constant _ -> "Ppat_constant"
+  | Ppat_interval _ -> "Ppat_interval"
+  | Ppat_tuple _ -> "Ppat_tuple"
+  | Ppat_construct _ -> "Ppat_construct"
+  | Ppat_variant _ -> "Ppat_variant"
+  | Ppat_record _ -> "Ppat_record"
+  | Ppat_array _ -> "Ppat_array"
+  | Ppat_or _ -> "Ppat_or"
+  | Ppat_constraint _ -> "Ppat_constraint"
+  | Ppat_type _ -> "Ppat_type"
+  | Ppat_lazy _ -> "Ppat_lazy"
+  | Ppat_unpack _ -> "Ppat_unpack"
+  | Ppat_exception _ -> "Ppat_exception"
+  | Ppat_extension _ -> "Ppat_extension"
+  | Ppat_open _ -> "Ppat_open"
+
+let rec skipWhite text i =
+  if i < 0 then 0
+  else
+    match text.[i] with
+    | ' ' | '\n' | '\r' | '\t' -> skipWhite text (i - 1)
+    | _ -> i
+
+let hasBraces attributes =
+  attributes |> List.exists (fun (loc, _) -> loc.Location.txt = "res.braces")
+
+let rec unwrapIfOption (t : Types.type_expr) =
+  match t.desc with
+  | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> unwrapIfOption t1
+  | Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType
+  | _ -> t
+
+let isJsxComponent (vb : Parsetree.value_binding) =
+  vb.pvb_attributes
+  |> List.exists (function
+       | {Location.txt = "react.component" | "jsx.component"}, _payload -> true
+       | _ -> false)
+
+let checkName name ~prefix ~exact =
+  if exact then name = prefix else startsWith name prefix
+
+let rec getUnqualifiedName txt =
+  match txt with
+  | Longident.Lident fieldName -> fieldName
+  | Ldot (t, _) -> getUnqualifiedName t
+  | _ -> ""
+
+let indent n text =
+  let spaces = String.make n ' ' in
+  let len = String.length text in
+  let text =
+    if len != 0 && text.[len - 1] = '\n' then String.sub text 0 (len - 1)
+    else text
+  in
+  let lines = String.split_on_char '\n' text in
+  match lines with
+  | [] -> ""
+  | [line] -> line
+  | line :: lines ->
+    line ^ "\n"
+    ^ (lines |> List.map (fun line -> spaces ^ line) |> String.concat "\n")
+
+let mkPosition (pos : Pos.t) =
+  let line, character = pos in
+  {Protocol.line; character}
+
+let rangeOfLoc (loc : Location.t) =
+  let start = loc |> Loc.start |> mkPosition in
+  let end_ = loc |> Loc.end_ |> mkPosition in
+  {Protocol.start; end_}
+
+let rec expandPath (path : Path.t) =
+  match path with
+  | Pident id -> [Ident.name id]
+  | Pdot (p, s, _) -> s :: expandPath p
+  | Papply _ -> []
+
+module Option = struct
+  let flatMap f o =
+    match o with
+    | None -> None
+    | Some v -> f v
+end
+
+let rec lastElements list =
+  match list with
+  | ([_; _] | [_] | []) as res -> res
+  | _ :: tl -> lastElements tl
+
+let lowercaseFirstChar s =
+  if String.length s = 0 then s
+  else String.mapi (fun i c -> if i = 0 then Char.lowercase_ascii c else c) s
+
+let cutAfterDash s =
+  match String.index s '-' with
+  | n -> ( try String.sub s 0 n with Invalid_argument _ -> s)
+  | exception Not_found -> s
+
+let fileNameHasUnallowedChars s =
+  let regexp = Str.regexp "[^A-Za-z0-9_]" in
+  try
+    ignore (Str.search_forward regexp s 0);
+    true
+  with Not_found -> false
+
+(* Flattens any namespace in the provided path.
+   Example:
+    Globals-RescriptBun.URL.t (which is an illegal path because of the namespace) becomes:
+    RescriptBun.Globals.URL.t
+*)
+let rec flattenAnyNamespaceInPath path =
+  match path with
+  | [] -> []
+  | head :: tail ->
+    if String.contains head '-' then
+      let parts = String.split_on_char '-' head in
+      (* Namespaces are in reverse order, so "URL-RescriptBun" where RescriptBun is the namespace. *)
+      (parts |> List.rev) @ flattenAnyNamespaceInPath tail
+    else head :: flattenAnyNamespaceInPath tail
+
+let printMaybeExoticIdent ?(allowUident = false) txt =
+  let len = String.length txt in
+
+  let rec loop i =
+    if i == len then txt
+    else if i == 0 then
+      match String.unsafe_get txt i with
+      | 'A' .. 'Z' when allowUident -> loop (i + 1)
+      | 'a' .. 'z' | '_' -> loop (i + 1)
+      | _ -> "\"" ^ txt ^ "\""
+    else
+      match String.unsafe_get txt i with
+      | 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '\'' | '_' -> loop (i + 1)
+      | _ -> "\"" ^ txt ^ "\""
+  in
+  if Res_token.is_keyword_txt txt then "\"" ^ txt ^ "\"" else loop 0
+
+let findPackageJson root =
+  let path = Uri.toPath root in
+
+  let rec loop path =
+    if path = "/" then None
+    else if Files.exists (Filename.concat path "package.json") then
+      Some (Filename.concat path "package.json")
+    else
+      let parent = Filename.dirname path in
+      if parent = path then (* reached root *) None else loop parent
+  in
+  loop path
diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml
new file mode 100644
index 0000000000..b433054d9d
--- /dev/null
+++ b/analysis/src/Xform.ml
@@ -0,0 +1,944 @@
+(** Code transformations using the parser/printer and ast operations *)
+
+let isBracedExpr = Res_parsetree_viewer.is_braced_expr
+
+let mkPosition (pos : Pos.t) =
+  let line, character = pos in
+  {Protocol.line; character}
+
+let rangeOfLoc (loc : Location.t) =
+  let start = loc |> Loc.start |> mkPosition in
+  let end_ = loc |> Loc.end_ |> mkPosition in
+  {Protocol.start; end_}
+
+let extractTypeFromExpr expr ~debug ~path ~currentFile ~full ~pos =
+  match
+    expr.Parsetree.pexp_loc
+    |> CompletionFrontEnd.findTypeOfExpressionAtLoc ~debug ~path ~currentFile
+         ~posCursor:(Pos.ofLexing expr.Parsetree.pexp_loc.loc_start)
+  with
+  | Some (completable, scope) -> (
+    let env = SharedTypes.QueryEnv.fromFile full.SharedTypes.file in
+    let completions =
+      completable
+      |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env
+           ~forHover:true
+    in
+    let rawOpens = Scope.getRawOpens scope in
+    match completions with
+    | {env} :: _ -> (
+      let opens =
+        CompletionBackEnd.getOpens ~debug ~rawOpens ~package:full.package ~env
+      in
+      match
+        CompletionBackEnd.completionsGetCompletionType2 ~debug ~full ~rawOpens
+          ~opens ~pos completions
+      with
+      | Some (typ, _env) ->
+        let extractedType =
+          match typ with
+          | ExtractedType t -> Some t
+          | TypeExpr t ->
+            TypeUtils.extractType t ~env ~package:full.package
+            |> TypeUtils.getExtractedType
+        in
+        extractedType
+      | None -> None)
+    | _ -> None)
+  | _ -> None
+
+module IfThenElse = struct
+  (* Convert if-then-else to switch *)
+
+  let rec listToPat ~itemToPat = function
+    | [] -> Some []
+    | x :: xList -> (
+      match (itemToPat x, listToPat ~itemToPat xList) with
+      | Some p, Some pList -> Some (p :: pList)
+      | _ -> None)
+
+  let rec expToPat (exp : Parsetree.expression) =
+    let mkPat ppat_desc =
+      Ast_helper.Pat.mk ~loc:exp.pexp_loc ~attrs:exp.pexp_attributes ppat_desc
+    in
+    match exp.pexp_desc with
+    | Pexp_construct (lid, None) -> Some (mkPat (Ppat_construct (lid, None)))
+    | Pexp_construct (lid, Some e1) -> (
+      match expToPat e1 with
+      | None -> None
+      | Some p1 -> Some (mkPat (Ppat_construct (lid, Some p1))))
+    | Pexp_variant (label, None) -> Some (mkPat (Ppat_variant (label, None)))
+    | Pexp_variant (label, Some e1) -> (
+      match expToPat e1 with
+      | None -> None
+      | Some p1 -> Some (mkPat (Ppat_variant (label, Some p1))))
+    | Pexp_constant c -> Some (mkPat (Ppat_constant c))
+    | Pexp_tuple eList -> (
+      match listToPat ~itemToPat:expToPat eList with
+      | None -> None
+      | Some patList -> Some (mkPat (Ppat_tuple patList)))
+    | Pexp_record (items, None) -> (
+      let itemToPat (x, e) =
+        match expToPat e with
+        | None -> None
+        | Some p -> Some (x, p)
+      in
+      match listToPat ~itemToPat items with
+      | None -> None
+      | Some patItems -> Some (mkPat (Ppat_record (patItems, Closed))))
+    | Pexp_record (_, Some _) -> None
+    | _ -> None
+
+  let mkIterator ~pos ~changed =
+    let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
+      let newExp =
+        match e.pexp_desc with
+        | Pexp_ifthenelse
+            ( {
+                pexp_desc =
+                  Pexp_apply
+                    ( {
+                        pexp_desc =
+                          Pexp_ident {txt = Lident (("=" | "<>") as op)};
+                      },
+                      [(Nolabel, arg1); (Nolabel, arg2)] );
+              },
+              e1,
+              Some e2 )
+          when Loc.hasPos ~pos e.pexp_loc -> (
+          let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in
+          let mkMatch ~arg ~pat =
+            let cases =
+              [
+                Ast_helper.Exp.case pat e1;
+                Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2;
+              ]
+            in
+            Ast_helper.Exp.match_ ~loc:e.pexp_loc ~attrs:e.pexp_attributes arg
+              cases
+          in
+
+          match expToPat arg2 with
+          | None -> (
+            match expToPat arg1 with
+            | None -> None
+            | Some pat1 ->
+              let newExp = mkMatch ~arg:arg2 ~pat:pat1 in
+              Some newExp)
+          | Some pat2 ->
+            let newExp = mkMatch ~arg:arg1 ~pat:pat2 in
+            Some newExp)
+        | _ -> None
+      in
+      match newExp with
+      | Some newExp -> changed := Some newExp
+      | None -> Ast_iterator.default_iterator.expr iterator e
+    in
+
+    {Ast_iterator.default_iterator with expr}
+
+  let xform ~pos ~codeActions ~printExpr ~path structure =
+    let changed = ref None in
+    let iterator = mkIterator ~pos ~changed in
+    iterator.structure iterator structure;
+    match !changed with
+    | None -> ()
+    | Some newExpr ->
+      let range = rangeOfLoc newExpr.pexp_loc in
+      let newText = printExpr ~range newExpr in
+      let codeAction =
+        CodeActions.make ~title:"Replace with switch" ~kind:RefactorRewrite
+          ~uri:path ~newText ~range
+      in
+      codeActions := codeAction :: !codeActions
+end
+
+module ModuleToFile = struct
+  let mkIterator ~pos ~changed ~path ~printStandaloneStructure =
+    let structure_item (iterator : Ast_iterator.iterator)
+        (structure_item : Parsetree.structure_item) =
+      (match structure_item.pstr_desc with
+      | Pstr_module
+          {pmb_loc; pmb_name; pmb_expr = {pmod_desc = Pmod_structure structure}}
+        when structure_item.pstr_loc |> Loc.hasPos ~pos ->
+        let range = rangeOfLoc structure_item.pstr_loc in
+        let newTextInCurrentFile = "" in
+        let textForExtractedFile =
+          printStandaloneStructure ~loc:pmb_loc structure
+        in
+        let moduleName = pmb_name.txt in
+        let newFilePath =
+          Uri.fromPath
+            (Filename.concat (Filename.dirname path) moduleName ^ ".res")
+        in
+        changed :=
+          Some
+            (CodeActions.makeWithDocumentChanges
+               ~title:
+                 (Printf.sprintf "Extract local module \"%s\" to file \"%s\""
+                    moduleName (moduleName ^ ".res"))
+               ~kind:RefactorRewrite
+               ~documentChanges:
+                 [
+                   Protocol.CreateFile
+                     {
+                       uri = newFilePath |> Uri.toString;
+                       options =
+                         Some
+                           {overwrite = Some false; ignoreIfExists = Some true};
+                     };
+                   TextDocumentEdit
+                     {
+                       textDocument =
+                         {uri = newFilePath |> Uri.toString; version = None};
+                       edits =
+                         [
+                           {
+                             newText = textForExtractedFile;
+                             range =
+                               {
+                                 start = {line = 0; character = 0};
+                                 end_ = {line = 0; character = 0};
+                               };
+                           };
+                         ];
+                     };
+                   TextDocumentEdit
+                     {
+                       textDocument = {uri = path; version = None};
+                       edits = [{newText = newTextInCurrentFile; range}];
+                     };
+                 ]);
+        ()
+      | _ -> ());
+      Ast_iterator.default_iterator.structure_item iterator structure_item
+    in
+
+    {Ast_iterator.default_iterator with structure_item}
+
+  let xform ~pos ~codeActions ~path ~printStandaloneStructure structure =
+    let changed = ref None in
+    let iterator = mkIterator ~pos ~path ~changed ~printStandaloneStructure in
+    iterator.structure iterator structure;
+    match !changed with
+    | None -> ()
+    | Some codeAction -> codeActions := codeAction :: !codeActions
+end
+
+module AddBracesToFn = struct
+  (* Add braces to fn without braces *)
+
+  let mkIterator ~pos ~changed =
+    (* While iterating the AST, keep info on which structure item we are in.
+       Printing from the structure item, rather than the body of the function,
+       gives better local pretty printing *)
+    let currentStructureItem = ref None in
+
+    let structure_item (iterator : Ast_iterator.iterator)
+        (item : Parsetree.structure_item) =
+      let saved = !currentStructureItem in
+      currentStructureItem := Some item;
+      Ast_iterator.default_iterator.structure_item iterator item;
+      currentStructureItem := saved
+    in
+    let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
+      let bracesAttribute =
+        let loc =
+          {
+            Location.none with
+            loc_start = Lexing.dummy_pos;
+            loc_end =
+              {
+                Lexing.dummy_pos with
+                pos_lnum = Lexing.dummy_pos.pos_lnum + 1 (* force line break *);
+              };
+          }
+        in
+        (Location.mkloc "res.braces" loc, Parsetree.PStr [])
+      in
+      let isFunction = function
+        | {Parsetree.pexp_desc = Pexp_fun _} -> true
+        | _ -> false
+      in
+      (match e.pexp_desc with
+      | Pexp_fun (_, _, _, bodyExpr)
+        when Loc.hasPos ~pos bodyExpr.pexp_loc
+             && isBracedExpr bodyExpr = false
+             && isFunction bodyExpr = false ->
+        bodyExpr.pexp_attributes <- bracesAttribute :: bodyExpr.pexp_attributes;
+        changed := !currentStructureItem
+      | _ -> ());
+      Ast_iterator.default_iterator.expr iterator e
+    in
+
+    {Ast_iterator.default_iterator with expr; structure_item}
+
+  let xform ~pos ~codeActions ~path ~printStructureItem structure =
+    let changed = ref None in
+    let iterator = mkIterator ~pos ~changed in
+    iterator.structure iterator structure;
+    match !changed with
+    | None -> ()
+    | Some newStructureItem ->
+      let range = rangeOfLoc newStructureItem.pstr_loc in
+      let newText = printStructureItem ~range newStructureItem in
+      let codeAction =
+        CodeActions.make ~title:"Add braces to function" ~kind:RefactorRewrite
+          ~uri:path ~newText ~range
+      in
+      codeActions := codeAction :: !codeActions
+end
+
+module AddTypeAnnotation = struct
+  (* Add type annotation to value declaration *)
+
+  type annotation = Plain | WithParens
+
+  let mkIterator ~pos ~result =
+    let processPattern ?(isUnlabeledOnlyArg = false) (pat : Parsetree.pattern) =
+      match pat.ppat_desc with
+      | Ppat_var {loc} when Loc.hasPos ~pos loc ->
+        result := Some (if isUnlabeledOnlyArg then WithParens else Plain)
+      | _ -> ()
+    in
+    let rec processFunction ~argNum (e : Parsetree.expression) =
+      match e.pexp_desc with
+      | Pexp_fun (argLabel, _, pat, e)
+      | Pexp_construct
+          ( {txt = Lident "Function$"},
+            Some {pexp_desc = Pexp_fun (argLabel, _, pat, e)} ) ->
+        let isUnlabeledOnlyArg =
+          argNum = 1 && argLabel = Nolabel
+          &&
+          match e.pexp_desc with
+          | Pexp_fun _ -> false
+          | _ -> true
+        in
+        processPattern ~isUnlabeledOnlyArg pat;
+        processFunction ~argNum:(argNum + 1) e
+      | _ -> ()
+    in
+    let structure_item (iterator : Ast_iterator.iterator)
+        (si : Parsetree.structure_item) =
+      match si.pstr_desc with
+      | Pstr_value (_recFlag, bindings) ->
+        let processBinding (vb : Parsetree.value_binding) =
+          (* Can't add a type annotation to a jsx component, or the compiler crashes *)
+          let isJsxComponent = Utils.isJsxComponent vb in
+          if not isJsxComponent then processPattern vb.pvb_pat;
+          processFunction vb.pvb_expr
+        in
+        bindings |> List.iter (processBinding ~argNum:1);
+        Ast_iterator.default_iterator.structure_item iterator si
+      | _ -> Ast_iterator.default_iterator.structure_item iterator si
+    in
+    {Ast_iterator.default_iterator with structure_item}
+
+  let xform ~path ~pos ~full ~structure ~codeActions ~debug =
+    let result = ref None in
+    let iterator = mkIterator ~pos ~result in
+    iterator.structure iterator structure;
+    match !result with
+    | None -> ()
+    | Some annotation -> (
+      match References.getLocItem ~full ~pos ~debug with
+      | None -> ()
+      | Some locItem -> (
+        match locItem.locType with
+        | Typed (name, typ, _) ->
+          let range, newText =
+            match annotation with
+            | Plain ->
+              ( rangeOfLoc {locItem.loc with loc_start = locItem.loc.loc_end},
+                ": " ^ (typ |> Shared.typeToString) )
+            | WithParens ->
+              ( rangeOfLoc locItem.loc,
+                "(" ^ name ^ ": " ^ (typ |> Shared.typeToString) ^ ")" )
+          in
+          let codeAction =
+            CodeActions.make ~title:"Add type annotation" ~kind:RefactorRewrite
+              ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions
+        | _ -> ()))
+end
+
+module ExpandCatchAllForVariants = struct
+  let mkIterator ~pos ~result =
+    let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
+      (if e.pexp_loc |> Loc.hasPos ~pos then
+         match e.pexp_desc with
+         | Pexp_match (switchExpr, cases) -> (
+           let catchAllCase =
+             cases
+             |> List.find_opt (fun (c : Parsetree.case) ->
+                    match c with
+                    | {pc_lhs = {ppat_desc = Ppat_any}} -> true
+                    | _ -> false)
+           in
+           match catchAllCase with
+           | None -> ()
+           | Some catchAllCase ->
+             result := Some (switchExpr, catchAllCase, cases))
+         | _ -> ());
+      Ast_iterator.default_iterator.expr iterator e
+    in
+    {Ast_iterator.default_iterator with expr}
+
+  let xform ~path ~pos ~full ~structure ~currentFile ~codeActions ~debug =
+    let result = ref None in
+    let iterator = mkIterator ~pos ~result in
+    iterator.structure iterator structure;
+    match !result with
+    | None -> ()
+    | Some (switchExpr, catchAllCase, cases) -> (
+      if Debug.verbose () then
+        print_endline
+          "[codeAction - ExpandCatchAllForVariants] Found target switch";
+      let rec findAllConstructorNames ?(mode : [`option | `default] = `default)
+          ?(constructorNames = []) (p : Parsetree.pattern) =
+        match p.ppat_desc with
+        | Ppat_construct ({txt = Lident "Some"}, Some payload)
+          when mode = `option ->
+          findAllConstructorNames ~mode ~constructorNames payload
+        | Ppat_construct ({txt}, _) -> Longident.last txt :: constructorNames
+        | Ppat_variant (name, _) -> name :: constructorNames
+        | Ppat_or (a, b) ->
+          findAllConstructorNames ~mode ~constructorNames a
+          @ findAllConstructorNames ~mode ~constructorNames b
+          @ constructorNames
+        | _ -> constructorNames
+      in
+      let getCurrentConstructorNames ?mode cases =
+        cases
+        |> List.map (fun (c : Parsetree.case) ->
+               if Option.is_some c.pc_guard then []
+               else findAllConstructorNames ?mode c.pc_lhs)
+        |> List.flatten
+      in
+      let currentConstructorNames = getCurrentConstructorNames cases in
+      match
+        switchExpr
+        |> extractTypeFromExpr ~debug ~path ~currentFile ~full
+             ~pos:(Pos.ofLexing switchExpr.pexp_loc.loc_end)
+      with
+      | Some (Tvariant {constructors}) ->
+        let missingConstructors =
+          constructors
+          |> List.filter (fun (c : SharedTypes.Constructor.t) ->
+                 currentConstructorNames |> List.mem c.cname.txt = false)
+        in
+        if List.length missingConstructors > 0 then
+          let newText =
+            missingConstructors
+            |> List.map (fun (c : SharedTypes.Constructor.t) ->
+                   c.cname.txt
+                   ^
+                   match c.args with
+                   | Args [] -> ""
+                   | Args _ | InlineRecord _ -> "(_)")
+            |> String.concat " | "
+          in
+          let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in
+          let codeAction =
+            CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite
+              ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions
+        else ()
+      | Some (Tpolyvariant {constructors}) ->
+        let missingConstructors =
+          constructors
+          |> List.filter (fun (c : SharedTypes.polyVariantConstructor) ->
+                 currentConstructorNames |> List.mem c.name = false)
+        in
+        if List.length missingConstructors > 0 then
+          let newText =
+            missingConstructors
+            |> List.map (fun (c : SharedTypes.polyVariantConstructor) ->
+                   Res_printer.polyvar_ident_to_string c.name
+                   ^
+                   match c.args with
+                   | [] -> ""
+                   | _ -> "(_)")
+            |> String.concat " | "
+          in
+          let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in
+          let codeAction =
+            CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite
+              ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions
+        else ()
+      | Some (Toption (env, innerType)) -> (
+        if Debug.verbose () then
+          print_endline
+            "[codeAction - ExpandCatchAllForVariants] Found option type";
+        let innerType =
+          match innerType with
+          | ExtractedType t -> Some t
+          | TypeExpr t -> (
+            match TypeUtils.extractType ~env ~package:full.package t with
+            | None -> None
+            | Some (t, _) -> Some t)
+        in
+        match innerType with
+        | Some ((Tvariant _ | Tpolyvariant _) as variant) ->
+          let currentConstructorNames =
+            getCurrentConstructorNames ~mode:`option cases
+          in
+          let hasNoneCase =
+            cases
+            |> List.exists (fun (c : Parsetree.case) ->
+                   match c.pc_lhs.ppat_desc with
+                   | Ppat_construct ({txt = Lident "None"}, _) -> true
+                   | _ -> false)
+          in
+          let missingConstructors =
+            match variant with
+            | Tvariant {constructors} ->
+              constructors
+              |> List.filter_map (fun (c : SharedTypes.Constructor.t) ->
+                     if currentConstructorNames |> List.mem c.cname.txt = false
+                     then
+                       Some
+                         ( c.cname.txt,
+                           match c.args with
+                           | Args [] -> false
+                           | _ -> true )
+                     else None)
+            | Tpolyvariant {constructors} ->
+              constructors
+              |> List.filter_map
+                   (fun (c : SharedTypes.polyVariantConstructor) ->
+                     if currentConstructorNames |> List.mem c.name = false then
+                       Some
+                         ( Res_printer.polyvar_ident_to_string c.name,
+                           match c.args with
+                           | [] -> false
+                           | _ -> true )
+                     else None)
+            | _ -> []
+          in
+          if List.length missingConstructors > 0 || not hasNoneCase then
+            let newText =
+              "Some("
+              ^ (missingConstructors
+                |> List.map (fun (name, hasArgs) ->
+                       name ^ if hasArgs then "(_)" else "")
+                |> String.concat " | ")
+              ^ ")"
+            in
+            let newText =
+              if hasNoneCase then newText else newText ^ " | None"
+            in
+            let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in
+            let codeAction =
+              CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite
+                ~uri:path ~newText ~range
+            in
+            codeActions := codeAction :: !codeActions
+          else ()
+        | _ -> ())
+      | _ -> ())
+end
+
+module ExhaustiveSwitch = struct
+  (* Expand expression to be an exhaustive switch of the underlying value *)
+  type posType = Single of Pos.t | Range of Pos.t * Pos.t
+
+  type completionType =
+    | Switch of {
+        pos: Pos.t;
+        switchExpr: Parsetree.expression;
+        completionExpr: Parsetree.expression;
+      }
+    | Selection of {expr: Parsetree.expression}
+
+  let mkIteratorSingle ~pos ~result =
+    let expr (iterator : Ast_iterator.iterator) (exp : Parsetree.expression) =
+      (match exp.pexp_desc with
+      | Pexp_ident _ when Loc.hasPosInclusiveEnd ~pos exp.pexp_loc ->
+        (* Exhaustive switch for having the cursor on an identifier. *)
+        result := Some (Selection {expr = exp})
+      | Pexp_match (completionExpr, [])
+        when Loc.hasPosInclusiveEnd ~pos exp.pexp_loc ->
+        (* No cases means there's no `|` yet in the switch, so `switch someExpr` *)
+        result := Some (Switch {pos; switchExpr = exp; completionExpr})
+      | _ -> ());
+      Ast_iterator.default_iterator.expr iterator exp
+    in
+    {Ast_iterator.default_iterator with expr}
+
+  let mkIteratorRange ~startPos ~endPos ~foundSelection =
+    let expr (iterator : Ast_iterator.iterator) (exp : Parsetree.expression) =
+      let expStartPos = Pos.ofLexing exp.pexp_loc.loc_start in
+      let expEndPos = Pos.ofLexing exp.pexp_loc.loc_end in
+
+      (if expStartPos = startPos then
+         match !foundSelection with
+         | None, endExpr -> foundSelection := (Some exp, endExpr)
+         | _ -> ());
+
+      (if expEndPos = endPos then
+         match !foundSelection with
+         | startExp, _ -> foundSelection := (startExp, Some exp));
+
+      Ast_iterator.default_iterator.expr iterator exp
+    in
+    {Ast_iterator.default_iterator with expr}
+
+  let xform ~printExpr ~path ~currentFile ~pos ~full ~structure ~codeActions
+      ~debug =
+    (* TODO: Adapt to '(' as leading/trailing character (skip one col, it's not included in the AST) *)
+    let result = ref None in
+    let foundSelection = ref (None, None) in
+    let iterator =
+      match pos with
+      | Single pos -> mkIteratorSingle ~pos ~result
+      | Range (startPos, endPos) ->
+        mkIteratorRange ~startPos ~endPos ~foundSelection
+    in
+    iterator.structure iterator structure;
+    (match !foundSelection with
+    | Some startExp, Some endExp ->
+      if debug then
+        Printf.printf "found selection: %s -> %s\n"
+          (Loc.toString startExp.pexp_loc)
+          (Loc.toString endExp.pexp_loc);
+      result := Some (Selection {expr = startExp})
+    | _ -> ());
+    match !result with
+    | None -> ()
+    | Some (Selection {expr}) -> (
+      match
+        expr
+        |> extractTypeFromExpr ~debug ~path ~currentFile ~full
+             ~pos:(Pos.ofLexing expr.pexp_loc.loc_start)
+      with
+      | None -> ()
+      | Some extractedType -> (
+        let open TypeUtils.Codegen in
+        let exhaustiveSwitch =
+          extractedTypeToExhaustiveCases
+            ~env:(SharedTypes.QueryEnv.fromFile full.file)
+            ~full extractedType
+        in
+        match exhaustiveSwitch with
+        | None -> ()
+        | Some cases ->
+          let range = rangeOfLoc expr.pexp_loc in
+          let newText =
+            printExpr ~range {expr with pexp_desc = Pexp_match (expr, cases)}
+          in
+          let codeAction =
+            CodeActions.make ~title:"Exhaustive switch" ~kind:RefactorRewrite
+              ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions))
+    | Some (Switch {switchExpr; completionExpr; pos}) -> (
+      match
+        completionExpr
+        |> extractTypeFromExpr ~debug ~path ~currentFile ~full ~pos
+      with
+      | None -> ()
+      | Some extractedType -> (
+        let open TypeUtils.Codegen in
+        let exhaustiveSwitch =
+          extractedTypeToExhaustiveCases
+            ~env:(SharedTypes.QueryEnv.fromFile full.file)
+            ~full extractedType
+        in
+        match exhaustiveSwitch with
+        | None -> ()
+        | Some cases ->
+          let range = rangeOfLoc switchExpr.pexp_loc in
+          let newText =
+            printExpr ~range
+              {switchExpr with pexp_desc = Pexp_match (completionExpr, cases)}
+          in
+          let codeAction =
+            CodeActions.make ~title:"Exhaustive switch" ~kind:RefactorRewrite
+              ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions))
+end
+
+module AddDocTemplate = struct
+  let createTemplate () =
+    let docContent = ["\n"; "\n"] in
+    let expression =
+      Ast_helper.Exp.constant
+        (Parsetree.Pconst_string (String.concat "" docContent, None))
+    in
+    let structureItemDesc = Parsetree.Pstr_eval (expression, []) in
+    let structureItem = Ast_helper.Str.mk structureItemDesc in
+    let attrLoc =
+      {
+        Location.none with
+        loc_start = Lexing.dummy_pos;
+        loc_end =
+          {
+            Lexing.dummy_pos with
+            pos_lnum = Lexing.dummy_pos.pos_lnum (* force line break *);
+          };
+      }
+    in
+    (Location.mkloc "res.doc" attrLoc, Parsetree.PStr [structureItem])
+
+  module Interface = struct
+    let mkIterator ~pos ~result =
+      let signature_item (iterator : Ast_iterator.iterator)
+          (item : Parsetree.signature_item) =
+        match item.psig_desc with
+        | Psig_value value_description as r
+          when Loc.hasPos ~pos value_description.pval_loc
+               && ProcessAttributes.findDocAttribute
+                    value_description.pval_attributes
+                  = None ->
+          result := Some (r, item.psig_loc)
+        | Psig_type (_, hd :: _) as r
+          when Loc.hasPos ~pos hd.ptype_loc
+               && ProcessAttributes.findDocAttribute hd.ptype_attributes = None
+          ->
+          result := Some (r, item.psig_loc)
+        | Psig_module {pmd_name = {loc}} as r ->
+          if Loc.start loc = pos then result := Some (r, item.psig_loc)
+          else Ast_iterator.default_iterator.signature_item iterator item
+        | _ -> Ast_iterator.default_iterator.signature_item iterator item
+      in
+      {Ast_iterator.default_iterator with signature_item}
+
+    let processSigValue (valueDesc : Parsetree.value_description) loc =
+      let attr = createTemplate () in
+      let newValueBinding =
+        {valueDesc with pval_attributes = attr :: valueDesc.pval_attributes}
+      in
+      let signature_item_desc = Parsetree.Psig_value newValueBinding in
+      Ast_helper.Sig.mk ~loc signature_item_desc
+
+    let processTypeDecl (typ : Parsetree.type_declaration) =
+      let attr = createTemplate () in
+      let newTypeDeclaration =
+        {typ with ptype_attributes = attr :: typ.ptype_attributes}
+      in
+      newTypeDeclaration
+
+    let processModDecl (modDecl : Parsetree.module_declaration) loc =
+      let attr = createTemplate () in
+      let newModDecl =
+        {modDecl with pmd_attributes = attr :: modDecl.pmd_attributes}
+      in
+      Ast_helper.Sig.mk ~loc (Parsetree.Psig_module newModDecl)
+
+    let xform ~path ~pos ~codeActions ~signature ~printSignatureItem =
+      let result = ref None in
+      let iterator = mkIterator ~pos ~result in
+      iterator.signature iterator signature;
+      match !result with
+      | Some (signatureItem, loc) -> (
+        let newSignatureItem =
+          match signatureItem with
+          | Psig_value value_desc ->
+            Some (processSigValue value_desc value_desc.pval_loc) (* Some loc *)
+          | Psig_type (flag, hd :: tl) ->
+            let newFirstTypeDecl = processTypeDecl hd in
+            Some
+              (Ast_helper.Sig.mk ~loc
+                 (Parsetree.Psig_type (flag, newFirstTypeDecl :: tl)))
+          | Psig_module modDecl -> Some (processModDecl modDecl loc)
+          | _ -> None
+        in
+
+        match newSignatureItem with
+        | Some signatureItem ->
+          let range = rangeOfLoc signatureItem.psig_loc in
+          let newText = printSignatureItem ~range signatureItem in
+          let codeAction =
+            CodeActions.make ~title:"Add Documentation template"
+              ~kind:RefactorRewrite ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions
+        | None -> ())
+      | None -> ()
+  end
+
+  module Implementation = struct
+    let mkIterator ~pos ~result =
+      let structure_item (iterator : Ast_iterator.iterator)
+          (si : Parsetree.structure_item) =
+        match si.pstr_desc with
+        | Pstr_value (_, {pvb_pat = {ppat_loc}; pvb_attributes} :: _) as r
+          when Loc.hasPos ~pos ppat_loc
+               && ProcessAttributes.findDocAttribute pvb_attributes = None ->
+          result := Some (r, si.pstr_loc)
+        | Pstr_primitive value_description as r
+          when Loc.hasPos ~pos value_description.pval_loc
+               && ProcessAttributes.findDocAttribute
+                    value_description.pval_attributes
+                  = None ->
+          result := Some (r, si.pstr_loc)
+        | Pstr_module {pmb_name = {loc}} as r ->
+          if Loc.start loc = pos then result := Some (r, si.pstr_loc)
+          else Ast_iterator.default_iterator.structure_item iterator si
+        | Pstr_type (_, hd :: _) as r
+          when Loc.hasPos ~pos hd.ptype_loc
+               && ProcessAttributes.findDocAttribute hd.ptype_attributes = None
+          ->
+          result := Some (r, si.pstr_loc)
+        | _ -> Ast_iterator.default_iterator.structure_item iterator si
+      in
+      {Ast_iterator.default_iterator with structure_item}
+
+    let processValueBinding (valueBinding : Parsetree.value_binding) =
+      let attr = createTemplate () in
+      let newValueBinding =
+        {valueBinding with pvb_attributes = attr :: valueBinding.pvb_attributes}
+      in
+      newValueBinding
+
+    let processPrimitive (valueDesc : Parsetree.value_description) loc =
+      let attr = createTemplate () in
+      let newValueDesc =
+        {valueDesc with pval_attributes = attr :: valueDesc.pval_attributes}
+      in
+      Ast_helper.Str.primitive ~loc newValueDesc
+
+    let processModuleBinding (modBind : Parsetree.module_binding) loc =
+      let attr = createTemplate () in
+      let newModBinding =
+        {modBind with pmb_attributes = attr :: modBind.pmb_attributes}
+      in
+      Ast_helper.Str.module_ ~loc newModBinding
+
+    let xform ~pos ~codeActions ~path ~printStructureItem ~structure =
+      let result = ref None in
+      let iterator = mkIterator ~pos ~result in
+      iterator.structure iterator structure;
+      match !result with
+      | None -> ()
+      | Some (structureItem, loc) -> (
+        let newStructureItem =
+          match structureItem with
+          | Pstr_value (flag, hd :: tl) ->
+            let newValueBinding = processValueBinding hd in
+            Some
+              (Ast_helper.Str.mk ~loc
+                 (Parsetree.Pstr_value (flag, newValueBinding :: tl)))
+          | Pstr_primitive valueDesc -> Some (processPrimitive valueDesc loc)
+          | Pstr_module modBind -> Some (processModuleBinding modBind loc)
+          | Pstr_type (flag, hd :: tl) ->
+            let newFirstTypeDecl = Interface.processTypeDecl hd in
+            Some
+              (Ast_helper.Str.mk ~loc
+                 (Parsetree.Pstr_type (flag, newFirstTypeDecl :: tl)))
+          | _ -> None
+        in
+
+        match newStructureItem with
+        | Some structureItem ->
+          let range = rangeOfLoc structureItem.pstr_loc in
+          let newText = printStructureItem ~range structureItem in
+          let codeAction =
+            CodeActions.make ~title:"Add Documentation template"
+              ~kind:RefactorRewrite ~uri:path ~newText ~range
+          in
+          codeActions := codeAction :: !codeActions
+        | None -> ())
+  end
+end
+
+let parseImplementation ~filename =
+  let {Res_driver.parsetree = structure; comments} =
+    Res_driver.parsing_engine.parse_implementation ~for_printer:false ~filename
+  in
+  let filterComments ~loc comments =
+    (* Relevant comments in the range of the expression *)
+    let filter comment =
+      Loc.hasPos ~pos:(Loc.start (Res_comment.loc comment)) loc
+    in
+    comments |> List.filter filter
+  in
+  let printExpr ~(range : Protocol.range) (expr : Parsetree.expression) =
+    let structure = [Ast_helper.Str.eval ~loc:expr.pexp_loc expr] in
+    structure
+    |> Res_printer.print_implementation
+         ~width:Res_multi_printer.default_print_width
+         ~comments:(comments |> filterComments ~loc:expr.pexp_loc)
+    |> Utils.indent range.start.character
+  in
+  let printStructureItem ~(range : Protocol.range)
+      (item : Parsetree.structure_item) =
+    let structure = [item] in
+    structure
+    |> Res_printer.print_implementation
+         ~width:Res_multi_printer.default_print_width
+         ~comments:(comments |> filterComments ~loc:item.pstr_loc)
+    |> Utils.indent range.start.character
+  in
+  let printStandaloneStructure ~(loc : Location.t) structure =
+    structure
+    |> Res_printer.print_implementation
+         ~width:Res_multi_printer.default_print_width
+         ~comments:(comments |> filterComments ~loc)
+  in
+  (structure, printExpr, printStructureItem, printStandaloneStructure)
+
+let parseInterface ~filename =
+  let {Res_driver.parsetree = structure; comments} =
+    Res_driver.parsing_engine.parse_interface ~for_printer:false ~filename
+  in
+  let filterComments ~loc comments =
+    (* Relevant comments in the range of the expression *)
+    let filter comment =
+      Loc.hasPos ~pos:(Loc.start (Res_comment.loc comment)) loc
+    in
+    comments |> List.filter filter
+  in
+  let printSignatureItem ~(range : Protocol.range)
+      (item : Parsetree.signature_item) =
+    let signature_item = [item] in
+    signature_item
+    |> Res_printer.print_interface ~width:Res_multi_printer.default_print_width
+         ~comments:(comments |> filterComments ~loc:item.psig_loc)
+    |> Utils.indent range.start.character
+  in
+  (structure, printSignatureItem)
+
+let extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug =
+  let pos = startPos in
+  let codeActions = ref [] in
+  match Files.classifySourceFile currentFile with
+  | Res ->
+    let structure, printExpr, printStructureItem, printStandaloneStructure =
+      parseImplementation ~filename:currentFile
+    in
+    IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure;
+    ModuleToFile.xform ~pos ~codeActions ~path ~printStandaloneStructure
+      structure;
+    AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
+    AddDocTemplate.Implementation.xform ~pos ~codeActions ~path
+      ~printStructureItem ~structure;
+
+    (* This Code Action needs type info *)
+    let () =
+      match Cmt.loadFullCmtFromPath ~path with
+      | Some full ->
+        AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug;
+        ExpandCatchAllForVariants.xform ~path ~pos ~full ~structure ~codeActions
+          ~currentFile ~debug;
+        ExhaustiveSwitch.xform ~printExpr ~path
+          ~pos:
+            (if startPos = endPos then Single startPos
+             else Range (startPos, endPos))
+          ~full ~structure ~codeActions ~debug ~currentFile
+      | None -> ()
+    in
+
+    !codeActions
+  | Resi ->
+    let signature, printSignatureItem = parseInterface ~filename:currentFile in
+    AddDocTemplate.Interface.xform ~pos ~codeActions ~path ~signature
+      ~printSignatureItem;
+    !codeActions
+  | Other -> []
diff --git a/analysis/src/dune b/analysis/src/dune
new file mode 100644
index 0000000000..c1bd828c11
--- /dev/null
+++ b/analysis/src/dune
@@ -0,0 +1,5 @@
+(library
+ (name analysis)
+ (flags
+  (-w "+6+26+27+32+33+39"))
+ (libraries unix str ext ml jsonlib syntax reanalyze))
diff --git a/analysis/vendor/.ocamlformat b/analysis/vendor/.ocamlformat
new file mode 100644
index 0000000000..593b6a1ffc
--- /dev/null
+++ b/analysis/vendor/.ocamlformat
@@ -0,0 +1 @@
+disable
diff --git a/analysis/vendor/dune b/analysis/vendor/dune
new file mode 100644
index 0000000000..2064349dd9
--- /dev/null
+++ b/analysis/vendor/dune
@@ -0,0 +1 @@
+(dirs ext ml res_syntax json js_parser)
diff --git a/analysis/vendor/json/Json.ml b/analysis/vendor/json/Json.ml
new file mode 100644
index 0000000000..8bb6b8a363
--- /dev/null
+++ b/analysis/vendor/json/Json.ml
@@ -0,0 +1,535 @@
+(** # Json parser
+ *
+ * Works with bucklescript and bsb-native
+ *
+ * ## Basics
+ *
+ * ```
+ * open Json.Infix; /* for the nice infix operators */
+ * let raw = {|{"hello": "folks"}|};
+ * let who = Json.parse(raw) |> Json.get("hello") |?> Json.string;
+ * Js.log(who);
+ * ```
+ *
+ * ## Parse & stringify
+ *
+ * @doc parse, stringify
+ *
+ * ## Accessing descendents
+ *
+ * @doc get, nth, getPath
+ *
+ * ## Coercing to types
+ *
+ * @doc string, number, array, obj, bool, null
+ *
+ * ## The JSON type
+ *
+ * @doc t
+ *
+ * ## Infix operators for easier working
+ *
+ * @doc Infix
+ *)
+
+type t =
+  | String of string
+  | Number of float
+  | Array of t list
+  | Object of (string * t) list
+  | True
+  | False
+  | Null
+
+let string_of_number f =
+  let s = string_of_float f in
+  if s.[String.length s - 1] = '.' then String.sub s 0 (String.length s - 1)
+  else s
+
+(**
+ * This module is provided for easier working with optional values.
+ *)
+module Infix = struct
+  (** The "force unwrap" operator
+   *
+   * If you're sure there's a value, you can force it.
+   * ```
+   * open Json.Infix;
+   * let x: int = Some(10) |! "Expected this to be present";
+   * Js.log(x);
+   * ```
+   *
+   * But you gotta be sure, otherwise it will throw.
+   * ```reason;raises
+   * open Json.Infix;
+   * let x: int = None |! "This will throw";
+   * ```
+   *)
+  let ( |! ) o d =
+    match o with
+    | None -> failwith d
+    | Some v -> v
+
+  (** The "upwrap with default" operator
+   * ```
+   * open Json.Infix;
+   * let x: int = Some(10) |? 4;
+   * let y: int = None |? 5;
+   * Js.log2(x, y);
+   * ```
+   *)
+  let ( |? ) o d =
+    match o with
+    | None -> d
+    | Some v -> v
+
+  (** The "transform contents into new optional" operator
+   * ```
+   * open Json.Infix;
+   * let maybeInc = x => x > 5 ? Some(x + 1) : None;
+   * let x: option(int) = Some(14) |?> maybeInc;
+   * let y: option(int) = None |?> maybeInc;
+   * ```
+   *)
+  let ( |?> ) o fn =
+    match o with
+    | None -> None
+    | Some v -> fn v
+
+  (** The "transform contents into new value & then re-wrap" operator
+   * ```
+   * open Json.Infix;
+   * let inc = x => x + 1;
+   * let x: option(int) = Some(7) |?>> inc;
+   * let y: option(int) = None |?>> inc;
+   * Js.log2(x, y);
+   * ```
+   *)
+  let ( |?>> ) o fn =
+    match o with
+    | None -> None
+    | Some v -> Some (fn v)
+
+  (** "handle the value if present, otherwise here's the default"
+   *
+   * It's called fold because that's what people call it :?. It's the same as "transform contents to new value" + "unwrap with default".
+   *
+   * ```
+   * open Json.Infix;
+   * let inc = x => x + 1;
+   * let x: int = fold(Some(4), 10, inc);
+   * let y: int = fold(None, 2, inc);
+   * Js.log2(x, y);
+   * ```
+   *)
+  let fold o d f =
+    match o with
+    | None -> d
+    | Some v -> f v
+end
+
+let escape text =
+  let ln = String.length text in
+  let buf = Buffer.create ln in
+  let rec loop i =
+    if i < ln then (
+      (match text.[i] with
+      | '\012' -> Buffer.add_string buf "\\f"
+      | '\\' -> Buffer.add_string buf "\\\\"
+      | '"' -> Buffer.add_string buf "\\\""
+      | '\n' -> Buffer.add_string buf "\\n"
+      | '\b' -> Buffer.add_string buf "\\b"
+      | '\r' -> Buffer.add_string buf "\\r"
+      | '\t' -> Buffer.add_string buf "\\t"
+      | c -> Buffer.add_char buf c);
+      loop (i + 1))
+  in
+  loop 0;
+  Buffer.contents buf
+
+(**
+ * ```
+ * let text = {|{"hello": "folks", "aa": [2, 3, "four"]}|};
+ * let result = Json.stringify(Json.parse(text));
+ * Js.log(result);
+ * assert(text == result);
+ * ```
+ *)
+let rec stringify t =
+  match t with
+  | String value -> "\"" ^ escape value ^ "\""
+  | Number num -> string_of_number num
+  | Array items -> "[" ^ String.concat ", " (List.map stringify items) ^ "]"
+  | Object items ->
+    "{"
+    ^ String.concat ", "
+        (List.map
+           (fun (k, v) -> "\"" ^ String.escaped k ^ "\": " ^ stringify v)
+           items)
+    ^ "}"
+  | True -> "true"
+  | False -> "false"
+  | Null -> "null"
+
+let white n =
+  let buffer = Buffer.create n in
+  for i = 0 to n - 1 do
+    Buffer.add_char buffer ' '
+  done;
+  Buffer.contents buffer
+
+let rec stringifyPretty ?(indent = 0) t =
+  match t with
+  | String value -> "\"" ^ escape value ^ "\""
+  | Number num -> string_of_number num
+  | Array [] -> "[]"
+  | Array [(String _ as contents)] -> "[" ^ stringifyPretty contents ^ "]"
+  | Array items ->
+    "[\n" ^ white indent
+    ^ String.concat
+        (",\n" ^ white indent)
+        (List.map (stringifyPretty ~indent:(indent + 2)) items)
+    ^ "\n"
+    ^ white (indent - 2)
+    ^ "]"
+  | Object [] -> "{}"
+  | Object items ->
+    "{\n" ^ white indent
+    ^ String.concat
+        (",\n" ^ white indent)
+        (List.map
+           (fun (k, v) ->
+             "\"" ^ String.escaped k ^ "\": "
+             ^ stringifyPretty ~indent:(indent + 2) v)
+           items)
+    ^ "\n"
+    ^ white (indent - 2)
+    ^ "}"
+  | True -> "true"
+  | False -> "false"
+  | Null -> "null"
+
+let unwrap message t =
+  match t with
+  | Some v -> v
+  | None -> failwith message
+
+module Parser = struct
+  let split_by ?(keep_empty = false) is_delim str =
+    let len = String.length str in
+    let rec loop acc last_pos pos =
+      if pos = -1 then
+        if last_pos = 0 && not keep_empty then acc
+        else String.sub str 0 last_pos :: acc
+      else if is_delim str.[pos] then
+        let new_len = last_pos - pos - 1 in
+        if new_len <> 0 || keep_empty then
+          let v = String.sub str (pos + 1) new_len in
+          loop (v :: acc) pos (pos - 1)
+        else loop acc pos (pos - 1)
+      else loop acc last_pos (pos - 1)
+    in
+    loop [] len (len - 1)
+
+  let fail text pos message =
+    let pre = String.sub text 0 pos in
+    let lines = split_by (fun c -> c = '\n') pre in
+    let count = List.length lines in
+    let last =
+      match count > 0 with
+      | true -> List.nth lines (count - 1)
+      | false -> ""
+    in
+    let col = String.length last + 1 in
+    let line = List.length lines in
+    let string =
+      Printf.sprintf "Error \"%s\" at %d:%d -> %s\n" message line col last
+    in
+    failwith string
+
+  let rec skipToNewline text pos =
+    if pos >= String.length text then pos
+    else if text.[pos] = '\n' then pos + 1
+    else skipToNewline text (pos + 1)
+
+  let stringTail text =
+    let len = String.length text in
+    if len > 1 then String.sub text 1 (len - 1) else ""
+
+  let rec skipToCloseMultilineComment text pos =
+    if pos + 1 >= String.length text then failwith "Unterminated comment"
+    else if text.[pos] = '*' && text.[pos + 1] = '/' then pos + 2
+    else skipToCloseMultilineComment text (pos + 1)
+
+  let rec skipWhite text pos =
+    if
+      pos < String.length text
+      && (text.[pos] = ' '
+         || text.[pos] = '\t'
+         || text.[pos] = '\n'
+         || text.[pos] = '\r')
+    then skipWhite text (pos + 1)
+    else pos
+
+  (* from https://stackoverflow.com/a/42431362 *)
+  let utf8encode s =
+    let prefs = [|0; 192; 224|] in
+    let s1 n = String.make 1 (Char.chr n) in
+    let rec ienc k sofar resid =
+      let bct = if k = 0 then 7 else 6 - k in
+      if resid < 1 lsl bct then s1 (prefs.(k) + resid) ^ sofar
+      else ienc (k + 1) (s1 (128 + (resid mod 64)) ^ sofar) (resid / 64)
+    in
+    ienc 0 "" (int_of_string ("0x" ^ s))
+
+  let parseString text pos =
+    (* let i = ref(pos); *)
+    let buffer = Buffer.create (String.length text) in
+    let ln = String.length text in
+    let rec loop i =
+      match i >= ln with
+      | true -> fail text i "Unterminated string"
+      | false -> (
+        match text.[i] with
+        | '"' -> i + 1
+        | '\\' -> (
+          match i + 1 >= ln with
+          | true -> fail text i "Unterminated string"
+          | false -> (
+            match text.[i + 1] with
+            | '/' ->
+              Buffer.add_char buffer '/';
+              loop (i + 2)
+            | 'f' ->
+              Buffer.add_char buffer '\012';
+              loop (i + 2)
+            | 'u' when i + 6 < ln ->
+              Buffer.add_string buffer (utf8encode (String.sub text (i + 2) 4));
+              loop (i + 7)
+            | _ ->
+              Buffer.add_string buffer (Scanf.unescaped (String.sub text i 2));
+              loop (i + 2)))
+        | c ->
+          Buffer.add_char buffer c;
+          loop (i + 1))
+    in
+    let final = loop pos in
+    (Buffer.contents buffer, final)
+
+  let parseDigits text pos =
+    let len = String.length text in
+    let rec loop i =
+      if i >= len then i
+      else
+        match text.[i] with
+        | '0' .. '9' -> loop (i + 1)
+        | _ -> i
+    in
+    loop (pos + 1)
+
+  let parseWithDecimal text pos =
+    let pos = parseDigits text pos in
+    if pos < String.length text && text.[pos] = '.' then
+      let pos = parseDigits text (pos + 1) in
+      pos
+    else pos
+
+  let parseNumber text pos =
+    let pos = parseWithDecimal text pos in
+    let ln = String.length text in
+    if pos < ln - 1 && (text.[pos] = 'E' || text.[pos] = 'e') then
+      let pos =
+        match text.[pos + 1] with
+        | '-' | '+' -> pos + 2
+        | _ -> pos + 1
+      in
+      parseDigits text pos
+    else pos
+
+  let parseNegativeNumber text pos =
+    let final =
+      if text.[pos] = '-' then parseNumber text (pos + 1)
+      else parseNumber text pos
+    in
+    (Number (float_of_string (String.sub text pos (final - pos))), final)
+
+  let expect char text pos message =
+    if text.[pos] <> char then fail text pos ("Expected: " ^ message)
+    else pos + 1
+
+  let parseComment : 'a. string -> int -> (string -> int -> 'a) -> 'a =
+   fun text pos next ->
+    if text.[pos] <> '/' then
+      if text.[pos] = '*' then
+        next text (skipToCloseMultilineComment text (pos + 1))
+      else failwith "Invalid syntax"
+    else next text (skipToNewline text (pos + 1))
+
+  let maybeSkipComment text pos =
+    if pos < String.length text && text.[pos] = '/' then
+      if pos + 1 < String.length text && text.[pos + 1] = '/' then
+        skipToNewline text (pos + 1)
+      else if pos + 1 < String.length text && text.[pos + 1] = '*' then
+        skipToCloseMultilineComment text (pos + 1)
+      else fail text pos "Invalid synatx"
+    else pos
+
+  let rec skip text pos =
+    if pos = String.length text then pos
+    else
+      let n = skipWhite text pos |> maybeSkipComment text in
+      if n > pos then skip text n else n
+
+  let rec parse text pos =
+    if pos >= String.length text then
+      fail text pos "Reached end of file without being done parsing"
+    else
+      match text.[pos] with
+      | '/' -> parseComment text (pos + 1) parse
+      | '[' -> parseArray text (pos + 1)
+      | '{' -> parseObject text (pos + 1)
+      | 'n' ->
+        if String.sub text pos 4 = "null" then (Null, pos + 4)
+        else fail text pos "unexpected character"
+      | 't' ->
+        if String.sub text pos 4 = "true" then (True, pos + 4)
+        else fail text pos "unexpected character"
+      | 'f' ->
+        if String.sub text pos 5 = "false" then (False, pos + 5)
+        else fail text pos "unexpected character"
+      | '\n' | '\t' | ' ' | '\r' -> parse text (skipWhite text pos)
+      | '"' ->
+        let s, pos = parseString text (pos + 1) in
+        (String s, pos)
+      | '-' | '0' .. '9' -> parseNegativeNumber text pos
+      | _ -> fail text pos "unexpected character"
+
+  and parseArrayValue text pos =
+    let pos = skip text pos in
+    let value, pos = parse text pos in
+    let pos = skip text pos in
+    match text.[pos] with
+    | ',' ->
+      let pos = skip text (pos + 1) in
+      if text.[pos] = ']' then ([value], pos + 1)
+      else
+        let rest, pos = parseArrayValue text pos in
+        (value :: rest, pos)
+    | ']' -> ([value], pos + 1)
+    | _ -> fail text pos "unexpected character"
+
+  and parseArray text pos =
+    let pos = skip text pos in
+    match text.[pos] with
+    | ']' -> (Array [], pos + 1)
+    | _ ->
+      let items, pos = parseArrayValue text pos in
+      (Array items, pos)
+
+  and parseObjectValue text pos =
+    let pos = skip text pos in
+    if text.[pos] <> '"' then fail text pos "Expected string"
+    else
+      let key, pos = parseString text (pos + 1) in
+      let pos = skip text pos in
+      let pos = expect ':' text pos "Colon" in
+      let value, pos = parse text pos in
+      let pos = skip text pos in
+      match text.[pos] with
+      | ',' ->
+        let pos = skip text (pos + 1) in
+        if text.[pos] = '}' then ([(key, value)], pos + 1)
+        else
+          let rest, pos = parseObjectValue text pos in
+          ((key, value) :: rest, pos)
+      | '}' -> ([(key, value)], pos + 1)
+      | _ ->
+        let rest, pos = parseObjectValue text pos in
+        ((key, value) :: rest, pos)
+
+  and parseObject text pos =
+    let pos = skip text pos in
+    if text.[pos] = '}' then (Object [], pos + 1)
+    else
+      let pairs, pos = parseObjectValue text pos in
+      (Object pairs, pos)
+end
+[@@nodoc]
+
+(** Turns some text into a json object. throws on failure *)
+let parse text =
+  try
+    let item, pos = Parser.parse text 0 in
+    let pos = Parser.skip text pos in
+    if pos < String.length text then
+      (* failwith
+         ("Extra data after parse finished: "
+         ^ String.sub text pos (String.length text - pos)) *)
+      None
+    else Some item
+  with Invalid_argument _ | Failure _ -> None
+
+(* Accessor helpers *)
+let bind v fn =
+  match v with
+  | None -> None
+  | Some v -> fn v
+
+(** If `t` is an object, get the value associated with the given string key *)
+let get key t =
+  match t with
+  | Object items -> ( try Some (List.assoc key items) with Not_found -> None)
+  | _ -> None
+
+(** If `t` is an array, get the value associated with the given index *)
+let nth n t =
+  match t with
+  | Array items ->
+    if n < List.length items then Some (List.nth items n) else None
+  | _ -> None
+
+let string t =
+  match t with
+  | String s -> Some s
+  | _ -> None
+let number t =
+  match t with
+  | Number s -> Some s
+  | _ -> None
+let array t =
+  match t with
+  | Array s -> Some s
+  | _ -> None
+let obj t =
+  match t with
+  | Object s -> Some s
+  | _ -> None
+let bool t =
+  match t with
+  | True -> Some true
+  | False -> Some false
+  | _ -> None
+let null t =
+  match t with
+  | Null -> Some ()
+  | _ -> None
+
+let rec parsePath keyList t =
+  match keyList with
+  | [] -> Some t
+  | head :: rest -> (
+    match get head t with
+    | None -> None
+    | Some value -> parsePath rest value)
+
+(** Get a deeply nested value from an object `t`.
+ * ```
+ * open Json.Infix;
+ * let json = Json.parse({|{"a": {"b": {"c": 2}}}|});
+ * let num = Json.getPath("a.b.c", json) |?> Json.number;
+ * assert(num == Some(2.))
+ * ```
+ *)
+let getPath path t =
+  let keys = Parser.split_by (fun c -> c = '.') path in
+  parsePath keys t
diff --git a/analysis/vendor/json/dune b/analysis/vendor/json/dune
new file mode 100644
index 0000000000..32dbef0de7
--- /dev/null
+++ b/analysis/vendor/json/dune
@@ -0,0 +1,5 @@
+(library
+ (name jsonlib)
+ (wrapped false)
+ (flags -w "-9")
+ (libraries))
diff --git a/biome.json b/biome.json
index 9899a7bbc5..44dd98952b 100644
--- a/biome.json
+++ b/biome.json
@@ -23,6 +23,10 @@
     "ignore": [
       "tests/build_tests/**",
       "tests/tests/**",
+      "tests/tools_tests/**",
+      "tests/analysis_tests/**",
+      "analysis/examples/**",
+      "analysis/reanalyze/examples/**",
       "lib/**",
       "ninja/**",
       "playground/**",
diff --git a/cli/bin_path.js b/cli/bin_path.js
index ca2e8afa08..727e233da0 100644
--- a/cli/bin_path.js
+++ b/cli/bin_path.js
@@ -1,6 +1,6 @@
 //@ts-check
 
-var path = require("path");
+const path = require("path");
 
 /**
  * @type{string}
@@ -11,7 +11,7 @@ var path = require("path");
  * Also, we do not have Windows ARM binaries yet. But the x64 binaries do work on Windows 11 ARM.
  * So omit the architecture for Windows, too.
  */
-var binDirName =
+const binDirName =
   process.arch === "x64" || process.platform === "win32"
     ? process.platform
     : process.platform + process.arch;
@@ -20,25 +20,40 @@ var binDirName =
  *
  * @type{string}
  */
-var binAbsolutePath = path.join(__dirname, "..", binDirName);
+const binAbsolutePath = path.join(__dirname, "..", binDirName);
 
 /**
  * @type{string}
  */
-var bsc_exe = path.join(binAbsolutePath, "bsc.exe");
+const bsc_exe = path.join(binAbsolutePath, "bsc.exe");
 
 /**
  * @type{string}
  */
-var ninja_exe = path.join(binAbsolutePath, "ninja.exe");
+const ninja_exe = path.join(binAbsolutePath, "ninja.exe");
 
 /**
  * @type{string}
  */
-var rescript_exe = path.join(binAbsolutePath, "rescript.exe");
+const rescript_exe = path.join(binAbsolutePath, "rescript.exe");
+
+/**
+ * @type{string}
+ */
+const rescript_tools_exe = path.join(binAbsolutePath, "rescript-tools.exe");
+
+/**
+ * @type{string}
+ */
+const rescript_editor_analysis_exe = path.join(
+  binAbsolutePath,
+  "rescript-editor-analysis.exe",
+);
 
 exports.dirName = binDirName;
 exports.absolutePath = binAbsolutePath;
 exports.bsc_exe = bsc_exe;
 exports.ninja_exe = ninja_exe;
 exports.rescript_exe = rescript_exe;
+exports.rescript_tools_exe = rescript_tools_exe;
+exports.rescript_editor_analysis_exe = rescript_editor_analysis_exe;
diff --git a/cli/rescript-tools b/cli/rescript-tools
new file mode 100755
index 0000000000..a4d5ae21ae
--- /dev/null
+++ b/cli/rescript-tools
@@ -0,0 +1,13 @@
+#!/usr/bin/env node
+//@ts-check
+"use strict";
+
+const path = require("path");
+const child_process = require("child_process");
+
+const { absolutePath: binAbsolutePath } = require("./bin_path");
+const rewatchExe = path.join(binAbsolutePath, "rescript-tools.exe");
+
+const args = process.argv.slice(2);
+
+child_process.spawnSync(rewatchExe, args, { stdio: "inherit" });
diff --git a/compiler/bsb_exe/dune b/compiler/bsb_exe/dune
index 491fbc076c..4478ba2cc4 100644
--- a/compiler/bsb_exe/dune
+++ b/compiler/bsb_exe/dune
@@ -6,6 +6,7 @@
 (executable
  (name rescript_main)
  (public_name rescript)
+ (package rescript)
  (enabled_if
   (<> %{profile} browser))
  (flags
diff --git a/compiler/bsb_helper_exe/dune b/compiler/bsb_helper_exe/dune
index f8536139b2..362ee51ea0 100644
--- a/compiler/bsb_helper_exe/dune
+++ b/compiler/bsb_helper_exe/dune
@@ -6,6 +6,7 @@
 (executable
  (name bsb_helper_main)
  (public_name bsb_helper)
+ (package rescript)
  (enabled_if
   (<> %{profile} browser))
  (flags
diff --git a/compiler/bsc/dune b/compiler/bsc/dune
index 1c2a961a91..240599abee 100644
--- a/compiler/bsc/dune
+++ b/compiler/bsc/dune
@@ -6,6 +6,7 @@
 (executable
  (name rescript_compiler_main)
  (public_name bsc)
+ (package rescript)
  (flags
   (:standard -w +a-4-9-30-40-41-42-48-70))
  (libraries common core depends gentype js_parser syntax))
diff --git a/compiler/cmij/dune b/compiler/cmij/dune
index 86b4f13966..baac16f9e3 100644
--- a/compiler/cmij/dune
+++ b/compiler/cmij/dune
@@ -6,6 +6,7 @@
 (executables
  (names cmjdump_main)
  (public_names cmjdump)
+ (package rescript)
  (flags
   (:standard -w +a-4-9-40-42-69))
  (libraries core))
diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml
index 279ef1a365..40cc7a1beb 100644
--- a/compiler/ml/parsetree.ml
+++ b/compiler/ml/parsetree.ml
@@ -210,7 +210,8 @@ and pattern_desc =
 and expression = {
   pexp_desc: expression_desc;
   pexp_loc: Location.t;
-  pexp_attributes: attributes; (* ... [@id1] [@id2] *)
+  (* Hack: made pexp_attributes mutable for use in analysis exe. Please do not use elsewhere! *)
+  mutable pexp_attributes: attributes; (* ... [@id1] [@id2] *)
 }
 
 and expression_desc =
diff --git a/compiler/ml/tast_iterator.ml b/compiler/ml/tast_iterator.ml
new file mode 100644
index 0000000000..8b713895b1
--- /dev/null
+++ b/compiler/ml/tast_iterator.ml
@@ -0,0 +1,363 @@
+(**************************************************************************)
+(*                                                                        *)
+(*                                 OCaml                                  *)
+(*                                                                        *)
+(*                          Isaac "Izzy" Avram                            *)
+(*                                                                        *)
+(*   Copyright 2019 Institut National de Recherche en Informatique et     *)
+(*     en Automatique.                                                    *)
+(*                                                                        *)
+(*   All rights reserved.  This file is distributed under the terms of    *)
+(*   the GNU Lesser General Public License version 2.1, with the          *)
+(*   special exception on linking described in the file LICENSE.          *)
+(*                                                                        *)
+(**************************************************************************)
+
+open Asttypes
+open Typedtree
+
+type iterator = {
+  case: iterator -> case -> unit;
+  cases: iterator -> case list -> unit;
+  env: iterator -> Env.t -> unit;
+  expr: iterator -> expression -> unit;
+  extension_constructor: iterator -> extension_constructor -> unit;
+  module_binding: iterator -> module_binding -> unit;
+  module_coercion: iterator -> module_coercion -> unit;
+  module_declaration: iterator -> module_declaration -> unit;
+  module_expr: iterator -> module_expr -> unit;
+  module_type: iterator -> module_type -> unit;
+  module_type_declaration: iterator -> module_type_declaration -> unit;
+  package_type: iterator -> package_type -> unit;
+  pat: iterator -> pattern -> unit;
+  row_field: iterator -> row_field -> unit;
+  object_field: iterator -> object_field -> unit;
+  signature: iterator -> signature -> unit;
+  signature_item: iterator -> signature_item -> unit;
+  structure: iterator -> structure -> unit;
+  structure_item: iterator -> structure_item -> unit;
+  typ: iterator -> core_type -> unit;
+  type_declaration: iterator -> type_declaration -> unit;
+  type_declarations: iterator -> rec_flag * type_declaration list -> unit;
+  type_extension: iterator -> type_extension -> unit;
+  type_kind: iterator -> type_kind -> unit;
+  value_binding: iterator -> value_binding -> unit;
+  value_bindings: iterator -> rec_flag * value_binding list -> unit;
+  value_description: iterator -> value_description -> unit;
+  with_constraint: iterator -> with_constraint -> unit;
+}
+
+let structure sub {str_items; str_final_env; _} =
+  List.iter (sub.structure_item sub) str_items;
+  sub.env sub str_final_env
+
+let module_type_declaration sub {mtd_type; _} =
+  Option.iter (sub.module_type sub) mtd_type
+
+let module_declaration sub {md_type; _} = sub.module_type sub md_type
+let include_infos f {incl_mod; _} = f incl_mod
+
+let structure_item sub {str_desc; str_env; _} =
+  sub.env sub str_env;
+  match str_desc with
+  | Tstr_eval (exp, _) -> sub.expr sub exp
+  | Tstr_value (rec_flag, list) -> sub.value_bindings sub (rec_flag, list)
+  | Tstr_primitive v -> sub.value_description sub v
+  | Tstr_type (rec_flag, list) -> sub.type_declarations sub (rec_flag, list)
+  | Tstr_typext te -> sub.type_extension sub te
+  | Tstr_exception ext -> sub.extension_constructor sub ext
+  | Tstr_module mb -> sub.module_binding sub mb
+  | Tstr_recmodule list -> List.iter (sub.module_binding sub) list
+  | Tstr_modtype x -> sub.module_type_declaration sub x
+  | Tstr_class _ -> ()
+  | Tstr_class_type () -> ()
+  | Tstr_include incl -> include_infos (sub.module_expr sub) incl
+  | Tstr_open _ -> ()
+  | Tstr_attribute _ -> ()
+
+let value_description sub x = sub.typ sub x.val_desc
+let label_decl sub {ld_type; _} = sub.typ sub ld_type
+
+let constructor_args sub = function
+  | Cstr_tuple l -> List.iter (sub.typ sub) l
+  | Cstr_record l -> List.iter (label_decl sub) l
+
+let constructor_decl sub {cd_args; cd_res; _} =
+  constructor_args sub cd_args;
+  Option.iter (sub.typ sub) cd_res
+
+let type_kind sub = function
+  | Ttype_abstract -> ()
+  | Ttype_variant list -> List.iter (constructor_decl sub) list
+  | Ttype_record list -> List.iter (label_decl sub) list
+  | Ttype_open -> ()
+
+let type_declaration sub {typ_cstrs; typ_kind; typ_manifest; typ_params; _} =
+  List.iter
+    (fun (c1, c2, _) ->
+      sub.typ sub c1;
+      sub.typ sub c2)
+    typ_cstrs;
+  sub.type_kind sub typ_kind;
+  Option.iter (sub.typ sub) typ_manifest;
+  List.iter (fun (c, _) -> sub.typ sub c) typ_params
+
+let type_declarations sub (_, list) = List.iter (sub.type_declaration sub) list
+
+let type_extension sub {tyext_constructors; tyext_params; _} =
+  List.iter (fun (c, _) -> sub.typ sub c) tyext_params;
+  List.iter (sub.extension_constructor sub) tyext_constructors
+
+let extension_constructor sub {ext_kind; _} =
+  match ext_kind with
+  | Text_decl (ctl, cto) ->
+    constructor_args sub ctl;
+    Option.iter (sub.typ sub) cto
+  | Text_rebind _ -> ()
+
+let pat sub {pat_extra; pat_desc; pat_env; _} =
+  let extra = function
+    | Tpat_type _ -> ()
+    | Tpat_unpack -> ()
+    | Tpat_open (_, _, env) -> sub.env sub env
+    | Tpat_constraint ct -> sub.typ sub ct
+  in
+  sub.env sub pat_env;
+  List.iter (fun (e, _, _) -> extra e) pat_extra;
+  match pat_desc with
+  | Tpat_any -> ()
+  | Tpat_var _ -> ()
+  | Tpat_constant _ -> ()
+  | Tpat_tuple l -> List.iter (sub.pat sub) l
+  | Tpat_construct (_, _, l) -> List.iter (sub.pat sub) l
+  | Tpat_variant (_, po, _) -> Option.iter (sub.pat sub) po
+  | Tpat_record (l, _) -> List.iter (fun (_, _, i) -> sub.pat sub i) l
+  | Tpat_array l -> List.iter (sub.pat sub) l
+  | Tpat_or (p1, p2, _) ->
+    sub.pat sub p1;
+    sub.pat sub p2
+  | Tpat_alias (p, _, _) -> sub.pat sub p
+  | Tpat_lazy p -> sub.pat sub p
+
+let expr sub {exp_extra; exp_desc; exp_env; _} =
+  let extra = function
+    | Texp_constraint cty -> sub.typ sub cty
+    | Texp_coerce ((), cty2) -> sub.typ sub cty2
+    | Texp_newtype _ -> ()
+    | Texp_poly cto -> Option.iter (sub.typ sub) cto
+    | Texp_open (_, _, _, _) -> ()
+  in
+  List.iter (fun (e, _, _) -> extra e) exp_extra;
+  sub.env sub exp_env;
+  match exp_desc with
+  | Texp_ident _ -> ()
+  | Texp_constant _ -> ()
+  | Texp_let (rec_flag, list, exp) ->
+    sub.value_bindings sub (rec_flag, list);
+    sub.expr sub exp
+  | Texp_function {cases; _} -> sub.cases sub cases
+  | Texp_apply (exp, list) ->
+    sub.expr sub exp;
+    List.iter (fun (_, o) -> Option.iter (sub.expr sub) o) list
+  | Texp_match (exp, list1, list2, _) ->
+    sub.expr sub exp;
+    sub.cases sub list1;
+    sub.cases sub list2
+  | Texp_try (exp, cases) ->
+    sub.expr sub exp;
+    sub.cases sub cases
+  | Texp_tuple list -> List.iter (sub.expr sub) list
+  | Texp_construct (_, _, args) -> List.iter (sub.expr sub) args
+  | Texp_variant (_, expo) -> Option.iter (sub.expr sub) expo
+  | Texp_record {fields; extended_expression; _} ->
+    Array.iter
+      (function
+        | _, Kept _ -> ()
+        | _, Overridden (_, exp) -> sub.expr sub exp)
+      fields;
+    Option.iter (sub.expr sub) extended_expression
+  | Texp_field (exp, _, _) -> sub.expr sub exp
+  | Texp_setfield (exp1, _, _, exp2) ->
+    sub.expr sub exp1;
+    sub.expr sub exp2
+  | Texp_array list -> List.iter (sub.expr sub) list
+  | Texp_ifthenelse (exp1, exp2, expo) ->
+    sub.expr sub exp1;
+    sub.expr sub exp2;
+    Option.iter (sub.expr sub) expo
+  | Texp_sequence (exp1, exp2) ->
+    sub.expr sub exp1;
+    sub.expr sub exp2
+  | Texp_while (exp1, exp2) ->
+    sub.expr sub exp1;
+    sub.expr sub exp2
+  | Texp_for (_, _, exp1, exp2, _, exp3) ->
+    sub.expr sub exp1;
+    sub.expr sub exp2;
+    sub.expr sub exp3
+  | Texp_send (exp, _, expo) ->
+    sub.expr sub exp;
+    Option.iter (sub.expr sub) expo
+  | Texp_new _ -> ()
+  | Texp_instvar _ -> ()
+  | Texp_setinstvar _ -> ()
+  | Texp_override _ -> ()
+  | Texp_letmodule (_, _, mexpr, exp) ->
+    sub.module_expr sub mexpr;
+    sub.expr sub exp
+  | Texp_letexception (cd, exp) ->
+    sub.extension_constructor sub cd;
+    sub.expr sub exp
+  | Texp_assert exp -> sub.expr sub exp
+  | Texp_lazy exp -> sub.expr sub exp
+  | Texp_object _ -> ()
+  | Texp_pack mexpr -> sub.module_expr sub mexpr
+  | Texp_unreachable -> ()
+  | Texp_extension_constructor _ -> ()
+
+let package_type sub {pack_fields; _} =
+  List.iter (fun (_, p) -> sub.typ sub p) pack_fields
+
+let signature sub {sig_items; sig_final_env; _} =
+  sub.env sub sig_final_env;
+  List.iter (sub.signature_item sub) sig_items
+
+let signature_item sub {sig_desc; sig_env; _} =
+  sub.env sub sig_env;
+  match sig_desc with
+  | Tsig_value v -> sub.value_description sub v
+  | Tsig_type (rf, tdl) -> sub.type_declarations sub (rf, tdl)
+  | Tsig_typext te -> sub.type_extension sub te
+  | Tsig_exception ext -> sub.extension_constructor sub ext
+  | Tsig_module x -> sub.module_declaration sub x
+  | Tsig_recmodule list -> List.iter (sub.module_declaration sub) list
+  | Tsig_modtype x -> sub.module_type_declaration sub x
+  | Tsig_include incl -> include_infos (sub.module_type sub) incl
+  | Tsig_class () -> ()
+  | Tsig_class_type () -> ()
+  | Tsig_open _od -> ()
+  | Tsig_attribute _ -> ()
+
+let module_type sub {mty_desc; mty_env; _} =
+  sub.env sub mty_env;
+  match mty_desc with
+  | Tmty_ident _ -> ()
+  | Tmty_alias _ -> ()
+  | Tmty_signature sg -> sub.signature sub sg
+  | Tmty_functor (_, _, mtype1, mtype2) ->
+    Option.iter (sub.module_type sub) mtype1;
+    sub.module_type sub mtype2
+  | Tmty_with (mtype, list) ->
+    sub.module_type sub mtype;
+    List.iter (fun (_, _, e) -> sub.with_constraint sub e) list
+  | Tmty_typeof mexpr -> sub.module_expr sub mexpr
+
+let with_constraint sub = function
+  | Twith_type decl -> sub.type_declaration sub decl
+  | Twith_typesubst decl -> sub.type_declaration sub decl
+  | Twith_module _ -> ()
+  | Twith_modsubst _ -> ()
+
+let module_coercion sub = function
+  | Tcoerce_none -> ()
+  | Tcoerce_functor (c1, c2) ->
+    sub.module_coercion sub c1;
+    sub.module_coercion sub c2
+  | Tcoerce_alias (_, c1) -> sub.module_coercion sub c1
+  | Tcoerce_structure (l1, l2, _) ->
+    List.iter (fun (_, c) -> sub.module_coercion sub c) l1;
+    List.iter (fun (_, _, c) -> sub.module_coercion sub c) l2
+  | Tcoerce_primitive {pc_env; _} -> sub.env sub pc_env
+
+let module_expr sub {mod_desc; mod_env; _} =
+  sub.env sub mod_env;
+  match mod_desc with
+  | Tmod_ident _ -> ()
+  | Tmod_structure st -> sub.structure sub st
+  | Tmod_functor (_, _, mtype, mexpr) ->
+    Option.iter (sub.module_type sub) mtype;
+    sub.module_expr sub mexpr
+  | Tmod_apply (mexp1, mexp2, c) ->
+    sub.module_expr sub mexp1;
+    sub.module_expr sub mexp2;
+    sub.module_coercion sub c
+  | Tmod_constraint (mexpr, _, Tmodtype_implicit, c) ->
+    sub.module_expr sub mexpr;
+    sub.module_coercion sub c
+  | Tmod_constraint (mexpr, _, Tmodtype_explicit mtype, c) ->
+    sub.module_expr sub mexpr;
+    sub.module_type sub mtype;
+    sub.module_coercion sub c
+  | Tmod_unpack (exp, _) -> sub.expr sub exp
+
+let module_binding sub {mb_expr; _} = sub.module_expr sub mb_expr
+
+let typ sub {ctyp_desc; ctyp_env; _} =
+  sub.env sub ctyp_env;
+  match ctyp_desc with
+  | Ttyp_any -> ()
+  | Ttyp_var _ -> ()
+  | Ttyp_arrow (_, ct1, ct2) ->
+    sub.typ sub ct1;
+    sub.typ sub ct2
+  | Ttyp_tuple list -> List.iter (sub.typ sub) list
+  | Ttyp_constr (_, _, list) -> List.iter (sub.typ sub) list
+  | Ttyp_object (list, _) -> List.iter (sub.object_field sub) list
+  | Ttyp_class () -> ()
+  | Ttyp_alias (ct, _) -> sub.typ sub ct
+  | Ttyp_variant (list, _, _) -> List.iter (sub.row_field sub) list
+  | Ttyp_poly (_, ct) -> sub.typ sub ct
+  | Ttyp_package pack -> sub.package_type sub pack
+
+let row_field sub = function
+  | Ttag (_label, _attrs, _bool, list) -> List.iter (sub.typ sub) list
+  | Tinherit ct -> sub.typ sub ct
+
+let object_field sub = function
+  | OTtag (_, _, ct) | OTinherit ct -> sub.typ sub ct
+
+let value_bindings sub (_, list) = List.iter (sub.value_binding sub) list
+let cases sub l = List.iter (sub.case sub) l
+
+let case sub {c_lhs; c_guard; c_rhs} =
+  sub.pat sub c_lhs;
+  Option.iter (sub.expr sub) c_guard;
+  sub.expr sub c_rhs
+
+let value_binding sub {vb_pat; vb_expr; _} =
+  sub.pat sub vb_pat;
+  sub.expr sub vb_expr
+
+let env _sub _ = ()
+
+let default_iterator =
+  {
+    case;
+    cases;
+    env;
+    expr;
+    extension_constructor;
+    module_binding;
+    module_coercion;
+    module_declaration;
+    module_expr;
+    module_type;
+    module_type_declaration;
+    package_type;
+    pat;
+    object_field;
+    row_field;
+    signature;
+    signature_item;
+    structure;
+    structure_item;
+    typ;
+    type_declaration;
+    type_declarations;
+    type_extension;
+    type_kind;
+    value_binding;
+    value_bindings;
+    value_description;
+    with_constraint;
+  }
diff --git a/compiler/ml/tast_iterator.mli b/compiler/ml/tast_iterator.mli
new file mode 100644
index 0000000000..85df043ff8
--- /dev/null
+++ b/compiler/ml/tast_iterator.mli
@@ -0,0 +1,54 @@
+(**************************************************************************)
+(*                                                                        *)
+(*                                 OCaml                                  *)
+(*                                                                        *)
+(*                           Isaac "Izzy" Avram                           *)
+(*                                                                        *)
+(*   Copyright 2019 Institut National de Recherche en Informatique et     *)
+(*     en Automatique.                                                    *)
+(*                                                                        *)
+(*   All rights reserved.  This file is distributed under the terms of    *)
+(*   the GNU Lesser General Public License version 2.1, with the          *)
+(*   special exception on linking described in the file LICENSE.          *)
+(*                                                                        *)
+(**************************************************************************)
+
+(**
+Allows the implementation of typed tree inspection using open recursion
+*)
+
+open Asttypes
+open Typedtree
+
+type iterator = {
+  case: iterator -> case -> unit;
+  cases: iterator -> case list -> unit;
+  env: iterator -> Env.t -> unit;
+  expr: iterator -> expression -> unit;
+  extension_constructor: iterator -> extension_constructor -> unit;
+  module_binding: iterator -> module_binding -> unit;
+  module_coercion: iterator -> module_coercion -> unit;
+  module_declaration: iterator -> module_declaration -> unit;
+  module_expr: iterator -> module_expr -> unit;
+  module_type: iterator -> module_type -> unit;
+  module_type_declaration: iterator -> module_type_declaration -> unit;
+  package_type: iterator -> package_type -> unit;
+  pat: iterator -> pattern -> unit;
+  row_field: iterator -> row_field -> unit;
+  object_field: iterator -> object_field -> unit;
+  signature: iterator -> signature -> unit;
+  signature_item: iterator -> signature_item -> unit;
+  structure: iterator -> structure -> unit;
+  structure_item: iterator -> structure_item -> unit;
+  typ: iterator -> core_type -> unit;
+  type_declaration: iterator -> type_declaration -> unit;
+  type_declarations: iterator -> rec_flag * type_declaration list -> unit;
+  type_extension: iterator -> type_extension -> unit;
+  type_kind: iterator -> type_kind -> unit;
+  value_binding: iterator -> value_binding -> unit;
+  value_bindings: iterator -> rec_flag * value_binding list -> unit;
+  value_description: iterator -> value_description -> unit;
+  with_constraint: iterator -> with_constraint -> unit;
+}
+
+val default_iterator : iterator
diff --git a/compiler/syntax/cli/dune b/compiler/syntax/cli/dune
index c7ebe8a40d..ba5f1ea4ce 100644
--- a/compiler/syntax/cli/dune
+++ b/compiler/syntax/cli/dune
@@ -6,6 +6,7 @@
 (executable
  (name res_cli)
  (public_name res_parser)
+ (package rescript)
  (enabled_if
   (<> %{profile} browser))
  (flags
diff --git a/compiler/syntax/src/res_multi_printer.mli b/compiler/syntax/src/res_multi_printer.mli
index bb66106900..c6b1d07cb8 100644
--- a/compiler/syntax/src/res_multi_printer.mli
+++ b/compiler/syntax/src/res_multi_printer.mli
@@ -1,3 +1,5 @@
+val default_print_width : int [@@live]
+
 (* Interface to print source code to res.
  * Takes a filename called "input" and returns the corresponding formatted res syntax *)
 val print : ?ignore_parse_errors:bool -> string -> string [@@dead "+print"]
diff --git a/dune b/dune
index f4b1eadb7b..91a5df6eca 100644
--- a/dune
+++ b/dune
@@ -1 +1 @@
-(dirs compiler tests)
+(dirs compiler tests analysis tools)
diff --git a/dune-project b/dune-project
index 2fa8675931..0532a0380d 100644
--- a/dune-project
+++ b/dune-project
@@ -41,3 +41,24 @@
   (reanalyze
    (= 2.25.1))
   dune))
+
+(package
+ (name analysis)
+ (synopsis "ReScript Analysis")
+ (depends
+  (ocaml
+   (>= 4.10))
+  (cppo
+   (= 1.6.9))
+  dune))
+
+(package
+ (name tools)
+ (synopsis "ReScript Tools")
+ (depends
+  (ocaml
+   (>= 4.10))
+  (cppo
+   (= 1.6.9))
+  analysis
+  dune))
diff --git a/lib/es6/RescriptTools.js b/lib/es6/RescriptTools.js
new file mode 100644
index 0000000000..68a040855e
--- /dev/null
+++ b/lib/es6/RescriptTools.js
@@ -0,0 +1,13 @@
+
+
+import * as Bin_pathJs from "../../cli/bin_path.js";
+
+let binaryPath = Bin_pathJs.rescript_tools_exe;
+
+let Docgen;
+
+export {
+  Docgen,
+  binaryPath,
+}
+/* binaryPath Not a pure module */
diff --git a/lib/es6/RescriptTools_Docgen.js b/lib/es6/RescriptTools_Docgen.js
new file mode 100644
index 0000000000..66f679a790
--- /dev/null
+++ b/lib/es6/RescriptTools_Docgen.js
@@ -0,0 +1,11 @@
+
+
+
+function decodeFromJson(prim) {
+  return prim;
+}
+
+export {
+  decodeFromJson,
+}
+/* No side effect */
diff --git a/lib/js/RescriptTools.js b/lib/js/RescriptTools.js
new file mode 100644
index 0000000000..cefe75eb6f
--- /dev/null
+++ b/lib/js/RescriptTools.js
@@ -0,0 +1,11 @@
+'use strict';
+
+let Bin_pathJs = require("../../cli/bin_path.js");
+
+let binaryPath = Bin_pathJs.rescript_tools_exe;
+
+let Docgen;
+
+exports.Docgen = Docgen;
+exports.binaryPath = binaryPath;
+/* binaryPath Not a pure module */
diff --git a/lib/js/RescriptTools_Docgen.js b/lib/js/RescriptTools_Docgen.js
new file mode 100644
index 0000000000..c11bf77584
--- /dev/null
+++ b/lib/js/RescriptTools_Docgen.js
@@ -0,0 +1,9 @@
+'use strict';
+
+
+function decodeFromJson(prim) {
+  return prim;
+}
+
+exports.decodeFromJson = decodeFromJson;
+/* No side effect */
diff --git a/package.json b/package.json
index 6b4f07f1e7..c1c9f1d857 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "bsc": "cli/bsc",
     "bstracing": "lib/bstracing",
     "rescript": "cli/rescript",
+    "rescript-tools": "cli/rescript-tools",
     "rewatch": "cli/rewatch"
   },
   "scripts": {
diff --git a/packages/artifacts.txt b/packages/artifacts.txt
index ac398baaae..7ed7e6d183 100644
--- a/packages/artifacts.txt
+++ b/packages/artifacts.txt
@@ -7,6 +7,7 @@ README.md
 cli/bin_path.js
 cli/bsc
 cli/rescript
+cli/rescript-tools
 cli/rescript_arg.js
 cli/rescript_bsb.js
 cli/rescript_dump.js
@@ -16,11 +17,15 @@ cli/rewatch
 darwin/bsb_helper.exe
 darwin/bsc.exe
 darwin/ninja.exe
+darwin/rescript-editor-analysis.exe
+darwin/rescript-tools.exe
 darwin/rescript.exe
 darwin/rewatch.exe
 darwinarm64/bsb_helper.exe
 darwinarm64/bsc.exe
 darwinarm64/ninja.exe
+darwinarm64/rescript-editor-analysis.exe
+darwinarm64/rescript-tools.exe
 darwinarm64/rescript.exe
 darwinarm64/rewatch.exe
 docs/docson/build-schema.json
@@ -180,6 +185,8 @@ lib/es6/Primitive_string_extern.js
 lib/es6/Primitive_util.js
 lib/es6/Promise.js
 lib/es6/RegExp.js
+lib/es6/RescriptTools.js
+lib/es6/RescriptTools_Docgen.js
 lib/es6/Result.js
 lib/es6/Set.js
 lib/es6/String.js
@@ -348,6 +355,8 @@ lib/js/Primitive_string_extern.js
 lib/js/Primitive_util.js
 lib/js/Promise.js
 lib/js/RegExp.js
+lib/js/RescriptTools.js
+lib/js/RescriptTools_Docgen.js
 lib/js/Result.js
 lib/js/Set.js
 lib/js/String.js
@@ -1129,6 +1138,16 @@ lib/ocaml/RegExp.cmt
 lib/ocaml/RegExp.cmti
 lib/ocaml/RegExp.res
 lib/ocaml/RegExp.resi
+lib/ocaml/RescriptTools.cmi
+lib/ocaml/RescriptTools.cmj
+lib/ocaml/RescriptTools.cmt
+lib/ocaml/RescriptTools.res
+lib/ocaml/RescriptTools_Docgen.cmi
+lib/ocaml/RescriptTools_Docgen.cmj
+lib/ocaml/RescriptTools_Docgen.cmt
+lib/ocaml/RescriptTools_Docgen.cmti
+lib/ocaml/RescriptTools_Docgen.res
+lib/ocaml/RescriptTools_Docgen.resi
 lib/ocaml/Result.cmi
 lib/ocaml/Result.cmj
 lib/ocaml/Result.cmt
@@ -1188,11 +1207,15 @@ lib/ocaml/WeakSet.res
 linux/bsb_helper.exe
 linux/bsc.exe
 linux/ninja.exe
+linux/rescript-editor-analysis.exe
+linux/rescript-tools.exe
 linux/rescript.exe
 linux/rewatch.exe
 linuxarm64/bsb_helper.exe
 linuxarm64/bsc.exe
 linuxarm64/ninja.exe
+linuxarm64/rescript-editor-analysis.exe
+linuxarm64/rescript-tools.exe
 linuxarm64/rescript.exe
 linuxarm64/rewatch.exe
 ninja.COPYING
@@ -1200,5 +1223,7 @@ package.json
 win32/bsb_helper.exe
 win32/bsc.exe
 win32/ninja.exe
+win32/rescript-editor-analysis.exe
+win32/rescript-tools.exe
 win32/rescript.exe
 win32/rewatch.exe
\ No newline at end of file
diff --git a/runtime/RescriptTools.res b/runtime/RescriptTools.res
new file mode 100644
index 0000000000..a636f55225
--- /dev/null
+++ b/runtime/RescriptTools.res
@@ -0,0 +1,14 @@
+module Docgen = RescriptTools_Docgen
+
+/** Returns the full file system path to the `rescript-tools` binary for the current platform, side stepping the JS that wraps the CLI.
+ 
+ You can use this when you're already running a JS process and want to avoid the overhead of starting another one.
+
+ ## Examples
+ ```rescript
+ // Prints the current ReScript Tools version.
+ let stringifiedJson = ChildProcess.execFileSync(RescriptTools.binaryPath, ["-v"])
+ ```
+ */
+@module("../../cli/bin_path.js")
+external binaryPath: string = "rescript_tools_exe"
diff --git a/runtime/RescriptTools_Docgen.res b/runtime/RescriptTools_Docgen.res
new file mode 100644
index 0000000000..c6b7d450c1
--- /dev/null
+++ b/runtime/RescriptTools_Docgen.res
@@ -0,0 +1,92 @@
+type field = {
+  name: string,
+  docstrings: array<string>,
+  signature: string,
+  optional: bool,
+  deprecated?: string,
+}
+
+@tag("kind")
+type constructorPayload = | @as("inlineRecord") InlineRecord({fields: array<field>})
+
+type constructor = {
+  name: string,
+  docstrings: array<string>,
+  signature: string,
+  deprecated?: string,
+  payload?: constructorPayload,
+}
+
+@tag("kind")
+type detail =
+  | @as("record") Record({items: array<field>})
+  | @as("variant") Variant({items: array<constructor>})
+
+type source = {
+  filepath: string,
+  line: int,
+  col: int,
+}
+
+@tag("kind")
+type rec item =
+  | @as("value")
+  Value({
+      id: string,
+      docstrings: array<string>,
+      signature: string,
+      name: string,
+      deprecated?: string,
+      source: source,
+    })
+  | @as("type")
+  Type({
+      id: string,
+      docstrings: array<string>,
+      signature: string,
+      name: string,
+      deprecated?: string,
+      source: source,
+      /** Additional documentation for constructors and record fields, if available. */
+      detail?: detail,
+    })
+  | @as("module")
+  Module({
+      id: string,
+      docstrings: array<string>,
+      deprecated?: string,
+      name: string,
+      moduletypeid?: string,
+      source: source,
+      items: array<item>,
+    })
+  | @as("moduleType")
+  ModuleType({
+      id: string,
+      docstrings: array<string>,
+      deprecated?: string,
+      name: string,
+      source: source,
+      items: array<item>,
+    })
+  | @as("moduleAlias")
+  ModuleAlias({
+      id: string,
+      docstrings: array<string>,
+      name: string,
+      source: source,
+      items: array<item>,
+    })
+
+type doc = {
+  name: string,
+  deprecated: option<string>,
+  docstrings: array<string>,
+  source: source,
+  items: array<item>,
+}
+
+/**
+`decodeFromJson(json)` parse JSON generated from `restool doc` command
+*/
+external decodeFromJson: Js.Json.t => doc = "%identity"
diff --git a/runtime/RescriptTools_Docgen.resi b/runtime/RescriptTools_Docgen.resi
new file mode 100644
index 0000000000..271f65f993
--- /dev/null
+++ b/runtime/RescriptTools_Docgen.resi
@@ -0,0 +1,88 @@
+type field = {
+  name: string,
+  docstrings: array<string>,
+  signature: string,
+  optional: bool,
+  deprecated?: string,
+}
+
+@tag("kind")
+type constructorPayload = | @as("inlineRecord") InlineRecord({fields: array<field>})
+
+type constructor = {
+  name: string,
+  docstrings: array<string>,
+  signature: string,
+  deprecated?: string,
+  payload?: constructorPayload,
+}
+@tag("kind")
+type detail =
+  | @as("record") Record({items: array<field>})
+  | @as("variant") Variant({items: array<constructor>})
+
+type source = {
+  filepath: string,
+  line: int,
+  col: int,
+}
+
+@tag("kind")
+type rec item =
+  | @as("value")
+  Value({
+      id: string,
+      docstrings: array<string>,
+      signature: string,
+      name: string,
+      deprecated?: string,
+      source: source,
+    })
+  | @as("type")
+  Type({
+      id: string,
+      docstrings: array<string>,
+      signature: string,
+      name: string,
+      deprecated?: string,
+      source: source,
+      /** Additional documentation for constructors and record fields, if available. */
+      detail?: detail,
+    })
+  | @as("module")
+  Module({
+      id: string,
+      docstrings: array<string>,
+      deprecated?: string,
+      name: string,
+      moduletypeid?: string,
+      source: source,
+      items: array<item>,
+    })
+  | @as("moduleType")
+  ModuleType({
+      id: string,
+      docstrings: array<string>,
+      deprecated?: string,
+      name: string,
+      source: source,
+      items: array<item>,
+    })
+  | @as("moduleAlias")
+  ModuleAlias({
+      id: string,
+      docstrings: array<string>,
+      name: string,
+      source: source,
+      items: array<item>,
+    })
+
+type doc = {
+  name: string,
+  deprecated: option<string>,
+  docstrings: array<string>,
+  source: source,
+  items: array<item>,
+}
+
+let decodeFromJson: Js.Json.t => doc
diff --git a/scripts/copyExes.js b/scripts/copyExes.js
index e14b21d8fb..feae5e9b76 100755
--- a/scripts/copyExes.js
+++ b/scripts/copyExes.js
@@ -33,6 +33,8 @@ function copyExe(dir, exe) {
 
 if (process.argv.includes("-all") || process.argv.includes("-compiler")) {
   copyExe(duneBinDir, "rescript");
+  copyExe(duneBinDir, "rescript-editor-analysis");
+  copyExe(duneBinDir, "rescript-tools");
   copyExe(duneBinDir, "bsc");
   copyExe(duneBinDir, "bsb_helper");
 }
diff --git a/scripts/format.sh b/scripts/format.sh
index 8b474b2ffd..8d0265abe2 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -4,5 +4,5 @@ shopt -s extglob
 
 dune build @fmt --auto-promote
 
-files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -path "tests/syntax_*/*" ! -path "tests/gentype_tests/typescript-react-example/node_modules/*")
+files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -path "tests/syntax_*" ! -path "tests/analysis_tests/tests*" ! -path "tests/gentype_tests/typescript-react-example/node_modules")
 ./cli/rescript format $files
diff --git a/scripts/format_check.sh b/scripts/format_check.sh
index 440a0ba0fd..588fcf4a3e 100755
--- a/scripts/format_check.sh
+++ b/scripts/format_check.sh
@@ -17,7 +17,7 @@ case "$(uname -s)" in
     fi
 
     echo "Checking ReScript code formatting..."
-    files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -path "tests/syntax_*/*" ! -path "tests/gentype_tests/typescript-react-example/node_modules/*")
+    files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -path "tests/syntax_*" ! -path "tests/analysis_tests/tests*" ! -path "tests/gentype_tests/typescript-react-example/node_modules")
     if ./cli/rescript format -check $files; then
       printf "${successGreen}✅ ReScript code formatting ok.${reset}\n"
     else
diff --git a/scripts/npmPack.js b/scripts/npmPack.js
index a4eae27d39..3221a5d41b 100755
--- a/scripts/npmPack.js
+++ b/scripts/npmPack.js
@@ -68,6 +68,8 @@ function getFilesAddedByCI() {
     "bsc.exe",
     "ninja.exe",
     "rescript.exe",
+    "rescript-editor-analysis.exe",
+    "rescript-tools.exe",
     "rewatch.exe",
   ];
 
diff --git a/tests/analysis_tests/Makefile b/tests/analysis_tests/Makefile
new file mode 100644
index 0000000000..ea4a1c02e1
--- /dev/null
+++ b/tests/analysis_tests/Makefile
@@ -0,0 +1,19 @@
+SHELL = /bin/bash
+
+test-analysis-binary:
+	make -C tests test
+	make -C tests-generic-jsx-transform test
+	make -C tests-incremental-typechecking test
+
+test-reanalyze:
+	make -C tests-reanalyze test
+
+test: test-analysis-binary test-reanalyze
+
+clean:
+	make -C tests clean
+	make -C tests-generic-jsx-transform clean
+	make -C tests-incremental-typechecking clean
+	make -C tests-reanalyze clean
+
+.PHONY: test-analysis-binary test-reanalyze clean test
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/Makefile b/tests/analysis_tests/tests-generic-jsx-transform/Makefile
new file mode 100644
index 0000000000..5084c67abd
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript
+
+test: build
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := test
+
+.PHONY: clean test
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/package-lock.json b/tests/analysis_tests/tests-generic-jsx-transform/package-lock.json
new file mode 100644
index 0000000000..8966274c5d
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/package-lock.json
@@ -0,0 +1,33 @@
+{
+  "name": "tests-generic-jsx-transform",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "rescript": "11.1.0-rc.2"
+      }
+    },
+    "node_modules/rescript": {
+      "version": "11.1.0-rc.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0-rc.2.tgz",
+      "integrity": "sha512-kCUtmsODEUF1Eth5ppc+yIK79HLI7CwRs1R4iopDek4FC58IqHSLT3K1XHGB39YCWuOuV9WMly+wksHRJcSLcw==",
+      "hasInstallScript": true,
+      "bin": {
+        "bsc": "bsc",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    }
+  },
+  "dependencies": {
+    "rescript": {
+      "version": "11.1.0-rc.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0-rc.2.tgz",
+      "integrity": "sha512-kCUtmsODEUF1Eth5ppc+yIK79HLI7CwRs1R4iopDek4FC58IqHSLT3K1XHGB39YCWuOuV9WMly+wksHRJcSLcw=="
+    }
+  }
+}
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/package.json b/tests/analysis_tests/tests-generic-jsx-transform/package.json
new file mode 100644
index 0000000000..000126246c
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/package.json
@@ -0,0 +1,10 @@
+{
+  "scripts": {
+    "build": "rescript",
+    "clean": "rescript clean -with-deps"
+  },
+  "private": true,
+  "dependencies": {
+    "rescript": "11.1.0-rc.2"
+  }
+}
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/rescript.json b/tests/analysis_tests/tests-generic-jsx-transform/rescript.json
new file mode 100644
index 0000000000..22fafc0470
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/rescript.json
@@ -0,0 +1,11 @@
+{
+  "name": "test-generic-jsx-transform",
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "bsc-flags": ["-w -33-44-8"],
+  "jsx": { "module": "GenericJsx" }
+}
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsx.res b/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsx.res
new file mode 100644
index 0000000000..d5f15273de
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsx.res
@@ -0,0 +1,63 @@
+/* Below is a number of aliases to the common `Jsx` module */
+type element = Jsx.element
+
+type component<'props> = Jsx.component<'props>
+
+type componentLike<'props, 'return> = Jsx.componentLike<'props, 'return>
+
+@module("preact")
+external jsx: (component<'props>, 'props) => element = "jsx"
+
+@module("preact")
+external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx"
+
+@module("preact")
+external jsxs: (component<'props>, 'props) => element = "jsxs"
+
+@module("preact")
+external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs"
+
+/* These identity functions and static values below are optional, but lets 
+you move things easily to the `element` type. The only required thing to 
+define though is `array`, which the JSX transform will output. */
+external array: array<element> => element = "%identity"
+@val external null: element = "null"
+
+external float: float => element = "%identity"
+external int: int => element = "%identity"
+external string: string => element = "%identity"
+
+/* These are needed for Fragment (<> </>) support */
+type fragmentProps = {children?: element}
+
+@module("preact") external jsxFragment: component<fragmentProps> = "Fragment"
+
+/* The Elements module is the equivalent to the ReactDOM module in React. This holds things relevant to _lowercase_ JSX elements. */
+module Elements = {
+  /* Here you can control what props lowercase JSX elements should have. 
+  A base that the React JSX transform uses is provided via JsxDOM.domProps, 
+  but you can make this anything. The editor tooling will support 
+  autocompletion etc for your specific type. */
+  type props = {
+    testing?: bool,
+    test2?: string,
+    children?: element
+  }
+
+  @module("preact")
+  external jsx: (string, props) => Jsx.element = "jsx"
+
+  @module("preact")
+  external div: (string, props) => Jsx.element = "jsx"
+
+  @module("preact")
+  external jsxKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsx"
+
+  @module("preact")
+  external jsxs: (string, props) => Jsx.element = "jsxs"
+
+  @module("preact")
+  external jsxsKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsxs"
+
+  external someElement: element => option<element> = "%identity"
+}
\ No newline at end of file
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsxCompletion.res b/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsxCompletion.res
new file mode 100644
index 0000000000..babce4b57e
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/src/GenericJsxCompletion.res
@@ -0,0 +1,25 @@
+// <div
+//      ^com
+
+// <div testing={}
+//               ^com
+
+module SomeComponent = {
+  @jsx.component
+  let make = (~someProp) => {
+    let someString = ""
+    let someInt = 12
+    let someArr = [GenericJsx.null]
+    ignore(someInt)
+    ignore(someArr)
+    // someString->st
+    //               ^com
+    open GenericJsx
+    <div>
+      {GenericJsx.string(someProp ++ someString)}
+      <div> {GenericJsx.null} </div>
+      // {someString->st}
+      //                ^com
+    </div>
+  }
+}
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsx.res.txt b/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsx.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt b/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt
new file mode 100644
index 0000000000..675fbdde43
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt
@@ -0,0 +1,138 @@
+Complete src/GenericJsxCompletion.res 0:8
+posCursor:[0:8] posNoWhite:[0:6] Found expr:[0:4->0:7]
+JSX <div:[0:4->0:7] > _children:None
+Completable: Cjsx([div], "", [])
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Pervasives
+Path GenericJsx.Elements.props
+[{
+    "label": "testing",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "test2",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }, {
+    "label": "children",
+    "kind": 4,
+    "tags": [],
+    "detail": "element",
+    "documentation": null
+  }]
+
+Complete src/GenericJsxCompletion.res 3:17
+posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:4->3:18]
+JSX <div:[3:4->3:7] testing[3:8->3:15]=...[3:16->3:18]> _children:None
+Completable: Cexpression CJsxPropValue [div] testing->recordBody
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Pervasives
+ContextPath CJsxPropValue [div] testing
+Path GenericJsx.Elements.props
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/GenericJsxCompletion.res 14:21
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[8:13->23:3]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[8:14->23:3]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[9:4->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[10:4->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[11:4->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[12:4->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[13:4->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[14:7->22:10]
+posCursor:[14:21] posNoWhite:[14:20] Found expr:[14:7->14:21]
+Completable: Cpath Value[someString]->st <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Pervasives
+ContextPath Value[someString]->st <<jsx>>
+ContextPath Value[someString]
+Path someString
+CPPipe env:GenericJsxCompletion
+Path Js.String2.st
+[{
+    "label": "GenericJsx.string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/GenericJsxCompletion.res 20:24
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[8:13->23:3]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[8:14->23:3]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[9:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[10:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[11:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[12:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[13:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[16:4->22:10]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[17:5->22:10]
+JSX <div:[17:5->17:8] > _children:17:8
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[17:8->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[18:7->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[19:7->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[19:7->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->22:4]
+posCursor:[20:24] posNoWhite:[20:23] Found expr:[20:10->20:24]
+Completable: Cpath Value[someString]->st <<jsx>>
+Raw opens: 1 GenericJsx.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Pervasives GenericJsx
+ContextPath Value[someString]->st <<jsx>>
+ContextPath Value[someString]
+Path someString
+CPPipe env:GenericJsxCompletion
+Path Js.String2.st
+[{
+    "label": "string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
diff --git a/tests/analysis_tests/tests-generic-jsx-transform/test.sh b/tests/analysis_tests/tests-generic-jsx-transform/test.sh
new file mode 100755
index 0000000000..c58c8e70b9
--- /dev/null
+++ b/tests/analysis_tests/tests-generic-jsx-transform/test.sh
@@ -0,0 +1,21 @@
+for file in src/*.res; do
+  output="$(dirname $file)/expected/$(basename $file).txt"
+  ../../../_build/install/default/bin/rescript-editor-analysis test $file &> $output
+  # CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+  if [ "$RUNNER_OS" == "Windows" ]; then
+    perl -pi -e 's/\r\n/\n/g' -- $output
+  fi
+done
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified src/expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff src/expected
+  exit 1
+fi
diff --git a/tests/analysis_tests/tests-incremental-typechecking/Makefile b/tests/analysis_tests/tests-incremental-typechecking/Makefile
new file mode 100644
index 0000000000..33e0783fb0
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript > /dev/null || true
+
+test: build
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := test
+
+.PHONY: clean test
diff --git a/tests/analysis_tests/tests-incremental-typechecking/package-lock.json b/tests/analysis_tests/tests-incremental-typechecking/package-lock.json
new file mode 100644
index 0000000000..b3161b7993
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/package-lock.json
@@ -0,0 +1,33 @@
+{
+  "name": "tests-incremental-typechecking",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "rescript": "11.1.0-rc.2"
+      }
+    },
+    "node_modules/rescript": {
+      "version": "11.1.0-rc.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0-rc.2.tgz",
+      "integrity": "sha512-kCUtmsODEUF1Eth5ppc+yIK79HLI7CwRs1R4iopDek4FC58IqHSLT3K1XHGB39YCWuOuV9WMly+wksHRJcSLcw==",
+      "hasInstallScript": true,
+      "bin": {
+        "bsc": "bsc",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    }
+  },
+  "dependencies": {
+    "rescript": {
+      "version": "11.1.0-rc.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0-rc.2.tgz",
+      "integrity": "sha512-kCUtmsODEUF1Eth5ppc+yIK79HLI7CwRs1R4iopDek4FC58IqHSLT3K1XHGB39YCWuOuV9WMly+wksHRJcSLcw=="
+    }
+  }
+}
diff --git a/tests/analysis_tests/tests-incremental-typechecking/package.json b/tests/analysis_tests/tests-incremental-typechecking/package.json
new file mode 100644
index 0000000000..000126246c
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/package.json
@@ -0,0 +1,10 @@
+{
+  "scripts": {
+    "build": "rescript",
+    "clean": "rescript clean -with-deps"
+  },
+  "private": true,
+  "dependencies": {
+    "rescript": "11.1.0-rc.2"
+  }
+}
diff --git a/tests/analysis_tests/tests-incremental-typechecking/rescript.json b/tests/analysis_tests/tests-incremental-typechecking/rescript.json
new file mode 100644
index 0000000000..22fafc0470
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/rescript.json
@@ -0,0 +1,11 @@
+{
+  "name": "test-generic-jsx-transform",
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "bsc-flags": ["-w -33-44-8"],
+  "jsx": { "module": "GenericJsx" }
+}
diff --git a/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Json.res b/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Json.res
new file mode 100644
index 0000000000..5173fefec0
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Json.res
@@ -0,0 +1,2 @@
+let x = Js.Json.Array()
+//                    ^com
diff --git a/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Own.res b/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Own.res
new file mode 100644
index 0000000000..b77e1f4ec6
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/src/ConstructorCompletion__Own.res
@@ -0,0 +1,6 @@
+module WithVariant = {
+  type t = One({miss: bool}) | Two(bool)
+}
+
+let x = WithVariant.One()
+//                      ^com
diff --git a/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Json.res.txt b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Json.res.txt
new file mode 100644
index 0000000000..6aeda7c87b
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Json.res.txt
@@ -0,0 +1,20 @@
+Complete src/ConstructorCompletion__Json.res 0:22
+posCursor:[0:22] posNoWhite:[0:21] Found expr:[0:8->0:23]
+Pexp_construct Js
+Json
+Array:[0:8->0:21] [0:21->0:23]
+Completable: Cexpression CTypeAtPos()->variantPayload::Array($0)
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Pervasives
+ContextPath CTypeAtPos()
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "t",
+    "documentation": {"kind": "markdown", "value": " The JSON data structure \n\n```rescript\n@unboxed\ntype t =\n  | Boolean(bool)\n  | @as(null) Null\n  | String(string)\n  | Number(float)\n  | Object(Js.Dict.t<t>)\n  | Array(array<t>)\n```"},
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt
new file mode 100644
index 0000000000..0199e72d8e
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt
@@ -0,0 +1,19 @@
+Complete src/ConstructorCompletion__Own.res 4:24
+posCursor:[4:24] posNoWhite:[4:23] Found expr:[4:8->4:25]
+Pexp_construct WithVariant
+One:[4:8->4:23] [4:23->4:25]
+Completable: Cexpression CTypeAtPos()->variantPayload::One($0)
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Pervasives
+ContextPath CTypeAtPos()
+[{
+    "label": "{}",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests-incremental-typechecking/test.sh b/tests/analysis_tests/tests-incremental-typechecking/test.sh
new file mode 100755
index 0000000000..c58c8e70b9
--- /dev/null
+++ b/tests/analysis_tests/tests-incremental-typechecking/test.sh
@@ -0,0 +1,21 @@
+for file in src/*.res; do
+  output="$(dirname $file)/expected/$(basename $file).txt"
+  ../../../_build/install/default/bin/rescript-editor-analysis test $file &> $output
+  # CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+  if [ "$RUNNER_OS" == "Windows" ]; then
+    perl -pi -e 's/\r\n/\n/g' -- $output
+  fi
+done
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified src/expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff src/expected
+  exit 1
+fi
diff --git a/tests/analysis_tests/tests-reanalyze/.gitignore b/tests/analysis_tests/tests-reanalyze/.gitignore
new file mode 100644
index 0000000000..757acb3713
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/.gitignore
@@ -0,0 +1 @@
+.merlin
diff --git a/tests/analysis_tests/tests-reanalyze/Makefile b/tests/analysis_tests/tests-reanalyze/Makefile
new file mode 100644
index 0000000000..cfb84e2bd6
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+build:
+	make -C deadcode build
+	make -C termination build
+
+test:
+	make -C deadcode test
+	make -C termination test
+
+clean:
+	make -C deadcode clean
+	make -C termination clean
+
+.DEFAULT_GOAL := build
+
+.PHONY: build clean clean test
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/.gitignore b/tests/analysis_tests/tests-reanalyze/deadcode/.gitignore
new file mode 100644
index 0000000000..1ccc52a7fb
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/lib
\ No newline at end of file
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/.watchmanconfig b/tests/analysis_tests/tests-reanalyze/deadcode/.watchmanconfig
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/Makefile b/tests/analysis_tests/tests-reanalyze/deadcode/Makefile
new file mode 100644
index 0000000000..fc76aaa370
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript
+
+test: build node_modules/.bin/rescript
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := build
+
+.PHONY: build clean test
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/bsconfig.json b/tests/analysis_tests/tests-reanalyze/deadcode/bsconfig.json
new file mode 100644
index 0000000000..a0e2baae9a
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/bsconfig.json
@@ -0,0 +1,23 @@
+{
+  "reanalyze": {
+    "analysis": ["dce"],
+    "suppress": [],
+    "unsuppress": [],
+    "transitive": true
+  },
+  "name": "sample-typescript-app",
+  "bsc-flags": ["-bs-super-errors -w a"],
+  "jsx": { "version": 3 },
+  "bs-dependencies": ["@rescript/react", "@glennsl/bs-json"],
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "package-specs": {
+    "module": "es6",
+    "in-source": true
+  },
+  "suffix": ".bs.js"
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt
new file mode 100644
index 0000000000..eb561f9f66
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt
@@ -0,0 +1,4297 @@
+
+  Scanning AutoAnnotate.cmt Source:AutoAnnotate.res
+  addVariantCaseDeclaration R AutoAnnotate.res:1:15 path:+AutoAnnotate.variant
+  addRecordLabelDeclaration variant AutoAnnotate.res:4:15 path:+AutoAnnotate.record
+  addRecordLabelDeclaration r2 AutoAnnotate.res:6:11 path:+AutoAnnotate.r2
+  addRecordLabelDeclaration r3 AutoAnnotate.res:8:11 path:+AutoAnnotate.r3
+  addRecordLabelDeclaration r4 AutoAnnotate.res:10:11 path:+AutoAnnotate.r4
+  addVariantCaseDeclaration R2 AutoAnnotate.res:14:2 path:+AutoAnnotate.annotatedVariant
+  addVariantCaseDeclaration R4 AutoAnnotate.res:15:2 path:+AutoAnnotate.annotatedVariant
+  Scanning BootloaderResource.cmt Source:BootloaderResource.res
+  Scanning BucklescriptAnnotations.cmt Source:BucklescriptAnnotations.res
+  addValueDeclaration +bar BucklescriptAnnotations.res:25:4 path:+BucklescriptAnnotations
+  addValueDeclaration +f BucklescriptAnnotations.res:26:6 path:+BucklescriptAnnotations
+  addValueReference BucklescriptAnnotations.res:26:6 --> BucklescriptAnnotations.res:25:11
+  addValueReference BucklescriptAnnotations.res:25:4 --> BucklescriptAnnotations.res:26:6
+  Scanning ComponentAsProp.cmt Source:ComponentAsProp.res
+  addValueDeclaration +make ComponentAsProp.res:6:4 path:+ComponentAsProp
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: ComponentAsProp.res:7:3
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: ComponentAsProp.res:8:5
+  addValueReference ComponentAsProp.res:9:6 --> ComponentAsProp.res:6:12
+  addValueReference ComponentAsProp.res:10:6 --> ComponentAsProp.res:6:20
+  addValueReference ComponentAsProp.res:12:24 --> ComponentAsProp.res:12:13
+  addValueReference ComponentAsProp.res:13:16 --> React.res:3:0
+  addValueReference ComponentAsProp.res:11:14 --> ComponentAsProp.res:6:34
+  addValueReference ComponentAsProp.res:8:5 --> ReactDOMRe.res:8:0
+  addValueReference ComponentAsProp.res:7:3 --> ReactDOMRe.res:8:0
+  Scanning CreateErrorHandler1.cmt Source:CreateErrorHandler1.res
+  addValueDeclaration +notification CreateErrorHandler1.res:3:6 path:+CreateErrorHandler1.Error1
+  addValueReference CreateErrorHandler1.res:3:6 --> CreateErrorHandler1.res:3:21
+  addValueReference CreateErrorHandler1.res:3:6 --> CreateErrorHandler1.res:3:21
+  addValueReference CreateErrorHandler1.res:8:0 --> ErrorHandler.resi:7:2
+  addValueReference ErrorHandler.resi:3:2 --> CreateErrorHandler1.res:3:6
+  Scanning CreateErrorHandler2.cmt Source:CreateErrorHandler2.res
+  addValueDeclaration +notification CreateErrorHandler2.res:3:6 path:+CreateErrorHandler2.Error2
+  addValueReference CreateErrorHandler2.res:3:6 --> CreateErrorHandler2.res:3:21
+  addValueReference ErrorHandler.resi:3:2 --> CreateErrorHandler2.res:3:6
+  Scanning DeadCodeImplementation.cmt Source:DeadCodeImplementation.res
+  addValueDeclaration +x DeadCodeImplementation.res:2:6 path:+DeadCodeImplementation.M
+  addValueReference DeadCodeInterface.res:2:2 --> DeadCodeImplementation.res:2:6
+  Scanning DeadCodeInterface.cmt Source:DeadCodeInterface.res
+  Scanning DeadExn.cmt Source:DeadExn.res
+  addValueDeclaration +eToplevel DeadExn.res:8:4 path:+DeadExn
+  addValueDeclaration +eInside DeadExn.res:10:4 path:+DeadExn
+  addExceptionDeclaration Etoplevel DeadExn.res:1:0 path:+DeadExn
+  addExceptionDeclaration Einside DeadExn.res:4:2 path:+DeadExn.Inside
+  addExceptionDeclaration DeadE DeadExn.res:7:0 path:+DeadExn
+  addValueReference DeadExn.res:8:4 --> DeadExn.res:1:0
+  addTypeReference DeadExn.res:8:16 --> DeadExn.res:1:0
+  addValueReference DeadExn.res:10:4 --> DeadExn.res:4:2
+  addTypeReference DeadExn.res:10:14 --> DeadExn.res:4:2
+  addValueReference DeadExn.res:12:7 --> DeadExn.res:10:4
+  Scanning DeadExn.cmti Source:DeadExn.resi
+  Scanning DeadRT.cmt Source:DeadRT.res
+  addValueDeclaration +emitModuleAccessPath DeadRT.res:5:8 path:+DeadRT
+  addVariantCaseDeclaration Root DeadRT.res:2:2 path:+DeadRT.moduleAccessPath
+  addVariantCaseDeclaration Kaboom DeadRT.res:3:2 path:+DeadRT.moduleAccessPath
+  addValueReference DeadRT.res:5:8 --> DeadRT.res:7:9
+  addValueReference DeadRT.res:5:8 --> DeadRT.res:5:31
+  addTypeReference DeadRT.res:11:16 --> DeadRT.res:3:2
+  Scanning DeadRT.cmti Source:DeadRT.resi
+  addVariantCaseDeclaration Root DeadRT.resi:2:2 path:DeadRT.moduleAccessPath
+  extendTypeDependencies DeadRT.res:2:2 --> DeadRT.resi:2:2
+  extendTypeDependencies DeadRT.resi:2:2 --> DeadRT.res:2:2
+  addVariantCaseDeclaration Kaboom DeadRT.resi:3:2 path:DeadRT.moduleAccessPath
+  extendTypeDependencies DeadRT.res:3:2 --> DeadRT.resi:3:2
+  extendTypeDependencies DeadRT.resi:3:2 --> DeadRT.res:3:2
+  addTypeReference DeadRT.res:3:2 --> DeadRT.resi:3:2
+  addTypeReference DeadRT.resi:3:2 --> DeadRT.res:3:2
+  addTypeReference DeadRT.res:2:2 --> DeadRT.resi:2:2
+  addTypeReference DeadRT.resi:2:2 --> DeadRT.res:2:2
+  Scanning DeadTest.cmt Source:DeadTest.res
+  addValueDeclaration +fortytwo DeadTest.res:2:4 path:+DeadTest
+  addValueDeclaration +fortyTwoButExported DeadTest.res:5:4 path:+DeadTest
+  addValueDeclaration +thisIsUsedOnce DeadTest.res:7:4 path:+DeadTest
+  addValueDeclaration +thisIsUsedTwice DeadTest.res:10:4 path:+DeadTest
+  addValueDeclaration +thisIsMarkedDead DeadTest.res:15:4 path:+DeadTest
+  addValueDeclaration +thisIsKeptAlive DeadTest.res:17:4 path:+DeadTest
+  addValueDeclaration +thisIsMarkedLive DeadTest.res:20:4 path:+DeadTest
+  addValueDeclaration +thisIsAlsoMarkedDead DeadTest.res:24:6 path:+DeadTest.Inner
+  addValueDeclaration +thisSignatureItemIsDead DeadTest.res:28:2 path:+DeadTest.M
+  addValueDeclaration +a DeadTest.res:36:2 path:+DeadTest.VariantUsedOnlyInImplementation
+  addValueDeclaration +x DeadTest.res:60:2 path:+DeadTest.MM
+  addValueDeclaration +y DeadTest.res:61:2 path:+DeadTest.MM
+  addValueDeclaration +unusedRec DeadTest.res:75:8 path:+DeadTest
+  addValueDeclaration +split_map DeadTest.res:77:8 path:+DeadTest
+  addValueDeclaration +rec1 DeadTest.res:82:8 path:+DeadTest
+  addValueDeclaration +rec2 DeadTest.res:83:4 path:+DeadTest
+  addValueDeclaration +recWithCallback DeadTest.res:85:8 path:+DeadTest
+  addValueDeclaration +foo DeadTest.res:90:8 path:+DeadTest
+  addValueDeclaration +bar DeadTest.res:94:4 path:+DeadTest
+  addValueDeclaration +withDefaultValue DeadTest.res:96:4 path:+DeadTest
+  addValueDeclaration +reasonResource DeadTest.res:111:6 path:+DeadTest.LazyDynamicallyLoadedComponent2
+  addValueDeclaration +makeProps DeadTest.res:114:6 path:+DeadTest.LazyDynamicallyLoadedComponent2
+  addValueDeclaration +make DeadTest.res:115:6 path:+DeadTest.LazyDynamicallyLoadedComponent2
+  addValueDeclaration +zzz DeadTest.res:127:4 path:+DeadTest
+  addValueDeclaration +second DeadTest.res:135:4 path:+DeadTest
+  addValueDeclaration +minute DeadTest.res:136:4 path:+DeadTest
+  addValueDeclaration +deadRef DeadTest.res:138:4 path:+DeadTest
+  addValueDeclaration +make DeadTest.res:141:4 path:+DeadTest
+  addValueDeclaration +theSideEffectIsLogging DeadTest.res:145:4 path:+DeadTest
+  addValueDeclaration +stringLengthNoSideEffects DeadTest.res:147:4 path:+DeadTest
+  addValueDeclaration +globallyLive1 DeadTest.res:152:6 path:+DeadTest.GloobLive
+  addValueDeclaration +globallyLive2 DeadTest.res:153:6 path:+DeadTest.GloobLive
+  addValueDeclaration +globallyLive3 DeadTest.res:154:6 path:+DeadTest.GloobLive
+  addValueDeclaration +funWithInnerVars DeadTest.res:169:4 path:+DeadTest
+  addValueDeclaration +deadIncorrect DeadTest.res:178:4 path:+DeadTest
+  addValueDeclaration +ira DeadTest.res:184:4 path:+DeadTest
+  addValueReference DeadTest.res:1:15 --> ImmutableArray.resi:9:0
+  addValueReference DeadTest.res:8:7 --> DeadTest.res:7:4
+  addValueReference DeadTest.res:11:7 --> DeadTest.res:10:4
+  addValueReference DeadTest.res:12:7 --> DeadTest.res:10:4
+  addValueReference DeadTest.res:20:4 --> DeadTest.res:17:4
+  addValueDeclaration +thisSignatureItemIsDead DeadTest.res:31:6 path:+DeadTest.M
+  addVariantCaseDeclaration A DeadTest.res:35:11 path:+DeadTest.VariantUsedOnlyInImplementation.t
+  addVariantCaseDeclaration A DeadTest.res:38:11 path:+DeadTest.VariantUsedOnlyInImplementation.t
+  extendTypeDependencies DeadTest.res:38:11 --> DeadTest.res:35:11
+  extendTypeDependencies DeadTest.res:35:11 --> DeadTest.res:38:11
+  addValueDeclaration +a DeadTest.res:39:6 path:+DeadTest.VariantUsedOnlyInImplementation
+  addTypeReference DeadTest.res:39:10 --> DeadTest.res:38:11
+  addValueReference DeadTest.res:42:17 --> DeadTest.res:36:2
+  addValueReference DeadTest.res:42:14 --> DeadTest.res:42:9
+  addValueDeclaration +_ DeadTest.res:44:0 path:+DeadTest
+  addTypeReference DeadTest.res:44:8 --> DeadTypeTest.resi:8:2
+  addValueDeclaration +_ DeadTest.res:45:0 path:+DeadTest
+  addTypeReference DeadTest.res:45:8 --> DeadTypeTest.resi:9:2
+  addRecordLabelDeclaration xxx DeadTest.res:48:2 path:+DeadTest.record
+  addRecordLabelDeclaration yyy DeadTest.res:49:2 path:+DeadTest.record
+  addValueDeclaration +_ DeadTest.res:52:0 path:+DeadTest
+  addTypeReference DeadTest.res:52:13 --> DeadTest.res:48:2
+  addValueReference DeadTest.res:52:13 --> DeadTest.res:52:8
+  addValueDeclaration +_ DeadTest.res:53:0 path:+DeadTest
+  addValueReference DeadTest.res:53:19 --> DeadTest.res:53:10
+  addTypeReference DeadTest.res:53:9 --> DeadTest.res:49:2
+  addValueDeclaration +_ DeadTest.res:56:2 path:+DeadTest.UnderscoreInside
+  addValueDeclaration +y DeadTest.res:63:6 path:+DeadTest.MM
+  addValueDeclaration +x DeadTest.res:64:6 path:+DeadTest.MM
+  addValueReference DeadTest.res:64:6 --> DeadTest.res:63:6
+  addValueDeclaration +valueOnlyInImplementation DeadTest.res:65:6 path:+DeadTest.MM
+  addValueReference DeadTest.res:69:9 --> DeadTest.res:60:2
+  addValueReference DeadTest.res:73:16 --> DeadValueTest.resi:1:0
+  addValueReference DeadTest.res:75:8 --> DeadTest.res:75:8
+  addValueReference DeadTest.res:77:8 --> DeadTest.res:77:20
+  addValueReference DeadTest.res:77:8 --> DeadTest.res:77:8
+  addValueReference DeadTest.res:82:8 --> DeadTest.res:83:4
+  addValueReference DeadTest.res:83:4 --> DeadTest.res:82:8
+  addValueDeclaration +cb DeadTest.res:86:6 path:+DeadTest
+  addValueReference DeadTest.res:86:6 --> DeadTest.res:85:8
+  addValueReference DeadTest.res:85:8 --> DeadTest.res:86:6
+  addValueDeclaration +cb DeadTest.res:91:6 path:+DeadTest
+  addValueReference DeadTest.res:91:6 --> DeadTest.res:94:4
+  addValueReference DeadTest.res:90:8 --> DeadTest.res:91:6
+  addValueReference DeadTest.res:94:4 --> DeadTest.res:90:8
+  addValueReference DeadTest.res:96:4 --> DeadTest.res:96:42
+  addValueReference DeadTest.res:96:4 --> DeadTest.res:96:24
+  addValueReference DeadTest.res:96:4 --> DeadTest.res:96:45
+  addTypeReference DeadTest.res:106:16 --> DeadRT.resi:2:2
+  addValueReference DeadTest.res:111:6 --> JSResource.res:3:0
+  addValueReference DeadTest.res:115:6 --> DynamicallyLoadedComponent.res:2:4
+  addValueReference DeadTest.res:115:6 --> DeadTest.res:111:6
+  addValueReference DeadTest.res:115:6 --> BootloaderResource.res:3:0
+  addValueReference DeadTest.res:115:6 --> DeadTest.res:115:13
+  addValueReference DeadTest.res:115:6 --> React.res:18:0
+  addValueDeclaration +a1 DeadTest.res:128:6 path:+DeadTest
+  addValueDeclaration +a2 DeadTest.res:129:6 path:+DeadTest
+  addValueDeclaration +a3 DeadTest.res:130:6 path:+DeadTest
+  addValueReference DeadTest.res:133:17 --> DynamicallyLoadedComponent.res:2:4
+  addValueReference DeadTest.res:133:17 --> React.res:18:0
+  addValueReference DeadTest.res:136:4 --> DeadTest.res:135:4
+  addValueReference DeadTest.res:141:32 --> DeadTest.res:141:12
+  addValueReference DeadTest.res:141:19 --> React.res:7:0
+  addValueReference DeadTest.res:143:16 --> DeadTest.res:141:4
+  addVariantCaseDeclaration A DeadTest.res:158:11 path:+DeadTest.WithInclude.t
+  addVariantCaseDeclaration A DeadTest.res:161:13 path:+DeadTest.WithInclude.T.t
+  addVariantCaseDeclaration A DeadTest.res:161:13 path:+DeadTest.WithInclude.t
+  extendTypeDependencies DeadTest.res:161:13 --> DeadTest.res:158:11
+  extendTypeDependencies DeadTest.res:158:11 --> DeadTest.res:161:13
+  addTypeReference DeadTest.res:166:7 --> DeadTest.res:158:11
+  addValueDeclaration +x DeadTest.res:170:6 path:+DeadTest
+  addValueDeclaration +y DeadTest.res:171:6 path:+DeadTest
+  addValueReference DeadTest.res:169:4 --> DeadTest.res:170:6
+  addValueReference DeadTest.res:169:4 --> DeadTest.res:171:6
+  addRecordLabelDeclaration a DeadTest.res:175:11 path:+DeadTest.rc
+  addValueDeclaration +_ DeadTest.res:180:0 path:+DeadTest
+  addValueReference DeadTest.res:180:8 --> DeadTest.res:178:4
+  addRecordLabelDeclaration IR.a DeadTest.res:182:24 path:+DeadTest.inlineRecord
+  addRecordLabelDeclaration IR.b DeadTest.res:182:32 path:+DeadTest.inlineRecord
+  addRecordLabelDeclaration IR.c DeadTest.res:182:40 path:+DeadTest.inlineRecord
+  addRecordLabelDeclaration IR.d DeadTest.res:182:51 path:+DeadTest.inlineRecord
+  addRecordLabelDeclaration IR.e DeadTest.res:182:65 path:+DeadTest.inlineRecord
+  addVariantCaseDeclaration IR DeadTest.res:182:20 path:+DeadTest.inlineRecord
+  addValueDeclaration +_ DeadTest.res:185:0 path:+DeadTest
+  addTypeReference DeadTest.res:187:20 --> DeadTest.res:182:20
+  addValueReference DeadTest.res:187:27 --> DeadTest.res:184:4
+  addTypeReference DeadTest.res:187:35 --> DeadTest.res:182:32
+  addValueReference DeadTest.res:187:35 --> DeadTest.res:187:7
+  addValueReference DeadTest.res:187:40 --> DeadTest.res:187:8
+  addTypeReference DeadTest.res:187:7 --> DeadTest.res:182:40
+  addValueReference DeadTest.res:186:9 --> DeadTest.res:185:8
+  addRecordLabelDeclaration IR2.a DeadTest.res:191:26 path:+DeadTest.inlineRecord2
+  addRecordLabelDeclaration IR2.b DeadTest.res:191:34 path:+DeadTest.inlineRecord2
+  addVariantCaseDeclaration IR2 DeadTest.res:191:21 path:+DeadTest.inlineRecord2
+  addRecordLabelDeclaration IR3.a DeadTest.res:193:34 path:+DeadTest.inlineRecord3
+  addRecordLabelDeclaration IR3.b DeadTest.res:193:42 path:+DeadTest.inlineRecord3
+  addVariantCaseDeclaration IR3 DeadTest.res:193:21 path:+DeadTest.inlineRecord3
+  addValueReference DeadTest.res:28:2 --> DeadTest.res:31:6
+  addValueReference DeadTest.res:36:2 --> DeadTest.res:39:6
+  addValueReference DeadTest.res:60:2 --> DeadTest.res:64:6
+  addValueReference DeadTest.res:61:2 --> DeadTest.res:63:6
+  addValueReference DeadTest.res:101:2 --> DeadTest.res:103:2
+  addTypeReference DeadTest.res:161:13 --> DeadTest.res:158:11
+  addTypeReference DeadTest.res:158:11 --> DeadTest.res:161:13
+  addTypeReference DeadTest.res:38:11 --> DeadTest.res:35:11
+  addTypeReference DeadTest.res:35:11 --> DeadTest.res:38:11
+  Scanning DeadTestBlacklist.cmt Source:DeadTestBlacklist.res
+  addValueDeclaration +x DeadTestBlacklist.res:1:4 path:+DeadTestBlacklist
+  Scanning DeadTestWithInterface.cmt Source:DeadTestWithInterface.res
+  addValueDeclaration +x DeadTestWithInterface.res:2:2 path:+DeadTestWithInterface.Ext_buffer
+  addValueDeclaration +x DeadTestWithInterface.res:4:6 path:+DeadTestWithInterface.Ext_buffer
+  addValueReference DeadTestWithInterface.res:2:2 --> DeadTestWithInterface.res:4:6
+  Interface 0
+  Scanning DeadTypeTest.cmt Source:DeadTypeTest.res
+  addValueDeclaration +a DeadTypeTest.res:4:4 path:+DeadTypeTest
+  addVariantCaseDeclaration A DeadTypeTest.res:2:2 path:+DeadTypeTest.t
+  addVariantCaseDeclaration B DeadTypeTest.res:3:2 path:+DeadTypeTest.t
+  addTypeReference DeadTypeTest.res:4:8 --> DeadTypeTest.res:2:2
+  addVariantCaseDeclaration OnlyInImplementation DeadTypeTest.res:7:2 path:+DeadTypeTest.deadType
+  addVariantCaseDeclaration OnlyInInterface DeadTypeTest.res:8:2 path:+DeadTypeTest.deadType
+  addVariantCaseDeclaration InBoth DeadTypeTest.res:9:2 path:+DeadTypeTest.deadType
+  addVariantCaseDeclaration InNeither DeadTypeTest.res:10:2 path:+DeadTypeTest.deadType
+  addValueDeclaration +_ DeadTypeTest.res:12:0 path:+DeadTypeTest
+  addTypeReference DeadTypeTest.res:12:8 --> DeadTypeTest.res:7:2
+  addValueDeclaration +_ DeadTypeTest.res:13:0 path:+DeadTypeTest
+  addTypeReference DeadTypeTest.res:13:8 --> DeadTypeTest.res:9:2
+  addRecordLabelDeclaration x DeadTypeTest.res:16:15 path:+DeadTypeTest.record
+  addRecordLabelDeclaration y DeadTypeTest.res:16:23 path:+DeadTypeTest.record
+  addRecordLabelDeclaration z DeadTypeTest.res:16:34 path:+DeadTypeTest.record
+  addValueReference DeadTypeTest.resi:4:0 --> DeadTypeTest.res:4:4
+  Scanning DeadTypeTest.cmti Source:DeadTypeTest.resi
+  addVariantCaseDeclaration A DeadTypeTest.resi:2:2 path:DeadTypeTest.t
+  extendTypeDependencies DeadTypeTest.res:2:2 --> DeadTypeTest.resi:2:2
+  extendTypeDependencies DeadTypeTest.resi:2:2 --> DeadTypeTest.res:2:2
+  addVariantCaseDeclaration B DeadTypeTest.resi:3:2 path:DeadTypeTest.t
+  extendTypeDependencies DeadTypeTest.res:3:2 --> DeadTypeTest.resi:3:2
+  extendTypeDependencies DeadTypeTest.resi:3:2 --> DeadTypeTest.res:3:2
+  addValueDeclaration +a DeadTypeTest.resi:4:0 path:DeadTypeTest
+  addVariantCaseDeclaration OnlyInImplementation DeadTypeTest.resi:7:2 path:DeadTypeTest.deadType
+  extendTypeDependencies DeadTypeTest.res:7:2 --> DeadTypeTest.resi:7:2
+  extendTypeDependencies DeadTypeTest.resi:7:2 --> DeadTypeTest.res:7:2
+  addVariantCaseDeclaration OnlyInInterface DeadTypeTest.resi:8:2 path:DeadTypeTest.deadType
+  extendTypeDependencies DeadTypeTest.res:8:2 --> DeadTypeTest.resi:8:2
+  extendTypeDependencies DeadTypeTest.resi:8:2 --> DeadTypeTest.res:8:2
+  addVariantCaseDeclaration InBoth DeadTypeTest.resi:9:2 path:DeadTypeTest.deadType
+  extendTypeDependencies DeadTypeTest.res:9:2 --> DeadTypeTest.resi:9:2
+  extendTypeDependencies DeadTypeTest.resi:9:2 --> DeadTypeTest.res:9:2
+  addVariantCaseDeclaration InNeither DeadTypeTest.resi:10:2 path:DeadTypeTest.deadType
+  extendTypeDependencies DeadTypeTest.res:10:2 --> DeadTypeTest.resi:10:2
+  extendTypeDependencies DeadTypeTest.resi:10:2 --> DeadTypeTest.res:10:2
+  addTypeReference DeadTypeTest.res:10:2 --> DeadTypeTest.resi:10:2
+  addTypeReference DeadTypeTest.resi:10:2 --> DeadTypeTest.res:10:2
+  addTypeReference DeadTypeTest.res:9:2 --> DeadTypeTest.resi:9:2
+  addTypeReference DeadTypeTest.resi:9:2 --> DeadTypeTest.res:9:2
+  addTypeReference DeadTypeTest.res:8:2 --> DeadTypeTest.resi:8:2
+  addTypeReference DeadTypeTest.resi:8:2 --> DeadTypeTest.res:8:2
+  addTypeReference DeadTypeTest.res:7:2 --> DeadTypeTest.resi:7:2
+  addTypeReference DeadTypeTest.resi:7:2 --> DeadTypeTest.res:7:2
+  addTypeReference DeadTypeTest.res:3:2 --> DeadTypeTest.resi:3:2
+  addTypeReference DeadTypeTest.resi:3:2 --> DeadTypeTest.res:3:2
+  addTypeReference DeadTypeTest.res:2:2 --> DeadTypeTest.resi:2:2
+  addTypeReference DeadTypeTest.resi:2:2 --> DeadTypeTest.res:2:2
+  Scanning DeadValueTest.cmt Source:DeadValueTest.res
+  addValueDeclaration +valueAlive DeadValueTest.res:1:4 path:+DeadValueTest
+  addValueDeclaration +valueDead DeadValueTest.res:2:4 path:+DeadValueTest
+  addValueDeclaration +valueOnlyInImplementation DeadValueTest.res:4:4 path:+DeadValueTest
+  addValueDeclaration +subList DeadValueTest.res:6:8 path:+DeadValueTest
+  addValueDeclaration +tail DeadValueTest.res:10:8 path:+DeadValueTest
+  addValueReference DeadValueTest.res:10:8 --> DeadValueTest.res:6:19
+  addValueReference DeadValueTest.res:10:8 --> DeadValueTest.res:6:22
+  addValueReference DeadValueTest.res:10:8 --> DeadValueTest.res:9:15
+  addValueReference DeadValueTest.res:10:8 --> DeadValueTest.res:6:8
+  addValueReference DeadValueTest.res:10:8 --> DeadValueTest.res:6:22
+  addValueReference DeadValueTest.res:6:8 --> DeadValueTest.res:9:9
+  addValueReference DeadValueTest.res:6:8 --> DeadValueTest.res:10:8
+  addValueReference DeadValueTest.res:6:8 --> DeadValueTest.res:10:8
+  addValueReference DeadValueTest.res:6:8 --> DeadValueTest.res:6:19
+  addValueReference DeadValueTest.res:6:8 --> DeadValueTest.res:6:25
+  addValueReference DeadValueTest.resi:1:0 --> DeadValueTest.res:1:4
+  addValueReference DeadValueTest.resi:2:0 --> DeadValueTest.res:2:4
+  Scanning DeadValueTest.cmti Source:DeadValueTest.resi
+  addValueDeclaration +valueAlive DeadValueTest.resi:1:0 path:DeadValueTest
+  addValueDeclaration +valueDead DeadValueTest.resi:2:0 path:DeadValueTest
+  Scanning Docstrings.cmt Source:Docstrings.res
+  addValueDeclaration +flat Docstrings.res:2:4 path:+Docstrings
+  addValueDeclaration +signMessage Docstrings.res:12:4 path:+Docstrings
+  addValueDeclaration +one Docstrings.res:15:4 path:+Docstrings
+  addValueDeclaration +two Docstrings.res:18:4 path:+Docstrings
+  addValueDeclaration +tree Docstrings.res:21:4 path:+Docstrings
+  addValueDeclaration +oneU Docstrings.res:24:4 path:+Docstrings
+  addValueDeclaration +twoU Docstrings.res:27:4 path:+Docstrings
+  addValueDeclaration +treeU Docstrings.res:30:4 path:+Docstrings
+  addValueDeclaration +useParam Docstrings.res:33:4 path:+Docstrings
+  addValueDeclaration +useParamU Docstrings.res:36:4 path:+Docstrings
+  addValueDeclaration +unnamed1 Docstrings.res:39:4 path:+Docstrings
+  addValueDeclaration +unnamed1U Docstrings.res:42:4 path:+Docstrings
+  addValueDeclaration +unnamed2 Docstrings.res:45:4 path:+Docstrings
+  addValueDeclaration +unnamed2U Docstrings.res:48:4 path:+Docstrings
+  addValueDeclaration +grouped Docstrings.res:51:4 path:+Docstrings
+  addValueDeclaration +unitArgWithoutConversion Docstrings.res:54:4 path:+Docstrings
+  addValueDeclaration +unitArgWithoutConversionU Docstrings.res:57:4 path:+Docstrings
+  addValueDeclaration +unitArgWithConversion Docstrings.res:64:4 path:+Docstrings
+  addValueDeclaration +unitArgWithConversionU Docstrings.res:67:4 path:+Docstrings
+  addValueReference Docstrings.res:12:4 --> Docstrings.res:12:21
+  addValueReference Docstrings.res:12:4 --> Docstrings.res:12:30
+  addValueReference Docstrings.res:15:4 --> Docstrings.res:15:10
+  addValueReference Docstrings.res:18:4 --> Docstrings.res:18:11
+  addValueReference Docstrings.res:18:4 --> Docstrings.res:18:14
+  addValueReference Docstrings.res:21:4 --> Docstrings.res:21:12
+  addValueReference Docstrings.res:21:4 --> Docstrings.res:21:15
+  addValueReference Docstrings.res:21:4 --> Docstrings.res:21:18
+  addValueReference Docstrings.res:24:4 --> Docstrings.res:24:14
+  addValueReference Docstrings.res:27:4 --> Docstrings.res:27:14
+  addValueReference Docstrings.res:27:4 --> Docstrings.res:27:17
+  addValueReference Docstrings.res:30:4 --> Docstrings.res:30:15
+  addValueReference Docstrings.res:30:4 --> Docstrings.res:30:18
+  addValueReference Docstrings.res:30:4 --> Docstrings.res:30:21
+  addValueReference Docstrings.res:33:4 --> Docstrings.res:33:15
+  addValueReference Docstrings.res:36:4 --> Docstrings.res:36:19
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:15
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:19
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:23
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:26
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:29
+  addValueReference Docstrings.res:51:4 --> Docstrings.res:51:32
+  addVariantCaseDeclaration A Docstrings.res:60:2 path:+Docstrings.t
+  addVariantCaseDeclaration B Docstrings.res:61:2 path:+Docstrings.t
+  addTypeReference Docstrings.res:64:34 --> Docstrings.res:60:2
+  addTypeReference Docstrings.res:67:39 --> Docstrings.res:60:2
+  Scanning DynamicallyLoadedComponent.cmt Source:DynamicallyLoadedComponent.res
+  addValueDeclaration +make DynamicallyLoadedComponent.res:2:4 path:+DynamicallyLoadedComponent
+  addValueReference DynamicallyLoadedComponent.res:2:32 --> DynamicallyLoadedComponent.res:2:12
+  addValueReference DynamicallyLoadedComponent.res:2:19 --> React.res:7:0
+  Scanning EmptyArray.cmt Source:EmptyArray.res
+  addValueDeclaration +make EmptyArray.res:5:6 path:+EmptyArray.Z
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: EmptyArray.res:6:5
+  addValueReference EmptyArray.res:6:5 --> ReactDOMRe.res:8:0
+  addValueReference EmptyArray.res:10:9 --> EmptyArray.res:5:6
+  addValueReference EmptyArray.res:10:9 --> React.res:18:0
+  Scanning ErrorHandler.cmt Source:ErrorHandler.res
+  addValueDeclaration +notify ErrorHandler.res:7:6 path:+ErrorHandler.Make
+  addValueDeclaration +x ErrorHandler.res:12:4 path:+ErrorHandler
+  addValueReference ErrorHandler.res:7:6 --> ErrorHandler.res:7:15
+  addValueReference ErrorHandler.res:7:6 --> ErrorHandler.res:3:2
+  addValueReference ErrorHandler.resi:3:2 --> ErrorHandler.res:3:2
+  addValueReference ErrorHandler.res:3:2 --> ErrorHandler.resi:3:2
+  addValueReference ErrorHandler.resi:7:2 --> ErrorHandler.res:7:6
+  addValueReference ErrorHandler.resi:10:0 --> ErrorHandler.res:12:4
+  Scanning ErrorHandler.cmti Source:ErrorHandler.resi
+  addValueDeclaration +notify ErrorHandler.resi:7:2 path:ErrorHandler.Make
+  addValueDeclaration +x ErrorHandler.resi:10:0 path:ErrorHandler
+  Scanning EverythingLiveHere.cmt Source:EverythingLiveHere.res
+  addValueDeclaration +x EverythingLiveHere.res:1:4 path:+EverythingLiveHere
+  addValueDeclaration +y EverythingLiveHere.res:3:4 path:+EverythingLiveHere
+  addValueDeclaration +z EverythingLiveHere.res:5:4 path:+EverythingLiveHere
+  Scanning FC.cmt Source:FC.res
+  addValueDeclaration +foo FC.res:6:4 path:+FC
+  addValueReference FC.res:7:13 --> FC.res:6:11
+  addValueReference FC.res:6:4 --> FC.res:2:2
+  addValueReference FC.res:6:4 --> FC.res:7:6
+  addValueReference FC.res:11:7 --> FC.res:6:4
+  Scanning FirstClassModules.cmt Source:FirstClassModules.res
+  addValueDeclaration +y FirstClassModules.res:23:6 path:+FirstClassModules.M
+  addValueDeclaration +k FirstClassModules.res:29:8 path:+FirstClassModules.M.InnerModule2
+  addValueDeclaration +k3 FirstClassModules.res:33:8 path:+FirstClassModules.M.InnerModule3
+  addValueDeclaration +u FirstClassModules.res:40:8 path:+FirstClassModules.M.Z
+  addValueDeclaration +x FirstClassModules.res:44:6 path:+FirstClassModules.M
+  addValueDeclaration +firstClassModule FirstClassModules.res:51:4 path:+FirstClassModules
+  addValueDeclaration +testConvert FirstClassModules.res:54:4 path:+FirstClassModules
+  addValueDeclaration +someFunctorAsFunction FirstClassModules.res:65:4 path:+FirstClassModules
+  addValueReference FirstClassModules.res:33:8 --> FirstClassModules.res:33:13
+  addValueReference FirstClassModules.res:54:4 --> FirstClassModules.res:54:19
+  addValueDeclaration +ww FirstClassModules.res:61:6 path:+FirstClassModules.SomeFunctor
+  addValueReference FirstClassModules.res:61:6 --> FirstClassModules.res:20:2
+  addValueReference FirstClassModules.res:65:4 --> FirstClassModules.res:65:29
+  addValueReference FirstClassModules.res:2:2 --> FirstClassModules.res:44:6
+  addValueReference FirstClassModules.res:4:2 --> FirstClassModules.res:43:2
+  addValueReference FirstClassModules.res:10:4 --> FirstClassModules.res:29:8
+  addValueReference FirstClassModules.res:14:4 --> FirstClassModules.res:33:8
+  addValueReference FirstClassModules.res:17:4 --> FirstClassModules.res:37:4
+  addValueReference FirstClassModules.res:37:4 --> FirstClassModules.res:17:4
+  addValueReference FirstClassModules.res:37:4 --> FirstClassModules.res:40:8
+  addValueReference FirstClassModules.res:20:2 --> FirstClassModules.res:23:6
+  addValueReference FirstClassModules.res:57:2 --> FirstClassModules.res:61:6
+  Scanning FirstClassModulesInterface.cmt Source:FirstClassModulesInterface.res
+  addValueDeclaration +r FirstClassModulesInterface.res:6:4 path:+FirstClassModulesInterface
+  addRecordLabelDeclaration x FirstClassModulesInterface.res:2:2 path:+FirstClassModulesInterface.record
+  addRecordLabelDeclaration y FirstClassModulesInterface.res:3:2 path:+FirstClassModulesInterface.record
+  addValueReference FirstClassModulesInterface.resi:7:0 --> FirstClassModulesInterface.res:6:4
+  addValueReference FirstClassModulesInterface.resi:11:2 --> FirstClassModulesInterface.res:9:2
+  addValueReference FirstClassModulesInterface.res:9:2 --> FirstClassModulesInterface.resi:11:2
+  Scanning FirstClassModulesInterface.cmti Source:FirstClassModulesInterface.resi
+  addRecordLabelDeclaration x FirstClassModulesInterface.resi:3:2 path:FirstClassModulesInterface.record
+  extendTypeDependencies FirstClassModulesInterface.res:2:2 --> FirstClassModulesInterface.resi:3:2
+  extendTypeDependencies FirstClassModulesInterface.resi:3:2 --> FirstClassModulesInterface.res:2:2
+  addRecordLabelDeclaration y FirstClassModulesInterface.resi:4:2 path:FirstClassModulesInterface.record
+  extendTypeDependencies FirstClassModulesInterface.res:3:2 --> FirstClassModulesInterface.resi:4:2
+  extendTypeDependencies FirstClassModulesInterface.resi:4:2 --> FirstClassModulesInterface.res:3:2
+  addValueDeclaration +r FirstClassModulesInterface.resi:7:0 path:FirstClassModulesInterface
+  addTypeReference FirstClassModulesInterface.res:3:2 --> FirstClassModulesInterface.resi:4:2
+  addTypeReference FirstClassModulesInterface.resi:4:2 --> FirstClassModulesInterface.res:3:2
+  addTypeReference FirstClassModulesInterface.res:2:2 --> FirstClassModulesInterface.resi:3:2
+  addTypeReference FirstClassModulesInterface.resi:3:2 --> FirstClassModulesInterface.res:2:2
+  Scanning Hooks.cmt Source:Hooks.res
+  addValueDeclaration +make Hooks.res:4:4 path:+Hooks
+  addValueDeclaration +default Hooks.res:25:4 path:+Hooks
+  addValueDeclaration +anotherComponent Hooks.res:28:4 path:+Hooks
+  addValueDeclaration +make Hooks.res:35:6 path:+Hooks.Inner
+  addValueDeclaration +anotherComponent Hooks.res:38:6 path:+Hooks.Inner
+  addValueDeclaration +make Hooks.res:42:8 path:+Hooks.Inner.Inner2
+  addValueDeclaration +anotherComponent Hooks.res:45:8 path:+Hooks.Inner.Inner2
+  addValueDeclaration +make Hooks.res:52:6 path:+Hooks.NoProps
+  addValueDeclaration +functionWithRenamedArgs Hooks.res:58:4 path:+Hooks
+  addValueDeclaration +componentWithRenamedArgs Hooks.res:64:4 path:+Hooks
+  addValueDeclaration +makeWithRef Hooks.res:70:4 path:+Hooks
+  addValueDeclaration +testForwardRef Hooks.res:80:4 path:+Hooks
+  addValueDeclaration +input Hooks.res:85:4 path:+Hooks
+  addValueDeclaration +polymorphicComponent Hooks.res:100:4 path:+Hooks
+  addValueDeclaration +functionReturningReactElement Hooks.res:103:4 path:+Hooks
+  addValueDeclaration +make Hooks.res:107:6 path:+Hooks.RenderPropRequiresConversion
+  addValueDeclaration +aComponentWithChildren Hooks.res:114:4 path:+Hooks
+  addRecordLabelDeclaration name Hooks.res:1:16 path:+Hooks.vehicle
+  addValueReference Hooks.res:5:26 --> React.res:167:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:7:3
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:8:5
+  addTypeReference Hooks.res:10:29 --> Hooks.res:1:16
+  addValueReference Hooks.res:10:29 --> Hooks.res:4:12
+  addValueReference Hooks.res:10:76 --> Hooks.res:5:7
+  addValueReference Hooks.res:9:7 --> React.res:7:0
+  addValueReference Hooks.res:8:5 --> ReactDOMRe.res:8:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames:props argNamesMaybe: Hooks.res:13:5
+  addValueReference Hooks.res:13:40 --> Hooks.res:5:7
+  addValueReference Hooks.res:13:26 --> Hooks.res:5:14
+  addValueReference Hooks.res:13:54 --> React.res:7:0
+  addValueReference Hooks.res:13:5 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:14:5 --> ImportHooks.res:13:0
+  DeadOptionalArgs.addReferences ImportHooks.makeProps called with optional argNames: argNamesMaybe: Hooks.res:14:5
+  addValueReference Hooks.res:14:5 --> React.res:3:0
+  addValueReference Hooks.res:14:63 --> React.res:7:0
+  addValueReference Hooks.res:14:5 --> ImportHooks.res:13:0
+  addValueReference Hooks.res:15:7 --> React.res:7:0
+  addValueReference Hooks.res:15:32 --> React.res:7:0
+  addValueReference Hooks.res:14:5 --> React.res:24:0
+  addValueReference Hooks.res:17:5 --> ImportHookDefault.res:6:0
+  DeadOptionalArgs.addReferences ImportHookDefault.makeProps called with optional argNames: argNamesMaybe: Hooks.res:17:5
+  addValueReference Hooks.res:17:5 --> React.res:3:0
+  addValueReference Hooks.res:18:61 --> React.res:7:0
+  addValueReference Hooks.res:17:5 --> ImportHookDefault.res:6:0
+  addValueReference Hooks.res:19:7 --> React.res:7:0
+  addValueReference Hooks.res:19:32 --> React.res:7:0
+  addValueReference Hooks.res:17:5 --> React.res:24:0
+  addValueReference Hooks.res:7:3 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:25:4 --> Hooks.res:4:4
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:30:3
+  addTypeReference Hooks.res:30:41 --> Hooks.res:1:16
+  addValueReference Hooks.res:30:41 --> Hooks.res:28:24
+  addValueReference Hooks.res:30:9 --> React.res:7:0
+  addValueReference Hooks.res:30:3 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:29:2 --> Hooks.res:28:34
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:35:28
+  addTypeReference Hooks.res:35:66 --> Hooks.res:1:16
+  addValueReference Hooks.res:35:66 --> Hooks.res:35:14
+  addValueReference Hooks.res:35:34 --> React.res:7:0
+  addValueReference Hooks.res:35:28 --> ReactDOMRe.res:8:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:38:40
+  addTypeReference Hooks.res:38:78 --> Hooks.res:1:16
+  addValueReference Hooks.res:38:78 --> Hooks.res:38:26
+  addValueReference Hooks.res:38:46 --> React.res:7:0
+  addValueReference Hooks.res:38:40 --> ReactDOMRe.res:8:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:42:30
+  addTypeReference Hooks.res:42:68 --> Hooks.res:1:16
+  addValueReference Hooks.res:42:68 --> Hooks.res:42:16
+  addValueReference Hooks.res:42:36 --> React.res:7:0
+  addValueReference Hooks.res:42:30 --> ReactDOMRe.res:8:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:46:7
+  addTypeReference Hooks.res:46:45 --> Hooks.res:1:16
+  addValueReference Hooks.res:46:45 --> Hooks.res:45:28
+  addValueReference Hooks.res:46:13 --> React.res:7:0
+  addValueReference Hooks.res:46:7 --> ReactDOMRe.res:8:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:52:20
+  addValueReference Hooks.res:52:25 --> React.res:3:0
+  addValueReference Hooks.res:52:20 --> ReactDOMRe.res:8:0
+  addTypeReference Hooks.res:60:2 --> Hooks.res:1:16
+  addValueReference Hooks.res:58:4 --> Hooks.res:58:31
+  addTypeReference Hooks.res:60:14 --> Hooks.res:1:16
+  addValueReference Hooks.res:58:4 --> Hooks.res:58:37
+  addValueReference Hooks.res:58:4 --> Hooks.res:58:31
+  addValueReference Hooks.res:58:4 --> Hooks.res:58:45
+  addTypeReference Hooks.res:66:15 --> Hooks.res:1:16
+  addValueReference Hooks.res:66:15 --> Hooks.res:64:32
+  addTypeReference Hooks.res:66:27 --> Hooks.res:1:16
+  addValueReference Hooks.res:66:27 --> Hooks.res:64:38
+  addValueReference Hooks.res:66:2 --> React.res:7:0
+  addValueReference Hooks.res:65:6 --> Hooks.res:64:32
+  addValueReference Hooks.res:65:2 --> Hooks.res:64:46
+  addValueDeclaration +_ Hooks.res:71:2 path:+Hooks
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames:props argNamesMaybe: Hooks.res:74:20
+  addValueReference Hooks.res:74:52 --> Hooks.res:74:11
+  addValueReference Hooks.res:74:32 --> ReactDOM.res:47:2
+  addTypeReference Hooks.res:74:73 --> Hooks.res:1:16
+  addValueReference Hooks.res:74:73 --> Hooks.res:70:19
+  addValueReference Hooks.res:74:60 --> React.res:7:0
+  addValueReference Hooks.res:74:20 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:75:14 --> React.res:3:0
+  addValueReference Hooks.res:73:11 --> Hooks.res:72:2
+  addValueReference Hooks.res:80:4 --> Hooks.res:70:4
+  addValueReference Hooks.res:80:4 --> React.res:90:0
+  addRecordLabelDeclaration x Hooks.res:82:10 path:+Hooks.r
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames:props argNamesMaybe: Hooks.res:85:47
+  addValueReference Hooks.res:85:66 --> Hooks.res:85:38
+  addTypeReference Hooks.res:85:87 --> Hooks.res:82:10
+  addValueReference Hooks.res:85:87 --> Hooks.res:85:30
+  addValueReference Hooks.res:85:74 --> React.res:7:0
+  addValueReference Hooks.res:85:47 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:85:4 --> React.res:90:0
+  addTypeReference Hooks.res:100:58 --> Hooks.res:1:16
+  addValueReference Hooks.res:100:58 --> Hooks.res:100:35
+  addValueReference Hooks.res:100:45 --> React.res:7:0
+  addValueReference Hooks.res:103:60 --> Hooks.res:103:37
+  addValueReference Hooks.res:103:47 --> React.res:7:0
+  addValueDeclaration +car Hooks.res:108:8 path:+Hooks.RenderPropRequiresConversion
+  addValueReference Hooks.res:109:30 --> Hooks.res:108:8
+  addValueReference Hooks.res:109:18 --> Hooks.res:109:18
+  addValueReference Hooks.res:109:4 --> Hooks.res:107:14
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:115:3
+  addTypeReference Hooks.res:115:41 --> Hooks.res:1:16
+  addValueReference Hooks.res:115:41 --> Hooks.res:114:30
+  addValueReference Hooks.res:115:9 --> React.res:7:0
+  DeadOptionalArgs.addReferences ReactDOMRe.createDOMElementVariadic called with optional argNames: argNamesMaybe: Hooks.res:115:57
+  addValueReference Hooks.res:115:62 --> Hooks.res:114:40
+  addValueReference Hooks.res:115:57 --> ReactDOMRe.res:8:0
+  addValueReference Hooks.res:115:3 --> ReactDOMRe.res:8:0
+  Scanning IgnoreInterface.cmt Source:IgnoreInterface.res
+  Scanning IgnoreInterface.cmti Source:IgnoreInterface.resi
+  Scanning ImmutableArray.cmt Source:ImmutableArray.res
+  addValueDeclaration +fromArray ImmutableArray.res:14:6 path:+ImmutableArray.Array
+  addValueDeclaration +toArray ImmutableArray.res:16:6 path:+ImmutableArray.Array
+  addValueDeclaration +length ImmutableArray.res:20:6 path:+ImmutableArray.Array
+  addValueDeclaration +size ImmutableArray.res:22:6 path:+ImmutableArray.Array
+  addValueDeclaration +get ImmutableArray.res:24:6 path:+ImmutableArray.Array
+  addValueDeclaration +getExn ImmutableArray.res:26:6 path:+ImmutableArray.Array
+  addValueDeclaration +getUnsafe ImmutableArray.res:28:6 path:+ImmutableArray.Array
+  addValueDeclaration +getUndefined ImmutableArray.res:30:6 path:+ImmutableArray.Array
+  addValueDeclaration +shuffle ImmutableArray.res:32:6 path:+ImmutableArray.Array
+  addValueDeclaration +reverse ImmutableArray.res:34:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeUninitialized ImmutableArray.res:36:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeUninitializedUnsafe ImmutableArray.res:38:6 path:+ImmutableArray.Array
+  addValueDeclaration +make ImmutableArray.res:40:6 path:+ImmutableArray.Array
+  addValueDeclaration +range ImmutableArray.res:42:6 path:+ImmutableArray.Array
+  addValueDeclaration +rangeBy ImmutableArray.res:44:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeByU ImmutableArray.res:46:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeBy ImmutableArray.res:47:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeByAndShuffleU ImmutableArray.res:49:6 path:+ImmutableArray.Array
+  addValueDeclaration +makeByAndShuffle ImmutableArray.res:50:6 path:+ImmutableArray.Array
+  addValueDeclaration +zip ImmutableArray.res:52:6 path:+ImmutableArray.Array
+  addValueDeclaration +zipByU ImmutableArray.res:54:6 path:+ImmutableArray.Array
+  addValueDeclaration +zipBy ImmutableArray.res:55:6 path:+ImmutableArray.Array
+  addValueDeclaration +unzip ImmutableArray.res:57:6 path:+ImmutableArray.Array
+  addValueDeclaration +concat ImmutableArray.res:59:6 path:+ImmutableArray.Array
+  addValueDeclaration +concatMany ImmutableArray.res:61:6 path:+ImmutableArray.Array
+  addValueDeclaration +slice ImmutableArray.res:63:6 path:+ImmutableArray.Array
+  addValueDeclaration +sliceToEnd ImmutableArray.res:65:6 path:+ImmutableArray.Array
+  addValueDeclaration +copy ImmutableArray.res:67:6 path:+ImmutableArray.Array
+  addValueDeclaration +forEachU ImmutableArray.res:69:6 path:+ImmutableArray.Array
+  addValueDeclaration +forEach ImmutableArray.res:70:6 path:+ImmutableArray.Array
+  addValueDeclaration +mapU ImmutableArray.res:72:6 path:+ImmutableArray.Array
+  addValueDeclaration +map ImmutableArray.res:73:6 path:+ImmutableArray.Array
+  addValueDeclaration +keepWithIndexU ImmutableArray.res:75:6 path:+ImmutableArray.Array
+  addValueDeclaration +keepWithIndex ImmutableArray.res:76:6 path:+ImmutableArray.Array
+  addValueDeclaration +keepMapU ImmutableArray.res:78:6 path:+ImmutableArray.Array
+  addValueDeclaration +keepMap ImmutableArray.res:79:6 path:+ImmutableArray.Array
+  addValueDeclaration +forEachWithIndexU ImmutableArray.res:81:6 path:+ImmutableArray.Array
+  addValueDeclaration +forEachWithIndex ImmutableArray.res:82:6 path:+ImmutableArray.Array
+  addValueDeclaration +mapWithIndexU ImmutableArray.res:84:6 path:+ImmutableArray.Array
+  addValueDeclaration +mapWithIndex ImmutableArray.res:85:6 path:+ImmutableArray.Array
+  addValueDeclaration +partitionU ImmutableArray.res:87:6 path:+ImmutableArray.Array
+  addValueDeclaration +partition ImmutableArray.res:88:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduceU ImmutableArray.res:90:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduce ImmutableArray.res:91:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduceReverseU ImmutableArray.res:93:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduceReverse ImmutableArray.res:94:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduceReverse2U ImmutableArray.res:96:6 path:+ImmutableArray.Array
+  addValueDeclaration +reduceReverse2 ImmutableArray.res:97:6 path:+ImmutableArray.Array
+  addValueDeclaration +someU ImmutableArray.res:99:6 path:+ImmutableArray.Array
+  addValueDeclaration +some ImmutableArray.res:100:6 path:+ImmutableArray.Array
+  addValueDeclaration +everyU ImmutableArray.res:102:6 path:+ImmutableArray.Array
+  addValueDeclaration +every ImmutableArray.res:103:6 path:+ImmutableArray.Array
+  addValueDeclaration +every2U ImmutableArray.res:105:6 path:+ImmutableArray.Array
+  addValueDeclaration +every2 ImmutableArray.res:106:6 path:+ImmutableArray.Array
+  addValueDeclaration +some2U ImmutableArray.res:108:6 path:+ImmutableArray.Array
+  addValueDeclaration +some2 ImmutableArray.res:109:6 path:+ImmutableArray.Array
+  addValueDeclaration +cmpU ImmutableArray.res:111:6 path:+ImmutableArray.Array
+  addValueDeclaration +cmp ImmutableArray.res:112:6 path:+ImmutableArray.Array
+  addValueDeclaration +eqU ImmutableArray.res:114:6 path:+ImmutableArray.Array
+  addValueDeclaration +eq ImmutableArray.res:115:6 path:+ImmutableArray.Array
+  addValueDeclaration +fromArray ImmutableArray.res:14:6 path:+ImmutableArray
+  addValueDeclaration +toArray ImmutableArray.res:16:6 path:+ImmutableArray
+  addValueDeclaration +length ImmutableArray.res:20:6 path:+ImmutableArray
+  addValueDeclaration +size ImmutableArray.res:22:6 path:+ImmutableArray
+  addValueDeclaration +get ImmutableArray.res:24:6 path:+ImmutableArray
+  addValueDeclaration +getExn ImmutableArray.res:26:6 path:+ImmutableArray
+  addValueDeclaration +getUnsafe ImmutableArray.res:28:6 path:+ImmutableArray
+  addValueDeclaration +getUndefined ImmutableArray.res:30:6 path:+ImmutableArray
+  addValueDeclaration +shuffle ImmutableArray.res:32:6 path:+ImmutableArray
+  addValueDeclaration +reverse ImmutableArray.res:34:6 path:+ImmutableArray
+  addValueDeclaration +makeUninitialized ImmutableArray.res:36:6 path:+ImmutableArray
+  addValueDeclaration +makeUninitializedUnsafe ImmutableArray.res:38:6 path:+ImmutableArray
+  addValueDeclaration +make ImmutableArray.res:40:6 path:+ImmutableArray
+  addValueDeclaration +range ImmutableArray.res:42:6 path:+ImmutableArray
+  addValueDeclaration +rangeBy ImmutableArray.res:44:6 path:+ImmutableArray
+  addValueDeclaration +makeByU ImmutableArray.res:46:6 path:+ImmutableArray
+  addValueDeclaration +makeBy ImmutableArray.res:47:6 path:+ImmutableArray
+  addValueDeclaration +makeByAndShuffleU ImmutableArray.res:49:6 path:+ImmutableArray
+  addValueDeclaration +makeByAndShuffle ImmutableArray.res:50:6 path:+ImmutableArray
+  addValueDeclaration +zip ImmutableArray.res:52:6 path:+ImmutableArray
+  addValueDeclaration +zipByU ImmutableArray.res:54:6 path:+ImmutableArray
+  addValueDeclaration +zipBy ImmutableArray.res:55:6 path:+ImmutableArray
+  addValueDeclaration +unzip ImmutableArray.res:57:6 path:+ImmutableArray
+  addValueDeclaration +concat ImmutableArray.res:59:6 path:+ImmutableArray
+  addValueDeclaration +concatMany ImmutableArray.res:61:6 path:+ImmutableArray
+  addValueDeclaration +slice ImmutableArray.res:63:6 path:+ImmutableArray
+  addValueDeclaration +sliceToEnd ImmutableArray.res:65:6 path:+ImmutableArray
+  addValueDeclaration +copy ImmutableArray.res:67:6 path:+ImmutableArray
+  addValueDeclaration +forEachU ImmutableArray.res:69:6 path:+ImmutableArray
+  addValueDeclaration +forEach ImmutableArray.res:70:6 path:+ImmutableArray
+  addValueDeclaration +mapU ImmutableArray.res:72:6 path:+ImmutableArray
+  addValueDeclaration +map ImmutableArray.res:73:6 path:+ImmutableArray
+  addValueDeclaration +keepWithIndexU ImmutableArray.res:75:6 path:+ImmutableArray
+  addValueDeclaration +keepWithIndex ImmutableArray.res:76:6 path:+ImmutableArray
+  addValueDeclaration +keepMapU ImmutableArray.res:78:6 path:+ImmutableArray
+  addValueDeclaration +keepMap ImmutableArray.res:79:6 path:+ImmutableArray
+  addValueDeclaration +forEachWithIndexU ImmutableArray.res:81:6 path:+ImmutableArray
+  addValueDeclaration +forEachWithIndex ImmutableArray.res:82:6 path:+ImmutableArray
+  addValueDeclaration +mapWithIndexU ImmutableArray.res:84:6 path:+ImmutableArray
+  addValueDeclaration +mapWithIndex ImmutableArray.res:85:6 path:+ImmutableArray
+  addValueDeclaration +partitionU ImmutableArray.res:87:6 path:+ImmutableArray
+  addValueDeclaration +partition ImmutableArray.res:88:6 path:+ImmutableArray
+  addValueDeclaration +reduceU ImmutableArray.res:90:6 path:+ImmutableArray
+  addValueDeclaration +reduce ImmutableArray.res:91:6 path:+ImmutableArray
+  addValueDeclaration +reduceReverseU ImmutableArray.res:93:6 path:+ImmutableArray
+  addValueDeclaration +reduceReverse ImmutableArray.res:94:6 path:+ImmutableArray
+  addValueDeclaration +reduceReverse2U ImmutableArray.res:96:6 path:+ImmutableArray
+  addValueDeclaration +reduceReverse2 ImmutableArray.res:97:6 path:+ImmutableArray
+  addValueDeclaration +someU ImmutableArray.res:99:6 path:+ImmutableArray
+  addValueDeclaration +some ImmutableArray.res:100:6 path:+ImmutableArray
+  addValueDeclaration +everyU ImmutableArray.res:102:6 path:+ImmutableArray
+  addValueDeclaration +every ImmutableArray.res:103:6 path:+ImmutableArray
+  addValueDeclaration +every2U ImmutableArray.res:105:6 path:+ImmutableArray
+  addValueDeclaration +every2 ImmutableArray.res:106:6 path:+ImmutableArray
+  addValueDeclaration +some2U ImmutableArray.res:108:6 path:+ImmutableArray
+  addValueDeclaration +some2 ImmutableArray.res:109:6 path:+ImmutableArray
+  addValueDeclaration +cmpU ImmutableArray.res:111:6 path:+ImmutableArray
+  addValueDeclaration +cmp ImmutableArray.res:112:6 path:+ImmutableArray
+  addValueDeclaration +eqU ImmutableArray.res:114:6 path:+ImmutableArray
+  addValueDeclaration +eq ImmutableArray.res:115:6 path:+ImmutableArray
+  addValueReference ImmutableArray.res:14:6 --> ImmutableArray.res:14:18
+  addValueReference ImmutableArray.res:14:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:16:6 --> ImmutableArray.res:16:16
+  addValueReference ImmutableArray.res:16:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:20:6 --> ImmutableArray.res:20:15
+  addValueReference ImmutableArray.res:20:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:22:6 --> ImmutableArray.res:22:13
+  addValueReference ImmutableArray.res:22:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:24:6 --> ImmutableArray.res:24:13
+  addValueReference ImmutableArray.res:24:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:24:6 --> ImmutableArray.res:24:16
+  addValueReference ImmutableArray.res:26:6 --> ImmutableArray.res:26:16
+  addValueReference ImmutableArray.res:26:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:26:6 --> ImmutableArray.res:26:19
+  addValueReference ImmutableArray.res:28:6 --> ImmutableArray.res:28:19
+  addValueReference ImmutableArray.res:28:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:28:6 --> ImmutableArray.res:28:22
+  addValueReference ImmutableArray.res:30:6 --> ImmutableArray.res:30:22
+  addValueReference ImmutableArray.res:30:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:30:6 --> ImmutableArray.res:30:25
+  addValueReference ImmutableArray.res:32:6 --> ImmutableArray.res:32:16
+  addValueReference ImmutableArray.res:32:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:32:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:34:6 --> ImmutableArray.res:34:16
+  addValueReference ImmutableArray.res:34:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:34:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:36:6 --> ImmutableArray.res:36:26
+  addValueReference ImmutableArray.res:36:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:38:6 --> ImmutableArray.res:38:32
+  addValueReference ImmutableArray.res:38:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:40:6 --> ImmutableArray.res:40:14
+  addValueReference ImmutableArray.res:40:6 --> ImmutableArray.res:40:17
+  addValueReference ImmutableArray.res:40:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:42:6 --> ImmutableArray.res:42:15
+  addValueReference ImmutableArray.res:42:6 --> ImmutableArray.res:42:18
+  addValueReference ImmutableArray.res:42:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:44:6 --> ImmutableArray.res:44:17
+  addValueReference ImmutableArray.res:44:6 --> ImmutableArray.res:44:20
+  addValueReference ImmutableArray.res:44:6 --> ImmutableArray.res:44:23
+  addValueReference ImmutableArray.res:44:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:46:6 --> ImmutableArray.res:46:17
+  addValueReference ImmutableArray.res:46:6 --> ImmutableArray.res:46:20
+  addValueReference ImmutableArray.res:46:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:47:6 --> ImmutableArray.res:47:16
+  addValueReference ImmutableArray.res:47:6 --> ImmutableArray.res:47:19
+  addValueReference ImmutableArray.res:47:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:49:6 --> ImmutableArray.res:49:27
+  addValueReference ImmutableArray.res:49:6 --> ImmutableArray.res:49:30
+  addValueReference ImmutableArray.res:49:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:50:6 --> ImmutableArray.res:50:26
+  addValueReference ImmutableArray.res:50:6 --> ImmutableArray.res:50:29
+  addValueReference ImmutableArray.res:50:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:52:6 --> ImmutableArray.res:52:13
+  addValueReference ImmutableArray.res:52:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:52:6 --> ImmutableArray.res:52:17
+  addValueReference ImmutableArray.res:52:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:52:6 --> ImmutableArray.res:9:2
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:54:16
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:54:20
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:54:24
+  addValueReference ImmutableArray.res:54:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:55:15
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:55:19
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:55:23
+  addValueReference ImmutableArray.res:55:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:57:6 --> ImmutableArray.res:57:14
+  addValueReference ImmutableArray.res:57:6 --> ImmutableArray.res:6:2
+  addValueReference ImmutableArray.res:57:6 --> ImmutableArray.res:10:2
+  addValueReference ImmutableArray.res:59:6 --> ImmutableArray.res:59:16
+  addValueReference ImmutableArray.res:59:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:59:6 --> ImmutableArray.res:59:20
+  addValueReference ImmutableArray.res:59:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:59:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:61:6 --> ImmutableArray.res:61:20
+  addValueReference ImmutableArray.res:61:6 --> ImmutableArray.res:7:2
+  addValueReference ImmutableArray.res:61:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:63:6 --> ImmutableArray.res:63:15
+  addValueReference ImmutableArray.res:63:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:63:6 --> ImmutableArray.res:63:18
+  addValueReference ImmutableArray.res:63:6 --> ImmutableArray.res:63:27
+  addValueReference ImmutableArray.res:63:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:65:6 --> ImmutableArray.res:65:20
+  addValueReference ImmutableArray.res:65:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:65:6 --> ImmutableArray.res:65:23
+  addValueReference ImmutableArray.res:65:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:67:6 --> ImmutableArray.res:67:13
+  addValueReference ImmutableArray.res:67:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:67:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:69:6 --> ImmutableArray.res:69:18
+  addValueReference ImmutableArray.res:69:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:69:6 --> ImmutableArray.res:69:21
+  addValueReference ImmutableArray.res:70:6 --> ImmutableArray.res:70:17
+  addValueReference ImmutableArray.res:70:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:70:6 --> ImmutableArray.res:70:20
+  addValueReference ImmutableArray.res:72:6 --> ImmutableArray.res:72:14
+  addValueReference ImmutableArray.res:72:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:72:6 --> ImmutableArray.res:72:17
+  addValueReference ImmutableArray.res:72:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:73:6 --> ImmutableArray.res:73:13
+  addValueReference ImmutableArray.res:73:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:73:6 --> ImmutableArray.res:73:16
+  addValueReference ImmutableArray.res:73:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:75:6 --> ImmutableArray.res:75:24
+  addValueReference ImmutableArray.res:75:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:75:6 --> ImmutableArray.res:75:27
+  addValueReference ImmutableArray.res:75:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:76:6 --> ImmutableArray.res:76:23
+  addValueReference ImmutableArray.res:76:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:76:6 --> ImmutableArray.res:76:26
+  addValueReference ImmutableArray.res:76:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:78:6 --> ImmutableArray.res:78:18
+  addValueReference ImmutableArray.res:78:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:78:6 --> ImmutableArray.res:78:21
+  addValueReference ImmutableArray.res:78:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:79:6 --> ImmutableArray.res:79:17
+  addValueReference ImmutableArray.res:79:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:79:6 --> ImmutableArray.res:79:20
+  addValueReference ImmutableArray.res:79:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:81:6 --> ImmutableArray.res:81:27
+  addValueReference ImmutableArray.res:81:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:81:6 --> ImmutableArray.res:81:30
+  addValueReference ImmutableArray.res:82:6 --> ImmutableArray.res:82:26
+  addValueReference ImmutableArray.res:82:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:82:6 --> ImmutableArray.res:82:29
+  addValueReference ImmutableArray.res:84:6 --> ImmutableArray.res:84:23
+  addValueReference ImmutableArray.res:84:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:84:6 --> ImmutableArray.res:84:26
+  addValueReference ImmutableArray.res:84:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:85:6 --> ImmutableArray.res:85:22
+  addValueReference ImmutableArray.res:85:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:85:6 --> ImmutableArray.res:85:25
+  addValueReference ImmutableArray.res:85:6 --> ImmutableArray.res:8:2
+  addValueReference ImmutableArray.res:87:6 --> ImmutableArray.res:87:20
+  addValueReference ImmutableArray.res:87:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:87:6 --> ImmutableArray.res:87:23
+  addValueReference ImmutableArray.res:87:6 --> ImmutableArray.res:10:2
+  addValueReference ImmutableArray.res:88:6 --> ImmutableArray.res:88:19
+  addValueReference ImmutableArray.res:88:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:88:6 --> ImmutableArray.res:88:22
+  addValueReference ImmutableArray.res:88:6 --> ImmutableArray.res:10:2
+  addValueReference ImmutableArray.res:90:6 --> ImmutableArray.res:90:17
+  addValueReference ImmutableArray.res:90:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:90:6 --> ImmutableArray.res:90:20
+  addValueReference ImmutableArray.res:90:6 --> ImmutableArray.res:90:23
+  addValueReference ImmutableArray.res:91:6 --> ImmutableArray.res:91:16
+  addValueReference ImmutableArray.res:91:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:91:6 --> ImmutableArray.res:91:19
+  addValueReference ImmutableArray.res:91:6 --> ImmutableArray.res:91:22
+  addValueReference ImmutableArray.res:93:6 --> ImmutableArray.res:93:24
+  addValueReference ImmutableArray.res:93:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:93:6 --> ImmutableArray.res:93:27
+  addValueReference ImmutableArray.res:93:6 --> ImmutableArray.res:93:30
+  addValueReference ImmutableArray.res:94:6 --> ImmutableArray.res:94:23
+  addValueReference ImmutableArray.res:94:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:94:6 --> ImmutableArray.res:94:26
+  addValueReference ImmutableArray.res:94:6 --> ImmutableArray.res:94:29
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:96:25
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:96:29
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:96:33
+  addValueReference ImmutableArray.res:96:6 --> ImmutableArray.res:96:36
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:97:24
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:97:28
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:97:32
+  addValueReference ImmutableArray.res:97:6 --> ImmutableArray.res:97:35
+  addValueReference ImmutableArray.res:99:6 --> ImmutableArray.res:99:15
+  addValueReference ImmutableArray.res:99:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:99:6 --> ImmutableArray.res:99:18
+  addValueReference ImmutableArray.res:100:6 --> ImmutableArray.res:100:14
+  addValueReference ImmutableArray.res:100:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:100:6 --> ImmutableArray.res:100:17
+  addValueReference ImmutableArray.res:102:6 --> ImmutableArray.res:102:16
+  addValueReference ImmutableArray.res:102:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:102:6 --> ImmutableArray.res:102:19
+  addValueReference ImmutableArray.res:103:6 --> ImmutableArray.res:103:15
+  addValueReference ImmutableArray.res:103:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:103:6 --> ImmutableArray.res:103:18
+  addValueReference ImmutableArray.res:105:6 --> ImmutableArray.res:105:17
+  addValueReference ImmutableArray.res:105:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:105:6 --> ImmutableArray.res:105:21
+  addValueReference ImmutableArray.res:105:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:105:6 --> ImmutableArray.res:105:25
+  addValueReference ImmutableArray.res:106:6 --> ImmutableArray.res:106:16
+  addValueReference ImmutableArray.res:106:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:106:6 --> ImmutableArray.res:106:20
+  addValueReference ImmutableArray.res:106:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:106:6 --> ImmutableArray.res:106:24
+  addValueReference ImmutableArray.res:108:6 --> ImmutableArray.res:108:16
+  addValueReference ImmutableArray.res:108:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:108:6 --> ImmutableArray.res:108:20
+  addValueReference ImmutableArray.res:108:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:108:6 --> ImmutableArray.res:108:24
+  addValueReference ImmutableArray.res:109:6 --> ImmutableArray.res:109:15
+  addValueReference ImmutableArray.res:109:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:109:6 --> ImmutableArray.res:109:19
+  addValueReference ImmutableArray.res:109:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:109:6 --> ImmutableArray.res:109:23
+  addValueReference ImmutableArray.res:111:6 --> ImmutableArray.res:111:14
+  addValueReference ImmutableArray.res:111:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:111:6 --> ImmutableArray.res:111:18
+  addValueReference ImmutableArray.res:111:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:111:6 --> ImmutableArray.res:111:22
+  addValueReference ImmutableArray.res:112:6 --> ImmutableArray.res:112:13
+  addValueReference ImmutableArray.res:112:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:112:6 --> ImmutableArray.res:112:17
+  addValueReference ImmutableArray.res:112:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:112:6 --> ImmutableArray.res:112:21
+  addValueReference ImmutableArray.res:114:6 --> ImmutableArray.res:114:13
+  addValueReference ImmutableArray.res:114:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:114:6 --> ImmutableArray.res:114:17
+  addValueReference ImmutableArray.res:114:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:114:6 --> ImmutableArray.res:114:21
+  addValueReference ImmutableArray.res:115:6 --> ImmutableArray.res:115:12
+  addValueReference ImmutableArray.res:115:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:115:6 --> ImmutableArray.res:115:16
+  addValueReference ImmutableArray.res:115:6 --> ImmutableArray.res:5:2
+  addValueReference ImmutableArray.res:115:6 --> ImmutableArray.res:115:20
+  addValueReference ImmutableArray.resi:6:2 --> ImmutableArray.res:24:6
+  addValueReference ImmutableArray.resi:9:0 --> ImmutableArray.res:14:6
+  addValueReference ImmutableArray.resi:12:0 --> ImmutableArray.res:16:6
+  addValueReference ImmutableArray.resi:14:0 --> ImmutableArray.res:20:6
+  addValueReference ImmutableArray.resi:17:0 --> ImmutableArray.res:22:6
+  addValueReference ImmutableArray.resi:19:0 --> ImmutableArray.res:24:6
+  addValueReference ImmutableArray.resi:21:0 --> ImmutableArray.res:26:6
+  addValueReference ImmutableArray.resi:23:0 --> ImmutableArray.res:28:6
+  addValueReference ImmutableArray.resi:25:0 --> ImmutableArray.res:30:6
+  addValueReference ImmutableArray.resi:27:0 --> ImmutableArray.res:32:6
+  addValueReference ImmutableArray.resi:29:0 --> ImmutableArray.res:34:6
+  addValueReference ImmutableArray.resi:31:0 --> ImmutableArray.res:36:6
+  addValueReference ImmutableArray.resi:33:0 --> ImmutableArray.res:38:6
+  addValueReference ImmutableArray.resi:35:0 --> ImmutableArray.res:40:6
+  addValueReference ImmutableArray.resi:37:0 --> ImmutableArray.res:42:6
+  addValueReference ImmutableArray.resi:39:0 --> ImmutableArray.res:44:6
+  addValueReference ImmutableArray.resi:41:0 --> ImmutableArray.res:46:6
+  addValueReference ImmutableArray.resi:42:0 --> ImmutableArray.res:47:6
+  addValueReference ImmutableArray.resi:44:0 --> ImmutableArray.res:49:6
+  addValueReference ImmutableArray.resi:45:0 --> ImmutableArray.res:50:6
+  addValueReference ImmutableArray.resi:47:0 --> ImmutableArray.res:52:6
+  addValueReference ImmutableArray.resi:49:0 --> ImmutableArray.res:54:6
+  addValueReference ImmutableArray.resi:50:0 --> ImmutableArray.res:55:6
+  addValueReference ImmutableArray.resi:52:0 --> ImmutableArray.res:57:6
+  addValueReference ImmutableArray.resi:54:0 --> ImmutableArray.res:59:6
+  addValueReference ImmutableArray.resi:56:0 --> ImmutableArray.res:61:6
+  addValueReference ImmutableArray.resi:58:0 --> ImmutableArray.res:63:6
+  addValueReference ImmutableArray.resi:60:0 --> ImmutableArray.res:65:6
+  addValueReference ImmutableArray.resi:62:0 --> ImmutableArray.res:67:6
+  addValueReference ImmutableArray.resi:64:0 --> ImmutableArray.res:69:6
+  addValueReference ImmutableArray.resi:65:0 --> ImmutableArray.res:70:6
+  addValueReference ImmutableArray.resi:67:0 --> ImmutableArray.res:72:6
+  addValueReference ImmutableArray.resi:68:0 --> ImmutableArray.res:73:6
+  addValueReference ImmutableArray.resi:70:0 --> ImmutableArray.res:75:6
+  addValueReference ImmutableArray.resi:71:0 --> ImmutableArray.res:76:6
+  addValueReference ImmutableArray.resi:73:0 --> ImmutableArray.res:78:6
+  addValueReference ImmutableArray.resi:74:0 --> ImmutableArray.res:79:6
+  addValueReference ImmutableArray.resi:76:0 --> ImmutableArray.res:81:6
+  addValueReference ImmutableArray.resi:77:0 --> ImmutableArray.res:82:6
+  addValueReference ImmutableArray.resi:79:0 --> ImmutableArray.res:84:6
+  addValueReference ImmutableArray.resi:80:0 --> ImmutableArray.res:85:6
+  addValueReference ImmutableArray.resi:82:0 --> ImmutableArray.res:87:6
+  addValueReference ImmutableArray.resi:83:0 --> ImmutableArray.res:88:6
+  addValueReference ImmutableArray.resi:85:0 --> ImmutableArray.res:90:6
+  addValueReference ImmutableArray.resi:86:0 --> ImmutableArray.res:91:6
+  addValueReference ImmutableArray.resi:88:0 --> ImmutableArray.res:93:6
+  addValueReference ImmutableArray.resi:89:0 --> ImmutableArray.res:94:6
+  addValueReference ImmutableArray.resi:91:0 --> ImmutableArray.res:96:6
+  addValueReference ImmutableArray.resi:92:0 --> ImmutableArray.res:97:6
+  addValueReference ImmutableArray.resi:94:0 --> ImmutableArray.res:99:6
+  addValueReference ImmutableArray.resi:95:0 --> ImmutableArray.res:100:6
+  addValueReference ImmutableArray.resi:97:0 --> ImmutableArray.res:102:6
+  addValueReference ImmutableArray.resi:98:0 --> ImmutableArray.res:103:6
+  addValueReference ImmutableArray.resi:100:0 --> ImmutableArray.res:105:6
+  addValueReference ImmutableArray.resi:101:0 --> ImmutableArray.res:106:6
+  addValueReference ImmutableArray.resi:103:0 --> ImmutableArray.res:108:6
+  addValueReference ImmutableArray.resi:104:0 --> ImmutableArray.res:109:6
+  addValueReference ImmutableArray.resi:106:0 --> ImmutableArray.res:111:6
+  addValueReference ImmutableArray.resi:107:0 --> ImmutableArray.res:112:6
+  addValueReference ImmutableArray.resi:109:0 --> ImmutableArray.res:114:6
+  addValueReference ImmutableArray.resi:110:0 --> ImmutableArray.res:115:6
+  Scanning ImmutableArray.cmti Source:ImmutableArray.resi
+  addValueDeclaration +get ImmutableArray.resi:6:2 path:ImmutableArray.Array
+  addValueDeclaration +fromArray ImmutableArray.resi:9:0 path:ImmutableArray
+  addValueDeclaration +toArray ImmutableArray.resi:12:0 path:ImmutableArray
+  addValueDeclaration +length ImmutableArray.resi:14:0 path:ImmutableArray
+  addValueDeclaration +size ImmutableArray.resi:17:0 path:ImmutableArray
+  addValueDeclaration +get ImmutableArray.resi:19:0 path:ImmutableArray
+  addValueDeclaration +getExn ImmutableArray.resi:21:0 path:ImmutableArray
+  addValueDeclaration +getUnsafe ImmutableArray.resi:23:0 path:ImmutableArray
+  addValueDeclaration +getUndefined ImmutableArray.resi:25:0 path:ImmutableArray
+  addValueDeclaration +shuffle ImmutableArray.resi:27:0 path:ImmutableArray
+  addValueDeclaration +reverse ImmutableArray.resi:29:0 path:ImmutableArray
+  addValueDeclaration +makeUninitialized ImmutableArray.resi:31:0 path:ImmutableArray
+  addValueDeclaration +makeUninitializedUnsafe ImmutableArray.resi:33:0 path:ImmutableArray
+  addValueDeclaration +make ImmutableArray.resi:35:0 path:ImmutableArray
+  addValueDeclaration +range ImmutableArray.resi:37:0 path:ImmutableArray
+  addValueDeclaration +rangeBy ImmutableArray.resi:39:0 path:ImmutableArray
+  addValueDeclaration +makeByU ImmutableArray.resi:41:0 path:ImmutableArray
+  addValueDeclaration +makeBy ImmutableArray.resi:42:0 path:ImmutableArray
+  addValueDeclaration +makeByAndShuffleU ImmutableArray.resi:44:0 path:ImmutableArray
+  addValueDeclaration +makeByAndShuffle ImmutableArray.resi:45:0 path:ImmutableArray
+  addValueDeclaration +zip ImmutableArray.resi:47:0 path:ImmutableArray
+  addValueDeclaration +zipByU ImmutableArray.resi:49:0 path:ImmutableArray
+  addValueDeclaration +zipBy ImmutableArray.resi:50:0 path:ImmutableArray
+  addValueDeclaration +unzip ImmutableArray.resi:52:0 path:ImmutableArray
+  addValueDeclaration +concat ImmutableArray.resi:54:0 path:ImmutableArray
+  addValueDeclaration +concatMany ImmutableArray.resi:56:0 path:ImmutableArray
+  addValueDeclaration +slice ImmutableArray.resi:58:0 path:ImmutableArray
+  addValueDeclaration +sliceToEnd ImmutableArray.resi:60:0 path:ImmutableArray
+  addValueDeclaration +copy ImmutableArray.resi:62:0 path:ImmutableArray
+  addValueDeclaration +forEachU ImmutableArray.resi:64:0 path:ImmutableArray
+  addValueDeclaration +forEach ImmutableArray.resi:65:0 path:ImmutableArray
+  addValueDeclaration +mapU ImmutableArray.resi:67:0 path:ImmutableArray
+  addValueDeclaration +map ImmutableArray.resi:68:0 path:ImmutableArray
+  addValueDeclaration +keepWithIndexU ImmutableArray.resi:70:0 path:ImmutableArray
+  addValueDeclaration +keepWithIndex ImmutableArray.resi:71:0 path:ImmutableArray
+  addValueDeclaration +keepMapU ImmutableArray.resi:73:0 path:ImmutableArray
+  addValueDeclaration +keepMap ImmutableArray.resi:74:0 path:ImmutableArray
+  addValueDeclaration +forEachWithIndexU ImmutableArray.resi:76:0 path:ImmutableArray
+  addValueDeclaration +forEachWithIndex ImmutableArray.resi:77:0 path:ImmutableArray
+  addValueDeclaration +mapWithIndexU ImmutableArray.resi:79:0 path:ImmutableArray
+  addValueDeclaration +mapWithIndex ImmutableArray.resi:80:0 path:ImmutableArray
+  addValueDeclaration +partitionU ImmutableArray.resi:82:0 path:ImmutableArray
+  addValueDeclaration +partition ImmutableArray.resi:83:0 path:ImmutableArray
+  addValueDeclaration +reduceU ImmutableArray.resi:85:0 path:ImmutableArray
+  addValueDeclaration +reduce ImmutableArray.resi:86:0 path:ImmutableArray
+  addValueDeclaration +reduceReverseU ImmutableArray.resi:88:0 path:ImmutableArray
+  addValueDeclaration +reduceReverse ImmutableArray.resi:89:0 path:ImmutableArray
+  addValueDeclaration +reduceReverse2U ImmutableArray.resi:91:0 path:ImmutableArray
+  addValueDeclaration +reduceReverse2 ImmutableArray.resi:92:0 path:ImmutableArray
+  addValueDeclaration +someU ImmutableArray.resi:94:0 path:ImmutableArray
+  addValueDeclaration +some ImmutableArray.resi:95:0 path:ImmutableArray
+  addValueDeclaration +everyU ImmutableArray.resi:97:0 path:ImmutableArray
+  addValueDeclaration +every ImmutableArray.resi:98:0 path:ImmutableArray
+  addValueDeclaration +every2U ImmutableArray.resi:100:0 path:ImmutableArray
+  addValueDeclaration +every2 ImmutableArray.resi:101:0 path:ImmutableArray
+  addValueDeclaration +some2U ImmutableArray.resi:103:0 path:ImmutableArray
+  addValueDeclaration +some2 ImmutableArray.resi:104:0 path:ImmutableArray
+  addValueDeclaration +cmpU ImmutableArray.resi:106:0 path:ImmutableArray
+  addValueDeclaration +cmp ImmutableArray.resi:107:0 path:ImmutableArray
+  addValueDeclaration +eqU ImmutableArray.resi:109:0 path:ImmutableArray
+  addValueDeclaration +eq ImmutableArray.resi:110:0 path:ImmutableArray
+  Scanning ImportHookDefault.cmt Source:ImportHookDefault.res
+  addValueDeclaration +make ImportHookDefault.res:6:0 path:+ImportHookDefault
+  addValueDeclaration +make2 ImportHookDefault.res:13:0 path:+ImportHookDefault
+  addRecordLabelDeclaration name ImportHookDefault.res:2:2 path:+ImportHookDefault.person
+  addRecordLabelDeclaration age ImportHookDefault.res:3:2 path:+ImportHookDefault.person
+  Scanning ImportHooks.cmt Source:ImportHooks.res
+  addValueDeclaration +make ImportHooks.res:13:0 path:+ImportHooks
+  addValueDeclaration +foo ImportHooks.res:20:0 path:+ImportHooks
+  addRecordLabelDeclaration name ImportHooks.res:3:2 path:+ImportHooks.person
+  addRecordLabelDeclaration age ImportHooks.res:4:2 path:+ImportHooks.person
+  Scanning ImportIndex.cmt Source:ImportIndex.res
+  addValueDeclaration +make ImportIndex.res:2:0 path:+ImportIndex
+  Scanning ImportJsValue.cmt Source:ImportJsValue.res
+  addValueDeclaration +round ImportJsValue.res:1:0 path:+ImportJsValue
+  addValueDeclaration +area ImportJsValue.res:15:0 path:+ImportJsValue
+  addValueDeclaration +returnMixedArray ImportJsValue.res:23:0 path:+ImportJsValue
+  addValueDeclaration +roundedNumber ImportJsValue.res:27:4 path:+ImportJsValue
+  addValueDeclaration +areaValue ImportJsValue.res:30:4 path:+ImportJsValue
+  addValueDeclaration +getAbs ImportJsValue.res:40:6 path:+ImportJsValue.AbsoluteValue
+  addValueDeclaration +useGetProp ImportJsValue.res:47:4 path:+ImportJsValue
+  addValueDeclaration +useGetAbs ImportJsValue.res:50:4 path:+ImportJsValue
+  addValueDeclaration +useColor ImportJsValue.res:58:0 path:+ImportJsValue
+  addValueDeclaration +higherOrder ImportJsValue.res:60:0 path:+ImportJsValue
+  addValueDeclaration +returnedFromHigherOrder ImportJsValue.res:64:4 path:+ImportJsValue
+  addValueDeclaration +convertVariant ImportJsValue.res:70:0 path:+ImportJsValue
+  addValueDeclaration +polymorphic ImportJsValue.res:73:0 path:+ImportJsValue
+  addValueDeclaration +default ImportJsValue.res:75:0 path:+ImportJsValue
+  addRecordLabelDeclaration x ImportJsValue.res:11:2 path:+ImportJsValue.point
+  addRecordLabelDeclaration y ImportJsValue.res:12:2 path:+ImportJsValue.point
+  addValueReference ImportJsValue.res:27:4 --> ImportJsValue.res:1:0
+  addValueReference ImportJsValue.res:30:4 --> ImportJsValue.res:15:0
+  addValueDeclaration +getAbs ImportJsValue.res:41:8 path:+ImportJsValue.AbsoluteValue
+  addValueReference ImportJsValue.res:41:8 --> ImportJsValue.res:40:16
+  addValueReference ImportJsValue.res:40:6 --> ImportJsValue.res:41:8
+  addValueReference ImportJsValue.res:47:4 --> ImportJsValue.res:47:18
+  addValueReference ImportJsValue.res:47:4 --> ImportJsValue.res:37:2
+  addValueReference ImportJsValue.res:50:4 --> ImportJsValue.res:50:17
+  addValueReference ImportJsValue.res:50:4 --> ImportJsValue.res:40:6
+  addValueReference ImportJsValue.res:64:4 --> ImportJsValue.res:60:0
+  addVariantCaseDeclaration I ImportJsValue.res:67:2 path:+ImportJsValue.variant
+  addVariantCaseDeclaration S ImportJsValue.res:68:2 path:+ImportJsValue.variant
+  Scanning ImportMyBanner.cmt Source:ImportMyBanner.res
+  addValueDeclaration +make ImportMyBanner.res:7:0 path:+ImportMyBanner
+  addValueDeclaration +make ImportMyBanner.res:12:4 path:+ImportMyBanner
+  addRecordLabelDeclaration text ImportMyBanner.res:5:16 path:+ImportMyBanner.message
+  addValueReference ImportMyBanner.res:12:4 --> ImportMyBanner.res:7:0
+  Scanning InnerModuleTypes.cmt Source:InnerModuleTypes.res
+  addVariantCaseDeclaration Foo InnerModuleTypes.res:2:11 path:+InnerModuleTypes.I.t
+  Scanning InnerModuleTypes.cmti Source:InnerModuleTypes.resi
+  addVariantCaseDeclaration Foo InnerModuleTypes.resi:2:11 path:InnerModuleTypes.I.t
+  extendTypeDependencies InnerModuleTypes.res:2:11 --> InnerModuleTypes.resi:2:11
+  extendTypeDependencies InnerModuleTypes.resi:2:11 --> InnerModuleTypes.res:2:11
+  addTypeReference InnerModuleTypes.res:2:11 --> InnerModuleTypes.resi:2:11
+  addTypeReference InnerModuleTypes.resi:2:11 --> InnerModuleTypes.res:2:11
+  Scanning JSResource.cmt Source:JSResource.res
+  Scanning JsxV4.cmt Source:JsxV4.res
+  addValueDeclaration +make JsxV4.res:4:23 path:+JsxV4.C
+  addValueReference JsxV4.res:4:36 --> React.res:3:0
+  addValueReference JsxV4.res:7:9 --> JsxV4.res:4:23
+  Scanning LetPrivate.cmt Source:LetPrivate.res
+  addValueDeclaration +y LetPrivate.res:7:4 path:+LetPrivate
+  addValueDeclaration +x LetPrivate.res:3:6 path:+LetPrivate.local_1
+  addValueReference LetPrivate.res:7:4 --> LetPrivate.res:3:6
+  Scanning ModuleAliases.cmt Source:ModuleAliases.res
+  addValueDeclaration +testNested ModuleAliases.res:22:4 path:+ModuleAliases
+  addValueDeclaration +testInner ModuleAliases.res:25:4 path:+ModuleAliases
+  addValueDeclaration +testInner2 ModuleAliases.res:28:4 path:+ModuleAliases
+  addRecordLabelDeclaration inner ModuleAliases.res:3:19 path:+ModuleAliases.Outer.Inner.innerT
+  addRecordLabelDeclaration nested ModuleAliases.res:11:16 path:+ModuleAliases.Outer2.Inner2.InnerNested.t
+  addValueReference ModuleAliases.res:22:4 --> ModuleAliases.res:22:18
+  addValueReference ModuleAliases.res:25:4 --> ModuleAliases.res:25:17
+  addValueReference ModuleAliases.res:28:4 --> ModuleAliases.res:28:18
+  Scanning ModuleAliases2.cmt Source:ModuleAliases2.res
+  addValueDeclaration +q ModuleAliases2.res:21:4 path:+ModuleAliases2
+  addRecordLabelDeclaration x ModuleAliases2.res:3:2 path:+ModuleAliases2.record
+  addRecordLabelDeclaration y ModuleAliases2.res:4:2 path:+ModuleAliases2.record
+  addRecordLabelDeclaration outer ModuleAliases2.res:9:16 path:+ModuleAliases2.Outer.outer
+  addRecordLabelDeclaration inner ModuleAliases2.res:13:18 path:+ModuleAliases2.Outer.Inner.inner
+  Scanning ModuleExceptionBug.cmt Source:ModuleExceptionBug.res
+  addValueDeclaration +customDouble ModuleExceptionBug.res:2:6 path:+ModuleExceptionBug.Dep
+  addValueDeclaration +ddjdj ModuleExceptionBug.res:7:4 path:+ModuleExceptionBug
+  addValueReference ModuleExceptionBug.res:2:6 --> ModuleExceptionBug.res:2:21
+  addExceptionDeclaration MyOtherException ModuleExceptionBug.res:5:0 path:+ModuleExceptionBug
+  addValueReference ModuleExceptionBug.res:8:7 --> ModuleExceptionBug.res:7:4
+  Scanning NestedModules.cmt Source:NestedModules.res
+  addValueDeclaration +notNested NestedModules.res:2:4 path:+NestedModules
+  addValueDeclaration +theAnswer NestedModules.res:6:6 path:+NestedModules.Universe
+  addValueDeclaration +notExported NestedModules.res:8:6 path:+NestedModules.Universe
+  addValueDeclaration +x NestedModules.res:14:8 path:+NestedModules.Universe.Nested2
+  addValueDeclaration +nested2Value NestedModules.res:17:8 path:+NestedModules.Universe.Nested2
+  addValueDeclaration +y NestedModules.res:19:8 path:+NestedModules.Universe.Nested2
+  addValueDeclaration +x NestedModules.res:25:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +y NestedModules.res:26:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +z NestedModules.res:27:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +w NestedModules.res:28:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +nested3Value NestedModules.res:34:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +nested3Function NestedModules.res:37:10 path:+NestedModules.Universe.Nested2.Nested3
+  addValueDeclaration +nested2Function NestedModules.res:41:8 path:+NestedModules.Universe.Nested2
+  addValueDeclaration +someString NestedModules.res:50:6 path:+NestedModules.Universe
+  addValueReference NestedModules.res:37:10 --> NestedModules.res:37:29
+  addValueReference NestedModules.res:41:8 --> NestedModules.res:41:27
+  addVariantCaseDeclaration A NestedModules.res:46:4 path:+NestedModules.Universe.variant
+  addVariantCaseDeclaration B NestedModules.res:47:4 path:+NestedModules.Universe.variant
+  Scanning NestedModulesInSignature.cmt Source:NestedModulesInSignature.res
+  addValueDeclaration +theAnswer NestedModulesInSignature.res:2:6 path:+NestedModulesInSignature.Universe
+  addValueReference NestedModulesInSignature.resi:2:2 --> NestedModulesInSignature.res:2:6
+  Scanning NestedModulesInSignature.cmti Source:NestedModulesInSignature.resi
+  addValueDeclaration +theAnswer NestedModulesInSignature.resi:2:2 path:NestedModulesInSignature.Universe
+  Scanning Newsyntax.cmt Source:Newsyntax.res
+  addValueDeclaration +x Newsyntax.res:1:4 path:+Newsyntax
+  addValueDeclaration +y Newsyntax.res:3:4 path:+Newsyntax
+  addRecordLabelDeclaration xxx Newsyntax.res:6:2 path:+Newsyntax.record
+  addRecordLabelDeclaration yyy Newsyntax.res:7:2 path:+Newsyntax.record
+  addVariantCaseDeclaration A Newsyntax.res:10:15 path:+Newsyntax.variant
+  addVariantCaseDeclaration B Newsyntax.res:10:17 path:+Newsyntax.variant
+  addVariantCaseDeclaration C Newsyntax.res:10:25 path:+Newsyntax.variant
+  addRecordLabelDeclaration xx Newsyntax.res:12:16 path:+Newsyntax.record2
+  addRecordLabelDeclaration yy Newsyntax.res:12:23 path:+Newsyntax.record2
+  Scanning Newton.cmt Source:Newton.res
+  addValueDeclaration +- Newton.res:1:4 path:+Newton
+  addValueDeclaration ++ Newton.res:2:4 path:+Newton
+  addValueDeclaration +* Newton.res:3:4 path:+Newton
+  addValueDeclaration +/ Newton.res:4:4 path:+Newton
+  addValueDeclaration +newton Newton.res:6:4 path:+Newton
+  addValueDeclaration +f Newton.res:25:4 path:+Newton
+  addValueDeclaration +fPrimed Newton.res:27:4 path:+Newton
+  addValueDeclaration +result Newton.res:29:4 path:+Newton
+  addValueDeclaration +current Newton.res:7:6 path:+Newton
+  addValueReference Newton.res:7:6 --> Newton.res:6:28
+  addValueDeclaration +iterateMore Newton.res:8:6 path:+Newton
+  addValueDeclaration +delta Newton.res:9:8 path:+Newton
+  addValueReference Newton.res:9:8 --> Newton.res:8:21
+  addValueReference Newton.res:9:8 --> Newton.res:8:31
+  addValueReference Newton.res:9:8 --> Newton.res:1:4
+  addValueReference Newton.res:9:8 --> Newton.res:8:31
+  addValueReference Newton.res:9:8 --> Newton.res:8:21
+  addValueReference Newton.res:9:8 --> Newton.res:1:4
+  addValueReference Newton.res:9:8 --> Newton.res:8:31
+  addValueReference Newton.res:9:8 --> Newton.res:8:21
+  addValueReference Newton.res:8:6 --> Newton.res:9:8
+  addValueReference Newton.res:8:6 --> Newton.res:6:38
+  addValueReference Newton.res:8:6 --> Newton.res:7:6
+  addValueReference Newton.res:8:6 --> Newton.res:8:31
+  addValueDeclaration +loop Newton.res:14:10 path:+Newton
+  addValueDeclaration +previous Newton.res:15:8 path:+Newton
+  addValueReference Newton.res:15:8 --> Newton.res:7:6
+  addValueDeclaration +next Newton.res:16:8 path:+Newton
+  addValueReference Newton.res:16:8 --> Newton.res:15:8
+  addValueReference Newton.res:16:8 --> Newton.res:15:8
+  addValueReference Newton.res:16:8 --> Newton.res:6:14
+  addValueReference Newton.res:16:8 --> Newton.res:15:8
+  addValueReference Newton.res:16:8 --> Newton.res:6:18
+  addValueReference Newton.res:16:8 --> Newton.res:4:4
+  addValueReference Newton.res:16:8 --> Newton.res:1:4
+  addValueReference Newton.res:14:10 --> Newton.res:7:6
+  addValueReference Newton.res:14:10 --> Newton.res:14:10
+  addValueReference Newton.res:14:10 --> Newton.res:15:8
+  addValueReference Newton.res:14:10 --> Newton.res:16:8
+  addValueReference Newton.res:14:10 --> Newton.res:8:6
+  addValueReference Newton.res:6:4 --> Newton.res:14:10
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:3:4
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:3:4
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:3:4
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:3:4
+  addValueReference Newton.res:25:4 --> Newton.res:1:4
+  addValueReference Newton.res:25:4 --> Newton.res:25:8
+  addValueReference Newton.res:25:4 --> Newton.res:3:4
+  addValueReference Newton.res:25:4 --> Newton.res:1:4
+  addValueReference Newton.res:25:4 --> Newton.res:2:4
+  addValueReference Newton.res:27:4 --> Newton.res:27:14
+  addValueReference Newton.res:27:4 --> Newton.res:3:4
+  addValueReference Newton.res:27:4 --> Newton.res:27:14
+  addValueReference Newton.res:27:4 --> Newton.res:3:4
+  addValueReference Newton.res:27:4 --> Newton.res:27:14
+  addValueReference Newton.res:27:4 --> Newton.res:3:4
+  addValueReference Newton.res:27:4 --> Newton.res:1:4
+  addValueReference Newton.res:27:4 --> Newton.res:1:4
+  addValueReference Newton.res:29:4 --> Newton.res:25:4
+  addValueReference Newton.res:29:4 --> Newton.res:27:4
+  addValueReference Newton.res:29:4 --> Newton.res:6:4
+  addValueReference Newton.res:31:8 --> Newton.res:29:4
+  addValueReference Newton.res:31:18 --> Newton.res:29:4
+  addValueReference Newton.res:31:16 --> Newton.res:25:4
+  Scanning Opaque.cmt Source:Opaque.res
+  addValueDeclaration +noConversion Opaque.res:5:4 path:+Opaque
+  addValueDeclaration +testConvertNestedRecordFromOtherFile Opaque.res:11:4 path:+Opaque
+  addVariantCaseDeclaration A Opaque.res:2:25 path:+Opaque.opaqueFromRecords
+  addValueReference Opaque.res:5:4 --> Opaque.res:5:20
+  addValueReference Opaque.res:11:4 --> Opaque.res:11:44
+  Scanning OptArg.cmt Source:OptArg.res
+  addValueDeclaration +foo OptArg.res:1:4 path:+OptArg
+  addValueDeclaration +bar OptArg.res:3:4 path:+OptArg
+  addValueDeclaration +threeArgs OptArg.res:9:4 path:+OptArg
+  addValueDeclaration +twoArgs OptArg.res:14:4 path:+OptArg
+  addValueDeclaration +oneArg OptArg.res:18:4 path:+OptArg
+  addValueDeclaration +wrapOneArg OptArg.res:20:4 path:+OptArg
+  addValueDeclaration +fourArgs OptArg.res:24:4 path:+OptArg
+  addValueDeclaration +wrapfourArgs OptArg.res:26:4 path:+OptArg
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:14
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:20
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:26
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:11
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:17
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:23
+  addValueReference OptArg.res:1:4 --> OptArg.res:1:29
+  addValueReference OptArg.res:3:4 --> OptArg.res:3:17
+  addValueReference OptArg.res:3:4 --> OptArg.res:3:27
+  DeadOptionalArgs.addReferences foo called with optional argNames:x argNamesMaybe: OptArg.res:5:7
+  addValueReference OptArg.res:5:7 --> OptArg.res:1:4
+  DeadOptionalArgs.addReferences bar called with optional argNames: argNamesMaybe: OptArg.res:7:7
+  addValueReference OptArg.res:7:7 --> OptArg.res:3:4
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:20
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:26
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:32
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:17
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:23
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:29
+  addValueReference OptArg.res:9:4 --> OptArg.res:9:35
+  DeadOptionalArgs.addReferences threeArgs called with optional argNames:c, a argNamesMaybe: OptArg.res:11:7
+  addValueReference OptArg.res:11:7 --> OptArg.res:9:4
+  DeadOptionalArgs.addReferences threeArgs called with optional argNames:a argNamesMaybe: OptArg.res:12:7
+  addValueReference OptArg.res:12:7 --> OptArg.res:9:4
+  addValueReference OptArg.res:14:4 --> OptArg.res:14:18
+  addValueReference OptArg.res:14:4 --> OptArg.res:14:24
+  addValueReference OptArg.res:14:4 --> OptArg.res:14:15
+  addValueReference OptArg.res:14:4 --> OptArg.res:14:21
+  addValueReference OptArg.res:14:4 --> OptArg.res:14:27
+  DeadOptionalArgs.addReferences twoArgs called with optional argNames: argNamesMaybe: OptArg.res:16:12
+  addValueReference OptArg.res:16:12 --> OptArg.res:14:4
+  addValueReference OptArg.res:18:4 --> OptArg.res:18:17
+  addValueReference OptArg.res:18:4 --> OptArg.res:18:14
+  addValueReference OptArg.res:18:4 --> OptArg.res:18:24
+  DeadOptionalArgs.addReferences oneArg called with optional argNames:a argNamesMaybe:a OptArg.res:20:30
+  addValueReference OptArg.res:20:4 --> OptArg.res:20:18
+  addValueReference OptArg.res:20:4 --> OptArg.res:20:24
+  addValueReference OptArg.res:20:4 --> OptArg.res:18:4
+  DeadOptionalArgs.addReferences wrapOneArg called with optional argNames:a argNamesMaybe: OptArg.res:22:7
+  addValueReference OptArg.res:22:7 --> OptArg.res:20:4
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:19
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:25
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:31
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:37
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:16
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:22
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:28
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:34
+  addValueReference OptArg.res:24:4 --> OptArg.res:24:40
+  DeadOptionalArgs.addReferences fourArgs called with optional argNames:c, b, a argNamesMaybe:c, b, a OptArg.res:26:44
+  addValueReference OptArg.res:26:4 --> OptArg.res:26:20
+  addValueReference OptArg.res:26:4 --> OptArg.res:26:26
+  addValueReference OptArg.res:26:4 --> OptArg.res:26:32
+  addValueReference OptArg.res:26:4 --> OptArg.res:26:38
+  addValueReference OptArg.res:26:4 --> OptArg.res:24:4
+  DeadOptionalArgs.addReferences wrapfourArgs called with optional argNames:c, a argNamesMaybe: OptArg.res:28:7
+  addValueReference OptArg.res:28:7 --> OptArg.res:26:4
+  DeadOptionalArgs.addReferences wrapfourArgs called with optional argNames:c, b argNamesMaybe: OptArg.res:29:7
+  addValueReference OptArg.res:29:7 --> OptArg.res:26:4
+  addValueReference OptArg.resi:1:0 --> OptArg.res:1:4
+  OptionalArgs.addFunctionReference OptArg.resi:1:0 OptArg.res:1:4
+  addValueReference OptArg.resi:2:0 --> OptArg.res:3:4
+  OptionalArgs.addFunctionReference OptArg.resi:2:0 OptArg.res:3:4
+  Scanning OptArg.cmti Source:OptArg.resi
+  addValueDeclaration +foo OptArg.resi:1:0 path:OptArg
+  addValueDeclaration +bar OptArg.resi:2:0 path:OptArg
+  Scanning Records.cmt Source:Records.res
+  addValueDeclaration +origin Records.res:11:4 path:+Records
+  addValueDeclaration +computeArea Records.res:14:4 path:+Records
+  addValueDeclaration +coord2d Records.res:20:4 path:+Records
+  addValueDeclaration +getOpt Records.res:36:4 path:+Records
+  addValueDeclaration +findAddress Records.res:39:4 path:+Records
+  addValueDeclaration +someBusiness Records.res:43:4 path:+Records
+  addValueDeclaration +findAllAddresses Records.res:46:4 path:+Records
+  addValueDeclaration +getPayload Records.res:65:4 path:+Records
+  addValueDeclaration +getPayloadRecord Records.res:74:4 path:+Records
+  addValueDeclaration +recordValue Records.res:77:4 path:+Records
+  addValueDeclaration +payloadValue Records.res:80:4 path:+Records
+  addValueDeclaration +getPayloadRecordPlusOne Records.res:83:4 path:+Records
+  addValueDeclaration +findAddress2 Records.res:96:4 path:+Records
+  addValueDeclaration +someBusiness2 Records.res:100:4 path:+Records
+  addValueDeclaration +computeArea3 Records.res:107:4 path:+Records
+  addValueDeclaration +computeArea4 Records.res:111:4 path:+Records
+  addValueDeclaration +testMyRec Records.res:127:4 path:+Records
+  addValueDeclaration +testMyRec2 Records.res:130:4 path:+Records
+  addValueDeclaration +testMyObj Records.res:133:4 path:+Records
+  addValueDeclaration +testMyObj2 Records.res:136:4 path:+Records
+  addValueDeclaration +testMyRecBsAs Records.res:145:4 path:+Records
+  addValueDeclaration +testMyRecBsAs2 Records.res:148:4 path:+Records
+  addRecordLabelDeclaration x Records.res:5:2 path:+Records.coord
+  addRecordLabelDeclaration y Records.res:6:2 path:+Records.coord
+  addRecordLabelDeclaration z Records.res:7:2 path:+Records.coord
+  addValueReference Records.res:14:4 --> Records.res:14:20
+  addValueReference Records.res:14:4 --> Records.res:14:23
+  addValueReference Records.res:14:4 --> Records.res:14:26
+  addValueReference Records.res:14:4 --> Records.res:16:31
+  addTypeReference Records.res:14:19 --> Records.res:5:2
+  addTypeReference Records.res:14:19 --> Records.res:6:2
+  addTypeReference Records.res:14:19 --> Records.res:7:2
+  addValueReference Records.res:20:4 --> Records.res:20:15
+  addValueReference Records.res:20:4 --> Records.res:20:18
+  addRecordLabelDeclaration name Records.res:24:2 path:+Records.person
+  addRecordLabelDeclaration age Records.res:25:2 path:+Records.person
+  addRecordLabelDeclaration address Records.res:26:2 path:+Records.person
+  addRecordLabelDeclaration name Records.res:31:2 path:+Records.business
+  addRecordLabelDeclaration owner Records.res:32:2 path:+Records.business
+  addRecordLabelDeclaration address Records.res:33:2 path:+Records.business
+  addValueReference Records.res:36:4 --> Records.res:36:14
+  addValueReference Records.res:36:4 --> Records.res:36:19
+  addValueReference Records.res:36:4 --> Records.res:36:28
+  addTypeReference Records.res:40:2 --> Records.res:33:2
+  addValueReference Records.res:39:4 --> Records.res:39:19
+  addValueReference Records.res:39:4 --> Records.res:40:35
+  addValueReference Records.res:39:4 --> Records.res:36:4
+  addValueReference Records.res:46:4 --> Records.res:46:24
+  addTypeReference Records.res:50:6 --> Records.res:33:2
+  addValueReference Records.res:46:4 --> Records.res:48:14
+  addValueReference Records.res:46:4 --> Records.res:50:39
+  addValueReference Records.res:46:4 --> Records.res:36:4
+  addTypeReference Records.res:51:6 --> Records.res:32:2
+  addValueReference Records.res:46:4 --> Records.res:48:14
+  addTypeReference Records.res:51:42 --> Records.res:26:2
+  addValueReference Records.res:46:4 --> Records.res:51:37
+  addValueReference Records.res:46:4 --> Records.res:51:68
+  addValueReference Records.res:46:4 --> Records.res:36:4
+  addValueReference Records.res:46:4 --> Records.res:36:4
+  addRecordLabelDeclaration num Records.res:60:2 path:+Records.payload
+  addRecordLabelDeclaration payload Records.res:61:2 path:+Records.payload
+  addValueReference Records.res:65:4 --> Records.res:65:19
+  addTypeReference Records.res:65:18 --> Records.res:61:2
+  addRecordLabelDeclaration v Records.res:69:2 path:+Records.record
+  addRecordLabelDeclaration w Records.res:70:2 path:+Records.record
+  addValueReference Records.res:74:4 --> Records.res:74:25
+  addTypeReference Records.res:74:24 --> Records.res:61:2
+  addValueReference Records.res:80:4 --> Records.res:77:4
+  addTypeReference Records.res:85:5 --> Records.res:69:2
+  addValueReference Records.res:83:4 --> Records.res:83:32
+  addValueReference Records.res:83:4 --> Records.res:83:32
+  addTypeReference Records.res:83:31 --> Records.res:61:2
+  addRecordLabelDeclaration name Records.res:90:2 path:+Records.business2
+  addRecordLabelDeclaration owner Records.res:91:2 path:+Records.business2
+  addRecordLabelDeclaration address2 Records.res:92:2 path:+Records.business2
+  addTypeReference Records.res:97:2 --> Records.res:92:2
+  addValueReference Records.res:96:4 --> Records.res:96:20
+  addValueReference Records.res:96:4 --> Records.res:97:58
+  addValueReference Records.res:96:4 --> Records.res:36:4
+  addValueReference Records.res:107:4 --> Records.res:107:20
+  addValueReference Records.res:107:4 --> Records.res:107:20
+  addValueReference Records.res:107:4 --> Records.res:107:20
+  addValueReference Records.res:107:4 --> Records.res:108:75
+  addValueReference Records.res:111:4 --> Records.res:111:20
+  addValueReference Records.res:111:4 --> Records.res:111:20
+  addValueReference Records.res:111:4 --> Records.res:111:20
+  addValueReference Records.res:111:4 --> Records.res:112:53
+  addRecordLabelDeclaration type_ Records.res:119:2 path:+Records.myRec
+  addTypeReference Records.res:127:30 --> Records.res:119:2
+  addValueReference Records.res:127:4 --> Records.res:127:17
+  addValueReference Records.res:130:4 --> Records.res:130:18
+  addValueReference Records.res:133:4 --> Records.res:133:17
+  addValueReference Records.res:136:4 --> Records.res:136:18
+  addRecordLabelDeclaration type_ Records.res:140:2 path:+Records.myRecBsAs
+  addTypeReference Records.res:145:38 --> Records.res:140:2
+  addValueReference Records.res:145:4 --> Records.res:145:21
+  addValueReference Records.res:148:4 --> Records.res:148:22
+  Scanning References.cmt Source:References.res
+  addValueDeclaration +create References.res:4:4 path:+References
+  addValueDeclaration +access References.res:7:4 path:+References
+  addValueDeclaration +update References.res:10:4 path:+References
+  addValueDeclaration +get References.res:17:2 path:+References.R
+  addValueDeclaration +make References.res:18:2 path:+References.R
+  addValueDeclaration +set References.res:19:2 path:+References.R
+  addValueDeclaration +get References.res:31:4 path:+References
+  addValueDeclaration +make References.res:34:4 path:+References
+  addValueDeclaration +set References.res:37:4 path:+References
+  addValueDeclaration +destroysRefIdentity References.res:43:4 path:+References
+  addValueDeclaration +preserveRefIdentity References.res:47:4 path:+References
+  addValueReference References.res:4:4 --> References.res:4:14
+  addValueReference References.res:7:4 --> References.res:7:13
+  addValueReference References.res:10:4 --> References.res:10:13
+  addValueReference References.res:10:4 --> References.res:10:13
+  addValueDeclaration +get References.res:22:6 path:+References.R
+  addValueReference References.res:22:6 --> References.res:22:12
+  addValueDeclaration +make References.res:23:6 path:+References.R
+  addValueDeclaration +set References.res:24:6 path:+References.R
+  addValueReference References.res:24:6 --> References.res:24:16
+  addValueReference References.res:24:6 --> References.res:24:13
+  addValueReference References.res:31:4 --> References.res:17:2
+  addValueReference References.res:34:4 --> References.res:18:2
+  addValueReference References.res:37:4 --> References.res:19:2
+  addRecordLabelDeclaration x References.res:39:27 path:+References.requiresConversion
+  addValueReference References.res:43:4 --> References.res:43:27
+  addValueReference References.res:47:4 --> References.res:47:27
+  addValueReference References.res:17:2 --> References.res:22:6
+  addValueReference References.res:18:2 --> References.res:23:6
+  addValueReference References.res:19:2 --> References.res:24:6
+  Scanning RepeatedLabel.cmt Source:RepeatedLabel.res
+  addValueDeclaration +userData RepeatedLabel.res:12:4 path:+RepeatedLabel
+  addRecordLabelDeclaration a RepeatedLabel.res:2:2 path:+RepeatedLabel.userData
+  addRecordLabelDeclaration b RepeatedLabel.res:3:2 path:+RepeatedLabel.userData
+  addRecordLabelDeclaration a RepeatedLabel.res:7:2 path:+RepeatedLabel.tabState
+  addRecordLabelDeclaration b RepeatedLabel.res:8:2 path:+RepeatedLabel.tabState
+  addRecordLabelDeclaration f RepeatedLabel.res:9:2 path:+RepeatedLabel.tabState
+  addValueReference RepeatedLabel.res:12:4 --> RepeatedLabel.res:12:17
+  addValueReference RepeatedLabel.res:12:4 --> RepeatedLabel.res:12:20
+  addTypeReference RepeatedLabel.res:12:16 --> RepeatedLabel.res:7:2
+  addTypeReference RepeatedLabel.res:12:16 --> RepeatedLabel.res:8:2
+  addValueReference RepeatedLabel.res:14:7 --> RepeatedLabel.res:12:4
+  Scanning RequireCond.cmt Source:RequireCond.res
+  Scanning Shadow.cmt Source:Shadow.res
+  addValueDeclaration +test Shadow.res:2:4 path:+Shadow
+  addValueDeclaration +test Shadow.res:5:4 path:+Shadow
+  addValueDeclaration +test Shadow.res:11:6 path:+Shadow.M
+  addValueDeclaration +test Shadow.res:9:6 path:+Shadow.M
+  Scanning TestDeadExn.cmt Source:TestDeadExn.res
+  Scanning TestEmitInnerModules.cmt Source:TestEmitInnerModules.res
+  addValueDeclaration +x TestEmitInnerModules.res:3:6 path:+TestEmitInnerModules.Inner
+  addValueDeclaration +y TestEmitInnerModules.res:5:6 path:+TestEmitInnerModules.Inner
+  addValueDeclaration +y TestEmitInnerModules.res:12:10 path:+TestEmitInnerModules.Outer.Medium.Inner
+  Scanning TestFirstClassModules.cmt Source:TestFirstClassModules.res
+  addValueDeclaration +convert TestFirstClassModules.res:2:4 path:+TestFirstClassModules
+  addValueDeclaration +convertInterface TestFirstClassModules.res:5:4 path:+TestFirstClassModules
+  addValueDeclaration +convertRecord TestFirstClassModules.res:8:4 path:+TestFirstClassModules
+  addValueDeclaration +convertFirstClassModuleWithTypeEquations TestFirstClassModules.res:27:4 path:+TestFirstClassModules
+  addValueReference TestFirstClassModules.res:2:4 --> TestFirstClassModules.res:2:15
+  addValueReference TestFirstClassModules.res:5:4 --> TestFirstClassModules.res:5:24
+  addValueReference TestFirstClassModules.res:8:4 --> TestFirstClassModules.res:8:21
+  addValueReference TestFirstClassModules.res:27:4 --> TestFirstClassModules.res:29:2
+  Scanning TestImmutableArray.cmt Source:TestImmutableArray.res
+  addValueDeclaration +testImmutableArrayGet TestImmutableArray.res:2:4 path:+TestImmutableArray
+  addValueDeclaration +testBeltArrayGet TestImmutableArray.res:12:4 path:+TestImmutableArray
+  addValueDeclaration +testBeltArraySet TestImmutableArray.res:17:4 path:+TestImmutableArray
+  addValueReference TestImmutableArray.res:2:4 --> TestImmutableArray.res:2:28
+  addValueReference TestImmutableArray.res:2:4 --> ImmutableArray.resi:6:2
+  addValueReference TestImmutableArray.res:12:4 --> TestImmutableArray.res:12:23
+  addValueReference TestImmutableArray.res:17:4 --> TestImmutableArray.res:17:23
+  Scanning TestImport.cmt Source:TestImport.res
+  addValueDeclaration +innerStuffContents TestImport.res:1:0 path:+TestImport
+  addValueDeclaration +innerStuffContentsAsEmptyObject TestImport.res:7:0 path:+TestImport
+  addValueDeclaration +innerStuffContents TestImport.res:13:4 path:+TestImport
+  addValueDeclaration +valueStartingWithUpperCaseLetter TestImport.res:15:0 path:+TestImport
+  addValueDeclaration +defaultValue TestImport.res:18:0 path:+TestImport
+  addValueDeclaration +make TestImport.res:24:0 path:+TestImport
+  addValueDeclaration +make TestImport.res:27:4 path:+TestImport
+  addValueDeclaration +defaultValue2 TestImport.res:29:0 path:+TestImport
+  addValueReference TestImport.res:13:4 --> TestImport.res:1:0
+  addRecordLabelDeclaration text TestImport.res:22:16 path:+TestImport.message
+  addValueReference TestImport.res:27:4 --> TestImport.res:24:0
+  Scanning TestInnedModuleTypes.cmt Source:TestInnedModuleTypes.res
+  addValueDeclaration +_ TestInnedModuleTypes.res:1:0 path:+TestInnedModuleTypes
+  addTypeReference TestInnedModuleTypes.res:1:8 --> InnerModuleTypes.resi:2:11
+  Scanning TestModuleAliases.cmt Source:TestModuleAliases.res
+  addValueDeclaration +testInner1 TestModuleAliases.res:32:4 path:+TestModuleAliases
+  addValueDeclaration +testInner1Expanded TestModuleAliases.res:35:4 path:+TestModuleAliases
+  addValueDeclaration +testInner2 TestModuleAliases.res:38:4 path:+TestModuleAliases
+  addValueDeclaration +testInner2Expanded TestModuleAliases.res:41:4 path:+TestModuleAliases
+  addValueReference TestModuleAliases.res:32:4 --> TestModuleAliases.res:32:18
+  addValueReference TestModuleAliases.res:35:4 --> TestModuleAliases.res:35:26
+  addValueReference TestModuleAliases.res:38:4 --> TestModuleAliases.res:38:18
+  addValueReference TestModuleAliases.res:41:4 --> TestModuleAliases.res:41:26
+  Scanning TestOptArg.cmt Source:TestOptArg.res
+  addValueDeclaration +foo TestOptArg.res:3:4 path:+TestOptArg
+  addValueDeclaration +bar TestOptArg.res:5:4 path:+TestOptArg
+  addValueDeclaration +notSuppressesOptArgs TestOptArg.res:9:4 path:+TestOptArg
+  addValueDeclaration +liveSuppressesOptArgs TestOptArg.res:14:4 path:+TestOptArg
+  DeadOptionalArgs.addReferences OptArg.bar called with optional argNames:z argNamesMaybe: TestOptArg.res:1:7
+  addValueReference TestOptArg.res:1:7 --> OptArg.resi:2:0
+  addValueReference TestOptArg.res:3:4 --> TestOptArg.res:3:14
+  addValueReference TestOptArg.res:3:4 --> TestOptArg.res:3:11
+  addValueReference TestOptArg.res:3:4 --> TestOptArg.res:3:17
+  DeadOptionalArgs.addReferences foo called with optional argNames:x argNamesMaybe: TestOptArg.res:5:16
+  addValueReference TestOptArg.res:5:4 --> TestOptArg.res:3:4
+  addValueReference TestOptArg.res:7:7 --> TestOptArg.res:5:4
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:31
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:37
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:43
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:28
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:34
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:40
+  addValueReference TestOptArg.res:9:4 --> TestOptArg.res:9:46
+  DeadOptionalArgs.addReferences notSuppressesOptArgs called with optional argNames: argNamesMaybe: TestOptArg.res:11:8
+  addValueReference TestOptArg.res:11:8 --> TestOptArg.res:9:4
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:32
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:38
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:44
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:29
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:35
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:41
+  addValueReference TestOptArg.res:14:4 --> TestOptArg.res:14:47
+  DeadOptionalArgs.addReferences liveSuppressesOptArgs called with optional argNames:x argNamesMaybe: TestOptArg.res:16:8
+  addValueReference TestOptArg.res:16:8 --> TestOptArg.res:14:4
+  Scanning TestPromise.cmt Source:TestPromise.res
+  addValueDeclaration +convert TestPromise.res:14:4 path:+TestPromise
+  addRecordLabelDeclaration x TestPromise.res:6:2 path:+TestPromise.fromPayload
+  addRecordLabelDeclaration s TestPromise.res:7:2 path:+TestPromise.fromPayload
+  addRecordLabelDeclaration result TestPromise.res:11:18 path:+TestPromise.toPayload
+  addValueReference TestPromise.res:14:4 --> TestPromise.res:14:33
+  addTypeReference TestPromise.res:14:32 --> TestPromise.res:7:2
+  Scanning ToSuppress.cmt Source:ToSuppress.res
+  addValueDeclaration +toSuppress ToSuppress.res:1:4 path:+ToSuppress
+  Scanning TransitiveType1.cmt Source:TransitiveType1.res
+  addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1
+  addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1
+  addValueReference TransitiveType1.res:2:4 --> TransitiveType1.res:2:15
+  addValueReference TransitiveType1.res:5:4 --> TransitiveType1.res:5:20
+  Scanning TransitiveType2.cmt Source:TransitiveType2.res
+  addValueDeclaration +convertT2 TransitiveType2.res:7:4 path:+TransitiveType2
+  addValueReference TransitiveType2.res:7:4 --> TransitiveType2.res:7:17
+  Scanning TransitiveType3.cmt Source:TransitiveType3.res
+  addValueDeclaration +convertT3 TransitiveType3.res:8:4 path:+TransitiveType3
+  addRecordLabelDeclaration i TransitiveType3.res:3:2 path:+TransitiveType3.t3
+  addRecordLabelDeclaration s TransitiveType3.res:4:2 path:+TransitiveType3.t3
+  addValueReference TransitiveType3.res:8:4 --> TransitiveType3.res:8:17
+  Scanning Tuples.cmt Source:Tuples.res
+  addValueDeclaration +testTuple Tuples.res:4:4 path:+Tuples
+  addValueDeclaration +origin Tuples.res:10:4 path:+Tuples
+  addValueDeclaration +computeArea Tuples.res:13:4 path:+Tuples
+  addValueDeclaration +computeAreaWithIdent Tuples.res:19:4 path:+Tuples
+  addValueDeclaration +computeAreaNoConverters Tuples.res:25:4 path:+Tuples
+  addValueDeclaration +coord2d Tuples.res:28:4 path:+Tuples
+  addValueDeclaration +getFirstName Tuples.res:43:4 path:+Tuples
+  addValueDeclaration +marry Tuples.res:46:4 path:+Tuples
+  addValueDeclaration +changeSecondAge Tuples.res:49:4 path:+Tuples
+  addValueReference Tuples.res:4:4 --> Tuples.res:4:18
+  addValueReference Tuples.res:4:4 --> Tuples.res:4:21
+  addValueReference Tuples.res:13:4 --> Tuples.res:13:20
+  addValueReference Tuples.res:13:4 --> Tuples.res:13:23
+  addValueReference Tuples.res:13:4 --> Tuples.res:13:26
+  addValueReference Tuples.res:13:4 --> Tuples.res:15:31
+  addValueReference Tuples.res:19:4 --> Tuples.res:19:29
+  addValueReference Tuples.res:19:4 --> Tuples.res:19:32
+  addValueReference Tuples.res:19:4 --> Tuples.res:19:35
+  addValueReference Tuples.res:19:4 --> Tuples.res:21:31
+  addValueReference Tuples.res:25:4 --> Tuples.res:25:32
+  addValueReference Tuples.res:25:4 --> Tuples.res:25:40
+  addValueReference Tuples.res:28:4 --> Tuples.res:28:15
+  addValueReference Tuples.res:28:4 --> Tuples.res:28:18
+  addRecordLabelDeclaration name Tuples.res:35:2 path:+Tuples.person
+  addRecordLabelDeclaration age Tuples.res:36:2 path:+Tuples.person
+  addTypeReference Tuples.res:43:49 --> Tuples.res:35:2
+  addValueReference Tuples.res:43:4 --> Tuples.res:43:21
+  addValueReference Tuples.res:46:4 --> Tuples.res:46:13
+  addValueReference Tuples.res:46:4 --> Tuples.res:46:20
+  addValueReference Tuples.res:49:4 --> Tuples.res:49:24
+  addTypeReference Tuples.res:49:84 --> Tuples.res:36:2
+  addValueReference Tuples.res:49:4 --> Tuples.res:49:31
+  addValueReference Tuples.res:49:4 --> Tuples.res:49:31
+  Scanning TypeParams1.cmt Source:TypeParams1.res
+  addValueDeclaration +exportSomething TypeParams1.res:4:4 path:+TypeParams1
+  Scanning TypeParams2.cmt Source:TypeParams2.res
+  addValueDeclaration +exportSomething TypeParams2.res:10:4 path:+TypeParams2
+  addRecordLabelDeclaration id TypeParams2.res:2:13 path:+TypeParams2.item
+  Scanning TypeParams3.cmt Source:TypeParams3.res
+  addValueDeclaration +test TypeParams3.res:2:4 path:+TypeParams3
+  addValueDeclaration +test2 TypeParams3.res:5:4 path:+TypeParams3
+  addValueReference TypeParams3.res:2:4 --> TypeParams3.res:2:12
+  addValueReference TypeParams3.res:5:4 --> TypeParams3.res:5:13
+  Scanning Types.cmt Source:Types.res
+  addValueDeclaration +someIntList Types.res:5:4 path:+Types
+  addValueDeclaration +map Types.res:8:4 path:+Types
+  addValueDeclaration +swap Types.res:23:8 path:+Types
+  addValueDeclaration +selfRecursiveConverter Types.res:42:4 path:+Types
+  addValueDeclaration +mutuallyRecursiveConverter Types.res:49:4 path:+Types
+  addValueDeclaration +testFunctionOnOptionsAsArgument Types.res:52:4 path:+Types
+  addValueDeclaration +stringT Types.res:60:4 path:+Types
+  addValueDeclaration +jsStringT Types.res:63:4 path:+Types
+  addValueDeclaration +jsString2T Types.res:66:4 path:+Types
+  addValueDeclaration +jsonStringify Types.res:78:4 path:+Types
+  addValueDeclaration +testConvertNull Types.res:92:4 path:+Types
+  addValueDeclaration +testMarshalFields Types.res:112:4 path:+Types
+  addValueDeclaration +setMatch Types.res:128:4 path:+Types
+  addValueDeclaration +testInstantiateTypeParameter Types.res:138:4 path:+Types
+  addValueDeclaration +currentTime Types.res:147:4 path:+Types
+  addValueDeclaration +i64Const Types.res:156:4 path:+Types
+  addValueDeclaration +optFunction Types.res:159:4 path:+Types
+  addVariantCaseDeclaration A Types.res:12:2 path:+Types.typeWithVars
+  addVariantCaseDeclaration B Types.res:13:2 path:+Types.typeWithVars
+  addValueReference Types.res:23:8 --> Types.res:23:16
+  addValueReference Types.res:23:8 --> Types.res:23:16
+  addValueReference Types.res:23:8 --> Types.res:23:8
+  addValueReference Types.res:23:8 --> Types.res:23:16
+  addValueReference Types.res:23:8 --> Types.res:23:8
+  addValueReference Types.res:23:8 --> Types.res:24:2
+  addRecordLabelDeclaration self Types.res:31:26 path:+Types.selfRecursive
+  addRecordLabelDeclaration b Types.res:34:31 path:+Types.mutuallyRecursiveA
+  addRecordLabelDeclaration a Types.res:35:26 path:+Types.mutuallyRecursiveB
+  addValueReference Types.res:42:4 --> Types.res:42:31
+  addTypeReference Types.res:42:30 --> Types.res:31:26
+  addValueReference Types.res:49:4 --> Types.res:49:35
+  addTypeReference Types.res:49:34 --> Types.res:34:31
+  addValueReference Types.res:52:4 --> Types.res:52:39
+  addValueReference Types.res:52:4 --> Types.res:52:54
+  addVariantCaseDeclaration A Types.res:56:2 path:+Types.opaqueVariant
+  addVariantCaseDeclaration B Types.res:57:2 path:+Types.opaqueVariant
+  addRecordLabelDeclaration i Types.res:87:2 path:+Types.record
+  addRecordLabelDeclaration s Types.res:88:2 path:+Types.record
+  addValueReference Types.res:92:4 --> Types.res:92:23
+  addValueReference Types.res:112:4 --> Types.res:112:39
+  addValueReference Types.res:128:4 --> Types.res:128:16
+  addRecordLabelDeclaration id Types.res:133:19 path:+Types.someRecord
+  addValueReference Types.res:138:4 --> Types.res:138:36
+  addValueDeclaration +x Types.res:166:6 path:+Types.ObjectId
+  Scanning Unboxed.cmt Source:Unboxed.res
+  addValueDeclaration +testV1 Unboxed.res:8:4 path:+Unboxed
+  addValueDeclaration +r2Test Unboxed.res:17:4 path:+Unboxed
+  addVariantCaseDeclaration A Unboxed.res:2:10 path:+Unboxed.v1
+  addVariantCaseDeclaration A Unboxed.res:5:10 path:+Unboxed.v2
+  addValueReference Unboxed.res:8:4 --> Unboxed.res:8:14
+  addRecordLabelDeclaration x Unboxed.res:11:11 path:+Unboxed.r1
+  addRecordLabelDeclaration B.g Unboxed.res:14:13 path:+Unboxed.r2
+  addVariantCaseDeclaration B Unboxed.res:14:10 path:+Unboxed.r2
+  addValueReference Unboxed.res:17:4 --> Unboxed.res:17:14
+  Scanning Uncurried.cmt Source:Uncurried.res
+  addValueDeclaration +uncurried0 Uncurried.res:14:4 path:+Uncurried
+  addValueDeclaration +uncurried1 Uncurried.res:17:4 path:+Uncurried
+  addValueDeclaration +uncurried2 Uncurried.res:20:4 path:+Uncurried
+  addValueDeclaration +uncurried3 Uncurried.res:23:4 path:+Uncurried
+  addValueDeclaration +curried3 Uncurried.res:26:4 path:+Uncurried
+  addValueDeclaration +callback Uncurried.res:29:4 path:+Uncurried
+  addValueDeclaration +callback2 Uncurried.res:35:4 path:+Uncurried
+  addValueDeclaration +callback2U Uncurried.res:38:4 path:+Uncurried
+  addValueDeclaration +sumU Uncurried.res:41:4 path:+Uncurried
+  addValueDeclaration +sumU2 Uncurried.res:44:4 path:+Uncurried
+  addValueDeclaration +sumCurried Uncurried.res:47:4 path:+Uncurried
+  addValueDeclaration +sumLblCurried Uncurried.res:53:4 path:+Uncurried
+  addValueReference Uncurried.res:17:4 --> Uncurried.res:17:20
+  addValueReference Uncurried.res:20:4 --> Uncurried.res:20:20
+  addValueReference Uncurried.res:20:4 --> Uncurried.res:20:23
+  addValueReference Uncurried.res:23:4 --> Uncurried.res:23:20
+  addValueReference Uncurried.res:23:4 --> Uncurried.res:23:23
+  addValueReference Uncurried.res:23:4 --> Uncurried.res:23:26
+  addValueReference Uncurried.res:26:4 --> Uncurried.res:26:16
+  addValueReference Uncurried.res:26:4 --> Uncurried.res:26:19
+  addValueReference Uncurried.res:26:4 --> Uncurried.res:26:22
+  addValueReference Uncurried.res:29:4 --> Uncurried.res:29:15
+  addRecordLabelDeclaration login Uncurried.res:31:13 path:+Uncurried.auth
+  addRecordLabelDeclaration loginU Uncurried.res:32:14 path:+Uncurried.authU
+  addTypeReference Uncurried.res:35:24 --> Uncurried.res:31:13
+  addValueReference Uncurried.res:35:4 --> Uncurried.res:35:16
+  addTypeReference Uncurried.res:38:25 --> Uncurried.res:32:14
+  addValueReference Uncurried.res:38:4 --> Uncurried.res:38:17
+  addValueReference Uncurried.res:41:4 --> Uncurried.res:41:17
+  addValueReference Uncurried.res:41:4 --> Uncurried.res:41:14
+  addValueReference Uncurried.res:41:4 --> Uncurried.res:41:17
+  addValueReference Uncurried.res:44:4 --> Uncurried.res:44:20
+  addValueReference Uncurried.res:44:4 --> Uncurried.res:44:15
+  addValueReference Uncurried.res:44:4 --> Uncurried.res:44:20
+  addValueReference Uncurried.res:47:4 --> Uncurried.res:49:2
+  addValueReference Uncurried.res:47:4 --> Uncurried.res:47:17
+  addValueReference Uncurried.res:47:4 --> Uncurried.res:49:2
+  addValueReference Uncurried.res:47:4 --> Uncurried.res:47:17
+  addValueReference Uncurried.res:53:4 --> Uncurried.res:55:3
+  addValueReference Uncurried.res:53:4 --> Uncurried.res:53:32
+  addValueReference Uncurried.res:53:4 --> Uncurried.res:55:3
+  addValueReference Uncurried.res:53:4 --> Uncurried.res:53:21
+  addValueReference Uncurried.res:53:4 --> Uncurried.res:53:32
+  Scanning Unison.cmt Source:Unison.res
+  addValueDeclaration +group Unison.res:17:4 path:+Unison
+  addValueDeclaration +fits Unison.res:19:8 path:+Unison
+  addValueDeclaration +toString Unison.res:26:8 path:+Unison
+  addVariantCaseDeclaration IfNeed Unison.res:4:2 path:+Unison.break
+  addVariantCaseDeclaration Never Unison.res:5:2 path:+Unison.break
+  addVariantCaseDeclaration Always Unison.res:6:2 path:+Unison.break
+  addRecordLabelDeclaration break Unison.res:9:2 path:+Unison.t
+  addRecordLabelDeclaration doc Unison.res:10:2 path:+Unison.t
+  addVariantCaseDeclaration Empty Unison.res:14:2 path:+Unison.stack
+  addVariantCaseDeclaration Cons Unison.res:15:2 path:+Unison.stack
+  addValueReference Unison.res:17:4 --> Unison.res:17:20
+  addTypeReference Unison.res:17:20 --> Unison.res:4:2
+  addValueReference Unison.res:17:4 --> Unison.res:17:13
+  addValueReference Unison.res:17:4 --> Unison.res:17:28
+  addValueReference Unison.res:19:8 --> Unison.res:19:16
+  addValueReference Unison.res:19:8 --> Unison.res:19:16
+  addValueReference Unison.res:19:8 --> Unison.res:23:10
+  addValueReference Unison.res:19:8 --> Unison.res:23:16
+  addValueReference Unison.res:19:8 --> Unison.res:19:8
+  addTypeReference Unison.res:23:9 --> Unison.res:10:2
+  addValueReference Unison.res:19:8 --> Unison.res:19:19
+  addValueReference Unison.res:26:8 --> Unison.res:26:20
+  addValueReference Unison.res:26:8 --> Unison.res:28:23
+  addValueReference Unison.res:26:8 --> Unison.res:19:8
+  addValueReference Unison.res:26:8 --> Unison.res:28:23
+  addValueReference Unison.res:26:8 --> Unison.res:26:20
+  addValueReference Unison.res:26:8 --> Unison.res:26:8
+  addValueReference Unison.res:26:8 --> Unison.res:28:17
+  addValueReference Unison.res:26:8 --> Unison.res:28:23
+  addValueReference Unison.res:26:8 --> Unison.res:26:20
+  addValueReference Unison.res:26:8 --> Unison.res:26:8
+  addValueReference Unison.res:26:8 --> Unison.res:28:17
+  addValueReference Unison.res:26:8 --> Unison.res:28:23
+  addValueReference Unison.res:26:8 --> Unison.res:26:20
+  addValueReference Unison.res:26:8 --> Unison.res:26:8
+  addValueReference Unison.res:26:8 --> Unison.res:28:10
+  addTypeReference Unison.res:28:9 --> Unison.res:9:2
+  addTypeReference Unison.res:28:9 --> Unison.res:10:2
+  addValueReference Unison.res:26:8 --> Unison.res:26:28
+  addTypeReference Unison.res:37:20 --> Unison.res:14:2
+  addValueReference Unison.res:37:0 --> Unison.res:26:8
+  addTypeReference Unison.res:38:20 --> Unison.res:15:2
+  DeadOptionalArgs.addReferences group called with optional argNames:break argNamesMaybe: Unison.res:38:25
+  addTypeReference Unison.res:38:38 --> Unison.res:5:2
+  addValueReference Unison.res:38:25 --> Unison.res:17:4
+  addTypeReference Unison.res:38:53 --> Unison.res:14:2
+  addValueReference Unison.res:38:0 --> Unison.res:26:8
+  addTypeReference Unison.res:39:20 --> Unison.res:15:2
+  DeadOptionalArgs.addReferences group called with optional argNames:break argNamesMaybe: Unison.res:39:25
+  addTypeReference Unison.res:39:38 --> Unison.res:6:2
+  addValueReference Unison.res:39:25 --> Unison.res:17:4
+  addTypeReference Unison.res:39:52 --> Unison.res:14:2
+  addValueReference Unison.res:39:0 --> Unison.res:26:8
+  Scanning UseImportJsValue.cmt Source:UseImportJsValue.res
+  addValueDeclaration +useGetProp UseImportJsValue.res:2:4 path:+UseImportJsValue
+  addValueDeclaration +useTypeImportedInOtherModule UseImportJsValue.res:5:4 path:+UseImportJsValue
+  addValueReference UseImportJsValue.res:2:4 --> UseImportJsValue.res:2:18
+  addValueReference UseImportJsValue.res:2:4 --> ImportJsValue.res:37:2
+  addValueReference UseImportJsValue.res:5:4 --> UseImportJsValue.res:5:36
+  Scanning Variants.cmt Source:Variants.res
+  addValueDeclaration +isWeekend Variants.res:13:4 path:+Variants
+  addValueDeclaration +monday Variants.res:21:4 path:+Variants
+  addValueDeclaration +saturday Variants.res:23:4 path:+Variants
+  addValueDeclaration +sunday Variants.res:25:4 path:+Variants
+  addValueDeclaration +onlySunday Variants.res:28:4 path:+Variants
+  addValueDeclaration +swap Variants.res:31:4 path:+Variants
+  addValueDeclaration +testConvert Variants.res:45:4 path:+Variants
+  addValueDeclaration +fortytwoOK Variants.res:48:4 path:+Variants
+  addValueDeclaration +fortytwoBAD Variants.res:52:4 path:+Variants
+  addValueDeclaration +testConvert2 Variants.res:64:4 path:+Variants
+  addValueDeclaration +testConvert3 Variants.res:76:4 path:+Variants
+  addValueDeclaration +testConvert2to3 Variants.res:80:4 path:+Variants
+  addValueDeclaration +id1 Variants.res:89:4 path:+Variants
+  addValueDeclaration +id2 Variants.res:92:4 path:+Variants
+  addValueDeclaration +polyWithOpt Variants.res:98:4 path:+Variants
+  addValueDeclaration +restResult1 Variants.res:112:4 path:+Variants
+  addValueDeclaration +restResult2 Variants.res:115:4 path:+Variants
+  addValueDeclaration +restResult3 Variants.res:118:4 path:+Variants
+  addValueReference Variants.res:13:4 --> Variants.res:13:17
+  addValueReference Variants.res:31:4 --> Variants.res:31:11
+  addValueReference Variants.res:45:4 --> Variants.res:45:19
+  addValueReference Variants.res:64:4 --> Variants.res:64:20
+  addValueReference Variants.res:76:4 --> Variants.res:76:20
+  addValueReference Variants.res:80:4 --> Variants.res:80:23
+  addValueReference Variants.res:89:4 --> Variants.res:89:11
+  addValueReference Variants.res:92:4 --> Variants.res:92:11
+  addVariantCaseDeclaration Type Variants.res:95:13 path:+Variants.type_
+  addValueReference Variants.res:98:4 --> Variants.res:98:18
+  addValueReference Variants.res:98:4 --> Variants.res:98:18
+  addValueReference Variants.res:98:4 --> Variants.res:98:18
+  addVariantCaseDeclaration Ok Variants.res:102:2 path:+Variants.result1
+  addVariantCaseDeclaration Error Variants.res:103:2 path:+Variants.result1
+  addValueReference Variants.res:112:4 --> Variants.res:112:19
+  addValueReference Variants.res:115:4 --> Variants.res:115:19
+  addValueReference Variants.res:118:4 --> Variants.res:118:19
+  Scanning VariantsWithPayload.cmt Source:VariantsWithPayload.res
+  addValueDeclaration +testWithPayload VariantsWithPayload.res:16:4 path:+VariantsWithPayload
+  addValueDeclaration +printVariantWithPayload VariantsWithPayload.res:19:4 path:+VariantsWithPayload
+  addValueDeclaration +testManyPayloads VariantsWithPayload.res:37:4 path:+VariantsWithPayload
+  addValueDeclaration +printManyPayloads VariantsWithPayload.res:40:4 path:+VariantsWithPayload
+  addValueDeclaration +testSimpleVariant VariantsWithPayload.res:54:4 path:+VariantsWithPayload
+  addValueDeclaration +testVariantWithPayloads VariantsWithPayload.res:65:4 path:+VariantsWithPayload
+  addValueDeclaration +printVariantWithPayloads VariantsWithPayload.res:68:4 path:+VariantsWithPayload
+  addValueDeclaration +testVariant1Int VariantsWithPayload.res:93:4 path:+VariantsWithPayload
+  addValueDeclaration +testVariant1Object VariantsWithPayload.res:99:4 path:+VariantsWithPayload
+  addRecordLabelDeclaration x VariantsWithPayload.res:2:2 path:+VariantsWithPayload.payload
+  addRecordLabelDeclaration y VariantsWithPayload.res:3:2 path:+VariantsWithPayload.payload
+  addValueReference VariantsWithPayload.res:16:4 --> VariantsWithPayload.res:16:23
+  addTypeReference VariantsWithPayload.res:26:57 --> VariantsWithPayload.res:2:2
+  addValueReference VariantsWithPayload.res:19:4 --> VariantsWithPayload.res:26:7
+  addTypeReference VariantsWithPayload.res:26:74 --> VariantsWithPayload.res:3:2
+  addValueReference VariantsWithPayload.res:19:4 --> VariantsWithPayload.res:26:7
+  addValueReference VariantsWithPayload.res:19:4 --> VariantsWithPayload.res:19:31
+  addValueReference VariantsWithPayload.res:37:4 --> VariantsWithPayload.res:37:24
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:42:9
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:43:9
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:43:13
+  addTypeReference VariantsWithPayload.res:44:55 --> VariantsWithPayload.res:2:2
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:44:11
+  addTypeReference VariantsWithPayload.res:44:72 --> VariantsWithPayload.res:3:2
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:44:11
+  addValueReference VariantsWithPayload.res:40:4 --> VariantsWithPayload.res:40:25
+  addVariantCaseDeclaration A VariantsWithPayload.res:49:2 path:+VariantsWithPayload.simpleVariant
+  addVariantCaseDeclaration B VariantsWithPayload.res:50:2 path:+VariantsWithPayload.simpleVariant
+  addVariantCaseDeclaration C VariantsWithPayload.res:51:2 path:+VariantsWithPayload.simpleVariant
+  addValueReference VariantsWithPayload.res:54:4 --> VariantsWithPayload.res:54:25
+  addVariantCaseDeclaration A VariantsWithPayload.res:58:2 path:+VariantsWithPayload.variantWithPayloads
+  addVariantCaseDeclaration B VariantsWithPayload.res:59:2 path:+VariantsWithPayload.variantWithPayloads
+  addVariantCaseDeclaration C VariantsWithPayload.res:60:2 path:+VariantsWithPayload.variantWithPayloads
+  addVariantCaseDeclaration D VariantsWithPayload.res:61:2 path:+VariantsWithPayload.variantWithPayloads
+  addVariantCaseDeclaration E VariantsWithPayload.res:62:2 path:+VariantsWithPayload.variantWithPayloads
+  addValueReference VariantsWithPayload.res:65:4 --> VariantsWithPayload.res:65:31
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:71:6
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:72:6
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:72:9
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:77:7
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:77:10
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:82:6
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:82:9
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:82:12
+  addValueReference VariantsWithPayload.res:68:4 --> VariantsWithPayload.res:68:31
+  addVariantCaseDeclaration R VariantsWithPayload.res:90:19 path:+VariantsWithPayload.variant1Int
+  addValueReference VariantsWithPayload.res:93:4 --> VariantsWithPayload.res:93:23
+  addVariantCaseDeclaration R VariantsWithPayload.res:96:22 path:+VariantsWithPayload.variant1Object
+  addValueReference VariantsWithPayload.res:99:4 --> VariantsWithPayload.res:99:26
+  addValueReference TestDeadExn.res:1:7 --> DeadExn.res:1:0
+  
+File References
+
+  AutoAnnotate.res -->> 
+  BootloaderResource.res -->> 
+  BucklescriptAnnotations.res -->> 
+  ComponentAsProp.res -->> React.res, ReactDOMRe.res
+  CreateErrorHandler1.res -->> ErrorHandler.resi
+  CreateErrorHandler2.res -->> 
+  DeadCodeImplementation.res -->> 
+  DeadCodeInterface.res -->> 
+  DeadExn.res -->> 
+  DeadExn.resi -->> 
+  DeadRT.res -->> 
+  DeadRT.resi -->> 
+  DeadTest.res -->> React.res, BootloaderResource.res, DeadValueTest.resi, DynamicallyLoadedComponent.res, ImmutableArray.resi, JSResource.res
+  DeadTestBlacklist.res -->> 
+  DeadTestWithInterface.res -->> 
+  DeadTypeTest.res -->> 
+  DeadTypeTest.resi -->> DeadTypeTest.res
+  DeadValueTest.res -->> 
+  DeadValueTest.resi -->> DeadValueTest.res
+  Docstrings.res -->> 
+  DynamicallyLoadedComponent.res -->> React.res
+  EmptyArray.res -->> React.res, ReactDOMRe.res
+  ErrorHandler.res -->> 
+  ErrorHandler.resi -->> ErrorHandler.res
+  EverythingLiveHere.res -->> 
+  FC.res -->> 
+  FirstClassModules.res -->> 
+  FirstClassModulesInterface.res -->> 
+  FirstClassModulesInterface.resi -->> FirstClassModulesInterface.res
+  Hooks.res -->> React.res, ReactDOM.res, ReactDOMRe.res, ImportHookDefault.res, ImportHooks.res
+  IgnoreInterface.res -->> 
+  IgnoreInterface.resi -->> 
+  ImmutableArray.res -->> 
+  ImmutableArray.resi -->> ImmutableArray.res
+  ImportHookDefault.res -->> 
+  ImportHooks.res -->> 
+  ImportIndex.res -->> 
+  ImportJsValue.res -->> 
+  ImportMyBanner.res -->> 
+  InnerModuleTypes.res -->> 
+  InnerModuleTypes.resi -->> 
+  JSResource.res -->> 
+  JsxV4.res -->> React.res
+  LetPrivate.res -->> 
+  ModuleAliases.res -->> 
+  ModuleAliases2.res -->> 
+  ModuleExceptionBug.res -->> 
+  NestedModules.res -->> 
+  NestedModulesInSignature.res -->> 
+  NestedModulesInSignature.resi -->> NestedModulesInSignature.res
+  Newsyntax.res -->> 
+  Newton.res -->> 
+  Opaque.res -->> 
+  OptArg.res -->> 
+  OptArg.resi -->> OptArg.res
+  Records.res -->> 
+  References.res -->> 
+  RepeatedLabel.res -->> 
+  RequireCond.res -->> 
+  Shadow.res -->> 
+  TestDeadExn.res -->> DeadExn.res
+  TestEmitInnerModules.res -->> 
+  TestFirstClassModules.res -->> 
+  TestImmutableArray.res -->> ImmutableArray.resi
+  TestImport.res -->> 
+  TestInnedModuleTypes.res -->> 
+  TestModuleAliases.res -->> 
+  TestOptArg.res -->> OptArg.resi
+  TestPromise.res -->> 
+  ToSuppress.res -->> 
+  TransitiveType1.res -->> 
+  TransitiveType2.res -->> 
+  TransitiveType3.res -->> 
+  Tuples.res -->> 
+  TypeParams1.res -->> 
+  TypeParams2.res -->> 
+  TypeParams3.res -->> 
+  Types.res -->> 
+  Unboxed.res -->> 
+  Uncurried.res -->> 
+  Unison.res -->> 
+  UseImportJsValue.res -->> ImportJsValue.res
+  Variants.res -->> 
+  VariantsWithPayload.res -->> 
+  Dead VariantCase +AutoAnnotate.annotatedVariant.R4: 0 references () [0]
+  Dead VariantCase +AutoAnnotate.annotatedVariant.R2: 0 references () [0]
+  Dead RecordLabel +AutoAnnotate.r4.r4: 0 references () [0]
+  Dead RecordLabel +AutoAnnotate.r3.r3: 0 references () [0]
+  Dead RecordLabel +AutoAnnotate.r2.r2: 0 references () [0]
+  Dead RecordLabel +AutoAnnotate.record.variant: 0 references () [0]
+  Dead VariantCase +AutoAnnotate.variant.R: 0 references () [0]
+  Dead Value +BucklescriptAnnotations.+bar: 0 references () [1]
+  Dead Value +BucklescriptAnnotations.+f: 0 references () [0]
+  Live Value +ComponentAsProp.+make: 0 references () [0]
+  Live Value +CreateErrorHandler1.Error1.+notification: 1 references (ErrorHandler.resi:3:2) [0]
+  Live Value +CreateErrorHandler2.Error2.+notification: 1 references (ErrorHandler.resi:3:2) [0]
+  Live Value +DeadCodeImplementation.M.+x: 1 references (DeadCodeInterface.res:2:2) [0]
+  Dead Value +DeadRT.+emitModuleAccessPath: 0 references () [0]
+  Live VariantCase +DeadRT.moduleAccessPath.Kaboom: 1 references (DeadRT.res:11:16) [0]
+  Live VariantCase DeadRT.moduleAccessPath.Root: 1 references (DeadTest.res:106:16) [1]
+  Live VariantCase +DeadRT.moduleAccessPath.Root: 1 references (DeadRT.resi:2:2) [0]
+  Live VariantCase DeadRT.moduleAccessPath.Kaboom: 1 references (DeadRT.res:3:2) [0]
+  Dead RecordLabel +DeadTest.inlineRecord3.IR3.b: 0 references () [0]
+  Dead RecordLabel +DeadTest.inlineRecord3.IR3.a: 0 references () [0]
+  Dead VariantCase +DeadTest.inlineRecord3.IR3: 0 references () [0]
+  Dead RecordLabel +DeadTest.inlineRecord2.IR2.b: 0 references () [0]
+  Dead RecordLabel +DeadTest.inlineRecord2.IR2.a: 0 references () [0]
+  Dead VariantCase +DeadTest.inlineRecord2.IR2: 0 references () [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Live Value +DeadTest.+ira: 1 references (DeadTest.res:187:27) [0]
+  Live RecordLabel +DeadTest.inlineRecord.IR.e: 0 references () [0]
+  Dead RecordLabel +DeadTest.inlineRecord.IR.d: 0 references () [0]
+  Live RecordLabel +DeadTest.inlineRecord.IR.c: 1 references (DeadTest.res:187:7) [0]
+  Live RecordLabel +DeadTest.inlineRecord.IR.b: 1 references (DeadTest.res:187:35) [0]
+  Dead RecordLabel +DeadTest.inlineRecord.IR.a: 0 references () [0]
+  Live VariantCase +DeadTest.inlineRecord.IR: 1 references (DeadTest.res:187:20) [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Live Value +DeadTest.+deadIncorrect: 1 references (DeadTest.res:180:8) [0]
+  Dead RecordLabel +DeadTest.rc.a: 0 references () [0]
+  Dead Value +DeadTest.+funWithInnerVars: 0 references () [1]
+  Dead Value +DeadTest.+y: 0 references () [0]
+  Dead Value +DeadTest.+x: 0 references () [0]
+  Live VariantCase +DeadTest.WithInclude.t.A: 1 references (DeadTest.res:166:7) [1]
+  Live VariantCase +DeadTest.WithInclude.t.A: 1 references (DeadTest.res:158:11) [0]
+  Live Value +DeadTest.GloobLive.+globallyLive3: 0 references () [0]
+  Live Value +DeadTest.GloobLive.+globallyLive2: 0 references () [0]
+  Live Value +DeadTest.GloobLive.+globallyLive1: 0 references () [0]
+  Dead Value +DeadTest.+stringLengthNoSideEffects: 0 references () [0]
+  Dead Value +DeadTest.+theSideEffectIsLogging: 0 references () [0]
+  Live Value +DeadTest.+make: 1 references (DeadTest.res:143:16) [0]
+  Dead Value +DeadTest.+deadRef: 0 references () [0]
+  Dead Value +DeadTest.+minute: 0 references () [0]
+  Dead Value +DeadTest.+second: 0 references () [0]
+  Dead Value +DeadTest.+a3: 0 references () [0]
+  Dead Value +DeadTest.+a2: 0 references () [0]
+  Dead Value +DeadTest.+a1: 0 references () [0]
+  Dead Value +DeadTest.+zzz: 0 references () [0]
+  Dead Value +DeadTest.LazyDynamicallyLoadedComponent2.+make: 0 references () [0]
+  Dead Value +DeadTest.LazyDynamicallyLoadedComponent2.+makeProps: 0 references () [0]
+  Dead Value +DeadTest.LazyDynamicallyLoadedComponent2.+reasonResource: 0 references () [0]
+  Dead Value +DeadTest.+withDefaultValue: 0 references () [0]
+  Dead Value +DeadTest.+bar: 0 references () [0]
+  Dead Value +DeadTest.+foo: 0 references () [1]
+  Dead Value +DeadTest.+cb: 0 references () [0]
+  Dead Value +DeadTest.+cb: 0 references () [0]
+  Dead Value +DeadTest.+recWithCallback: 0 references () [0]
+  Dead Value +DeadTest.+rec2: 0 references () [0]
+  Dead Value +DeadTest.+rec1: 0 references () [0]
+  Dead Value +DeadTest.+split_map: 0 references () [0]
+  Dead Value +DeadTest.+unusedRec: 0 references () [0]
+  Dead Value +DeadTest.MM.+valueOnlyInImplementation: 0 references () [0]
+  Live Value +DeadTest.MM.+x: 1 references (DeadTest.res:69:9) [1]
+  Live Value +DeadTest.MM.+x: 1 references (DeadTest.res:60:2) [0]
+  Dead Value +DeadTest.MM.+y: 0 references () [1]
+  Live Value +DeadTest.MM.+y: 1 references (DeadTest.res:64:6) [0]
+  Dead Value +DeadTest.UnderscoreInside.+_: 0 references () [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Live RecordLabel +DeadTest.record.yyy: 1 references (DeadTest.res:53:9) [0]
+  Live RecordLabel +DeadTest.record.xxx: 1 references (DeadTest.res:52:13) [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Dead Value +DeadTest.+_: 0 references () [0]
+  Live Value +DeadTest.VariantUsedOnlyInImplementation.+a: 1 references (DeadTest.res:42:17) [1]
+  Live Value +DeadTest.VariantUsedOnlyInImplementation.+a: 1 references (DeadTest.res:36:2) [0]
+  Live VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A: 1 references (DeadTest.res:39:10) [0]
+  Live VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A: 1 references (DeadTest.res:38:11) [0]
+  Dead Value +DeadTest.M.+thisSignatureItemIsDead: 0 references () [1]
+  Dead Value +DeadTest.M.+thisSignatureItemIsDead: 0 references () [0]
+  Dead Value +DeadTest.Inner.+thisIsAlsoMarkedDead: 0 references () [0]
+  Live Value +DeadTest.+thisIsMarkedLive: 0 references () [0]
+  Live Value +DeadTest.+thisIsKeptAlive: 1 references (DeadTest.res:20:4) [0]
+  Dead Value +DeadTest.+thisIsMarkedDead: 0 references () [0]
+  Live Value +DeadTest.+thisIsUsedTwice: 2 references (DeadTest.res:11:7, DeadTest.res:12:7) [0]
+  Live Value +DeadTest.+thisIsUsedOnce: 1 references (DeadTest.res:8:7) [0]
+  Live Value +DeadTest.+fortyTwoButExported: 0 references () [0]
+  Dead Value +DeadTest.+fortytwo: 0 references () [0]
+  Dead Value +DeadTestBlacklist.+x: 0 references () [0]
+  Dead Value +DeadTestWithInterface.Ext_buffer.+x: 0 references () [1]
+  Dead Value +DeadTestWithInterface.Ext_buffer.+x: 0 references () [0]
+  Dead VariantCase DeadTypeTest.deadType.InNeither: 0 references () [0]
+  Live VariantCase +DeadTypeTest.deadType.InBoth: 1 references (DeadTypeTest.res:13:8) [1]
+  Live VariantCase DeadTypeTest.deadType.InBoth: 2 references (DeadTest.res:45:8, DeadTypeTest.res:9:2) [0]
+  Live VariantCase DeadTypeTest.deadType.OnlyInInterface: 1 references (DeadTest.res:44:8) [0]
+  Live VariantCase +DeadTypeTest.deadType.OnlyInImplementation: 1 references (DeadTypeTest.res:12:8) [1]
+  Live VariantCase DeadTypeTest.deadType.OnlyInImplementation: 1 references (DeadTypeTest.res:7:2) [0]
+  Dead Value DeadTypeTest.+a: 0 references () [0]
+  Dead VariantCase DeadTypeTest.t.B: 0 references () [0]
+  Live VariantCase +DeadTypeTest.t.A: 1 references (DeadTypeTest.res:4:8) [1]
+  Live VariantCase DeadTypeTest.t.A: 1 references (DeadTypeTest.res:2:2) [0]
+  Live Value +Docstrings.+unitArgWithConversionU: 0 references () [0]
+  Live Value +Docstrings.+unitArgWithConversion: 0 references () [0]
+  Dead VariantCase +Docstrings.t.B: 0 references () [0]
+  Live VariantCase +Docstrings.t.A: 2 references (Docstrings.res:64:34, Docstrings.res:67:39) [0]
+  Live Value +Docstrings.+unitArgWithoutConversionU: 0 references () [0]
+  Live Value +Docstrings.+unitArgWithoutConversion: 0 references () [0]
+  Live Value +Docstrings.+grouped: 0 references () [0]
+  Live Value +Docstrings.+unnamed2U: 0 references () [0]
+  Live Value +Docstrings.+unnamed2: 0 references () [0]
+  Live Value +Docstrings.+unnamed1U: 0 references () [0]
+  Live Value +Docstrings.+unnamed1: 0 references () [0]
+  Live Value +Docstrings.+useParamU: 0 references () [0]
+  Live Value +Docstrings.+useParam: 0 references () [0]
+  Live Value +Docstrings.+treeU: 0 references () [0]
+  Live Value +Docstrings.+twoU: 0 references () [0]
+  Live Value +Docstrings.+oneU: 0 references () [0]
+  Live Value +Docstrings.+tree: 0 references () [0]
+  Live Value +Docstrings.+two: 0 references () [0]
+  Live Value +Docstrings.+one: 0 references () [0]
+  Live Value +Docstrings.+signMessage: 0 references () [0]
+  Live Value +Docstrings.+flat: 0 references () [0]
+  Live Value +EmptyArray.Z.+make: 1 references (EmptyArray.res:10:9) [0]
+  Dead Value +EverythingLiveHere.+z: 0 references () [0]
+  Dead Value +EverythingLiveHere.+y: 0 references () [0]
+  Dead Value +EverythingLiveHere.+x: 0 references () [0]
+  Live Value +FC.+foo: 1 references (FC.res:11:7) [0]
+  Live Value +FirstClassModules.+someFunctorAsFunction: 0 references () [0]
+  Live Value +FirstClassModules.SomeFunctor.+ww: 1 references (FirstClassModules.res:57:2) [0]
+  Live Value +FirstClassModules.+testConvert: 0 references () [0]
+  Live Value +FirstClassModules.+firstClassModule: 0 references () [0]
+  Live Value +FirstClassModules.M.+x: 1 references (FirstClassModules.res:2:2) [0]
+  Live Value +FirstClassModules.M.Z.+u: 1 references (FirstClassModules.res:37:4) [0]
+  Live Value +FirstClassModules.M.InnerModule3.+k3: 1 references (FirstClassModules.res:14:4) [0]
+  Live Value +FirstClassModules.M.InnerModule2.+k: 1 references (FirstClassModules.res:10:4) [0]
+  Live Value +FirstClassModules.M.+y: 1 references (FirstClassModules.res:20:2) [0]
+  Dead Value FirstClassModulesInterface.+r: 0 references () [0]
+  Dead RecordLabel FirstClassModulesInterface.record.y: 0 references () [0]
+  Dead RecordLabel FirstClassModulesInterface.record.x: 0 references () [0]
+  Live Value +Hooks.+aComponentWithChildren: 0 references () [0]
+  Live Value +Hooks.RenderPropRequiresConversion.+car: 1 references (Hooks.res:109:30) [0]
+  Live Value +Hooks.RenderPropRequiresConversion.+make: 0 references () [0]
+  Live Value +Hooks.+functionReturningReactElement: 0 references () [0]
+  Live Value +Hooks.+polymorphicComponent: 0 references () [0]
+  Live Value +Hooks.+input: 0 references () [0]
+  Live RecordLabel +Hooks.r.x: 1 references (Hooks.res:85:87) [0]
+  Live Value +Hooks.+testForwardRef: 0 references () [0]
+  Dead Value +Hooks.+_: 0 references () [0]
+  Live Value +Hooks.+makeWithRef: 1 references (Hooks.res:80:4) [0]
+  Live Value +Hooks.+componentWithRenamedArgs: 0 references () [0]
+  Live Value +Hooks.+functionWithRenamedArgs: 0 references () [0]
+  Live Value +Hooks.NoProps.+make: 0 references () [0]
+  Live Value +Hooks.Inner.Inner2.+anotherComponent: 0 references () [0]
+  Live Value +Hooks.Inner.Inner2.+make: 0 references () [0]
+  Live Value +Hooks.Inner.+anotherComponent: 0 references () [0]
+  Live Value +Hooks.Inner.+make: 0 references () [0]
+  Live Value +Hooks.+anotherComponent: 0 references () [0]
+  Live Value +Hooks.+default: 0 references () [0]
+  Live Value +Hooks.+make: 1 references (Hooks.res:25:4) [0]
+  Live RecordLabel +Hooks.vehicle.name: 13 references (Hooks.res:10:29, Hooks.res:30:41, Hooks.res:35:66, Hooks.res:38:78, Hooks.res:42:68, Hooks.res:46:45, Hooks.res:60:2, Hooks.res:60:14, Hooks.res:66:15, Hooks.res:66:27, Hooks.res:74:73, Hooks.res:100:58, Hooks.res:115:41) [0]
+  Live Value +ImportIndex.+make: 0 references () [0]
+  Dead Value +ImportMyBanner.+make: 0 references () [0]
+  Live Value +ImportMyBanner.+make: 0 references () [0]
+  Dead RecordLabel +ImportMyBanner.message.text: 0 references () [0]
+  Live VariantCase InnerModuleTypes.I.t.Foo: 1 references (TestInnedModuleTypes.res:1:8) [1]
+  Live VariantCase +InnerModuleTypes.I.t.Foo: 1 references (InnerModuleTypes.resi:2:11) [0]
+  Live Value +JsxV4.C.+make: 1 references (JsxV4.res:7:9) [0]
+  Live Value +LetPrivate.+y: 0 references () [0]
+  Live Value +LetPrivate.local_1.+x: 1 references (LetPrivate.res:7:4) [0]
+  Live Value +ModuleAliases.+testInner2: 0 references () [0]
+  Live Value +ModuleAliases.+testInner: 0 references () [0]
+  Live Value +ModuleAliases.+testNested: 0 references () [0]
+  Dead RecordLabel +ModuleAliases.Outer2.Inner2.InnerNested.t.nested: 0 references () [0]
+  Dead RecordLabel +ModuleAliases.Outer.Inner.innerT.inner: 0 references () [0]
+  Dead Value +ModuleAliases2.+q: 0 references () [0]
+  Dead RecordLabel +ModuleAliases2.Outer.Inner.inner.inner: 0 references () [0]
+  Dead RecordLabel +ModuleAliases2.Outer.outer.outer: 0 references () [0]
+  Dead RecordLabel +ModuleAliases2.record.y: 0 references () [0]
+  Dead RecordLabel +ModuleAliases2.record.x: 0 references () [0]
+  Live Value +ModuleExceptionBug.+ddjdj: 1 references (ModuleExceptionBug.res:8:7) [0]
+  Dead Exception +ModuleExceptionBug.MyOtherException: 0 references () [0]
+  Dead Value +ModuleExceptionBug.Dep.+customDouble: 0 references () [0]
+  Live Value +NestedModules.Universe.+someString: 0 references () [0]
+  Dead VariantCase +NestedModules.Universe.variant.B: 0 references () [0]
+  Dead VariantCase +NestedModules.Universe.variant.A: 0 references () [0]
+  Live Value +NestedModules.Universe.Nested2.+nested2Function: 0 references () [0]
+  Live Value +NestedModules.Universe.Nested2.Nested3.+nested3Function: 0 references () [0]
+  Live Value +NestedModules.Universe.Nested2.Nested3.+nested3Value: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.Nested3.+w: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.Nested3.+z: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.Nested3.+y: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.Nested3.+x: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.+y: 0 references () [0]
+  Live Value +NestedModules.Universe.Nested2.+nested2Value: 0 references () [0]
+  Dead Value +NestedModules.Universe.Nested2.+x: 0 references () [0]
+  Dead Value +NestedModules.Universe.+notExported: 0 references () [0]
+  Live Value +NestedModules.Universe.+theAnswer: 0 references () [0]
+  Live Value +NestedModules.+notNested: 0 references () [0]
+  Live Value NestedModulesInSignature.Universe.+theAnswer: 0 references () [0]
+  Dead RecordLabel +Newsyntax.record2.yy: 0 references () [0]
+  Dead RecordLabel +Newsyntax.record2.xx: 0 references () [0]
+  Dead VariantCase +Newsyntax.variant.C: 0 references () [0]
+  Dead VariantCase +Newsyntax.variant.B: 0 references () [0]
+  Dead VariantCase +Newsyntax.variant.A: 0 references () [0]
+  Dead RecordLabel +Newsyntax.record.yyy: 0 references () [0]
+  Dead RecordLabel +Newsyntax.record.xxx: 0 references () [0]
+  Dead Value +Newsyntax.+y: 0 references () [0]
+  Dead Value +Newsyntax.+x: 0 references () [0]
+  Live Value +Newton.+result: 2 references (Newton.res:31:8, Newton.res:31:18) [0]
+  Live Value +Newton.+fPrimed: 1 references (Newton.res:29:4) [0]
+  Live Value +Newton.+f: 2 references (Newton.res:29:4, Newton.res:31:16) [0]
+  Live Value +Newton.+newton: 1 references (Newton.res:29:4) [2]
+  Live Value +Newton.+loop: 1 references (Newton.res:6:4) [1]
+  Live Value +Newton.+next: 1 references (Newton.res:14:10) [0]
+  Live Value +Newton.+previous: 2 references (Newton.res:14:10, Newton.res:16:8) [0]
+  Live Value +Newton.+iterateMore: 1 references (Newton.res:14:10) [1]
+  Live Value +Newton.+delta: 1 references (Newton.res:8:6) [0]
+  Live Value +Newton.+current: 3 references (Newton.res:8:6, Newton.res:14:10, Newton.res:15:8) [0]
+  Live Value +Newton.+/: 1 references (Newton.res:16:8) [0]
+  Live Value +Newton.+*: 2 references (Newton.res:25:4, Newton.res:27:4) [0]
+  Live Value +Newton.++: 1 references (Newton.res:25:4) [0]
+  Live Value +Newton.+-: 4 references (Newton.res:9:8, Newton.res:16:8, Newton.res:25:4, Newton.res:27:4) [0]
+  Live Value +Opaque.+testConvertNestedRecordFromOtherFile: 0 references () [0]
+  Live Value +Opaque.+noConversion: 0 references () [0]
+  Dead VariantCase +Opaque.opaqueFromRecords.A: 0 references () [0]
+  Live Value +Records.+testMyRecBsAs2: 0 references () [0]
+  Live Value +Records.+testMyRecBsAs: 0 references () [0]
+  Live RecordLabel +Records.myRecBsAs.type_: 1 references (Records.res:145:38) [0]
+  Live Value +Records.+testMyObj2: 0 references () [0]
+  Live Value +Records.+testMyObj: 0 references () [0]
+  Live Value +Records.+testMyRec2: 0 references () [0]
+  Live Value +Records.+testMyRec: 0 references () [0]
+  Live RecordLabel +Records.myRec.type_: 1 references (Records.res:127:30) [0]
+  Live Value +Records.+computeArea4: 0 references () [0]
+  Live Value +Records.+computeArea3: 0 references () [0]
+  Live Value +Records.+someBusiness2: 0 references () [0]
+  Live Value +Records.+findAddress2: 0 references () [0]
+  Live RecordLabel +Records.business2.address2: 1 references (Records.res:97:2) [0]
+  Dead RecordLabel +Records.business2.owner: 0 references () [0]
+  Dead RecordLabel +Records.business2.name: 0 references () [0]
+  Live Value +Records.+getPayloadRecordPlusOne: 0 references () [0]
+  Live Value +Records.+payloadValue: 0 references () [0]
+  Live Value +Records.+recordValue: 1 references (Records.res:80:4) [0]
+  Live Value +Records.+getPayloadRecord: 0 references () [0]
+  Dead RecordLabel +Records.record.w: 0 references () [0]
+  Live RecordLabel +Records.record.v: 1 references (Records.res:85:5) [0]
+  Live Value +Records.+getPayload: 0 references () [0]
+  Live RecordLabel +Records.payload.payload: 3 references (Records.res:65:18, Records.res:74:24, Records.res:83:31) [0]
+  Dead RecordLabel +Records.payload.num: 0 references () [0]
+  Live Value +Records.+findAllAddresses: 0 references () [0]
+  Live Value +Records.+someBusiness: 0 references () [0]
+  Live Value +Records.+findAddress: 0 references () [0]
+  Live Value +Records.+getOpt: 3 references (Records.res:39:4, Records.res:46:4, Records.res:96:4) [0]
+  Live RecordLabel +Records.business.address: 2 references (Records.res:40:2, Records.res:50:6) [0]
+  Live RecordLabel +Records.business.owner: 1 references (Records.res:51:6) [0]
+  Dead RecordLabel +Records.business.name: 0 references () [0]
+  Live RecordLabel +Records.person.address: 1 references (Records.res:51:42) [0]
+  Dead RecordLabel +Records.person.age: 0 references () [0]
+  Dead RecordLabel +Records.person.name: 0 references () [0]
+  Live Value +Records.+coord2d: 0 references () [0]
+  Live Value +Records.+computeArea: 0 references () [0]
+  Live Value +Records.+origin: 0 references () [0]
+  Live RecordLabel +Records.coord.z: 1 references (Records.res:14:19) [0]
+  Live RecordLabel +Records.coord.y: 1 references (Records.res:14:19) [0]
+  Live RecordLabel +Records.coord.x: 1 references (Records.res:14:19) [0]
+  Live Value +References.+preserveRefIdentity: 0 references () [0]
+  Live Value +References.+destroysRefIdentity: 0 references () [0]
+  Dead RecordLabel +References.requiresConversion.x: 0 references () [0]
+  Live Value +References.+set: 0 references () [0]
+  Live Value +References.+make: 0 references () [0]
+  Live Value +References.+get: 0 references () [0]
+  Live Value +References.R.+set: 1 references (References.res:37:4) [1]
+  Live Value +References.R.+set: 1 references (References.res:19:2) [0]
+  Live Value +References.R.+make: 1 references (References.res:34:4) [1]
+  Live Value +References.R.+make: 1 references (References.res:18:2) [0]
+  Live Value +References.R.+get: 1 references (References.res:31:4) [1]
+  Live Value +References.R.+get: 1 references (References.res:17:2) [0]
+  Live Value +References.+update: 0 references () [0]
+  Live Value +References.+access: 0 references () [0]
+  Live Value +References.+create: 0 references () [0]
+  Live Value +RepeatedLabel.+userData: 1 references (RepeatedLabel.res:14:7) [0]
+  Dead RecordLabel +RepeatedLabel.tabState.f: 0 references () [0]
+  Live RecordLabel +RepeatedLabel.tabState.b: 1 references (RepeatedLabel.res:12:16) [0]
+  Live RecordLabel +RepeatedLabel.tabState.a: 1 references (RepeatedLabel.res:12:16) [0]
+  Dead RecordLabel +RepeatedLabel.userData.b: 0 references () [0]
+  Dead RecordLabel +RepeatedLabel.userData.a: 0 references () [0]
+  Dead Value +Shadow.M.+test: 0 references () [0]
+  Live Value +Shadow.M.+test: 0 references () [0]
+  Live Value +Shadow.+test: 0 references () [0]
+  Live Value +Shadow.+test: 0 references () [0]
+  Live Value +TestEmitInnerModules.Outer.Medium.Inner.+y: 0 references () [0]
+  Live Value +TestEmitInnerModules.Inner.+y: 0 references () [0]
+  Live Value +TestEmitInnerModules.Inner.+x: 0 references () [0]
+  Live Value +TestFirstClassModules.+convertFirstClassModuleWithTypeEquations: 0 references () [0]
+  Live Value +TestFirstClassModules.+convertRecord: 0 references () [0]
+  Live Value +TestFirstClassModules.+convertInterface: 0 references () [0]
+  Live Value +TestFirstClassModules.+convert: 0 references () [0]
+  Dead Value +TestImmutableArray.+testBeltArraySet: 0 references () [0]
+  Dead Value +TestImmutableArray.+testBeltArrayGet: 0 references () [0]
+  Live Value +TestImmutableArray.+testImmutableArrayGet: 0 references () [0]
+  Live Value +TestImport.+defaultValue2: 0 references () [0]
+  Dead Value +TestImport.+make: 0 references () [0]
+  Live Value +TestImport.+make: 0 references () [0]
+  Dead RecordLabel +TestImport.message.text: 0 references () [0]
+  Live Value +TestImport.+defaultValue: 0 references () [0]
+  Live Value +TestImport.+valueStartingWithUpperCaseLetter: 0 references () [0]
+  Dead Value +TestImport.+innerStuffContents: 0 references () [0]
+  Live Value +TestImport.+innerStuffContentsAsEmptyObject: 0 references () [0]
+  Live Value +TestImport.+innerStuffContents: 0 references () [0]
+  Dead Value +TestInnedModuleTypes.+_: 0 references () [0]
+  Live Value +TestModuleAliases.+testInner2Expanded: 0 references () [0]
+  Live Value +TestModuleAliases.+testInner2: 0 references () [0]
+  Live Value +TestModuleAliases.+testInner1Expanded: 0 references () [0]
+  Live Value +TestModuleAliases.+testInner1: 0 references () [0]
+  Live Value +TestOptArg.+liveSuppressesOptArgs: 1 references (TestOptArg.res:16:8) [0]
+  Live Value +TestOptArg.+notSuppressesOptArgs: 1 references (TestOptArg.res:11:8) [0]
+  Live Value +TestOptArg.+bar: 1 references (TestOptArg.res:7:7) [0]
+  Live Value +TestOptArg.+foo: 1 references (TestOptArg.res:5:4) [0]
+  Live Value +TestPromise.+convert: 0 references () [0]
+  Dead RecordLabel +TestPromise.toPayload.result: 0 references () [0]
+  Live RecordLabel +TestPromise.fromPayload.s: 1 references (TestPromise.res:14:32) [0]
+  Dead RecordLabel +TestPromise.fromPayload.x: 0 references () [0]
+  Dead Value +ToSuppress.+toSuppress: 0 references () [0]
+  Live Value +TransitiveType1.+convertAlias: 0 references () [0]
+  Live Value +TransitiveType1.+convert: 0 references () [0]
+  Dead Value +TransitiveType2.+convertT2: 0 references () [0]
+  Live Value +TransitiveType3.+convertT3: 0 references () [0]
+  Dead RecordLabel +TransitiveType3.t3.s: 0 references () [0]
+  Dead RecordLabel +TransitiveType3.t3.i: 0 references () [0]
+  Live Value +Tuples.+changeSecondAge: 0 references () [0]
+  Live Value +Tuples.+marry: 0 references () [0]
+  Live Value +Tuples.+getFirstName: 0 references () [0]
+  Live RecordLabel +Tuples.person.age: 1 references (Tuples.res:49:84) [0]
+  Live RecordLabel +Tuples.person.name: 1 references (Tuples.res:43:49) [0]
+  Live Value +Tuples.+coord2d: 0 references () [0]
+  Live Value +Tuples.+computeAreaNoConverters: 0 references () [0]
+  Live Value +Tuples.+computeAreaWithIdent: 0 references () [0]
+  Live Value +Tuples.+computeArea: 0 references () [0]
+  Live Value +Tuples.+origin: 0 references () [0]
+  Live Value +Tuples.+testTuple: 0 references () [0]
+  Dead Value +TypeParams1.+exportSomething: 0 references () [0]
+  Dead Value +TypeParams2.+exportSomething: 0 references () [0]
+  Dead RecordLabel +TypeParams2.item.id: 0 references () [0]
+  Live Value +TypeParams3.+test2: 0 references () [0]
+  Live Value +TypeParams3.+test: 0 references () [0]
+  Dead Value +Types.ObjectId.+x: 0 references () [0]
+  Live Value +Types.+optFunction: 0 references () [0]
+  Live Value +Types.+i64Const: 0 references () [0]
+  Live Value +Types.+currentTime: 0 references () [0]
+  Live Value +Types.+testInstantiateTypeParameter: 0 references () [0]
+  Dead RecordLabel +Types.someRecord.id: 0 references () [0]
+  Live Value +Types.+setMatch: 0 references () [0]
+  Live Value +Types.+testMarshalFields: 0 references () [0]
+  Live Value +Types.+testConvertNull: 0 references () [0]
+  Dead RecordLabel +Types.record.s: 0 references () [0]
+  Dead RecordLabel +Types.record.i: 0 references () [0]
+  Live Value +Types.+jsonStringify: 0 references () [0]
+  Live Value +Types.+jsString2T: 0 references () [0]
+  Live Value +Types.+jsStringT: 0 references () [0]
+  Live Value +Types.+stringT: 0 references () [0]
+  Dead VariantCase +Types.opaqueVariant.B: 0 references () [0]
+  Dead VariantCase +Types.opaqueVariant.A: 0 references () [0]
+  Live Value +Types.+testFunctionOnOptionsAsArgument: 0 references () [0]
+  Live Value +Types.+mutuallyRecursiveConverter: 0 references () [0]
+  Live Value +Types.+selfRecursiveConverter: 0 references () [0]
+  Dead RecordLabel +Types.mutuallyRecursiveB.a: 0 references () [0]
+  Live RecordLabel +Types.mutuallyRecursiveA.b: 1 references (Types.res:49:34) [0]
+  Live RecordLabel +Types.selfRecursive.self: 1 references (Types.res:42:30) [0]
+  Live Value +Types.+swap: 0 references () [0]
+  Dead VariantCase +Types.typeWithVars.B: 0 references () [0]
+  Dead VariantCase +Types.typeWithVars.A: 0 references () [0]
+  Live Value +Types.+map: 0 references () [0]
+  Live Value +Types.+someIntList: 0 references () [0]
+  Live Value +Unboxed.+r2Test: 0 references () [0]
+  Dead RecordLabel +Unboxed.r2.B.g: 0 references () [0]
+  Dead VariantCase +Unboxed.r2.B: 0 references () [0]
+  Dead RecordLabel +Unboxed.r1.x: 0 references () [0]
+  Live Value +Unboxed.+testV1: 0 references () [0]
+  Dead VariantCase +Unboxed.v2.A: 0 references () [0]
+  Dead VariantCase +Unboxed.v1.A: 0 references () [0]
+  Live Value +Uncurried.+sumLblCurried: 0 references () [0]
+  Live Value +Uncurried.+sumCurried: 0 references () [0]
+  Live Value +Uncurried.+sumU2: 0 references () [0]
+  Live Value +Uncurried.+sumU: 0 references () [0]
+  Live Value +Uncurried.+callback2U: 0 references () [0]
+  Live Value +Uncurried.+callback2: 0 references () [0]
+  Live RecordLabel +Uncurried.authU.loginU: 1 references (Uncurried.res:38:25) [0]
+  Live RecordLabel +Uncurried.auth.login: 1 references (Uncurried.res:35:24) [0]
+  Live Value +Uncurried.+callback: 0 references () [0]
+  Live Value +Uncurried.+curried3: 0 references () [0]
+  Live Value +Uncurried.+uncurried3: 0 references () [0]
+  Live Value +Uncurried.+uncurried2: 0 references () [0]
+  Live Value +Uncurried.+uncurried1: 0 references () [0]
+  Live Value +Uncurried.+uncurried0: 0 references () [0]
+  Live Value +Unison.+toString: 3 references (Unison.res:37:0, Unison.res:38:0, Unison.res:39:0) [0]
+  Live Value +Unison.+fits: 1 references (Unison.res:26:8) [0]
+  Live Value +Unison.+group: 2 references (Unison.res:38:25, Unison.res:39:25) [0]
+  Live VariantCase +Unison.stack.Cons: 2 references (Unison.res:38:20, Unison.res:39:20) [0]
+  Live VariantCase +Unison.stack.Empty: 3 references (Unison.res:37:20, Unison.res:38:53, Unison.res:39:52) [0]
+  Live RecordLabel +Unison.t.doc: 2 references (Unison.res:23:9, Unison.res:28:9) [0]
+  Live RecordLabel +Unison.t.break: 1 references (Unison.res:28:9) [0]
+  Live VariantCase +Unison.break.Always: 1 references (Unison.res:39:38) [0]
+  Live VariantCase +Unison.break.Never: 1 references (Unison.res:38:38) [0]
+  Live VariantCase +Unison.break.IfNeed: 1 references (Unison.res:17:20) [0]
+  Live Value +UseImportJsValue.+useTypeImportedInOtherModule: 0 references () [0]
+  Live Value +UseImportJsValue.+useGetProp: 0 references () [0]
+  Live Value +Variants.+restResult3: 0 references () [0]
+  Live Value +Variants.+restResult2: 0 references () [0]
+  Live Value +Variants.+restResult1: 0 references () [0]
+  Dead VariantCase +Variants.result1.Error: 0 references () [0]
+  Dead VariantCase +Variants.result1.Ok: 0 references () [0]
+  Live Value +Variants.+polyWithOpt: 0 references () [0]
+  Dead VariantCase +Variants.type_.Type: 0 references () [0]
+  Live Value +Variants.+id2: 0 references () [0]
+  Live Value +Variants.+id1: 0 references () [0]
+  Live Value +Variants.+testConvert2to3: 0 references () [0]
+  Live Value +Variants.+testConvert3: 0 references () [0]
+  Live Value +Variants.+testConvert2: 0 references () [0]
+  Live Value +Variants.+fortytwoBAD: 0 references () [0]
+  Live Value +Variants.+fortytwoOK: 0 references () [0]
+  Live Value +Variants.+testConvert: 0 references () [0]
+  Live Value +Variants.+swap: 0 references () [0]
+  Live Value +Variants.+onlySunday: 0 references () [0]
+  Live Value +Variants.+sunday: 0 references () [0]
+  Live Value +Variants.+saturday: 0 references () [0]
+  Live Value +Variants.+monday: 0 references () [0]
+  Live Value +Variants.+isWeekend: 0 references () [0]
+  Live Value +VariantsWithPayload.+testVariant1Object: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variant1Object.R: 0 references () [0]
+  Live Value +VariantsWithPayload.+testVariant1Int: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variant1Int.R: 0 references () [0]
+  Live Value +VariantsWithPayload.+printVariantWithPayloads: 0 references () [0]
+  Live Value +VariantsWithPayload.+testVariantWithPayloads: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variantWithPayloads.E: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variantWithPayloads.D: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variantWithPayloads.C: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variantWithPayloads.B: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.variantWithPayloads.A: 0 references () [0]
+  Live Value +VariantsWithPayload.+testSimpleVariant: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.simpleVariant.C: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.simpleVariant.B: 0 references () [0]
+  Dead VariantCase +VariantsWithPayload.simpleVariant.A: 0 references () [0]
+  Live Value +VariantsWithPayload.+printManyPayloads: 0 references () [0]
+  Live Value +VariantsWithPayload.+testManyPayloads: 0 references () [0]
+  Live Value +VariantsWithPayload.+printVariantWithPayload: 0 references () [0]
+  Live Value +VariantsWithPayload.+testWithPayload: 0 references () [0]
+  Live RecordLabel +VariantsWithPayload.payload.y: 2 references (VariantsWithPayload.res:26:74, VariantsWithPayload.res:44:72) [0]
+  Live RecordLabel +VariantsWithPayload.payload.x: 2 references (VariantsWithPayload.res:26:57, VariantsWithPayload.res:44:55) [0]
+  Live Value +DeadExn.+eInside: 1 references (DeadExn.res:12:7) [0]
+  Dead Value +DeadExn.+eToplevel: 0 references () [0]
+  Dead Exception +DeadExn.DeadE: 0 references () [0]
+  Live Exception +DeadExn.Inside.Einside: 1 references (DeadExn.res:10:14) [0]
+  Live Exception +DeadExn.Etoplevel: 1 references (DeadExn.res:8:16) [0]
+  Live RecordLabel +DeadTypeTest.record.z: 0 references () [0]
+  Live RecordLabel +DeadTypeTest.record.y: 0 references () [0]
+  Live RecordLabel +DeadTypeTest.record.x: 0 references () [0]
+  Dead Value +DeadTypeTest.+_: 0 references () [0]
+  Dead Value +DeadTypeTest.+_: 0 references () [0]
+  Dead VariantCase +DeadTypeTest.deadType.InNeither: 0 references () [0]
+  Live VariantCase +DeadTypeTest.deadType.OnlyInInterface: 1 references (DeadTypeTest.resi:8:2) [0]
+  Dead Value +DeadTypeTest.+a: 0 references () [0]
+  Dead VariantCase +DeadTypeTest.t.B: 0 references () [0]
+  Dead Value DeadValueTest.+valueDead: 0 references () [0]
+  Live Value DeadValueTest.+valueAlive: 1 references (DeadTest.res:73:16) [0]
+  Live Value +DynamicallyLoadedComponent.+make: 1 references (DeadTest.res:133:17) [0]
+  Dead Value ErrorHandler.+x: 0 references () [0]
+  Live Value ErrorHandler.Make.+notify: 1 references (CreateErrorHandler1.res:8:0) [0]
+  Dead Value +FirstClassModulesInterface.+r: 0 references () [0]
+  Dead RecordLabel +FirstClassModulesInterface.record.y: 0 references () [0]
+  Dead RecordLabel +FirstClassModulesInterface.record.x: 0 references () [0]
+  Dead Value ImmutableArray.+eq: 0 references () [0]
+  Dead Value ImmutableArray.+eqU: 0 references () [0]
+  Dead Value ImmutableArray.+cmp: 0 references () [0]
+  Dead Value ImmutableArray.+cmpU: 0 references () [0]
+  Dead Value ImmutableArray.+some2: 0 references () [0]
+  Dead Value ImmutableArray.+some2U: 0 references () [0]
+  Dead Value ImmutableArray.+every2: 0 references () [0]
+  Dead Value ImmutableArray.+every2U: 0 references () [0]
+  Dead Value ImmutableArray.+every: 0 references () [0]
+  Dead Value ImmutableArray.+everyU: 0 references () [0]
+  Dead Value ImmutableArray.+some: 0 references () [0]
+  Dead Value ImmutableArray.+someU: 0 references () [0]
+  Dead Value ImmutableArray.+reduceReverse2: 0 references () [0]
+  Dead Value ImmutableArray.+reduceReverse2U: 0 references () [0]
+  Dead Value ImmutableArray.+reduceReverse: 0 references () [0]
+  Dead Value ImmutableArray.+reduceReverseU: 0 references () [0]
+  Dead Value ImmutableArray.+reduce: 0 references () [0]
+  Dead Value ImmutableArray.+reduceU: 0 references () [0]
+  Dead Value ImmutableArray.+partition: 0 references () [0]
+  Dead Value ImmutableArray.+partitionU: 0 references () [0]
+  Dead Value ImmutableArray.+mapWithIndex: 0 references () [0]
+  Dead Value ImmutableArray.+mapWithIndexU: 0 references () [0]
+  Dead Value ImmutableArray.+forEachWithIndex: 0 references () [0]
+  Dead Value ImmutableArray.+forEachWithIndexU: 0 references () [0]
+  Dead Value ImmutableArray.+keepMap: 0 references () [0]
+  Dead Value ImmutableArray.+keepMapU: 0 references () [0]
+  Dead Value ImmutableArray.+keepWithIndex: 0 references () [0]
+  Dead Value ImmutableArray.+keepWithIndexU: 0 references () [0]
+  Dead Value ImmutableArray.+map: 0 references () [0]
+  Dead Value ImmutableArray.+mapU: 0 references () [0]
+  Dead Value ImmutableArray.+forEach: 0 references () [0]
+  Dead Value ImmutableArray.+forEachU: 0 references () [0]
+  Dead Value ImmutableArray.+copy: 0 references () [0]
+  Dead Value ImmutableArray.+sliceToEnd: 0 references () [0]
+  Dead Value ImmutableArray.+slice: 0 references () [0]
+  Dead Value ImmutableArray.+concatMany: 0 references () [0]
+  Dead Value ImmutableArray.+concat: 0 references () [0]
+  Dead Value ImmutableArray.+unzip: 0 references () [0]
+  Dead Value ImmutableArray.+zipBy: 0 references () [0]
+  Dead Value ImmutableArray.+zipByU: 0 references () [0]
+  Dead Value ImmutableArray.+zip: 0 references () [0]
+  Dead Value ImmutableArray.+makeByAndShuffle: 0 references () [0]
+  Dead Value ImmutableArray.+makeByAndShuffleU: 0 references () [0]
+  Dead Value ImmutableArray.+makeBy: 0 references () [0]
+  Dead Value ImmutableArray.+makeByU: 0 references () [0]
+  Dead Value ImmutableArray.+rangeBy: 0 references () [0]
+  Dead Value ImmutableArray.+range: 0 references () [0]
+  Dead Value ImmutableArray.+make: 0 references () [0]
+  Dead Value ImmutableArray.+makeUninitializedUnsafe: 0 references () [0]
+  Dead Value ImmutableArray.+makeUninitialized: 0 references () [0]
+  Dead Value ImmutableArray.+reverse: 0 references () [0]
+  Dead Value ImmutableArray.+shuffle: 0 references () [0]
+  Dead Value ImmutableArray.+getUndefined: 0 references () [0]
+  Dead Value ImmutableArray.+getUnsafe: 0 references () [0]
+  Dead Value ImmutableArray.+getExn: 0 references () [0]
+  Dead Value ImmutableArray.+get: 0 references () [0]
+  Dead Value ImmutableArray.+size: 0 references () [0]
+  Dead Value ImmutableArray.+length: 0 references () [0]
+  Dead Value ImmutableArray.+toArray: 0 references () [0]
+  Live Value ImmutableArray.+fromArray: 1 references (DeadTest.res:1:15) [0]
+  Live Value ImmutableArray.Array.+get: 1 references (TestImmutableArray.res:2:4) [0]
+  Live Value +ImportHookDefault.+make2: 0 references () [0]
+  Live Value +ImportHookDefault.+make: 1 references (Hooks.res:17:5) [0]
+  Dead RecordLabel +ImportHookDefault.person.age: 0 references () [0]
+  Dead RecordLabel +ImportHookDefault.person.name: 0 references () [0]
+  Live Value +ImportHooks.+foo: 0 references () [0]
+  Live Value +ImportHooks.+make: 1 references (Hooks.res:14:5) [0]
+  Dead RecordLabel +ImportHooks.person.age: 0 references () [0]
+  Dead RecordLabel +ImportHooks.person.name: 0 references () [0]
+  Live Value +ImportJsValue.+default: 0 references () [0]
+  Live Value +ImportJsValue.+polymorphic: 0 references () [0]
+  Live Value +ImportJsValue.+convertVariant: 0 references () [0]
+  Dead VariantCase +ImportJsValue.variant.S: 0 references () [0]
+  Dead VariantCase +ImportJsValue.variant.I: 0 references () [0]
+  Live Value +ImportJsValue.+returnedFromHigherOrder: 0 references () [0]
+  Live Value +ImportJsValue.+higherOrder: 1 references (ImportJsValue.res:64:4) [0]
+  Live Value +ImportJsValue.+useColor: 0 references () [0]
+  Live Value +ImportJsValue.+useGetAbs: 0 references () [0]
+  Live Value +ImportJsValue.+useGetProp: 0 references () [0]
+  Live Value +ImportJsValue.AbsoluteValue.+getAbs: 1 references (ImportJsValue.res:50:4) [1]
+  Live Value +ImportJsValue.AbsoluteValue.+getAbs: 1 references (ImportJsValue.res:40:6) [0]
+  Live Value +ImportJsValue.+areaValue: 0 references () [0]
+  Live Value +ImportJsValue.+roundedNumber: 0 references () [0]
+  Live Value +ImportJsValue.+returnMixedArray: 0 references () [0]
+  Live Value +ImportJsValue.+area: 1 references (ImportJsValue.res:30:4) [0]
+  Dead RecordLabel +ImportJsValue.point.y: 0 references () [0]
+  Dead RecordLabel +ImportJsValue.point.x: 0 references () [0]
+  Live Value +ImportJsValue.+round: 1 references (ImportJsValue.res:27:4) [0]
+  Live Value +NestedModulesInSignature.Universe.+theAnswer: 1 references (NestedModulesInSignature.resi:2:2) [0]
+  Live Value OptArg.+bar: 1 references (TestOptArg.res:1:7) [0]
+  Dead Value OptArg.+foo: 0 references () [0]
+  Dead Value +DeadValueTest.+tail: 0 references () [0]
+  Dead Value +DeadValueTest.+subList: 0 references () [0]
+  Dead Value +DeadValueTest.+valueOnlyInImplementation: 0 references () [0]
+  Dead Value +DeadValueTest.+valueDead: 0 references () [0]
+  Live Value +DeadValueTest.+valueAlive: 1 references (DeadValueTest.resi:1:0) [0]
+  Dead Value +ErrorHandler.+x: 0 references () [0]
+  Live Value +ErrorHandler.Make.+notify: 1 references (ErrorHandler.resi:7:2) [0]
+  Dead Value +ImmutableArray.+eq: 0 references () [0]
+  Dead Value +ImmutableArray.+eqU: 0 references () [0]
+  Dead Value +ImmutableArray.+cmp: 0 references () [0]
+  Dead Value +ImmutableArray.+cmpU: 0 references () [0]
+  Dead Value +ImmutableArray.+some2: 0 references () [0]
+  Dead Value +ImmutableArray.+some2U: 0 references () [0]
+  Dead Value +ImmutableArray.+every2: 0 references () [0]
+  Dead Value +ImmutableArray.+every2U: 0 references () [0]
+  Dead Value +ImmutableArray.+every: 0 references () [0]
+  Dead Value +ImmutableArray.+everyU: 0 references () [0]
+  Dead Value +ImmutableArray.+some: 0 references () [0]
+  Dead Value +ImmutableArray.+someU: 0 references () [0]
+  Dead Value +ImmutableArray.+reduceReverse2: 0 references () [0]
+  Dead Value +ImmutableArray.+reduceReverse2U: 0 references () [0]
+  Dead Value +ImmutableArray.+reduceReverse: 0 references () [0]
+  Dead Value +ImmutableArray.+reduceReverseU: 0 references () [0]
+  Dead Value +ImmutableArray.+reduce: 0 references () [0]
+  Dead Value +ImmutableArray.+reduceU: 0 references () [0]
+  Dead Value +ImmutableArray.+partition: 0 references () [0]
+  Dead Value +ImmutableArray.+partitionU: 0 references () [0]
+  Dead Value +ImmutableArray.+mapWithIndex: 0 references () [0]
+  Dead Value +ImmutableArray.+mapWithIndexU: 0 references () [0]
+  Dead Value +ImmutableArray.+forEachWithIndex: 0 references () [0]
+  Dead Value +ImmutableArray.+forEachWithIndexU: 0 references () [0]
+  Dead Value +ImmutableArray.+keepMap: 0 references () [0]
+  Dead Value +ImmutableArray.+keepMapU: 0 references () [0]
+  Dead Value +ImmutableArray.+keepWithIndex: 0 references () [0]
+  Dead Value +ImmutableArray.+keepWithIndexU: 0 references () [0]
+  Dead Value +ImmutableArray.+map: 0 references () [0]
+  Dead Value +ImmutableArray.+mapU: 0 references () [0]
+  Dead Value +ImmutableArray.+forEach: 0 references () [0]
+  Dead Value +ImmutableArray.+forEachU: 0 references () [0]
+  Dead Value +ImmutableArray.+copy: 0 references () [0]
+  Dead Value +ImmutableArray.+sliceToEnd: 0 references () [0]
+  Dead Value +ImmutableArray.+slice: 0 references () [0]
+  Dead Value +ImmutableArray.+concatMany: 0 references () [0]
+  Dead Value +ImmutableArray.+concat: 0 references () [0]
+  Dead Value +ImmutableArray.+unzip: 0 references () [0]
+  Dead Value +ImmutableArray.+zipBy: 0 references () [0]
+  Dead Value +ImmutableArray.+zipByU: 0 references () [0]
+  Dead Value +ImmutableArray.+zip: 0 references () [0]
+  Dead Value +ImmutableArray.+makeByAndShuffle: 0 references () [0]
+  Dead Value +ImmutableArray.+makeByAndShuffleU: 0 references () [0]
+  Dead Value +ImmutableArray.+makeBy: 0 references () [0]
+  Dead Value +ImmutableArray.+makeByU: 0 references () [0]
+  Dead Value +ImmutableArray.+rangeBy: 0 references () [0]
+  Dead Value +ImmutableArray.+range: 0 references () [0]
+  Dead Value +ImmutableArray.+make: 0 references () [0]
+  Dead Value +ImmutableArray.+makeUninitializedUnsafe: 0 references () [0]
+  Dead Value +ImmutableArray.+makeUninitialized: 0 references () [0]
+  Dead Value +ImmutableArray.+reverse: 0 references () [0]
+  Dead Value +ImmutableArray.+shuffle: 0 references () [0]
+  Dead Value +ImmutableArray.+getUndefined: 0 references () [0]
+  Dead Value +ImmutableArray.+getUnsafe: 0 references () [0]
+  Dead Value +ImmutableArray.+getExn: 0 references () [0]
+  Live Value +ImmutableArray.+get: 1 references (ImmutableArray.resi:6:2) [0]
+  Dead Value +ImmutableArray.+size: 0 references () [0]
+  Dead Value +ImmutableArray.+length: 0 references () [0]
+  Dead Value +ImmutableArray.+toArray: 0 references () [0]
+  Live Value +ImmutableArray.+fromArray: 1 references (ImmutableArray.resi:9:0) [0]
+  Live Value +OptArg.+wrapfourArgs: 2 references (OptArg.res:28:7, OptArg.res:29:7) [0]
+  Live Value +OptArg.+fourArgs: 1 references (OptArg.res:26:4) [0]
+  Live Value +OptArg.+wrapOneArg: 1 references (OptArg.res:22:7) [0]
+  Live Value +OptArg.+oneArg: 1 references (OptArg.res:20:4) [0]
+  Live Value +OptArg.+twoArgs: 1 references (OptArg.res:16:12) [0]
+  Live Value +OptArg.+threeArgs: 2 references (OptArg.res:11:7, OptArg.res:12:7) [0]
+  Live Value +OptArg.+bar: 2 references (OptArg.res:7:7, OptArg.resi:2:0) [0]
+  Live Value +OptArg.+foo: 1 references (OptArg.res:5:7) [0]
+
+  Incorrect Dead Annotation
+  DeadTest.res:178:1-22
+  deadIncorrect  is annotated @dead but is live
+
+  Warning Unused Argument
+  TestOptArg.res:9:1-65
+  optional argument x of function notSuppressesOptArgs is never used
+
+  Warning Unused Argument
+  TestOptArg.res:9:1-65
+  optional argument y of function notSuppressesOptArgs is never used
+
+  Warning Unused Argument
+  TestOptArg.res:9:1-65
+  optional argument z of function notSuppressesOptArgs is never used
+
+  Warning Redundant Optional Argument
+  TestOptArg.res:3:1-28
+  optional argument x of function foo is always supplied (1 calls)
+
+  Warning Redundant Optional Argument
+  Unison.res:17:1-60
+  optional argument break of function group is always supplied (2 calls)
+
+  Warning Unused Argument
+  OptArg.resi:2:1-50
+  optional argument x of function bar is never used
+
+  Warning Redundant Optional Argument
+  OptArg.res:26:1-70
+  optional argument c of function wrapfourArgs is always supplied (2 calls)
+
+  Warning Unused Argument
+  OptArg.res:24:1-63
+  optional argument d of function fourArgs is never used
+
+  Warning Redundant Optional Argument
+  OptArg.res:20:1-51
+  optional argument a of function wrapOneArg is always supplied (1 calls)
+
+  Warning Unused Argument
+  OptArg.res:14:1-42
+  optional argument a of function twoArgs is never used
+
+  Warning Unused Argument
+  OptArg.res:14:1-42
+  optional argument b of function twoArgs is never used
+
+  Warning Unused Argument
+  OptArg.res:9:1-54
+  optional argument b of function threeArgs is never used
+
+  Warning Redundant Optional Argument
+  OptArg.res:9:1-54
+  optional argument a of function threeArgs is always supplied (2 calls)
+
+  Warning Unused Argument
+  OptArg.res:3:1-38
+  optional argument x of function bar is never used
+
+  Warning Unused Argument
+  OptArg.res:1:1-48
+  optional argument y of function foo is never used
+
+  Warning Unused Argument
+  OptArg.res:1:1-48
+  optional argument z of function foo is never used
+
+  Warning Dead Module
+  AutoAnnotate.res:0:1
+  AutoAnnotate is a dead module as all its items are dead.
+
+  Warning Dead Type
+  AutoAnnotate.res:1:16-21
+  variant.R is a variant case which is never constructed
+  <-- line 1
+  type variant = | @dead("variant.R") R(int)
+
+  Warning Dead Type
+  AutoAnnotate.res:4:16-31
+  record.variant is a record label never used to read a value
+  <-- line 4
+  type record = {@dead("record.variant") variant: variant}
+
+  Warning Dead Type
+  AutoAnnotate.res:6:12-18
+  r2.r2 is a record label never used to read a value
+  <-- line 6
+  type r2 = {@dead("r2.r2") r2: int}
+
+  Warning Dead Type
+  AutoAnnotate.res:8:12-18
+  r3.r3 is a record label never used to read a value
+  <-- line 8
+  type r3 = {@dead("r3.r3") r3: int}
+
+  Warning Dead Type
+  AutoAnnotate.res:10:12-18
+  r4.r4 is a record label never used to read a value
+  <-- line 10
+  type r4 = {@dead("r4.r4") r4: int}
+
+  Warning Dead Type
+  AutoAnnotate.res:14:3-14
+  annotatedVariant.R2 is a variant case which is never constructed
+  <-- line 14
+    | @dead("annotatedVariant.R2") R2(r2, r3)
+
+  Warning Dead Type
+  AutoAnnotate.res:15:5-10
+  annotatedVariant.R4 is a variant case which is never constructed
+  <-- line 15
+    | @dead("annotatedVariant.R4") R4(r4)
+
+  Warning Dead Module
+  BucklescriptAnnotations.res:0:1
+  BucklescriptAnnotations is a dead module as all its items are dead.
+
+  Warning Dead Value
+  BucklescriptAnnotations.res:25:1-70
+  bar is never used
+  <-- line 25
+  @dead("bar") let bar = (x: someMethods) => {
+
+  Warning Dead Exception
+  DeadExn.res:7:1-15
+  DeadE is never raised or passed as value
+  <-- line 7
+  @dead("DeadE") exception DeadE
+
+  Warning Dead Value
+  DeadExn.res:8:1-25
+  eToplevel is never used
+  <-- line 8
+  @dead("eToplevel") let eToplevel = Etoplevel
+
+  Warning Dead Value
+  DeadRT.res:5:1-116
+  emitModuleAccessPath is never used
+  <-- line 5
+  @dead("emitModuleAccessPath") let rec emitModuleAccessPath = moduleAccessPath =>
+
+  Warning Dead Value
+  DeadTest.res:2:1-17
+  fortytwo is never used
+  <-- line 2
+  @dead("fortytwo") let fortytwo = 42
+
+  Warning Dead Module
+  DeadTest.res:27:8-97
+  DeadTest.M is a dead module as all its items are dead.
+
+  Warning Dead Value
+  DeadTest.res:31:3-34
+  M.thisSignatureItemIsDead is never used
+  <-- line 31
+    @dead("M.thisSignatureItemIsDead") let thisSignatureItemIsDead = 34
+
+  Warning Dead Value
+  DeadTest.res:61:3-12
+  MM.y is never used
+  <-- line 61
+    @dead("MM.y") let y: int
+
+  Warning Dead Value
+  DeadTest.res:65:3-35
+  MM.valueOnlyInImplementation is never used
+  <-- line 65
+    @dead("MM.valueOnlyInImplementation") let valueOnlyInImplementation = 7
+
+  Warning Dead Value
+  DeadTest.res:75:1-37
+  unusedRec is never used
+  <-- line 75
+  @dead("unusedRec") let rec unusedRec = () => unusedRec()
+
+  Warning Dead Value
+  DeadTest.res:77:1-60
+  split_map is never used
+  <-- line 77
+  @dead("split_map") let rec split_map = l => {
+
+  Warning Dead Value
+  DeadTest.res:82:1-27
+  rec1 is never used
+  <-- line 82
+  @dead("rec1") let rec rec1 = () => rec2()
+
+  Warning Dead Value
+  DeadTest.res:83:1-23
+  rec2 is never used
+  <-- line 83
+  @dead("rec2") and rec2 = () => rec1()
+
+  Warning Dead Value
+  DeadTest.res:85:1-77
+  recWithCallback is never used
+  <-- line 85
+  @dead("recWithCallback") let rec recWithCallback = () => {
+
+  Warning Dead Value
+  DeadTest.res:90:1-53
+  foo is never used
+  <-- line 90
+  @dead("foo") let rec foo = () => {
+
+  Warning Dead Value
+  DeadTest.res:94:1-21
+  bar is never used
+  <-- line 94
+  @dead("bar") and bar = () => foo()
+
+  Warning Dead Value
+  DeadTest.res:96:1-71
+  withDefaultValue is never used
+  <-- line 96
+  @dead("withDefaultValue") let withDefaultValue = (~paramWithDefault=3, y) => paramWithDefault + y
+
+  Warning Dead Module
+  DeadTest.res:110:8-413
+  DeadTest.LazyDynamicallyLoadedComponent2 is a dead module as all its items are dead.
+
+  Warning Dead Value With Side Effects
+  DeadTest.res:111:3-142
+  LazyDynamicallyLoadedComponent2.reasonResource is never used and could have side effects
+
+  Warning Dead Value
+  DeadTest.res:114:3-54
+  LazyDynamicallyLoadedComponent2.makeProps is never used
+  <-- line 114
+    @dead("LazyDynamicallyLoadedComponent2.makeProps") let makeProps = DynamicallyLoadedComponent.makeProps
+
+  Warning Dead Value
+  DeadTest.res:115:3-170
+  LazyDynamicallyLoadedComponent2.make is never used
+  <-- line 115
+    @dead("LazyDynamicallyLoadedComponent2.make") let make = props =>
+
+  Warning Dead Value
+  DeadTest.res:127:1-52
+  zzz is never used
+  <-- line 127
+  @dead("zzz") let zzz = {
+
+  Warning Dead Value
+  DeadTest.res:135:1-15
+  second is never used
+  <-- line 135
+  @dead("second") let second = 1L
+
+  Warning Dead Value
+  DeadTest.res:136:1-35
+  minute is never used
+  <-- line 136
+  @dead("minute") let minute = Int64.mul(60L, second)
+
+  Warning Dead Value
+  DeadTest.res:138:1-21
+  deadRef is never used
+  <-- line 138
+  @dead("deadRef") let deadRef = ref(12)
+
+  Warning Dead Value With Side Effects
+  DeadTest.res:145:1-40
+  theSideEffectIsLogging is never used and could have side effects
+
+  Warning Dead Value
+  DeadTest.res:147:1-54
+  stringLengthNoSideEffects is never used
+  <-- line 147
+  @dead("stringLengthNoSideEffects") let stringLengthNoSideEffects = String.length("sdkdl")
+
+  Warning Dead Type
+  DeadTest.res:175:12-17
+  rc.a is a record label never used to read a value
+  <-- line 175
+  type rc = {@dead("rc.a") a: int}
+
+  Warning Dead Type
+  DeadTest.res:182:25-30
+  inlineRecord.IR.a is a record label never used to read a value
+  <-- line 182
+  type inlineRecord = IR({@dead("inlineRecord.IR.a") a: int, b: int, c: string, @dead d: int, @live e: int})
+
+  Warning Dead Module
+  DeadTestBlacklist.res:0:1
+  DeadTestBlacklist is a dead module as all its items are dead.
+
+  Warning Dead Value
+  DeadTestBlacklist.res:1:1-10
+  x is never used
+  <-- line 1
+  @dead("x") let x = 34
+
+  Warning Dead Module
+  DeadTestWithInterface.res:1:8-54
+  DeadTestWithInterface.Ext_buffer is a dead module as all its items are dead.
+
+  Warning Dead Value
+  DeadTestWithInterface.res:2:3-12
+  Ext_buffer.x is never used
+  <-- line 2
+    @dead("Ext_buffer.x") let x: int
+
+  Warning Dead Value
+  DeadTestWithInterface.res:4:3-12
+  Ext_buffer.x is never used
+  <-- line 4
+    @dead("Ext_buffer.x") let x = 42
+
+  Warning Dead Type
+  DeadTypeTest.res:3:5
+  t.B is a variant case which is never constructed
+  <-- line 3
+    | @dead("t.B") B
+
+  Warning Dead Value
+  DeadTypeTest.res:4:1-9
+  a is never used
+  <-- line 4
+  @dead("a") let a = A
+
+  Warning Dead Type
+  DeadTypeTest.res:10:5-13
+  deadType.InNeither is a variant case which is never constructed
+  <-- line 10
+    | @dead("deadType.InNeither") InNeither
+
+  Warning Dead Type
+  DeadTypeTest.resi:3:5
+  t.B is a variant case which is never constructed
+  <-- line 3
+    | @dead("t.B") B
+
+  Warning Dead Value
+  DeadTypeTest.resi:4:1-8
+  a is never used
+  <-- line 4
+  @dead("a") let a: t
+
+  Warning Dead Type
+  DeadTypeTest.resi:10:5-13
+  deadType.InNeither is a variant case which is never constructed
+  <-- line 10
+    | @dead("deadType.InNeither") InNeither
+
+  Warning Dead Value
+  DeadValueTest.res:2:1-17
+  valueDead is never used
+  <-- line 2
+  @dead("valueDead") let valueDead = 2
+
+  Warning Dead Value
+  DeadValueTest.res:4:1-33
+  valueOnlyInImplementation is never used
+  <-- line 4
+  @dead("valueOnlyInImplementation") let valueOnlyInImplementation = 3
+
+  Warning Dead Value
+  DeadValueTest.res:6:1-260
+  subList is never used
+  <-- line 6
+  @dead("subList") let rec subList = (b, e, l) =>
+
+  Warning Dead Value
+  DeadValueTest.resi:2:1-18
+  valueDead is never used
+  <-- line 2
+  @dead("valueDead") let valueDead: int
+
+  Warning Dead Type
+  Docstrings.res:61:5
+  t.B is a variant case which is never constructed
+  <-- line 61
+    | @dead("t.B") B
+
+  Warning Dead Module
+  ErrorHandler.res:0:1
+  ErrorHandler is a dead module as all its items are dead.
+
+  Warning Dead Value
+  ErrorHandler.res:12:1-10
+  x is never used
+  <-- line 12
+  @dead("x") let x = 42
+
+  Warning Dead Module
+  ErrorHandler.resi:0:1
+  ErrorHandler is a dead module as all its items are dead.
+
+  Warning Dead Value
+  ErrorHandler.resi:10:1-10
+  x is never used
+  <-- line 10
+  @dead("x") let x: int
+
+  Warning Dead Module
+  EverythingLiveHere.res:0:1
+  EverythingLiveHere is a dead module as all its items are dead.
+
+  Warning Dead Value
+  EverythingLiveHere.res:1:1-9
+  x is never used
+  <-- line 1
+  @dead("x") let x = 1
+
+  Warning Dead Value
+  EverythingLiveHere.res:3:1-9
+  y is never used
+  <-- line 3
+  @dead("y") let y = 3
+
+  Warning Dead Value
+  EverythingLiveHere.res:5:1-9
+  z is never used
+  <-- line 5
+  @dead("z") let z = 4
+
+  Warning Dead Module
+  FirstClassModulesInterface.res:0:1
+  FirstClassModulesInterface is a dead module as all its items are dead.
+
+  Warning Dead Type
+  FirstClassModulesInterface.res:2:3-8
+  record.x is a record label never used to read a value
+  <-- line 2
+    @dead("record.x") x: int,
+
+  Warning Dead Type
+  FirstClassModulesInterface.res:3:3-11
+  record.y is a record label never used to read a value
+  <-- line 3
+    @dead("record.y") y: string,
+
+  Warning Dead Value
+  FirstClassModulesInterface.res:6:1-26
+  r is never used
+  <-- line 6
+  @dead("r") let r = {x: 3, y: "hello"}
+
+  Warning Dead Module
+  FirstClassModulesInterface.resi:0:1
+  FirstClassModulesInterface is a dead module as all its items are dead.
+
+  Warning Dead Type
+  FirstClassModulesInterface.resi:3:3-8
+  record.x is a record label never used to read a value
+  <-- line 3
+    @dead("record.x") x: int,
+
+  Warning Dead Type
+  FirstClassModulesInterface.resi:4:3-11
+  record.y is a record label never used to read a value
+  <-- line 4
+    @dead("record.y") y: string,
+
+  Warning Dead Value
+  FirstClassModulesInterface.resi:7:1-13
+  r is never used
+  <-- line 7
+  @dead("r") let r: record
+
+  Warning Dead Value
+  ImmutableArray.res:16:3-41
+  toArray is never used
+  <-- line 16
+    @dead("toArray") let toArray = a => Array.copy(a->fromT)
+
+  Warning Dead Value
+  ImmutableArray.res:20:3-42
+  length is never used
+  <-- line 20
+    @dead("length") let length = a => Array.length(a->fromT)
+
+  Warning Dead Value
+  ImmutableArray.res:22:3-38
+  size is never used
+  <-- line 22
+    @dead("size") let size = a => Array.size(a->fromT)
+
+  Warning Dead Value
+  ImmutableArray.res:26:3-50
+  getExn is never used
+  <-- line 26
+    @dead("getExn") let getExn = (a, x) => Array.getExn(a->fromT, x)
+
+  Warning Dead Value
+  ImmutableArray.res:28:3-56
+  getUnsafe is never used
+  <-- line 28
+    @dead("getUnsafe") let getUnsafe = (a, x) => Array.getUnsafe(a->fromT, x)
+
+  Warning Dead Value
+  ImmutableArray.res:30:3-62
+  getUndefined is never used
+  <-- line 30
+    @dead("getUndefined") let getUndefined = (a, x) => Array.getUndefined(a->fromT, x)
+
+  Warning Dead Value
+  ImmutableArray.res:32:3-49
+  shuffle is never used
+  <-- line 32
+    @dead("shuffle") let shuffle = x => Array.shuffle(x->fromT)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:34:3-49
+  reverse is never used
+  <-- line 34
+    @dead("reverse") let reverse = x => Array.reverse(x->fromT)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:36:3-62
+  makeUninitialized is never used
+  <-- line 36
+    @dead("makeUninitialized") let makeUninitialized = x => Array.makeUninitialized(x)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:38:3-74
+  makeUninitializedUnsafe is never used
+  <-- line 38
+    @dead("makeUninitializedUnsafe") let makeUninitializedUnsafe = x => Array.makeUninitializedUnsafe(x)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:40:3-44
+  make is never used
+  <-- line 40
+    @dead("make") let make = (x, y) => Array.make(x, y)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:42:3-46
+  range is never used
+  <-- line 42
+    @dead("range") let range = (x, y) => Array.range(x, y)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:44:3-64
+  rangeBy is never used
+  <-- line 44
+    @dead("rangeBy") let rangeBy = (x, y, ~step) => Array.rangeBy(x, y, ~step)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:46:3-50
+  makeByU is never used
+  <-- line 46
+    @dead("makeByU") let makeByU = (c, f) => Array.makeByU(c, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:47:3-48
+  makeBy is never used
+  <-- line 47
+    @dead("makeBy") let makeBy = (c, f) => Array.makeBy(c, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:49:3-70
+  makeByAndShuffleU is never used
+  <-- line 49
+    @dead("makeByAndShuffleU") let makeByAndShuffleU = (c, f) => Array.makeByAndShuffleU(c, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:50:3-68
+  makeByAndShuffle is never used
+  <-- line 50
+    @dead("makeByAndShuffle") let makeByAndShuffle = (c, f) => Array.makeByAndShuffle(c, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:52:3-61
+  zip is never used
+  <-- line 52
+    @dead("zip") let zip = (a1, a2) => Array.zip(fromT(a1), fromT(a2))->toTp
+
+  Warning Dead Value
+  ImmutableArray.res:54:3-72
+  zipByU is never used
+  <-- line 54
+    @dead("zipByU") let zipByU = (a1, a2, f) => Array.zipByU(fromT(a1), fromT(a2), f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:55:3-70
+  zipBy is never used
+  <-- line 55
+    @dead("zipBy") let zipBy = (a1, a2, f) => Array.zipBy(fromT(a1), fromT(a2), f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:57:3-47
+  unzip is never used
+  <-- line 57
+    @dead("unzip") let unzip = a => Array.unzip(a->fromTp)->toT2
+
+  Warning Dead Value
+  ImmutableArray.res:59:3-66
+  concat is never used
+  <-- line 59
+    @dead("concat") let concat = (a1, a2) => Array.concat(a1->fromT, a2->fromT)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:61:3-67
+  concatMany is never used
+  <-- line 61
+    @dead("concatMany") let concatMany = (a: t<t<_>>) => Array.concatMany(a->fromTT)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:63:3-77
+  slice is never used
+  <-- line 63
+    @dead("slice") let slice = (a, ~offset, ~len) => Array.slice(a->fromT, ~offset, ~len)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:65:3-63
+  sliceToEnd is never used
+  <-- line 65
+    @dead("sliceToEnd") let sliceToEnd = (a, b) => Array.sliceToEnd(a->fromT, b)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:67:3-43
+  copy is never used
+  <-- line 67
+    @dead("copy") let copy = a => Array.copy(a->fromT)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:69:3-54
+  forEachU is never used
+  <-- line 69
+    @dead("forEachU") let forEachU = (a, f) => Array.forEachU(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:70:3-52
+  forEach is never used
+  <-- line 70
+    @dead("forEach") let forEach = (a, f) => Array.forEach(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:72:3-51
+  mapU is never used
+  <-- line 72
+    @dead("mapU") let mapU = (a, f) => Array.mapU(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:73:3-49
+  map is never used
+  <-- line 73
+    @dead("map") let map = (a, f) => Array.map(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:75:3-71
+  keepWithIndexU is never used
+  <-- line 75
+    @dead("keepWithIndexU") let keepWithIndexU = (a, f) => Array.keepWithIndexU(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:76:3-69
+  keepWithIndex is never used
+  <-- line 76
+    @dead("keepWithIndex") let keepWithIndex = (a, f) => Array.keepWithIndex(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:78:3-59
+  keepMapU is never used
+  <-- line 78
+    @dead("keepMapU") let keepMapU = (a, f) => Array.keepMapU(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:79:3-57
+  keepMap is never used
+  <-- line 79
+    @dead("keepMap") let keepMap = (a, f) => Array.keepMap(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:81:3-72
+  forEachWithIndexU is never used
+  <-- line 81
+    @dead("forEachWithIndexU") let forEachWithIndexU = (a, f) => Array.forEachWithIndexU(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:82:3-70
+  forEachWithIndex is never used
+  <-- line 82
+    @dead("forEachWithIndex") let forEachWithIndex = (a, f) => Array.forEachWithIndex(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:84:3-69
+  mapWithIndexU is never used
+  <-- line 84
+    @dead("mapWithIndexU") let mapWithIndexU = (a, f) => Array.mapWithIndexU(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:85:3-67
+  mapWithIndex is never used
+  <-- line 85
+    @dead("mapWithIndex") let mapWithIndex = (a, f) => Array.mapWithIndex(a->fromT, f)->toT
+
+  Warning Dead Value
+  ImmutableArray.res:87:3-64
+  partitionU is never used
+  <-- line 87
+    @dead("partitionU") let partitionU = (a, f) => Array.partitionU(a->fromT, f)->toT2
+
+  Warning Dead Value
+  ImmutableArray.res:88:3-62
+  partition is never used
+  <-- line 88
+    @dead("partition") let partition = (a, f) => Array.partition(a->fromT, f)->toT2
+
+  Warning Dead Value
+  ImmutableArray.res:90:3-58
+  reduceU is never used
+  <-- line 90
+    @dead("reduceU") let reduceU = (a, b, f) => Array.reduceU(a->fromT, b, f)
+
+  Warning Dead Value
+  ImmutableArray.res:91:3-56
+  reduce is never used
+  <-- line 91
+    @dead("reduce") let reduce = (a, b, f) => Array.reduce(a->fromT, b, f)
+
+  Warning Dead Value
+  ImmutableArray.res:93:3-72
+  reduceReverseU is never used
+  <-- line 93
+    @dead("reduceReverseU") let reduceReverseU = (a, b, f) => Array.reduceReverseU(a->fromT, b, f)
+
+  Warning Dead Value
+  ImmutableArray.res:94:3-70
+  reduceReverse is never used
+  <-- line 94
+    @dead("reduceReverse") let reduceReverse = (a, b, f) => Array.reduceReverse(a->fromT, b, f)
+
+  Warning Dead Value
+  ImmutableArray.res:96:3-91
+  reduceReverse2U is never used
+  <-- line 96
+    @dead("reduceReverse2U") let reduceReverse2U = (a1, a2, c, f) => Array.reduceReverse2U(fromT(a1), fromT(a2), c, f)
+
+  Warning Dead Value
+  ImmutableArray.res:97:3-89
+  reduceReverse2 is never used
+  <-- line 97
+    @dead("reduceReverse2") let reduceReverse2 = (a1, a2, c, f) => Array.reduceReverse2(fromT(a1), fromT(a2), c, f)
+
+  Warning Dead Value
+  ImmutableArray.res:99:3-48
+  someU is never used
+  <-- line 99
+    @dead("someU") let someU = (a, f) => Array.someU(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:100:3-46
+  some is never used
+  <-- line 100
+    @dead("some") let some = (a, f) => Array.some(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:102:3-50
+  everyU is never used
+  <-- line 102
+    @dead("everyU") let everyU = (a, f) => Array.everyU(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:103:3-48
+  every is never used
+  <-- line 103
+    @dead("every") let every = (a, f) => Array.every(a->fromT, f)
+
+  Warning Dead Value
+  ImmutableArray.res:105:3-69
+  every2U is never used
+  <-- line 105
+    @dead("every2U") let every2U = (a1, a2, f) => Array.every2U(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:106:3-67
+  every2 is never used
+  <-- line 106
+    @dead("every2") let every2 = (a1, a2, f) => Array.every2(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:108:3-67
+  some2U is never used
+  <-- line 108
+    @dead("some2U") let some2U = (a1, a2, f) => Array.some2U(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:109:3-65
+  some2 is never used
+  <-- line 109
+    @dead("some2") let some2 = (a1, a2, f) => Array.some2(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:111:3-63
+  cmpU is never used
+  <-- line 111
+    @dead("cmpU") let cmpU = (a1, a2, f) => Array.cmpU(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:112:3-61
+  cmp is never used
+  <-- line 112
+    @dead("cmp") let cmp = (a1, a2, f) => Array.cmp(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:114:3-61
+  eqU is never used
+  <-- line 114
+    @dead("eqU") let eqU = (a1, a2, f) => Array.eqU(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.res:115:3-59
+  eq is never used
+  <-- line 115
+    @dead("eq") let eq = (a1, a2, f) => Array.eq(fromT(a1), fromT(a2), f)
+
+  Warning Dead Value
+  ImmutableArray.resi:12:1-31
+  toArray is never used
+  <-- line 12
+  @dead("toArray") let toArray: t<'a> => array<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:14:1-107
+  length is never used
+  <-- line 14
+  @dead("length") @ocaml.doc(" Subset of the Belt.Array oprerations that do not mutate the array. ")
+
+  Warning Dead Value
+  ImmutableArray.resi:17:1-22
+  size is never used
+  <-- line 17
+  @dead("size") let size: t<'a> => int
+
+  Warning Dead Value
+  ImmutableArray.resi:19:1-35
+  get is never used
+  <-- line 19
+  @dead("get") let get: (t<'a>, int) => option<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:21:1-30
+  getExn is never used
+  <-- line 21
+  @dead("getExn") let getExn: (t<'a>, int) => 'a
+
+  Warning Dead Value
+  ImmutableArray.resi:23:1-33
+  getUnsafe is never used
+  <-- line 23
+  @dead("getUnsafe") let getUnsafe: (t<'a>, int) => 'a
+
+  Warning Dead Value
+  ImmutableArray.resi:25:1-50
+  getUndefined is never used
+  <-- line 25
+  @dead("getUndefined") let getUndefined: (t<'a>, int) => Js.undefined<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:27:1-27
+  shuffle is never used
+  <-- line 27
+  @dead("shuffle") let shuffle: t<'a> => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:29:1-27
+  reverse is never used
+  <-- line 29
+  @dead("reverse") let reverse: t<'a> => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:31:1-49
+  makeUninitialized is never used
+  <-- line 31
+  @dead("makeUninitialized") let makeUninitialized: int => t<Js.undefined<'a>>
+
+  Warning Dead Value
+  ImmutableArray.resi:33:1-41
+  makeUninitializedUnsafe is never used
+  <-- line 33
+  @dead("makeUninitializedUnsafe") let makeUninitializedUnsafe: int => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:35:1-28
+  make is never used
+  <-- line 35
+  @dead("make") let make: (int, 'a) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:37:1-31
+  range is never used
+  <-- line 37
+  @dead("range") let range: (int, int) => t<int>
+
+  Warning Dead Value
+  ImmutableArray.resi:39:1-45
+  rangeBy is never used
+  <-- line 39
+  @dead("rangeBy") let rangeBy: (int, int, ~step: int) => t<int>
+
+  Warning Dead Value
+  ImmutableArray.resi:41:1-42
+  makeByU is never used
+  <-- line 41
+  @dead("makeByU") let makeByU: (int, (. int) => 'a) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:42:1-37
+  makeBy is never used
+  <-- line 42
+  @dead("makeBy") let makeBy: (int, int => 'a) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:44:1-52
+  makeByAndShuffleU is never used
+  <-- line 44
+  @dead("makeByAndShuffleU") let makeByAndShuffleU: (int, (. int) => 'a) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:45:1-47
+  makeByAndShuffle is never used
+  <-- line 45
+  @dead("makeByAndShuffle") let makeByAndShuffle: (int, int => 'a) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:47:1-38
+  zip is never used
+  <-- line 47
+  @dead("zip") let zip: (t<'a>, t<'b>) => t<('a, 'b)>
+
+  Warning Dead Value
+  ImmutableArray.resi:49:1-53
+  zipByU is never used
+  <-- line 49
+  @dead("zipByU") let zipByU: (t<'a>, t<'b>, (. 'a, 'b) => 'c) => t<'c>
+
+  Warning Dead Value
+  ImmutableArray.resi:50:1-50
+  zipBy is never used
+  <-- line 50
+  @dead("zipBy") let zipBy: (t<'a>, t<'b>, ('a, 'b) => 'c) => t<'c>
+
+  Warning Dead Value
+  ImmutableArray.resi:52:1-40
+  unzip is never used
+  <-- line 52
+  @dead("unzip") let unzip: t<('a, 'a)> => (t<'a>, t<'a>)
+
+  Warning Dead Value
+  ImmutableArray.resi:54:1-35
+  concat is never used
+  <-- line 54
+  @dead("concat") let concat: (t<'a>, t<'a>) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:56:1-33
+  concatMany is never used
+  <-- line 56
+  @dead("concatMany") let concatMany: t<t<'a>> => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:58:1-52
+  slice is never used
+  <-- line 58
+  @dead("slice") let slice: (t<'a>, ~offset: int, ~len: int) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:60:1-37
+  sliceToEnd is never used
+  <-- line 60
+  @dead("sliceToEnd") let sliceToEnd: (t<'a>, int) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:62:1-24
+  copy is never used
+  <-- line 62
+  @dead("copy") let copy: t<'a> => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:64:1-45
+  forEachU is never used
+  <-- line 64
+  @dead("forEachU") let forEachU: (t<'a>, (. 'a) => unit) => unit
+
+  Warning Dead Value
+  ImmutableArray.resi:65:1-40
+  forEach is never used
+  <-- line 65
+  @dead("forEach") let forEach: (t<'a>, 'a => unit) => unit
+
+  Warning Dead Value
+  ImmutableArray.resi:67:1-40
+  mapU is never used
+  <-- line 67
+  @dead("mapU") let mapU: (t<'a>, (. 'a) => 'b) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:68:1-35
+  map is never used
+  <-- line 68
+  @dead("map") let map: (t<'a>, 'a => 'b) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:70:1-57
+  keepWithIndexU is never used
+  <-- line 70
+  @dead("keepWithIndexU") let keepWithIndexU: (t<'a>, (. 'a, int) => bool) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:71:1-54
+  keepWithIndex is never used
+  <-- line 71
+  @dead("keepWithIndex") let keepWithIndex: (t<'a>, ('a, int) => bool) => t<'a>
+
+  Warning Dead Value
+  ImmutableArray.resi:73:1-52
+  keepMapU is never used
+  <-- line 73
+  @dead("keepMapU") let keepMapU: (t<'a>, (. 'a) => option<'b>) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:74:1-47
+  keepMap is never used
+  <-- line 74
+  @dead("keepMap") let keepMap: (t<'a>, 'a => option<'b>) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:76:1-59
+  forEachWithIndexU is never used
+  <-- line 76
+  @dead("forEachWithIndexU") let forEachWithIndexU: (t<'a>, (. int, 'a) => unit) => unit
+
+  Warning Dead Value
+  ImmutableArray.resi:77:1-56
+  forEachWithIndex is never used
+  <-- line 77
+  @dead("forEachWithIndex") let forEachWithIndex: (t<'a>, (int, 'a) => unit) => unit
+
+  Warning Dead Value
+  ImmutableArray.resi:79:1-54
+  mapWithIndexU is never used
+  <-- line 79
+  @dead("mapWithIndexU") let mapWithIndexU: (t<'a>, (. int, 'a) => 'b) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:80:1-51
+  mapWithIndex is never used
+  <-- line 80
+  @dead("mapWithIndex") let mapWithIndex: (t<'a>, (int, 'a) => 'b) => t<'b>
+
+  Warning Dead Value
+  ImmutableArray.resi:82:1-57
+  partitionU is never used
+  <-- line 82
+  @dead("partitionU") let partitionU: (t<'a>, (. 'a) => bool) => (t<'a>, t<'a>)
+
+  Warning Dead Value
+  ImmutableArray.resi:83:1-52
+  partition is never used
+  <-- line 83
+  @dead("partition") let partition: (t<'a>, 'a => bool) => (t<'a>, t<'a>)
+
+  Warning Dead Value
+  ImmutableArray.resi:85:1-48
+  reduceU is never used
+  <-- line 85
+  @dead("reduceU") let reduceU: (t<'a>, 'b, (. 'b, 'a) => 'b) => 'b
+
+  Warning Dead Value
+  ImmutableArray.resi:86:1-45
+  reduce is never used
+  <-- line 86
+  @dead("reduce") let reduce: (t<'a>, 'b, ('b, 'a) => 'b) => 'b
+
+  Warning Dead Value
+  ImmutableArray.resi:88:1-55
+  reduceReverseU is never used
+  <-- line 88
+  @dead("reduceReverseU") let reduceReverseU: (t<'a>, 'b, (. 'b, 'a) => 'b) => 'b
+
+  Warning Dead Value
+  ImmutableArray.resi:89:1-52
+  reduceReverse is never used
+  <-- line 89
+  @dead("reduceReverse") let reduceReverse: (t<'a>, 'b, ('b, 'a) => 'b) => 'b
+
+  Warning Dead Value
+  ImmutableArray.resi:91:1-67
+  reduceReverse2U is never used
+  <-- line 91
+  @dead("reduceReverse2U") let reduceReverse2U: (t<'a>, t<'b>, 'c, (. 'c, 'a, 'b) => 'c) => 'c
+
+  Warning Dead Value
+  ImmutableArray.resi:92:1-64
+  reduceReverse2 is never used
+  <-- line 92
+  @dead("reduceReverse2") let reduceReverse2: (t<'a>, t<'b>, 'c, ('c, 'a, 'b) => 'c) => 'c
+
+  Warning Dead Value
+  ImmutableArray.resi:94:1-42
+  someU is never used
+  <-- line 94
+  @dead("someU") let someU: (t<'a>, (. 'a) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:95:1-37
+  some is never used
+  <-- line 95
+  @dead("some") let some: (t<'a>, 'a => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:97:1-43
+  everyU is never used
+  <-- line 97
+  @dead("everyU") let everyU: (t<'a>, (. 'a) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:98:1-38
+  every is never used
+  <-- line 98
+  @dead("every") let every: (t<'a>, 'a => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:100:1-55
+  every2U is never used
+  <-- line 100
+  @dead("every2U") let every2U: (t<'a>, t<'b>, (. 'a, 'b) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:101:1-52
+  every2 is never used
+  <-- line 101
+  @dead("every2") let every2: (t<'a>, t<'b>, ('a, 'b) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:103:1-54
+  some2U is never used
+  <-- line 103
+  @dead("some2U") let some2U: (t<'a>, t<'b>, (. 'a, 'b) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:104:1-51
+  some2 is never used
+  <-- line 104
+  @dead("some2") let some2: (t<'a>, t<'b>, ('a, 'b) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:106:1-50
+  cmpU is never used
+  <-- line 106
+  @dead("cmpU") let cmpU: (t<'a>, t<'a>, (. 'a, 'a) => int) => int
+
+  Warning Dead Value
+  ImmutableArray.resi:107:1-47
+  cmp is never used
+  <-- line 107
+  @dead("cmp") let cmp: (t<'a>, t<'a>, ('a, 'a) => int) => int
+
+  Warning Dead Value
+  ImmutableArray.resi:109:1-51
+  eqU is never used
+  <-- line 109
+  @dead("eqU") let eqU: (t<'a>, t<'a>, (. 'a, 'a) => bool) => bool
+
+  Warning Dead Value
+  ImmutableArray.resi:110:1-48
+  eq is never used
+  <-- line 110
+  @dead("eq") let eq: (t<'a>, t<'a>, ('a, 'a) => bool) => bool
+
+  Warning Dead Type
+  ImportHookDefault.res:2:3-14
+  person.name is a record label never used to read a value
+  <-- line 2
+    @dead("person.name") name: string,
+
+  Warning Dead Type
+  ImportHookDefault.res:3:3-10
+  person.age is a record label never used to read a value
+  <-- line 3
+    @dead("person.age") age: int,
+
+  Warning Dead Type
+  ImportHooks.res:3:3-14
+  person.name is a record label never used to read a value
+  <-- line 3
+    @dead("person.name") name: string,
+
+  Warning Dead Type
+  ImportHooks.res:4:3-10
+  person.age is a record label never used to read a value
+  <-- line 4
+    @dead("person.age") age: int,
+
+  Warning Dead Type
+  ImportJsValue.res:11:3-8
+  point.x is a record label never used to read a value
+  <-- line 11
+    @dead("point.x") x: int,
+
+  Warning Dead Type
+  ImportJsValue.res:12:3-16
+  point.y is a record label never used to read a value
+  <-- line 12
+    @dead("point.y") y: option<int>,
+
+  Warning Dead Type
+  ImportJsValue.res:67:3-10
+  variant.I is a variant case which is never constructed
+  <-- line 67
+    | @dead("variant.I") I(int)
+
+  Warning Dead Type
+  ImportJsValue.res:68:5-13
+  variant.S is a variant case which is never constructed
+  <-- line 68
+    | @dead("variant.S") S(string)
+
+  Warning Dead Type
+  ImportMyBanner.res:5:17-28
+  message.text is a record label never used to read a value
+  <-- line 5
+  type message = {@dead("message.text") text: string}
+
+  Warning Dead Value
+  ImportMyBanner.res:12:1-15
+  make is never used
+  <-- line 12
+  @dead("make") let make = make
+
+  Warning Dead Module
+  ModuleAliases.res:2:10-56
+  ModuleAliases.Outer.Inner is a dead module as all its items are dead.
+
+  Warning Dead Type
+  ModuleAliases.res:3:20-32
+  Outer.Inner.innerT.inner is a record label never used to read a value
+  <-- line 3
+      type innerT = {@dead("Outer.Inner.innerT.inner") inner: string}
+
+  Warning Dead Module
+  ModuleAliases.res:10:12-61
+  ModuleAliases.Outer2.Inner2.InnerNested is a dead module as all its items are dead.
+
+  Warning Dead Type
+  ModuleAliases.res:11:17-27
+  Outer2.Inner2.InnerNested.t.nested is a record label never used to read a value
+  <-- line 11
+        type t = {@dead("Outer2.Inner2.InnerNested.t.nested") nested: int}
+
+  Warning Dead Module
+  ModuleAliases2.res:0:1
+  ModuleAliases2 is a dead module as all its items are dead.
+
+  Warning Dead Type
+  ModuleAliases2.res:3:3-8
+  record.x is a record label never used to read a value
+  <-- line 3
+    @dead("record.x") x: int,
+
+  Warning Dead Type
+  ModuleAliases2.res:4:3-11
+  record.y is a record label never used to read a value
+  <-- line 4
+    @dead("record.y") y: string,
+
+  Warning Dead Module
+  ModuleAliases2.res:7:8-130
+  ModuleAliases2.Outer is a dead module as all its items are dead.
+
+  Warning Dead Type
+  ModuleAliases2.res:9:17-29
+  Outer.outer.outer is a record label never used to read a value
+  <-- line 9
+    type outer = {@dead("Outer.outer.outer") outer: string}
+
+  Warning Dead Module
+  ModuleAliases2.res:11:10-68
+  ModuleAliases2.Outer.Inner is a dead module as all its items are dead.
+
+  Warning Dead Type
+  ModuleAliases2.res:13:19-31
+  Outer.Inner.inner.inner is a record label never used to read a value
+  <-- line 13
+      type inner = {@dead("Outer.Inner.inner.inner") inner: string}
+
+  Warning Dead Value
+  ModuleAliases2.res:21:1-10
+  q is never used
+  <-- line 21
+  @dead("q") let q = 42
+
+  Warning Dead Module
+  ModuleExceptionBug.res:1:8-52
+  ModuleExceptionBug.Dep is a dead module as all its items are dead.
+
+  Warning Dead Value
+  ModuleExceptionBug.res:2:3-35
+  Dep.customDouble is never used
+  <-- line 2
+    @dead("Dep.customDouble") let customDouble = foo => foo * 2
+
+  Warning Dead Exception
+  ModuleExceptionBug.res:5:1-26
+  MyOtherException is never raised or passed as value
+  <-- line 5
+  @dead("MyOtherException") exception MyOtherException
+
+  Warning Dead Value
+  NestedModules.res:8:3-22
+  Universe.notExported is never used
+  <-- line 8
+    @dead("Universe.notExported") let notExported = 33
+
+  Warning Dead Value
+  NestedModules.res:14:5-13
+  Universe.Nested2.x is never used
+  <-- line 14
+      @dead("Universe.Nested2.x") let x = 0
+
+  Warning Dead Value
+  NestedModules.res:19:5-13
+  Universe.Nested2.y is never used
+  <-- line 19
+      @dead("Universe.Nested2.y") let y = 2
+
+  Warning Dead Value
+  NestedModules.res:25:7-15
+  Universe.Nested2.Nested3.x is never used
+  <-- line 25
+        @dead("Universe.Nested2.Nested3.x") let x = 0
+
+  Warning Dead Value
+  NestedModules.res:26:7-15
+  Universe.Nested2.Nested3.y is never used
+  <-- line 26
+        @dead("Universe.Nested2.Nested3.y") let y = 1
+
+  Warning Dead Value
+  NestedModules.res:27:7-15
+  Universe.Nested2.Nested3.z is never used
+  <-- line 27
+        @dead("Universe.Nested2.Nested3.z") let z = 2
+
+  Warning Dead Value
+  NestedModules.res:28:7-15
+  Universe.Nested2.Nested3.w is never used
+  <-- line 28
+        @dead("Universe.Nested2.Nested3.w") let w = 3
+
+  Warning Dead Type
+  NestedModules.res:46:5-7
+  Universe.variant.A is a variant case which is never constructed
+  <-- line 46
+      | @dead("Universe.variant.A") A
+
+  Warning Dead Type
+  NestedModules.res:47:7-15
+  Universe.variant.B is a variant case which is never constructed
+  <-- line 47
+      | @dead("Universe.variant.B") B(string)
+
+  Warning Dead Module
+  Newsyntax.res:0:1
+  Newsyntax is a dead module as all its items are dead.
+
+  Warning Dead Value
+  Newsyntax.res:1:1-10
+  x is never used
+  <-- line 1
+  @dead("x") let x = 34
+
+  Warning Dead Value
+  Newsyntax.res:3:1-10
+  y is never used
+  <-- line 3
+  @dead("y") let y = 11
+
+  Warning Dead Type
+  Newsyntax.res:6:3-10
+  record.xxx is a record label never used to read a value
+  <-- line 6
+    @dead("record.xxx") xxx: int,
+
+  Warning Dead Type
+  Newsyntax.res:7:3-10
+  record.yyy is a record label never used to read a value
+  <-- line 7
+    @dead("record.yyy") yyy: int,
+
+  Warning Dead Type
+  Newsyntax.res:10:16
+  variant.A is a variant case which is never constructed
+  <-- line 10
+  type variant = | @dead("variant.A") A | @dead("variant.B") B(int)|@dead("variant.C") C
+
+  Warning Dead Type
+  Newsyntax.res:10:20-25
+  variant.B is a variant case which is never constructed
+  <-- line 10
+  type variant = | @dead("variant.A") A | @dead("variant.B") B(int)|@dead("variant.C") C
+
+  Warning Dead Type
+  Newsyntax.res:10:26-27
+  variant.C is a variant case which is never constructed
+  <-- line 10
+  type variant = | @dead("variant.A") A | @dead("variant.B") B(int)|@dead("variant.C") C
+
+  Warning Dead Type
+  Newsyntax.res:12:17-22
+  record2.xx is a record label never used to read a value
+  <-- line 12
+  type record2 = {@dead("record2.xx") xx:int,@dead("record2.yy") yy:int}
+
+  Warning Dead Type
+  Newsyntax.res:12:24-29
+  record2.yy is a record label never used to read a value
+  <-- line 12
+  type record2 = {@dead("record2.xx") xx:int,@dead("record2.yy") yy:int}
+
+  Warning Dead Type
+  Opaque.res:2:26-41
+  opaqueFromRecords.A is a variant case which is never constructed
+  <-- line 2
+  type opaqueFromRecords = | @dead("opaqueFromRecords.A") A(Records.coord)
+
+  Warning Dead Value
+  OptArg.resi:1:1-54
+  foo is never used
+  <-- line 1
+  @dead("foo") let foo: (~x: int=?, ~y: int=?, ~z: int=?, int) => int
+
+  Warning Dead Type
+  Records.res:24:3-14
+  person.name is a record label never used to read a value
+  <-- line 24
+    @dead("person.name") name: string,
+
+  Warning Dead Type
+  Records.res:25:3-10
+  person.age is a record label never used to read a value
+  <-- line 25
+    @dead("person.age") age: int,
+
+  Warning Dead Type
+  Records.res:31:3-14
+  business.name is a record label never used to read a value
+  <-- line 31
+    @dead("business.name") name: string,
+
+  Warning Dead Type
+  Records.res:60:3-10
+  payload.num is a record label never used to read a value
+  <-- line 60
+    @dead("payload.num") num: int,
+
+  Warning Dead Type
+  Records.res:70:3-8
+  record.w is a record label never used to read a value
+  <-- line 70
+    @dead("record.w") w: int,
+
+  Warning Dead Type
+  Records.res:90:3-14
+  business2.name is a record label never used to read a value
+  <-- line 90
+    @dead("business2.name") name: string,
+
+  Warning Dead Type
+  Records.res:91:3-30
+  business2.owner is a record label never used to read a value
+  <-- line 91
+    @dead("business2.owner") owner: Js.Nullable.t<person>,
+
+  Warning Dead Type
+  References.res:39:28-33
+  requiresConversion.x is a record label never used to read a value
+  <-- line 39
+  type requiresConversion = {@dead("requiresConversion.x") x: int}
+
+  Warning Dead Type
+  RepeatedLabel.res:2:3-9
+  userData.a is a record label never used to read a value
+  <-- line 2
+    @dead("userData.a") a: bool,
+
+  Warning Dead Type
+  RepeatedLabel.res:3:3-8
+  userData.b is a record label never used to read a value
+  <-- line 3
+    @dead("userData.b") b: int,
+
+  Warning Dead Type
+  RepeatedLabel.res:9:3-11
+  tabState.f is a record label never used to read a value
+  <-- line 9
+    @dead("tabState.f") f: string,
+
+  Warning Dead Value
+  Shadow.res:11:3-22
+  M.test is never used
+  <-- line 11
+    @dead("M.test") let test = () => "a"
+
+  Warning Dead Value
+  TestImmutableArray.res:12:1-54
+  testBeltArrayGet is never used
+  <-- line 12
+  @dead("testBeltArrayGet") let testBeltArrayGet = arr => {
+
+  Warning Dead Value
+  TestImmutableArray.res:17:1-58
+  testBeltArraySet is never used
+  <-- line 17
+  @dead("testBeltArraySet") let testBeltArraySet = arr => {
+
+  Warning Dead Value
+  TestImport.res:13:1-43
+  innerStuffContents is never used
+  <-- line 13
+  @dead("innerStuffContents") let innerStuffContents = innerStuffContents
+
+  Warning Dead Type
+  TestImport.res:22:17-28
+  message.text is a record label never used to read a value
+  <-- line 22
+  type message = {@dead("message.text") text: string}
+
+  Warning Dead Value
+  TestImport.res:27:1-15
+  make is never used
+  <-- line 27
+  @dead("make") let make = make
+
+  Warning Dead Type
+  TestPromise.res:6:3-8
+  fromPayload.x is a record label never used to read a value
+  <-- line 6
+    @dead("fromPayload.x") x: int,
+
+  Warning Dead Type
+  TestPromise.res:11:19-32
+  toPayload.result is a record label never used to read a value
+  <-- line 11
+  type toPayload = {@dead("toPayload.result") result: string}
+
+  Warning Dead Module
+  TransitiveType2.res:0:1
+  TransitiveType2 is a dead module as all its items are dead.
+
+  Warning Dead Value
+  TransitiveType2.res:7:1-28
+  convertT2 is never used
+  <-- line 7
+  @dead("convertT2") let convertT2 = (x: t2) => x
+
+  Warning Dead Type
+  TransitiveType3.res:3:3-8
+  t3.i is a record label never used to read a value
+  <-- line 3
+    @dead("t3.i") i: int,
+
+  Warning Dead Type
+  TransitiveType3.res:4:3-11
+  t3.s is a record label never used to read a value
+  <-- line 4
+    @dead("t3.s") s: string,
+
+  Warning Dead Module
+  TypeParams1.res:0:1
+  TypeParams1 is a dead module as all its items are dead.
+
+  Warning Dead Value
+  TypeParams1.res:4:1-24
+  exportSomething is never used
+  <-- line 4
+  @dead("exportSomething") let exportSomething = 10
+
+  Warning Dead Module
+  TypeParams2.res:0:1
+  TypeParams2 is a dead module as all its items are dead.
+
+  Warning Dead Type
+  TypeParams2.res:2:14-20
+  item.id is a record label never used to read a value
+  <-- line 2
+  type item = {@dead("item.id") id: int}
+
+  Warning Dead Value
+  TypeParams2.res:10:1-24
+  exportSomething is never used
+  <-- line 10
+  @dead("exportSomething") let exportSomething = 10
+
+  Warning Dead Type
+  Types.res:12:3-13
+  typeWithVars.A is a variant case which is never constructed
+  <-- line 12
+    | @dead("typeWithVars.A") A('x, 'y)
+
+  Warning Dead Type
+  Types.res:13:5-9
+  typeWithVars.B is a variant case which is never constructed
+  <-- line 13
+    | @dead("typeWithVars.B") B('z)
+
+  Warning Dead Type
+  Types.res:35:27-47
+  mutuallyRecursiveB.a is a record label never used to read a value
+  <-- line 35
+  and mutuallyRecursiveB = {@dead("mutuallyRecursiveB.a") a: mutuallyRecursiveA}
+
+  Warning Dead Type
+  Types.res:56:3-5
+  opaqueVariant.A is a variant case which is never constructed
+  <-- line 56
+    | @dead("opaqueVariant.A") A
+
+  Warning Dead Type
+  Types.res:57:5
+  opaqueVariant.B is a variant case which is never constructed
+  <-- line 57
+    | @dead("opaqueVariant.B") B
+
+  Warning Dead Type
+  Types.res:87:3-8
+  record.i is a record label never used to read a value
+  <-- line 87
+    @dead("record.i") i: int,
+
+  Warning Dead Type
+  Types.res:88:3-11
+  record.s is a record label never used to read a value
+  <-- line 88
+    @dead("record.s") s: string,
+
+  Warning Dead Type
+  Types.res:133:20-26
+  someRecord.id is a record label never used to read a value
+  <-- line 133
+  type someRecord = {@dead("someRecord.id") id: int}
+
+  Warning Dead Module
+  Types.res:161:8-79
+  Types.ObjectId is a dead module as all its items are dead.
+
+  Warning Dead Value
+  Types.res:166:3-11
+  ObjectId.x is never used
+  <-- line 166
+    @dead("ObjectId.x") let x = 1
+
+  Warning Dead Type
+  Unboxed.res:2:11-16
+  v1.A is a variant case which is never constructed
+  <-- line 2
+  type v1 = | @dead("v1.A") A(int)
+
+  Warning Dead Type
+  Unboxed.res:5:11-16
+  v2.A is a variant case which is never constructed
+  <-- line 5
+  type v2 = | @dead("v2.A") A(int)
+
+  Warning Dead Type
+  Unboxed.res:11:12-17
+  r1.x is a record label never used to read a value
+  <-- line 11
+  type r1 = {@dead("r1.x") x: int}
+
+  Warning Dead Type
+  Unboxed.res:14:11-24
+  r2.B is a variant case which is never constructed
+  <-- line 14
+  type r2 = | @dead("r2.B") B({@dead("r2.B.g") g: string})
+
+  Warning Dead Type
+  Unboxed.res:14:14-22
+  r2.B.g is a record label never used to read a value
+  <-- line 14
+  type r2 = | @dead("r2.B") B({@dead("r2.B.g") g: string})
+
+  Warning Dead Type
+  Variants.res:95:14-39
+  type_.Type is a variant case which is never constructed
+  <-- line 95
+  type type_ = | @dead("type_.Type") @genType.as("type") Type
+
+  Warning Dead Type
+  Variants.res:102:3-10
+  result1.Ok is a variant case which is never constructed
+  <-- line 102
+    | @dead("result1.Ok") Ok('a)
+
+  Warning Dead Type
+  Variants.res:103:5-13
+  result1.Error is a variant case which is never constructed
+  <-- line 103
+    | @dead("result1.Error") Error('b)
+
+  Warning Dead Type
+  VariantsWithPayload.res:49:3-5
+  simpleVariant.A is a variant case which is never constructed
+  <-- line 49
+    | @dead("simpleVariant.A") A
+
+  Warning Dead Type
+  VariantsWithPayload.res:50:5
+  simpleVariant.B is a variant case which is never constructed
+  <-- line 50
+    | @dead("simpleVariant.B") B
+
+  Warning Dead Type
+  VariantsWithPayload.res:51:5
+  simpleVariant.C is a variant case which is never constructed
+  <-- line 51
+    | @dead("simpleVariant.C") C
+
+  Warning Dead Type
+  VariantsWithPayload.res:58:3-29
+  variantWithPayloads.A is a variant case which is never constructed
+  <-- line 58
+    | @dead("variantWithPayloads.A") @genType.as("ARenamed") A
+
+  Warning Dead Type
+  VariantsWithPayload.res:59:5-10
+  variantWithPayloads.B is a variant case which is never constructed
+  <-- line 59
+    | @dead("variantWithPayloads.B") B(int)
+
+  Warning Dead Type
+  VariantsWithPayload.res:60:5-15
+  variantWithPayloads.C is a variant case which is never constructed
+  <-- line 60
+    | @dead("variantWithPayloads.C") C(int, int)
+
+  Warning Dead Type
+  VariantsWithPayload.res:61:5-17
+  variantWithPayloads.D is a variant case which is never constructed
+  <-- line 61
+    | @dead("variantWithPayloads.D") D((int, int))
+
+  Warning Dead Type
+  VariantsWithPayload.res:62:5-23
+  variantWithPayloads.E is a variant case which is never constructed
+  <-- line 62
+    | @dead("variantWithPayloads.E") E(int, string, int)
+
+  Warning Dead Type
+  VariantsWithPayload.res:90:20-25
+  variant1Int.R is a variant case which is never constructed
+  <-- line 90
+  type variant1Int = | @dead("variant1Int.R") R(int)
+
+  Warning Dead Type
+  VariantsWithPayload.res:96:23-32
+  variant1Object.R is a variant case which is never constructed
+  <-- line 96
+  type variant1Object = | @dead("variant1Object.R") R(payload)
+  
+  Analysis reported 306 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:86, Warning Dead Value:177, Warning Dead Value With Side Effects:2, Warning Redundant Optional Argument:5, Warning Unused Argument:11)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt
new file mode 100644
index 0000000000..171a115010
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt
@@ -0,0 +1,127 @@
+
+
+  Exception Analysis
+  Exn.res:1:5-10
+  raises might raise Not_found (Exn.res:1:19) and is not annotated with @raises(Not_found)
+
+  Exception Analysis
+  Exn.res:19:5-28
+  callsRaiseWithAnnotation might raise Not_found (Exn.res:19:31) and is not annotated with @raises(Not_found)
+
+  Exception Analysis
+  Exn.res:22:5-42
+  callsRaiseWithAnnotationAndIsAnnotated might raise Not_found (Exn.res:22:45) and is not annotated with @raises(Not_found)
+
+  Exception Analysis
+  Exn.res:22:5-42
+  callsRaiseWithAnnotationAndIsAnnotated might raise Not_found (Exn.res:22:45) and is annotated with redundant @raises(A)
+
+  Exception Analysis
+  Exn.res:24:5
+  z might raise Failure (Exn.res:24:8) and is not annotated with @raises(Failure)
+
+  Exception Analysis
+  Exn.res:26:5-19
+  incompleteMatch might raise Match_failure (Exn.res:27:2) and is not annotated with @raises(Match_failure)
+
+  Exception Analysis
+  Exn.res:34:5-13
+  twoRaises might raise [A (Exn.res:36:4), B (Exn.res:39:4)] and is not annotated with @raises([A, B])
+
+  Exception Analysis
+  Exn.res:43:5-14
+  sequencing might raise A (Exn.res:44:2) and is not annotated with @raises(A)
+
+  Exception Analysis
+  Exn.res:50:5-14
+  wrongCatch might raise B (Exn.res:51:6) and is not annotated with @raises(B)
+
+  Exception Analysis
+  Exn.res:56:5-15
+  wrongCatch2 might raise [C (Exn.res:57:24), Match_failure (Exn.res:57:2)] and is not annotated with @raises([C, Match_failure])
+
+  Exception Analysis
+  Exn.res:64:5-19
+  raise2Annotate3 might raise [A (Exn.res:66:4), B (Exn.res:69:4)] and is annotated with redundant @raises(C)
+
+  Exception Analysis
+  Exn.res:75:5-24
+  parse_json_from_file might raise Error (Exn.res:78:4) and is not annotated with @raises(Error)
+
+  Exception Analysis
+  Exn.res:84:5-11
+  reRaise might raise B (Exn.res:86:19) and is not annotated with @raises(B)
+
+  Exception Analysis
+  Exn.res:95:5-22
+  raiseInInternalLet might raise A (Exn.res:96:14) and is not annotated with @raises(A)
+
+  Exception Analysis
+  Exn.res:100:5-16
+  indirectCall might raise Not_found (Exn.res:100:31) and is not annotated with @raises(Not_found)
+
+  Exception Analysis
+  Exn.res:148:5-16
+  severalCases might raise Failure (Exn.res:150:13 Exn.res:151:13 Exn.res:152:15) and is not annotated with @raises(Failure)
+
+  Exception Analysis
+  Exn.res:159:32-56
+  String.uncapitalize_ascii does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:161:32-63
+  String.uncapitalize_ascii does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:163:47-71
+  String.uncapitalize_ascii does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:163:47-79
+  expression does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:169:51-55
+  expression does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:167:25-56
+  String.uncapitalize_ascii does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:176:5-23
+  redundantAnnotation raises nothing and is annotated with redundant @raises(Invalid_argument)
+
+  Exception Analysis
+  Exn.res:178:5-6
+  _x might raise A (Exn.res:178:9) and is not annotated with @raises(A)
+
+  Exception Analysis
+  Exn.res:180:5
+  _ might raise A (Exn.res:180:8) and is not annotated with @raises(A)
+
+  Exception Analysis
+  Exn.res:182:5-6
+  () might raise A (Exn.res:182:9) and is not annotated with @raises(A)
+
+  Exception Analysis
+  Exn.res:184:1-16
+  Toplevel expression might raise Not_found (Exn.res:184:0) and is not annotated with @raises(Not_found)
+
+  Exception Analysis
+  Exn.res:186:1-19
+  Toplevel expression might raise exit (Exn.res:186:7) and is not annotated with @raises(exit)
+
+  Exception Analysis
+  Exn.res:196:45-46
+  expression does not raise and is annotated with redundant @doesNotRaise
+
+  Exception Analysis
+  Exn.res:196:5-21
+  onResultPipeWrong might raise Assert_failure (Exn.res:196:48) and is not annotated with @raises(Assert_failure)
+
+  Exception Analysis
+  ExnA.res:1:5-7
+  bar might raise Not_found (ExnA.res:1:16) and is not annotated with @raises(Not_found)
+  
+  Analysis reported 31 issues (Exception Analysis:31)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/package-lock.json b/tests/analysis_tests/tests-reanalyze/deadcode/package-lock.json
new file mode 100644
index 0000000000..1ca4ed0ba0
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/package-lock.json
@@ -0,0 +1,205 @@
+{
+  "name": "deadcode",
+  "version": "0.1.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "deadcode",
+      "version": "0.1.0",
+      "dependencies": {
+        "@glennsl/bs-json": "^5.0.4",
+        "@rescript/react": "^0.10.3"
+      },
+      "devDependencies": {
+        "react": "^16.13.1",
+        "react-dom": "^16.8.6",
+        "rescript": "^10.1.2"
+      }
+    },
+    "node_modules/@glennsl/bs-json": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/@glennsl/bs-json/-/bs-json-5.0.4.tgz",
+      "integrity": "sha512-Th9DetZjRlMZrb74kgGJ44oWcoFyOTE884WlSuXft0Cd+J09vHRxiB7eVyK7Gthb4cSevsBBJDHYAbGGL25wPw=="
+    },
+    "node_modules/@rescript/react": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.10.3.tgz",
+      "integrity": "sha512-Lf9rzrR3bQPKJjOK3PBRa/B3xrJ7CqQ1HYr9VHPVxJidarIJJFZBhj0Dg1uZURX+Wg/xiP0PHFxXmdj2bK8Vxw==",
+      "peerDependencies": {
+        "react": ">=16.8.1",
+        "react-dom": ">=16.8.1"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "dependencies": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "node_modules/react": {
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+      "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+      "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
+      },
+      "peerDependencies": {
+        "react": "^16.14.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "node_modules/rescript": {
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.2.tgz",
+      "integrity": "sha512-PPdhOiN+lwqxQ0qvzHc1KW0TL12LDy2X+qo/JIMIP3bMfiH0vxQH2e/lXuoutWWm04fGQGTv3hWH+gKW3/UyfA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "bsc": "bsc",
+        "bsrefmt": "bsrefmt",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
+      "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1"
+      }
+    }
+  },
+  "dependencies": {
+    "@glennsl/bs-json": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/@glennsl/bs-json/-/bs-json-5.0.4.tgz",
+      "integrity": "sha512-Th9DetZjRlMZrb74kgGJ44oWcoFyOTE884WlSuXft0Cd+J09vHRxiB7eVyK7Gthb4cSevsBBJDHYAbGGL25wPw=="
+    },
+    "@rescript/react": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.10.3.tgz",
+      "integrity": "sha512-Lf9rzrR3bQPKJjOK3PBRa/B3xrJ7CqQ1HYr9VHPVxJidarIJJFZBhj0Dg1uZURX+Wg/xiP0PHFxXmdj2bK8Vxw==",
+      "requires": {}
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+    },
+    "prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "requires": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "react": {
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+      "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2"
+      }
+    },
+    "react-dom": {
+      "version": "16.14.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+      "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1",
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.19.1"
+      }
+    },
+    "react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "rescript": {
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.2.tgz",
+      "integrity": "sha512-PPdhOiN+lwqxQ0qvzHc1KW0TL12LDy2X+qo/JIMIP3bMfiH0vxQH2e/lXuoutWWm04fGQGTv3hWH+gKW3/UyfA==",
+      "dev": true
+    },
+    "scheduler": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
+      "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "object-assign": "^4.1.1"
+      }
+    }
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/package.json b/tests/analysis_tests/tests-reanalyze/deadcode/package.json
new file mode 100644
index 0000000000..651648f5a2
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "deadcode",
+  "version": "0.1.0",
+  "private": true,
+  "devDependencies": {
+    "react": "^16.13.1",
+    "react-dom": "^16.8.6",
+    "rescript": "^10.1.2"
+  },
+  "dependencies": {
+    "@glennsl/bs-json": "^5.0.4",
+    "@rescript/react": "^0.10.3"
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/AutoAnnotate.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/AutoAnnotate.res
new file mode 100644
index 0000000000..2b4b65a2e5
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/AutoAnnotate.res
@@ -0,0 +1,16 @@
+type variant = R(int)
+
+@genType
+type record = {variant: variant}
+
+type r2 = {r2: int}
+
+type r3 = {r3: int}
+
+type r4 = {r4: int}
+
+@genType
+type annotatedVariant =
+  | R2(r2, r3)
+  | R4(r4)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/BootloaderResource.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/BootloaderResource.res
new file mode 100644
index 0000000000..298fe7e5de
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/BootloaderResource.res
@@ -0,0 +1,5 @@
+/* NOTE: This is a spooky interface that provides no type safety. It should be
+ * improved. Use with caution. */
+@module("BootloaderResource")
+external read: JSResource.t<'a> => 'a = "read"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/BucklescriptAnnotations.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/BucklescriptAnnotations.res
new file mode 100644
index 0000000000..1f37609837
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/BucklescriptAnnotations.res
@@ -0,0 +1,29 @@
+@genType
+type someMutableFields = {
+  @set
+  "mutable0": string,
+  "immutable": int,
+  @set
+  "mutable1": string,
+  @set
+  "mutable2": string,
+}
+
+@genType
+type someMethods = {
+  @meth
+  "send": string => unit,
+  @meth
+  "on": (string, (. int) => unit) => unit,
+  @meth
+  "threeargs": (int, string, int) => string,
+  "twoArgs": (. int, string) => int,
+}
+
+// let foo = (x: someMethods) => x["threeargs"](3, "a", 4)
+
+let bar = (x: someMethods) => {
+  let f = x["twoArgs"]
+  f(. 3, "a")
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ComponentAsProp.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ComponentAsProp.res
new file mode 100644
index 0000000000..a1f3d45658
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ComponentAsProp.res
@@ -0,0 +1,17 @@
+@ocaml.doc(
+  " This is like declaring a normal ReasonReact component's `make` function, except the body is a the interop hook wrapJsForReason "
+)
+@genType
+@react.component
+let make = (~title, ~description, ~button=?) => {
+  <div>
+    <div>
+      title
+      description
+      {switch button {
+      | Some(button) => button
+      | None => React.null
+      }}
+    </div>
+  </div>
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler1.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler1.res
new file mode 100644
index 0000000000..291e3e19bb
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler1.res
@@ -0,0 +1,9 @@
+module Error1 = {
+  type t = string
+  let notification = s => (s, s)
+}
+
+module MyErrorHandler = ErrorHandler.Make(Error1)
+
+MyErrorHandler.notify("abc")
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler2.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler2.res
new file mode 100644
index 0000000000..9b64fc61db
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/CreateErrorHandler2.res
@@ -0,0 +1,7 @@
+module Error2 = {
+  type t = int
+  let notification = n => (string_of_int(n), "")
+}
+
+module MyErrorHandler = ErrorHandler.Make(Error2) /* MyErrorHandler.notify(42) */
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeImplementation.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeImplementation.res
new file mode 100644
index 0000000000..b5f2228e73
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeImplementation.res
@@ -0,0 +1,4 @@
+module M: DeadCodeInterface.T = {
+  let x = 42
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeInterface.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeInterface.res
new file mode 100644
index 0000000000..7b53107d12
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadCodeInterface.res
@@ -0,0 +1,4 @@
+module type T = {
+  let x: int
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.res
new file mode 100644
index 0000000000..3f68ffce74
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.res
@@ -0,0 +1,13 @@
+exception Etoplevel
+
+module Inside = {
+  exception Einside
+}
+
+exception DeadE
+let eToplevel = Etoplevel
+
+let eInside = Inside.Einside
+
+Js.log(eInside)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.resi
new file mode 100644
index 0000000000..25af4a7a86
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadExn.resi
@@ -0,0 +1,3 @@
+// empty
+exception Etoplevel
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.res
new file mode 100644
index 0000000000..be16b074f3
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.res
@@ -0,0 +1,12 @@
+type moduleAccessPath =
+  | Root(string)
+  | Kaboom
+
+let rec emitModuleAccessPath = moduleAccessPath =>
+  switch moduleAccessPath {
+  | Root(s) => s
+  | Kaboom => ""
+  }
+
+let () = Js.log(Kaboom)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.resi
new file mode 100644
index 0000000000..0f11daf1b4
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadRT.resi
@@ -0,0 +1,4 @@
+type moduleAccessPath =
+  | Root(string)
+  | Kaboom
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res
new file mode 100644
index 0000000000..901d5af049
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res
@@ -0,0 +1,193 @@
+let _ = Js.log(ImmutableArray.fromArray)
+let fortytwo = 42
+
+@genType
+let fortyTwoButExported = 42
+
+let thisIsUsedOnce = 34
+ignore(thisIsUsedOnce)
+
+let thisIsUsedTwice = 34
+ignore(thisIsUsedTwice)
+ignore(thisIsUsedTwice)
+
+@dead
+let thisIsMarkedDead = 99
+
+let thisIsKeptAlive = 42
+
+@live
+let thisIsMarkedLive = thisIsKeptAlive
+
+module Inner = {
+  @dead
+  let thisIsAlsoMarkedDead = 99
+}
+
+module M: {
+  @dead
+  let thisSignatureItemIsDead: int
+} = {
+  let thisSignatureItemIsDead = 34
+}
+
+module VariantUsedOnlyInImplementation: {
+  type t = A // TODO: discovered this automatically
+  let a: t
+} = {
+  type t = A
+  let a = A
+}
+
+let _ = (x => x)(VariantUsedOnlyInImplementation.a)
+
+let _ = DeadTypeTest.OnlyInInterface
+let _ = DeadTypeTest.InBoth
+
+type record = {
+  xxx: int,
+  yyy: int,
+}
+
+let _ = r => r.xxx
+let _ = ({yyy}) => yyy
+
+module UnderscoreInside = {
+  let _ = 13
+}
+
+module MM: {
+  let x: int
+  let y: int
+} = {
+  let y = 55
+  let x = y
+  let valueOnlyInImplementation = 7
+}
+
+let _ = {
+  Js.log(MM.x)
+  44
+}
+
+let () = Js.log(DeadValueTest.valueAlive)
+
+let rec unusedRec = () => unusedRec()
+
+let rec split_map = l => {
+  let _ = split_map(l)
+  list{}
+}
+
+let rec rec1 = () => rec2()
+and rec2 = () => rec1()
+
+let rec recWithCallback = () => {
+  let cb = () => recWithCallback()
+  cb()
+}
+
+let rec foo = () => {
+  let cb = () => bar()
+  cb()
+}
+and bar = () => foo()
+
+let withDefaultValue = (~paramWithDefault=3, y) => paramWithDefault + y
+
+external unsafe_string1: (bytes, int, int) => Digest.t = "caml_md5_string"
+
+module Ext_buffer: {
+  external unsafe_string2: (bytes, int, int) => Digest.t = "caml_md5_string"
+} = {
+  external unsafe_string2: (bytes, int, int) => Digest.t = "caml_md5_string"
+}
+
+let () = Js.log(DeadRT.Root("xzz"))
+
+module type LocalDynamicallyLoadedComponent2 = module type of DynamicallyLoadedComponent
+
+module LazyDynamicallyLoadedComponent2 = {
+  let reasonResource: JSResource.t<
+    module(LocalDynamicallyLoadedComponent2),
+  > = JSResource.jSResource("DynamicallyLoadedComponent.bs")
+  let makeProps = DynamicallyLoadedComponent.makeProps
+  let make = props =>
+    React.createElement(
+      {
+        module Comp = unpack(BootloaderResource.read(reasonResource))
+        Comp.make
+      },
+      props,
+    )
+}
+
+module Chat = {}
+
+let zzz = {
+  let a1 = 1
+  let a2 = 2
+  let a3 = 3
+}
+
+let () = Js.log(<DynamicallyLoadedComponent s="" />)
+
+let second = 1L
+let minute = Int64.mul(60L, second)
+
+let deadRef = ref(12)
+
+@react.component
+let make = (~s) => React.string(s)
+
+let () = Js.log(make)
+
+let theSideEffectIsLogging = Js.log(123)
+
+let stringLengthNoSideEffects = String.length("sdkdl")
+
+// Trace.infok("", "", ({pf}) => pf("%s", ""))
+
+module GloobLive = {
+  let globallyLive1 = 1
+  let globallyLive2 = 2
+  let globallyLive3 = 3
+}
+
+module WithInclude: {
+  type t = A
+} = {
+  module T = {
+    type t = A
+  }
+  include T
+}
+
+Js.log(WithInclude.A)
+
+@dead
+let funWithInnerVars = () => {
+  let x = 34
+  let y = 36
+  x + y
+}
+
+type rc = {a: int}
+
+@dead
+let deadIncorrect = 34
+
+let _ = deadIncorrect
+
+type inlineRecord = IR({a: int, b: int, c: string, @dead d: int, @live e: int})
+
+let ira = 10
+let _ = ir =>
+  switch ir {
+  | IR({c} as r) => IR({a: ira, b: r.b, c, d: 0, e: 0})
+  }
+
+@dead
+type inlineRecord2 = IR2({a: int, b: int})
+
+type inlineRecord3 = | @dead IR3({a: int, b: int})
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestBlacklist.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestBlacklist.res
new file mode 100644
index 0000000000..da4a1822bf
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestBlacklist.res
@@ -0,0 +1,2 @@
+let x = 34
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.res
new file mode 100644
index 0000000000..033e13293e
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.res
@@ -0,0 +1,6 @@
+module Ext_buffer: {
+  let x: int
+} = {
+  let x = 42
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.resi
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTestWithInterface.resi
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.res
new file mode 100644
index 0000000000..6b70da5546
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.res
@@ -0,0 +1,16 @@
+type t =
+  | A
+  | B
+let a = A
+
+type deadType =
+  | OnlyInImplementation
+  | OnlyInInterface
+  | InBoth
+  | InNeither
+
+let _ = OnlyInImplementation
+let _ = InBoth
+
+@live
+type record = {x: int, y: string, z: float}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.resi
new file mode 100644
index 0000000000..05809ebc76
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTypeTest.resi
@@ -0,0 +1,11 @@
+type t =
+  | A
+  | B
+let a: t
+
+type deadType =
+  | OnlyInImplementation
+  | OnlyInInterface
+  | InBoth
+  | InNeither
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.res
new file mode 100644
index 0000000000..04689d45ba
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.res
@@ -0,0 +1,21 @@
+let valueAlive = 1
+let valueDead = 2
+
+let valueOnlyInImplementation = 3
+
+let rec subList = (b, e, l) =>
+  switch l {
+  | list{} => failwith("subList")
+  | list{h, ...t} =>
+    let tail = if e == 0 {
+      list{}
+    } else {
+      subList(b - 1, e - 1, t)
+    }
+    if b > 0 {
+      tail
+    } else {
+      list{h, ...tail}
+    }
+  }
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.resi
new file mode 100644
index 0000000000..68149978b3
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadValueTest.resi
@@ -0,0 +1,3 @@
+let valueAlive: int
+let valueDead: int
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Docstrings.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Docstrings.res
new file mode 100644
index 0000000000..25c66f43fc
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Docstrings.res
@@ -0,0 +1,68 @@
+@ocaml.doc(" hello ") @genType
+let flat = 34
+
+@ocaml.doc("
+  * Sign a message with a key.
+  *
+  * @param message - A message to be signed
+  * @param key - The key with which to sign the message
+  * @returns A signed message
+ ")
+@genType
+let signMessage = (. message, key) => message ++ string_of_int(key)
+
+@genType
+let one = a => a + 0
+
+@genType
+let two = (a, b) => a + b + 0
+
+@genType
+let tree = (a, b, c) => a + b + c + 0
+
+@genType
+let oneU = (. a) => a + 0
+
+@genType
+let twoU = (. a, b) => a + b + 0
+
+@genType
+let treeU = (. a, b, c) => a + b + c + 0
+
+@genType
+let useParam = param => param + 34
+
+@genType
+let useParamU = (. param) => param + 34
+
+@genType
+let unnamed1 = (_: int) => 34
+
+@genType
+let unnamed1U = (. _: int) => 34
+
+@genType
+let unnamed2 = (_: int, _: int) => 34
+
+@genType
+let unnamed2U = (. _: int, _: int) => 34
+
+@genType
+let grouped = (~x, ~y, a, b, c, ~z) => x + y + a + b + c + z
+
+@genType
+let unitArgWithoutConversion = () => "abc"
+
+@genType
+let unitArgWithoutConversionU = (. ()) => "abc"
+
+type t =
+  | A
+  | B
+
+@genType
+let unitArgWithConversion = () => A
+
+@genType
+let unitArgWithConversionU = (. ()) => A
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DynamicallyLoadedComponent.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DynamicallyLoadedComponent.res
new file mode 100644
index 0000000000..44d361daa8
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DynamicallyLoadedComponent.res
@@ -0,0 +1,3 @@
+@react.component
+let make = (~s) => React.string(s)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/EmptyArray.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/EmptyArray.res
new file mode 100644
index 0000000000..9c44bd9a7b
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/EmptyArray.res
@@ -0,0 +1,10 @@
+// @@config({flags : ["-dsource"]});
+
+module Z = {
+  @react.component
+  let make = () => {
+    <br />
+  }
+}
+
+let _ = <Z />
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.res
new file mode 100644
index 0000000000..6e4576e2d8
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.res
@@ -0,0 +1,13 @@
+module type Error = {
+  type t
+  let notification: t => (string, string)
+}
+
+module Make = (Error: Error) => {
+  let notify = x => Error.notification(x)
+}
+
+// This is ignored as there's an interface file
+@genType
+let x = 42
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.resi
new file mode 100644
index 0000000000..87b3ae0bb8
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ErrorHandler.resi
@@ -0,0 +1,11 @@
+module type Error = {
+  type t
+  let notification: t => (string, string)
+}
+module Make: (Error: Error) =>
+{
+  let notify: Error.t => (string, string)
+}
+
+let x: int
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/EverythingLiveHere.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/EverythingLiveHere.res
new file mode 100644
index 0000000000..bb0f1915dd
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/EverythingLiveHere.res
@@ -0,0 +1,6 @@
+let x = 1
+
+let y = 3
+
+let z = 4
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/FC.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/FC.res
new file mode 100644
index 0000000000..c582dc1fe5
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/FC.res
@@ -0,0 +1,12 @@
+module type ReplacebleComponent = {
+  @react.component
+  let make: unit => React.element
+}
+
+let foo = (~impl: module(ReplacebleComponent)) => {
+  let module(X) = impl
+  X.make
+}
+
+Js.log(foo)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModules.res
new file mode 100644
index 0000000000..43fc7ff618
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModules.res
@@ -0,0 +1,66 @@
+module type MT = {
+  let x: int
+  type t = int
+  @module("foo") external f: int => int = "f"
+  module type MT2 = {
+    type tt = string
+  }
+  module EmptyInnerModule: {}
+  module InnerModule2: {
+    let k: t
+  }
+  module InnerModule3: {
+    type inner = int
+    let k3: inner => inner
+  }
+  module type TT = {
+    let u: (int, int)
+  }
+  module Z: TT
+  let y: string
+}
+module M = {
+  let y = "abc"
+  module type MT2 = {
+    type tt = string
+  }
+  module EmptyInnerModule = {}
+  module InnerModule2 = {
+    let k = 4242
+  }
+  module InnerModule3 = {
+    type inner = int
+    let k3 = x => x + 1
+  }
+
+  module type TT = {
+    let u: (int, int)
+  }
+  module Z = {
+    let u = (0, 0)
+  }
+  type t = int
+  @module("foo") external f: int => int = "f"
+  let x = 42
+}
+
+@genType
+type firstClassModule = module(MT)
+
+@genType
+let firstClassModule: firstClassModule = module(M)
+
+@genType
+let testConvert = (m: module(MT)) => m
+
+module type ResT = {
+  let ww: string
+}
+
+module SomeFunctor = (X: MT): ResT => {
+  let ww = X.y
+}
+
+@genType
+let someFunctorAsFunction = (x: module(MT)): module(ResT) => module(SomeFunctor(unpack(x)))
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.res
new file mode 100644
index 0000000000..c872847dc1
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.res
@@ -0,0 +1,13 @@
+type record = {
+  x: int,
+  y: string,
+}
+
+let r = {x: 3, y: "hello"}
+
+module type MT = {
+  let x: int
+}
+
+type firstClassModule = module(MT)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.resi
new file mode 100644
index 0000000000..58128ff6af
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/FirstClassModulesInterface.resi
@@ -0,0 +1,16 @@
+@genType
+type record = {
+  x: int,
+  y: string,
+}
+
+let r: record
+
+@genType
+module type MT = {
+  let x: int
+}
+
+@genType
+type firstClassModule = module(MT)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Hooks.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Hooks.res
new file mode 100644
index 0000000000..f35c242879
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Hooks.res
@@ -0,0 +1,115 @@
+type vehicle = {name: string}
+
+@react.component
+let make = (~vehicle) => {
+  let (count, setCount) = React.useState(() => 0)
+
+  <div>
+    <p>
+      {React.string(
+        "Hooks example " ++ (vehicle.name ++ (" clicked " ++ (string_of_int(count) ++ " times"))),
+      )}
+    </p>
+    <button onClick={_ => setCount(_ => count + 1)}> {React.string("Click me")} </button>
+    <ImportHooks person={name: "Mary", age: 71} renderMe={x => React.string(x["randomString"])}>
+      {React.string("child1")} {React.string("child2")}
+    </ImportHooks>
+    <ImportHookDefault
+      person={name: "DefaultImport", age: 42} renderMe={x => React.string(x["randomString"])}>
+      {React.string("child1")} {React.string("child2")}
+    </ImportHookDefault>
+  </div>
+}
+
+@genType
+let default = make
+
+@genType @react.component
+let anotherComponent = (~vehicle, ~callback: unit => unit) => {
+  callback()
+  <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+}
+
+module Inner = {
+  @genType @react.component
+  let make = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+  @genType @react.component
+  let anotherComponent = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+  module Inner2 = {
+    @genType @react.component
+    let make = (~vehicle) => <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+
+    @genType @react.component
+    let anotherComponent = (~vehicle) =>
+      <div> {React.string("Another Hook " ++ vehicle.name)} </div>
+  }
+}
+
+module NoProps = {
+  @genType @react.component
+  let make = () => <div> React.null </div>
+}
+
+type cb = (~_to: vehicle) => unit
+
+@genType
+let functionWithRenamedArgs = (~_to, ~_Type, ~cb: cb) => {
+  cb(~_to)
+  _to.name ++ _Type.name
+}
+
+@genType @react.component
+let componentWithRenamedArgs = (~_to, ~_Type, ~cb: cb) => {
+  cb(~_to)
+  React.string(_to.name ++ _Type.name)
+}
+
+@genType @react.component
+let makeWithRef = (~vehicle) => {
+  let _ = 34
+  ref =>
+    switch ref->Js.Nullable.toOption {
+    | Some(ref) => <button ref={ReactDOM.Ref.domRef(ref)}> {React.string(vehicle.name)} </button>
+    | None => React.null
+    }
+}
+
+@genType
+let testForwardRef = React.forwardRef(makeWithRef)
+
+type r = {x: string}
+
+@genType @react.component
+let input = React.forwardRef((~r, (), ref) => <div ref={Obj.magic(ref)}> {React.string(r.x)} </div>)
+
+@genType
+type callback<'input, 'output> = React.callback<'input, 'output>
+
+@genType
+type testReactContext = React.Context.t<int>
+
+@genType
+type testReactRef = React.Ref.t<int>
+
+@genType
+type testDomRef = ReactDOM.domRef
+
+@genType @react.component
+let polymorphicComponent = (~p as (x, _)) => React.string(x.name)
+
+@genType @react.component
+let functionReturningReactElement = (~name) => React.string(name)
+
+module RenderPropRequiresConversion = {
+  @genType @react.component
+  let make = (~renderVehicle: {"vehicle": vehicle, "number": int} => React.element) => {
+    let car = {name: "Car"}
+    renderVehicle({"vehicle": car, "number": 42})
+  }
+}
+
+@genType @react.component
+let aComponentWithChildren = (~vehicle, ~children) =>
+  <div> {React.string("Another Hook " ++ vehicle.name)} <div> children </div> </div>
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.res
new file mode 100644
index 0000000000..441f4ed8ee
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.res
@@ -0,0 +1,3 @@
+@gentype
+type t = int
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.resi
new file mode 100644
index 0000000000..5cc1d27942
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/IgnoreInterface.resi
@@ -0,0 +1,6 @@
+// Use the annotations, and definitions, from the .re file
+@@genType.ignoreInterface
+
+@genType
+type t
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.res
new file mode 100644
index 0000000000..8ed5e0809e
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.res
@@ -0,0 +1,119 @@
+type t<+'a>
+module Array = {
+  open Belt
+  type array2<'a> = (array<'a>, array<'a>)
+  external fromT: t<'a> => array<'a> = "%identity"
+  external fromTp: t<('a, 'b)> => array<('a, 'b)> = "%identity"
+  external fromTT: t<t<'a>> => array<array<'a>> = "%identity"
+  external toT: array<'a> => t<'a> = "%identity"
+  external toTp: array<('a, 'b)> => t<('a, 'b)> = "%identity"
+  external toT2: array2<'a> => (t<'a>, t<'a>) = "%identity"
+
+  /* Conversions involve a copy */
+
+  let fromArray = a => Array.copy(a)->toT
+
+  let toArray = a => Array.copy(a->fromT)
+
+  /* Type-cast immutable functions from Belt.Array. */
+
+  let length = a => Array.length(a->fromT)
+
+  let size = a => Array.size(a->fromT)
+
+  let get = (a, x) => (a->fromT)[x]
+
+  let getExn = (a, x) => Array.getExn(a->fromT, x)
+
+  let getUnsafe = (a, x) => Array.getUnsafe(a->fromT, x)
+
+  let getUndefined = (a, x) => Array.getUndefined(a->fromT, x)
+
+  let shuffle = x => Array.shuffle(x->fromT)->toT
+
+  let reverse = x => Array.reverse(x->fromT)->toT
+
+  let makeUninitialized = x => Array.makeUninitialized(x)->toT
+
+  let makeUninitializedUnsafe = x => Array.makeUninitializedUnsafe(x)->toT
+
+  let make = (x, y) => Array.make(x, y)->toT
+
+  let range = (x, y) => Array.range(x, y)->toT
+
+  let rangeBy = (x, y, ~step) => Array.rangeBy(x, y, ~step)->toT
+
+  let makeByU = (c, f) => Array.makeByU(c, f)->toT
+  let makeBy = (c, f) => Array.makeBy(c, f)->toT
+
+  let makeByAndShuffleU = (c, f) => Array.makeByAndShuffleU(c, f)->toT
+  let makeByAndShuffle = (c, f) => Array.makeByAndShuffle(c, f)->toT
+
+  let zip = (a1, a2) => Array.zip(fromT(a1), fromT(a2))->toTp
+
+  let zipByU = (a1, a2, f) => Array.zipByU(fromT(a1), fromT(a2), f)->toT
+  let zipBy = (a1, a2, f) => Array.zipBy(fromT(a1), fromT(a2), f)->toT
+
+  let unzip = a => Array.unzip(a->fromTp)->toT2
+
+  let concat = (a1, a2) => Array.concat(a1->fromT, a2->fromT)->toT
+
+  let concatMany = (a: t<t<_>>) => Array.concatMany(a->fromTT)->toT
+
+  let slice = (a, ~offset, ~len) => Array.slice(a->fromT, ~offset, ~len)->toT
+
+  let sliceToEnd = (a, b) => Array.sliceToEnd(a->fromT, b)->toT
+
+  let copy = a => Array.copy(a->fromT)->toT
+
+  let forEachU = (a, f) => Array.forEachU(a->fromT, f)
+  let forEach = (a, f) => Array.forEach(a->fromT, f)
+
+  let mapU = (a, f) => Array.mapU(a->fromT, f)->toT
+  let map = (a, f) => Array.map(a->fromT, f)->toT
+
+  let keepWithIndexU = (a, f) => Array.keepWithIndexU(a->fromT, f)->toT
+  let keepWithIndex = (a, f) => Array.keepWithIndex(a->fromT, f)->toT
+
+  let keepMapU = (a, f) => Array.keepMapU(a->fromT, f)->toT
+  let keepMap = (a, f) => Array.keepMap(a->fromT, f)->toT
+
+  let forEachWithIndexU = (a, f) => Array.forEachWithIndexU(a->fromT, f)
+  let forEachWithIndex = (a, f) => Array.forEachWithIndex(a->fromT, f)
+
+  let mapWithIndexU = (a, f) => Array.mapWithIndexU(a->fromT, f)->toT
+  let mapWithIndex = (a, f) => Array.mapWithIndex(a->fromT, f)->toT
+
+  let partitionU = (a, f) => Array.partitionU(a->fromT, f)->toT2
+  let partition = (a, f) => Array.partition(a->fromT, f)->toT2
+
+  let reduceU = (a, b, f) => Array.reduceU(a->fromT, b, f)
+  let reduce = (a, b, f) => Array.reduce(a->fromT, b, f)
+
+  let reduceReverseU = (a, b, f) => Array.reduceReverseU(a->fromT, b, f)
+  let reduceReverse = (a, b, f) => Array.reduceReverse(a->fromT, b, f)
+
+  let reduceReverse2U = (a1, a2, c, f) => Array.reduceReverse2U(fromT(a1), fromT(a2), c, f)
+  let reduceReverse2 = (a1, a2, c, f) => Array.reduceReverse2(fromT(a1), fromT(a2), c, f)
+
+  let someU = (a, f) => Array.someU(a->fromT, f)
+  let some = (a, f) => Array.some(a->fromT, f)
+
+  let everyU = (a, f) => Array.everyU(a->fromT, f)
+  let every = (a, f) => Array.every(a->fromT, f)
+
+  let every2U = (a1, a2, f) => Array.every2U(fromT(a1), fromT(a2), f)
+  let every2 = (a1, a2, f) => Array.every2(fromT(a1), fromT(a2), f)
+
+  let some2U = (a1, a2, f) => Array.some2U(fromT(a1), fromT(a2), f)
+  let some2 = (a1, a2, f) => Array.some2(fromT(a1), fromT(a2), f)
+
+  let cmpU = (a1, a2, f) => Array.cmpU(fromT(a1), fromT(a2), f)
+  let cmp = (a1, a2, f) => Array.cmp(fromT(a1), fromT(a2), f)
+
+  let eqU = (a1, a2, f) => Array.eqU(fromT(a1), fromT(a2), f)
+  let eq = (a1, a2, f) => Array.eq(fromT(a1), fromT(a2), f)
+}
+
+include Array
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.resi
new file mode 100644
index 0000000000..914ddfd766
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImmutableArray.resi
@@ -0,0 +1,111 @@
+@ocaml.doc(" Immutable arrays are covariant. ")
+type t<+'a>
+
+@ocaml.doc(" Redefine the [_] syntax, and disable the assignment [_] = _. ")
+module Array: {
+  let get: (t<'a>, int) => option<'a>
+}
+
+@ocaml.doc(" Converting from/to normal arrays involves making a copy. ")
+let fromArray: array<'a> => t<'a>
+
+let toArray: t<'a> => array<'a>
+
+@ocaml.doc(" Subset of the Belt.Array oprerations that do not mutate the array. ")
+let length: t<'a> => int
+
+let size: t<'a> => int
+
+let get: (t<'a>, int) => option<'a>
+
+let getExn: (t<'a>, int) => 'a
+
+let getUnsafe: (t<'a>, int) => 'a
+
+let getUndefined: (t<'a>, int) => Js.undefined<'a>
+
+let shuffle: t<'a> => t<'a>
+
+let reverse: t<'a> => t<'a>
+
+let makeUninitialized: int => t<Js.undefined<'a>>
+
+let makeUninitializedUnsafe: int => t<'a>
+
+let make: (int, 'a) => t<'a>
+
+let range: (int, int) => t<int>
+
+let rangeBy: (int, int, ~step: int) => t<int>
+
+let makeByU: (int, (. int) => 'a) => t<'a>
+let makeBy: (int, int => 'a) => t<'a>
+
+let makeByAndShuffleU: (int, (. int) => 'a) => t<'a>
+let makeByAndShuffle: (int, int => 'a) => t<'a>
+
+let zip: (t<'a>, t<'b>) => t<('a, 'b)>
+
+let zipByU: (t<'a>, t<'b>, (. 'a, 'b) => 'c) => t<'c>
+let zipBy: (t<'a>, t<'b>, ('a, 'b) => 'c) => t<'c>
+
+let unzip: t<('a, 'a)> => (t<'a>, t<'a>)
+
+let concat: (t<'a>, t<'a>) => t<'a>
+
+let concatMany: t<t<'a>> => t<'a>
+
+let slice: (t<'a>, ~offset: int, ~len: int) => t<'a>
+
+let sliceToEnd: (t<'a>, int) => t<'a>
+
+let copy: t<'a> => t<'a>
+
+let forEachU: (t<'a>, (. 'a) => unit) => unit
+let forEach: (t<'a>, 'a => unit) => unit
+
+let mapU: (t<'a>, (. 'a) => 'b) => t<'b>
+let map: (t<'a>, 'a => 'b) => t<'b>
+
+let keepWithIndexU: (t<'a>, (. 'a, int) => bool) => t<'a>
+let keepWithIndex: (t<'a>, ('a, int) => bool) => t<'a>
+
+let keepMapU: (t<'a>, (. 'a) => option<'b>) => t<'b>
+let keepMap: (t<'a>, 'a => option<'b>) => t<'b>
+
+let forEachWithIndexU: (t<'a>, (. int, 'a) => unit) => unit
+let forEachWithIndex: (t<'a>, (int, 'a) => unit) => unit
+
+let mapWithIndexU: (t<'a>, (. int, 'a) => 'b) => t<'b>
+let mapWithIndex: (t<'a>, (int, 'a) => 'b) => t<'b>
+
+let partitionU: (t<'a>, (. 'a) => bool) => (t<'a>, t<'a>)
+let partition: (t<'a>, 'a => bool) => (t<'a>, t<'a>)
+
+let reduceU: (t<'a>, 'b, (. 'b, 'a) => 'b) => 'b
+let reduce: (t<'a>, 'b, ('b, 'a) => 'b) => 'b
+
+let reduceReverseU: (t<'a>, 'b, (. 'b, 'a) => 'b) => 'b
+let reduceReverse: (t<'a>, 'b, ('b, 'a) => 'b) => 'b
+
+let reduceReverse2U: (t<'a>, t<'b>, 'c, (. 'c, 'a, 'b) => 'c) => 'c
+let reduceReverse2: (t<'a>, t<'b>, 'c, ('c, 'a, 'b) => 'c) => 'c
+
+let someU: (t<'a>, (. 'a) => bool) => bool
+let some: (t<'a>, 'a => bool) => bool
+
+let everyU: (t<'a>, (. 'a) => bool) => bool
+let every: (t<'a>, 'a => bool) => bool
+
+let every2U: (t<'a>, t<'b>, (. 'a, 'b) => bool) => bool
+let every2: (t<'a>, t<'b>, ('a, 'b) => bool) => bool
+
+let some2U: (t<'a>, t<'b>, (. 'a, 'b) => bool) => bool
+let some2: (t<'a>, t<'b>, ('a, 'b) => bool) => bool
+
+let cmpU: (t<'a>, t<'a>, (. 'a, 'a) => int) => int
+let cmp: (t<'a>, t<'a>, ('a, 'a) => int) => int
+
+let eqU: (t<'a>, t<'a>, (. 'a, 'a) => bool) => bool
+let eq: (t<'a>, t<'a>, ('a, 'a) => bool) => bool
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHookDefault.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHookDefault.res
new file mode 100644
index 0000000000..1b49ea2c7e
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHookDefault.res
@@ -0,0 +1,19 @@
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType.import(("./hookExample", "default")) @react.component
+external make: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: ImportHooks.renderMe<string>,
+) => React.element = "make"
+
+@genType.import("./hookExample") @react.component
+external make2: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: ImportHooks.renderMe<string>,
+) => React.element = "default"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHooks.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHooks.res
new file mode 100644
index 0000000000..31b0e29976
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportHooks.res
@@ -0,0 +1,22 @@
+@genType
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType
+type renderMe<'a> = React.component<{
+  "randomString": string,
+  "poly": 'a,
+}>
+
+@genType.import("./hookExample") @react.component
+external make: (
+  ~person: person,
+  ~children: React.element,
+  ~renderMe: renderMe<'a>,
+) => React.element = "makeRenamed"
+
+@genType.import("./hookExample")
+external foo: (~person: person) => string = "foo"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportIndex.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportIndex.res
new file mode 100644
index 0000000000..c4d5ce3294
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportIndex.res
@@ -0,0 +1,4 @@
+// TODO: rename metodd back once remmt bug is fixed
+@genType.import("./") @react.component
+external make: (~method: @string [#push | #replace]=?) => React.element = "default"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportJsValue.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportJsValue.res
new file mode 100644
index 0000000000..e85b7fd830
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportJsValue.res
@@ -0,0 +1,85 @@
+@ocaml.doc("
+  * Wrap JS values to be used from Reason
+  ")
+@genType.import("./MyMath")
+external /* This is the module to import from. */
+/* Name and type of the JS value to bind to. */
+round: float => float = "round"
+
+@genType
+type point = {
+  x: int,
+  y: option<int>,
+}
+
+@genType.import("./MyMath")
+external /* This is the module to import from. */
+/* Name and type of the JS value to bind to. */
+area: point => int = "area"
+
+@genType.import("./MyMath")
+type numberOrString
+
+@genType.import("./MyMath")
+external returnMixedArray: unit => array<numberOrString> = "returnMixedArray"
+
+@genType
+let roundedNumber = round(1.8)
+
+@genType
+let areaValue = area({x: 3, y: None})
+
+module AbsoluteValue = {
+  @genType.import(("./MyMath", "AbsoluteValue"))
+  type t = {"getAbs": (. unit) => int}
+
+  /* This is untyped */
+  @send external getProp: t => int = "getProp"
+
+  /* This is also untyped, as we "trust" the type declaration in absoluteVaue */
+  let getAbs = (x: t) => {
+    let getAbs = x["getAbs"]
+    getAbs(.)
+  }
+}
+
+@genType
+let useGetProp = (x: AbsoluteValue.t) => x->AbsoluteValue.getProp + 1
+
+@genType
+let useGetAbs = (x: AbsoluteValue.t) => x->AbsoluteValue.getAbs + 1
+
+@genType.import("./MyMath")
+type stringFunction
+
+@genType
+type color = [#tomato | #gray]
+
+@genType.import("./MyMath") external useColor: color => int = "useColor"
+
+@genType.import("./MyMath")
+external higherOrder: ((int, int) => int) => int = "higherOrder"
+
+@genType
+let returnedFromHigherOrder = higherOrder(\"+")
+
+type variant =
+  | I(int)
+  | S(string)
+
+@genType.import("./MyMath")
+external convertVariant: variant => variant = "convertVariant"
+
+@genType.import("./MyMath") external polymorphic: 'a => 'a = "polymorphic"
+
+@genType.import("./MyMath") external default: int = "default"
+
+@genType.import(("./MyMath", "num"))
+type num
+
+@genType.import(("./MyMath", "num"))
+type myNum
+
+@genType.import("./MyMath")
+type polyType<'a>
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportMyBanner.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportMyBanner.res
new file mode 100644
index 0000000000..9b26fa2366
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ImportMyBanner.res
@@ -0,0 +1,12 @@
+@ocaml.doc("
+  * Wrap component MyBanner to be used from Reason.
+  ")
+@genType
+type message = {text: string}
+
+@genType.import("./MyBanner")
+external /* Module with the JS component to be wrapped. */
+/* The make function will be automatically generated from the types below. */
+make: (~show: bool, ~message: option<message>=?, 'a) => React.element = "make"
+
+let make = make
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.res
new file mode 100644
index 0000000000..44813b6f7f
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.res
@@ -0,0 +1,3 @@
+module I = {
+  type t = Foo
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.resi
new file mode 100644
index 0000000000..0a83a7cc6b
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/InnerModuleTypes.resi
@@ -0,0 +1,3 @@
+module I: {
+  type t = Foo
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/JSResource.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/JSResource.res
new file mode 100644
index 0000000000..860277d273
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/JSResource.res
@@ -0,0 +1,4 @@
+type t<'a>
+
+@module external jSResource: string => t<'a> = "JSResource"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/JsxV4.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/JsxV4.res
new file mode 100644
index 0000000000..e9edcb20e8
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/JsxV4.res
@@ -0,0 +1,7 @@
+@@jsxConfig({version: 4})
+
+module C = {
+  @react.component let make = () => React.null
+}
+
+let _ = <C />
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/LetPrivate.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/LetPrivate.res
new file mode 100644
index 0000000000..909c265556
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/LetPrivate.res
@@ -0,0 +1,8 @@
+%%private(
+  @genType
+  let x = 34
+)
+
+@genType
+let y = x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases.res
new file mode 100644
index 0000000000..5d035fc231
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases.res
@@ -0,0 +1,29 @@
+module Outer = {
+  module Inner = {
+    type innerT = {inner: string}
+  }
+}
+
+module Outer2 = {
+  module OuterInnerAlias = Outer.Inner
+  module Inner2 = {
+    module InnerNested = {
+      type t = {nested: int}
+    }
+    module OuterInnerAlias2 = OuterInnerAlias
+  }
+}
+
+module Outer2Alias = Outer2
+
+module InnerNestedAlias = Outer2.Inner2.InnerNested
+
+@genType
+let testNested = (x: InnerNestedAlias.t) => x
+
+@genType
+let testInner = (x: Outer2Alias.OuterInnerAlias.innerT) => x
+
+@genType
+let testInner2 = (x: Outer2Alias.Inner2.OuterInnerAlias2.innerT) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases2.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases2.res
new file mode 100644
index 0000000000..f35c7084e4
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleAliases2.res
@@ -0,0 +1,22 @@
+@genType
+type record = {
+  x: int,
+  y: string,
+}
+
+module Outer = {
+  @genType
+  type outer = {outer: string}
+
+  module Inner = {
+    @genType
+    type inner = {inner: string}
+  }
+}
+
+module OuterAlias = Outer
+
+module InnerAlias = OuterAlias.Inner
+
+let q = 42
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleExceptionBug.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleExceptionBug.res
new file mode 100644
index 0000000000..f9b36ce235
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ModuleExceptionBug.res
@@ -0,0 +1,8 @@
+module Dep = {
+  let customDouble = foo => foo * 2
+}
+
+exception MyOtherException
+
+let ddjdj = 34
+Js.log(ddjdj)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModules.res
new file mode 100644
index 0000000000..113012a7a9
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModules.res
@@ -0,0 +1,52 @@
+@genType
+let notNested = 1
+
+module Universe = {
+  @genType
+  let theAnswer = 42
+
+  let notExported = 33
+
+  @genType
+  type nestedType = array<string>
+
+  module Nested2 = {
+    let x = 0
+
+    @genType
+    let nested2Value = 1
+
+    let y = 2
+
+    @genType
+    type nested2Type = array<array<string>>
+
+    module Nested3 = {
+      let x = 0
+      let y = 1
+      let z = 2
+      let w = 3
+
+      @genType
+      type nested3Type = array<array<array<string>>>
+
+      @genType
+      let nested3Value = "nested3Value"
+
+      @genType
+      let nested3Function = (x: nested2Type) => x
+    }
+
+    @genType
+    let nested2Function = (x: Nested3.nested3Type) => x
+  }
+
+  @genType
+  type variant =
+    | A
+    | B(string)
+
+  @genType
+  let someString = "some exported string"
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.res
new file mode 100644
index 0000000000..1a7182871c
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.res
@@ -0,0 +1,4 @@
+module Universe = {
+  let theAnswer = 42
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.resi
new file mode 100644
index 0000000000..15b4921460
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/NestedModulesInSignature.resi
@@ -0,0 +1,5 @@
+module Universe: {
+  @genType
+  let theAnswer: int
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Newsyntax.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Newsyntax.res
new file mode 100644
index 0000000000..6a109b809c
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Newsyntax.res
@@ -0,0 +1,12 @@
+let x = 34
+
+let y = 11
+
+type record = {
+  xxx: int,
+  yyy: int,
+}
+
+type variant = A | B(int)|C
+
+type record2 = {xx:int,yy:int}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Newton.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Newton.res
new file mode 100644
index 0000000000..ce8f338890
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Newton.res
@@ -0,0 +1,32 @@
+let \"-" = \"-."
+let \"+" = \"+."
+let \"*" = \"*."
+let \"/" = \"/."
+
+let newton = (~f, ~fPrimed, ~initial, ~threshold) => {
+  let current = ref(initial)
+  let iterateMore = (previous, next) => {
+    let delta = next >= previous ? next - previous : previous - next
+    current := next
+    !(delta < threshold)
+  }
+  @progress(iterateMore)
+  let rec loop = () => {
+    let previous = current.contents
+    let next = previous - f(previous) / fPrimed(previous)
+    if iterateMore(previous, next) {
+      loop()
+    } else {
+      current.contents
+    }
+  }
+  loop()
+}
+let f = x => x * x * x - 2.0 * x * x - 11.0 * x + 12.0
+
+let fPrimed = x => 3.0 * x * x - 4.0 * x - 11.0
+
+let result = newton(~f, ~fPrimed, ~initial=5.0, ~threshold=0.0003)
+
+Js.log2(result, f(result))
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Opaque.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Opaque.res
new file mode 100644
index 0000000000..a23eed7cfa
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Opaque.res
@@ -0,0 +1,12 @@
+@genType.opaque
+type opaqueFromRecords = A(Records.coord)
+
+@genType
+let noConversion = (x: opaqueFromRecords) => x
+
+@genType
+type pair = (opaqueFromRecords, opaqueFromRecords)
+
+@genType
+let testConvertNestedRecordFromOtherFile = (x: Records.business) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.res
new file mode 100644
index 0000000000..a556a52c0f
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.res
@@ -0,0 +1,30 @@
+let foo = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let bar = (~x=?, ~y, ~z=?, w) => y + w
+
+Js.log(foo(~x=3, 4))
+
+Js.log(bar(~y=3, 4))
+
+let threeArgs = (~a=1, ~b=2, ~c=3, d) => a + b + c + d
+
+Js.log(threeArgs(~a=4, ~c=7, 1))
+Js.log(threeArgs(~a=4, 1))
+
+let twoArgs = (~a=1, ~b=2, c) => a + b + c
+
+Js.log(1 |> twoArgs)
+
+let oneArg = (~a=1, ~z, b) => a + b
+
+let wrapOneArg = (~a=?, n) => oneArg(~a?, ~z=33, n)
+
+Js.log(wrapOneArg(~a=3, 44))
+
+let fourArgs = (~a=1, ~b=2, ~c=3, ~d=4, n) => a + b + c + d + n
+
+let wrapfourArgs = (~a=?, ~b=?, ~c=?, n) => fourArgs(~a?, ~b?, ~c?, n)
+
+Js.log(wrapfourArgs(~a=3, ~c=44, 44))
+Js.log(wrapfourArgs(~b=4, ~c=44, 44))
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.resi
new file mode 100644
index 0000000000..f51814f3c6
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/OptArg.resi
@@ -0,0 +1,3 @@
+let foo: (~x: int=?, ~y: int=?, ~z: int=?, int) => int
+let bar: (~x: 'a=?, ~y: int, ~z: 'b=?, int) => int
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Records.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Records.res
new file mode 100644
index 0000000000..3d7bfc2f12
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Records.res
@@ -0,0 +1,149 @@
+open Belt
+
+@genType
+type coord = {
+  x: int,
+  y: int,
+  z: option<int>,
+}
+
+@genType
+let origin = {x: 0, y: 0, z: Some(0)}
+
+@genType
+let computeArea = ({x, y, z}) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let coord2d = (x, y) => {x: x, y: y, z: None}
+
+@genType
+type person = {
+  name: string,
+  age: int,
+  address: option<string>,
+}
+
+@genType
+type business = {
+  name: string,
+  owner: option<person>,
+  address: option<string>,
+}
+
+let getOpt = (opt, default, foo) => opt->Option.mapWithDefault(default, foo)
+
+@genType
+let findAddress = (business: business): list<string> =>
+  business.address->getOpt(list{}, a => list{a})
+
+@genType
+let someBusiness = {name: "SomeBusiness", owner: None, address: None}
+
+@genType
+let findAllAddresses = (businesses: array<business>): array<string> =>
+  businesses
+  ->Array.map(business =>
+    \"@"(
+      business.address->getOpt(list{}, a => list{a}),
+      business.owner->getOpt(list{}, p => p.address->getOpt(list{}, a => list{a})),
+    )
+  )
+  ->List.fromArray
+  ->List.flatten
+  ->List.toArray
+
+@genType
+type payload<'a> = {
+  num: int,
+  payload: 'a,
+}
+
+@genType
+let getPayload = ({payload}) => payload
+
+@genType
+type record = {
+  v: int,
+  w: int,
+}
+
+@genType
+let getPayloadRecord = ({payload}): record => payload
+
+@genType
+let recordValue = {v: 1, w: 1}
+
+@genType
+let payloadValue = {num: 1, payload: recordValue}
+
+@genType
+let getPayloadRecordPlusOne = ({payload}): record => {
+  ...payload,
+  v: payload.v + 1,
+}
+
+@genType
+type business2 = {
+  name: string,
+  owner: Js.Nullable.t<person>,
+  address2: Js.Nullable.t<string>,
+}
+
+@genType
+let findAddress2 = (business: business2): list<string> =>
+  business.address2->Js.Nullable.toOption->getOpt(list{}, a => list{a})
+
+@genType
+let someBusiness2 = {
+  name: "SomeBusiness",
+  owner: Js.Nullable.null,
+  address2: Js.Nullable.null,
+}
+
+@genType
+let computeArea3 = (o: {"x": int, "y": int, "z": Js.Nullable.t<int>}) =>
+  o["x"] * o["y"] * o["z"]->Js.Nullable.toOption->Option.mapWithDefault(1, n => n)
+
+@genType
+let computeArea4 = (o: {"x": int, "y": int, "z": option<int>}) =>
+  o["x"] * o["y"] * o["z"]->Option.mapWithDefault(1, n => n)
+
+@genType
+type mix = {"a": int, "b": int, "c": option<{"name": string, "surname": string}>}
+
+@genType
+type myRec = {
+  @genType.as("type")
+  type_: string,
+}
+
+@genType
+type myObj = {"type_": string}
+
+@genType
+let testMyRec = (x: myRec) => x.type_
+
+@genType
+let testMyRec2 = (x: myRec) => x
+
+@genType
+let testMyObj = (x: myObj) => x["type_"]
+
+@genType
+let testMyObj2 = (x: myObj) => x
+
+@genType
+type myRecBsAs = {
+  @as("type")
+  type_: string,
+}
+
+@genType
+let testMyRecBsAs = (x: myRecBsAs) => x.type_
+
+@genType
+let testMyRecBsAs2 = (x: myRecBsAs) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/References.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/References.res
new file mode 100644
index 0000000000..3cdd25f15c
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/References.res
@@ -0,0 +1,48 @@
+// Test pervasive references
+
+@genType
+let create = (x: int) => ref(x)
+
+@genType
+let access = r => r.contents + 1
+
+@genType
+let update = r => r.contents = r.contents + 1
+
+// Abstract version of references: works when conversion is required.
+
+module R: {
+  @genType
+  type t<'a>
+  let get: t<'a> => 'a
+  let make: 'a => t<'a>
+  let set: (t<'a>, 'a) => unit
+} = {
+  type t<'a> = ref<'a>
+  let get = r => r.contents
+  let make = ref
+  let set = (r, v) => r.contents = v
+}
+
+@genType
+type t<'a> = R.t<'a>
+
+@genType
+let get = R.get
+
+@gentype
+let make = R.make
+
+@genType
+let set = R.set
+
+type requiresConversion = {x: int}
+
+// Careful: conversion makes a copy and destroys the reference identity.
+@genType
+let destroysRefIdentity = (x: ref<requiresConversion>) => x
+
+// Using abstract references preserves the identity.
+@genType
+let preserveRefIdentity = (x: R.t<requiresConversion>) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/RepeatedLabel.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/RepeatedLabel.res
new file mode 100644
index 0000000000..3c0b6ddeb9
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/RepeatedLabel.res
@@ -0,0 +1,15 @@
+type userData = {
+  a: bool,
+  b: int,
+}
+
+type tabState = {
+  a: bool,
+  b: int,
+  f: string,
+}
+
+let userData = ({a, b}): userData => {a: a, b: b}
+
+Js.log(userData)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/RequireCond.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/RequireCond.res
new file mode 100644
index 0000000000..8c3638e16f
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/RequireCond.res
@@ -0,0 +1,20 @@
+@module
+@deprecated(
+  "Please use this syntax to guarantee safe usage: [%requireCond(`gk, \"gk_name\", ConditionalModule)]"
+)
+external make: (
+  @string [@as("qe.bool") #qeBool | @as("gk") #gk],
+  string,
+  string,
+) => Js.Nullable.t<'a> = "requireCond"
+
+@module
+@deprecated(
+  "Please use this syntax to guarantee safe usage: [%requireCond(`gk, \"gk_name\", {\"true\": ModuleA, \"false\": ModuleB})]"
+)
+external either: (
+  @string [@as("qe.bool") #qeBool | @as("gk") #gk],
+  string,
+  {"true": string, "false": string},
+) => 'b = "requireCond"
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Shadow.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Shadow.res
new file mode 100644
index 0000000000..7f83e62512
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Shadow.res
@@ -0,0 +1,13 @@
+@genType
+let test = () => 3
+
+@genType
+let test = () => "a"
+
+module M = {
+  @genType
+  let test = () => 3
+
+  let test = () => "a"
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestDeadExn.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestDeadExn.res
new file mode 100644
index 0000000000..7810b9ccf3
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestDeadExn.res
@@ -0,0 +1,2 @@
+Js.log(DeadExn.Etoplevel)
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestEmitInnerModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestEmitInnerModules.res
new file mode 100644
index 0000000000..345d6476d2
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestEmitInnerModules.res
@@ -0,0 +1,16 @@
+module Inner = {
+  @genType
+  let x = 34
+  @genType
+  let y = "hello"
+}
+
+module Outer = {
+  module Medium = {
+    module Inner = {
+      @genType
+      let y = 44
+    }
+  }
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestFirstClassModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestFirstClassModules.res
new file mode 100644
index 0000000000..87abb8d1a8
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestFirstClassModules.res
@@ -0,0 +1,31 @@
+@genType
+let convert = (x: FirstClassModules.firstClassModule) => x
+
+@genType
+let convertInterface = (x: FirstClassModulesInterface.firstClassModule) => x
+
+@genType
+let convertRecord = (x: FirstClassModulesInterface.record) => x
+
+module type MT = {
+  type outer
+  let out: outer => outer
+
+  module Inner: {
+    type inner
+    let inn: inner => inner
+  }
+}
+
+@genType
+type firstClassModuleWithTypeEquations<'i, 'o> = module(MT with
+  type Inner.inner = 'i
+  and type outer = 'o
+)
+
+@genType
+let convertFirstClassModuleWithTypeEquations = (
+  type o i,
+  x: module(MT with type Inner.inner = i and type outer = o),
+) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImmutableArray.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImmutableArray.res
new file mode 100644
index 0000000000..03176dad17
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImmutableArray.res
@@ -0,0 +1,21 @@
+@genType
+let testImmutableArrayGet = arr => {
+  open ImmutableArray
+  arr[3]
+}
+
+/*
+   type error
+   let testImmutableArraySet = arr => ImmutableArray.(arr[3] = 4);
+ */
+
+let testBeltArrayGet = arr => {
+  open Belt
+  arr[3]
+}
+
+let testBeltArraySet = arr => {
+  open Belt
+  arr[3] = 4
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImport.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImport.res
new file mode 100644
index 0000000000..41d3371654
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestImport.res
@@ -0,0 +1,30 @@
+@genType.import((
+  "./exportNestedValues",
+  "TopLevelClass.MiddleLevelElements.stuff.InnerStuff.innerStuffContents",
+))
+external innerStuffContents: {"x": int} = "innerStuffContents"
+
+@genType.import((
+  "./exportNestedValues",
+  "TopLevelClass.MiddleLevelElements.stuff.InnerStuff.innerStuffContents",
+))
+external innerStuffContentsAsEmptyObject: {.} = "innerStuffContentsAsEmptyObject"
+
+let innerStuffContents = innerStuffContents
+
+@genType.import(("./exportNestedValues", "ValueStartingWithUpperCaseLetter"))
+external valueStartingWithUpperCaseLetter: string = "valueStartingWithUpperCaseLetter"
+
+@genType.import(("./exportNestedValues", "default"))
+external defaultValue: int = "defaultValue"
+
+@genType
+type message = {text: string}
+
+@genType.import(("./MyBanner", "TopLevelClass.MiddleLevelElements.MyBannerInternal"))
+external make: (~show: bool, ~message: option<message>=?, 'a) => React.element = "make"
+
+let make = make
+
+@genType.import(("./exportNestedValues", "default"))
+external defaultValue2: int = "defaultValue2"
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestInnedModuleTypes.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestInnedModuleTypes.res
new file mode 100644
index 0000000000..67e9669961
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestInnedModuleTypes.res
@@ -0,0 +1 @@
+let _ = InnerModuleTypes.I.Foo
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestModuleAliases.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestModuleAliases.res
new file mode 100644
index 0000000000..324df34996
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestModuleAliases.res
@@ -0,0 +1,42 @@
+module OtherFile = ModuleAliases2
+module OtherFileAlias = OtherFile
+
+@genType
+type record = OtherFile.record
+
+@genType
+type record2 = OtherFileAlias.record
+
+module OuterAlias = OtherFile.Outer
+
+@genType
+type outer = OtherFileAlias.Outer.outer
+
+@genType
+type outer2 = OuterAlias.outer
+
+module OtherFile1 = OtherFile
+module Outer2 = OtherFile1.Outer
+module Inner2 = Outer2.Inner
+
+@genType
+type my2 = Inner2.inner
+
+@genType
+type inner1 = OtherFile.InnerAlias.inner
+
+@genType
+type inner2 = OtherFile.Outer.Inner.inner
+
+@genType
+let testInner1 = (x: inner1) => x
+
+@genType
+let testInner1Expanded = (x: OtherFile.InnerAlias.inner) => x
+
+@genType
+let testInner2 = (x: inner2) => x
+
+@genType
+let testInner2Expanded = (x: OtherFile.Outer.Inner.inner) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestOptArg.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestOptArg.res
new file mode 100644
index 0000000000..ef38c7c7ec
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestOptArg.res
@@ -0,0 +1,16 @@
+Js.log(OptArg.bar(~z=3, ~y=3, 4))
+
+let foo = (~x=3, y) => x + y
+
+let bar = () => foo(~x=12, 3)
+
+Js.log(bar)
+
+let notSuppressesOptArgs = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let _ = notSuppressesOptArgs(3)
+
+@live
+let liveSuppressesOptArgs = (~x=1, ~y=2, ~z=3, w) => x + y + z + w
+
+let _ = liveSuppressesOptArgs(~x=3, 3)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TestPromise.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestPromise.res
new file mode 100644
index 0000000000..0bd3ddd60a
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TestPromise.res
@@ -0,0 +1,15 @@
+@genType
+type promise<'a> = Js.Promise.t<'a>
+
+@genType
+type fromPayload = {
+  x: int,
+  s: string,
+}
+
+@genType
+type toPayload = {result: string}
+
+@genType
+let convert = Js.Promise.then_(({s}) => Js.Promise.resolve({result: s}))
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ToSuppress.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ToSuppress.res
new file mode 100644
index 0000000000..e21f46451b
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ToSuppress.res
@@ -0,0 +1 @@
+let toSuppress = 0
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType1.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType1.res
new file mode 100644
index 0000000000..617a7e5bd4
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType1.res
@@ -0,0 +1,6 @@
+@genType
+let convert = (x: TransitiveType2.t2) => x
+
+@genType
+let convertAlias = (x: TransitiveType2.t2Alias) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType2.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType2.res
new file mode 100644
index 0000000000..694ac8da31
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType2.res
@@ -0,0 +1,8 @@
+@genType
+type t2 = option<TransitiveType3.t3>
+
+@genType
+type t2Alias = t2
+
+let convertT2 = (x: t2) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType3.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType3.res
new file mode 100644
index 0000000000..c7e5a16226
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TransitiveType3.res
@@ -0,0 +1,9 @@
+@genType
+type t3 = {
+  i: int,
+  s: string,
+}
+
+@genType
+let convertT3 = (x: t3) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Tuples.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Tuples.res
new file mode 100644
index 0000000000..d2194a49d9
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Tuples.res
@@ -0,0 +1,50 @@
+open Belt
+
+@genType
+let testTuple = ((a, b)) => a + b
+
+@genType
+type coord = (int, int, option<int>)
+
+@genType
+let origin = (0, 0, Some(0))
+
+@genType
+let computeArea = ((x, y, z)) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let computeAreaWithIdent = ((x, y, z): coord) => {
+  open Option
+  x * y * z->mapWithDefault(1, n => n)
+}
+
+@genType
+let computeAreaNoConverters = ((x: int, y: int)) => x * y
+
+@genType
+let coord2d = (x, y) => (x, y, None)
+
+@genType
+type coord2 = (int, int, Js.Nullable.t<int>)
+
+@genType
+type person = {
+  name: string,
+  age: int,
+}
+
+@genType
+type couple = (person, person)
+
+@genType
+let getFirstName = ((first, _second): couple) => first.name
+
+@genType
+let marry = (first, second): couple => (first, second)
+
+@genType
+let changeSecondAge = ((first, second): couple): couple => (first, {...second, age: second.age + 1})
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams1.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams1.res
new file mode 100644
index 0000000000..71331ca9f6
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams1.res
@@ -0,0 +1,5 @@
+@gentype
+type ocaml_array<'a> = array<'a>
+
+let exportSomething = 10
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams2.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams2.res
new file mode 100644
index 0000000000..ed998b3077
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams2.res
@@ -0,0 +1,11 @@
+@genType
+type item = {id: int}
+
+@genType
+type items = TypeParams1.ocaml_array<item>
+
+@genType
+type items2 = array<item>
+
+let exportSomething = 10
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams3.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams3.res
new file mode 100644
index 0000000000..173eea5d51
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TypeParams3.res
@@ -0,0 +1,6 @@
+@genType
+let test = (x: TypeParams2.items) => x
+
+@genType
+let test2 = (x: TypeParams2.items2) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Types.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Types.res
new file mode 100644
index 0000000000..3ce309c105
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Types.res
@@ -0,0 +1,168 @@
+@genType
+type t = int
+
+@genType
+let someIntList = list{1, 2, 3}
+
+@genType
+let map = List.map
+
+@genType
+type typeWithVars<'x, 'y, 'z> =
+  | A('x, 'y)
+  | B('z)
+
+@genType
+type rec tree = {"label": string, "left": option<tree>, "right": option<tree>}
+
+/*
+ * A tree is a recursive type which does not require any conversion (JS object).
+ * All is well.
+ */
+@genType
+let rec swap = (tree: tree): tree =>
+  {
+    "label": tree["label"],
+    "left": tree["right"]->Belt.Option.map(swap),
+    "right": tree["left"]->Belt.Option.map(swap),
+  }
+
+@genType
+type rec selfRecursive = {self: selfRecursive}
+
+@genType
+type rec mutuallyRecursiveA = {b: mutuallyRecursiveB}
+and mutuallyRecursiveB = {a: mutuallyRecursiveA}
+
+/*
+ * This is a recursive type which requires conversion (a record).
+ * Only a shallow conversion of the top-level element is performed.
+ */
+@genType
+let selfRecursiveConverter = ({self}) => self
+
+/*
+ * This is a mutually recursive type which requires conversion (a record).
+ * Only a shallow conversion of the two top-level elements is performed.
+ */
+@genType
+let mutuallyRecursiveConverter = ({b}) => b
+
+@genType
+let testFunctionOnOptionsAsArgument = (a: option<'a>, foo) => foo(a)
+
+@genType.opaque
+type opaqueVariant =
+  | A
+  | B
+
+@genType
+let stringT: String.t = "a"
+
+@genType
+let jsStringT: Js.String.t = "a"
+
+@genType
+let jsString2T: Js.String2.t = "a"
+
+@genType
+type twice<'a> = ('a, 'a)
+
+@gentype
+type genTypeMispelled = int
+
+@genType
+type dictString = Js.Dict.t<string>
+
+@genType
+let jsonStringify = Js.Json.stringify
+
+@genType
+type nullOrString = Js.Null.t<string>
+
+@genType
+type nullOrString2 = Js.null<string>
+
+type record = {
+  i: int,
+  s: string,
+}
+
+@genType
+let testConvertNull = (x: Js.Null.t<record>) => x
+
+@genType
+type decorator<'a, 'b> = 'a => 'b constraint 'a = int constraint 'b = _ => _
+
+/* Bucklescript's marshaling rules. */
+@genType
+type marshalFields = {
+  "_rec": string,
+  "_switch": string,
+  "switch": string,
+  "__": string,
+  "___": string,
+  "foo__": string,
+  "_foo__": string,
+  "_Uppercase": string,
+  "_Uppercase__": string,
+}
+
+@genType
+let testMarshalFields: marshalFields = {
+  "_rec": "rec",
+  "_switch" /* reason keywords are not recognized */: "_switch",
+  "switch": "switch",
+  "__": "__",
+  "___": "_",
+  "foo__": "foo",
+  "_foo__": "_foo",
+  "_Uppercase": "Uppercase",
+  "_Uppercase__": "_Uppercase",
+}
+
+@genType
+type marshalMutableField = {@set "_match": int}
+
+@genType
+let setMatch = (x: marshalMutableField) => x["_match"] = 34
+
+type ocaml_array<'a> = array<'a>
+
+// This should be considered annotated automatically.
+type someRecord = {id: int}
+
+type instantiateTypeParameter = ocaml_array<someRecord>
+
+@genType
+let testInstantiateTypeParameter = (x: instantiateTypeParameter) => x
+
+@genType @genType.as("Vector")
+type vector<'a> = ('a, 'a)
+
+@genType
+type date = Js.Date.t
+
+@genType
+let currentTime = Js.Date.make()
+
+@genType
+type i64A = Int64.t
+
+@genType
+type i64B = int64
+
+@genType
+let i64Const: i64B = 34L
+
+@genType
+let optFunction = Some(() => 3)
+
+module ObjectId: {
+  @genType
+  type t = int
+} = {
+  type t = int
+  let x = 1
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Unboxed.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unboxed.res
new file mode 100644
index 0000000000..208a6456d3
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unboxed.res
@@ -0,0 +1,18 @@
+@genType @ocaml.unboxed
+type v1 = A(int)
+
+@genType @unboxed
+type v2 = A(int)
+
+@genType
+let testV1 = (x: v1) => x
+
+@genType @unboxed
+type r1 = {x: int}
+
+@genType @ocaml.unboxed
+type r2 = B({g: string})
+
+@genType
+let r2Test = (x: r2) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Uncurried.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Uncurried.res
new file mode 100644
index 0000000000..cf85f9d9cd
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Uncurried.res
@@ -0,0 +1,57 @@
+@genType
+type u0 = (. unit) => string
+
+@genType
+type u1 = (. int) => string
+
+@genType
+type u2 = (. int, string) => string
+
+@genType
+type u3 = (. int, string, int) => string
+
+@genType
+let uncurried0 = (. ()) => ""
+
+@genType
+let uncurried1 = (. x) => x |> string_of_int
+
+@genType
+let uncurried2 = (. x, y) => (x |> string_of_int) ++ y
+
+@genType
+let uncurried3 = (. x, y, z) => (x |> string_of_int) ++ (y ++ (z |> string_of_int))
+
+@genType
+let curried3 = (x, y, z) => (x |> string_of_int) ++ (y ++ (z |> string_of_int))
+
+@genType
+let callback = cb => cb() |> string_of_int
+
+type auth = {login: unit => string}
+type authU = {loginU: (. unit) => string}
+
+@genType
+let callback2 = auth => auth.login()
+
+@genType
+let callback2U = auth => auth.loginU(.)
+
+@genType
+let sumU = (. n, m) => Js.log4("sumU 2nd arg", m, "result", n + m)
+
+@genType
+let sumU2 = (. n, . m) => Js.log4("sumU2 2nd arg", m, "result", n + m)
+
+@genType
+let sumCurried = n => {
+  Js.log2("sumCurried 1st arg", n)
+  m => Js.log4("sumCurried 2nd arg", m, "result", n + m)
+}
+
+@genType
+let sumLblCurried = (s: string, ~n) => {
+  Js.log3(s, "sumLblCurried 1st arg", n)
+  (~m) => Js.log4("sumLblCurried 2nd arg", m, "result", n + m)
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res
new file mode 100644
index 0000000000..a2e5698e65
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res
@@ -0,0 +1,40 @@
+// Exmple of several DCE checks operating in unison
+
+type break =
+  | IfNeed
+  | Never
+  | Always
+
+type t = {
+  break: break,
+  doc: string,
+}
+
+type rec stack =
+  | Empty
+  | Cons(t, stack)
+
+let group = (~break=IfNeed, doc) => {break: break, doc: doc}
+
+let rec fits = (w, stack) =>
+  switch stack {
+  | _ when w < 0 => false
+  | Empty => true
+  | Cons({doc}, stack) => fits(w - String.length(doc), stack)
+  }
+
+let rec toString = (~width, stack) =>
+  switch stack {
+  | Cons({break, doc}, stack) =>
+    switch break {
+    | IfNeed => (fits(width, stack) ? "fits " : "no ") ++ (stack |> toString(~width=width - 1))
+    | Never => "never " ++ (doc ++ (stack |> toString(~width=width - 1)))
+    | Always => "always " ++ (doc ++ (stack |> toString(~width=width - 1)))
+    }
+  | Empty => ""
+  }
+
+toString(~width=80, Empty)
+toString(~width=80, Cons(group(~break=Never, "abc"), Empty))
+toString(~width=80, Cons(group(~break=Always, "d"), Empty))
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/UseImportJsValue.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/UseImportJsValue.res
new file mode 100644
index 0000000000..3cc2e2c883
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/UseImportJsValue.res
@@ -0,0 +1,6 @@
+@genType
+let useGetProp = (x: ImportJsValue.AbsoluteValue.t) => x->ImportJsValue.AbsoluteValue.getProp + 1
+
+@genType
+let useTypeImportedInOtherModule = (x: ImportJsValue.stringFunction) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Variants.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Variants.res
new file mode 100644
index 0000000000..241ff945ea
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Variants.res
@@ -0,0 +1,119 @@
+@genType
+type weekday = [
+  | #monday
+  | #tuesday
+  | #wednesday
+  | #thursday
+  | #friday
+  | #saturday
+  | #sunday
+]
+
+@genType
+let isWeekend = (x: weekday) =>
+  switch x {
+  | #saturday
+  | #sunday => true
+  | _ => false
+  }
+
+@genType
+let monday = #monday
+@genType
+let saturday = #saturday
+@genType
+let sunday = #sunday
+
+@genType
+let onlySunday = (_: [#sunday]) => ()
+
+@genType
+let swap = x =>
+  switch x {
+  | #sunday => #saturday
+  | #saturday => #sunday
+  }
+
+@genType
+type testGenTypeAs = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("42") #fortytwo
+]
+
+@genType
+let testConvert = (x: testGenTypeAs) => x
+
+@genType
+let fortytwoOK: testGenTypeAs = #fortytwo
+
+/* Exporting this is BAD: type inference means it's not mapped to "42" */
+@genType
+let fortytwoBAD = #fortytwo
+
+@genType
+type testGenTypeAs2 = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("42") #fortytwo
+]
+
+/* Since testGenTypeAs2 is the same type as testGenTypeAs1,
+ share the conversion map. */
+@genType
+let testConvert2 = (x: testGenTypeAs2) => x
+
+@genType
+type testGenTypeAs3 = [
+  | @genType.as("type") #type_
+  | @genType.as("module") #module_
+  | @genType.as("THIS IS DIFFERENT") #fortytwo
+]
+
+/* Since testGenTypeAs3 has a different representation:
+ use a new conversion map. */
+@genType
+let testConvert3 = (x: testGenTypeAs3) => x
+
+/* This converts between testGenTypeAs2 and testGenTypeAs3 */
+@genType
+let testConvert2to3 = (x: testGenTypeAs2): testGenTypeAs3 => x
+
+@genType
+type x1 = [#x | @genType.as("same") #x1]
+
+@genType
+type x2 = [#x | @genType.as("same") #x2]
+
+@genType
+let id1 = (x: x1) => x
+
+@genType
+let id2 = (x: x2) => x
+
+@genType @genType.as("type")
+type type_ = | @genType.as("type") Type
+
+@genType
+let polyWithOpt = foo => foo === "bar" ? None : foo !== "baz" ? Some(#One(foo)) : Some(#Two(1))
+
+@genType
+type result1<'a, 'b> =
+  | Ok('a)
+  | Error('b)
+
+@genType
+type result2<'a, 'b> = result<'a, 'b>
+
+@genType
+type result3<'a, 'b> = Belt.Result.t<'a, 'b>
+
+@genType
+let restResult1 = (x: result1<int, string>) => x
+
+@genType
+let restResult2 = (x: result2<int, string>) => x
+
+@genType
+let restResult3 = (x: result3<int, string>) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/VariantsWithPayload.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/VariantsWithPayload.res
new file mode 100644
index 0000000000..4da94ed578
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/VariantsWithPayload.res
@@ -0,0 +1,100 @@
+type payload = {
+  x: int,
+  y: option<string>,
+}
+
+type withPayload = [
+  | #a
+  | @genType.as("bRenamed") #b
+  | @genType.as(true) #True
+  | @genType.as(20) #Twenty
+  | @genType.as(0.5) #Half
+  | #c(payload)
+]
+
+@genType
+let testWithPayload = (x: withPayload) => x
+
+@genType
+let printVariantWithPayload = (x: withPayload) =>
+  switch x {
+  | #a => Js.log("printVariantWithPayload: a")
+  | #b => Js.log("printVariantWithPayload: b")
+  | #True => Js.log("printVariantWithPayload: True")
+  | #Twenty => Js.log("printVariantWithPayload: Twenty")
+  | #Half => Js.log("printVariantWithPayload: Half")
+  | #c(payload) => Js.log4("printVariantWithPayload x:", payload.x, "y:", payload.y)
+  }
+
+@genType
+type manyPayloads = [
+  | @genType.as("oneRenamed") #one(int)
+  | @genType.as(2) #two(string, string)
+  | #three(payload)
+]
+
+@genType
+let testManyPayloads = (x: manyPayloads) => x
+
+@genType
+let printManyPayloads = (x: manyPayloads) =>
+  switch x {
+  | #one(n) => Js.log2("printManyPayloads one:", n)
+  | #two(s1, s2) => Js.log3("printManyPayloads two:", s1, s2)
+  | #three(payload) => Js.log4("printManyPayloads x:", payload.x, "y:", payload.y)
+  }
+
+@genType
+type simpleVariant =
+  | A
+  | B
+  | C
+
+@genType
+let testSimpleVariant = (x: simpleVariant) => x
+
+@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
+
+@genType
+let printVariantWithPayloads = x =>
+  switch x {
+  | A => Js.log2("printVariantWithPayloads", "A")
+  | B(x) => Js.log2("printVariantWithPayloads", "B(" ++ (string_of_int(x) ++ ")"))
+  | C(x, y) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "C(" ++ (string_of_int(x) ++ (", " ++ (string_of_int(y) ++ ")"))),
+    )
+  | D((x, y)) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "D((" ++ (string_of_int(x) ++ (", " ++ (string_of_int(y) ++ "))"))),
+    )
+  | E(x, s, y) =>
+    Js.log2(
+      "printVariantWithPayloads",
+      "E(" ++ (string_of_int(x) ++ (", " ++ (s ++ (", " ++ (string_of_int(y) ++ ")"))))),
+    )
+  }
+
+@genType
+type variant1Int = R(int)
+
+@genType
+let testVariant1Int = (x: variant1Int) => x
+
+@genType
+type variant1Object = R(payload)
+
+@genType
+let testVariant1Object = (x: variant1Object) => x
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Arr.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Arr.res
new file mode 100644
index 0000000000..c9f473c818
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Arr.res
@@ -0,0 +1,10 @@
+module B = Belt
+module Array = B.Array
+
+module MM = {
+  let ff = a =>
+    switch a[3] {
+    | _ => 11
+    }
+}
+
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BeltTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BeltTest.res
new file mode 100644
index 0000000000..4205f3e0b7
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BeltTest.res
@@ -0,0 +1,31 @@
+open Belt.List
+
+@raises(Not_found)
+let lstHead1 = l => l->Belt.List.headExn
+
+@raises(Not_found)
+let lstHead2 = l => l->Belt_List.headExn
+
+@raises(Not_found)
+let mapGetExn1 = (s, k) => s->Belt.Map.Int.getExn(k)
+
+@raises(Not_found)
+let mapGetExn2 = (s, k) => s->Belt_Map.Int.getExn(k)
+
+@raises(Not_found)
+let mapGetExn3 = (s, k) => s->Belt_MapInt.getExn(k)
+
+@raises(Not_found)
+let mapGetExn4 = (s, k) => s->Belt.Map.String.getExn(k)
+
+@raises(Not_found)
+let mapGetExn5 = (s, k) => s->Belt_Map.String.getExn(k)
+
+@raises(Not_found)
+let mapGetExn6 = (s, k) => s->Belt_MapString.getExn(k)
+
+@raises(Not_found)
+let mapGetExn7 = (s, k) => s->Belt.Map.getExn(k)
+
+@raises(Not_found)
+let mapGetExn8 = (s, k) => s->Belt_Map.getExn(k)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BsJson.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BsJson.res
new file mode 100644
index 0000000000..ad892c8e7b
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/BsJson.res
@@ -0,0 +1,5 @@
+@raise(DecodeError)
+let testBsJson = x => Json_decode.string(x)
+
+@raise(DecodeError)
+let testBsJson2 = x => Json.Decode.string(x)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res
new file mode 100644
index 0000000000..0ccfe96c69
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Exn.res
@@ -0,0 +1,196 @@
+let raises = () => raise(Not_found)
+
+let catches1 = try () catch {
+| Not_found => ()
+}
+
+let catches2 = switch () {
+| _ => ()
+| exception Not_found => ()
+}
+
+let raiseAndCatch = try raise(Not_found) catch {
+| _ => ()
+}
+
+@raises(Not_found)
+let raisesWithAnnotaion = () => raise(Not_found)
+
+let callsRaiseWithAnnotation = raisesWithAnnotaion()
+
+@raises(A)
+let callsRaiseWithAnnotationAndIsAnnotated = raisesWithAnnotaion()
+
+let z = List.hd(list{})
+
+let incompleteMatch = l =>
+  switch l {
+  | list{} => ()
+  }
+
+exception A
+exception B
+
+let twoRaises = (x, y) => {
+  if x {
+    raise(A)
+  }
+  if y {
+    raise(B)
+  }
+}
+
+let sequencing = () => {
+  raise(A)
+  try raise(B) catch {
+  | _ => ()
+  }
+}
+
+let wrongCatch = () =>
+  try raise(B) catch {
+  | A => ()
+  }
+
+exception C
+let wrongCatch2 = b =>
+  switch b ? raise(B) : raise(C) {
+  | exception A => ()
+  | exception B => ()
+  | list{} => ()
+  }
+
+@raises([A, B, C])
+let raise2Annotate3 = (x, y) => {
+  if x {
+    raise(A)
+  }
+  if y {
+    raise(B)
+  }
+}
+
+exception Error(string, string, int)
+
+let parse_json_from_file = s => {
+  switch 34 {
+  | exception Error(p1, p2, e) =>
+    raise(Error(p1, p2, e))
+  | v =>
+    v
+  }
+}
+
+let reRaise = () =>
+  switch raise(A) {
+  | exception A => raise(B)
+  | _ => 11
+  }
+
+let switchWithCatchAll = switch raise(A) {
+| exception _ => 1
+| _ => 2
+}
+
+let raiseInInternalLet = b => {
+  let a = b ? raise(A) : 22
+  a + 34
+}
+
+let indirectCall = () => () |> raisesWithAnnotaion
+
+@raises(Invalid_argument)
+let array = a => a[2]
+
+let id = x => x
+
+let tryChar = v => {
+  try id(Char.chr(v)) |> ignore catch {
+  | _ => ()
+  }
+  42
+}
+
+module StringHash = Hashtbl.Make({
+  include String
+  let hash = Hashtbl.hash
+})
+
+let specializedHash = tbl => StringHash.find(tbl, "abc")
+
+@raises(Not_found)
+let genericHash = tbl => Hashtbl.find(tbl, "abc")
+
+@raises(Not_found)
+let raiseAtAt = () => \"@@"(raise, Not_found)
+
+@raises(Not_found)
+let raisePipe = Not_found |> raise
+
+@raises(Not_found)
+let raiseArrow = Not_found->raise
+
+@raises(Js.Exn.Error)
+let bar = () => Js.Json.parseExn("!!!")
+
+let foo = () =>
+  try Js.Json.parseExn("!!!") catch {
+  | Js.Exn.Error(_) => Js.Json.null
+  }
+
+@raises(Invalid_argument)
+let stringMake1 = String.make(12, ' ')
+
+let stringMake2 = (@doesNotRaise String.make)(12, ' ')
+
+let stringMake3 = @doesNotRaise String.make(12, ' ')
+
+let severalCases = cases =>
+  switch cases {
+  | "one" => failwith("one")
+  | "two" => failwith("two")
+  | "three" => failwith("three")
+  | _ => ()
+  }
+
+@raises(genericException)
+let genericRaiseIsNotSupported = exn => raise(exn)
+
+let redundant = (@doesNotRaise String.uncapitalize_ascii)("abc")
+
+let redundant2 = @doesNotRaise String.uncapitalize_ascii("abc")
+
+let redundant3 = @doesNotRaise (@doesNotRaise String.uncapitalize_ascii)("abc")
+
+let redundant4 = () => {
+  let _ = String.uncapitalize_ascii("abc")
+  let _ = @doesNotRaise String.uncapitalize_ascii("abc")
+  let _ = String.uncapitalize_ascii("abc")
+  let _ = String.uncapitalize_ascii(@doesNotRaise "abc")
+}
+
+@raises(exit)
+let exits = () => exit(1)
+
+@raises(Invalid_argument)
+let redundantAnnotation = () => ()
+
+let _x = raise(A)
+
+let _ = raise(A)
+
+let () = raise(A)
+
+raise(Not_found)
+
+true ? exits() : ()
+
+// Examples with pipe
+
+let onFunction = () => (@doesNotRaise Belt.Array.getExn)([], 0)
+
+let onResult = () => @doesNotRaise Belt.Array.getExn([], 0)
+
+let onFunctionPipe = () => []->(@doesNotRaise Belt.Array.getExn)(0)
+
+let onResultPipeWrong = () => @doesNotRaise []->Belt.Array.getExn(0)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnA.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnA.res
new file mode 100644
index 0000000000..9cf43951d1
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnA.res
@@ -0,0 +1 @@
+let bar = () => ExnB.foo()
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnB.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnB.res
new file mode 100644
index 0000000000..2a50ef4652
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExnB.res
@@ -0,0 +1,2 @@
+@raises(Not_found)
+let foo = () => raise(Not_found)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExportWithRename.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExportWithRename.res
new file mode 100644
index 0000000000..b8b4865b2a
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/ExportWithRename.res
@@ -0,0 +1,2 @@
+@genType("ExportWithRename") @react.component
+let make = (~s) => React.string(s)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/InnerModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/InnerModules.res
new file mode 100644
index 0000000000..7c7f9dc1a0
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/InnerModules.res
@@ -0,0 +1,27 @@
+@raises(exit)
+let wrapExitTop = x => exit(x)
+
+module M1 = {
+  @raises(exit)
+  let wrapExitM1 = x => exit(x)
+
+  @raises(exit)
+  let callLocally = x => wrapExitM1(x)
+
+  @raises(exit)
+  let callTop = x => wrapExitTop(x)
+
+  module M2 = {
+    @raises(exit)
+    let wrapExitM2 = x => exit(x)
+
+    @raises(exit)
+    let callM1 = x => wrapExitM1(x)
+
+    @raises(exit)
+    let callTop = x => wrapExitTop(x)
+  }
+}
+
+@raises(exit)
+let callM1 = x => M1.wrapExitM1(x)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestInnerModules.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestInnerModules.res
new file mode 100644
index 0000000000..6b1e88516b
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestInnerModules.res
@@ -0,0 +1,8 @@
+@raises(exit)
+let testTop = x => InnerModules.wrapExitTop(x)
+
+@raises(exit)
+let testM1 = x => InnerModules.M1.wrapExitM1(x)
+
+@raises(exit)
+let testM2 = x => InnerModules.M1.M2.wrapExitM2(x)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestYojson.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestYojson.res
new file mode 100644
index 0000000000..4bb1e410a1
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/TestYojson.res
@@ -0,0 +1,17 @@
+@raises(Yojson.Json_error)
+let foo = x => Yojson.Basic.from_string(x)
+
+let bar = (str, json) =>
+  switch {
+    open Yojson.Basic.Util
+    json |> member(str)
+  } {
+  | j => j
+  | exception Yojson.Basic.Util.Type_error("a", d) when d == json => json
+  }
+
+@raises(Yojson.Basic.Util.Type_error)
+let toString = x => Yojson.Basic.Util.to_string(x)
+
+@raises(Yojson.Basic.Util.Type_error)
+let toInt = x => Yojson.Basic.Util.to_int(x)
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Yojson.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Yojson.res
new file mode 100644
index 0000000000..40758bda19
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exception/Yojson.res
@@ -0,0 +1,19 @@
+exception Json_error(string)
+
+module Basic = {
+  type t
+
+  @raises(Json_error)
+  let from_string: string => t = _ => raise(Json_error("Basic.from_string"))
+
+  module Util = {
+    exception Type_error(string, t)
+
+    @raises(Type_error)
+    let member: (string, t) => t = (_s, j) => raise(Type_error("Basic.Util.member", j))
+
+    let to_int: t => int = _ => 34
+
+    let to_string: t => string = _ => ""
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/exportNestedValues.js b/tests/analysis_tests/tests-reanalyze/deadcode/src/exportNestedValues.js
new file mode 100644
index 0000000000..4c1f469e26
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/exportNestedValues.js
@@ -0,0 +1,17 @@
+/* @flow strict */
+
+class InnerClass {
+  static InnerStuff = {
+    innerStuffContents: { x: 34 }
+  };
+}
+
+export class TopLevelClass {
+  static MiddleLevelElements = {
+    stuff: InnerClass
+  };
+}
+
+export const ValueStartingWithUpperCaseLetter = "ValueStartingWithUpperCaseLetter";
+
+export default 42;
\ No newline at end of file
diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/test.sh b/tests/analysis_tests/tests-reanalyze/deadcode/test.sh
new file mode 100755
index 0000000000..ea28ad7789
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/deadcode/test.sh
@@ -0,0 +1,39 @@
+output="expected/deadcode.txt"
+if [ "$RUNNER_OS" == "Windows" ]; then
+  exclude_dirs="src\exception"
+  suppress="src\ToSuppress.res"
+else
+  exclude_dirs="src/exception"
+  suppress="src/ToSuppress.res"
+fi
+dune exec rescript-editor-analysis -- reanalyze -config -debug -ci -exclude-paths $exclude_dirs -live-names globallyLive1 -live-names globallyLive2,globallyLive3 -suppress $suppress > $output
+# CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+if [ "$RUNNER_OS" == "Windows" ]; then
+  perl -pi -e 's/\r\n/\n/g' -- $output
+fi
+
+output="expected/exception.txt"
+if [ "$RUNNER_OS" == "Windows" ]; then
+  unsuppress_dirs="src\exception"
+else
+  unsuppress_dirs="src/exception"
+fi
+dune exec rescript-editor-analysis -- reanalyze -exception -ci -suppress src -unsuppress $unsuppress_dirs > $output
+# CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+if [ "$RUNNER_OS" == "Windows" ]; then
+  perl -pi -e 's/\r\n/\n/g' -- $output
+fi
+
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff expected
+  exit 1
+fi
diff --git a/tests/analysis_tests/tests-reanalyze/termination/.gitignore b/tests/analysis_tests/tests-reanalyze/termination/.gitignore
new file mode 100644
index 0000000000..1ccc52a7fb
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/lib
\ No newline at end of file
diff --git a/tests/analysis_tests/tests-reanalyze/termination/.watchmanconfig b/tests/analysis_tests/tests-reanalyze/termination/.watchmanconfig
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests-reanalyze/termination/Makefile b/tests/analysis_tests/tests-reanalyze/termination/Makefile
new file mode 100644
index 0000000000..fc76aaa370
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript
+
+test: build node_modules/.bin/rescript
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := build
+
+.PHONY: build clean test
diff --git a/tests/analysis_tests/tests-reanalyze/termination/README.md b/tests/analysis_tests/tests-reanalyze/termination/README.md
new file mode 100644
index 0000000000..5f6bc67aa7
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/README.md
@@ -0,0 +1,198 @@
+# Termination Analysis
+
+```
+Concrete Programs
+x y l f                   Variables
+arg := e | ~l=e           Function Argument
+args := args, ..., argn   Function Arguments
+e := x | e(args)          Expressions
+k := <l1:k1, ..., ln:kn>  Kind
+P := f1 = e1 ... fn = en  Program
+E := x1:k1, ..., xn:kn    Environment
+
+The variables xi in E must be distinct from labels in kj and in args.
+  E |- e  Well Formedness Relation
+  For fi = ei in P, and fi:ki in E, check E,ki |- ei.
+
+  E |- x if x:k not in E  No Escape
+
+  E |- x   E |- e
+  ---------------
+     E |- ~x=e
+
+  E |- x   E |- args
+  ------------------
+     E |- e(args)
+
+           E(x) = <l1:k1, ... ln:kn>
+        E(l1) = E(x1) ... E(ln) = E(xn)
+            E |- arg1 ... E |- argk
+  -------------------------------------------
+  E |- x(~l1:x1, ... ~ln:xn, arg1, ..., argk)
+
+Abstract Programs
+arg := l:x                Function Argument
+args := arg1, ...., argn  Function Arguments
+C ::=                     Commmand
+      x<args>             Call
+      Some | None         Optional Value
+      switch x<args> {    Switch on Optionals
+        | Some -> C1
+        | None -> C2 }
+      C1 ; ... ; Cn       Sequential Composition
+      C1 | ... | Cn       Nondeterministic Choice
+      { C1, ..., Cn }     No Evaluation Order
+FT := f1<args1> = C1      Function Table
+     ...
+     fn<argsn> = Cn
+Stack := f1<args1> ... fn<argsn>
+progress := Progress | NoProgress
+values := {some, none} -> ?progress
+State := (progress, ?values)
+
+Eval.run: (FT, Stack, f<args>, C, State) ---> State
+```
+
+
+# Termination Types
+
+```
+p ::= 0 | 1
+r ::= p | {Some:p, None:p}
+t ::= * | t1=>r[s]t2
+
+
+-----------------
+G, x:t |-p x:0[]t
+
+
+    G, x:t1 |-0 e:p[s]t2
+-----------------------------
+G|-p0 (x)=>e: p0[](t1=>p[s]t2)
+
+
+G|-p0 e1:p1[s1]t1  G|-p1 e2:p2[s2](t1=>p[s]t2)
+    p3=p2+p  s3=(p2=1 ? s1+s2 : s1+s2+s)
+----------------------------------------------
+         G|-p0 e2(e1): p3[s3]t2
+
+
+G, fi: ti=>pi[si,fi]ti', x: ti |-0 ei: pi[si]ti'
+     G, fi: ti=>pi[si]ti' |-p0 e': p[s]t
+              fi not in si,ti'
+------------------------------------------------
+    G |-p0 let rec fi = (xi)=>ei; e : p[s]t
+
+```
+
+# Type Inference
+
+```
+s ::=
+      S        Set variable.
+      Loop     May loop.
+      f        May call f before making progress.
+      p.s      If p==1 then empty else s.
+      s1+s2    Union.
+
+p ::=
+      P        Progress variable.
+      0        Does not make progress.
+      1        Makes.
+      p1+p2    Makes progress if either does.
+      p1|p2    Makes progress if both do.
+
+t ::=
+      T           Type variable.
+      *           Base type.
+      t1=>p[s]t2  Function that calls s before making progress.
+
+
+-----------------
+G, x:t |-p x:0[]t
+
+
+G, x:T1 |-0 e:p[s]t2  T1 fresh
+------------------------------
+G|-p0 (x)=>e: p0[](T1=>p[s]t2)
+
+
+G|-p0 e1:p1[s1]t1  G|-p1 e2:p2[s2]t  P,S,T2 fresh
+-------------------------------------------------
+G|-p0 e2(e1): (p2+P)[s1+s2+p2.S]T2  t=t1=>P[S]T2
+
+
+G, fi: Ti=>Pi[Si+fi]Ti', x: Ti |-0 ei: pi[si]ti'
+             Ti,Ti',Pi,Si fresh
+     G, fi: ti=>pi[si]ti' |-p0 e': p[s]t          
+------------------------------------------------
+    G |-p0 let rec fi = (xi)=>ei; e : p[s]t 
+    pi=Pi si=Si ti'=Ti'  fi not in si,ti'
+
+```
+
+Constraint equations:
+```
+  0+p=p  1+p=1  p1+p2=p2+p1  p1+(p2+p3)=(p1+p2)+p3
+```
+
+```
+  0.s=s  1.s=[]
+```
+
+```
+  f-f=Loop  s+Loop=Loop
+  p.Loop ~~~> add  p=1
+```
+
+
+# Example Inference
+
+
+```reason
+let rec iter = (f, x) => { f(x); iter(f,x); };
+```
+
+```
+iter:(*=>P[S]*)=>P1[S1+iter](*=>P2[S2]*), f:*=>P3[S3]*, x:* |-0 f(x) : ???
+
+     |-0 f(x) : (0+P3)[0.S3]*
+     |-0 f(x) : P3[S3]*
+     
+     |-P3 iter(f) : (P3+P1)[P3.(S1+iter)](*=>P2[S2]*)
+       P3=P  S3=S
+     |-P iter(f) : (P+P1)[P.(S1+iter)](*=>P2[S2]*)
+     
+     |-(P+P1) iter(f,x) : (P+P1+P2)[P.(S1+iter)+(P+P1).S2]*
+
+
+     |-0 (x) => { f(x); iter(f,x); } : 0[](*=>(P+P1+P2)[S+P.(S1+iter)+(P+P1).S2]*)
+       P1=0  S1=[]  P2=P+P1+P2  S2=S+P.(S1+iter)+(P+P1).S2
+       iter not in S+P.(S1+iter)+(P+P1).S2  
+
+       P2=P+P2  S2=S+P.iter+P.S2
+       iter not in S+P.iter+P.S2  
+```
+
+
+Resolving "not in":
+
+```
+S+P.iter+P.S2 - iter =
+S+P.Loop+P.S2 =  ---> add P=1
+S
+
+S2=S
+P2=1
+```
+
+After Applying substitutions:
+
+```
+iter:(*=>1[S]*)=>0[](*=>1[S]*)
+```
+
+In words: `iter` expects as first parameter a function that: makes progress when called, and let `S` bet the set of functions it calls before making progress. When supplied the first argument, `iter` does not make progress. When supplied the second argument, it makes progress, and calls functions in set `S` before making progress.
+
+
+
diff --git a/tests/analysis_tests/tests-reanalyze/termination/bsconfig.json b/tests/analysis_tests/tests-reanalyze/termination/bsconfig.json
new file mode 100644
index 0000000000..ebd0a3a8ec
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/bsconfig.json
@@ -0,0 +1,22 @@
+{
+  "reanalyze": {
+    "analysis": ["termination"],
+    "suppress": [],
+    "unsuppress": []
+  },
+  "name": "arnold",
+  "bsc-flags": ["-bs-super-errors"],
+  "jsx": { "version": 3 },
+  "bs-dependencies": [],
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "package-specs": {
+    "module": "es6",
+    "in-source": true
+  },
+  "suffix": ".bs.js"
+}
diff --git a/tests/analysis_tests/tests-reanalyze/termination/expected/termination.txt b/tests/analysis_tests/tests-reanalyze/termination/expected/termination.txt
new file mode 100644
index 0000000000..063f51b6dd
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/expected/termination.txt
@@ -0,0 +1,230 @@
+
+  Scanning TestCyberTruck.cmt Source:TestCyberTruck.res
+
+  Function Table
+  1 justReturn: _
+
+  Termination Analysis for justReturn
+
+  Function Table
+  1 alwaysLoop: alwaysLoop
+
+  Termination Analysis for alwaysLoop
+
+  Function Table
+  1 alwaysProgress: +progress; alwaysProgress
+
+  Termination Analysis for alwaysProgress
+
+  Function Table
+  1 alwaysProgressWrongOrder: alwaysProgressWrongOrder; +progress
+
+  Termination Analysis for alwaysProgressWrongOrder
+
+  Function Table
+  1 doNotAlias: _
+
+  Termination Analysis for doNotAlias
+
+  Function Table
+  1 progressOnBothBranches: [+progress || +progress2]; progressOnBothBranches
+
+  Termination Analysis for progressOnBothBranches
+
+  Function Table
+  1 progressOnOneBranch: [+progress || _]; progressOnOneBranch
+
+  Termination Analysis for progressOnOneBranch
+
+  Function Table
+  1 callParseFunction<parseFunction>: parseFunction
+  2 testParametricFunction: [+progress || _]; testParametricFunction2
+  3 testParametricFunction2: callParseFunction<parseFunction:testParametricFunction>
+
+  Termination Analysis for testParametricFunction
+
+  Function Table
+  1 doNothing: _
+  2 testCacheHit: [doNothing; doNothing; +Progress.Nested.f || _]; testCacheHit
+
+  Termination Analysis for testCacheHit
+
+  Function Table
+  1 evalOrderIsNotLeftToRight: {+progress, evalOrderIsNotLeftToRight}; _
+
+  Termination Analysis for evalOrderIsNotLeftToRight
+
+  Function Table
+  1 evalOrderIsNotRightToLeft: {evalOrderIsNotRightToLeft, +progress}; _
+
+  Termination Analysis for evalOrderIsNotRightToLeft
+
+  Function Table
+  1 butFirstArgumentIsAlwaysEvaluated: +progress; butFirstArgumentIsAlwaysEvaluated
+
+  Termination Analysis for butFirstArgumentIsAlwaysEvaluated
+
+  Function Table
+  1 butSecondArgumentIsAlwaysEvaluated: +progress; butSecondArgumentIsAlwaysEvaluated
+
+  Termination Analysis for butSecondArgumentIsAlwaysEvaluated
+
+  Function Table
+  1 parseExpression: [_ || _]; [+Parser.next; parseExpression; parseExpression; _ || parseInt]
+  2 parseInt: [_ || _]; +Parser.next; _
+  3 parseList<f>: parseList$loop<f:f>
+  4 parseList$loop<f>: [_ || f; parseList$loop<f:f>; _]
+  5 parseListExpression: parseList<f:parseExpression>
+  6 parseListExpression2: parseExpression; parseList<f:parseExpression>
+  7 parseListInt: parseList<f:parseInt>
+  8 parseListIntTailRecursive: parseListIntTailRecursive$loop
+  9 parseListIntTailRecursive$loop: [_ || parseInt; parseListIntTailRecursive$loop]
+  10 parseListListInt: parseList<f:parseListInt>
+
+  Termination Analysis for parseListInt
+
+  Termination Analysis for parseListListInt
+
+  Termination Analysis for parseExpression
+
+  Termination Analysis for parseListExpression
+
+  Termination Analysis for parseListExpression2
+
+  Termination Analysis for parseListIntTailRecursive
+
+  Function Table
+  1 loopAfterProgress: loopAfterProgress
+  2 testLoopAfterProgress: +progress; loopAfterProgress
+
+  Termination Analysis for testLoopAfterProgress
+
+  Function Table
+  1 counterCompiled: +initState; [_ || counterCompiled; _]; _
+  2 onClick1: [_ || counterCompiled]
+
+  Termination Analysis for counterCompiled
+
+  Function Table
+  1 countRendersCompiled: [_ || countRendersCompiled; _]; _
+
+  Termination Analysis for countRendersCompiled
+
+  Function Table
+  1 alwaysReturnNone: [+Parser.next; alwaysReturnNone || None]
+  2 parseIntO: [+Parser.next; Some || None]
+  3 parseIntOWrapper: parseIntO
+  4 parseListIntO: parseListO<f:parseIntO>
+  5 parseListO<f>: parseListO$loop<f:f>
+  6 parseListO$loop<f>: [+Parser.next; _ || switch f {some: parseListO$loop<f:f>, none: _}]
+  7 testAlwaysReturnNone: alwaysReturnNone
+  8 thisMakesNoProgress: None; [_ || +Parser.next; Some]
+
+  Termination Analysis for parseListIntO
+
+  Termination Analysis for testAlwaysReturnNone
+
+  Termination Analysis for parseIntOWrapper
+
+  Termination Analysis for thisMakesNoProgress
+
+  Function Table
+  1 f: [g; _ || _ || +Parser.next; f]
+  2 g: +Parser.next; gParam<g:g>
+  3 gParam<g>: [g; _ || f]
+
+  Termination Analysis for f
+
+  Function Table
+  1 concat<f, g>: switch f {some: switch g {some: Some, none: None}, none: None}
+  2 kleene<f>: switch f {some: kleene<f:f>, none: _}
+  3 one: [+Parser.next; Some || None]
+  4 oneTwo: concat<f:one,g:two>
+  5 oneTwoStar: kleene<f:oneTwo>
+  6 two: [+Parser.next; Some || None]
+
+  Termination Analysis for oneTwoStar
+
+  Function Table
+  1 testTry: [+progress; testTry || +progress; testTry]
+
+  Termination Analysis for testTry
+
+  Termination Analysis Stats
+  Files:1
+  Recursive Blocks:21
+  Functions:49
+  Infinite Loops:10
+  Hygiene Errors:1
+  Cache Hits:8/31
+  
+
+  Error Termination
+  TestCyberTruck.res:29:28-39
+  Possible infinite loop when calling alwaysLoop
+  CallStack:
+    1 alwaysLoop (TestCyberTruck.res 29)
+
+  Error Termination
+  TestCyberTruck.res:40:3-28
+  Possible infinite loop when calling alwaysProgressWrongOrder
+  CallStack:
+    1 alwaysProgressWrongOrder (TestCyberTruck.res 39)
+
+  Error Hygiene
+  TestCyberTruck.res:47:15-24
+  doNotAlias can only be called directly, or passed as labeled argument
+
+  Error Termination
+  TestCyberTruck.res:68:3-24
+  Possible infinite loop when calling progressOnOneBranch
+  CallStack:
+    1 progressOnOneBranch (TestCyberTruck.res 64)
+
+  Error Termination
+  TestCyberTruck.res:80:48-63
+  Possible infinite loop when calling parseFunction which is testParametricFunction
+  CallStack:
+    3 callParseFunction<parseFunction:testParametricFunction> (TestCyberTruck.res 79)
+    2 testParametricFunction2 (TestCyberTruck.res 77)
+    1 testParametricFunction (TestCyberTruck.res 73)
+
+  Error Termination
+  TestCyberTruck.res:89:3-17
+  Possible infinite loop when calling testCacheHit
+  CallStack:
+    1 testCacheHit (TestCyberTruck.res 83)
+
+  Error Termination
+  TestCyberTruck.res:97:31-58
+  Possible infinite loop when calling evalOrderIsNotLeftToRight
+  CallStack:
+    1 evalOrderIsNotLeftToRight (TestCyberTruck.res 95)
+
+  Error Termination
+  TestCyberTruck.res:104:19-46
+  Possible infinite loop when calling evalOrderIsNotRightToLeft
+  CallStack:
+    1 evalOrderIsNotRightToLeft (TestCyberTruck.res 102)
+
+  Error Termination
+  TestCyberTruck.res:180:15-21
+  Possible infinite loop when calling parseList$loop<f:f> which is parseList$loop<f:parseListInt>
+  CallStack:
+    3 parseList$loop<f:parseListInt> (TestCyberTruck.res 183)
+    2 parseList<f:parseListInt> (TestCyberTruck.res 201)
+    1 parseListListInt (TestCyberTruck.res 201)
+
+  Error Termination
+  TestCyberTruck.res:238:31-49
+  Possible infinite loop when calling loopAfterProgress
+  CallStack:
+    1 loopAfterProgress (TestCyberTruck.res 236)
+
+  Error Termination
+  TestCyberTruck.res:286:32-61
+  Possible infinite loop when calling countRendersCompiled
+  CallStack:
+    1 countRendersCompiled (TestCyberTruck.res 283)
+  
+  Analysis reported 11 issues (Error Hygiene:1, Error Termination:10)
diff --git a/tests/analysis_tests/tests-reanalyze/termination/package-lock.json b/tests/analysis_tests/tests-reanalyze/termination/package-lock.json
new file mode 100644
index 0000000000..ec4b873ca7
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/package-lock.json
@@ -0,0 +1,36 @@
+{
+  "name": "termination",
+  "version": "0.1.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "termination",
+      "version": "0.1.0",
+      "devDependencies": {
+        "rescript": "^10.1.2"
+      }
+    },
+    "node_modules/rescript": {
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.2.tgz",
+      "integrity": "sha512-PPdhOiN+lwqxQ0qvzHc1KW0TL12LDy2X+qo/JIMIP3bMfiH0vxQH2e/lXuoutWWm04fGQGTv3hWH+gKW3/UyfA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "bsc": "bsc",
+        "bsrefmt": "bsrefmt",
+        "bstracing": "lib/bstracing",
+        "rescript": "rescript"
+      }
+    }
+  },
+  "dependencies": {
+    "rescript": {
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/rescript/-/rescript-10.1.2.tgz",
+      "integrity": "sha512-PPdhOiN+lwqxQ0qvzHc1KW0TL12LDy2X+qo/JIMIP3bMfiH0vxQH2e/lXuoutWWm04fGQGTv3hWH+gKW3/UyfA==",
+      "dev": true
+    }
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/termination/package.json b/tests/analysis_tests/tests-reanalyze/termination/package.json
new file mode 100644
index 0000000000..6ed0f459a5
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "termination",
+  "version": "0.1.0",
+  "private": true,
+  "devDependencies": {
+    "rescript": "^10.1.2"
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res b/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res
new file mode 100644
index 0000000000..5c5fcbf835
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res
@@ -0,0 +1,449 @@
+@@warning("-39-48-27")
+
+// A progress function will eventually terminate
+let progress = {
+  let counter = ref(Random.int(100))
+  () => {
+    if counter.contents < 0 {
+      assert false
+    }
+    counter := counter.contents - 1
+  }
+}
+
+// Another progress function
+let progress2 = progress
+
+// A progress function can be taken from a module
+module Progress = {
+  module Nested = {
+    let f = progress
+  }
+}
+
+// Need to declare at least one progress function and one recursive definition
+@progress(progress)
+let rec justReturn = () => ()
+
+@progress(progress)
+let rec alwaysLoop = () => alwaysLoop() // infinite loop
+
+@progress(progress)
+let rec alwaysProgress = () => {
+  // Terminates
+  progress()
+  alwaysProgress()
+}
+
+@progress(progress)
+let rec alwaysProgressWrongOrder = () => {
+  alwaysProgressWrongOrder()
+  progress() // Oops: this is too late
+}
+
+@progress(progress)
+let rec doNotAlias = () => {
+  // Must not alias recursive functions
+  let alias = doNotAlias
+  alias()
+}
+
+@progress((progress, progress2))
+let rec // Terminates as each branch makes progress
+progressOnBothBranches = x => {
+  if x > 3 {
+    progress()
+  } else {
+    progress2()
+  }
+  progressOnBothBranches(x)
+}
+
+@progress(progress)
+let rec // Loops as progress is only on one branch
+progressOnOneBranch = x => {
+  if x > 3 {
+    progress()
+  }
+  progressOnOneBranch(x)
+}
+
+@progress(progress)
+let rec // callParseFunction is parametric: it takes a parse function and calls it
+testParametricFunction = x => {
+  if x > 3 {
+    progress()
+  }
+  testParametricFunction2(x)
+}
+and testParametricFunction2 = x => callParseFunction(x, ~parseFunction=testParametricFunction)
+and callParseFunction = (x, ~parseFunction) => parseFunction(x) // loops
+
+@progress(Progress.Nested.f)
+let rec testCacheHit = x => {
+  if x > 0 {
+    doNothing(x)
+    doNothing(x) // this should hit the analysis cache
+    Progress.Nested.f()
+  }
+  testCacheHit(x)
+}
+and doNothing = _ => ()
+
+@progress(progress)
+let rec // Loops as can't rely on a specific evaluation order
+evalOrderIsNotLeftToRight = x => {
+  let combineTwoUnits = ((), ()) => ()
+  combineTwoUnits(progress(), evalOrderIsNotLeftToRight(x))
+}
+
+@progress(progress)
+let rec // Loops as can't rely on a specific evaluation order
+evalOrderIsNotRightToLeft = x => {
+  let combineTwoUnits = ((), ()) => ()
+  combineTwoUnits(evalOrderIsNotRightToLeft(x), progress())
+}
+
+@progress(progress)
+let rec // Terminates: all arguments are evaluated in some order
+butFirstArgumentIsAlwaysEvaluated = x => {
+  let combineTwoUnits = ((), ()) => ()
+  combineTwoUnits(progress(), ())
+  butFirstArgumentIsAlwaysEvaluated(x)
+}
+
+@progress(progress)
+let rec // Terminates: all arguments are evaluated in some order
+butSecondArgumentIsAlwaysEvaluated = x => {
+  let combineTwoUnits = ((), ()) => ()
+  combineTwoUnits((), progress())
+  butSecondArgumentIsAlwaysEvaluated(x)
+}
+
+module Parser = {
+  type token =
+    | Asterisk
+    | Eof
+    | Lparen
+    | Int(int)
+    | Plus
+    | Rparen
+
+  type position = {
+    lnum: int,
+    cnum: int,
+  }
+
+  type t = {
+    mutable position: position,
+    mutable errors: list<string>,
+    mutable token: token,
+  }
+
+  let tokenToString = token =>
+    switch token {
+    | Asterisk => "*"
+    | Eof => "Eof"
+    | Lparen => "("
+    | Int(n) => string_of_int(n)
+    | Plus => "+"
+    | Rparen => ")"
+    }
+
+  let next = p => {
+    p.token = Random.bool() ? Eof : Int(Random.int(1000))
+    p.position = {lnum: Random.int(1000), cnum: Random.int(80)}
+  }
+
+  let err = (p, s) => p.errors = list{s, ...p.errors}
+
+  let expect = (p, token) =>
+    if p.token == token {
+      next(p)
+    } else {
+      err(p, "expected token " ++ tokenToString(p.token))
+    }
+}
+
+module Expr = {
+  type rec t =
+    | Int(int)
+    | Plus(t, t)
+}
+
+let parseList = (p: Parser.t, ~f) => {
+  let rec loop = (p: Parser.t) =>
+    if p.token == Asterisk {
+      list{}
+    } else {
+      let item = f(p)
+      let l = loop(p)
+      list{item, ...l}
+    }
+  loop(p)
+}
+
+let parseInt = (p: Parser.t) => {
+  let res = switch p.token {
+  | Int(n) => n
+  | _ =>
+    Parser.err(p, "integer expected")
+    -1
+  }
+  Parser.next(p)
+  res
+}
+
+@progress(Parser.next)
+let rec parseListInt = p => parseList(p, ~f=parseInt)
+
+@progress
+and parseListListInt = p => parseList(p, ~f=parseListInt)
+
+@progress
+and parseExpression = (~x=4, p: Parser.t) =>
+  switch p.token {
+  | Lparen =>
+    Parser.next(p)
+    let e1 = parseExpression(p)
+    Parser.expect(p, Plus)
+    let e2 = parseExpression(p)
+    Parser.expect(p, Lparen)
+    Expr.Plus(e1, e2)
+  | _ => Expr.Int(parseInt(p))
+  }
+
+@progress
+and parseListExpression = p => parseList(p, ~f=parseExpression)
+
+@progress
+and parseListExpression2 = p => parseList(p, ~f=parseExpression(~x=7))
+
+@progress
+and parseListIntTailRecursive = p => {
+  let rec loop = (p: Parser.t, l) =>
+    if p.token == Asterisk {
+      List.rev(l)
+    } else {
+      loop(p, list{parseInt(p), ...l})
+    }
+  loop(p, list{})
+}
+
+@progress(progress)
+let rec testLoopAfterProgress = () => {
+  progress()
+  loopAfterProgress()
+}
+and loopAfterProgress = () => loopAfterProgress()
+
+module UITermination = {
+  type state = int
+  type setState = (~f: state => option<state>) => unit
+
+  type onClick = unit => unit
+  type dom
+
+  let nothing: onClick = () => ()
+
+  type div = (~text: string, ~onClick: onClick) => dom
+  let div: div = (~text, ~onClick) => assert false
+
+  let initState = n => n == 0 ? Some(42) : None
+  let increment = n => Some(n + 1)
+
+  let incrementOnClick = (~setState: setState): onClick => () => setState(~f=increment)
+
+  let counter = (state: state, ~setState: setState) => {
+    setState(~f=initState)
+    div(~text=string_of_int(state), ~onClick=() => setState(~f=increment))
+  }
+
+  @progress(initState)
+  let rec counterCompiled = (state: state) => {
+    switch initState(state) {
+    | None => ()
+    | Some(newState) => ignore(counterCompiled(newState))
+    }
+    ignore(string_of_int(state))
+  }
+
+  and onClick1 = state =>
+    switch increment(state) {
+    | None => ()
+    | Some(newState) => counterCompiled(newState)
+    }
+
+  let countRenders = (state: state, ~setState: setState) => {
+    setState(~f=increment)
+    div(~text="I have been rendered " ++ (string_of_int(state) ++ " times"), ~onClick=nothing)
+  }
+
+  @progress(initState)
+  let rec countRendersCompiled = (state: state) => {
+    switch increment(state) {
+    | None => ()
+    | Some(newState) => ignore(countRendersCompiled(newState))
+    }
+    ignore("I have been rendered " ++ (string_of_int(state) ++ " times"))
+  }
+}
+
+module ParserWihtOptionals = {
+  let parseListO = (p: Parser.t, ~f) => {
+    let rec loop = nodes =>
+      if p.token == Asterisk {
+        Parser.next(p)
+        list{}
+      } else {
+        switch f(p) {
+        | None => List.rev(nodes)
+        | Some(item) => loop(list{item, ...nodes})
+        }
+      }
+    loop(list{})
+  }
+
+  let parseIntO = (p: Parser.t) =>
+    switch p.token {
+    | Int(n) =>
+      Parser.next(p)
+      Some(n)
+    | _ =>
+      Parser.err(p, "integer expected")
+      None
+    }
+
+  @progress((Parser.next, Parser.next))
+  let rec parseListIntO = p => parseListO(p, ~f=parseIntO)
+
+  and alwaysReturnNone = (p: Parser.t) =>
+    switch p.token {
+    | Int(_) =>
+      Parser.next(p)
+      alwaysReturnNone(p)
+    | _ => None
+    }
+
+  @progress
+  and testAlwaysReturnNone = p => alwaysReturnNone(p)
+
+  @progress
+  and parseIntOWrapper = p => parseIntO(p)
+
+  @progress
+  and thisMakesNoProgress = (p: Parser.t, y) => {
+    let x = None
+    switch y {
+    | Some(_) => x
+    | _ =>
+      Parser.next(p)
+      Some(10)
+    }
+  }
+}
+
+module Riddle = {
+  @progress(Parser.next)
+  let rec f = (p: Parser.t) =>
+    switch p.token {
+    | Int(i) => g(p) + i
+    | Eof => 0
+    | _ =>
+      Parser.next(p)
+      f(p)
+    }
+
+  and gParam = (p: Parser.t, ~g) =>
+    switch p.token {
+    | Int(i) => g(p) + i
+    | _ => f(p)
+    }
+
+  and g = p => {
+    Parser.next(p)
+    gParam(p, ~g)
+  }
+}
+
+module TerminationTypes = {
+  // p ::= P | N   (P progress, or N no progress)
+  // r ::= p | {Some:p, None:p}    (result, in case of optional specify progress separately)
+  // t ::= _ | t1=>r[f1,... fn]t2  (when called, the function makes progress or not
+  // and calls f1,...,fn without making progeess first)
+  // Abbreviations: omit empty [], and rhs _
+
+  let rec f /* _=>P[g] */ = p => g(p)
+  and g /* _=>P */ = p => {
+    Parser.next(p)
+    f(p)
+  }
+
+  let rec kleene0 /* (~f:_=>P, _) => P */ = (~f, p) => {
+    f(p)
+    kleene0(~f, p)
+  }
+
+  let union /* (~f:_=>{Some:P, None:N}, ~g:_=>{Some:P, None:N}, _) => {Some:P, None:N} */ = (
+    ~f,
+    ~g,
+    p,
+  ) =>
+    switch f(p) {
+    | None => g(p)
+    | Some(x) => x
+    }
+
+  let concat /* (~f:_=>{Some:P, None:N}, ~g:_=>{Some:P, None:N}, _) => {Some:P, None:N} */ = (
+    ~f,
+    ~g,
+    p,
+  ) =>
+    switch f(p) {
+    | None => None
+    | Some(x) =>
+      switch g(p) {
+      | None => None
+      | Some(y) => Some(x ++ y)
+      }
+    }
+
+  let rec kleene /* (~f:_=>{Some:P, None:N}, _) => N */ = (~f, p) =>
+    switch f(p) {
+    | None => list{}
+    | Some(x) => list{x, ...kleene(~f, p)}
+    }
+
+  and one /* _=>{Some:P, None:N} */ = (p: Parser.t) =>
+    switch p.token {
+    | Int(1) =>
+      Parser.next(p)
+      Some("1")
+    | _ => None
+    }
+
+  and two /* _=>{Some:P, None:N} */ = (p: Parser.t) =>
+    switch p.token {
+    | Int(2) =>
+      Parser.next(p)
+      Some("2")
+    | _ => None
+    }
+
+  and oneTwo /* _=>{Some:P, None:N} */ = p => concat(~f=one, ~g=two, p)
+
+  @progress(Parser.next)
+  and oneTwoStar /* _=>N */ = p => kleene(~f=oneTwo, p)
+}
+
+@progress(progress)
+let rec testTry = () => {
+  try raise(Not_found) catch {
+  | Not_found =>
+    let _ = #abc(progress())
+    testTry()
+  | _ =>
+    let _ = [(), progress(), ()]
+    testTry()
+  }
+}
diff --git a/tests/analysis_tests/tests-reanalyze/termination/test.sh b/tests/analysis_tests/tests-reanalyze/termination/test.sh
new file mode 100755
index 0000000000..3897ae695f
--- /dev/null
+++ b/tests/analysis_tests/tests-reanalyze/termination/test.sh
@@ -0,0 +1,19 @@
+output="expected/termination.txt"
+dune exec rescript-editor-analysis -- reanalyze -config -ci -debug > $output
+# CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+if [ "$RUNNER_OS" == "Windows" ]; then
+  perl -pi -e 's/\r\n/\n/g' -- $output
+fi
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff expected
+  exit 1
+fi
diff --git a/tests/analysis_tests/tests/Makefile b/tests/analysis_tests/tests/Makefile
new file mode 100644
index 0000000000..5084c67abd
--- /dev/null
+++ b/tests/analysis_tests/tests/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript
+
+test: build
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := test
+
+.PHONY: clean test
diff --git a/tests/analysis_tests/tests/bsconfig.json b/tests/analysis_tests/tests/bsconfig.json
new file mode 100644
index 0000000000..f14d618fef
--- /dev/null
+++ b/tests/analysis_tests/tests/bsconfig.json
@@ -0,0 +1,15 @@
+{
+  "name": "test",
+  "reanalyze": {
+    "analysis": ["dce"]
+  },
+  "sources": [
+    {
+      "dir": "src",
+      "subdirs": true
+    }
+  ],
+  "bsc-flags": ["-w -33-44-8"],
+  "bs-dependencies": ["@rescript/react"],
+  "jsx": { "version": 4 }
+}
diff --git a/tests/analysis_tests/tests/not_compiled/Diagnostics.res b/tests/analysis_tests/tests/not_compiled/Diagnostics.res
new file mode 100644
index 0000000000..c81f1e63e5
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/Diagnostics.res
@@ -0,0 +1,5 @@
+let = 1 + 1.0
+let add = =2
+lett a = 2
+
+//^dia
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/not_compiled/DocTemplate.res b/tests/analysis_tests/tests/not_compiled/DocTemplate.res
new file mode 100644
index 0000000000..2d7fe5d081
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/DocTemplate.res
@@ -0,0 +1,20 @@
+type a = {a: int}
+//	^xfm
+
+type rec t = A | B
+// ^xfm
+and e = C
+@unboxed type name = Name(string)
+//             ^xfm
+let a = 1
+//  ^xfm
+let inc = x => x + 1
+//  ^xfm
+module T = {
+  //   ^xfm
+  let b = 1
+  //  ^xfm
+}
+@module("path")
+external dirname: string => string = "dirname"
+//^xfm
diff --git a/tests/analysis_tests/tests/not_compiled/DocTemplate.resi b/tests/analysis_tests/tests/not_compiled/DocTemplate.resi
new file mode 100644
index 0000000000..6940b7fe59
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/DocTemplate.resi
@@ -0,0 +1,20 @@
+type a = {a: int}
+//	^xfm
+
+type rec t = A | B
+// ^xfm
+and e = C
+@unboxed type name = Name(string)
+//             ^xfm
+let a: int
+//  ^xfm
+let inc: int => int
+//  ^xfm
+module T: {
+  //   ^xfm
+  let b: int
+  //  ^xfm
+}
+@module("path")
+external dirname: string => string = "dirname"
+//^xfm
diff --git a/tests/analysis_tests/tests/not_compiled/expected/Diagnostics.res.txt b/tests/analysis_tests/tests/not_compiled/expected/Diagnostics.res.txt
new file mode 100644
index 0000000000..a5df33b710
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/expected/Diagnostics.res.txt
@@ -0,0 +1,17 @@
+[{
+  "range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 6}},
+  "message": "consecutive statements on a line must be separated by ';' or a newline",
+  "severity": 1,
+  "source": "ReScript"
+}, {
+  "range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 11}},
+  "message": "This let-binding misses an expression",
+  "severity": 1,
+  "source": "ReScript"
+}, {
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "message": "I was expecting a name for this let-binding. Example: `let message = \"hello\"`",
+  "severity": 1,
+  "source": "ReScript"
+}]
+
diff --git a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt
new file mode 100644
index 0000000000..fcb084a8ca
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.res.txt
@@ -0,0 +1,135 @@
+Xform not_compiled/DocTemplate.res 3:3
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
+newText:
+<--here
+/**
+
+*/
+type rec t = A | B
+// ^xfm
+and e = C
+
+Xform not_compiled/DocTemplate.res 6:15
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
+newText:
+<--here
+/**
+
+*/
+@unboxed
+type name = Name(string)
+
+Xform not_compiled/DocTemplate.res 8:4
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 9}}
+newText:
+<--here
+/**
+
+*/
+let a = 1
+
+Xform not_compiled/DocTemplate.res 10:4
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 20}}
+newText:
+<--here
+/**
+
+*/
+let inc = x => x + 1
+
+Xform not_compiled/DocTemplate.res 12:7
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
+newText:
+<--here
+/**
+
+*/
+module T = {
+  //   ^xfm
+  let b = 1
+  //  ^xfm
+}
+Hit: Extract local module "T" to file "T.res"
+
+CreateFile: T.res
+
+TextDocumentEdit: T.res
+{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
+newText:
+<--here
+//   ^xfm
+let b = 1
+//  ^xfm
+
+
+TextDocumentEdit: not_compiled/DocTemplate.res
+{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
+newText:
+<--here
+
+
+Xform not_compiled/DocTemplate.res 14:6
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 11}}
+newText:
+  <--here
+  /**
+  
+  */
+  let b = 1
+Hit: Extract local module "T" to file "T.res"
+
+CreateFile: T.res
+
+TextDocumentEdit: T.res
+{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
+newText:
+<--here
+//   ^xfm
+let b = 1
+//  ^xfm
+
+
+TextDocumentEdit: not_compiled/DocTemplate.res
+{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
+newText:
+<--here
+
+
+Xform not_compiled/DocTemplate.res 18:2
+can't find module DocTemplate
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.res
+{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
+newText:
+<--here
+/**
+
+*/
+@module("path")
+external dirname: string => string = "dirname"
+
diff --git a/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt
new file mode 100644
index 0000000000..ef4987a7cf
--- /dev/null
+++ b/tests/analysis_tests/tests/not_compiled/expected/DocTemplate.resi.txt
@@ -0,0 +1,92 @@
+Xform not_compiled/DocTemplate.resi 3:3
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 3, "character": 0}, "end": {"line": 5, "character": 9}}
+newText:
+<--here
+/**
+
+*/
+type rec t = A | B
+// ^xfm
+and e = C
+
+Xform not_compiled/DocTemplate.resi 6:15
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 6, "character": 0}, "end": {"line": 6, "character": 33}}
+newText:
+<--here
+/**
+
+*/
+@unboxed
+type name = Name(string)
+
+Xform not_compiled/DocTemplate.resi 8:4
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 8, "character": 0}, "end": {"line": 8, "character": 10}}
+newText:
+<--here
+/**
+
+*/
+let a: int
+
+Xform not_compiled/DocTemplate.resi 10:4
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 10, "character": 0}, "end": {"line": 10, "character": 19}}
+newText:
+<--here
+/**
+
+*/
+let inc: int => int
+
+Xform not_compiled/DocTemplate.resi 12:7
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 12, "character": 0}, "end": {"line": 16, "character": 1}}
+newText:
+<--here
+/**
+
+*/
+module T: {
+  //   ^xfm
+  let b: int
+  //  ^xfm
+}
+
+Xform not_compiled/DocTemplate.resi 14:6
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 12}}
+newText:
+  <--here
+  /**
+  
+  */
+  let b: int
+
+Xform not_compiled/DocTemplate.resi 18:2
+Hit: Add Documentation template
+
+TextDocumentEdit: DocTemplate.resi
+{"start": {"line": 17, "character": 0}, "end": {"line": 18, "character": 46}}
+newText:
+<--here
+/**
+
+*/
+@module("path")
+external dirname: string => string = "dirname"
+
diff --git a/tests/analysis_tests/tests/package-lock.json b/tests/analysis_tests/tests/package-lock.json
new file mode 100644
index 0000000000..cdb575c401
--- /dev/null
+++ b/tests/analysis_tests/tests/package-lock.json
@@ -0,0 +1,193 @@
+{
+  "name": "tests",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "rescript": "../../.."
+      },
+      "devDependencies": {
+        "@rescript/react": "0.13.0"
+      }
+    },
+    "../..": {
+      "name": "rescript",
+      "version": "12.0.0-alpha.5",
+      "extraneous": true,
+      "hasInstallScript": true,
+      "license": "SEE LICENSE IN LICENSE",
+      "bin": {
+        "bsc": "cli/bsc",
+        "bstracing": "lib/bstracing",
+        "rescript": "cli/rescript",
+        "rewatch": "cli/rewatch"
+      },
+      "devDependencies": {
+        "@biomejs/biome": "1.8.3",
+        "mocha": "10.1.0",
+        "nyc": "15.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "../../..": {
+      "name": "rescript",
+      "version": "12.0.0-alpha.5",
+      "hasInstallScript": true,
+      "license": "SEE LICENSE IN LICENSE",
+      "bin": {
+        "bsc": "cli/bsc",
+        "bstracing": "lib/bstracing",
+        "rescript": "cli/rescript",
+        "rewatch": "cli/rewatch"
+      },
+      "devDependencies": {
+        "@biomejs/biome": "1.8.3",
+        "mocha": "10.1.0",
+        "nyc": "15.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@rescript/react": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.13.0.tgz",
+      "integrity": "sha512-YSIWIyMlyF9ZaP6Q3hScl1h3wRbdIP4+Cb7PlDt7Y1PG8M8VWYhLoIgLb78mbBHcwFbZu0d5zAt1LSX5ilOiWQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=18.0.0",
+        "react-dom": ">=18.0.0"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      },
+      "peerDependencies": {
+        "react": "^18.2.0"
+      }
+    },
+    "node_modules/rescript": {
+      "resolved": "../../..",
+      "link": true
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    }
+  },
+  "dependencies": {
+    "@rescript/react": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.13.0.tgz",
+      "integrity": "sha512-YSIWIyMlyF9ZaP6Q3hScl1h3wRbdIP4+Cb7PlDt7Y1PG8M8VWYhLoIgLb78mbBHcwFbZu0d5zAt1LSX5ilOiWQ==",
+      "dev": true,
+      "requires": {}
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true,
+      "peer": true
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dev": true,
+      "peer": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "dev": true,
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "dev": true,
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      }
+    },
+    "rescript": {
+      "version": "file:../../..",
+      "requires": {
+        "@biomejs/biome": "1.8.3",
+        "mocha": "10.1.0",
+        "nyc": "15.0.0"
+      }
+    },
+    "scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "dev": true,
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    }
+  }
+}
diff --git a/tests/analysis_tests/tests/package.json b/tests/analysis_tests/tests/package.json
new file mode 100644
index 0000000000..e1924d04c8
--- /dev/null
+++ b/tests/analysis_tests/tests/package.json
@@ -0,0 +1,13 @@
+{
+  "scripts": {
+    "build": "rescript",
+    "clean": "rescript clean -with-deps"
+  },
+  "private": true,
+  "devDependencies": {
+    "@rescript/react": "0.13.0"
+  },
+  "dependencies": {
+    "rescript": "../../.."
+  }
+}
diff --git a/tests/analysis_tests/tests/src/Auto.res b/tests/analysis_tests/tests/src/Auto.res
new file mode 100644
index 0000000000..52a0b7c4e6
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Auto.res
@@ -0,0 +1,4 @@
+open! ShadowedBelt
+
+let m = List.map
+//           ^hov
diff --git a/tests/analysis_tests/tests/src/BrokenParserCases.res b/tests/analysis_tests/tests/src/BrokenParserCases.res
new file mode 100644
index 0000000000..c56a3d5aa8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/BrokenParserCases.res
@@ -0,0 +1,13 @@
+// --- BROKEN PARSER CASES ---
+// This below demonstrates an issue when what you're completing is the _last_ labelled argument, and there's a unit application after it. The parser wrongly merges the unit argument as the expression of the labelled argument assignment, where is should really let the trailing unit argument be, and set a %rescript.exprhole as the expression of the assignment, just like it normally does.
+// let _ = someFn(~isOff=, ())
+//                      ^com
+
+// This should parse as a single item tuple when in a pattern?
+// switch s { | (t) }
+//               ^com
+
+// Here the parser eats the arrow and considers the None in the expression part of the pattern.
+// let _ = switch x { | None |  => None }
+//                           ^com
+
diff --git a/tests/analysis_tests/tests/src/CodeLens.res b/tests/analysis_tests/tests/src/CodeLens.res
new file mode 100644
index 0000000000..c558530543
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CodeLens.res
@@ -0,0 +1,11 @@
+let add = (x, y) => x + y
+
+let foo = (~age, ~name) => name ++ Int.toString(age)
+
+let ff = (~opt1=0, ~a, ~b, (), ~opt2=0, (), ~c) => a + b + c + opt1 + opt2
+
+let compFF = Completion.ff
+
+@react.component
+let make = (~name) => React.string(name)
+//^cle
diff --git a/tests/analysis_tests/tests/src/Codemod.res b/tests/analysis_tests/tests/src/Codemod.res
new file mode 100644
index 0000000000..ef85b9a74d
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Codemod.res
@@ -0,0 +1,9 @@
+type someTyp = [#valid | #invalid]
+
+let ff = (v1: someTyp, v2: someTyp) => {
+  let x = switch (v1, v2) {
+  //      ^c-a (#valid, #valid) | (#invalid, _)
+  | (#valid, #invalid) => ()
+  }
+  x
+}
diff --git a/tests/analysis_tests/tests/src/CompletableComponent.res b/tests/analysis_tests/tests/src/CompletableComponent.res
new file mode 100644
index 0000000000..1ab3749084
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletableComponent.res
@@ -0,0 +1,10 @@
+type status = On | Off
+
+@@jsxConfig({version: 4, mode: "automatic"})
+
+@react.component
+let make = (~status: status, ~name: string) => {
+  ignore(status)
+  ignore(name)
+  React.null
+}
diff --git a/tests/analysis_tests/tests/src/CompletePrioritize1.res b/tests/analysis_tests/tests/src/CompletePrioritize1.res
new file mode 100644
index 0000000000..5fcccf3deb
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletePrioritize1.res
@@ -0,0 +1,7 @@
+module Test = {
+  type t = {name: int}
+  let add = (a: float) => a +. 1.0
+}
+let a: Test.t = {name: 4}
+// a->
+//    ^com
diff --git a/tests/analysis_tests/tests/src/CompletePrioritize2.res b/tests/analysis_tests/tests/src/CompletePrioritize2.res
new file mode 100644
index 0000000000..f7f5adcf5f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletePrioritize2.res
@@ -0,0 +1,14 @@
+let ax = 4
+let _ = ax
+let ax = ""
+let _ = ax
+module Test = {
+  type t = {name: int}
+  let add = (ax: t) => ax.name + 1
+}
+let ax: Test.t = {name: 4}
+// ax->
+//     ^com
+
+// ax
+//   ^com
diff --git a/tests/analysis_tests/tests/src/Completion.res b/tests/analysis_tests/tests/src/Completion.res
new file mode 100644
index 0000000000..b6332345d2
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Completion.res
@@ -0,0 +1,468 @@
+module MyList = Belt.List
+// MyList.m
+//         ^com
+// Array.
+//       ^com
+// Array.m
+//        ^com
+
+module Dep: {
+  @ocaml.doc("Some doc comment") @deprecated("Use customDouble instead")
+  let customDouble: int => int
+} = {
+  let customDouble = foo => foo * 2
+}
+
+// let cc = Dep.c
+//               ^com
+
+module Lib = {
+  let foo = (~age, ~name) => name ++ Int.toString(age)
+  let next = (~number=0, ~year) => number + year
+}
+
+// let x = Lib.foo(~
+//                  ^com
+
+// [1,2,3]->m
+//           ^com
+
+// "abc"->toU
+//           ^com
+
+let op = Some(3)
+
+// op->e
+//      ^com
+
+module ForAuto = {
+  type t = int
+  let abc = (x: t, _y: int) => x
+  let abd = (x: t, _y: int) => x
+}
+
+let fa: ForAuto.t = 34
+// fa->
+//     ^com
+
+// "hello"->Js.Dict.u
+//                   ^com
+
+module O = {
+  module Comp = {
+    @react.component
+    let make = (~first="", ~zoo=3, ~second) => React.string(first ++ second ++ Int.toString(zoo))
+  }
+}
+
+let zzz = 11
+
+// let comp = <O.Comp second=z
+//                            ^com
+
+// let comp = <O.Comp z
+//                     ^com
+
+// @reac
+//      ^com
+
+// @react.
+//        ^com
+
+// let x = Lib.foo(~name, ~
+//                         ^com
+
+// let x = Lib.foo(~age, ~
+//                        ^com
+
+// let x = Lib.foo(~age={3+4}, ~
+//                              ^com
+
+let _ = Lib.foo(
+  //~age,
+  //~
+  // ^com
+  ~age=3,
+  ~name="",
+)
+
+let someObj = {"name": "a", "age": 32}
+
+// someObj["a
+//           ^com
+
+let nestedObj = {"x": {"y": {"name": "a", "age": 32}}}
+
+// nestedObj["x"]["y"]["
+//                      ^com
+
+let o: Objects.objT = assert(false)
+// o["a
+//     ^com
+
+type nestedObjT = {"x": Objects.nestedObjT}
+let no: nestedObjT = assert(false)
+// no["x"]["y"]["
+//               ^com
+
+type r = {x: int, y: string}
+type rAlias = r
+let r: rAlias = assert(false)
+// r.
+//   ^com
+
+// Objects.Rec.recordVal.
+//                       ^com
+
+let myAmazingFunction = (x, y) => x + y
+
+@react.component
+let make = () => {
+  // my
+  //   ^com
+  <> </>
+}
+
+// Objects.object["
+//                 ^com
+
+let foo = {
+  let x = {
+    3
+  }
+  let y = 4
+  let add = (a, b) =>
+    switch a {
+    | 3 => a + b
+    | _ => 42
+    }
+  let z = assert(false)
+  let _ = z
+  module Inner = {
+    type z = int
+    let v = 44
+  }
+  exception MyException(int, string, float, array<Js.Json.t>)
+  let _ = raise(MyException(2, "", 1.0, []))
+  add((x: Inner.z), Inner.v + y)
+}
+
+exception MyOtherException
+
+// <O.
+//    ^com
+
+type aa = {x: int, name: string}
+type bb = {aa: aa, w: int}
+let q: bb = assert(false)
+// q.aa.
+//      ^com
+// q.aa.n
+//       ^com
+
+// Lis
+//    ^com
+
+module WithChildren = {
+  @react.component
+  let make = (~children, ~name as _: string) => <jsx> children </jsx>
+}
+// <WithChildren
+//              ^com
+
+// type t = Js.n
+//              ^com
+// type t = ForAuto.
+//                  ^com
+
+type z = Allo | Asterix | Baba
+
+// let q = As
+//           ^com
+
+// module M = For
+//               ^com
+
+module Private = {
+  %%private(let awr = 3)
+  let b = awr
+}
+
+// Private.
+//         ^com
+
+module Shadow = {
+  module A = {
+    let shadowed = 3
+  }
+  module B = {
+    let shadowed = ""
+  }
+}
+
+// sha
+//    ^com
+open Shadow.A
+// sha
+//    ^com
+open Shadow.B
+// sha
+//    ^com
+let _ = shadowed
+
+module FAR = {
+  type forAutoRecord = {forAuto: ForAuto.t, something: option<int>}
+  let forAutoRecord: forAutoRecord = assert(false)
+}
+
+module FAO = {
+  let forAutoObject = {"forAutoLabel": FAR.forAutoRecord, "age": 32}
+}
+
+// FAO.forAutoObject["
+//                    ^com
+
+// FAO.forAutoObject["forAutoLabel"].
+//                                   ^com
+
+// FAO.forAutoObject["forAutoLabel"].forAuto->
+//                                            ^com
+
+// FAO.forAutoObject["forAutoLabel"].forAuto->ForAuto.a
+//                                                     ^com
+
+let name = "abc"
+// let template = `My name is ${na}`
+//                                ^com
+
+let notHere = "      "
+//               ^com
+
+let someR = Some(r)
+let _ = switch someR {
+| Some(_z) => 1
+// + _z.
+//      ^com
+| _ => 3
+}
+
+module SomeLocalModule = {
+  let aa = 10
+  let bb = 20
+  type zz = int
+}
+
+// let _ = SomeLo
+//               ^com
+// type zz = SomeLocalModule.
+//                           ^com
+
+type record = {
+  someProp: string,
+  //  otherProp: SomeLocalModule.
+  //                             ^com
+  thirdProp: string,
+}
+
+type someLocalVariant = SomeLocalVariantItem
+
+// type t = SomeLocal
+//                   ^com
+
+// let _ : SomeLocal
+//                  ^com
+
+let _foo = _world => {
+  // let _ = _w
+  //           ^com
+  3
+}
+
+type someType = {hello: string}
+// type t = SomeType(s)
+//                    ^com
+
+type funRecord = {
+  someFun: (~name: string) => unit,
+  stuff: string,
+}
+
+let funRecord: funRecord = assert(false)
+
+// let _ = funRecord.someFun(~ )
+//                            ^com
+
+let retAA = () => {x: 3, name: ""}
+
+// retAA().
+//         ^com
+
+let ff = (~opt1=0, ~a, ~b, (), ~opt2=0, (), ~c) => a + b + c + opt1 + opt2
+
+// ff(~c=1)(~
+//           ^com
+
+// ff(~c=1)()(~
+//             ^com
+
+// ff(~c=1, ())(~
+//               ^com
+
+// ff(~c=1, (), ())(~
+//                   ^com
+
+// ff(~c=1, (), ~b=1)(~
+//                     ^com
+
+// ff(~opt2=1)(~
+//              ^com
+
+type callback = (~a: int) => int
+
+let withCallback: (~b: int) => callback = (~b) => {
+  ()
+  (~a) => a + b
+}
+
+// withCallback(~
+//               ^com
+
+// withCallback(~a)(~
+//                   ^com
+
+// withCallback(~b)(~
+//                   ^com
+
+let _ =
+  <div
+    onClick={_ => {
+      ()
+      //        let _: Res
+      //                  ^com
+    }}
+    name="abc">
+    {React.string(name)}
+  </div>
+
+//let _ = switch Some(3) { | Some(thisIsNotSaved) -> this
+//                                                       ^com
+
+let _ = <div name="" />
+//            ^hov
+
+// let _ = FAO.forAutoObject["age"]
+//               ^hov
+
+// let _ = ff(~opt1=3)
+//               ^hov
+
+// (let _ = ff(~opt1=3))
+//                     ^com
+
+type v = This | That
+
+let _ = x =>
+  switch x {
+  // | T
+  //    ^com
+  | _ => 4
+  }
+
+module AndThatOther = {
+  type v = And | ThatOther
+}
+
+let _ = x =>
+  switch x {
+  // | AndThatOther.T
+  //                 ^com
+  | _ => 4
+  }
+
+// let _  = ` ${ForAuto.}`
+//                      ^com
+
+// let _  = `abc ${FAO.forAutoObject[""}`
+//                                    ^com
+
+// let _ = `${funRecord.}`
+//                      ^com
+
+let _ = _ => {
+  open Js
+  //  []->ma
+  //        ^com
+  ()
+}
+
+let red = "#ff0000"
+
+let header1 = `
+    color: ${red}; `
+//            ^com
+
+let header2 = `
+    color: ${red};
+    background-color: ${red}; `
+//                       ^com
+
+// let _ = `color: ${r
+//                    ^com
+
+let onClick = evt => {
+  // SomeLocalModule.
+  //                 ^com
+  evt->ReactEvent.Synthetic.preventDefault
+  // SomeLocalModule.
+  //                 ^com
+  Js.log("Hello")
+}
+
+// let _ = 123->t
+//               ^com
+
+// let _ = 123.0->t
+//                 ^com
+
+let ok = Ok(true)
+
+// ok->g
+//      ^com
+
+type someRecordWithDeprecatedField = {
+  name: string,
+  @deprecated
+  someInt: int,
+  @deprecated("Use 'someInt'.")
+  someFloat: float,
+}
+
+let rWithDepr: someRecordWithDeprecatedField = {
+  name: "hej",
+  someInt: 12,
+  someFloat: 12.,
+}
+
+// Should show deprecated status
+// rWithDepr.so
+//             ^com
+
+type someVariantWithDeprecated =
+  | @deprecated DoNotUseMe | UseMeInstead | @deprecated("Use 'UseMeInstead'") AndNotMe
+
+// Should show deprecated status
+// let v: someVariantWithDeprecated =
+//                                   ^com
+
+let uncurried = num => num + 2
+
+// let _ = uncurried(. 1)->toS
+//                            ^com
+
+type withUncurried = {fn: int => unit}
+
+// let f: withUncurried = {fn: }
+//                            ^com
+
+// let someRecord = { FAR. }
+//                        ^com
diff --git a/tests/analysis_tests/tests/src/CompletionAttributes.res b/tests/analysis_tests/tests/src/CompletionAttributes.res
new file mode 100644
index 0000000000..674580a9eb
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionAttributes.res
@@ -0,0 +1,39 @@
+// @modu
+//      ^com
+
+// @module("") external doStuff: t = "test"
+//          ^com
+
+// @@js
+//     ^com
+
+// @@jsxConfig({})
+//              ^com
+
+// @@jsxConfig({m})
+//               ^com
+
+// @@jsxConfig({module_: })
+//                       ^com
+
+// @@jsxConfig({module_: "", })
+//                           ^com
+
+// @module({}) external doStuff: t = "default"
+//          ^com
+
+// @module({with: }) external doStuff: t = "default"
+//               ^com
+
+// @module({with: {}}) external doStuff: t = "default"
+//                 ^com
+
+// @module({from: "" }) external doStuff: t = "default"
+//                 ^com
+
+// @module({from: }) external doStuff: t = "default"
+//               ^com
+
+// let dd = %t
+//            ^com
+
diff --git a/tests/analysis_tests/tests/src/CompletionDicts.res b/tests/analysis_tests/tests/src/CompletionDicts.res
new file mode 100644
index 0000000000..fff57d79ad
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionDicts.res
@@ -0,0 +1,16 @@
+// let dict = Js.Dict.fromArray([])
+//                               ^com
+
+// let dict = Js.Dict.fromArray([()])
+//                                ^com
+
+// let dict = Js.Dict.fromArray([("key", )])
+//                                      ^com
+
+// ^in+
+let dict = Js.Dict.fromArray([
+  ("key", true),
+  //  ("key2", )
+  //          ^com
+])
+// ^in-
diff --git a/tests/analysis_tests/tests/src/CompletionExpressions.res b/tests/analysis_tests/tests/src/CompletionExpressions.res
new file mode 100644
index 0000000000..d8ce1ebe26
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionExpressions.res
@@ -0,0 +1,390 @@
+let s = true
+let f = Some([false])
+
+// switch (s, f) { | }
+//                  ^com
+
+type otherRecord = {
+  someField: int,
+  otherField: string,
+}
+
+type rec someRecord = {
+  age: int,
+  offline: bool,
+  online: option<bool>,
+  variant: someVariant,
+  polyvariant: somePolyVariant,
+  nested: option<otherRecord>,
+}
+and someVariant = One | Two | Three(int, string)
+and somePolyVariant = [#one | #two(bool) | #three(someRecord, bool)]
+
+let fnTakingRecord = (r: someRecord) => {
+  ignore(r)
+}
+
+// let _ = fnTakingRecord({})
+//                         ^com
+
+// let _ = fnTakingRecord({n})
+//                          ^com
+
+// let _ = fnTakingRecord({offline: })
+//                                 ^com
+
+// let _ = fnTakingRecord({age: 123, })
+//                                  ^com
+
+// let _ = fnTakingRecord({age: 123,  offline: true})
+//                                   ^com
+
+// let _ = fnTakingRecord({age: 123, nested: })
+//                                          ^com
+
+// let _ = fnTakingRecord({age: 123, nested: {}})
+//                                            ^com
+
+// let _ = fnTakingRecord({age: 123, nested: Some({})})
+//                                                 ^com
+
+// let _ = fnTakingRecord({age: 123, variant: })
+//                                           ^com
+
+// let _ = fnTakingRecord({age: 123, variant: O })
+//                                             ^com
+
+// let _ = fnTakingRecord({age: 123, polyvariant: #three() })
+//                                                       ^com
+
+// let _ = fnTakingRecord({age: 123, polyvariant: #three({}, ) })
+//                                                          ^com
+
+// let _ = fnTakingRecord({age: 123, polyvariant: #three({}, t) })
+//                                                            ^com
+
+let fnTakingArray = (arr: array<option<bool>>) => {
+  ignore(arr)
+}
+
+// let _ = fnTakingArray()
+//                       ^com
+
+// let _ = fnTakingArray([])
+//                        ^com
+
+// let _ = fnTakingArray(s)
+//                        ^com
+
+// let _ = fnTakingArray([Some()])
+//                             ^com
+
+// let _ = fnTakingArray([None, ])
+//                             ^com
+
+// let _ = fnTakingArray([None, , None])
+//                             ^com
+
+let someBoolVar = true
+
+// let _ = fnTakingRecord({offline: so })
+//                                    ^com
+
+let fnTakingOtherRecord = (r: otherRecord) => {
+  ignore(r)
+}
+
+// let _ = fnTakingOtherRecord({otherField: })
+//                                         ^com
+
+type recordWithOptionalField = {
+  someField: int,
+  someOptField?: bool,
+}
+
+let fnTakingRecordWithOptionalField = (r: recordWithOptionalField) => {
+  ignore(r)
+}
+
+// let _ = fnTakingRecordWithOptionalField({someOptField: })
+//                                                       ^com
+type recordWithOptVariant = {someVariant: option<someVariant>}
+
+let fnTakingRecordWithOptVariant = (r: recordWithOptVariant) => {
+  ignore(r)
+}
+
+// let _ = fnTakingRecordWithOptVariant({someVariant: })
+//                                                   ^com
+
+type variantWithInlineRecord =
+  WithInlineRecord({someBoolField: bool, otherField: option<bool>, nestedRecord: otherRecord})
+
+let fnTakingInlineRecord = (r: variantWithInlineRecord) => {
+  ignore(r)
+}
+
+// let _ = fnTakingInlineRecord(WithInlineRecord())
+//                                               ^com
+
+// let _ = fnTakingInlineRecord(WithInlineRecord({}))
+//                                                ^com
+
+// let _ = fnTakingInlineRecord(WithInlineRecord({s}))
+//                                                 ^com
+
+// let _ = fnTakingInlineRecord(WithInlineRecord({nestedRecord: }))
+//                                                             ^com
+
+// let _ = fnTakingInlineRecord(WithInlineRecord({nestedRecord: {} }))
+//                                                               ^com
+
+type variant = First | Second(bool)
+
+let fnTakingCallback = (
+  cb: unit => unit,
+  cb2: bool => unit,
+  cb3: ReactEvent.Mouse.t => unit,
+  cb4: (~on: bool, ~off: bool=?, variant) => int,
+  cb5: (bool, option<bool>, bool) => unit,
+  cb6: (~on: bool=?, ~off: bool=?, unit) => int,
+) => {
+  let _ = cb
+  let _ = cb2
+  let _ = cb3
+  let _ = cb4
+  let _ = cb5
+  let _ = cb6
+}
+
+// fnTakingCallback()
+//                  ^com
+
+// fnTakingCallback(a)
+//                   ^com
+
+// fnTakingCallback(a, )
+//                    ^com
+
+// fnTakingCallback(a, b, )
+//                       ^com
+
+// fnTakingCallback(a, b, c, )
+//                           ^com
+
+// fnTakingCallback(a, b, c, d, )
+//                              ^com
+
+// fnTakingCallback(a, b, c, d, e, )
+//                                ^com
+
+let something = {
+  let second = true
+  let second2 = 1
+  ignore(second)
+  ignore(second2)
+  Js.log(s)
+  //      ^com
+}
+
+let fff: recordWithOptionalField = {
+  someField: 123,
+  someOptField: true,
+}
+
+ignore(fff)
+
+// fff.someOpt
+//            ^com
+
+type someTyp = {test: bool}
+
+let takesCb = cb => {
+  cb({test: true})
+}
+
+// takesCb()
+//         ^com
+
+module Environment = {
+  type t = {hello: bool}
+}
+
+let takesCb2 = cb => {
+  cb({Environment.hello: true})
+}
+
+// takesCb2()
+//          ^com
+
+type apiCallResult = {hi: bool}
+
+let takesCb3 = cb => {
+  cb({hi: true})
+}
+
+// takesCb3()
+//          ^com
+
+let takesCb4 = cb => {
+  cb(Some({hi: true}))
+}
+
+// takesCb4()
+//          ^com
+
+let takesCb5 = cb => {
+  cb([Some({hi: true})])
+}
+
+// takesCb5()
+//          ^com
+
+module RecordSourceSelectorProxy = {
+  type t
+}
+
+@val
+external commitLocalUpdate: (~updater: RecordSourceSelectorProxy.t => unit) => unit =
+  "commitLocalUpdate"
+
+// commitLocalUpdate(~updater=)
+//                            ^com
+
+let fnTakingAsyncCallback = (cb: unit => promise<unit>) => {
+  let _ = cb
+}
+
+// fnTakingAsyncCallback()
+//                       ^com
+
+let arr = ["hello"]
+
+// arr->Belt.Array.map()
+//                     ^com
+
+type exoticPolyvariant = [#"some exotic"]
+
+let takesExotic = (e: exoticPolyvariant) => {
+  ignore(e)
+}
+
+// takesExotic()
+//             ^com
+
+let fnTakingPolyVariant = (a: somePolyVariant) => {
+  ignore(a)
+}
+
+// fnTakingPolyVariant()
+//                     ^com
+
+// fnTakingPolyVariant(#)
+//                      ^com
+
+// fnTakingPolyVariant(#o)
+//                       ^com
+
+// fnTakingPolyVariant(o)
+//                      ^com
+
+module SuperInt: {
+  type t
+  let increment: (t, int) => t
+  let decrement: (t, int => int) => t
+  let make: int => t
+  let toInt: t => int
+} = {
+  type t = int
+  let increment = (t, num) => t + num
+  let decrement = (t, decrementer) => decrementer(t)
+  let make = t => t
+  let toInt = t => t
+}
+
+type withIntLocal = {superInt: SuperInt.t}
+
+// let withInt: withIntLocal = {superInt: }
+//                                       ^com
+
+// CompletionSupport.makeTestHidden()
+//                                  ^com
+
+open CompletionSupport
+// CompletionSupport.makeTestHidden()
+//                                  ^com
+
+let mkStuff = (r: Js.Re.t) => {
+  ignore(r)
+  "hello"
+}
+
+// mkStuff()
+//         ^com
+
+module Money: {
+  type t
+
+  let zero: t
+
+  let nonTType: string
+
+  let make: unit => t
+
+  let fromInt: int => t
+
+  let plus: (t, t) => t
+} = {
+  type t = string
+
+  let zero: t = "0"
+
+  let nonTType = "0"
+
+  let make = (): t => zero
+
+  let fromInt = (int): t => int->Js.Int.toString
+
+  let plus = (m1, _) => m1
+}
+
+let tArgCompletionTestFn = (_tVal: Money.t) => ()
+
+// tArgCompletionTestFn()
+//                      ^com
+
+let labeledTArgCompletionTestFn = (~tVal as _: Money.t) => ()
+
+// labeledTArgCompletionTestFn(~tVal=)
+//                                   ^com
+
+let someTyp: someTyp = {test: true}
+
+// switch someTyp. { | _ => () }
+//                ^com
+
+type config = {
+  includeName: bool,
+  operator?: [#"and" | #or],
+  showMore: bool,
+}
+
+type hookReturn = {name: string}
+
+let hook = (config: config) => {
+  ignore(config)
+  {
+    name: "tester",
+  }
+}
+
+let {name} = hook({
+  //                  ^com
+  // ope
+  //    ^com
+  includeName: true,
+  showMore: true,
+})
+
+// switch someTyp. { | }
+//                ^com
diff --git a/tests/analysis_tests/tests/src/CompletionFunctionArguments.res b/tests/analysis_tests/tests/src/CompletionFunctionArguments.res
new file mode 100644
index 0000000000..5000ee49c6
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionFunctionArguments.res
@@ -0,0 +1,123 @@
+let someFn = (~isOn, ~isOff=false, ()) => {
+  if isOn && !isOff {
+    "on"
+  } else {
+    "off"
+  }
+}
+
+let tLocalVar = false
+
+// let _ = someFn(~isOn=)
+//                      ^com
+
+// let _ = someFn(~isOn=t)
+//                       ^com
+
+// let _ = someFn(~isOff=)
+//                       ^com
+
+let _ = @res.partial someFn(
+  ~isOn={
+    // switch someFn(~isOn=)
+    //                     ^com
+    true
+  },
+)
+
+let someOtherFn = (includeName, age, includeAge) => {
+  "Hello" ++
+  (includeName ? " Some Name" : "") ++
+  ", you are age " ++
+  Belt.Int.toString(includeAge ? age : 0)
+}
+
+// let _ = someOtherFn(f)
+//                      ^com
+
+module OIncludeMeInCompletions = {}
+
+type someVariant = One | Two | Three(int, string)
+
+let someFnTakingVariant = (
+  configOpt: option<someVariant>,
+  ~configOpt2=One,
+  ~config: someVariant,
+) => {
+  ignore(config)
+  ignore(configOpt)
+  ignore(configOpt2)
+}
+
+// let _ = someFnTakingVariant(~config=)
+//                                     ^com
+
+// let _ = someFnTakingVariant(~config=O)
+//                                      ^com
+
+// let _ = someFnTakingVariant(So)
+//                               ^com
+
+// let _ = someFnTakingVariant(~configOpt2=O)
+//                                          ^com
+
+// let _ = someOtherFn()
+//                     ^com
+
+// let _ = someOtherFn(1, 2, )
+//                          ^com
+
+// let _ = 1->someOtherFn(1, t)
+//                            ^com
+
+let fnTakingTuple = (arg: (int, int, float)) => {
+  ignore(arg)
+}
+
+// let _ = fnTakingTuple()
+//                       ^com
+
+type someRecord = {
+  age: int,
+  offline: bool,
+  online: option<bool>,
+}
+
+let fnTakingRecord = (r: someRecord) => {
+  ignore(r)
+}
+
+// let _ = fnTakingRecord({})
+//                         ^com
+
+module FineModule = {
+  type t = {
+    online: bool,
+    somethingElse: string,
+  }
+
+  let setToFalse = (t: t) => {
+    ...t,
+    online: false,
+  }
+}
+
+let _ =
+  <div
+    onMouseDown={thisGetsBrokenLoc => {
+      let reassignedWorks = thisGetsBrokenLoc
+      ignore(reassignedWorks)
+      // thisGetsBrokenLoc->a
+      //                     ^com
+      // reassignedWorks->a
+      //                   ^com
+    }}
+  />
+
+let fineModuleVal = {
+  FineModule.online: true,
+  somethingElse: "",
+}
+
+// makeItem(~changefreq=Monthly, ~lastmod=fineModuleVal->, ~priority=Low)
+//                                                       ^com
diff --git a/tests/analysis_tests/tests/src/CompletionInferValues.res b/tests/analysis_tests/tests/src/CompletionInferValues.res
new file mode 100644
index 0000000000..f0edbfe8c8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionInferValues.res
@@ -0,0 +1,169 @@
+let getBool = () => true
+let getInt = () => 123
+
+type someRecord = {name: string, age: int}
+
+let someFnWithCallback = (cb: (~num: int, ~someRecord: someRecord, ~isOn: bool) => unit) => {
+  let _ = cb
+}
+
+let reactEventFn = (cb: ReactEvent.Mouse.t => unit) => {
+  let _ = cb
+}
+
+@val external getSomeRecord: unit => someRecord = "getSomeRecord"
+
+// let x = 123; let aliased = x; aliased->f
+//                                         ^com
+
+// let x = getSomeRecord(); x.
+//                            ^com
+
+// let x = getSomeRecord(); let aliased = x; aliased.
+//                                                   ^com
+
+// someFnWithCallback((~someRecord, ~num, ~isOn) => someRecord.)
+//                                                             ^com
+
+// let aliasedFn = someFnWithCallback; aliasedFn((~num, ~someRecord, ~isOn) => someRecord.)
+//                                                                                        ^com
+
+// reactEventFn(event => { event->pr });
+//                                  ^com
+
+module Div = {
+  @react.component
+  let make = (~onMouseEnter: option<JsxEvent.Mouse.t => unit>=?) => {
+    let _ = onMouseEnter
+    React.null
+  }
+}
+
+// let _ = <div onMouseEnter={event => { event->pr }} />
+//                                                ^com
+
+// let _ = <Div onMouseEnter={event => { event->pr }} />
+//                                                ^com
+
+// let _ = <div onMouseEnter={event => { let btn = event->JsxEvent.Mouse.button; btn->t }} />
+//                                                                                     ^com
+
+// let _ = <div onMouseEnter={event => { let btn = event->JsxEvent.Mouse.button->Belt.Int.toString; btn->spl }} />
+//                                                                                                          ^com
+
+// let _ = <div onMouseEnter={event => { let btn = event->JsxEvent.Mouse.button->Belt.Int.toString->Js.String2.split("/"); btn->ma }} />
+//                                                                                                                                ^com
+
+// let x: someRecord = {name: "Hello", age: 123}; x.
+//                                                  ^com
+
+type someVariant = One | Two | Three(int, string)
+type somePolyVariant = [#one | #two | #three(int, string)]
+type someNestedRecord = {someRecord: someRecord}
+
+type someRecordWithNestedStuff = {
+  things: string,
+  someInt: int,
+  srecord: someRecord,
+  nested: someNestedRecord,
+  someStuff: bool,
+}
+
+type otherNestedRecord = {
+  someRecord: someRecord,
+  someTuple: (someVariant, int, somePolyVariant),
+  optRecord: option<someRecord>,
+}
+
+// Destructure record
+// let x: someRecordWithNestedStuff = Obj.magic(); let {srecord} = x; srecord.
+//                                                                            ^com
+
+// Follow aliased
+// let x: someRecordWithNestedStuff = Obj.magic(); let {nested: aliased} = x; aliased.
+//                                                                                    ^com
+
+// Follow nested record
+// let x: someRecordWithNestedStuff = Obj.magic(); let {srecord, nested: {someRecord}} = x; someRecord.
+//                                                                                                     ^com
+
+// Destructure string
+// let x: someRecordWithNestedStuff = Obj.magic(); let {things} = x; things->slic
+//                                                                               ^com
+
+// Destructure int
+// let x: someRecordWithNestedStuff = Obj.magic(); let {someInt} = x; someInt->toS
+//                                                                                ^com
+
+// Follow tuples
+// let x: otherNestedRecord = Obj.magic(); let {someTuple} = x; let (_, someInt, _) = someTuple; someInt->toS
+//                                                                                                           ^com
+
+// Same as above, but follow in switch case
+// let x: otherNestedRecord; switch x { | {someTuple} => let (_, someInt, _) = someTuple; someInt->toS }
+//                                                                                                    ^com
+
+// Follow variant payloads
+// let x: otherNestedRecord; switch x { | {someTuple:(Three(_, str), _, _)} => str->slic }
+//                                                                                      ^com
+
+// Follow polyvariant payloads
+// let x: otherNestedRecord; switch x { | {someTuple:(_, _, #three(_, str))} => str->slic }
+//                                                                                       ^com
+
+// Follow options
+// let x: otherNestedRecord; switch x { | {optRecord:Some({name})} => name->slic }
+//                                                                              ^com
+
+// Follow arrays
+// let x: array<otherNestedRecord>; switch x { | [inner] => inner.s }
+//                                                                 ^com
+
+// Infer top level return
+// let x = 123; switch x { | 123 => () | v => v->toSt }
+//                                                   ^com
+
+let fnWithRecordCallback = (cb: someRecord => unit) => {
+  let _ = cb
+}
+
+// Complete pattern of function parameter
+// fnWithRecordCallback(({}) => {()})
+//                        ^com
+
+let fn2 = (~cb: CompletionSupport.Nested.config => unit) => {
+  let _ = cb
+}
+
+// fn2(~cb=({root}) => {root-> })
+//                            ^com
+
+type sameFileRecord = {root: CompletionSupport.Test.t, test: int}
+
+let fn3 = (~cb: sameFileRecord => unit) => {
+  let _ = cb
+}
+
+// fn3(~cb=({root}) => {root-> })
+//                            ^com
+
+// Handles pipe chains as input for switch
+// let x = 123; switch x->Belt.Int.toString { | }
+//                                             ^com
+
+// Handles pipe chains as input for switch
+// let x = 123; switch x->Belt.Int.toString->Js.String2.split("/") { | }
+//                                                                    ^com
+
+// Regular completion works
+// let renderer = CompletionSupport2.makeRenderer(~prepare=() => "hello",~render=({support}) => {support.},())
+//                                                                                                       ^com
+
+// But pipe completion gets the wrong completion path. Should be `ReactDOM.Client.Root.t`, but ends up being `CompletionSupport2.ReactDOM.Client.Root.t`.
+// let renderer = CompletionSupport2.makeRenderer(~prepare=() => "hello",~render=({support:{root}}) => {root->},())
+//                                                                                                            ^com
+
+// Handles reusing the same name already in scope for bindings
+let res = 1
+// switch res { | res => res }
+//                         ^hov
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res
new file mode 100644
index 0000000000..a525974966
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionJsx.res
@@ -0,0 +1,91 @@
+let someString = "hello"
+ignore(someString)
+
+// someString->st
+//               ^com
+
+module SomeComponent = {
+  @react.component
+  let make = (~someProp) => {
+    let someInt = 12
+    let someArr = [React.null]
+    ignore(someInt)
+    ignore(someArr)
+    // someString->st
+    //               ^com
+    <div>
+      {React.string(someProp)}
+      <div> {React.null} </div>
+      // {someString->st}
+      //                ^com
+      // {"Some string"->st}
+      //                   ^com
+      // {"Some string"->Js.String2.trim->st}
+      //                                    ^com
+      // {someInt->}
+      //           ^com
+      // {12->}
+      //      ^com
+      // {someArr->a}
+      //            ^com
+      // <di
+      //    ^com
+    </div>
+  }
+}
+
+module CompWithoutJsxPpx = {
+  type props = {name: string}
+
+  let make = ({name}) => {
+    ignore(name)
+    React.null
+  }
+}
+
+// <CompWithoutJsxPpx n
+//                     ^com
+
+// <SomeComponent someProp=>
+//                         ^com
+
+// <h1 hidd
+//         ^com
+
+module IntrinsicElementLowercase = {
+  type props = {name?: string, age?: int}
+
+  @module("react")
+  external make: (@as("mesh") _, props) => Jsx.element = "createElement"
+}
+
+// <IntrinsicElementLowercase
+//                            ^com
+
+module MultiPropComp = {
+  type time = Now | Later
+  @react.component
+  let make = (~name, ~age, ~time: time) => {
+    ignore(time)
+    name ++ age
+  }
+}
+
+// <MultiPropComp name="Hello" time= age="35"
+//                                  ^com
+
+// <MultiPropComp name="Hello" time= age
+//                                  ^com
+
+// <MultiPropComp name time= age
+//                          ^com
+
+module Info = {
+  @react.component
+  let make = (~_type: [#warning | #info]) => {
+    React.string((_type :> string))
+  }
+}
+
+// <Info _type={#warning} >
+//                        ^com
diff --git a/tests/analysis_tests/tests/src/CompletionJsxProps.res b/tests/analysis_tests/tests/src/CompletionJsxProps.res
new file mode 100644
index 0000000000..7562778b3f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionJsxProps.res
@@ -0,0 +1,46 @@
+// let _ = <CompletionSupport.TestComponent on=
+//                                             ^com
+
+// let _ = <CompletionSupport.TestComponent on=t
+//                                              ^com
+
+// let _ = <CompletionSupport.TestComponent test=T
+//                                                ^com
+
+// let _ = <CompletionSupport.TestComponent polyArg=
+//                                                  ^com
+
+// let _ = <CompletionSupport.TestComponent polyArg=#t
+//                                                    ^com
+
+// let _ = <div muted= />
+//                    ^com
+
+// let _ = <div onMouseEnter= />
+//                           ^com
+
+// Should wrap in {}
+// let _ = <CompletionSupport.TestComponent testArr=
+//                                                  ^com
+
+// Should not wrap in {}
+// let _ = <CompletionSupport.TestComponent testArr={[]}
+//                                                    ^com
+
+let tsomeVar = #two
+
+// let _ = <CompletionSupport.TestComponent polyArg={}
+//                                                   ^com
+
+// let _ = <CompletionSupport.TestComponent on={t}
+//                                               ^com
+
+@@jsxConfig({version: 4, mode: "automatic"})
+
+module CompletableComponentLazy = {
+  let loadComponent = () => Js.import(CompletableComponent.make)
+  let make = React.lazy_(loadComponent)
+}
+
+// let _ = <CompletableComponentLazy status=
+//                                          ^com
diff --git a/tests/analysis_tests/tests/src/CompletionPattern.res b/tests/analysis_tests/tests/src/CompletionPattern.res
new file mode 100644
index 0000000000..e3fe6d342e
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionPattern.res
@@ -0,0 +1,244 @@
+let v = (true, Some(false), (true, true))
+
+let _ = switch v {
+| (true, _, _) => 1
+| _ => 2
+}
+
+// switch v {
+//           ^com
+
+// switch v { | }
+//             ^com
+
+// switch v { | (t, _) }
+//                ^com
+
+// switch v { | (_, _, (f, _)) }
+//                       ^com
+
+let x = true
+
+// switch x { |
+//             ^com
+
+// switch x { | t
+//               ^com
+
+type nestedRecord = {nested: bool}
+
+type rec someRecord = {
+  first: int,
+  second: (bool, option<someRecord>),
+  optThird: option<[#first | #second(someRecord)]>,
+  nest: nestedRecord,
+}
+
+let f: someRecord = {
+  first: 123,
+  second: (true, None),
+  optThird: None,
+  nest: {nested: true},
+}
+
+let z = (f, true)
+ignore(z)
+
+// switch f { | }
+//             ^com
+
+// switch f { | {}}
+//               ^com
+
+// switch f { | {first,  , second }}
+//                      ^com
+
+// switch f { | {fi}}
+//                 ^com
+
+// switch z { | ({o}, _)}
+//                 ^com
+
+// switch f { | {nest: }}
+//                    ^com
+
+// switch f { | {nest: {}}}
+//                      ^com
+
+let _ = switch f {
+| {first: 123, nest} =>
+  ()
+  // switch nest { | {}}
+  //                  ^com
+  nest.nested
+| _ => false
+}
+
+// let {} = f
+//      ^com
+
+// let {nest: {n}}} = f
+//              ^com
+
+type someVariant = One | Two(bool) | Three(someRecord, bool)
+
+let z = Two(true)
+ignore(z)
+
+// switch z { | Two()}
+//                  ^com
+
+// switch z { | Two(t)}
+//                   ^com
+
+// switch z { | Three({})}
+//                     ^com
+
+// switch z { | Three({}, t)}
+//                         ^com
+
+type somePolyVariant = [#one | #two(bool) | #three(someRecord, bool)]
+let b: somePolyVariant = #two(true)
+ignore(b)
+
+// switch b { | #two()}
+//                   ^com
+
+// switch b { | #two(t)}
+//                    ^com
+
+// switch b { | #three({})}
+//                      ^com
+
+// switch b { | #three({}, t)}
+//                          ^com
+
+let c: array<bool> = []
+ignore(c)
+
+// switch c { | }
+//             ^com
+
+// switch c { | [] }
+//               ^com
+
+let o = Some(true)
+ignore(o)
+
+// switch o { | Some() }
+//                   ^com
+
+type multiPayloadVariant = Test(int, bool, option<bool>, array<bool>)
+
+let p = Test(1, true, Some(false), [])
+
+// switch p { | Test(1, )}
+//                     ^com
+
+// switch p { | Test(1, true, )}
+//                           ^com
+
+// switch p { | Test(1, , None)}
+//                     ^com
+
+// switch p { | Test(1, true, None, )}
+//                                 ^com
+
+type multiPayloadPolyVariant = [#test(int, bool, option<bool>, array<bool>)]
+
+let v: multiPayloadPolyVariant = #test(1, true, Some(false), [])
+
+// switch v { | #test(1, )}
+//                      ^com
+
+// switch v { | #test(1, true, )}
+//                            ^com
+
+// switch v { | #test(1, , None)}
+//                      ^com
+
+// switch v { | #test(1, true, None, )}
+//                                  ^com
+
+let s = (true, Some(true), [false])
+
+// switch s { | () }
+//               ^com
+
+// switch s { | (true, ) }
+//                     ^com
+
+// switch s { | (true, , []) }
+//                    ^com
+
+// switch s { | (true, []) => () |  }
+//                                 ^com
+
+// switch s { | (true, []) => () | (true, , [])  }
+//                                       ^com
+
+// switch z { | One |  }
+//                   ^com
+
+// switch z { | One | Two(true | )  }
+//                              ^com
+
+// switch z { | One | Three({test: true}, true | )  }
+//                                              ^com
+
+// switch b { | #one | #two(true | )  }
+//                                ^com
+
+// switch b { | #one | #three({test: true}, true | )  }
+//                                                ^com
+
+// switch s { | (true, _, []) }
+//                      ^com
+
+type recordWithFn = {someFn: unit => unit}
+
+let ff: recordWithFn = {someFn: () => ()}
+
+// switch ff { | {someFn: }}
+//                       ^com
+
+let xn: exn = Obj.magic()
+
+// switch xn { | }
+//              ^com
+
+let getThing = async () => One
+
+// switch await getThing() { | }
+//                            ^com
+
+let res: result<someVariant, somePolyVariant> = Ok(One)
+
+// switch res { | Ok() }
+//                   ^com
+
+// switch res { | Error() }
+//                      ^com
+
+@react.component
+let make = (~thing: result<someVariant, unit>) => {
+  switch thing {
+  | Ok(Three(r, _)) =>
+    let _x = r
+  // switch r { | {first, }}
+  //                     ^com
+  | _ => ()
+  }
+}
+
+type results = {
+  query: string,
+  nbHits: int,
+}
+
+type hitsUse = {results: results, hits: array<string>}
+
+let hitsUse = (): hitsUse => Obj.magic()
+
+// let {results: {query, nbHits}, } = hitsUse()
+//                               ^com
diff --git a/tests/analysis_tests/tests/src/CompletionPipeChain.res b/tests/analysis_tests/tests/src/CompletionPipeChain.res
new file mode 100644
index 0000000000..2a16c21cfa
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionPipeChain.res
@@ -0,0 +1,105 @@
+module Integer: {
+  type t
+  let increment: (t, int) => t
+  let decrement: (t, int => int) => t
+  let make: int => t
+  let toInt: t => int
+} = {
+  type t = int
+  let increment = (t, num) => t + num
+  let decrement = (t, decrementer) => decrementer(t)
+  let make = t => t
+  let toInt = t => t
+}
+
+module SuperFloat: {
+  type t
+  let fromInteger: Integer.t => t
+  let toInteger: t => Integer.t
+} = {
+  type t = float
+  let fromInteger = t => t->Integer.toInt->Belt.Float.fromInt
+  let toInteger = t => t->Belt.Float.toInt->Integer.make
+}
+
+let toFlt = i => i->SuperFloat.fromInteger
+let int = Integer.make(1)
+let f = int->Integer.increment(2)
+// let _ = int->
+//              ^com
+
+// let _ = int->toFlt->
+//                     ^com
+
+// let _ = int->Integer.increment(2)->
+//                                    ^com
+
+// let _ = Integer.increment(int, 2)->
+//                                    ^com
+
+// let _ = int->Integer.decrement(t => t - 1)->
+//                                             ^com
+
+// let _ = int->Integer.increment(2)->Integer.decrement(t => t - 1)->
+//                                                                   ^com
+
+// let _ = int->Integer.increment(2)->SuperFloat.fromInteger->
+//                                                            ^com
+
+// let _ = int->Integer.increment(2)->SuperFloat.fromInteger->t
+//                                                             ^com
+
+// let _ = int->Integer.increment(2)->Integer.toInt->CompletionSupport.Test.make->
+//                                                                                ^com
+
+// let _ = CompletionSupport.Test.make(1)->CompletionSupport.Test.addSelf(2)->
+//                                                                            ^com
+
+let _ = [123]->Js.Array2.forEach(v => Js.log(v))
+// ->
+//   ^com
+
+let _ = [123]->Belt.Array.reduce(0, (acc, curr) => acc + curr)
+// ->t
+//    ^com
+
+type aliasedType = CompletionSupport.Test.t
+
+let aliased: aliasedType = {name: 123}
+let notAliased: CompletionSupport.Test.t = {name: 123}
+
+// aliased->
+//          ^com
+
+// notAliased->
+//             ^com
+
+let renderer = CompletionSupport2.makeRenderer(
+  ~prepare=() => "hello",
+  ~render=props => {
+    ignore(props)
+
+    // Doesn't work when tried through this chain. Presumably because it now goes through multiple different files.
+    // props.support.root->ren
+    //                        ^com
+    let root = props.support.root
+    ignore(root)
+
+    // Works here though when it's lifted out. Probably because it only goes through one file...?
+    // root->ren
+    //          ^com
+    React.null
+  },
+  (),
+)
+
+// Console.log(int->)
+//                  ^com
+
+// Console.log(int->t)
+//                   ^com
+
+let r = %re("/t/g")
+
+// r->la
+//      ^com
diff --git a/tests/analysis_tests/tests/src/CompletionPipeSubmodules.res b/tests/analysis_tests/tests/src/CompletionPipeSubmodules.res
new file mode 100644
index 0000000000..a3ee0af8dd
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionPipeSubmodules.res
@@ -0,0 +1,45 @@
+module A = {
+  module B1 = {
+    type b1 = B1
+    let xx = B1
+  }
+  module B2 = {
+    let yy = 20
+  }
+  type t = {v: B1.b1}
+  let x = {v: B1.B1}
+}
+
+// let _ = A.B1.xx->
+//                  ^com
+// b1 seen from B1 is A.B1.b1
+
+// let _ = A.x.v->
+//                ^com
+// B1.b1 seen from A  is A.B1.b1
+
+module C = {
+  type t = C
+}
+
+module D = {
+  module C2 = {
+    type t2 = C2
+  }
+
+  type d = {v: C.t, v2: C2.t2}
+  let d = {v: C.C, v2: C2.C2}
+}
+
+module E = {
+  type e = {v: D.d}
+  let e = {v: D.d}
+}
+
+// let _ = E.e.v.v->
+//                  ^com
+// C.t seen from D is C.t
+
+// let _ = E.e.v.v2->
+//                   ^com
+// C2.t2 seen from D is D.C2.t2
diff --git a/tests/analysis_tests/tests/src/CompletionResolve.res b/tests/analysis_tests/tests/src/CompletionResolve.res
new file mode 100644
index 0000000000..187e00046f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionResolve.res
@@ -0,0 +1,4 @@
+// ^cre Belt_Array
+
+// ^cre ModuleStuff
+
diff --git a/tests/analysis_tests/tests/src/CompletionSupport.res b/tests/analysis_tests/tests/src/CompletionSupport.res
new file mode 100644
index 0000000000..3c15a0a976
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionSupport.res
@@ -0,0 +1,42 @@
+module Test = {
+  type t = {name: int}
+  let add = (ax: t) => ax.name + 1
+  let addSelf = (ax: t) => {name: ax.name + 1}
+  let make = (name: int): t => {name: name}
+}
+
+module TestHidden: {
+  type t
+  let make: int => t
+  let self: t => t
+} = {
+  type t = {name: int}
+  let make = (name: int): t => {name: name}
+  let self = t => t
+}
+
+type testVariant = One | Two | Three(int)
+
+module TestComponent = {
+  @react.component
+  let make = (
+    ~on: bool,
+    ~test: testVariant,
+    ~testArr: array<testVariant>,
+    ~polyArg: option<[#one | #two | #two2 | #three(int, bool)]>=?,
+  ) => {
+    ignore(on)
+    ignore(test)
+    ignore(testArr)
+    ignore(polyArg)
+    React.null
+  }
+}
+
+module Nested = {
+  type config = {root: ReactDOM.Client.Root.t}
+}
+
+type options = {test: TestHidden.t}
+
+let makeTestHidden = t => TestHidden.self(t)
diff --git a/tests/analysis_tests/tests/src/CompletionSupport2.res b/tests/analysis_tests/tests/src/CompletionSupport2.res
new file mode 100644
index 0000000000..925fc5d57a
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionSupport2.res
@@ -0,0 +1,17 @@
+module Internal = {
+  type prepareProps<'prepared> = {
+    someName: string,
+    support: CompletionSupport.Nested.config,
+    prepared: 'prepared,
+  }
+}
+
+let makeRenderer = (
+  ~prepare: unit => 'prepared,
+  ~render: Internal.prepareProps<'prepared> => React.element,
+  (),
+) => {
+  let _ = prepare
+  let _ = render
+  "123"
+}
diff --git a/tests/analysis_tests/tests/src/CompletionTypeAnnotation.res b/tests/analysis_tests/tests/src/CompletionTypeAnnotation.res
new file mode 100644
index 0000000000..bfe480ea69
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionTypeAnnotation.res
@@ -0,0 +1,57 @@
+type someRecord = {
+  age: int,
+  name: string,
+}
+
+type someVariant = One | Two(bool)
+
+type somePolyVariant = [#one | #two(bool)]
+
+// let x: someRecord =
+//                    ^com
+
+// let x: someRecord = {}
+//                      ^com
+
+// let x: someVariant =
+//                     ^com
+
+// let x: someVariant = O
+//                       ^com
+
+// let x: somePolyVariant =
+//                         ^com
+
+// let x: somePolyVariant = #o
+//                            ^com
+
+type someFunc = (int, string) => bool
+
+// let x: someFunc =
+//                  ^com
+
+type someTuple = (bool, option<bool>)
+
+// let x: someTuple =
+//                   ^com
+
+// let x: someTuple = (true, )
+//                          ^com
+
+// let x: option<someVariant> =
+//                             ^com
+
+// let x: option<someVariant> = Some()
+//                                   ^com
+
+// let x: array<someVariant> =
+//                            ^com
+
+// let x: array<someVariant> = []
+//                              ^com
+
+// let x: array<option<someVariant>> =
+//                                    ^com
+
+// let x: option<array<someVariant>> = Some([])
+//                                           ^com
diff --git a/tests/analysis_tests/tests/src/CompletionTypeT.res b/tests/analysis_tests/tests/src/CompletionTypeT.res
new file mode 100644
index 0000000000..86371b35f7
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CompletionTypeT.res
@@ -0,0 +1,9 @@
+let date = Some(Js.Date.make())
+
+type withDate = {date: Js.Date.t}
+
+// let x = switch date { | }
+//                        ^com
+
+// let x: withDate = {date: }
+//                         ^com
diff --git a/tests/analysis_tests/tests/src/Component.res b/tests/analysis_tests/tests/src/Component.res
new file mode 100644
index 0000000000..aa3f50cb0d
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Component.res
@@ -0,0 +1,2 @@
+@react.component
+let make = () => React.null
diff --git a/tests/analysis_tests/tests/src/Component.resi b/tests/analysis_tests/tests/src/Component.resi
new file mode 100644
index 0000000000..1ca44ce260
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Component.resi
@@ -0,0 +1,2 @@
+@react.component
+let make: unit => React.element
diff --git a/tests/analysis_tests/tests/src/CreateInterface.res b/tests/analysis_tests/tests/src/CreateInterface.res
new file mode 100644
index 0000000000..7bc514cc27
--- /dev/null
+++ b/tests/analysis_tests/tests/src/CreateInterface.res
@@ -0,0 +1,145 @@
+// ^int
+
+type r = {name: string, age: int}
+
+let add = (~x, ~y) => x + y
+
+@react.component
+let make = (~name) => React.string(name)
+
+module Other = {
+  @react.component
+  let otherComponentName = (~name) => React.string(name)
+}
+
+module Mod = {
+  @react.component
+  let make = (~name) => React.string(name)
+}
+
+module type ModTyp = {
+  @react.component
+  let make: (~name: string) => React.element
+}
+
+@module("path") external dirname: string => string = "dirname"
+
+@module("path") @variadic
+external join: array<string> => string = "join"
+
+@val
+external padLeft: (
+  string,
+  @unwrap
+  [
+    | #Str(string)
+    | #Int(int)
+  ],
+) => string = "padLeft"
+
+@inline
+let f1 = 10
+
+@inline let f2 = "some string"
+
+@genType @inline
+let f3 = 10
+
+@genType @inline
+let f4 = "some string"
+
+@genType @inline let f5 = 5.5
+
+module RFS = {
+  @module("fs")
+  external readFileSync: (
+    ~name: string,
+    @string
+    [
+      | #utf8
+      | @as("ascii") #useAscii
+    ],
+  ) => string = "readFileSync"
+}
+
+module Functor = () => {
+  @react.component
+  let make = () => React.null
+}
+
+module type FT = {
+  module Functor: (
+    X: {
+      let a: int
+      @react.component
+      let make: (~name: string) => React.element
+      let b: int
+    },
+    Y: ModTyp,
+  ) =>
+  {
+    @react.component
+    let make: (~name: string) => React.element
+  }
+}
+
+module NormaList = List
+open Belt
+module BeltList = List
+
+module type MT2 = ModTyp
+
+module rec RM: ModTyp = D
+and D: ModTyp = Mod
+
+module type OptT = {
+  @react.component
+  let withOpt1: (~x: int=?, ~y: int) => int
+
+  module type Opt2 = {
+    @react.component
+    let withOpt2: (~x: int=?, ~y: int) => int
+  }
+
+  module type Opt3 = {
+    @react.component
+    let withOpt3: (~x: option<int>, ~y: int) => int
+  }
+}
+
+module Opt = {
+  @react.component
+  let withOpt1 = (~x=3, ~y) => x + y
+
+  module Opt2 = {
+    @react.component
+    let withOpt2 = (~x: option<int>=?, ~y: int) =>
+      switch x {
+      | None => 0
+      | Some(x) => x
+      } +
+      y
+  }
+  module type Opt2 = module type of Opt2
+
+  module Opt3 = {
+    @react.component
+    let withOpt3 = (~x: option<int>, ~y: int) =>
+      switch x {
+      | None => 0
+      | Some(x) => x
+      } +
+      y
+  }
+  module type Opt3 = module type of Opt3
+}
+
+module Opt2: OptT = Opt
+module Opt3 = Opt
+
+module Memo = {
+  @react.component
+  let make = (~name) => React.string(name)
+
+  let make = React.memo(make)
+}
diff --git a/tests/analysis_tests/tests/src/Cross.res b/tests/analysis_tests/tests/src/Cross.res
new file mode 100644
index 0000000000..051b404115
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Cross.res
@@ -0,0 +1,41 @@
+let crossRef = References.x
+//               ^ref
+
+let crossRef2 = References.x
+
+module Ref = References
+
+let crossRef3 = References.x
+
+let crossRefWithInterface = ReferencesWithInterface.x
+//                             ^ref
+
+let crossRefWithInterface2 = ReferencesWithInterface.x
+
+module RefWithInterface = ReferencesWithInterface
+
+let crossRefWithInterface3 = ReferencesWithInterface.x
+
+let _ = RenameWithInterface.x
+//           ^ren RenameWithInterfacePrime
+
+let _ = RenameWithInterface.x
+//                          ^ren xPrime
+
+let typeDef = {TypeDefinition.item: "foobar"}
+//   ^typ
+
+let _ = DefinitionWithInterface.y
+//                              ^def
+
+type defT = DefinitionWithInterface.t
+//                                  ^def
+
+type defT2 = DefinitionWithInterface.t
+//                                   ^typ
+
+// DefinitionWithInterface.a
+//                          ^com
+
+let yy = DefinitionWithInterface.Inner.y
+//                                     ^def
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/Dce.res b/tests/analysis_tests/tests/src/Dce.res
new file mode 100644
index 0000000000..de0b8b498c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Dce.res
@@ -0,0 +1,4 @@
+// Note: in test mode this only reports on src/dce
+
+// ^dce
+
diff --git a/tests/analysis_tests/tests/src/Debug.res b/tests/analysis_tests/tests/src/Debug.res
new file mode 100644
index 0000000000..8490433e40
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Debug.res
@@ -0,0 +1,17 @@
+// turn on by adding this comment // ^db+
+
+let _ = ShadowedBelt.List.map
+//                         ^def
+
+open Js
+module Before = {
+  open Belt
+  let _ = Id.getCmpInternal
+}
+module Inner = {
+  // eqN
+  //    ^com
+  open List
+  let _ = map
+}
+// ^db-
diff --git a/tests/analysis_tests/tests/src/Definition.res b/tests/analysis_tests/tests/src/Definition.res
new file mode 100644
index 0000000000..530f7cf7f8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Definition.res
@@ -0,0 +1,28 @@
+let xx = 10
+
+let y = xx
+//      ^def
+
+module Inner = {
+  type tInner = int
+  let vInner = 34
+}
+
+type typeInner = Inner.tInner
+//                     ^def
+
+// open Belt
+let m1 = List.map
+//            ^hov
+
+open ShadowedBelt
+let m2 = List.map
+//            ^hov
+
+let uncurried = (. x, y) => x + y
+
+uncurried(. 3, 12)->ignore
+// ^hov
+
+uncurried(. 3, 12)->ignore
+// ^def
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/DefinitionWithInterface.res b/tests/analysis_tests/tests/src/DefinitionWithInterface.res
new file mode 100644
index 0000000000..78dc781033
--- /dev/null
+++ b/tests/analysis_tests/tests/src/DefinitionWithInterface.res
@@ -0,0 +1,12 @@
+let y = 4
+//  ^def
+
+type t = int
+
+let aabbcc = 3
+let _ = aabbcc
+
+module Inner = {
+  let y = 100
+  //  ^def
+}
diff --git a/tests/analysis_tests/tests/src/DefinitionWithInterface.resi b/tests/analysis_tests/tests/src/DefinitionWithInterface.resi
new file mode 100644
index 0000000000..eacfbc1082
--- /dev/null
+++ b/tests/analysis_tests/tests/src/DefinitionWithInterface.resi
@@ -0,0 +1,9 @@
+let y: int
+//  ^def
+
+type t
+
+module Inner: {
+  let y: int
+  //  ^def
+}
diff --git a/tests/analysis_tests/tests/src/Destructuring.res b/tests/analysis_tests/tests/src/Destructuring.res
new file mode 100644
index 0000000000..0ef61122e2
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Destructuring.res
@@ -0,0 +1,33 @@
+type x = {name: string, age: int}
+
+let x = {name: "123", age: 12}
+
+let {name, } = x
+//         ^com
+
+// let {} = x
+//      ^com
+
+let f = (x: x) => {
+  let {name, } = x
+  //         ^com
+  name
+}
+
+let f2 = (x: x) => {
+  // let {} = x
+  //      ^com
+  ignore(x)
+}
+
+type recordWithOptField = {
+  someField: int,
+  someOptField?: bool
+}
+
+let x: recordWithOptField = {
+  someField: 123
+}
+
+// let {} = x
+//      ^com
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/Div.res b/tests/analysis_tests/tests/src/Div.res
new file mode 100644
index 0000000000..e4f08defb9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Div.res
@@ -0,0 +1,5 @@
+let q = <div />
+//        ^hov
+
+// <div dangerous
+//               ^com
diff --git a/tests/analysis_tests/tests/src/DocComments.res b/tests/analysis_tests/tests/src/DocComments.res
new file mode 100644
index 0000000000..defdc43c86
--- /dev/null
+++ b/tests/analysis_tests/tests/src/DocComments.res
@@ -0,0 +1,50 @@
+@ns.doc("  Doc comment with a triple-backquote example
+  
+  ```res example
+    let a = 10
+    /*
+     * stuff
+     */
+  ```
+")
+let docComment1 = 12
+//       ^hov
+
+/**
+  Doc comment with a triple-backquote example
+  
+  ```res example
+    let a = 10
+    /*
+     * stuff
+     */
+  ```
+*/
+let docComment2 = 12
+//    ^hov
+
+
+@ns.doc("  Doc comment with a triple-backquote example
+  
+  ```res example
+    let a = 10
+    let b = 20
+  ```
+")
+let docCommentNoNested1 = 12
+//       ^hov
+
+/**
+  Doc comment with a triple-backquote example
+  
+  ```res example
+    let a = 10
+    let b = 20
+  ```
+*/
+let docCommentNoNested2 = 12
+//    ^hov
+
+@res.doc("New doc comment format")
+let newDoc = 10
+//   ^hov
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/DocumentSymbol.res b/tests/analysis_tests/tests/src/DocumentSymbol.res
new file mode 100644
index 0000000000..84dfeff0ce
--- /dev/null
+++ b/tests/analysis_tests/tests/src/DocumentSymbol.res
@@ -0,0 +1,34 @@
+module MyList = Belt.List
+
+module Dep: {
+  @ocaml.doc("Some doc comment") @deprecated("Use customDouble instead")
+  let customDouble: int => int
+} = {
+  let customDouble = foo => foo * 2
+}
+
+module Lib = {
+  let foo = (~age, ~name) => name ++ Int.toString(age)
+  let next = (~number=0, ~year) => number + year
+}
+
+let op = Some(3)
+
+module ForAuto = {
+  type t = int
+  let abc = (x: t, _y: int) => x
+  let abd = (x: t, _y: int) => x
+}
+
+let fa: ForAuto.t = 34
+
+module O = {
+  module Comp = {
+    @react.component
+    let make = (~first="", ~zoo=3, ~second) => React.string(first ++ second ++ Int.toString(zoo))
+  }
+}
+
+let zzz = 11
+
+//^doc
diff --git a/tests/analysis_tests/tests/src/EnvCompletion.res b/tests/analysis_tests/tests/src/EnvCompletion.res
new file mode 100644
index 0000000000..1bddb38e14
--- /dev/null
+++ b/tests/analysis_tests/tests/src/EnvCompletion.res
@@ -0,0 +1,63 @@
+type things = One | Two
+type things2 = Four | Five
+
+let res: EnvCompletionOtherFile.someResult<things, string> = Okay(One)
+
+let use = (): EnvCompletionOtherFile.response => {
+  stuff: First,
+  res: Failure(""),
+}
+
+// switch res { | }
+//               ^com
+
+// switch res { | Okay() }
+//                     ^com
+
+// switch res { | Failure() }
+//                        ^com
+
+// switch use() { | }
+//                 ^com
+
+// switch use() { | {} }
+//                   ^com
+
+// switch use() { | {stuff: } }
+//                         ^com
+
+// switch use() { | {stuff: Second() } }
+//                                 ^com
+
+// switch use() { | {stuff: Second({}) } }
+//                                  ^com
+
+// switch use() { | {res: } }
+//                       ^com
+
+// switch use() { | {res: Okay() } }
+//                             ^com
+
+// switch use() { | {res: Okay(Second()) } }
+//                                    ^com
+
+// switch use() { | {res: Okay(Second({})) } }
+//                                     ^com
+
+let res2: EnvCompletionOtherFile.someRecord<things2> = {
+  name: "string",
+  theThing: Four,
+  theVariant: First,
+}
+
+// switch res2 { | }
+//                ^com
+
+// switch res2 { | {} }
+//                  ^com
+
+// switch res2 { | {theThing: } }
+//                           ^com
+
+// switch res2 { | {theVariant: } }
+//                             ^com
diff --git a/tests/analysis_tests/tests/src/EnvCompletionOtherFile.res b/tests/analysis_tests/tests/src/EnvCompletionOtherFile.res
new file mode 100644
index 0000000000..1218b0010c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/EnvCompletionOtherFile.res
@@ -0,0 +1,13 @@
+type someResult<'a, 'b> = Okay('a) | Failure('b)
+
+type r1 = {age: int}
+
+type theVariant = First | Second(r1)
+
+type someRecord<'thing> = {
+  name: string,
+  theThing: 'thing,
+  theVariant: theVariant,
+}
+
+type response = {stuff: theVariant, res: someResult<theVariant, string>}
diff --git a/tests/analysis_tests/tests/src/ExhaustiveSwitch.res b/tests/analysis_tests/tests/src/ExhaustiveSwitch.res
new file mode 100644
index 0000000000..82fca239fa
--- /dev/null
+++ b/tests/analysis_tests/tests/src/ExhaustiveSwitch.res
@@ -0,0 +1,43 @@
+type someVariant = One | Two | Three(option<bool>)
+type somePolyVariant = [#one | #two | #three(option<bool>) | #"exotic ident" | #"switch"]
+
+let withSomeVariant = One
+let withSomePoly: somePolyVariant = #one
+let someBool = true
+let someOpt = Some(true)
+
+// switch withSomeVarian
+//                      ^com
+
+// switch withSomePol
+//                   ^com
+
+// switch someBoo
+//               ^com
+
+// switch someOp
+//              ^com
+
+type rcrd = {someVariant: someVariant}
+
+let getV = r => r.someVariant
+
+let x: rcrd = {
+  someVariant: One,
+}
+
+let vvv = Some(x->getV)
+
+// switch x->getV
+//           ^xfm
+
+// x->getV
+// ^xfm  ^
+
+// vvv
+//  ^xfm
+
+// ^ve+ 11.1
+// switch withSomeVarian
+//                      ^com
+// ^ve-
diff --git a/tests/analysis_tests/tests/src/Fragment.res b/tests/analysis_tests/tests/src/Fragment.res
new file mode 100644
index 0000000000..f5e425cef0
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Fragment.res
@@ -0,0 +1,11 @@
+module SectionHeader = {
+  @react.component
+  let make = (~children) => children
+}
+
+
+let z1 = <> <SectionHeader> {React.string("abc")} </SectionHeader> </>
+//                 ^hov
+
+let z2 = <> <SectionHeader> {React.string("abc")} </SectionHeader> </>
+//                                                      ^hov
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/Highlight.res b/tests/analysis_tests/tests/src/Highlight.res
new file mode 100644
index 0000000000..225f0c48a9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Highlight.res
@@ -0,0 +1,136 @@
+module M = {
+  module C = Component
+}
+
+let _c = <Component />
+
+let _mc = <M.C />
+
+let _d = <div />
+
+let _d2 =
+  <div>
+    {React.string("abc")}
+    <div> {React.string("abc")} </div>
+    {React.string("abc")}
+    {React.string("abc")}
+  </div>
+
+type pair<'x, 'y> = ('x, 'y)
+
+type looooooooooooooooooooooooooooooooooooooong_int = int
+
+type looooooooooooooooooooooooooooooooooooooong_string = string
+
+type pairIntString = list<
+  pair<
+    looooooooooooooooooooooooooooooooooooooong_int,
+    looooooooooooooooooooooooooooooooooooooong_string,
+  >,
+>
+
+let _ = !(3 < 4) || 3 > 4
+
+module type MT = {
+  module DDF: {
+
+  }
+}
+
+module DDF: MT = {
+  module DDF = {
+
+  }
+}
+
+module XX = {
+  module YY = {
+    type t = int
+  }
+}
+
+open XX.YY
+
+type tt = t
+
+// ^hig
+
+module T = {
+  type someRecord<'typeParameter> = {
+    someField: int,
+    someOtherField: string,
+    theParam: 'typeParameter,
+  }
+
+  type someEnum = A | B | C
+}
+
+let foo = x => x.T.someField
+
+let add = (~hello as x, ~world) => x + world
+
+let _ = @res.partial add(~hello=3)
+
+let _ = <div scale="abc"> <div /> </div>
+
+module SomeComponent = {
+  module Nested = {
+    @react.component
+    let make = (~children) => {
+      <> {children} </>
+    }
+  }
+}
+
+let _ = <SomeComponent.Nested> <div /> </SomeComponent.Nested>
+
+// true/false
+let _ = true || false
+
+// to/downto as label
+let toAs = (~to as x) => x
+let _toEquals = toAs(~to=10)
+
+let to = 1
+for _ in to + to to to + to {
+  ()
+}
+
+module ToAsProp = {
+  @react.component
+  let make = (~to) => {
+    <> {React.int(to)} </>
+  }
+}
+let _ = <ToAsProp to=3 />
+
+// quoted identifiers
+let \"true" = 4
+let _ = \"true"
+
+let enumInModule = T.A
+
+type typeInModule = XX.YY.t
+
+module QQ = {
+  type somePolyEnumType = [
+    | #someMember
+    | #AnotherMember
+    | #SomeMemberWithPayload(list<int>)
+    | #"fourth Member"
+  ]
+}
+
+let _ = x =>
+  switch x {
+  | #stuff => 3
+  | #...QQ.somePolyEnumType => 4
+  }
+
+let _ = 3 == 3 || 3 === 3
+
+let _ = (~_type_ as _) => ()
+
+let _ = {"abc": 34}
+
+let _ = {"Key": 2}
diff --git a/tests/analysis_tests/tests/src/Hover.res b/tests/analysis_tests/tests/src/Hover.res
new file mode 100644
index 0000000000..e038a2490c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Hover.res
@@ -0,0 +1,268 @@
+let abc = 22 + 34
+//  ^hov
+
+type t = (int, float)
+//   ^hov
+
+module Id = {
+  //   ^hov
+  type x = int
+}
+
+@ocaml.doc("This module is commented")
+module Dep: {
+  @ocaml.doc("Some doc comment")
+  let customDouble: int => int
+} = {
+  let customDouble = foo => foo * 2
+}
+
+module D = Dep
+//         ^hov
+
+let cd = D.customDouble
+//         ^hov
+
+module HoverInsideModuleWithComponent = {
+  let x = 2 // check that hover on x works
+  //  ^hov
+  @react.component
+  let make = () => React.null
+}
+
+@ocaml.doc("Doc comment for functionWithTypeAnnotation")
+let functionWithTypeAnnotation: unit => int = () => 1
+//  ^hov
+
+@react.component
+let make = (~name) => React.string(name)
+//           ^hov
+
+module C2 = {
+  @react.component
+  let make2 = (~name: string) => React.string(name)
+  //           ^hov
+}
+
+let num = 34
+//        ^hov
+
+module type Logger = {
+  //         ^hov
+  let log: string => unit
+}
+
+module JsLogger: Logger = {
+  //   ^hov
+  let log = (msg: string) => Js.log(msg)
+  let _oneMore = 3
+}
+
+module JJ = JsLogger
+//            ^def
+
+module IdDefinedTwice = {
+  //     ^hov
+  let _x = 10
+  let y = 20
+  let _x = 10
+}
+
+module A = {
+  let x = 13
+}
+
+module B = A
+//     ^hov
+
+module C = B
+//     ^hov
+
+module Comp = {
+  @react.component
+  let make = (~children: React.element) => children
+}
+
+module Comp1 = Comp
+
+let _ =
+  <Comp>
+    <div />
+    <div />
+  </Comp>
+//        ^hov
+
+let _ =
+  <Comp1>
+    <div />
+    <div />
+  </Comp1>
+//        ^hov
+
+type r<'a> = {i: 'a, f: float}
+
+let _get = r => r.f +. r.i
+//                       ^hov
+
+let withAs = (~xx as yyy) => yyy + 1
+//                   ^hov
+
+module AA = {
+  type cond<'a> = [< #str(string)] as 'a
+  let fnnxx = (b: cond<_>) => true ? b : b
+}
+
+let funAlias = AA.fnnxx
+
+let typeOk = funAlias
+//              ^hov
+
+let typeDuplicate = AA.fnnxx
+//                       ^hov
+
+@live let dd = 34
+// ^hov
+
+let arity0a = (. ()) => {
+  //^hov
+  let f = () => 3
+  f
+}
+
+let arity0b = (. (), . ()) => 3
+//  ^hov
+
+let arity0c = (. (), ()) => 3
+//  ^hov
+
+let arity0d = (. ()) => {
+  // ^hov
+  let f = () => 3
+  f
+}
+
+/**doc comment 1*/
+let docComment1 = 12
+//       ^hov
+
+/** doc comment 2 */
+let docComment2 = 12
+//    ^hov
+
+module ModWithDocComment = {
+  /*** module level doc comment 1 */
+
+  /** doc comment for x */
+  let x = 44
+
+  /*** module level doc comment 2 */
+}
+
+module TypeSubstitutionRecords = {
+  type foo<'a> = {content: 'a, zzz: string}
+  type bar = {age: int}
+  type foobar = foo<bar>
+
+  let x1: foo<bar> = {content: {age: 42}, zzz: ""}
+  //                   ^hov
+  let x2: foobar = {content: {age: 42}, zzz: ""}
+  //                  ^hov
+
+  // x1.content.
+  //            ^com
+
+  // x2.content.
+  //            ^com
+
+  type foo2<'b> = foo<'b>
+  type foobar2 = foo2<bar>
+
+  let y1: foo2<bar> = {content: {age: 42}, zzz: ""}
+  let y2: foobar2 = {content: {age: 42}, zzz: ""}
+
+  // y1.content.
+  //            ^com
+
+  // y2.content.
+  //            ^com
+}
+
+module CompV4 = {
+  type props<'n, 's> = {n?: 'n, s: 's}
+  let make = props => {
+    let _ = props.n == Some(10)
+    React.string(props.s)
+  }
+}
+
+let mk = CompV4.make
+//  ^hov
+
+type useR = {x: int, y: list<option<r<float>>>}
+
+let testUseR = (v: useR) => v
+//              ^hov
+
+let usr: useR = {
+  x: 123,
+  y: list{},
+}
+
+// let f = usr
+//           ^hov
+
+
+module NotShadowed = {
+  /** Stuff */
+  let xx_ = 10
+
+  /** More Stuff */
+  let xx = xx_
+}
+
+module Shadowed = {
+  /** Stuff */
+  let xx = 10
+
+  /** More Stuff */
+  let xx = xx
+}
+
+let _ = NotShadowed.xx
+//                  ^hov
+
+let _ = Shadowed.xx
+//               ^hov
+
+type recordWithDocstringField = {
+  /** Mighty fine field here. */
+  someField: bool,
+}
+
+let x: recordWithDocstringField = {
+  someField: true,
+}
+
+// x.someField
+//    ^hov
+
+let someField = x.someField
+//                 ^hov
+
+type variant = | /** Cool variant! */ CoolVariant | /** Other cool variant */ OtherCoolVariant
+
+let coolVariant = CoolVariant
+//                  ^hov
+
+// Hover on unsaved
+// let fff = "hello"; fff
+//                     ^hov
+
+// switch x { | {someField} => someField }
+//                               ^hov
+
+module Arr = Belt.Array
+//      ^hov
+
+type aliased = variant
+//    ^hov
diff --git a/tests/analysis_tests/tests/src/InlayHint.res b/tests/analysis_tests/tests/src/InlayHint.res
new file mode 100644
index 0000000000..07f1f08e09
--- /dev/null
+++ b/tests/analysis_tests/tests/src/InlayHint.res
@@ -0,0 +1,36 @@
+let not_include = "Not Include"
+let string = "ReScript"
+let number = 1
+let float = 1.1
+let char = 'c'
+
+let add = (x, y) => x + y
+
+let my_sum = 3->add(1)->add(1)->add(1)->add(8)
+
+let withAs = (~xx as yyy) => yyy + 1
+
+
+@react.component
+let make = (~name) => React.string(name)
+
+let tuple = ("ReScript", "lol")
+
+let (lang, _) = tuple
+
+type foo = {
+  name: string,
+  age: int,
+}
+
+let bar = () => ({name: "ReScript", age: 2}, tuple)
+let ({name:_, age:_}, t) = bar()
+
+let alice = {
+  name: "Alice",
+  age: 42,
+};
+
+let {name, age} = alice;
+
+//^hin
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/Jsx2.res b/tests/analysis_tests/tests/src/Jsx2.res
new file mode 100644
index 0000000000..5e3878abb3
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Jsx2.res
@@ -0,0 +1,174 @@
+module M = {
+  @react.component
+  let make = (~first, ~fun="", ~second="") => React.string(first ++ fun ++ second)
+}
+
+let _ = <M first="abc" />
+//       ^def
+
+// <M second=fi
+//             ^com
+
+// <M second="abc" f
+//                  ^com
+
+// let e = <M
+//           ^com
+
+@react.component
+let make = (~first) => React.string(first)
+
+let y = 44
+
+// <M prop={A(3)} k
+//                 ^com
+
+// <M prop=A(3) k
+//               ^com
+
+// <M prop=foo(1+2) k
+//                   ^com
+
+// <M prop=list{1,2,3} k
+//                      ^com
+
+// <M prop=<N /> k
+//                ^com
+
+// <M prop=1.5 k
+//              ^com
+
+// <M prop=0X33 k
+//               ^com
+
+// <M prop=12e+3 k
+//                ^com
+
+// <M prop='z' k
+//              ^com
+
+// <M prop=`before${foo}` k
+//                         ^com
+
+// <M prop=module(@foo Three: X_int) k
+//                                    ^com
+
+// <M prop=%bs.raw("1") k
+//                       ^com
+
+let _ = <Component />
+//         ^def
+
+module Ext = {
+  @react.component @module("@material-ui/core")
+  external make: (~align: string=?) => React.element = "Typography"
+}
+
+let _ = Ext.make
+
+// <Ext al
+//        ^com
+
+// <M first
+//         ^com
+
+// <M first=#a k
+//              ^com
+
+// <M first =  ?   #a k
+//                     ^com
+
+// <M>
+//    ^com
+
+module WithChildren = {
+  @react.component
+  let make = (~name as _: string, ~children) => <jsx> children </jsx>
+}
+
+let _ = <WithChildren name=""> <div /> </WithChildren>
+// <WithChildren
+//              ^com
+// <WithChildren n
+//                ^com
+
+// let c : React.e
+//                ^com
+// let c : ReactDOMR
+//                  ^com
+
+module DefineSomeFields = {
+  type r = {thisField: int, thatField: string}
+  let thisValue = 10
+  // let foo x = x.th
+  //                 ^com
+}
+
+// let q = DefineSomeFields.
+//                          ^com
+// let foo x = x.DefineSomeFields.th
+//                                  ^com
+
+let _ = x => x.DefineSomeFields.thisField + DefineSomeFields.thisValue
+
+module Outer = {
+  module Inner = {
+    let hello = 3
+  }
+}
+let _ = Outer.Inner.hello
+
+let _ =
+  <div
+  // x=Outer.Inner.h
+  //                ^com
+    name=""
+  />
+
+let _ =
+  <div
+  // x=Outer.Inner.
+  //               ^com
+    name=""
+  />
+
+let _ =
+  <div
+  // x=
+  //   ^com
+    name=""
+  />
+
+module Nested = {
+  module Comp = {
+    @react.component
+    let make = (~name) => React.string(name)
+  }
+}
+
+let _ = <Nested.Comp name="" />
+
+// let _ = <Nested.Co name="" />
+//                   ^com
+
+// let _ = <Nested. name="" />
+//                 ^com
+
+module Comp = {
+  @react.component
+  let make = (~age) => React.int(age)
+}
+
+let _ = {
+  <> <Comp age=34 /> </>
+  //        ^hov
+}
+
+let _ = {
+  <> {<> <Comp age=34 /> </>} </>
+  //            ^hov
+}
+
+module type ExtT = module type of Ext
+
+let _ = module(Ext: ExtT)
diff --git a/tests/analysis_tests/tests/src/Jsx2.resi b/tests/analysis_tests/tests/src/Jsx2.resi
new file mode 100644
index 0000000000..c0d35a245b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Jsx2.resi
@@ -0,0 +1,12 @@
+@react.component
+let make: (~first: string) => React.element
+//  ^hov
+
+let y: int
+//  ^hov
+
+// type t = React.e
+//                 ^com
+
+// let x : React.e
+//                ^com
diff --git a/tests/analysis_tests/tests/src/JsxV4.res b/tests/analysis_tests/tests/src/JsxV4.res
new file mode 100644
index 0000000000..a48c69f7de
--- /dev/null
+++ b/tests/analysis_tests/tests/src/JsxV4.res
@@ -0,0 +1,28 @@
+@@jsxConfig({version: 4})
+
+module M4 = {
+  /** Doc Comment For M4 */
+  @react.component
+  let make = (~first, ~fun="", ~second="") => React.string(first ++ fun ++ second)
+}
+
+let _ = <M4 first="abc" />
+//       ^def
+
+// <M4 first="abc" f
+//                  ^com
+
+let _ = <M4 first="abc" />
+//       ^hov
+
+module MM = {
+  @react.component
+  let make = () => React.null
+}
+
+module Other = {
+  @react.component
+  let make = (~name) => React.string(name)
+}
+
+// ^int
diff --git a/tests/analysis_tests/tests/src/LongIdentTest.res b/tests/analysis_tests/tests/src/LongIdentTest.res
new file mode 100644
index 0000000000..fdff57c9d7
--- /dev/null
+++ b/tests/analysis_tests/tests/src/LongIdentTest.res
@@ -0,0 +1,7 @@
+module Map = TableclothMap
+
+let zz = Map.add
+//           ^hov
+// Triggers the processing of `Of(M)._t` and Lident.Apply ends up in the AST
+// even though it's not expressible in ReScript syntax.
+// This simulates ReScript projects with OCaml dependencies containing ident apply.
diff --git a/tests/analysis_tests/tests/src/ModuleStuff.res b/tests/analysis_tests/tests/src/ModuleStuff.res
new file mode 100644
index 0000000000..13210ac762
--- /dev/null
+++ b/tests/analysis_tests/tests/src/ModuleStuff.res
@@ -0,0 +1,5 @@
+/*** This is a top level module doc. */
+
+module Nested = {
+  /*** Module doc for nested. */
+}
diff --git a/tests/analysis_tests/tests/src/Objects.res b/tests/analysis_tests/tests/src/Objects.res
new file mode 100644
index 0000000000..4718f37464
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Objects.res
@@ -0,0 +1,11 @@
+type objT = {"name": string, "age": int}
+
+type nestedObjT = {"y": objT}
+
+module Rec = {
+  type recordt = {xx: int, ss: string}
+
+  let recordVal: recordt = assert false
+}
+
+let object: objT = {"name": "abc", "age": 4}
diff --git a/tests/analysis_tests/tests/src/Patterns.res b/tests/analysis_tests/tests/src/Patterns.res
new file mode 100644
index 0000000000..8d6dc6520b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Patterns.res
@@ -0,0 +1,32 @@
+module A = {
+  let makeX = () => (1, 2)
+
+  let (xxx, yyy) = makeX()
+
+  type t = {name: string, age: int}
+
+  let makeT = () => {name: "", age: 0}
+
+  let {name, age} = makeT()
+
+  let (a | a, b) = makeX()
+
+  type rec arr = A(array<arr>)
+
+  let A([v1, _, _]) | _ as v1 = assert false
+
+}
+
+let y = A.xxx
+//        ^def
+
+let z = A.yyy
+
+let n1 = A.name
+//         ^def
+
+let n2 = A.a
+//         ^def
+
+let n3 = A.v1
+//         ^def
diff --git a/tests/analysis_tests/tests/src/PolyRec.res b/tests/analysis_tests/tests/src/PolyRec.res
new file mode 100644
index 0000000000..c2fdbdf523
--- /dev/null
+++ b/tests/analysis_tests/tests/src/PolyRec.res
@@ -0,0 +1,14 @@
+let rec sum = x =>
+  switch x {
+  | #Leaf => 0
+  | #Node(value, left, right) => value + left->sum + right->sum
+  }
+
+let myTree = #Node(
+  1,
+  #Node(2, #Node(4, #Leaf, #Leaf), #Node(6, #Leaf, #Leaf)),
+  #Node(3, #Node(5, #Leaf, #Leaf), #Node(7, #Leaf, #Leaf)),
+)
+
+let () = myTree->sum->Js.log
+//        ^hov
diff --git a/tests/analysis_tests/tests/src/QueryFile.res b/tests/analysis_tests/tests/src/QueryFile.res
new file mode 100644
index 0000000000..0ba6f3d1d5
--- /dev/null
+++ b/tests/analysis_tests/tests/src/QueryFile.res
@@ -0,0 +1,6 @@
+module Types = {
+  type byAddress = SchemaAssets.input_ByAddress
+  type location = SchemaAssets.input_Location
+
+  type variables = {location: location}
+}
diff --git a/tests/analysis_tests/tests/src/RecModules.res b/tests/analysis_tests/tests/src/RecModules.res
new file mode 100644
index 0000000000..6794387f45
--- /dev/null
+++ b/tests/analysis_tests/tests/src/RecModules.res
@@ -0,0 +1,22 @@
+module rec A: {
+  type t
+
+  @send external child: t => B.t = "child"
+} = A
+
+and B: {
+  type t
+
+  @send external parent: t => A.t = "parent"
+} = B
+
+module C = {
+  type t
+
+  @send external createA: t => A.t = "createA"
+}
+
+module MC = C
+//          ^hov
+module MA = A
+//          ^hov
diff --git a/tests/analysis_tests/tests/src/RecordCompletion.res b/tests/analysis_tests/tests/src/RecordCompletion.res
new file mode 100644
index 0000000000..0ec0c69829
--- /dev/null
+++ b/tests/analysis_tests/tests/src/RecordCompletion.res
@@ -0,0 +1,24 @@
+type t = {n: array<string>}
+
+let t = {n: []}
+
+type t2 = {n2: t}
+
+let t2 = {n2: t}
+
+// t.n->m
+//       ^com
+
+// t2.n2.n->m
+//           ^com
+
+module R = {
+  type t = {name: string}
+}
+
+let n = {R.name: ""}
+// n.R.
+//     ^com
+
+// n.R. xx
+//     ^com
diff --git a/tests/analysis_tests/tests/src/RecoveryOnProp.res b/tests/analysis_tests/tests/src/RecoveryOnProp.res
new file mode 100644
index 0000000000..8af974749c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/RecoveryOnProp.res
@@ -0,0 +1,12 @@
+let name = ""
+
+let _ =
+  <div
+    onClick={_ => {
+      ()
+      //        let _: Res
+      //                  ^com
+    }}
+    name="abc">
+    {React.string(name)}
+  </div>
diff --git a/tests/analysis_tests/tests/src/References.res b/tests/analysis_tests/tests/src/References.res
new file mode 100644
index 0000000000..b3762e0b6e
--- /dev/null
+++ b/tests/analysis_tests/tests/src/References.res
@@ -0,0 +1,25 @@
+let x = 12
+//  ^ref
+
+let a = x
+
+let b = a
+
+let c = x
+
+let foo = (~xx) => xx + 1
+//                 ^ref
+
+module M: {
+  let aa: int
+} = {
+  let aa = 10
+}
+
+let bb = M.aa
+let cc = bb
+let dd = M.aa
+//          ^ref
+
+let _ = <ComponentInner/>
+//             ^ref
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/ReferencesWithInterface.res b/tests/analysis_tests/tests/src/ReferencesWithInterface.res
new file mode 100644
index 0000000000..54b4b8b16f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/ReferencesWithInterface.res
@@ -0,0 +1,2 @@
+let x = 2
+//  ^ref
diff --git a/tests/analysis_tests/tests/src/ReferencesWithInterface.resi b/tests/analysis_tests/tests/src/ReferencesWithInterface.resi
new file mode 100644
index 0000000000..765cf3c6d8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/ReferencesWithInterface.resi
@@ -0,0 +1,2 @@
+let x: int
+//  ^ref
diff --git a/tests/analysis_tests/tests/src/Rename.res b/tests/analysis_tests/tests/src/Rename.res
new file mode 100644
index 0000000000..b9e3270de3
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Rename.res
@@ -0,0 +1,11 @@
+let x = 12
+//  ^ren y
+
+let a = x
+
+let b = a
+
+let c = x
+
+let foo = (~xx) => xx + 1
+//                 ^ren yy
diff --git a/tests/analysis_tests/tests/src/RenameWithInterface.res b/tests/analysis_tests/tests/src/RenameWithInterface.res
new file mode 100644
index 0000000000..5c68741c77
--- /dev/null
+++ b/tests/analysis_tests/tests/src/RenameWithInterface.res
@@ -0,0 +1,2 @@
+let x = 2
+//  ^ren y
diff --git a/tests/analysis_tests/tests/src/RenameWithInterface.resi b/tests/analysis_tests/tests/src/RenameWithInterface.resi
new file mode 100644
index 0000000000..8657edc21f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/RenameWithInterface.resi
@@ -0,0 +1,2 @@
+let x: int
+//  ^ren y
diff --git a/tests/analysis_tests/tests/src/Reprod.res b/tests/analysis_tests/tests/src/Reprod.res
new file mode 100644
index 0000000000..e617686fc9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Reprod.res
@@ -0,0 +1,56 @@
+module Query = {
+  let use = (~variables: QueryFile.Types.variables) => {
+    ignore(variables)
+    ""
+  }
+}
+
+// let x = Query.use(~variables={location: ByAddress()})
+//                                                   ^com
+
+type nestedRecord = {nested: bool}
+
+type rec someRecord = {
+  first: int,
+  second: (bool, option<someRecord>),
+  optThird: option<[#first | #second(someRecord)]>,
+  nest: nestedRecord,
+}
+
+type somePolyVariant = [#one | #two(bool) | #three(someRecord, bool)]
+
+type someVariant = One | Two(bool) | Three(someRecord, bool)
+
+type paramRecord<'a, 'b> = {
+  first: 'a,
+  second: 'b,
+}
+
+let record: paramRecord<someVariant, QueryFile.Types.byAddress> = {
+  first: One,
+  second: {city: "city"},
+}
+
+// switch record { | {first: }}
+//                          ^com
+
+// switch record { | {second: }}
+//                           ^com
+
+// TODO: Functions, aliases/definitions, records, variants, polyvariants, tuples
+
+let res: result<someVariant, somePolyVariant> = Ok(One)
+
+// switch res { | Ok() }
+//                   ^com
+
+// switch res { | Error() }
+//                      ^com
+
+let resOpt: result<option<someVariant>, unit> = Ok(None)
+
+// switch resOpt { | Ok() }
+//                      ^com
+
+// switch resOpt { | Ok(Some()) }
+//                           ^com
diff --git a/tests/analysis_tests/tests/src/SchemaAssets.res b/tests/analysis_tests/tests/src/SchemaAssets.res
new file mode 100644
index 0000000000..b83ff5c0d9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/SchemaAssets.res
@@ -0,0 +1,6 @@
+@live
+type rec input_ByAddress = {city: string}
+@tag("__$inputUnion")
+and input_Location =
+  | @as("byAddress") ByAddress(input_ByAddress)
+  | @as("byId") ById(string)
diff --git a/tests/analysis_tests/tests/src/ShadowedBelt.res b/tests/analysis_tests/tests/src/ShadowedBelt.res
new file mode 100644
index 0000000000..143c0e9158
--- /dev/null
+++ b/tests/analysis_tests/tests/src/ShadowedBelt.res
@@ -0,0 +1,3 @@
+module List = {
+  let map = (l, fn) => List.map(fn, l)
+}
diff --git a/tests/analysis_tests/tests/src/SignatureHelp.res b/tests/analysis_tests/tests/src/SignatureHelp.res
new file mode 100644
index 0000000000..eb30878467
--- /dev/null
+++ b/tests/analysis_tests/tests/src/SignatureHelp.res
@@ -0,0 +1,159 @@
+type someVariant = One | Two | Three
+
+/** Does stuff. */
+let someFunc = (one: int, ~two: option<string>=?, ~three: unit => unit, ~four: someVariant, ()) => {
+  ignore(one)
+  ignore(two)
+  ignore(three())
+  ignore(four)
+}
+
+let otherFunc = (first: string, second: int, third: float) => {
+  ignore(first)
+  ignore(second)
+  ignore(third)
+}
+
+// let _ = someFunc(
+//                  ^she
+
+// let _ = someFunc(1
+//                   ^she
+
+// let _ = someFunc(123, ~two
+//                           ^she
+
+// let _ = someFunc(123, ~two="123"
+//                               ^she
+
+// let _ = someFunc(123, ~two="123", ~four
+//                                    ^she
+
+// let _ = someFunc(123, ~two="123", ~four=O
+//                                        ^she
+
+// let _ = otherFunc(
+//                   ^she
+
+// let _ = otherFunc("123"
+//                      ^she
+
+// let _ = otherFunc("123", 123, 123.0)
+//                                 ^she
+
+// let _ = Completion.Lib.foo(~age
+//                               ^she
+
+let iAmSoSpecial = (iJustHaveOneArg: string) => {
+  ignore(iJustHaveOneArg)
+}
+
+// let _ = iAmSoSpecial(
+//                      ^she
+
+// let _ = "hello"->otherFunc(1
+//                             ^she
+
+let fn = (age: int, name: string, year: int) => {
+  ignore(age)
+  ignore(name)
+  ignore(year)
+}
+
+// let _ = fn(22, )
+//               ^she
+
+// let _ = fn(22, , 2023)
+//               ^she
+
+// let _ = fn(12, "hello", )
+//                        ^she
+
+// let _ = fn({ iAmSoSpecial() })
+//                           ^she
+
+// let _ = fn({ iAmSoSpecial({ someFunc() }) })
+//                                      ^she
+
+/** This is my own special thing. */
+type mySpecialThing = string
+
+type t =
+  | /** One is cool. */ One({miss?: bool, hit?: bool, stuff?: string})
+  | /** Two is fun! */ Two(mySpecialThing)
+  | /** Three is... three */ Three(mySpecialThing, array<option<string>>)
+
+let _one = One({})
+//              ^she
+
+let _one = One({miss: true})
+//                ^she
+
+let _one = One({hit: true, miss: true})
+//                     ^she
+
+let two = Two("true")
+//             ^she
+
+let three = Three("", [])
+//                 ^she
+
+let three2 = Three("", [])
+//                      ^she
+
+let _deepestTakesPrecedence = [12]->Js.Array2.map(v =>
+  if v > 0 {
+    One({})
+    //   ^she
+  } else {
+    Two("")
+  }
+)
+
+/** Main docstring here. */
+let _usesCorrectTypeInfo = [12]->Array.map(v => v)
+//                                        ^she
+
+/** Type x... */
+type x = {
+  age?: int,
+  name?: string,
+}
+
+/** Type tt! */
+type tt = One
+
+/** Some stuff */
+let stuffers = (x: x, y: tt) => {
+  ignore(x)
+  ignore(y)
+  "hello"
+}
+
+let _ = stuffers({}, One)
+//                ^she
+
+let _ = stuffers({}, One)
+//                    ^she
+
+let _ = switch _one {
+| One({hit: _hit}) => ""
+//      ^she
+| One(_a) => ""
+//     ^she
+| Two(_ms) => ""
+//     ^she
+| Three(_a, []) => ""
+//       ^she
+| Three(_, _b) => ""
+//          ^she
+}
+
+let _bb = Ok(true)
+//            ^she
+
+let _bbb = Error("err")
+//                 ^she
+
+let _cc = Some(true)
+//              ^she
diff --git a/tests/analysis_tests/tests/src/TableclothMap.res b/tests/analysis_tests/tests/src/TableclothMap.res
new file mode 100644
index 0000000000..515bdd62d5
--- /dev/null
+++ b/tests/analysis_tests/tests/src/TableclothMap.res
@@ -0,0 +1,12 @@
+let add = 3
+
+module Of = (M: {}) => {
+  type _t = int
+}
+
+module M = {}
+
+module O = Of(M)
+module Int = {
+  type _t = O._t
+}
diff --git a/tests/analysis_tests/tests/src/TableclothMap.resi b/tests/analysis_tests/tests/src/TableclothMap.resi
new file mode 100644
index 0000000000..91d267349e
--- /dev/null
+++ b/tests/analysis_tests/tests/src/TableclothMap.resi
@@ -0,0 +1,3 @@
+let add: int
+
+module Int: {}
diff --git a/tests/analysis_tests/tests/src/TypeAtPosCompletion.res b/tests/analysis_tests/tests/src/TypeAtPosCompletion.res
new file mode 100644
index 0000000000..41d558bedc
--- /dev/null
+++ b/tests/analysis_tests/tests/src/TypeAtPosCompletion.res
@@ -0,0 +1,25 @@
+type optRecord = {
+  name: string,
+  age?: int,
+  online?: bool,
+}
+
+let optRecord = {
+  name: "Hello",
+  //             ^com
+}
+
+type someVariant = One(int, optRecord)
+
+let x = One(
+  1,
+  {
+    name: "What",
+    //            ^com
+  },
+)
+
+let arr = [
+  optRecord,
+  //        ^com
+]
diff --git a/tests/analysis_tests/tests/src/TypeDefinition.res b/tests/analysis_tests/tests/src/TypeDefinition.res
new file mode 100644
index 0000000000..b4b74d7d16
--- /dev/null
+++ b/tests/analysis_tests/tests/src/TypeDefinition.res
@@ -0,0 +1,25 @@
+type variant = Foo | Bar
+
+type record = {item: string}
+//       ^typ
+
+let x = Foo
+//  ^typ
+
+let y = {item: "foo"}
+//  ^typ
+
+type obj = {"foo": string}
+
+let obj: obj = {"foo": "bar"}
+//  ^typ
+
+let f = r => r.item
+//           ^typ
+
+let g = v =>
+  switch v {
+  //     ^typ
+  | Foo => "Foo"
+  | Bar => "Bar"
+  }
diff --git a/tests/analysis_tests/tests/src/Xform.res b/tests/analysis_tests/tests/src/Xform.res
new file mode 100644
index 0000000000..fcd287ea06
--- /dev/null
+++ b/tests/analysis_tests/tests/src/Xform.res
@@ -0,0 +1,145 @@
+type kind = First | Second | Third | Fourth(int)
+type r = {name: string, age: int}
+
+let ret = _ => assert(false)
+let kind = assert(false)
+
+if kind == First {
+  // ^xfm
+  ret("First")
+} else {
+  ret("Not First")
+}
+
+#kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First")
+//             ^xfm
+
+let name = "hello"
+//   ^xfm
+
+let annotated: int = 34
+//   ^xfm
+
+module T = {
+  type r = {a: int, x: string}
+}
+
+let foo = x =>
+  //      ^xfm
+  switch x {
+  | None => 33
+  | Some(q) => q.T.a + 1
+  //     ^xfm
+  }
+
+let withAs = (~x as name) => name + 1
+//                   ^xfm
+
+@react.component
+let make = (~name) => React.string(name)
+//   ^xfm
+
+let _ = (~x) => x + 1
+//       ^xfm
+
+//
+// Add braces to the body of a function
+//
+
+let noBraces = () => name
+//                   ^xfm
+
+let nested = () => {
+  let _noBraces = (_x, _y, _z) => "someNewFunc"
+  //                              ^xfm
+}
+
+let bar = () => {
+  module Inner = {
+    let foo = (_x, y, _z) =>
+      switch y {
+      | #some => 3
+      | #stuff => 4
+      }
+    //^xfm
+  }
+  Inner.foo(1, ...)
+}
+
+module ExtractableModule = {
+  /** Doc comment. */
+  type t = int
+  // A comment here
+  let doStuff = a => a + 1
+  // ^xfm
+}
+
+let variant = First
+
+let _x = switch variant {
+| First => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variant {
+| First | Second => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variant {
+| First if 1 > 2 => "first"
+| Second => "second"
+| _ => "other"
+//  ^xfm
+}
+
+let polyvariant: [#first | #second | #"illegal identifier" | #third(int)] = #first
+
+let _y = switch polyvariant {
+| #first => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _y = switch polyvariant {
+| #first | #second => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let variantOpt = Some(variant)
+
+let _x = switch variantOpt {
+| Some(First) => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variantOpt {
+| Some(First) | Some(Second) => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variantOpt {
+| Some(First | Second) => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let polyvariantOpt = Some(polyvariant)
+
+let _x = switch polyvariantOpt {
+| Some(#first) => "first"
+| None => "nothing"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch polyvariantOpt {
+| Some(#first | #second) => "first"
+| _ => "other"
+//  ^xfm
+}
diff --git a/tests/analysis_tests/tests/src/dce/DceTest.res b/tests/analysis_tests/tests/src/dce/DceTest.res
new file mode 100644
index 0000000000..df5e542079
--- /dev/null
+++ b/tests/analysis_tests/tests/src/dce/DceTest.res
@@ -0,0 +1 @@
+let x = 12
diff --git a/tests/analysis_tests/tests/src/expected/Auto.res.txt b/tests/analysis_tests/tests/src/expected/Auto.res.txt
new file mode 100644
index 0000000000..75383fb52e
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Auto.res.txt
@@ -0,0 +1,3 @@
+Hover src/Auto.res 2:13
+{"contents": {"kind": "markdown", "value": "```rescript\n('a => 'b, List.t<'a>) => List.t<'b>\n```\n\n---\n\n```\n \n```\n```rescript\ntype List.t<'a> = list<'a>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22List.res%22%2C34%2C0%5D)\n"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/BrokenParserCases.res.txt b/tests/analysis_tests/tests/src/expected/BrokenParserCases.res.txt
new file mode 100644
index 0000000000..821b3b35f8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/BrokenParserCases.res.txt
@@ -0,0 +1,27 @@
+Complete src/BrokenParserCases.res 2:24
+posCursor:[2:24] posNoWhite:[2:23] Found expr:[2:11->2:30]
+Pexp_apply ...[2:11->2:17] (~isOff2:19->2:24=...[2:27->2:29])
+Completable: CnamedArg(Value[someFn], isOff, [isOff])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFn]
+Path someFn
+[]
+
+Complete src/BrokenParserCases.res 6:17
+posCursor:[6:17] posNoWhite:[6:16] Found pattern:[6:16->6:19]
+Completable: Cpattern Value[s]=t
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[]
+
+Complete src/BrokenParserCases.res 10:29
+posCursor:[10:29] posNoWhite:[10:27] Found pattern:[10:24->10:39]
+posCursor:[10:29] posNoWhite:[10:27] Found pattern:[10:24->10:28]
+Ppat_construct None:[10:24->10:28]
+Completable: Cpath Value[None]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[None]
+Path None
+[]
+
diff --git a/tests/analysis_tests/tests/src/expected/CodeLens.res.txt b/tests/analysis_tests/tests/src/expected/CodeLens.res.txt
new file mode 100644
index 0000000000..e75a286516
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CodeLens.res.txt
@@ -0,0 +1,12 @@
+Code Lens src/CodeLens.res
+[{
+        "range": {"start": {"line": 4, "character": 4}, "end": {"line": 4, "character": 6}},
+        "command": {"title": "(~opt1: int=?, ~a: int, ~b: int, unit, ~opt2: int=?, unit, ~c: int) => int", "command": ""}
+    }, {
+        "range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 7}},
+        "command": {"title": "(~age: int, ~name: string) => string", "command": ""}
+    }, {
+        "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 7}},
+        "command": {"title": "(int, int) => int", "command": ""}
+    }]
+
diff --git a/tests/analysis_tests/tests/src/expected/Codemod.res.txt b/tests/analysis_tests/tests/src/expected/Codemod.res.txt
new file mode 100644
index 0000000000..5e4783d5dd
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Codemod.res.txt
@@ -0,0 +1,8 @@
+Codemod AddMissingCasessrc/Codemod.res 3:10
+switch (v1, v2) {
+          //      ^c-a (#valid, #valid) | (#invalid, _)
+          | (#valid, #invalid) => ()
+          | (#valid, #valid) => failwith("TODO")
+          | (#invalid, _) => failwith("TODO")
+          }
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletableComponent.res.txt b/tests/analysis_tests/tests/src/expected/CompletableComponent.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/CompletePrioritize1.res.txt b/tests/analysis_tests/tests/src/expected/CompletePrioritize1.res.txt
new file mode 100644
index 0000000000..ba9668b1b4
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletePrioritize1.res.txt
@@ -0,0 +1,19 @@
+Complete src/CompletePrioritize1.res 5:6
+posCursor:[5:6] posNoWhite:[5:5] Found expr:[5:3->0:-1]
+Completable: Cpath Value[a]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[a]->
+ContextPath Value[a]
+Path a
+CPPipe env:CompletePrioritize1
+CPPipe type path:Test.t
+CPPipe pathFromEnv:Test found:true
+Path Test.
+[{
+    "label": "Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => float",
+    "documentation": null
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletePrioritize2.res.txt b/tests/analysis_tests/tests/src/expected/CompletePrioritize2.res.txt
new file mode 100644
index 0000000000..717157ce31
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletePrioritize2.res.txt
@@ -0,0 +1,34 @@
+Complete src/CompletePrioritize2.res 9:7
+posCursor:[9:7] posNoWhite:[9:6] Found expr:[9:3->0:-1]
+Completable: Cpath Value[ax]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[ax]->
+ContextPath Value[ax]
+Path ax
+CPPipe env:CompletePrioritize2
+CPPipe type path:Test.t
+CPPipe pathFromEnv:Test found:true
+Path Test.
+[{
+    "label": "Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }]
+
+Complete src/CompletePrioritize2.res 12:5
+posCursor:[12:5] posNoWhite:[12:4] Found expr:[12:3->12:5]
+Pexp_ident ax:[12:3->12:5]
+Completable: Cpath Value[ax]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[ax]
+Path ax
+[{
+    "label": "ax",
+    "kind": 12,
+    "tags": [],
+    "detail": "Test.t",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype t = {name: int}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt
new file mode 100644
index 0000000000..da4248ef4d
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt
@@ -0,0 +1,2526 @@
+Complete src/Completion.res 1:11
+posCursor:[1:11] posNoWhite:[1:10] Found expr:[1:3->1:11]
+Pexp_ident MyList.m:[1:3->1:11]
+Completable: Cpath Value[MyList, m]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[MyList, m]
+Path MyList.m
+[{
+    "label": "mapReverse",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nEquivalent to:\n\n```res\nmap(someList, f)->reverse\n```\n\n## Examples\n\n```rescript\nlist{3, 4, 5}->Belt.List.mapReverse(x => x * x) /* list{25, 16, 9} */\n```\n"}
+  }, {
+    "label": "makeBy",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int => 'a) => t<'a>",
+    "documentation": {"kind": "markdown", "value": "\nReturn a list of length `numItems` with element `i` initialized with `f(i)`.\nReturns an empty list if `numItems` is negative.\n\n## Examples\n\n```rescript\nBelt.List.makeBy(5, i => i) // list{0, 1, 2, 3, 4}\n\nBelt.List.makeBy(5, i => i * i) // list{0, 1, 4, 9, 16}\n```\n"}
+  }, {
+    "label": "make",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, 'a) => t<'a>",
+    "documentation": {"kind": "markdown", "value": "\nReturns a list of length `numItems` with each element filled with value `v`. Returns an empty list if `numItems` is negative.\n\n## Examples\n\n```rescript\nBelt.List.make(3, 1) // list{1, 1, 1}\n```\n"}
+  }, {
+    "label": "mapReverse2U",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(t<'a>, t<'b>, ('a, 'b) => 'c) => t<'c>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `mapReverse2` instead\n\n Uncurried version of [mapReverse2](#mapReverse2). "}
+  }, {
+    "label": "map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nReturns a new list with `f` applied to each element of `someList`.\n\n## Examples\n\n```rescript\nlist{1, 2}->Belt.List.map(x => x + 1) // list{3, 4}\n```\n"}
+  }, {
+    "label": "mapWithIndexU",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(t<'a>, (int, 'a) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `mapWithIndex` instead\n\n Uncurried version of [mapWithIndex](#mapWithIndex). "}
+  }, {
+    "label": "mapU",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `map` instead\n\n Uncurried version of [map](#map). "}
+  }, {
+    "label": "makeByU",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(int, int => 'a) => t<'a>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `makeBy` instead\n\n Uncurried version of [makeBy](#makeBy) "}
+  }, {
+    "label": "mapReverse2",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, t<'b>, ('a, 'b) => 'c) => t<'c>",
+    "documentation": {"kind": "markdown", "value": "\nEquivalent to: `zipBy(xs, ys, f)->reverse`\n\n## Examples\n\n```rescript\n\nBelt.List.mapReverse2(list{1, 2, 3}, list{1, 2}, (a, b) => a + b) // list{4, 2}\n```\n"}
+  }, {
+    "label": "mapWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, (int, 'a) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies `f` to each element of `someList`.\nFunction `f` takes two arguments: the index starting from 0 and the element from `someList`, in that order.\n\n## Examples\n\n```rescript\nlist{1, 2, 3}->Belt.List.mapWithIndex((index, x) => index + x) // list{1, 3, 5}\n```\n"}
+  }, {
+    "label": "mapReverseU",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `mapReverse` instead\n\n Uncurried version of [mapReverse](#mapReverse). "}
+  }]
+
+Complete src/Completion.res 3:9
+posCursor:[3:9] posNoWhite:[3:8] Found expr:[3:3->3:9]
+Pexp_ident Array.:[3:3->3:9]
+Completable: Cpath Value[Array, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Array, ""]
+Path Array.
+[{
+    "label": "splice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  array<'a>,\n  ~start: int,\n  ~remove: int,\n  ~insert: array<'a>,\n) => unit",
+    "documentation": null
+  }, {
+    "label": "concat",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<'a>) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`concat(array1, array2)` concatenates the two arrays, creating a new array.\n\nSee [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN.\n\n## Examples\n```rescript\nlet array1 = [\"hi\", \"hello\"]\nlet array2 = [\"yay\", \"wehoo\"]\n\nlet someArray = array1->Array.concat(array2)\n\nConsole.log(someArray) // [\"hi\", \"hello\", \"yay\", \"wehoo\"]\n```\n"}
+  }, {
+    "label": "filterMap",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => option<'b>) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`filterMap(array, fn)`\n\nCalls `fn` for each element and returns a new array containing results of the `fn` calls which are not `None`.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\nConsole.log(\n  array->Array.filterMap(item =>\n    switch item {\n    | \"Hello\" => Some(item->String.length)\n    | _ => None\n    }\n  ),\n) // [5]\n```\n"}
+  }, {
+    "label": "shift",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`shift(array)` removes the first item in the array, and returns it.\n\nBeware this will *mutate* the array.\n\nSee [`Array.shift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nlet lastItem = someArray->Array.shift // \"hi\"\n\nConsole.log(someArray) // [\"hello\"]. Notice first item is gone.\n```\n"}
+  }, {
+    "label": "findMap",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => option<'b>) => option<'b>",
+    "documentation": {"kind": "markdown", "value": "\n  `findMap(arr, fn)`\n\n  Calls `fn` for each element and returns the first value from `fn` that is `Some(_)`.\n  Otherwise returns `None`\n\n  ```res example\n  Array.findMap([1, 2, 3], n => mod(n, 2) == 0 ? Some(n - 2) : None) == Some(0) // true\n  ```\n"}
+  }, {
+    "label": "concatMany",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<array<'a>>) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`concatMany(array1, arrays)` concatenates array1 with several other arrays, creating a new array.\n\nSee [`Array.concat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) on MDN.\n\n## Examples\n```rescript\nlet array1 = [\"hi\", \"hello\"]\nlet array2 = [\"yay\"]\nlet array3 = [\"wehoo\"]\n\nlet someArray = array1->Array.concatMany([array2, array3])\n\nConsole.log(someArray) // [\"hi\", \"hello\", \"yay\", \"wehoo\"]\n```\n"}
+  }, {
+    "label": "joinWith",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(array<string>, string) => string",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `join` instead\n\n\n`joinWith(array, separator)` produces a string where all items of `array` are printed, separated by `separator`. Array items must be strings, to join number or other arrays, use `joinWithUnsafe`. Under the hood this will run JavaScript's `toString` on all the array items.\n\n## Examples\n```rescript\nlet array = [\"One\", \"Two\", \"Three\"]\n\nConsole.log(array->Array.joinWith(\" -- \")) // One -- Two -- Three\n```\n"}
+  }, {
+    "label": "joinWithUnsafe",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(array<'a>, string) => string",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `joinUnsafe` instead\n\n\n`joinWithUnsafe(array, separator)` produces a string where all items of `array` are printed, separated by `separator`. Under the hood this will run JavaScript's `toString` on all the array items.\n\n## Examples\n```rescript\nlet array = [1, 2, 3]\n\nConsole.log(array->Array.joinWithUnsafe(\" -- \")) // 1 -- 2 -- 3\n```\n"}
+  }, {
+    "label": "reduceRight",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'b, ('b, 'a) => 'b) => 'b",
+    "documentation": {"kind": "markdown", "value": "\n  `reduceRight(xs, init, fn)`\n\n  Works like `Array.reduce`; except that function `fn` is applied to each item of `xs` from the last back to the first.\n\n  ```res example\n  Array.reduceRight([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"dcba\"\n  ```\n"}
+  }, {
+    "label": "reduceRightWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'b, ('b, 'a, int) => 'b) => 'b",
+    "documentation": {"kind": "markdown", "value": "\n  `reduceRightWithIndex(xs, init, fn)`\n\n  Like `reduceRight`, but with an additional index argument on the callback function.\n\n  ```res example\n  Array.reduceRightWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n  ```\n"}
+  }, {
+    "label": "toShuffled",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`toShuffled(array)` returns a new array with all items in `array` in a random order.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet shuffledArray = array->Array.toShuffled\n\nConsole.log(shuffledArray)\n```\n"}
+  }, {
+    "label": "getSymbol",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, Symbol.t) => option<'b>",
+    "documentation": null
+  }, {
+    "label": "getSymbolUnsafe",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, Symbol.t) => 'b",
+    "documentation": null
+  }, {
+    "label": "findIndexOpt",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => option<int>",
+    "documentation": {"kind": "markdown", "value": "\n`findIndexOpt(array, checker)` returns the index of the first element of `array` where the provided `checker` function returns true.\n\nReturns `None` if no item matches.\n\nSee [`Array.findIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) on MDN.\n\n## Examples\n```rescript\ntype languages = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, TypeScript, JavaScript]\n\nswitch array->Array.findIndexOpt(item => item == ReScript) {\n| None => Console.log(\"Ahh, no ReScript...\")\n| Some(index) => Console.log(\"Yay, ReScript at index \" ++ Int.toString(index))\n}\n```\n"}
+  }, {
+    "label": "shuffle",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => unit",
+    "documentation": {"kind": "markdown", "value": "\n`shuffle(array)` randomizes the position of all items in `array`.\n\nBeware this will *mutate* the array.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\narray->Array.shuffle\n\nConsole.log(array)\n```\n"}
+  }, {
+    "label": "copy",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`copy(array)` makes a copy of the array with the items in it, but does not make copies of the items themselves.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3]\nlet copyOfMyArray = myArray->Array.copy\n\nConsole.log(copyOfMyArray) // [1, 2, 3]\nConsole.log(myArray === copyOfMyArray) // false\n```\n"}
+  }, {
+    "label": "setUnsafe",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int, 'a) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`setUnsafe(array, index, item)` sets the provided `item` at `index` of `array`.\n\nBeware this will *mutate* the array, and is *unsafe*.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\narray->Array.setUnsafe(1, \"Hello\")\n\nConsole.log(array[1]) // \"Hello\"\n```\n"}
+  }, {
+    "label": "findIndexWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => bool) => int",
+    "documentation": {"kind": "markdown", "value": "\n`findIndexWithIndex(array, checker)` returns the index of the first element of `array` where the provided `checker` function returns true.\n\nReturns `-1` if the item does not exist. Consider using `Array.findIndexOpt` if you want an option instead (where `-1` would be `None`).\n\nSee [`Array.findIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) on MDN.\n\n## Examples\n```rescript\ntype languages = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, JavaScript]\n\nlet isReScriptFirst = array->Array.findIndexWithIndex((item, index) => index === 0 && item == ReScript)\nlet isTypeScriptFirst = array->Array.findIndexWithIndex((item, index) => index === 0 && item == TypeScript)\n\nConsole.log(isReScriptFirst) // 0\nConsole.log(isTypeScriptFirst) // -1\n```\n"}
+  }, {
+    "label": "someWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "\n`someWithIndex(array, checker)` returns true if running the provided `checker` function on any element in `array` returns true.\n\nSee [`Array.some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\nConsole.log(array->Array.someWithIndex((greeting, index) => greeting === \"Hello\" && index === 0)) // true\n```\n"}
+  }, {
+    "label": "slice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ~start: int, ~end: int) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`slice(array, ~start, ~end)` creates a new array of items copied from `array` from `start` until (but not including) `end`.\n\nSee [`Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) on MDN.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3, 4]\n\nConsole.log(myArray->Array.slice(~start=1, ~end=3)) // [2, 3]\n```\n"}
+  }, {
+    "label": "fillToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a, ~start: int) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`fillToEnd(array, value, ~start)` fills `array` with `value` from the `start` index.\n\nBeware this will *mutate* the array.\n\nSee [`Array.fill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) on MDN.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3, 4]\nmyArray->Array.fillToEnd(9, ~start=1)\n\nConsole.log(myArray) // [1, 9, 9, 9]\n```\n"}
+  }, {
+    "label": "includes",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => bool",
+    "documentation": {"kind": "markdown", "value": "\n`includes(array, item)` checks whether `array` includes `item`, by doing a [strict check for equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality).\n\nSee [`Array.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) on MDN.\n\n## Examples\n```rescript\nConsole.log([1, 2]->Array.includes(1)) // true\nConsole.log([1, 2]->Array.includes(3)) // false\nConsole.log([{\"language\": \"ReScript\"}]->Array.includes({\"language\": \"ReScript\"})) // false, because of strict equality\n```\n"}
+  }, {
+    "label": "fromInitializer",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~length: int, int => 'a) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n  `fromInitializer(~length, f)`\n\n  Creates an array of length `length` initialized with the value returned from `f ` for each index.\n\n  ```res example\n  Array.fromInitializer(~length=3, i => i + 3) == [3, 4, 5]\n  ```\n"}
+  }, {
+    "label": "find",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`find(array, checker)` returns the first element of `array` where the provided `checker` function returns true.\n\nSee [`Array.find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) on MDN.\n\n## Examples\n```rescript\ntype languages = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, TypeScript, JavaScript]\n\nswitch array->Array.find(item => item == ReScript) {\n| None => Console.log(\"No item...\")\n| Some(_) => Console.log(\"Yay, ReScript!\")\n}\n```\n"}
+  }, {
+    "label": "make",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~length: int, 'a) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n  `make(~length, init)`\n\n  Creates an array of length `length` initialized with the value of `init`.\n\n  ```res example\n  Array.make(~length=3, #apple) == [#apple, #apple, #apple]\n  ```\n"}
+  }, {
+    "label": "lastIndexOfFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a, int) => int",
+    "documentation": null
+  }, {
+    "label": "toLocaleString",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => string",
+    "documentation": null
+  }, {
+    "label": "toSpliced",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  array<'a>,\n  ~start: int,\n  ~remove: int,\n  ~insert: array<'a>,\n) => array<'a>",
+    "documentation": null
+  }, {
+    "label": "sort",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, 'a) => Ordering.t) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`sort(array, comparator)` sorts `array` in-place using the `comparator` function.\n\nBeware this will *mutate* the array.\n\nSee [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) on MDN.\n\n## Examples\n```rescript\nlet someArray = [3, 2, 1]\nsomeArray->Array.sort((a, b) => float(a - b))\n\nConsole.log(someArray) // [1, 2, 3]\n```\n"}
+  }, {
+    "label": "length",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => int",
+    "documentation": {"kind": "markdown", "value": "\n`length(array)` returns the length of (i.e. number of items in) the array.\n\nSee [`Array.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\n\nConsole.log(someArray->Array.length) // 2\n```\n"}
+  }, {
+    "label": "every",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "\n`every(array, predicate)` returns true if `predicate` returns true for all items in `array`.\n\nSee [`Array.every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) on MDN.\n\n## Examples\n```rescript\nlet array = [1, 2, 3, 4]\n\nConsole.log(array->Array.every(num => num <= 4)) // true\nConsole.log(array->Array.every(num => num === 1)) // false\n```\n"}
+  }, {
+    "label": "flat",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<array<'a>> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`flat(arrays)` concatenates an array of arrays into a single array.\n\nSee [`Array.flat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) on MDN.\n\n## Examples\n```rescript\nConsole.log([[1], [2], [3, 4]]->Array.flat) // [1, 2, 3, 4]\n```\n"}
+  }, {
+    "label": "map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => 'b) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`map(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`.\n\nSee [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet mappedArray = array->Array.map(greeting => greeting ++ \" to you\")\n\nConsole.log(mappedArray) // [\"Hello to you\", \"Hi to you\", \"Good bye to you\"]\n```\n"}
+  }, {
+    "label": "with",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int, 'a) => array<'a>",
+    "documentation": null
+  }, {
+    "label": "lastIndexOfOpt",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => option<int>",
+    "documentation": null
+  }, {
+    "label": "toReversed",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`toReversed(array)` creates a new array with all items from `array` in reversed order.\n\nSee [`Array.toReversed`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nlet reversed = someArray->Array.toReversed\n\nConsole.log(reversed) // [\"hello\", \"h1\"]\nConsole.log(someArray) // [\"h1\", \"hello\"]. Original unchanged\n```\n"}
+  }, {
+    "label": "copyWithin",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  array<'a>,\n  ~target: int,\n  ~start: int,\n  ~end: int,\n) => array<'a>",
+    "documentation": null
+  }, {
+    "label": "toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => string",
+    "documentation": {"kind": "markdown", "value": "\n`toString(array)` stringifies `array` by running `toString` on all of the array elements and joining them with \",\".\n\nSee [`Array.toString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString) on MDN.\n\n## Examples\n```rescript\nlet array = [1, 2, 3, 4]\n\nConsole.log(array->Array.toString) // \"1,2,3,4\"\n```\n"}
+  }, {
+    "label": "everyWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "\n`everyWithIndex(array, checker)` returns true if all items in `array` returns true when running the provided `checker` function.\n\nSee [`Array.every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) on MDN.\n\n## Examples\n```rescript\nlet array = [1, 2, 3, 4]\n\nConsole.log(array->Array.everyWithIndex((num, index) => index < 2 && num <= 2)) // true\nConsole.log(array->Array.everyWithIndex((num, index) => index < 2 && num >= 2)) // false\n```\n"}
+  }, {
+    "label": "fill",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a, ~start: int, ~end: int) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`fill(array, value, ~start, ~end)` fills `array` with `value` from `start` to `end`.\n\nBeware this will *mutate* the array.\n\nSee [`Array.fill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) on MDN.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3, 4]\nmyArray->Array.fill(9, ~start=1, ~end=2)\n\nConsole.log(myArray) // [1, 9, 9, 4]\n```\n"}
+  }, {
+    "label": "findWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => bool) => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`findWithIndex(array, checker)` returns the first element of `array` where the provided `checker` function returns true.\n\nSee [`Array.find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) on MDN.\n\n## Examples\n```rescript\ntype languages = ReScript | TypeScript | JavaScript\n\nlet array = [TypeScript, JavaScript, ReScript]\n\nswitch array->Array.findWithIndex((item, index) => index > 1 && item == ReScript) {\n| None => Console.log(\"No item...\")\n| Some(_) => Console.log(\"Yay, ReScript exists in a later position!\")\n}\n```\n"}
+  }, {
+    "label": "reverse",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => unit",
+    "documentation": {"kind": "markdown", "value": "\n`reverse(array)` reverses the order of the items in `array`.\n\nBeware this will *mutate* the array.\n\nSee [`Array.reverse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nsomeArray->Array.reverse\n\nConsole.log(someArray) // [\"hello\", \"h1\"]\n```\n"}
+  }, {
+    "label": "getUnsafe",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int) => 'a",
+    "documentation": {"kind": "markdown", "value": "\n`getUnsafe(array, index)` returns the element at `index` of `array`.\n\nThis is _unsafe_, meaning it will return `undefined` value if `index` does not exist in `array`.\n\nUse `Array.getUnsafe` only when you are sure the `index` exists (i.e. when using for-loop).\n\n## Examples\n```rescript\nlet array = [1, 2, 3]\nfor index in 0 to array->Array.length - 1 {\n  let value = array->Array.getUnsafe(index)\n  Console.log(value)\n}\n```\n"}
+  }, {
+    "label": "unshiftMany",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<'a>) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`unshiftMany(array, itemsArray)` inserts many new items to the start of the array.\n\nBeware this will *mutate* the array.\n\nSee [`Array.push`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nsomeArray->Array.unshiftMany([\"yay\", \"wehoo\"])\n\nConsole.log(someArray) // [\"yay\", \"wehoo\", \"hi\", \"hello\"]\n```\n"}
+  }, {
+    "label": "lastIndexOf",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => int",
+    "documentation": null
+  }, {
+    "label": "filter",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`filter(array, checker)` returns a new array containing all elements from `array` for which the provided `checker` function returns true.\n\nSee [`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) on MDN.\n\n## Examples\n```rescript\nlet array = [1, 2, 3, 4]\n\nConsole.log(array->Array.filter(num => num > 2)) // [3, 4]\n```\n"}
+  }, {
+    "label": "compare",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<'a>, ('a, 'a) => Ordering.t) => Ordering.t",
+    "documentation": null
+  }, {
+    "label": "join",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<string>, string) => string",
+    "documentation": {"kind": "markdown", "value": "\n`join(array, separator)` produces a string where all items of `array` are printed, separated by `separator`. Array items must be strings, to join number or other arrays, use `joinUnsafe`. Under the hood this will run JavaScript's `toString` on all the array items.\n\nSee [Array.join](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join)\n\n## Examples\n```rescript\nlet array = [\"One\", \"Two\", \"Three\"]\n\nConsole.log(array->Array.join(\" -- \")) // One -- Two -- Three\n```\n"}
+  }, {
+    "label": "last",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`last(array)` returns the last element of `array`.\n\nReturns `None` if the array is empty.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\narray->Array.last == Some(\"Good bye\") // true\n[]->Array.last == None // true\n```\n"}
+  }, {
+    "label": "isArray",
+    "kind": 12,
+    "tags": [],
+    "detail": "'a => bool",
+    "documentation": null
+  }, {
+    "label": "indexOfOpt",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => option<int>",
+    "documentation": {"kind": "markdown", "value": "\n`indexOfOpt(array, item)` returns an option of the index of the provided `item` in `array`. Uses [strict check for equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) when comparing items.\n\nSee [`Array.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) on MDN.\n\n## Examples\n```rescript\nConsole.log([1, 2]->Array.indexOfOpt(2)) // Some(1)\nConsole.log([1, 2]->Array.indexOfOpt(3)) // None\nConsole.log([{\"language\": \"ReScript\"}]->Array.indexOfOpt({\"language\": \"ReScript\"})) // None, because of strict equality\n```\n"}
+  }, {
+    "label": "forEachWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => unit) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`forEachWithIndex(array, fn)` runs the provided `fn` on every element of `array`.\n\nSee [`Array.forEach`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\narray->Array.forEachWithIndex((item, index) => {\n  Console.log(\"At item \" ++ Int.toString(index) ++ \": \" ++ item)\n})\n```\n"}
+  }, {
+    "label": "reduce",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'b, ('b, 'a) => 'b) => 'b",
+    "documentation": {"kind": "markdown", "value": "\n  `reduce(xs, init, fn)`\n\n  Applies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an “accumulator”; which starts with a value of `init`. `reduce` returns the final value of the accumulator.\n\n  ```res example\n  Array.reduce([2, 3, 4], 1, (a, b) => a + b) == 10\n\n  Array.reduce([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"abcd\"\n  ```\n"}
+  }, {
+    "label": "sliceToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ~start: int) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(array, start)` creates a new array from `array`, with all items from `array` starting from `start`.\n\nSee [`Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) on MDN.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3, 4]\n\nConsole.log(myArray->Array.sliceToEnd(~start=1)) // [2, 3, 4]\n```\n"}
+  }, {
+    "label": "fromArrayLikeWithMap",
+    "kind": 12,
+    "tags": [],
+    "detail": "(Js.Array2.array_like<'a>, 'a => 'b) => array<'b>",
+    "documentation": null
+  }, {
+    "label": "fillAll",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`fillAll(array, value)` fills the entire `array` with `value`.\n\nBeware this will *mutate* the array.\n\nSee [`Array.fill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) on MDN.\n\n## Examples\n```rescript\nlet myArray = [1, 2, 3, 4]\nmyArray->Array.fillAll(9)\n\nConsole.log(myArray) // [9, 9, 9, 9]\n```\n"}
+  }, {
+    "label": "set",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int, 'a) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`set(array, index, item)` sets the provided `item` at `index` of `array`.\n\nBeware this will *mutate* the array.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\narray->Array.set(1, \"Hello\")\n\nConsole.log(array[1]) // \"Hello\"\n```\n"}
+  }, {
+    "label": "filterWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => bool) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`filterWithIndex(array, checker)` returns a new array containing all elements from `array` for which the provided `checker` function returns true.\n\nSee [`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) on MDN.\n\n## Examples\n```rescript\nlet array = [1, 2, 3, 4]\n\nConsole.log(array->Array.filterWithIndex((num, index) => index === 0 || num === 2)) // [1, 2]\n```\n"}
+  }, {
+    "label": "findIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => int",
+    "documentation": {"kind": "markdown", "value": "\n`findIndex(array, checker)` returns the index of the first element of `array` where the provided `checker` function returns true.\n\nReturns `-1` if the item does not exist. Consider using `Array.findIndexOpt` if you want an option instead (where `-1` would be `None`).\n\nSee [`Array.findIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) on MDN.\n\n## Examples\n```rescript\ntype languages = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, JavaScript]\n\nConsole.log(array->Array.findIndex(item => item == ReScript)) // 0\nConsole.log(array->Array.findIndex(item => item == TypeScript)) // -1\n```\n"}
+  }, {
+    "label": "setSymbol",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, Symbol.t, 'b) => unit",
+    "documentation": null
+  }, {
+    "label": "equal",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<'a>, ('a, 'a) => bool) => bool",
+    "documentation": null
+  }, {
+    "label": "joinUnsafe",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, string) => string",
+    "documentation": {"kind": "markdown", "value": "\n`joinUnsafe(array, separator)` produces a string where all items of `array` are printed, separated by `separator`. Under the hood this will run JavaScript's `toString` on all the array items.\n\nSee [Array.join](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join)\n\n## Examples\n```rescript\nlet array = [1, 2, 3]\n\nConsole.log(array->Array.joinUnsafe(\" -- \")) // 1 -- 2 -- 3\n```\n"}
+  }, {
+    "label": "mapWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => 'b) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`mapWithIndex(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`.\n\nSee [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet mappedArray =\n  array->Array.mapWithIndex((greeting, index) =>\n    greeting ++ \" at position \" ++ Int.toString(index)\n  )\n\nConsole.log(mappedArray) // [\"Hello at position 0\", \"Hi at position 1\", \"Good bye at position 2\"]\n```\n"}
+  }, {
+    "label": "flatMapWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => array<'b>) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`flatMapWithIndex(array, mapper)` returns a new array concatenating the arrays returned from running `mapper` on all items in `array`.\n\n## Examples\n```rescript\ntype language = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, TypeScript, JavaScript]\n\nConsole.log(\n  array->Array.flatMapWithIndex((item, index) =>\n    switch item {\n    | ReScript => [index]\n    | TypeScript => [index, index + 1]\n    | JavaScript => [index, index + 1, index + 2]\n    }\n  ),\n)\n// [0, 1, 2, 2, 3, 4]\n```\n"}
+  }, {
+    "label": "copyWithinToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ~target: int, ~start: int) => array<'a>",
+    "documentation": null
+  }, {
+    "label": "unshift",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`unshift(array, item)` inserts a new item at the start of the array.\n\nBeware this will *mutate* the array.\n\nSee [`Array.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nsomeArray->Array.unshift(\"yay\")\n\nConsole.log(someArray) // [\"yay\", \"hi\", \"hello\"]\n```\n"}
+  }, {
+    "label": "indexOf",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => int",
+    "documentation": {"kind": "markdown", "value": "\n`indexOf(array, item)` returns the index of the provided `item` in `array`. Uses [strict check for equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) when comparing items.\n\nReturns `-1` if the item doesn not exist. Check out `Array.indexOfOpt` for a version that returns `None` instead of `-1` if the item does not exist.\n\nSee [`Array.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) on MDN.\n\n## Examples\n```rescript\nConsole.log([1, 2]->Array.indexOf(2)) // 1\nConsole.log([1, 2]->Array.indexOf(3)) // -1\nConsole.log([{\"language\": \"ReScript\"}]->Array.indexOf({\"language\": \"ReScript\"})) // -1, because of strict equality\n```\n"}
+  }, {
+    "label": "push",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`push(array, item)` appends `item` to the end of `array`.\n\nBeware this will *mutate* the array.\n\nSee [`Array.push`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nsomeArray->Array.push(\"yay\")\n\nConsole.log(someArray) // [\"hi\", \"hello\", \"yay\"]\n```\n"}
+  }, {
+    "label": "toSorted",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, 'a) => Ordering.t) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`toSorted(array, comparator)` returns a new, sorted array from `array`, using the `comparator` function.\n\nSee [`Array.toSorted`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted) on MDN.\n\n## Examples\n```rescript\nlet someArray = [3, 2, 1]\nlet sorted = someArray->Array.toSorted(Int.compare)\n\nConsole.log(sorted) // [1, 2, 3]\nConsole.log(someArray) // [3, 2, 1]. Original unchanged\n```\n"}
+  }, {
+    "label": "reduceWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'b, ('b, 'a, int) => 'b) => 'b",
+    "documentation": {"kind": "markdown", "value": "\n  `reduceWithIndex(x, init, fn)`\n\n  Applies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an “accumulator”, which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator.\n\n  ```res example\n  Array.reduceWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n  ```\n"}
+  }, {
+    "label": "some",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "\n`some(array, predicate)` returns true if `predicate` returns true for any element in `array`.\n\nSee [`Array.some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\nConsole.log(array->Array.some(greeting => greeting === \"Hello\")) // true\n```\n"}
+  }, {
+    "label": "unsafe_get",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(array<'a>, int) => 'a",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use getUnsafe instead. This will be removed in v13\n\n\n`unsafe_get(array, index)` returns the element at `index` of `array`.\n\nThis is _unsafe_, meaning it will return `undefined` value if `index` does not exist in `array`.\n\nUse `Array.unsafe_get` only when you are sure the `index` exists (i.e. when using for-loop).\n\n## Examples\n```rescript\nlet array = [1, 2, 3]\nfor index in 0 to array->Array.length - 1 {\n  let value = array->Array.unsafe_get(index)\n  Console.log(value)\n}\n```\n"}
+  }, {
+    "label": "copyAllWithin",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ~target: int) => array<'a>",
+    "documentation": null
+  }, {
+    "label": "keepSome",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<option<'a>> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n  `keepSome(arr)`\n\n  Returns a new array containing `value` for all elements that are `Some(value)`\n  and ignoring every value that is `None`\n\n  ```res example\n  Array.keepSome([Some(1), None, Some(3)]) == [1, 3]\n  ```\n"}
+  }, {
+    "label": "at",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int) => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n  `at(array, index)`\n\n  Get an element by its index. Negative indices count backwards from the last item.\n\n  ## Examples\n  ```rescript\n  [\"a\", \"b\", \"c\"]->Array.at(0) // Some(\"a\")\n  [\"a\", \"b\", \"c\"]->Array.at(2) // Some(\"c\")\n  [\"a\", \"b\", \"c\"]->Array.at(3) // None\n  [\"a\", \"b\", \"c\"]->Array.at(-1) // Some(\"c\")\n  [\"a\", \"b\", \"c\"]->Array.at(-3) // Some(\"a\")\n  [\"a\", \"b\", \"c\"]->Array.at(-4) // None\n  ```\n"}
+  }, {
+    "label": "pop",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<'a> => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`pop(array)` removes the last item from `array` and returns it.\n\nBeware this will *mutate* the array.\n\nSee [`Array.pop`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nlet lastItem = someArray->Array.pop // \"hello\"\n\nConsole.log(someArray) // [\"hi\"]. Notice last item is gone.\n```\n"}
+  }, {
+    "label": "get",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, int) => option<'a>",
+    "documentation": {"kind": "markdown", "value": "\n`get(array, index)` returns the element at `index` of `array`.\n\nReturns `None` if the index does not exist in the array. Equivalent to doing `array[index]` in JavaScript.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\narray->Array.get(0) == Some(\"Hello\") // true\narray->Array.get(3) == None // true\n```\n"}
+  }, {
+    "label": "pushMany",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, array<'a>) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`pushMany(array, itemsArray)` appends many new items to the end of the array.\n\nBeware this will *mutate* the array.\n\nSee [`Array.push`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) on MDN.\n\n## Examples\n```rescript\nlet someArray = [\"hi\", \"hello\"]\nsomeArray->Array.pushMany([\"yay\", \"wehoo\"])\n\nConsole.log(someArray) // [\"hi\", \"hello\", \"yay\", \"wehoo\"]\n```\n"}
+  }, {
+    "label": "fromIterator",
+    "kind": 12,
+    "tags": [],
+    "detail": "Iterator.t<'a> => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n `fromIterator(iterator)`\n\n  Creates an array from the provided `iterator`\n\n  ```res example\n  let map = Map.fromArray([(\"foo\", 1), (\"bar\", 2)])\n\n  Array.fromIterator(map->Map.values) // [1, 2]\n  ```\n "}
+  }, {
+    "label": "forEach",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => unit) => unit",
+    "documentation": {"kind": "markdown", "value": "\n`forEach(array, fn)` runs the provided `fn` on every element of `array`.\n\nSee [`Array.forEach`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\n\narray->Array.forEach(item => {\n  Console.log(item)\n})\n```\n"}
+  }, {
+    "label": "flatMap",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => array<'b>) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`flatMap(array, mapper)` returns a new array concatenating the arrays returned from running `mapper` on all items in `array`.\n\n## Examples\n```rescript\ntype language = ReScript | TypeScript | JavaScript\n\nlet array = [ReScript, TypeScript, JavaScript]\n\nConsole.log(\n  array->Array.flatMap(item =>\n    switch item {\n    | ReScript => [1, 2, 3]\n    | TypeScript => [4, 5, 6]\n    | JavaScript => [7, 8, 9]\n    }\n  ),\n)\n// [1, 2, 3, 4, 5, 6, 7, 8, 9]\n```\n"}
+  }, {
+    "label": "fromArrayLike",
+    "kind": 12,
+    "tags": [],
+    "detail": "Js.Array2.array_like<'a> => array<'a>",
+    "documentation": null
+  }, {
+    "label": "indexOfFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a, int) => int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 5:10
+posCursor:[5:10] posNoWhite:[5:9] Found expr:[5:3->5:10]
+Pexp_ident Array.m:[5:3->5:10]
+Completable: Cpath Value[Array, m]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Array, m]
+Path Array.m
+[{
+    "label": "make",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~length: int, 'a) => array<'a>",
+    "documentation": {"kind": "markdown", "value": "\n  `make(~length, init)`\n\n  Creates an array of length `length` initialized with the value of `init`.\n\n  ```res example\n  Array.make(~length=3, #apple) == [#apple, #apple, #apple]\n  ```\n"}
+  }, {
+    "label": "map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, 'a => 'b) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`map(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`.\n\nSee [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet mappedArray = array->Array.map(greeting => greeting ++ \" to you\")\n\nConsole.log(mappedArray) // [\"Hello to you\", \"Hi to you\", \"Good bye to you\"]\n```\n"}
+  }, {
+    "label": "mapWithIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "(array<'a>, ('a, int) => 'b) => array<'b>",
+    "documentation": {"kind": "markdown", "value": "\n`mapWithIndex(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`.\n\nSee [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet mappedArray =\n  array->Array.mapWithIndex((greeting, index) =>\n    greeting ++ \" at position \" ++ Int.toString(index)\n  )\n\nConsole.log(mappedArray) // [\"Hello at position 0\", \"Hi at position 1\", \"Good bye at position 2\"]\n```\n"}
+  }]
+
+Complete src/Completion.res 15:17
+posCursor:[15:17] posNoWhite:[15:16] Found expr:[15:12->15:17]
+Pexp_ident Dep.c:[15:12->15:17]
+Completable: Cpath Value[Dep, c]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Dep, c]
+Path Dep.c
+[{
+    "label": "customDouble",
+    "kind": 12,
+    "tags": [1],
+    "detail": "int => int",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use customDouble instead\n\nSome doc comment"}
+  }]
+
+Complete src/Completion.res 23:20
+posCursor:[23:20] posNoWhite:[23:19] Found expr:[23:11->23:20]
+Pexp_apply ...[23:11->23:18] ()
+Completable: CnamedArg(Value[Lib, foo], "", [])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lib, foo]
+Path Lib.foo
+Found type for function (~age: int, ~name: string) => string
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 26:13
+posCursor:[26:13] posNoWhite:[26:12] Found expr:[26:3->26:13]
+Completable: Cpath array<int>->m
+Package opens Pervasives.JsxModules.place holder
+ContextPath array<int>->m
+ContextPath array<int>
+CPPipe env:Completion
+Path Js.Array2.m
+[{
+    "label": "Js.Array2.mapi",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, ('a, int) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}
+  }, {
+    "label": "Js.Array2.map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}
+  }]
+
+Complete src/Completion.res 29:13
+posCursor:[29:13] posNoWhite:[29:12] Found expr:[29:3->29:13]
+Completable: Cpath string->toU
+Package opens Pervasives.JsxModules.place holder
+ContextPath string->toU
+ContextPath string
+CPPipe env:Completion
+Path Js.String2.toU
+[{
+    "label": "Js.String2.toUpperCase",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}
+  }]
+
+Complete src/Completion.res 34:8
+posCursor:[34:8] posNoWhite:[34:7] Found expr:[34:3->34:8]
+Completable: Cpath Value[op]->e
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[op]->e
+ContextPath Value[op]
+Path op
+CPPipe env:Completion
+Path Belt.Option.e
+[{
+    "label": "Belt.Option.eqU",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(option<'a>, option<'b>, ('a, 'b) => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use `eq` instead\n\n\nUncurried version of `eq`\n"}
+  }, {
+    "label": "Belt.Option.eq",
+    "kind": 12,
+    "tags": [],
+    "detail": "(option<'a>, option<'b>, ('a, 'b) => bool) => bool",
+    "documentation": {"kind": "markdown", "value": "\nEvaluates two optional values for equality with respect to a predicate\nfunction. If both `optValue1` and `optValue2` are `None`, returns `true`.\nIf one of the arguments is `Some(value)` and the other is `None`, returns\n`false`.\n\nIf arguments are `Some(value1)` and `Some(value2)`, returns the result of\n`predicate(value1, value2)`; the predicate function must return a bool.\n\n## Examples\n\n```rescript\nlet clockEqual = (a, b) => mod(a, 12) == mod(b, 12)\n\nopen Belt.Option\n\neq(Some(3), Some(15), clockEqual) /* true */\n\neq(Some(3), None, clockEqual) /* false */\n\neq(None, Some(3), clockEqual) /* false */\n\neq(None, None, clockEqual) /* true */\n```\n"}
+  }]
+
+Complete src/Completion.res 44:7
+posCursor:[44:7] posNoWhite:[44:6] Found expr:[44:3->54:3]
+Pexp_apply ...[50:9->50:10] (...[44:3->50:8], ...[51:2->54:3])
+posCursor:[44:7] posNoWhite:[44:6] Found expr:[44:3->50:8]
+Completable: Cpath Value[fa]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fa]->
+ContextPath Value[fa]
+Path fa
+CPPipe env:Completion
+CPPipe type path:ForAuto.t
+CPPipe pathFromEnv:ForAuto found:true
+Path ForAuto.
+[{
+    "label": "ForAuto.abc",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "ForAuto.abd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 47:21
+posCursor:[47:21] posNoWhite:[47:20] Found expr:[47:3->47:21]
+posCursor:[47:21] posNoWhite:[47:20] Found expr:[47:12->47:21]
+Pexp_ident Js.Dict.u:[47:12->47:21]
+Completable: Cpath Value[Js, Dict, u]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Js, Dict, u]
+Path Js.Dict.u
+[{
+    "label": "unsafeGet",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, key) => 'a",
+    "documentation": {"kind": "markdown", "value": "\n`Js.Dict.unsafeGet(key)` returns the value if the key exists, otherwise an `undefined` value is returned. Use this only when you are sure the key exists (i.e. when having used the `keys()` function to check that the key is valid).\n\n## Examples\n\n```rescript\nJs.Dict.unsafeGet(ages, \"Fred\") == 49\nJs.Dict.unsafeGet(ages, \"Paul\") // returns undefined\n```\n"}
+  }, {
+    "label": "unsafeDeleteKey",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<string>, string) => unit",
+    "documentation": {"kind": "markdown", "value": " Experimental internal function "}
+  }]
+
+Complete src/Completion.res 59:30
+posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:15->59:30]
+JSX <O.Comp:[59:15->59:21] second[59:22->59:28]=...[59:29->59:30]> _children:None
+Completable: Cexpression CJsxPropValue [O, Comp] second=z
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [O, Comp] second
+Path O.Comp.make
+[{
+    "label": "zzz",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 62:23
+posCursor:[62:23] posNoWhite:[62:22] Found expr:[62:15->62:23]
+JSX <O.Comp:[62:15->62:21] z[62:22->62:23]=...[62:22->62:23]> _children:None
+Completable: Cjsx([O, Comp], z, [z])
+Package opens Pervasives.JsxModules.place holder
+Path O.Comp.make
+[{
+    "label": "zoo",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 65:8
+Attribute id:reac:[65:3->65:8] label:reac
+Completable: Cdecorator(reac)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "react.component",
+    "kind": 4,
+    "tags": [],
+    "detail": "",
+    "documentation": {"kind": "markdown", "value": "The `@react.component` decorator is used to annotate functions that are RescriptReact components.\n\nYou will need this decorator whenever you want to use a ReScript / React component in ReScript JSX expressions.\n\nNote: The `@react.component` decorator requires the `jsx` config to be set in your `rescript.json`/`bsconfig.json` to enable the required React transformations.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#react-component-decorator)."},
+    "insertTextFormat": 2
+  }]
+
+Complete src/Completion.res 68:10
+posCursor:[68:10] posNoWhite:[68:9] Found expr:[0:-1->86:1]
+Pexp_apply ...[80:6->80:7] (...[80:8->86:1])
+Attribute id:react.let:[68:3->80:3] label:react.
+Completable: Cdecorator(react.)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "component",
+    "kind": 4,
+    "tags": [],
+    "detail": "",
+    "documentation": {"kind": "markdown", "value": "The `@react.component` decorator is used to annotate functions that are RescriptReact components.\n\nYou will need this decorator whenever you want to use a ReScript / React component in ReScript JSX expressions.\n\nNote: The `@react.component` decorator requires the `jsx` config to be set in your `rescript.json`/`bsconfig.json` to enable the required React transformations.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#react-component-decorator)."},
+    "insertTextFormat": 2
+  }]
+
+Complete src/Completion.res 71:27
+posCursor:[71:27] posNoWhite:[71:26] Found expr:[71:11->71:27]
+Pexp_apply ...[71:11->71:18] (~name71:20->71:24=...[71:20->71:24])
+Completable: CnamedArg(Value[Lib, foo], "", [name])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lib, foo]
+Path Lib.foo
+Found type for function (~age: int, ~name: string) => string
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 74:26
+posCursor:[74:26] posNoWhite:[74:25] Found expr:[74:11->74:26]
+Pexp_apply ...[74:11->74:18] (~age74:20->74:23=...[74:20->74:23])
+Completable: CnamedArg(Value[Lib, foo], "", [age])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lib, foo]
+Path Lib.foo
+Found type for function (~age: int, ~name: string) => string
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 77:32
+posCursor:[77:32] posNoWhite:[77:31] Found expr:[77:11->77:32]
+Pexp_apply ...[77:11->77:18] (~age77:20->77:23=...[77:25->77:28])
+Completable: CnamedArg(Value[Lib, foo], "", [age])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lib, foo]
+Path Lib.foo
+Found type for function (~age: int, ~name: string) => string
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 82:5
+posCursor:[82:5] posNoWhite:[82:4] Found expr:[80:8->86:1]
+Pexp_apply ...[80:8->80:15] (~age84:3->84:6=...[84:7->84:8], ~name85:3->85:7=...[85:8->85:10])
+Completable: CnamedArg(Value[Lib, foo], "", [age, name])
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lib, foo]
+Path Lib.foo
+Found type for function (~age: int, ~name: string) => string
+[]
+
+Complete src/Completion.res 90:13
+posCursor:[90:13] posNoWhite:[90:12] Found expr:[90:3->93:18]
+Pexp_send a[90:12->90:13] e:[90:3->90:10]
+Completable: Cpath Value[someObj]["a"]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someObj]["a"]
+ContextPath Value[someObj]
+Path someObj
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 95:24
+posCursor:[95:24] posNoWhite:[95:23] Found expr:[95:3->99:6]
+Pexp_send [95:24->95:24] e:[95:3->95:22]
+Completable: Cpath Value[nestedObj]["x"]["y"][""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[nestedObj]["x"]["y"][""]
+ContextPath Value[nestedObj]["x"]["y"]
+ContextPath Value[nestedObj]["x"]
+ContextPath Value[nestedObj]
+Path nestedObj
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 99:7
+posCursor:[99:7] posNoWhite:[99:6] Found expr:[99:3->102:20]
+Pexp_send a[99:6->99:7] e:[99:3->99:4]
+Completable: Cpath Value[o]["a"]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[o]["a"]
+ContextPath Value[o]
+Path o
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 104:17
+posCursor:[104:17] posNoWhite:[104:16] Found expr:[104:3->125:19]
+Pexp_send [104:17->104:17] e:[104:3->104:15]
+Completable: Cpath Value[no]["x"]["y"][""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[no]["x"]["y"][""]
+ContextPath Value[no]["x"]["y"]
+ContextPath Value[no]["x"]
+ContextPath Value[no]
+Path no
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }, {
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 110:5
+posCursor:[110:5] posNoWhite:[110:4] Found expr:[110:3->110:5]
+Pexp_field [110:3->110:4] _:[116:0->110:5]
+Completable: Cpath Value[r].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[r].""
+ContextPath Value[r]
+Path r
+[{
+    "label": "x",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype r = {x: int, y: string}\n```"}
+  }, {
+    "label": "y",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\ny: string\n```\n\n```rescript\ntype r = {x: int, y: string}\n```"}
+  }]
+
+Complete src/Completion.res 113:25
+posCursor:[113:25] posNoWhite:[113:24] Found expr:[113:3->113:25]
+Pexp_field [113:3->113:24] _:[116:0->113:25]
+Completable: Cpath Value[Objects, Rec, recordVal].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Objects, Rec, recordVal].""
+ContextPath Value[Objects, Rec, recordVal]
+Path Objects.Rec.recordVal
+[{
+    "label": "xx",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nxx: int\n```\n\n```rescript\ntype recordt = {xx: int, ss: string}\n```"}
+  }, {
+    "label": "ss",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nss: string\n```\n\n```rescript\ntype recordt = {xx: int, ss: string}\n```"}
+  }]
+
+Complete src/Completion.res 120:7
+posCursor:[120:7] posNoWhite:[120:6] Found expr:[119:11->123:1]
+posCursor:[120:7] posNoWhite:[120:6] Found expr:[119:11->123:1]
+posCursor:[120:7] posNoWhite:[120:6] Found expr:[120:5->122:5]
+posCursor:[120:7] posNoWhite:[120:6] Found expr:[120:5->120:7]
+Pexp_ident my:[120:5->120:7]
+Completable: Cpath Value[my]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[my]
+Path my
+[{
+    "label": "myAmazingFunction",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 125:19
+posCursor:[125:19] posNoWhite:[125:18] Found expr:[125:3->145:32]
+Pexp_send [125:19->125:19] e:[125:3->125:17]
+Completable: Cpath Value[Objects, object][""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Objects, object][""]
+ContextPath Value[Objects, object]
+Path Objects.object
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }, {
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 151:6
+posCursor:[151:6] posNoWhite:[151:5] Found expr:[151:4->151:6]
+JSX <O.:[151:4->151:6] > _children:None
+Completable: Cpath Module[O, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[O, ""]
+Path O.
+[{
+    "label": "Comp",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Comp",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 157:8
+posCursor:[157:8] posNoWhite:[157:7] Found expr:[157:3->157:8]
+Pexp_field [157:3->157:7] _:[165:0->157:8]
+Completable: Cpath Value[q].aa.""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[q].aa.""
+ContextPath Value[q].aa
+ContextPath Value[q]
+Path q
+[{
+    "label": "x",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype aa = {x: int, name: string}\n```"}
+  }, {
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype aa = {x: int, name: string}\n```"}
+  }]
+
+Complete src/Completion.res 159:9
+posCursor:[159:9] posNoWhite:[159:8] Found expr:[159:3->159:9]
+Pexp_field [159:3->159:7] n:[159:8->159:9]
+Completable: Cpath Value[q].aa.n
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[q].aa.n
+ContextPath Value[q].aa
+ContextPath Value[q]
+Path q
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype aa = {x: int, name: string}\n```"}
+  }]
+
+Complete src/Completion.res 162:6
+posCursor:[162:6] posNoWhite:[162:5] Found expr:[162:3->162:6]
+Pexp_construct Lis:[162:3->162:6] None
+Completable: Cpath Value[Lis]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Lis]
+Path Lis
+[{
+    "label": "List",
+    "kind": 9,
+    "tags": [],
+    "detail": "module List",
+    "documentation": null,
+    "data": {
+      "modulePath": "List",
+      "filePath": "src/Completion.res"
+    }
+  }]
+
+Complete src/Completion.res 169:16
+posCursor:[169:16] posNoWhite:[169:15] Found expr:[169:4->169:16]
+JSX <WithChildren:[169:4->169:16] > _children:None
+Completable: Cpath Module[WithChildren]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[WithChildren]
+Path WithChildren
+[{
+    "label": "WithChildren",
+    "kind": 9,
+    "tags": [],
+    "detail": "module WithChildren",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 172:16
+posCursor:[172:16] posNoWhite:[172:15] Found type:[172:12->172:16]
+Ptyp_constr Js.n:[172:12->172:16]
+Completable: Cpath Type[Js, n]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[Js, n]
+Path Js.n
+[{
+    "label": "null_undefined",
+    "kind": 22,
+    "tags": [],
+    "detail": "type null_undefined",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype null_undefined<'a> = nullable<'a>\n```"}
+  }, {
+    "label": "nullable",
+    "kind": 22,
+    "tags": [],
+    "detail": "type nullable",
+    "documentation": {"kind": "markdown", "value": "```rescript\n@unboxed\ntype nullable<'a> = Js_null_undefined.t<'a> =\n  | Value('a)\n  | @as(null) Null\n  | @as(undefined) Undefined\n```"}
+  }, {
+    "label": "null",
+    "kind": 22,
+    "tags": [],
+    "detail": "type null",
+    "documentation": {"kind": "markdown", "value": "```rescript\n@unboxed\ntype null<'a> = Js_null.t<'a> = Value('a) | @as(null) Null\n```"}
+  }]
+
+Complete src/Completion.res 174:20
+posCursor:[174:20] posNoWhite:[174:19] Found type:[174:12->174:20]
+Ptyp_constr ForAuto.:[174:12->174:20]
+Completable: Cpath Type[ForAuto, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[ForAuto, ""]
+Path ForAuto.
+[{
+    "label": "t",
+    "kind": 22,
+    "tags": [],
+    "detail": "type t",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype t = int\n```"}
+  }]
+
+Complete src/Completion.res 179:13
+posCursor:[179:13] posNoWhite:[179:12] Found expr:[179:11->179:13]
+Pexp_construct As:[179:11->179:13] None
+Completable: Cpath Value[As]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[As]
+Path As
+[{
+    "label": "Asterix",
+    "kind": 4,
+    "tags": [],
+    "detail": "Asterix",
+    "documentation": {"kind": "markdown", "value": "```rescript\nAsterix\n```\n\n```rescript\ntype z = Allo | Asterix | Baba\n```"}
+  }, {
+    "label": "AsyncIterator",
+    "kind": 9,
+    "tags": [],
+    "detail": "module AsyncIterator",
+    "documentation": null,
+    "data": {
+      "modulePath": "AsyncIterator",
+      "filePath": "src/Completion.res"
+    }
+  }]
+
+Complete src/Completion.res 182:17
+Pmod_ident For:[182:14->182:17]
+Completable: Cpath Module[For]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[For]
+Path For
+[{
+    "label": "ForAuto",
+    "kind": 9,
+    "tags": [],
+    "detail": "module ForAuto",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 190:11
+posCursor:[190:11] posNoWhite:[190:10] Found expr:[190:3->190:11]
+Pexp_ident Private.:[190:3->190:11]
+Completable: Cpath Value[Private, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Private, ""]
+Path Private.
+[{
+    "label": "b",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 202:6
+posCursor:[202:6] posNoWhite:[202:5] Found expr:[202:3->202:6]
+Pexp_ident sha:[202:3->202:6]
+Completable: Cpath Value[sha]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[sha]
+Path sha
+[]
+
+Complete src/Completion.res 205:6
+posCursor:[205:6] posNoWhite:[205:5] Found expr:[205:3->205:6]
+Pexp_ident sha:[205:3->205:6]
+Completable: Cpath Value[sha]
+Raw opens: 1 Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Completion
+ContextPath Value[sha]
+Path sha
+[{
+    "label": "shadowed",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 208:6
+posCursor:[208:6] posNoWhite:[208:5] Found expr:[208:3->208:6]
+Pexp_ident sha:[208:3->208:6]
+Completable: Cpath Value[sha]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[sha]
+Path sha
+[{
+    "label": "shadowed",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 221:22
+posCursor:[221:22] posNoWhite:[221:21] Found expr:[221:3->224:22]
+Pexp_send [221:22->221:22] e:[221:3->221:20]
+Completable: Cpath Value[FAO, forAutoObject][""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[FAO, forAutoObject][""]
+ContextPath Value[FAO, forAutoObject]
+Path FAO.forAutoObject
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "forAutoLabel",
+    "kind": 4,
+    "tags": [],
+    "detail": "FAR.forAutoRecord",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 224:37
+posCursor:[224:37] posNoWhite:[224:36] Found expr:[224:3->224:37]
+Pexp_field [224:3->224:36] _:[233:0->224:37]
+Completable: Cpath Value[FAO, forAutoObject]["forAutoLabel"].""
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[FAO, forAutoObject]["forAutoLabel"].""
+ContextPath Value[FAO, forAutoObject]["forAutoLabel"]
+ContextPath Value[FAO, forAutoObject]
+Path FAO.forAutoObject
+[{
+    "label": "forAuto",
+    "kind": 5,
+    "tags": [],
+    "detail": "ForAuto.t",
+    "documentation": {"kind": "markdown", "value": "```rescript\nforAuto: ForAuto.t\n```\n\n```rescript\ntype forAutoRecord = {\n  forAuto: ForAuto.t,\n  something: option<int>,\n}\n```"}
+  }, {
+    "label": "something",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomething: option<int>\n```\n\n```rescript\ntype forAutoRecord = {\n  forAuto: ForAuto.t,\n  something: option<int>,\n}\n```"}
+  }]
+
+Complete src/Completion.res 227:46
+posCursor:[227:46] posNoWhite:[227:45] Found expr:[227:3->0:-1]
+Completable: Cpath Value[FAO, forAutoObject]["forAutoLabel"].forAuto->
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto->
+ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto
+ContextPath Value[FAO, forAutoObject]["forAutoLabel"]
+ContextPath Value[FAO, forAutoObject]
+Path FAO.forAutoObject
+CPPipe env:Completion envFromCompletionItem:Completion.FAR
+CPPipe type path:ForAuto.t
+CPPipe pathFromEnv:ForAuto found:false
+Path ForAuto.
+[{
+    "label": "ForAuto.abc",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "ForAuto.abd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 230:55
+posCursor:[230:55] posNoWhite:[230:54] Found expr:[230:3->230:55]
+posCursor:[230:55] posNoWhite:[230:54] Found expr:[230:46->230:55]
+Pexp_ident ForAuto.a:[230:46->230:55]
+Completable: Cpath Value[ForAuto, a]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ForAuto, a]
+Path ForAuto.a
+[{
+    "label": "abc",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "abd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 234:34
+posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:36]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:34], ...[234:34->234:35])
+posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:34]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:30], ...[234:32->234:34])
+posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:32->234:34]
+Pexp_ident na:[234:32->234:34]
+Completable: Cpath Value[na]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[na]
+Path na
+[{
+    "label": "name",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 237:17
+posCursor:[237:17] posNoWhite:[237:14] Found expr:[237:14->237:22]
+Completable: Cnone
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+[]
+
+Complete src/Completion.res 243:8
+posCursor:[243:8] posNoWhite:[243:7] Found expr:[242:14->243:8]
+Pexp_apply ...[243:3->243:4] (...[242:14->242:15], ...[243:5->243:8])
+posCursor:[243:8] posNoWhite:[243:7] Found expr:[243:5->243:8]
+Pexp_field [243:5->243:7] _:[245:0->243:8]
+Completable: Cpath Value[_z].""
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[_z].""
+ContextPath Value[_z]
+Path _z
+[{
+    "label": "x",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype r = {x: int, y: string}\n```"}
+  }, {
+    "label": "y",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\ny: string\n```\n\n```rescript\ntype r = {x: int, y: string}\n```"}
+  }]
+
+Complete src/Completion.res 254:17
+posCursor:[254:17] posNoWhite:[254:16] Found expr:[254:11->254:17]
+Pexp_construct SomeLo:[254:11->254:17] None
+Completable: Cpath Value[SomeLo]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[SomeLo]
+Path SomeLo
+[{
+    "label": "SomeLocalModule",
+    "kind": 9,
+    "tags": [],
+    "detail": "module SomeLocalModule",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 256:29
+posCursor:[256:29] posNoWhite:[256:28] Found type:[256:13->256:29]
+Ptyp_constr SomeLocalModule.:[256:13->256:29]
+Completable: Cpath Type[SomeLocalModule, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[SomeLocalModule, ""]
+Path SomeLocalModule.
+[{
+    "label": "zz",
+    "kind": 22,
+    "tags": [],
+    "detail": "type zz",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype zz = int\n```"}
+  }]
+
+Complete src/Completion.res 261:33
+posCursor:[261:33] posNoWhite:[261:32] Found type:[261:17->263:11]
+Ptyp_constr SomeLocalModule.:[261:17->263:11]
+Completable: Cpath Type[SomeLocalModule, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[SomeLocalModule, ""]
+Path SomeLocalModule.
+[{
+    "label": "zz",
+    "kind": 22,
+    "tags": [],
+    "detail": "type zz",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype zz = int\n```"}
+  }]
+
+Complete src/Completion.res 268:21
+Ptype_variant unary SomeLocal:[268:12->268:21]
+Completable: Cpath Value[SomeLocal]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[SomeLocal]
+Path SomeLocal
+[{
+    "label": "SomeLocalVariantItem",
+    "kind": 4,
+    "tags": [],
+    "detail": "SomeLocalVariantItem",
+    "documentation": {"kind": "markdown", "value": "```rescript\nSomeLocalVariantItem\n```\n\n```rescript\ntype someLocalVariant = SomeLocalVariantItem\n```"}
+  }, {
+    "label": "SomeLocalModule",
+    "kind": 9,
+    "tags": [],
+    "detail": "module SomeLocalModule",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 271:20
+posCursor:[271:20] posNoWhite:[271:19] Found pattern:[271:7->274:3]
+posCursor:[271:20] posNoWhite:[271:19] Found type:[271:11->274:3]
+Ptyp_constr SomeLocal:[271:11->274:3]
+Completable: Cpath Type[SomeLocal]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[SomeLocal]
+Path SomeLocal
+[{
+    "label": "SomeLocalModule",
+    "kind": 9,
+    "tags": [],
+    "detail": "module SomeLocalModule",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 275:15
+posCursor:[275:15] posNoWhite:[275:14] Found expr:[274:11->278:1]
+posCursor:[275:15] posNoWhite:[275:14] Found expr:[274:11->278:1]
+posCursor:[275:15] posNoWhite:[275:14] Found expr:[275:5->277:3]
+posCursor:[275:15] posNoWhite:[275:14] Found expr:[275:13->275:15]
+Pexp_ident _w:[275:13->275:15]
+Completable: Cpath Value[_w]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[_w]
+Path _w
+[{
+    "label": "_world",
+    "kind": 12,
+    "tags": [],
+    "detail": "'a",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 281:22
+posCursor:[281:22] posNoWhite:[281:21] Found type:[281:21->281:22]
+Ptyp_constr s:[281:21->281:22]
+Completable: Cpath Type[s]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[s]
+Path s
+[{
+    "label": "someType",
+    "kind": 22,
+    "tags": [],
+    "detail": "type someType",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someType = {hello: string}\n```"}
+  }, {
+    "label": "someLocalVariant",
+    "kind": 22,
+    "tags": [],
+    "detail": "type someLocalVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someLocalVariant = SomeLocalVariantItem\n```"}
+  }]
+
+Complete src/Completion.res 291:30
+posCursor:[291:30] posNoWhite:[291:29] Found expr:[291:11->291:32]
+Pexp_apply ...[291:11->291:28] ()
+Completable: CnamedArg(Value[funRecord].someFun, "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[funRecord].someFun
+ContextPath Value[funRecord]
+Path funRecord
+Found type for function (~name: string) => unit
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 296:11
+posCursor:[296:11] posNoWhite:[296:10] Found expr:[296:3->296:11]
+Pexp_field [296:3->296:10] _:[299:0->296:11]
+Completable: Cpath Value[retAA](Nolabel).""
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[retAA](Nolabel).""
+ContextPath Value[retAA](Nolabel)
+ContextPath Value[retAA]
+Path retAA
+[{
+    "label": "x",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype aa = {x: int, name: string}\n```"}
+  }, {
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype aa = {x: int, name: string}\n```"}
+  }]
+
+Complete src/Completion.res 301:13
+posCursor:[301:13] posNoWhite:[301:12] Found expr:[301:3->301:13]
+Pexp_apply ...[301:3->301:11] ()
+Completable: CnamedArg(Value[ff](~c), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~c)
+ContextPath Value[ff]
+Path ff
+Found type for function (
+  ~opt1: int=?,
+  ~a: int,
+  ~b: int,
+  unit,
+  ~opt2: int=?,
+  unit,
+) => int
+[{
+    "label": "opt1",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }, {
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "opt2",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 304:15
+posCursor:[304:15] posNoWhite:[304:14] Found expr:[304:3->304:15]
+Pexp_apply ...[304:3->304:13] ()
+Completable: CnamedArg(Value[ff](~c)(Nolabel), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~c)(Nolabel)
+ContextPath Value[ff](~c)
+ContextPath Value[ff]
+Path ff
+Found type for function (~a: int, ~b: int, ~opt2: int=?, unit) => int
+[{
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "opt2",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 307:17
+posCursor:[307:17] posNoWhite:[307:16] Found expr:[307:3->307:17]
+Pexp_apply ...[307:3->307:15] ()
+Completable: CnamedArg(Value[ff](~c, Nolabel), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~c, Nolabel)
+ContextPath Value[ff]
+Path ff
+Found type for function (~a: int, ~b: int, ~opt2: int=?, unit) => int
+[{
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "opt2",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 310:21
+posCursor:[310:21] posNoWhite:[310:20] Found expr:[310:3->310:21]
+Pexp_apply ...[310:3->310:19] ()
+Completable: CnamedArg(Value[ff](~c, Nolabel, Nolabel), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~c, Nolabel, Nolabel)
+ContextPath Value[ff]
+Path ff
+Found type for function (~a: int, ~b: int) => int
+[{
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 313:23
+posCursor:[313:23] posNoWhite:[313:22] Found expr:[313:3->313:23]
+Pexp_apply ...[313:3->313:21] ()
+Completable: CnamedArg(Value[ff](~c, Nolabel, ~b), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~c, Nolabel, ~b)
+ContextPath Value[ff]
+Path ff
+Found type for function (~a: int, ~opt2: int=?, unit) => int
+[{
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "opt2",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 316:16
+posCursor:[316:16] posNoWhite:[316:15] Found expr:[316:3->316:16]
+Pexp_apply ...[316:3->316:14] ()
+Completable: CnamedArg(Value[ff](~opt2), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff](~opt2)
+ContextPath Value[ff]
+Path ff
+Found type for function (~opt1: int=?, ~a: int, ~b: int, unit, unit, ~c: int) => int
+[{
+    "label": "opt1",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }, {
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "c",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 326:17
+posCursor:[326:17] posNoWhite:[326:16] Found expr:[326:3->326:17]
+Pexp_apply ...[326:3->326:15] ()
+Completable: CnamedArg(Value[withCallback], "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[withCallback]
+Path withCallback
+Found type for function (~b: int) => callback
+[{
+    "label": "b",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 329:21
+posCursor:[329:21] posNoWhite:[329:20] Found expr:[329:3->329:21]
+Pexp_apply ...[329:3->329:19] ()
+Completable: CnamedArg(Value[withCallback](~a), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[withCallback](~a)
+ContextPath Value[withCallback]
+Path withCallback
+Found type for function int
+[]
+
+Complete src/Completion.res 332:21
+posCursor:[332:21] posNoWhite:[332:20] Found expr:[332:3->332:21]
+Pexp_apply ...[332:3->332:19] ()
+Completable: CnamedArg(Value[withCallback](~b), "", [])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[withCallback](~b)
+ContextPath Value[withCallback]
+Path withCallback
+Found type for function (~a: int) => int
+[{
+    "label": "a",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 339:26
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[336:3->349:23]
+JSX <div:[336:3->336:6] onClick[337:4->337:11]=...[337:13->349:23]> _children:None
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[337:13->349:23]
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[337:13->341:6]
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[337:13->341:6]
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[338:6->341:5]
+posCursor:[339:26] posNoWhite:[339:25] Found expr:[339:16->341:5]
+posCursor:[339:26] posNoWhite:[339:25] Found pattern:[339:20->341:5]
+posCursor:[339:26] posNoWhite:[339:25] Found type:[339:23->341:5]
+Ptyp_constr Res:[339:23->341:5]
+posCursor:[339:26] posNoWhite:[339:25] Found pattern:[339:20->341:5]
+posCursor:[339:26] posNoWhite:[339:25] Found type:[339:23->341:5]
+Ptyp_constr Res:[339:23->341:5]
+Completable: Cpath Type[Res]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[Res]
+Path Res
+[{
+    "label": "RescriptReactErrorBoundary",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptReactErrorBoundary",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptReactErrorBoundary",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "RescriptReactRouter",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptReactRouter",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptReactRouter",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "RescriptTools",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptTools",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptTools",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "RescriptTools_Docgen",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptTools_Docgen",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptTools_Docgen",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "Result",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Result",
+    "documentation": null,
+    "data": {
+      "modulePath": "Result",
+      "filePath": "src/Completion.res"
+    }
+  }]
+
+Complete src/Completion.res 346:57
+posCursor:[346:57] posNoWhite:[346:56] Found expr:[346:53->349:23]
+posCursor:[346:57] posNoWhite:[346:56] Found expr:[346:53->346:57]
+Pexp_ident this:[346:53->346:57]
+Completable: Cpath Value[this]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[this]
+Path this
+[{
+    "label": "thisIsNotSaved",
+    "kind": 12,
+    "tags": [],
+    "detail": "\\\"Type Not Known\"",
+    "documentation": null
+  }]
+
+Hover src/Completion.res 349:14
+{"contents": {"kind": "markdown", "value": "```rescript\nJsxDOM.domProps\n```\n\n---\n\n```\n \n```\n```rescript\ntype JsxDOM.domProps = {\n  key?: string,\n  children?: Jsx.element,\n  ref?: domRef,\n  ariaCurrent?: [\n    | #date\n    | #\"false\"\n    | #location\n    | #page\n    | #step\n    | #time\n    | #\"true\"\n  ],\n  ariaDetails?: string,\n  ariaDisabled?: bool,\n  ariaHidden?: bool,\n  ariaInvalid?: [#\"false\" | #grammar | #spelling | #\"true\"],\n  ariaKeyshortcuts?: string,\n  ariaLabel?: string,\n  ariaRoledescription?: string,\n  ariaAutocomplete?: [#both | #inline | #list | #none],\n  ariaChecked?: [#\"false\" | #mixed | #\"true\"],\n  ariaExpanded?: bool,\n  ariaHaspopup?: [\n    | #dialog\n    | #\"false\"\n    | #grid\n    | #listbox\n    | #menu\n    | #tree\n    | #\"true\"\n  ],\n  ariaLevel?: int,\n  ariaModal?: bool,\n  ariaMultiline?: bool,\n  ariaMultiselectable?: bool,\n  ariaOrientation?: [#horizontal | #undefined | #vertical],\n  ariaPlaceholder?: string,\n  ariaPressed?: [#\"false\" | #mixed | #\"true\"],\n  ariaReadonly?: bool,\n  ariaRequired?: bool,\n  ariaSelected?: bool,\n  ariaSort?: string,\n  ariaValuemax?: float,\n  ariaValuemin?: float,\n  ariaValuenow?: float,\n  ariaValuetext?: string,\n  ariaAtomic?: bool,\n  ariaBusy?: bool,\n  ariaLive?: [#assertive | #off | #polite | #rude],\n  ariaRelevant?: string,\n  ariaDropeffect?: [\n    | #copy\n    | #execute\n    | #link\n    | #move\n    | #none\n    | #popup\n  ],\n  ariaGrabbed?: bool,\n  ariaActivedescendant?: string,\n  ariaColcount?: int,\n  ariaColindex?: int,\n  ariaColspan?: int,\n  ariaControls?: string,\n  ariaDescribedby?: string,\n  ariaErrormessage?: string,\n  ariaFlowto?: string,\n  ariaLabelledby?: string,\n  ariaOwns?: string,\n  ariaPosinset?: int,\n  ariaRowcount?: int,\n  ariaRowindex?: int,\n  ariaRowspan?: int,\n  ariaSetsize?: int,\n  defaultChecked?: bool,\n  defaultValue?: string,\n  accessKey?: string,\n  capture?: [#environment | #user],\n  className?: string,\n  contentEditable?: bool,\n  contextMenu?: string,\n  dataTestId?: string,\n  dir?: string,\n  draggable?: bool,\n  hidden?: bool,\n  id?: string,\n  lang?: string,\n  role?: string,\n  style?: style,\n  spellCheck?: bool,\n  tabIndex?: int,\n  title?: string,\n  itemID?: string,\n  itemProp?: string,\n  itemRef?: string,\n  itemScope?: bool,\n  itemType?: string,\n  accept?: string,\n  acceptCharset?: string,\n  action?: string,\n  allowFullScreen?: bool,\n  alt?: string,\n  as_?: string,\n  async?: bool,\n  autoComplete?: string,\n  autoCapitalize?: string,\n  autoFocus?: bool,\n  autoPlay?: bool,\n  challenge?: string,\n  charSet?: string,\n  checked?: bool,\n  cite?: string,\n  crossOrigin?: string,\n  cols?: int,\n  colSpan?: int,\n  content?: string,\n  controls?: bool,\n  coords?: string,\n  data?: string,\n  dateTime?: string,\n  default?: bool,\n  defer?: bool,\n  disabled?: bool,\n  download?: string,\n  encType?: string,\n  form?: string,\n  formAction?: string,\n  formTarget?: string,\n  formMethod?: string,\n  headers?: string,\n  height?: string,\n  high?: int,\n  href?: string,\n  hrefLang?: string,\n  htmlFor?: string,\n  httpEquiv?: string,\n  icon?: string,\n  inputMode?: string,\n  integrity?: string,\n  keyType?: string,\n  kind?: string,\n  label?: string,\n  list?: string,\n  loading?: [#eager | #lazy],\n  loop?: bool,\n  low?: int,\n  manifest?: string,\n  max?: string,\n  maxLength?: int,\n  media?: string,\n  mediaGroup?: string,\n  method?: string,\n  min?: string,\n  minLength?: int,\n  multiple?: bool,\n  muted?: bool,\n  name?: string,\n  nonce?: string,\n  noValidate?: bool,\n  open_?: bool,\n  optimum?: int,\n  pattern?: string,\n  placeholder?: string,\n  playsInline?: bool,\n  poster?: string,\n  preload?: string,\n  radioGroup?: string,\n  readOnly?: bool,\n  rel?: string,\n  required?: bool,\n  reversed?: bool,\n  rows?: int,\n  rowSpan?: int,\n  sandbox?: string,\n  scope?: string,\n  scoped?: bool,\n  scrolling?: string,\n  selected?: bool,\n  shape?: string,\n  size?: int,\n  sizes?: string,\n  span?: int,\n  src?: string,\n  srcDoc?: string,\n  srcLang?: string,\n  srcSet?: string,\n  start?: int,\n  step?: float,\n  summary?: string,\n  target?: string,\n  type_?: string,\n  useMap?: string,\n  value?: string,\n  width?: string,\n  wrap?: string,\n  onCopy?: JsxEvent.Clipboard.t => unit,\n  onCut?: JsxEvent.Clipboard.t => unit,\n  onPaste?: JsxEvent.Clipboard.t => unit,\n  onCompositionEnd?: JsxEvent.Composition.t => unit,\n  onCompositionStart?: JsxEvent.Composition.t => unit,\n  onCompositionUpdate?: JsxEvent.Composition.t => unit,\n  onKeyDown?: JsxEvent.Keyboard.t => unit,\n  onKeyPress?: JsxEvent.Keyboard.t => unit,\n  onKeyUp?: JsxEvent.Keyboard.t => unit,\n  onFocus?: JsxEvent.Focus.t => unit,\n  onBlur?: JsxEvent.Focus.t => unit,\n  onBeforeInput?: JsxEvent.Form.t => unit,\n  onChange?: JsxEvent.Form.t => unit,\n  onInput?: JsxEvent.Form.t => unit,\n  onReset?: JsxEvent.Form.t => unit,\n  onSubmit?: JsxEvent.Form.t => unit,\n  onInvalid?: JsxEvent.Form.t => unit,\n  onClick?: JsxEvent.Mouse.t => unit,\n  onContextMenu?: JsxEvent.Mouse.t => unit,\n  onDoubleClick?: JsxEvent.Mouse.t => unit,\n  onDrag?: JsxEvent.Mouse.t => unit,\n  onDragEnd?: JsxEvent.Mouse.t => unit,\n  onDragEnter?: JsxEvent.Mouse.t => unit,\n  onDragExit?: JsxEvent.Mouse.t => unit,\n  onDragLeave?: JsxEvent.Mouse.t => unit,\n  onDragOver?: JsxEvent.Mouse.t => unit,\n  onDragStart?: JsxEvent.Mouse.t => unit,\n  onDrop?: JsxEvent.Mouse.t => unit,\n  onMouseDown?: JsxEvent.Mouse.t => unit,\n  onMouseEnter?: JsxEvent.Mouse.t => unit,\n  onMouseLeave?: JsxEvent.Mouse.t => unit,\n  onMouseMove?: JsxEvent.Mouse.t => unit,\n  onMouseOut?: JsxEvent.Mouse.t => unit,\n  onMouseOver?: JsxEvent.Mouse.t => unit,\n  onMouseUp?: JsxEvent.Mouse.t => unit,\n  onSelect?: JsxEvent.Selection.t => unit,\n  onTouchCancel?: JsxEvent.Touch.t => unit,\n  onTouchEnd?: JsxEvent.Touch.t => unit,\n  onTouchMove?: JsxEvent.Touch.t => unit,\n  onTouchStart?: JsxEvent.Touch.t => unit,\n  onPointerOver?: JsxEvent.Pointer.t => unit,\n  onPointerEnter?: JsxEvent.Pointer.t => unit,\n  onPointerDown?: JsxEvent.Pointer.t => unit,\n  onPointerMove?: JsxEvent.Pointer.t => unit,\n  onPointerUp?: JsxEvent.Pointer.t => unit,\n  onPointerCancel?: JsxEvent.Pointer.t => unit,\n  onPointerOut?: JsxEvent.Pointer.t => unit,\n  onPointerLeave?: JsxEvent.Pointer.t => unit,\n  onGotPointerCapture?: JsxEvent.Pointer.t => unit,\n  onLostPointerCapture?: JsxEvent.Pointer.t => unit,\n  onScroll?: JsxEvent.UI.t => unit,\n  onWheel?: JsxEvent.Wheel.t => unit,\n  onAbort?: JsxEvent.Media.t => unit,\n  onCanPlay?: JsxEvent.Media.t => unit,\n  onCanPlayThrough?: JsxEvent.Media.t => unit,\n  onDurationChange?: JsxEvent.Media.t => unit,\n  onEmptied?: JsxEvent.Media.t => unit,\n  onEncrypted?: JsxEvent.Media.t => unit,\n  onEnded?: JsxEvent.Media.t => unit,\n  onError?: JsxEvent.Media.t => unit,\n  onLoadedData?: JsxEvent.Media.t => unit,\n  onLoadedMetadata?: JsxEvent.Media.t => unit,\n  onLoadStart?: JsxEvent.Media.t => unit,\n  onPause?: JsxEvent.Media.t => unit,\n  onPlay?: JsxEvent.Media.t => unit,\n  onPlaying?: JsxEvent.Media.t => unit,\n  onProgress?: JsxEvent.Media.t => unit,\n  onRateChange?: JsxEvent.Media.t => unit,\n  onSeeked?: JsxEvent.Media.t => unit,\n  onSeeking?: JsxEvent.Media.t => unit,\n  onStalled?: JsxEvent.Media.t => unit,\n  onSuspend?: JsxEvent.Media.t => unit,\n  onTimeUpdate?: JsxEvent.Media.t => unit,\n  onVolumeChange?: JsxEvent.Media.t => unit,\n  onWaiting?: JsxEvent.Media.t => unit,\n  onLoad?: JsxEvent.Image.t => unit,\n  onAnimationStart?: JsxEvent.Animation.t => unit,\n  onAnimationEnd?: JsxEvent.Animation.t => unit,\n  onAnimationIteration?: JsxEvent.Animation.t => unit,\n  onTransitionEnd?: JsxEvent.Transition.t => unit,\n  accentHeight?: string,\n  accumulate?: string,\n  additive?: string,\n  alignmentBaseline?: string,\n  allowReorder?: string,\n  alphabetic?: string,\n  amplitude?: string,\n  arabicForm?: string,\n  ascent?: string,\n  attributeName?: string,\n  attributeType?: string,\n  autoReverse?: string,\n  azimuth?: string,\n  baseFrequency?: string,\n  baseProfile?: string,\n  baselineShift?: string,\n  bbox?: string,\n  begin?: string,\n  begin_?: string,\n  bias?: string,\n  by?: string,\n  calcMode?: string,\n  capHeight?: string,\n  clip?: string,\n  clipPath?: string,\n  clipPathUnits?: string,\n  clipRule?: string,\n  colorInterpolation?: string,\n  colorInterpolationFilters?: string,\n  colorProfile?: string,\n  colorRendering?: string,\n  contentScriptType?: string,\n  contentStyleType?: string,\n  cursor?: string,\n  cx?: string,\n  cy?: string,\n  d?: string,\n  decelerate?: string,\n  descent?: string,\n  diffuseConstant?: string,\n  direction?: string,\n  display?: string,\n  divisor?: string,\n  dominantBaseline?: string,\n  dur?: string,\n  dx?: string,\n  dy?: string,\n  edgeMode?: string,\n  elevation?: string,\n  enableBackground?: string,\n  end?: string,\n  end_?: string,\n  exponent?: string,\n  externalResourcesRequired?: string,\n  fill?: string,\n  fillOpacity?: string,\n  fillRule?: string,\n  filter?: string,\n  filterRes?: string,\n  filterUnits?: string,\n  floodColor?: string,\n  floodOpacity?: string,\n  focusable?: string,\n  fontFamily?: string,\n  fontSize?: string,\n  fontSizeAdjust?: string,\n  fontStretch?: string,\n  fontStyle?: string,\n  fontVariant?: string,\n  fontWeight?: string,\n  fomat?: string,\n  from?: string,\n  fx?: string,\n  fy?: string,\n  g1?: string,\n  g2?: string,\n  glyphName?: string,\n  glyphOrientationHorizontal?: string,\n  glyphOrientationVertical?: string,\n  glyphRef?: string,\n  gradientTransform?: string,\n  gradientUnits?: string,\n  hanging?: string,\n  horizAdvX?: string,\n  horizOriginX?: string,\n  ideographic?: string,\n  imageRendering?: string,\n  in_?: string,\n  in2?: string,\n  intercept?: string,\n  k?: string,\n  k1?: string,\n  k2?: string,\n  k3?: string,\n  k4?: string,\n  kernelMatrix?: string,\n  kernelUnitLength?: string,\n  kerning?: string,\n  keyPoints?: string,\n  keySplines?: string,\n  keyTimes?: string,\n  lengthAdjust?: string,\n  letterSpacing?: string,\n  lightingColor?: string,\n  limitingConeAngle?: string,\n  local?: string,\n  markerEnd?: string,\n  markerHeight?: string,\n  markerMid?: string,\n  markerStart?: string,\n  markerUnits?: string,\n  markerWidth?: string,\n  mask?: string,\n  maskContentUnits?: string,\n  maskUnits?: string,\n  mathematical?: string,\n  mode?: string,\n  numOctaves?: string,\n  offset?: string,\n  opacity?: string,\n  operator?: string,\n  order?: string,\n  orient?: string,\n  orientation?: string,\n  origin?: string,\n  overflow?: string,\n  overflowX?: string,\n  overflowY?: string,\n  overlinePosition?: string,\n  overlineThickness?: string,\n  paintOrder?: string,\n  panose1?: string,\n  pathLength?: string,\n  patternContentUnits?: string,\n  patternTransform?: string,\n  patternUnits?: string,\n  pointerEvents?: string,\n  points?: string,\n  pointsAtX?: string,\n  pointsAtY?: string,\n  pointsAtZ?: string,\n  preserveAlpha?: string,\n  preserveAspectRatio?: string,\n  primitiveUnits?: string,\n  r?: string,\n  radius?: string,\n  refX?: string,\n  refY?: string,\n  renderingIntent?: string,\n  repeatCount?: string,\n  repeatDur?: string,\n  requiredExtensions?: string,\n  requiredFeatures?: string,\n  restart?: string,\n  result?: string,\n  rotate?: string,\n  rx?: string,\n  ry?: string,\n  scale?: string,\n  seed?: string,\n  shapeRendering?: string,\n  slope?: string,\n  spacing?: string,\n  specularConstant?: string,\n  specularExponent?: string,\n  speed?: string,\n  spreadMethod?: string,\n  startOffset?: string,\n  stdDeviation?: string,\n  stemh?: string,\n  stemv?: string,\n  stitchTiles?: string,\n  stopColor?: string,\n  stopOpacity?: string,\n  strikethroughPosition?: string,\n  strikethroughThickness?: string,\n  string?: string,\n  stroke?: string,\n  strokeDasharray?: string,\n  strokeDashoffset?: string,\n  strokeLinecap?: string,\n  strokeLinejoin?: string,\n  strokeMiterlimit?: string,\n  strokeOpacity?: string,\n  strokeWidth?: string,\n  surfaceScale?: string,\n  systemLanguage?: string,\n  tableValues?: string,\n  targetX?: string,\n  targetY?: string,\n  textAnchor?: string,\n  textDecoration?: string,\n  textLength?: string,\n  textRendering?: string,\n  to?: string,\n  to_?: string,\n  transform?: string,\n  u1?: string,\n  u2?: string,\n  underlinePosition?: string,\n  underlineThickness?: string,\n  unicode?: string,\n  unicodeBidi?: string,\n  unicodeRange?: string,\n  unitsPerEm?: string,\n  vAlphabetic?: string,\n  vHanging?: string,\n  vIdeographic?: string,\n  vMathematical?: string,\n  values?: string,\n  vectorEffect?: string,\n  version?: string,\n  vertAdvX?: string,\n  vertAdvY?: string,\n  vertOriginX?: string,\n  vertOriginY?: string,\n  viewBox?: string,\n  viewTarget?: string,\n  visibility?: string,\n  widths?: string,\n  wordSpacing?: string,\n  writingMode?: string,\n  x?: string,\n  x1?: string,\n  x2?: string,\n  xChannelSelector?: string,\n  xHeight?: string,\n  xlinkActuate?: string,\n  xlinkArcrole?: string,\n  xlinkHref?: string,\n  xlinkRole?: string,\n  xlinkShow?: string,\n  xlinkTitle?: string,\n  xlinkType?: string,\n  xmlns?: string,\n  xmlnsXlink?: string,\n  xmlBase?: string,\n  xmlLang?: string,\n  xmlSpace?: string,\n  y?: string,\n  y1?: string,\n  y2?: string,\n  yChannelSelector?: string,\n  z?: string,\n  zoomAndPan?: string,\n  about?: string,\n  datatype?: string,\n  inlist?: string,\n  prefix?: string,\n  property?: string,\n  resource?: string,\n  typeof?: string,\n  vocab?: string,\n  dangerouslySetInnerHTML?: {\"__html\": string},\n  suppressContentEditableWarning?: bool,\n}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxDOM.res%22%2C30%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx.res%22%2C24%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype domRef\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxDOM.res%22%2C25%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype style = JsxDOMStyle.t\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxDOM.res%22%2C24%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Clipboard.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C95%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Composition.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C107%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Keyboard.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C118%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Focus.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C142%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Form.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C154%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Mouse.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C163%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Selection.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C244%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Touch.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C253%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Pointer.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C194%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.UI.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C278%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Wheel.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C291%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Media.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C305%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Image.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C314%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Animation.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C323%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype JsxEvent.Transition.t = synthetic<tag>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxEvent.res%22%2C336%2C2%5D)\n"}}
+
+Hover src/Completion.res 352:17
+Nothing at that position. Now trying to use completion.
+posCursor:[352:17] posNoWhite:[352:16] Found expr:[352:11->352:35]
+Pexp_send age[352:30->352:33] e:[352:11->352:28]
+posCursor:[352:17] posNoWhite:[352:16] Found expr:[352:11->352:28]
+Pexp_ident FAO.forAutoObject:[352:11->352:28]
+Completable: Cpath Value[FAO, forAutoObject]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[FAO, forAutoObject]
+Path FAO.forAutoObject
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+{"contents": {"kind": "markdown", "value": "```rescript\n{\"age\": int, \"forAutoLabel\": FAR.forAutoRecord}\n```"}}
+
+Hover src/Completion.res 355:17
+Nothing at that position. Now trying to use completion.
+posCursor:[355:17] posNoWhite:[355:16] Found expr:[355:11->355:22]
+Pexp_apply ...[355:11->355:13] (~opt1355:15->355:19=...[355:20->355:21])
+Completable: CnamedArg(Value[ff], opt1, [opt1])
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ff]
+Path ff
+Found type for function (
+  ~opt1: int=?,
+  ~a: int,
+  ~b: int,
+  unit,
+  ~opt2: int=?,
+  unit,
+  ~c: int,
+) => int
+{"contents": {"kind": "markdown", "value": "```rescript\noption<int>\n```"}}
+
+Complete src/Completion.res 358:23
+posCursor:[358:23] posNoWhite:[358:22] Found expr:[0:-1->358:23]
+posCursor:[358:23] posNoWhite:[358:22] Found expr:[358:12->358:23]
+[]
+
+Complete src/Completion.res 365:8
+posCursor:[365:8] posNoWhite:[365:7] Found expr:[363:8->368:3]
+posCursor:[365:8] posNoWhite:[365:7] Found expr:[363:8->368:3]
+posCursor:[365:8] posNoWhite:[365:7] Found pattern:[365:7->367:5]
+posCursor:[365:8] posNoWhite:[365:7] Found pattern:[365:7->365:8]
+Ppat_construct T:[365:7->365:8]
+Completable: Cpattern Value[x]=T
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[x]
+Path x
+Completable: Cpath Value[T]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[T]
+Path T
+[{
+    "label": "That",
+    "kind": 4,
+    "tags": [],
+    "detail": "That",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThat\n```\n\n```rescript\ntype v = This | That\n```"}
+  }, {
+    "label": "This",
+    "kind": 4,
+    "tags": [],
+    "detail": "This",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThis\n```\n\n```rescript\ntype v = This | That\n```"}
+  }, {
+    "label": "TableclothMap",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TableclothMap",
+    "documentation": null,
+    "data": {
+      "modulePath": "TableclothMap",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "Type",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Type",
+    "documentation": null,
+    "data": {
+      "modulePath": "Type",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "TypeAtPosCompletion",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypeAtPosCompletion",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypeAtPosCompletion",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "TypeDefinition",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypeDefinition",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypeDefinition",
+      "filePath": "src/Completion.res"
+    }
+  }, {
+    "label": "TypedArray",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypedArray",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypedArray",
+      "filePath": "src/Completion.res"
+    }
+  }]
+
+Complete src/Completion.res 376:21
+posCursor:[376:21] posNoWhite:[376:20] Found expr:[374:8->379:3]
+posCursor:[376:21] posNoWhite:[376:20] Found expr:[374:8->379:3]
+posCursor:[376:21] posNoWhite:[376:20] Found pattern:[376:7->378:5]
+posCursor:[376:21] posNoWhite:[376:20] Found pattern:[376:7->376:21]
+Ppat_construct AndThatOther.T:[376:7->376:21]
+Completable: Cpath Value[AndThatOther, T]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[AndThatOther, T]
+Path AndThatOther.T
+[{
+    "label": "ThatOther",
+    "kind": 4,
+    "tags": [],
+    "detail": "ThatOther",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThatOther\n```\n\n```rescript\ntype v = And | ThatOther\n```"}
+  }]
+
+Complete src/Completion.res 381:24
+posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:12->381:26]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:24], ...[381:24->381:25])
+posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:12->381:24]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:14], ...[381:16->381:24])
+posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:16->381:24]
+Pexp_ident ForAuto.:[381:16->381:24]
+Completable: Cpath Value[ForAuto, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ForAuto, ""]
+Path ForAuto.
+[{
+    "label": "abc",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "abd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 384:38
+posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:12->384:41]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:12->384:39], ...[384:39->384:40])
+posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:12->384:39]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:12->384:17], ...[384:19->384:39])
+posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:19->384:39]
+Pexp_send [384:38->384:38] e:[384:19->384:36]
+Completable: Cpath Value[FAO, forAutoObject][""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[FAO, forAutoObject][""]
+ContextPath Value[FAO, forAutoObject]
+Path FAO.forAutoObject
+[{
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "forAutoLabel",
+    "kind": 4,
+    "tags": [],
+    "detail": "FAR.forAutoRecord",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 387:24
+posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:11->387:26]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[387:11->387:24], ...[387:24->387:25])
+posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:11->387:24]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[387:11->387:12], ...[387:14->387:24])
+posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:14->387:24]
+Pexp_field [387:14->387:23] _:[387:24->387:24]
+Completable: Cpath Value[funRecord].""
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[funRecord].""
+ContextPath Value[funRecord]
+Path funRecord
+[{
+    "label": "someFun",
+    "kind": 5,
+    "tags": [],
+    "detail": "(~name: string) => unit",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeFun: (~name: string) => unit\n```\n\n```rescript\ntype funRecord = {\n  someFun: (~name: string) => unit,\n  stuff: string,\n}\n```"}
+  }, {
+    "label": "stuff",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nstuff: string\n```\n\n```rescript\ntype funRecord = {\n  someFun: (~name: string) => unit,\n  stuff: string,\n}\n```"}
+  }]
+
+Complete src/Completion.res 392:12
+posCursor:[392:12] posNoWhite:[392:11] Found expr:[390:8->395:1]
+posCursor:[392:12] posNoWhite:[392:11] Found expr:[390:8->395:1]
+posCursor:[392:12] posNoWhite:[392:11] Found expr:[391:2->394:4]
+posCursor:[392:12] posNoWhite:[392:11] Found expr:[392:6->394:4]
+posCursor:[392:12] posNoWhite:[392:11] Found expr:[392:6->392:12]
+Completable: Cpath array->ma
+Raw opens: 3 Js.place holder ... Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 3 Completion Completion Js
+ContextPath array->ma
+ContextPath array
+CPPipe env:Completion
+Path Js.Array2.ma
+[{
+    "label": "Array2.mapi",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, ('a, int) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}
+  }, {
+    "label": "Array2.map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}
+  }]
+
+Complete src/Completion.res 400:14
+posCursor:[400:14] posNoWhite:[400:13] Found expr:[399:14->400:20]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[399:14->400:16], ...[400:16->400:19])
+posCursor:[400:14] posNoWhite:[400:13] Found expr:[399:14->400:16]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[399:14->400:11], ...[400:13->400:16])
+posCursor:[400:14] posNoWhite:[400:13] Found expr:[400:13->400:16]
+Pexp_ident red:[400:13->400:16]
+Completable: Cpath Value[red]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[red]
+Path red
+[{
+    "label": "red",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 405:25
+posCursor:[405:25] posNoWhite:[405:24] Found expr:[403:14->405:31]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[403:14->405:27], ...[405:27->405:30])
+posCursor:[405:25] posNoWhite:[405:24] Found expr:[403:14->405:27]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[403:14->405:22], ...[405:24->405:27])
+posCursor:[405:25] posNoWhite:[405:24] Found expr:[405:24->405:27]
+Pexp_ident red:[405:24->405:27]
+Completable: Cpath Value[red]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[red]
+Path red
+[{
+    "label": "red",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 408:22
+posCursor:[408:22] posNoWhite:[408:21] Found expr:[408:11->469:0]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[408:11->426:17], ...[431:0->469:0])
+posCursor:[408:22] posNoWhite:[408:21] Found expr:[408:11->426:17]
+Pexp_apply ...__ghost__[0:-1->0:-1] (...[408:11->408:19], ...[408:21->426:17])
+posCursor:[408:22] posNoWhite:[408:21] Found expr:[408:21->426:17]
+posCursor:[408:22] posNoWhite:[408:21] Found expr:[408:21->408:22]
+Pexp_ident r:[408:21->408:22]
+Completable: Cpath Value[r]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[r]
+Path r
+[{
+    "label": "red",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }, {
+    "label": "retAA",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => aa",
+    "documentation": null
+  }, {
+    "label": "r",
+    "kind": 12,
+    "tags": [],
+    "detail": "rAlias",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype r = {x: int, y: string}\n```"}
+  }]
+
+Complete src/Completion.res 412:21
+posCursor:[412:21] posNoWhite:[412:20] Found expr:[411:14->418:1]
+posCursor:[412:21] posNoWhite:[412:20] Found expr:[411:14->418:1]
+posCursor:[412:21] posNoWhite:[412:20] Found expr:[412:5->417:17]
+posCursor:[412:21] posNoWhite:[412:20] Found expr:[412:5->414:42]
+posCursor:[412:21] posNoWhite:[412:20] Found expr:[412:5->414:5]
+Pexp_ident SomeLocalModule.:[412:5->414:5]
+Completable: Cpath Value[SomeLocalModule, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[SomeLocalModule, ""]
+Path SomeLocalModule.
+[{
+    "label": "bb",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "aa",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 415:21
+posCursor:[415:21] posNoWhite:[415:20] Found expr:[411:14->418:1]
+posCursor:[415:21] posNoWhite:[415:20] Found expr:[411:14->418:1]
+posCursor:[415:21] posNoWhite:[415:20] Found expr:[414:2->417:17]
+posCursor:[415:21] posNoWhite:[415:20] Found expr:[415:5->417:17]
+Pexp_apply ...[415:5->417:8] (...[417:9->417:16])
+posCursor:[415:21] posNoWhite:[415:20] Found expr:[415:5->417:8]
+Pexp_ident SomeLocalModule.:[415:5->417:8]
+Completable: Cpath Value[SomeLocalModule, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[SomeLocalModule, ""]
+Path SomeLocalModule.
+[{
+    "label": "bb",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "aa",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Completion.res 420:17
+posCursor:[420:17] posNoWhite:[420:16] Found expr:[420:11->420:17]
+Completable: Cpath int->t
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath int->t
+ContextPath int
+CPPipe env:Completion
+Path Belt.Int.t
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => float",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"}
+  }]
+
+Complete src/Completion.res 423:19
+posCursor:[423:19] posNoWhite:[423:18] Found expr:[423:11->423:19]
+Completable: Cpath float->t
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath float->t
+ContextPath float
+CPPipe env:Completion
+Path Belt.Float.t
+[{
+    "label": "Belt.Float.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => int",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Float.toInt(1.0) === 1) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Float.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Float.toString(1.0) === \"1.0\") /* true */\n```\n"}
+  }]
+
+Complete src/Completion.res 428:8
+posCursor:[428:8] posNoWhite:[428:7] Found expr:[428:3->428:8]
+Completable: Cpath Value[ok]->g
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[ok]->g
+ContextPath Value[ok]
+Path ok
+CPPipe env:Completion
+Path Belt.Result.g
+[{
+    "label": "Belt.Result.getExn",
+    "kind": 12,
+    "tags": [],
+    "detail": "t<'a, 'b> => 'a",
+    "documentation": {"kind": "markdown", "value": "\n`getExn(res)`: when `res` is `Ok(n)`, returns `n` when `res` is `Error(m)`, raise an exception\n\n## Examples\n\n```rescript\nBelt.Result.getExn(Belt.Result.Ok(42)) == 42\n\nBelt.Result.getExn(Belt.Result.Error(\"Invalid data\")) /* raises exception */\n```\n"}
+  }, {
+    "label": "Belt.Result.getWithDefault",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a, 'b>, 'a) => 'a",
+    "documentation": {"kind": "markdown", "value": "\n`getWithDefault(res, defaultValue)`: If `res` is `Ok(n)`, returns `n`,\notherwise `default`\n\n## Examples\n\n```rescript\nBelt.Result.getWithDefault(Ok(42), 0) == 42\n\nBelt.Result.getWithDefault(Error(\"Invalid Data\"), 0) == 0\n```\n"}
+  }]
+
+Complete src/Completion.res 446:15
+posCursor:[446:15] posNoWhite:[446:14] Found expr:[446:3->446:15]
+Pexp_field [446:3->446:12] so:[446:13->446:15]
+Completable: Cpath Value[rWithDepr].so
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[rWithDepr].so
+ContextPath Value[rWithDepr]
+Path rWithDepr
+[{
+    "label": "someInt",
+    "kind": 5,
+    "tags": [1],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "Deprecated: \n\n```rescript\nsomeInt: int\n```\n\n```rescript\ntype someRecordWithDeprecatedField = {\n  name: string,\n  someInt: int,\n  someFloat: float,\n}\n```"}
+  }, {
+    "label": "someFloat",
+    "kind": 5,
+    "tags": [1],
+    "detail": "float",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use 'someInt'.\n\n```rescript\nsomeFloat: float\n```\n\n```rescript\ntype someRecordWithDeprecatedField = {\n  name: string,\n  someInt: int,\n  someFloat: float,\n}\n```"}
+  }]
+
+Complete src/Completion.res 453:37
+XXX Not found!
+Completable: Cexpression Type[someVariantWithDeprecated]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[someVariantWithDeprecated]
+Path someVariantWithDeprecated
+[{
+    "label": "DoNotUseMe",
+    "kind": 4,
+    "tags": [1],
+    "detail": "DoNotUseMe",
+    "documentation": {"kind": "markdown", "value": "Deprecated: \n\n```rescript\nDoNotUseMe\n```\n\n```rescript\ntype someVariantWithDeprecated =\n  | DoNotUseMe\n  | UseMeInstead\n  | AndNotMe\n```"},
+    "insertText": "DoNotUseMe",
+    "insertTextFormat": 2
+  }, {
+    "label": "UseMeInstead",
+    "kind": 4,
+    "tags": [],
+    "detail": "UseMeInstead",
+    "documentation": {"kind": "markdown", "value": "```rescript\nUseMeInstead\n```\n\n```rescript\ntype someVariantWithDeprecated =\n  | DoNotUseMe\n  | UseMeInstead\n  | AndNotMe\n```"},
+    "insertText": "UseMeInstead",
+    "insertTextFormat": 2
+  }, {
+    "label": "AndNotMe",
+    "kind": 4,
+    "tags": [1],
+    "detail": "AndNotMe",
+    "documentation": {"kind": "markdown", "value": "Deprecated: Use 'UseMeInstead'\n\n```rescript\nAndNotMe\n```\n\n```rescript\ntype someVariantWithDeprecated =\n  | DoNotUseMe\n  | UseMeInstead\n  | AndNotMe\n```"},
+    "insertText": "AndNotMe",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Completion.res 458:30
+posCursor:[458:30] posNoWhite:[458:29] Found expr:[458:11->458:30]
+Completable: Cpath Value[uncurried](Nolabel)->toS
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Value[uncurried](Nolabel)->toS
+ContextPath Value[uncurried](Nolabel)
+ContextPath Value[uncurried]
+Path uncurried
+CPPipe env:Completion
+Path Belt.Int.toS
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }]
+
+Complete src/Completion.res 463:30
+XXX Not found!
+Completable: Cexpression Type[withUncurried]->recordField(fn)
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath Type[withUncurried]
+Path withUncurried
+[{
+    "label": "v => v",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:v} => ${0:v}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Completion.res 466:26
+posCursor:[466:26] posNoWhite:[466:25] Found expr:[466:22->466:26]
+Pexp_ident FAR.:[466:22->466:26]
+Completable: Cpath ValueOrField[FAR, ""]
+Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 2 Completion Completion
+ContextPath ValueOrField[FAR, ""]
+Path FAR.
+[{
+    "label": "forAutoRecord",
+    "kind": 12,
+    "tags": [],
+    "detail": "forAutoRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype forAutoRecord = {\n  forAuto: ForAuto.t,\n  something: option<int>,\n}\n```"}
+  }, {
+    "label": "forAuto",
+    "kind": 5,
+    "tags": [],
+    "detail": "ForAuto.t",
+    "documentation": {"kind": "markdown", "value": "```rescript\nforAuto: ForAuto.t\n```\n\n```rescript\ntype forAutoRecord = {\n  forAuto: ForAuto.t,\n  something: option<int>,\n}\n```"}
+  }, {
+    "label": "something",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomething: option<int>\n```\n\n```rescript\ntype forAutoRecord = {\n  forAuto: ForAuto.t,\n  something: option<int>,\n}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt
new file mode 100644
index 0000000000..11921fa2a4
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt
@@ -0,0 +1,241 @@
+Complete src/CompletionAttributes.res 0:8
+Attribute id:modu:[0:3->0:8] label:modu
+Completable: Cdecorator(modu)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "module",
+    "kind": 4,
+    "tags": [],
+    "detail": "",
+    "documentation": {"kind": "markdown", "value": "The `@module` decorator is used to bind to a JavaScript module.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#module-decorator)."},
+    "insertText": "module(\"$0\")",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionAttributes.res 3:12
+XXX Not found!
+Completable: CdecoratorPayload(module=)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "@rescript/react",
+    "kind": 4,
+    "tags": [],
+    "detail": "Package",
+    "documentation": null
+  }, {
+    "label": "./test.json",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }, {
+    "label": "./tst.js",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }]
+
+Complete src/CompletionAttributes.res 6:7
+Attribute id:js:[6:3->6:7] label:@js
+Completable: Cdecorator(@js)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "jsxConfig",
+    "kind": 4,
+    "tags": [],
+    "detail": "",
+    "documentation": {"kind": "markdown", "value": "The `@@jsxConfig` decorator is used to change the config for JSX on the fly.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/docs/manual/latest/jsx#file-level-configuration)."},
+    "insertText": "jsxConfig({$0})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionAttributes.res 9:16
+XXX Not found!
+Completable: JsxConfig
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "version",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nversion?: int\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }, {
+    "label": "module_",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nmodule_?: string\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }, {
+    "label": "mode",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nmode?: string\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }]
+
+Complete src/CompletionAttributes.res 12:17
+XXX Not found!
+Completable: JsxConfig
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "module_",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nmodule_?: string\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }, {
+    "label": "mode",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nmode?: string\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }]
+
+Complete src/CompletionAttributes.res 15:25
+XXX Not found!
+Completable: JsxConfig
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "\"$0\"",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionAttributes.res 18:29
+XXX Not found!
+Completable: JsxConfig
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "version",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nversion?: int\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }, {
+    "label": "mode",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nmode?: string\n```\n\n```rescript\ntype jsxConfig = {version: int, module_: string, mode: string}\n```"}
+  }]
+
+Complete src/CompletionAttributes.res 21:12
+XXX Not found!
+Completable: CdecoratorPayload(moduleWithImportAttributes)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "from",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfrom?: string\n```\n\n```rescript\ntype moduleConfig = {from: string, with: string}\n```"}
+  }, {
+    "label": "with",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nwith?: string\n```\n\n```rescript\ntype moduleConfig = {from: string, with: string}\n```"}
+  }]
+
+Complete src/CompletionAttributes.res 24:17
+XXX Not found!
+Completable: CdecoratorPayload(moduleWithImportAttributes)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "importAttributesConfig",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype importAttributesConfig = {type_: string}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionAttributes.res 27:19
+XXX Not found!
+Completable: CdecoratorPayload(moduleWithImportAttributes)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "type_",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype_?: string\n```\n\n```rescript\ntype importAttributesConfig = {type_: string}\n```"}
+  }]
+
+Complete src/CompletionAttributes.res 30:19
+XXX Not found!
+Completable: CdecoratorPayload(module=)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "@rescript/react",
+    "kind": 4,
+    "tags": [],
+    "detail": "Package",
+    "documentation": null
+  }, {
+    "label": "./test.json",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }, {
+    "label": "./tst.js",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }]
+
+Complete src/CompletionAttributes.res 33:17
+XXX Not found!
+Completable: CdecoratorPayload(module=)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "@rescript/react",
+    "kind": 4,
+    "tags": [],
+    "detail": "Package",
+    "documentation": null
+  }, {
+    "label": "./test.json",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }, {
+    "label": "./tst.js",
+    "kind": 4,
+    "tags": [],
+    "detail": "Local file",
+    "documentation": null
+  }]
+
+Complete src/CompletionAttributes.res 36:14
+posCursor:[36:14] posNoWhite:[36:13] Found expr:[36:12->36:14]
+Completable: CextensionNode(t)
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "todo",
+    "kind": 4,
+    "tags": [],
+    "detail": "`%todo` is used to tell the compiler that some code still needs to be implemented.",
+    "documentation": null,
+    "insertText": "todo"
+  }, {
+    "label": "todo (with payload)",
+    "kind": 4,
+    "tags": [],
+    "detail": "`%todo` is used to tell the compiler that some code still needs to be implemented. With a payload.",
+    "documentation": null,
+    "insertText": "todo(\"${0:TODO}\")",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionDicts.res.txt b/tests/analysis_tests/tests/src/expected/CompletionDicts.res.txt
new file mode 100644
index 0000000000..9ae52f7398
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionDicts.res.txt
@@ -0,0 +1,70 @@
+Complete src/CompletionDicts.res 0:33
+posCursor:[0:33] posNoWhite:[0:32] Found expr:[0:14->0:35]
+Pexp_apply ...[0:14->0:31] (...[0:32->0:34])
+Completable: Cexpression CArgument Value[Js, Dict, fromArray]($0)->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Js, Dict, fromArray]($0)
+ContextPath Value[Js, Dict, fromArray]
+Path Js.Dict.fromArray
+[{
+    "label": "(_, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(key, 'a)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionDicts.res 3:34
+posCursor:[3:34] posNoWhite:[3:33] Found expr:[3:14->3:37]
+Pexp_apply ...[3:14->3:31] (...[3:32->3:36])
+Completable: Cexpression CArgument Value[Js, Dict, fromArray]($0)->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Js, Dict, fromArray]($0)
+ContextPath Value[Js, Dict, fromArray]
+Path Js.Dict.fromArray
+[{
+    "label": "(_, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(key, 'a)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionDicts.res 6:40
+posCursor:[6:40] posNoWhite:[6:39] Found expr:[6:14->6:44]
+Pexp_apply ...[6:14->6:31] (...[6:32->6:43])
+Completable: Cexpression CArgument Value[Js, Dict, fromArray]($0)->array, tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Js, Dict, fromArray]($0)
+ContextPath Value[Js, Dict, fromArray]
+Path Js.Dict.fromArray
+[]
+
+
+Complete src/CompletionDicts.res 12:14
+posCursor:[12:14] posNoWhite:[12:13] Found expr:[10:11->14:2]
+Pexp_apply ...[10:11->10:28] (...[10:29->14:1])
+Completable: Cexpression CArgument Value[Js, Dict, fromArray]($0)->array, tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Js, Dict, fromArray]($0)
+ContextPath Value[Js, Dict, fromArray]
+Path Js.Dict.fromArray
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt b/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt
new file mode 100644
index 0000000000..d450629eb1
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt
@@ -0,0 +1,1427 @@
+Complete src/CompletionExpressions.res 3:20
+XXX Not found!
+Completable: Cpattern CTuple(Value[s], Value[f])
+Package opens Pervasives.JsxModules.place holder
+ContextPath CTuple(Value[s], Value[f])
+ContextPath Value[s]
+Path s
+ContextPath Value[f]
+Path f
+[{
+    "label": "(_, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(bool, option<array<bool>>)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 26:27
+posCursor:[26:27] posNoWhite:[26:26] Found expr:[26:11->26:29]
+Pexp_apply ...[26:11->26:25] (...[26:26->26:28])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "offline",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\noffline: bool\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline: option<bool>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "variant",
+    "kind": 5,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\nvariant: someVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "polyvariant",
+    "kind": 5,
+    "tags": [],
+    "detail": "somePolyVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\npolyvariant: somePolyVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<otherRecord>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: option<otherRecord>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 29:28
+posCursor:[29:28] posNoWhite:[29:27] Found expr:[29:11->29:30]
+Pexp_apply ...[29:11->29:25] (...[29:27->29:28])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)=n->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<otherRecord>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: option<otherRecord>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 32:35
+posCursor:[32:35] posNoWhite:[32:34] Found expr:[32:11->32:38]
+Pexp_apply ...[32:11->32:25] (...[32:26->32:38])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(offline)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 35:36
+posCursor:[35:36] posNoWhite:[35:35] Found expr:[35:11->35:39]
+Pexp_apply ...[35:11->35:25] (...[35:26->35:38])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "offline",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\noffline: bool\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline: option<bool>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "variant",
+    "kind": 5,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\nvariant: someVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "polyvariant",
+    "kind": 5,
+    "tags": [],
+    "detail": "somePolyVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\npolyvariant: somePolyVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<otherRecord>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: option<otherRecord>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 38:37
+posCursor:[38:37] posNoWhite:[38:35] Found expr:[38:11->38:53]
+Pexp_apply ...[38:11->38:25] (...[38:26->38:52])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline: option<bool>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "variant",
+    "kind": 5,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\nvariant: someVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "polyvariant",
+    "kind": 5,
+    "tags": [],
+    "detail": "somePolyVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\npolyvariant: somePolyVariant\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }, {
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<otherRecord>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: option<otherRecord>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 41:44
+posCursor:[41:44] posNoWhite:[41:43] Found expr:[41:11->41:47]
+Pexp_apply ...[41:11->41:25] (...[41:26->41:47])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "Some(nested)",
+    "kind": 12,
+    "tags": [],
+    "detail": "otherRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"},
+    "insertText": "Some(nested)$0",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "otherRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"},
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "otherRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"}
+  }, {
+    "label": "Some({})",
+    "kind": 12,
+    "tags": [],
+    "detail": "otherRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"},
+    "insertText": "Some({$0})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 44:46
+posCursor:[44:46] posNoWhite:[44:45] Found expr:[44:11->44:49]
+Pexp_apply ...[44:11->44:25] (...[44:26->44:48])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[]
+
+Complete src/CompletionExpressions.res 47:51
+posCursor:[47:51] posNoWhite:[47:50] Found expr:[47:11->47:55]
+Pexp_apply ...[47:11->47:25] (...[47:26->47:54])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(nested), variantPayload::Some($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "someField",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeField: int\n```\n\n```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"}
+  }, {
+    "label": "otherField",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\notherField: string\n```\n\n```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 50:45
+posCursor:[50:45] posNoWhite:[50:44] Found expr:[50:11->50:48]
+Pexp_apply ...[50:11->50:25] (...[50:26->50:48])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(variant)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Two",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(int, string)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(int, string)\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 53:47
+posCursor:[53:47] posNoWhite:[53:46] Found expr:[53:11->53:50]
+Pexp_apply ...[53:11->53:25] (...[53:26->53:49])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)=O->recordField(variant)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 56:57
+posCursor:[56:57] posNoWhite:[56:56] Found expr:[56:11->56:61]
+Pexp_apply ...[56:11->56:25] (...[56:26->56:60])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(polyvariant), polyvariantPayload::three($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>, variant: someVariant, polyvariant: somePolyVariant, nested: option<otherRecord>}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 59:60
+posCursor:[59:60] posNoWhite:[59:59] Found expr:[59:11->59:65]
+Pexp_apply ...[59:11->59:25] (...[59:26->59:64])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordField(polyvariant), polyvariantPayload::three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 62:62
+posCursor:[62:62] posNoWhite:[62:61] Found expr:[62:11->62:66]
+Pexp_apply ...[62:11->62:25] (...[62:26->62:65])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)=t->recordField(polyvariant), polyvariantPayload::three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 69:25
+posCursor:[69:25] posNoWhite:[69:24] Found expr:[69:11->69:26]
+Pexp_apply ...[69:11->69:24] (...[69:25->69:26])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 72:26
+posCursor:[72:26] posNoWhite:[72:25] Found expr:[72:11->72:28]
+Pexp_apply ...[72:11->72:24] (...[72:25->72:27])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 75:26
+posCursor:[75:26] posNoWhite:[75:25] Found expr:[75:11->75:27]
+Pexp_apply ...[75:11->75:24] (...[75:25->75:26])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)=s
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "s",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 78:31
+posCursor:[78:31] posNoWhite:[78:30] Found expr:[78:11->78:34]
+Pexp_apply ...[78:11->78:24] (...[78:25->78:33])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)->array, variantPayload::Some($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 81:31
+posCursor:[81:31] posNoWhite:[81:30] Found expr:[81:11->81:34]
+Pexp_apply ...[81:11->81:24] (...[81:25->81:33])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 84:31
+posCursor:[84:31] posNoWhite:[84:30] Found expr:[84:11->84:40]
+Pexp_apply ...[84:11->84:24] (...[84:25->84:39])
+Completable: Cexpression CArgument Value[fnTakingArray]($0)->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingArray]($0)
+ContextPath Value[fnTakingArray]
+Path fnTakingArray
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 89:38
+posCursor:[89:38] posNoWhite:[89:37] Found expr:[89:11->89:41]
+Pexp_apply ...[89:11->89:25] (...[89:26->89:40])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)=so
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "someBoolVar",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 96:43
+posCursor:[96:43] posNoWhite:[96:42] Found expr:[96:11->96:46]
+Pexp_apply ...[96:11->96:30] (...[96:31->96:46])
+Completable: Cexpression CArgument Value[fnTakingOtherRecord]($0)->recordField(otherField)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingOtherRecord]($0)
+ContextPath Value[fnTakingOtherRecord]
+Path fnTakingOtherRecord
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "\"$0\"",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 108:57
+posCursor:[108:57] posNoWhite:[108:56] Found expr:[108:11->108:60]
+Pexp_apply ...[108:11->108:42] (...[108:43->108:60])
+Completable: Cexpression CArgument Value[fnTakingRecordWithOptionalField]($0)->recordField(someOptField)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecordWithOptionalField]($0)
+ContextPath Value[fnTakingRecordWithOptionalField]
+Path fnTakingRecordWithOptionalField
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 116:53
+posCursor:[116:53] posNoWhite:[116:52] Found expr:[116:11->116:56]
+Pexp_apply ...[116:11->116:39] (...[116:40->116:56])
+Completable: Cexpression CArgument Value[fnTakingRecordWithOptVariant]($0)->recordField(someVariant)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecordWithOptVariant]($0)
+ContextPath Value[fnTakingRecordWithOptVariant]
+Path fnTakingRecordWithOptVariant
+[{
+    "label": "Some(someVariant)",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some(someVariant)$0",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(int, string)\n```"}
+  }, {
+    "label": "Some(One)",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some(One)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(Two)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some(Two)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(Three(_, _))",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(int, string)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(int, string)\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some(Three(${1:_}, ${2:_}))",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 126:49
+posCursor:[126:49] posNoWhite:[126:48] Found expr:[126:11->126:51]
+Pexp_apply ...[126:11->126:31] (...[126:32->126:50])
+Completable: Cexpression CArgument Value[fnTakingInlineRecord]($0)->variantPayload::WithInlineRecord($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingInlineRecord]($0)
+ContextPath Value[fnTakingInlineRecord]
+Path fnTakingInlineRecord
+[{
+    "label": "{}",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 129:50
+posCursor:[129:50] posNoWhite:[129:49] Found expr:[129:11->129:53]
+Pexp_apply ...[129:11->129:31] (...[129:32->129:52])
+Completable: Cexpression CArgument Value[fnTakingInlineRecord]($0)->variantPayload::WithInlineRecord($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingInlineRecord]($0)
+ContextPath Value[fnTakingInlineRecord]
+Path fnTakingInlineRecord
+[{
+    "label": "someBoolField",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null
+  }, {
+    "label": "otherField",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null
+  }, {
+    "label": "nestedRecord",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 132:51
+posCursor:[132:51] posNoWhite:[132:50] Found expr:[132:11->132:54]
+Pexp_apply ...[132:11->132:31] (...[132:32->132:53])
+Completable: Cexpression CArgument Value[fnTakingInlineRecord]($0)=s->variantPayload::WithInlineRecord($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingInlineRecord]($0)
+ContextPath Value[fnTakingInlineRecord]
+Path fnTakingInlineRecord
+[{
+    "label": "someBoolField",
+    "kind": 4,
+    "tags": [],
+    "detail": "Inline record",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 135:63
+posCursor:[135:63] posNoWhite:[135:62] Found expr:[135:11->135:67]
+Pexp_apply ...[135:11->135:31] (...[135:32->135:66])
+Completable: Cexpression CArgument Value[fnTakingInlineRecord]($0)->variantPayload::WithInlineRecord($0), recordField(nestedRecord)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingInlineRecord]($0)
+ContextPath Value[fnTakingInlineRecord]
+Path fnTakingInlineRecord
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "otherRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 138:65
+posCursor:[138:65] posNoWhite:[138:64] Found expr:[138:11->138:70]
+Pexp_apply ...[138:11->138:31] (...[138:32->138:69])
+Completable: Cexpression CArgument Value[fnTakingInlineRecord]($0)->variantPayload::WithInlineRecord($0), recordField(nestedRecord), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingInlineRecord]($0)
+ContextPath Value[fnTakingInlineRecord]
+Path fnTakingInlineRecord
+[{
+    "label": "someField",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeField: int\n```\n\n```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"}
+  }, {
+    "label": "otherField",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\notherField: string\n```\n\n```rescript\ntype otherRecord = {someField: int, otherField: string}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 159:20
+posCursor:[159:20] posNoWhite:[159:19] Found expr:[159:3->159:21]
+Pexp_apply ...[159:3->159:19] (...[159:20->159:21])
+Completable: Cexpression CArgument Value[fnTakingCallback]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($0)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "() => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "() => ${0:()}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 162:21
+posCursor:[162:21] posNoWhite:[162:20] Found expr:[162:3->162:22]
+Pexp_apply ...[162:3->162:19] (...[162:20->162:21])
+Completable: Cexpression CArgument Value[fnTakingCallback]($0)=a
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($0)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[]
+
+Complete src/CompletionExpressions.res 165:22
+posCursor:[165:22] posNoWhite:[165:21] Found expr:[165:3->165:24]
+Pexp_apply ...[165:3->165:19] (...[165:20->165:21])
+Completable: Cexpression CArgument Value[fnTakingCallback]($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($1)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "v => v",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:v} => ${0:v}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 168:25
+posCursor:[168:25] posNoWhite:[168:24] Found expr:[168:3->168:27]
+Pexp_apply ...[168:3->168:19] (...[168:20->168:21], ...[168:23->168:24])
+Completable: Cexpression CArgument Value[fnTakingCallback]($2)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($2)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "event => event",
+    "kind": 12,
+    "tags": [],
+    "detail": "ReactEvent.Mouse.t => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:event} => ${0:event}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 171:29
+posCursor:[171:29] posNoWhite:[171:27] Found expr:[171:3->171:30]
+Pexp_apply ...[171:3->171:19] (...[171:20->171:21], ...[171:23->171:24], ...[171:26->171:27])
+Completable: Cexpression CArgument Value[fnTakingCallback]($3)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($3)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "(~on, ~off=?, variant) => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~on: bool, ~off: bool=?, variant) => int",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "(~on, ~off=?, ${1:variant}) => {${0:()}}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 174:32
+posCursor:[174:32] posNoWhite:[174:30] Found expr:[174:3->174:33]
+Pexp_apply ...[174:3->174:19] (...[174:20->174:21], ...[174:23->174:24], ...[174:26->174:27], ...[174:29->174:30])
+Completable: Cexpression CArgument Value[fnTakingCallback]($4)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($4)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "(v1, v2, v3) => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "(bool, option<bool>, bool) => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "(${1:v1}, ${2:v2}, ${3:v3}) => {${0:()}}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 177:34
+posCursor:[177:34] posNoWhite:[177:33] Found expr:[177:3->177:36]
+Pexp_apply ...[177:3->177:19] (...[177:20->177:21], ...[177:23->177:24], ...[177:26->177:27], ...[177:29->177:30], ...[177:32->177:33])
+Completable: Cexpression CArgument Value[fnTakingCallback]($5)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingCallback]($5)
+ContextPath Value[fnTakingCallback]
+Path fnTakingCallback
+[{
+    "label": "(~on=?, ~off=?, ()) => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~on: bool=?, ~off: bool=?, unit) => int",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "(~on=?, ~off=?, ()) => {${0:()}}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 185:10
+posCursor:[185:10] posNoWhite:[185:9] Found expr:[181:2->185:11]
+posCursor:[185:10] posNoWhite:[185:9] Found expr:[182:2->185:11]
+posCursor:[185:10] posNoWhite:[185:9] Found expr:[183:2->185:11]
+posCursor:[185:10] posNoWhite:[185:9] Found expr:[184:2->185:11]
+posCursor:[185:10] posNoWhite:[185:9] Found expr:[185:2->185:11]
+Pexp_apply ...[185:2->185:8] (...[185:9->185:10])
+Completable: Cexpression CArgument Value[Js, log]($0)=s
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Js, log]($0)
+ContextPath Value[Js, log]
+Path Js.log
+[{
+    "label": "second2",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }, {
+    "label": "second",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "someBoolVar",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "s",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionExpressions.res 196:14
+posCursor:[196:14] posNoWhite:[196:13] Found expr:[196:3->196:14]
+Pexp_field [196:3->196:6] someOpt:[196:7->196:14]
+Completable: Cpath Value[fff].someOpt
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fff].someOpt
+ContextPath Value[fff]
+Path fff
+[{
+    "label": "someOptField",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeOptField?: bool\n```\n\n```rescript\ntype recordWithOptionalField = {\n  someField: int,\n  someOptField?: bool,\n}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 205:11
+posCursor:[205:11] posNoWhite:[205:10] Found expr:[205:3->205:12]
+Pexp_apply ...[205:3->205:10] (...[205:11->205:12])
+Completable: Cexpression CArgument Value[takesCb]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesCb]($0)
+ContextPath Value[takesCb]
+Path takesCb
+[{
+    "label": "someTyp => someTyp",
+    "kind": 12,
+    "tags": [],
+    "detail": "someTyp => 'a",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:someTyp} => ${0:someTyp}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 216:12
+posCursor:[216:12] posNoWhite:[216:11] Found expr:[216:3->216:13]
+Pexp_apply ...[216:3->216:11] (...[216:12->216:13])
+Completable: Cexpression CArgument Value[takesCb2]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesCb2]($0)
+ContextPath Value[takesCb2]
+Path takesCb2
+[{
+    "label": "environment => environment",
+    "kind": 12,
+    "tags": [],
+    "detail": "Environment.t => 'a",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:environment} => ${0:environment}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 225:12
+posCursor:[225:12] posNoWhite:[225:11] Found expr:[225:3->225:13]
+Pexp_apply ...[225:3->225:11] (...[225:12->225:13])
+Completable: Cexpression CArgument Value[takesCb3]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesCb3]($0)
+ContextPath Value[takesCb3]
+Path takesCb3
+[{
+    "label": "apiCallResult => apiCallResult",
+    "kind": 12,
+    "tags": [],
+    "detail": "apiCallResult => 'a",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:apiCallResult} => ${0:apiCallResult}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 232:12
+posCursor:[232:12] posNoWhite:[232:11] Found expr:[232:3->232:13]
+Pexp_apply ...[232:3->232:11] (...[232:12->232:13])
+Completable: Cexpression CArgument Value[takesCb4]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesCb4]($0)
+ContextPath Value[takesCb4]
+Path takesCb4
+[{
+    "label": "apiCallResult => apiCallResult",
+    "kind": 12,
+    "tags": [],
+    "detail": "option<apiCallResult> => 'a",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:apiCallResult} => ${0:apiCallResult}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 239:12
+posCursor:[239:12] posNoWhite:[239:11] Found expr:[239:3->239:13]
+Pexp_apply ...[239:3->239:11] (...[239:12->239:13])
+Completable: Cexpression CArgument Value[takesCb5]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesCb5]($0)
+ContextPath Value[takesCb5]
+Path takesCb5
+[{
+    "label": "apiCallResults => apiCallResults",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<option<apiCallResult>> => 'a",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:apiCallResults} => ${0:apiCallResults}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 250:30
+posCursor:[250:30] posNoWhite:[250:29] Found expr:[250:3->250:31]
+Pexp_apply ...[250:3->250:20] (~updater250:22->250:29=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[commitLocalUpdate](~updater)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[commitLocalUpdate](~updater)
+ContextPath Value[commitLocalUpdate]
+Path commitLocalUpdate
+[{
+    "label": "recordSourceSelectorProxy => recordSourceSelectorProxy",
+    "kind": 12,
+    "tags": [],
+    "detail": "RecordSourceSelectorProxy.t => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:recordSourceSelectorProxy} => ${0:recordSourceSelectorProxy}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 257:25
+posCursor:[257:25] posNoWhite:[257:24] Found expr:[257:3->257:26]
+Pexp_apply ...[257:3->257:24] (...[257:25->257:26])
+Completable: Cexpression CArgument Value[fnTakingAsyncCallback]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingAsyncCallback]($0)
+ContextPath Value[fnTakingAsyncCallback]
+Path fnTakingAsyncCallback
+[{
+    "label": "async () => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => promise<unit>",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "async () => ${0:()}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 262:23
+posCursor:[262:23] posNoWhite:[262:22] Found expr:[262:3->262:24]
+Completable: Cexpression CArgument Value[Belt, Array, map]($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Belt, Array, map]($1)
+ContextPath Value[Belt, Array, map]
+Path Belt.Array.map
+[{
+    "label": "v => v",
+    "kind": 12,
+    "tags": [],
+    "detail": "'a => 'b",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "${1:v} => ${0:v}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 271:15
+posCursor:[271:15] posNoWhite:[271:14] Found expr:[271:3->271:16]
+Pexp_apply ...[271:3->271:14] (...[271:15->271:16])
+Completable: Cexpression CArgument Value[takesExotic]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[takesExotic]($0)
+ContextPath Value[takesExotic]
+Path takesExotic
+[{
+    "label": "#\"some exotic\"",
+    "kind": 4,
+    "tags": [],
+    "detail": "#\"some exotic\"",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#\"some exotic\"\n```\n\n```rescript\n[#\"some exotic\"]\n```"},
+    "insertText": "#\"some exotic\"",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 278:23
+posCursor:[278:23] posNoWhite:[278:22] Found expr:[278:3->278:24]
+Pexp_apply ...[278:3->278:22] (...[278:23->278:24])
+Completable: Cexpression CArgument Value[fnTakingPolyVariant]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingPolyVariant]($0)
+ContextPath Value[fnTakingPolyVariant]
+Path fnTakingPolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(someRecord, bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two(bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 281:24
+posCursor:[281:24] posNoWhite:[281:23] Found expr:[281:3->290:18]
+Pexp_apply ...[281:3->281:22] (...[281:23->281:25], ...[290:0->290:16])
+Completable: Cexpression CArgument Value[fnTakingPolyVariant]($0)=#
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingPolyVariant]($0)
+ContextPath Value[fnTakingPolyVariant]
+Path fnTakingPolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(someRecord, bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two(bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 284:25
+posCursor:[284:25] posNoWhite:[284:24] Found expr:[284:3->284:26]
+Pexp_apply ...[284:3->284:22] (...[284:23->284:25])
+Completable: Cexpression CArgument Value[fnTakingPolyVariant]($0)=#o
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingPolyVariant]($0)
+ContextPath Value[fnTakingPolyVariant]
+Path fnTakingPolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "one",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 287:24
+posCursor:[287:24] posNoWhite:[287:23] Found expr:[287:3->287:25]
+Pexp_apply ...[287:3->287:22] (...[287:23->287:24])
+Completable: Cexpression CArgument Value[fnTakingPolyVariant]($0)=o
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingPolyVariant]($0)
+ContextPath Value[fnTakingPolyVariant]
+Path fnTakingPolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 306:41
+XXX Not found!
+Completable: Cexpression Type[withIntLocal]->recordField(superInt)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[withIntLocal]
+Path withIntLocal
+[{
+    "label": "SuperInt.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null,
+    "insertText": "SuperInt.make($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 309:36
+posCursor:[309:36] posNoWhite:[309:35] Found expr:[309:3->309:37]
+Pexp_apply ...[309:3->309:35] (...[309:36->309:37])
+Completable: Cexpression CArgument Value[CompletionSupport, makeTestHidden]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[CompletionSupport, makeTestHidden]($0)
+ContextPath Value[CompletionSupport, makeTestHidden]
+Path CompletionSupport.makeTestHidden
+[{
+    "label": "CompletionSupport.TestHidden.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null,
+    "insertText": "CompletionSupport.TestHidden.make($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 313:36
+posCursor:[313:36] posNoWhite:[313:35] Found expr:[313:3->313:37]
+Pexp_apply ...[313:3->313:35] (...[313:36->313:37])
+Completable: Cexpression CArgument Value[CompletionSupport, makeTestHidden]($0)
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[CompletionSupport, makeTestHidden]($0)
+ContextPath Value[CompletionSupport, makeTestHidden]
+Path CompletionSupport.makeTestHidden
+[{
+    "label": "TestHidden.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null,
+    "insertText": "TestHidden.make($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 321:11
+posCursor:[321:11] posNoWhite:[321:10] Found expr:[321:3->321:12]
+Pexp_apply ...[321:3->321:10] (...[321:11->321:12])
+Completable: Cexpression CArgument Value[mkStuff]($0)
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[mkStuff]($0)
+ContextPath Value[mkStuff]
+Path mkStuff
+[{
+    "label": "%re()",
+    "kind": 4,
+    "tags": [],
+    "detail": "Regular expression",
+    "documentation": null,
+    "insertText": "%re(\"/$0/g\")",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Re.fromString()",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => t",
+    "documentation": null,
+    "insertText": "Js.Re.fromString($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Re.fromStringWithFlags()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(string, ~flags: string) => t",
+    "documentation": null,
+    "insertText": "Js.Re.fromStringWithFlags($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 352:24
+posCursor:[352:24] posNoWhite:[352:23] Found expr:[352:3->352:25]
+Pexp_apply ...[352:3->352:23] (...[352:24->352:25])
+Completable: Cexpression CArgument Value[tArgCompletionTestFn]($0)
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[tArgCompletionTestFn]($0)
+ContextPath Value[tArgCompletionTestFn]
+Path tArgCompletionTestFn
+[{
+    "label": "Money.fromInt()",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null,
+    "insertText": "Money.fromInt($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Money.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => t",
+    "documentation": null,
+    "insertText": "Money.make($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Money.zero",
+    "kind": 12,
+    "tags": [],
+    "detail": "t",
+    "documentation": null,
+    "insertText": "Money.zero",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 357:37
+posCursor:[357:37] posNoWhite:[357:36] Found expr:[357:3->357:38]
+Pexp_apply ...[357:3->357:30] (~tVal357:32->357:36=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[labeledTArgCompletionTestFn](~tVal)
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[labeledTArgCompletionTestFn](~tVal)
+ContextPath Value[labeledTArgCompletionTestFn]
+Path labeledTArgCompletionTestFn
+[{
+    "label": "Money.fromInt()",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null,
+    "insertText": "Money.fromInt($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Money.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => t",
+    "documentation": null,
+    "insertText": "Money.make($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Money.zero",
+    "kind": 12,
+    "tags": [],
+    "detail": "t",
+    "documentation": null,
+    "insertText": "Money.zero",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionExpressions.res 362:18
+posCursor:[362:18] posNoWhite:[362:17] Found expr:[362:3->362:32]
+posCursor:[362:18] posNoWhite:[362:17] Found expr:[362:10->362:18]
+Pexp_field [362:10->362:17] _:[362:19->362:18]
+Completable: Cpath Value[someTyp].""
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath Value[someTyp].""
+ContextPath Value[someTyp]
+Path someTyp
+[{
+    "label": "test",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntest: bool\n```\n\n```rescript\ntype someTyp = {test: bool}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 380:22
+posCursor:[380:22] posNoWhite:[380:18] Found expr:[380:13->386:2]
+Pexp_apply ...[380:13->380:17] (...[380:18->386:1])
+Completable: Cexpression CArgument Value[hook]($0)->recordBody
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[hook]($0)
+ContextPath Value[hook]
+Path hook
+[{
+    "label": "operator",
+    "kind": 5,
+    "tags": [],
+    "detail": "[#\"and\" | #or]",
+    "documentation": {"kind": "markdown", "value": "```rescript\noperator?: [#\"and\" | #or]\n```\n\n```rescript\ntype config = {includeName: bool, operator: option<[#\"and\" | #or]>, showMore: bool}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 382:8
+posCursor:[382:8] posNoWhite:[382:7] Found expr:[380:13->386:2]
+Pexp_apply ...[380:13->380:17] (...[380:18->386:1])
+Completable: Cexpression CArgument Value[hook]($0)=ope->recordBody
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath CArgument Value[hook]($0)
+ContextPath Value[hook]
+Path hook
+[{
+    "label": "operator",
+    "kind": 5,
+    "tags": [],
+    "detail": "[#\"and\" | #or]",
+    "documentation": {"kind": "markdown", "value": "```rescript\noperator?: [#\"and\" | #or]\n```\n\n```rescript\ntype config = {includeName: bool, operator: option<[#\"and\" | #or]>, showMore: bool}\n```"}
+  }]
+
+Complete src/CompletionExpressions.res 388:18
+posCursor:[388:18] posNoWhite:[388:17] Found expr:[388:3->388:24]
+posCursor:[388:18] posNoWhite:[388:17] Found expr:[388:10->388:18]
+Pexp_field [388:10->388:17] _:[388:19->388:18]
+Completable: Cpath Value[someTyp].""
+Raw opens: 1 CompletionSupport.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 CompletionSupport
+ContextPath Value[someTyp].""
+ContextPath Value[someTyp]
+Path someTyp
+[{
+    "label": "test",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntest: bool\n```\n\n```rescript\ntype someTyp = {test: bool}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt
new file mode 100644
index 0000000000..92e0ecdb04
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt
@@ -0,0 +1,495 @@
+Complete src/CompletionFunctionArguments.res 10:24
+posCursor:[10:24] posNoWhite:[10:23] Found expr:[10:11->10:25]
+Pexp_apply ...[10:11->10:17] (~isOn10:19->10:23=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[someFn](~isOn)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFn](~isOn)
+ContextPath Value[someFn]
+Path someFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 13:25
+posCursor:[13:25] posNoWhite:[13:24] Found expr:[13:11->13:26]
+Pexp_apply ...[13:11->13:17] (~isOn13:19->13:23=...[13:24->13:25])
+Completable: Cexpression CArgument Value[someFn](~isOn)=t
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFn](~isOn)
+ContextPath Value[someFn]
+Path someFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "sortText": "A true"
+  }, {
+    "label": "tLocalVar",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 16:25
+posCursor:[16:25] posNoWhite:[16:24] Found expr:[16:11->16:26]
+Pexp_apply ...[16:11->16:17] (~isOff16:19->16:24=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[someFn](~isOff)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFn](~isOff)
+ContextPath Value[someFn]
+Path someFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 21:27
+posCursor:[21:27] posNoWhite:[21:26] Found expr:[19:21->25:1]
+Pexp_apply ...[19:21->19:27] (~isOn20:3->20:7=...[21:7->23:8])
+posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->23:8]
+posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->21:28]
+posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:14->21:28]
+Pexp_apply ...[21:14->21:20] (~isOn21:22->21:26=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[someFn](~isOn)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFn](~isOn)
+ContextPath Value[someFn]
+Path someFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 34:24
+posCursor:[34:24] posNoWhite:[34:23] Found expr:[34:11->34:25]
+Pexp_apply ...[34:11->34:22] (...[34:23->34:24])
+Completable: Cexpression CArgument Value[someOtherFn]($0)=f
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someOtherFn]($0)
+ContextPath Value[someOtherFn]
+Path someOtherFn
+[{
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 51:39
+posCursor:[51:39] posNoWhite:[51:38] Found expr:[51:11->51:40]
+Pexp_apply ...[51:11->51:30] (~config51:32->51:38=...__ghost__[0:-1->0:-1])
+Completable: Cexpression CArgument Value[someFnTakingVariant](~config)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFnTakingVariant](~config)
+ContextPath Value[someFnTakingVariant]
+Path someFnTakingVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Two",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(int, string)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(int, string)\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionFunctionArguments.res 54:40
+posCursor:[54:40] posNoWhite:[54:39] Found expr:[54:11->54:41]
+Pexp_apply ...[54:11->54:30] (~config54:32->54:38=...[54:39->54:40])
+Completable: Cexpression CArgument Value[someFnTakingVariant](~config)=O
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFnTakingVariant](~config)
+ContextPath Value[someFnTakingVariant]
+Path someFnTakingVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "sortText": "A One",
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "OIncludeMeInCompletions",
+    "kind": 9,
+    "tags": [],
+    "detail": "module OIncludeMeInCompletions",
+    "documentation": null
+  }, {
+    "label": "Obj",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Obj",
+    "documentation": null,
+    "data": {
+      "modulePath": "Obj",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Object",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Object",
+    "documentation": null,
+    "data": {
+      "modulePath": "Object",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Objects",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Objects",
+    "documentation": null,
+    "data": {
+      "modulePath": "Objects",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Option",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Option",
+    "documentation": null,
+    "data": {
+      "modulePath": "Option",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Ordering",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Ordering",
+    "documentation": null,
+    "data": {
+      "modulePath": "Ordering",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }]
+
+Complete src/CompletionFunctionArguments.res 57:33
+posCursor:[57:33] posNoWhite:[57:32] Found expr:[57:11->57:34]
+Pexp_apply ...[57:11->57:30] (...[57:31->57:33])
+Completable: Cexpression CArgument Value[someFnTakingVariant]($0)=So
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFnTakingVariant]($0)
+ContextPath Value[someFnTakingVariant]
+Path someFnTakingVariant
+[{
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionFunctionArguments.res 60:44
+posCursor:[60:44] posNoWhite:[60:43] Found expr:[60:11->60:45]
+Pexp_apply ...[60:11->60:30] (~configOpt260:32->60:42=...[60:43->60:44])
+Completable: Cexpression CArgument Value[someFnTakingVariant](~configOpt2)=O
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someFnTakingVariant](~configOpt2)
+ContextPath Value[someFnTakingVariant]
+Path someFnTakingVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two | Three(int, string)\n```"},
+    "sortText": "A One",
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "OIncludeMeInCompletions",
+    "kind": 9,
+    "tags": [],
+    "detail": "module OIncludeMeInCompletions",
+    "documentation": null
+  }, {
+    "label": "Obj",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Obj",
+    "documentation": null,
+    "data": {
+      "modulePath": "Obj",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Object",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Object",
+    "documentation": null,
+    "data": {
+      "modulePath": "Object",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Objects",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Objects",
+    "documentation": null,
+    "data": {
+      "modulePath": "Objects",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Option",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Option",
+    "documentation": null,
+    "data": {
+      "modulePath": "Option",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }, {
+    "label": "Ordering",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Ordering",
+    "documentation": null,
+    "data": {
+      "modulePath": "Ordering",
+      "filePath": "src/CompletionFunctionArguments.res"
+    }
+  }]
+
+Complete src/CompletionFunctionArguments.res 63:23
+posCursor:[63:23] posNoWhite:[63:22] Found expr:[63:11->63:24]
+Pexp_apply ...[63:11->63:22] (...[63:23->63:24])
+Completable: Cexpression CArgument Value[someOtherFn]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someOtherFn]($0)
+ContextPath Value[someOtherFn]
+Path someOtherFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 66:28
+posCursor:[66:28] posNoWhite:[66:27] Found expr:[66:11->66:30]
+Pexp_apply ...[66:11->66:22] (...[66:23->66:24], ...[66:26->66:27])
+Completable: Cexpression CArgument Value[someOtherFn]($2)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someOtherFn]($2)
+ContextPath Value[someOtherFn]
+Path someOtherFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 69:30
+posCursor:[69:30] posNoWhite:[69:29] Found expr:[69:11->69:31]
+Completable: Cexpression CArgument Value[someOtherFn]($2)=t
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[someOtherFn]($2)
+ContextPath Value[someOtherFn]
+Path someOtherFn
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "sortText": "A true"
+  }, {
+    "label": "tLocalVar",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 76:25
+posCursor:[76:25] posNoWhite:[76:24] Found expr:[76:11->76:26]
+Pexp_apply ...[76:11->76:24] (...[76:25->76:26])
+Completable: Cexpression CArgument Value[fnTakingTuple]($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingTuple]($0)
+ContextPath Value[fnTakingTuple]
+Path fnTakingTuple
+[{
+    "label": "(_, _, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int, float)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_}, ${3:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionFunctionArguments.res 89:27
+posCursor:[89:27] posNoWhite:[89:26] Found expr:[89:11->89:29]
+Pexp_apply ...[89:11->89:25] (...[89:26->89:28])
+Completable: Cexpression CArgument Value[fnTakingRecord]($0)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[fnTakingRecord]($0)
+ContextPath Value[fnTakingRecord]
+Path fnTakingRecord
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>}\n```"}
+  }, {
+    "label": "offline",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\noffline: bool\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>}\n```"}
+  }, {
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline: option<bool>\n```\n\n```rescript\ntype someRecord = {age: int, offline: bool, online: option<bool>}\n```"}
+  }]
+
+Complete src/CompletionFunctionArguments.res 109:29
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[105:3->114:4]
+JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:114:2
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[106:35->113:5]
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[106:16->113:5]
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[107:6->109:29]
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[108:6->109:29]
+posCursor:[109:29] posNoWhite:[109:28] Found expr:[109:9->109:29]
+Completable: Cpath Value[thisGetsBrokenLoc]->a <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[thisGetsBrokenLoc]->a <<jsx>>
+ContextPath Value[thisGetsBrokenLoc]
+Path thisGetsBrokenLoc
+CPPipe env:CompletionFunctionArguments
+CPPipe type path:JsxEvent.Mouse.t
+CPPipe pathFromEnv:JsxEvent.Mouse found:false
+Path JsxEvent.Mouse.a
+[{
+    "label": "JsxEvent.Mouse.altKey",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 111:27
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[105:3->114:4]
+JSX <div:[105:3->105:6] onMouseDown[106:4->106:15]=...[106:35->113:5]> _children:114:2
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[106:35->113:5]
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[106:16->113:5]
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[107:6->111:27]
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[108:6->111:27]
+posCursor:[111:27] posNoWhite:[111:26] Found expr:[111:9->111:27]
+Completable: Cpath Value[reassignedWorks]->a <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[reassignedWorks]->a <<jsx>>
+ContextPath Value[reassignedWorks]
+Path reassignedWorks
+CPPipe env:CompletionFunctionArguments
+CPPipe type path:JsxEvent.Mouse.t
+CPPipe pathFromEnv:JsxEvent.Mouse found:false
+Path JsxEvent.Mouse.a
+[{
+    "label": "JsxEvent.Mouse.altKey",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionFunctionArguments.res 121:57
+posCursor:[121:57] posNoWhite:[121:56] Found expr:[121:3->121:73]
+Pexp_apply ...[121:3->121:11] (~changefreq121:13->121:23=...[121:24->121:31], ~lastmod121:34->121:41=...[121:42->0:-1], ~priority121:60->121:68=...[121:69->121:72])
+posCursor:[121:57] posNoWhite:[121:56] Found expr:[121:42->0:-1]
+posCursor:[121:57] posNoWhite:[121:56] Found expr:[121:42->0:-1]
+Completable: Cpath Value[fineModuleVal]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fineModuleVal]->
+ContextPath Value[fineModuleVal]
+Path fineModuleVal
+CPPipe env:CompletionFunctionArguments
+CPPipe type path:FineModule.t
+CPPipe pathFromEnv:FineModule found:true
+Path FineModule.
+[{
+    "label": "FineModule.setToFalse",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt b/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt
new file mode 100644
index 0000000000..ee5375d92f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionInferValues.res.txt
@@ -0,0 +1,873 @@
+Complete src/CompletionInferValues.res 15:43
+posCursor:[15:43] posNoWhite:[15:42] Found expr:[15:33->15:43]
+Completable: Cpath Value[aliased]->f
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[aliased]->f
+ContextPath Value[aliased]
+Path aliased
+ContextPath Value[x]
+Path x
+ContextPath int
+CPPipe env:CompletionInferValues
+Path Belt.Int.f
+[{
+    "label": "Belt.Int.fromString",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => option<int>",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.fromFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => int",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 18:30
+posCursor:[18:30] posNoWhite:[18:29] Found expr:[18:28->18:30]
+Pexp_field [18:28->18:29] _:[33:0->18:30]
+Completable: Cpath Value[x].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x].""
+ContextPath Value[x]
+Path x
+ContextPath Value[getSomeRecord](Nolabel)
+ContextPath Value[getSomeRecord]
+Path getSomeRecord
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 21:53
+posCursor:[21:53] posNoWhite:[21:52] Found expr:[21:45->21:53]
+Pexp_field [21:45->21:52] _:[33:0->21:53]
+Completable: Cpath Value[aliased].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[aliased].""
+ContextPath Value[aliased]
+Path aliased
+ContextPath Value[x]
+Path x
+ContextPath Value[getSomeRecord](Nolabel)
+ContextPath Value[getSomeRecord]
+Path getSomeRecord
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 24:63
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:3->24:64]
+Pexp_apply ...[24:3->24:21] (...[24:22->24:63])
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:22->24:63]
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:23->24:63]
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:36->24:63]
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:42->24:63]
+posCursor:[24:63] posNoWhite:[24:62] Found expr:[24:52->24:63]
+Pexp_field [24:52->24:62] _:[24:63->24:63]
+Completable: Cpath Value[someRecord].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someRecord].""
+ContextPath Value[someRecord]
+Path someRecord
+ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord)
+ContextPath CArgument Value[someFnWithCallback]($0)
+ContextPath Value[someFnWithCallback]
+Path someFnWithCallback
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 27:90
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:39->27:91]
+Pexp_apply ...[27:39->27:48] (...[27:49->27:90])
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:49->27:90]
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:50->27:90]
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:56->27:90]
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:69->27:90]
+posCursor:[27:90] posNoWhite:[27:89] Found expr:[27:79->27:90]
+Pexp_field [27:79->27:89] _:[27:90->27:90]
+Completable: Cpath Value[someRecord].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someRecord].""
+ContextPath Value[someRecord]
+Path someRecord
+ContextPath CArgument CArgument Value[aliasedFn]($0)(~someRecord)
+ContextPath CArgument Value[aliasedFn]($0)
+ContextPath Value[aliasedFn]
+Path aliasedFn
+ContextPath Value[someFnWithCallback]
+Path someFnWithCallback
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 30:36
+posCursor:[30:36] posNoWhite:[30:35] Found expr:[30:3->30:39]
+Pexp_apply ...[30:3->30:15] (...[30:16->30:38])
+posCursor:[30:36] posNoWhite:[30:35] Found expr:[30:16->30:38]
+posCursor:[30:36] posNoWhite:[30:35] Found expr:[30:16->30:38]
+posCursor:[30:36] posNoWhite:[30:35] Found expr:[30:27->30:36]
+Completable: Cpath Value[event]->pr
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[event]->pr
+ContextPath Value[event]
+Path event
+ContextPath CArgument CArgument Value[reactEventFn]($0)($0)
+ContextPath CArgument Value[reactEventFn]($0)
+ContextPath Value[reactEventFn]
+Path reactEventFn
+CPPipe env:CompletionInferValues
+CPPipe type path:ReactEvent.Mouse.t
+CPPipe pathFromEnv:ReactEvent.Mouse found:false
+Path ReactEvent.Mouse.pr
+[{
+    "label": "ReactEvent.Mouse.preventDefault",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionInferValues.res 41:50
+posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:12->41:56]
+JSX <div:[41:12->41:15] onMouseEnter[41:16->41:28]=...[41:36->41:52]> _children:41:54
+posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:36->41:52]
+posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:29->41:52]
+posCursor:[41:50] posNoWhite:[41:49] Found expr:[41:41->41:50]
+Completable: Cpath Value[event]->pr <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[event]->pr <<jsx>>
+ContextPath Value[event]
+Path event
+ContextPath CArgument CJsxPropValue [div] onMouseEnter($0)
+ContextPath CJsxPropValue [div] onMouseEnter
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+CPPipe env:CompletionInferValues
+CPPipe type path:JsxEvent.Mouse.t
+CPPipe pathFromEnv:JsxEvent.Mouse found:false
+Path JsxEvent.Mouse.pr
+[{
+    "label": "JsxEvent.Mouse.preventDefault",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionInferValues.res 44:50
+posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:12->44:56]
+JSX <Div:[44:12->44:15] onMouseEnter[44:16->44:28]=...[44:36->44:52]> _children:44:54
+posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:36->44:52]
+posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:29->44:52]
+posCursor:[44:50] posNoWhite:[44:49] Found expr:[44:41->44:50]
+Completable: Cpath Value[event]->pr <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[event]->pr <<jsx>>
+ContextPath Value[event]
+Path event
+ContextPath CArgument CJsxPropValue [Div] onMouseEnter($0)
+ContextPath CJsxPropValue [Div] onMouseEnter
+Path Div.make
+CPPipe env:CompletionInferValues envFromCompletionItem:CompletionInferValues.Div
+CPPipe type path:JsxEvent.Mouse.t
+CPPipe pathFromEnv:JsxEvent.Mouse found:false
+Path JsxEvent.Mouse.pr
+[{
+    "label": "JsxEvent.Mouse.preventDefault",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionInferValues.res 47:87
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:12->47:93]
+JSX <div:[47:12->47:15] onMouseEnter[47:16->47:28]=...[47:36->47:89]> _children:47:91
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:36->47:89]
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:29->47:89]
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:41->47:87]
+posCursor:[47:87] posNoWhite:[47:86] Found expr:[47:81->47:87]
+Completable: Cpath Value[btn]->t <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[btn]->t <<jsx>>
+ContextPath Value[btn]
+Path btn
+ContextPath Value[JsxEvent, Mouse, button](Nolabel)
+ContextPath Value[JsxEvent, Mouse, button]
+Path JsxEvent.Mouse.button
+CPPipe env:CompletionInferValues envFromCompletionItem:JsxEvent.Mouse
+Path Belt.Int.t
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => float",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 50:108
+posCursor:[50:108] posNoWhite:[50:107] Found expr:[50:12->50:114]
+JSX <div:[50:12->50:15] onMouseEnter[50:16->50:28]=...[50:36->50:110]> _children:50:112
+posCursor:[50:108] posNoWhite:[50:107] Found expr:[50:36->50:110]
+posCursor:[50:108] posNoWhite:[50:107] Found expr:[50:29->50:110]
+posCursor:[50:108] posNoWhite:[50:107] Found expr:[50:41->50:108]
+posCursor:[50:108] posNoWhite:[50:107] Found expr:[50:100->50:108]
+Completable: Cpath Value[btn]->spl <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[btn]->spl <<jsx>>
+ContextPath Value[btn]
+Path btn
+ContextPath Value[Belt, Int, toString](Nolabel)
+ContextPath Value[Belt, Int, toString]
+Path Belt.Int.toString
+CPPipe env:CompletionInferValues envFromCompletionItem:Belt_Int
+Path Js.String2.spl
+[{
+    "label": "Js.String2.splitAtMost",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, ~limit: int) => array<t>",
+    "documentation": {"kind": "markdown", "value": "\n`splitAtMost delimiter ~limit: n str` splits the given `str` at every occurrence of `delimiter` and returns an array of the first `n` resulting substrings. If `n` is negative or greater than the number of substrings, the array will contain all the substrings.\n\n```\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 3 = [|\"ant\"; \"bee\"; \"cat\"|];;\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 0 = [| |];;\nsplitAtMost \"ant/bee/cat/dog/elk\" \"/\" ~limit: 9 = [|\"ant\"; \"bee\"; \"cat\"; \"dog\"; \"elk\"|];;\n```\n"}
+  }, {
+    "label": "Js.String2.splitByRe",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, Js_re.t) => array<option<t>>",
+    "documentation": {"kind": "markdown", "value": "\n`splitByRe(str, regex)` splits the given `str` at every occurrence of `regex`\nand returns an array of the resulting substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.splitByRe(\"art; bed , cog ;dad\", /\\s*[,;]\\s*TODO/) == [\n    Some(\"art\"),\n    Some(\"bed\"),\n    Some(\"cog\"),\n    Some(\"dad\"),\n  ]\n```\n"}
+  }, {
+    "label": "Js.String2.split",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => array<t>",
+    "documentation": {"kind": "markdown", "value": "\n`split(str, delimiter)` splits the given `str` at every occurrence of\n`delimiter` and returns an array of the resulting substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.split(\"2018-01-02\", \"-\") == [\"2018\", \"01\", \"02\"]\nJs.String2.split(\"a,b,,c\", \",\") == [\"a\", \"b\", \"\", \"c\"]\nJs.String2.split(\"good::bad as great::awful\", \"::\") == [\"good\", \"bad as great\", \"awful\"]\nJs.String2.split(\"has-no-delimiter\", \";\") == [\"has-no-delimiter\"]\n```\n"}
+  }, {
+    "label": "Js.String2.splitByReAtMost",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, Js_re.t, ~limit: int) => array<option<t>>",
+    "documentation": {"kind": "markdown", "value": "\n`splitByReAtMost(str, regex, ~limit:n)` splits the given `str` at every\noccurrence of `regex` and returns an array of the first `n` resulting\nsubstrings. If `n` is negative or greater than the number of substrings, the\narray will contain all the substrings.\n\nSee [`String.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=3) == [\n    Some(\"one\"),\n    Some(\"two\"),\n    Some(\"three\"),\n  ]\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=0) == []\n\nJs.String2.splitByReAtMost(\"one: two: three: four\", /\\s*:\\s*TODO/, ~limit=8) == [\n    Some(\"one\"),\n    Some(\"two\"),\n    Some(\"three\"),\n    Some(\"four\"),\n  ]\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 53:130
+posCursor:[53:130] posNoWhite:[53:129] Found expr:[53:12->53:136]
+JSX <div:[53:12->53:15] onMouseEnter[53:16->53:28]=...[53:36->53:132]> _children:53:134
+posCursor:[53:130] posNoWhite:[53:129] Found expr:[53:36->53:132]
+posCursor:[53:130] posNoWhite:[53:129] Found expr:[53:29->53:132]
+posCursor:[53:130] posNoWhite:[53:129] Found expr:[53:41->53:130]
+posCursor:[53:130] posNoWhite:[53:129] Found expr:[53:123->53:130]
+Completable: Cpath Value[btn]->ma <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[btn]->ma <<jsx>>
+ContextPath Value[btn]
+Path btn
+ContextPath Value[Js, String2, split](Nolabel, Nolabel)
+ContextPath Value[Js, String2, split]
+Path Js.String2.split
+CPPipe env:CompletionInferValues envFromCompletionItem:Js_string2
+Path Js.Array2.ma
+[{
+    "label": "Js.Array2.mapi",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, ('a, int) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}
+  }, {
+    "label": "Js.Array2.map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 56:52
+posCursor:[56:52] posNoWhite:[56:51] Found expr:[56:50->56:52]
+Pexp_field [56:50->56:51] _:[59:0->56:52]
+Completable: Cpath Value[x].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x].""
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecord]
+Path someRecord
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 78:78
+posCursor:[78:78] posNoWhite:[78:77] Found expr:[78:70->78:78]
+Pexp_field [78:70->78:77] _:[125:0->78:78]
+Completable: Cpath Value[srecord].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[srecord].""
+ContextPath Value[srecord]
+Path srecord
+ContextPath CPatternPath(Value[x])->recordField(srecord)
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecordWithNestedStuff]
+Path someRecordWithNestedStuff
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 82:86
+posCursor:[82:86] posNoWhite:[82:85] Found expr:[82:78->82:86]
+Pexp_field [82:78->82:85] _:[125:0->82:86]
+Completable: Cpath Value[aliased].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[aliased].""
+ContextPath Value[aliased]
+Path aliased
+ContextPath CPatternPath(Value[x])->recordField(nested)
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecordWithNestedStuff]
+Path someRecordWithNestedStuff
+[{
+    "label": "someRecord",
+    "kind": 5,
+    "tags": [],
+    "detail": "someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype someNestedRecord = {someRecord: someRecord}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 86:103
+posCursor:[86:103] posNoWhite:[86:102] Found expr:[86:92->86:103]
+Pexp_field [86:92->86:102] _:[125:0->86:103]
+Completable: Cpath Value[someRecord].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someRecord].""
+ContextPath Value[someRecord]
+Path someRecord
+ContextPath CPatternPath(Value[x])->recordField(nested)->recordField(someRecord)
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecordWithNestedStuff]
+Path someRecordWithNestedStuff
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 90:81
+posCursor:[90:81] posNoWhite:[90:80] Found expr:[90:69->90:81]
+Completable: Cpath Value[things]->slic
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[things]->slic
+ContextPath Value[things]
+Path things
+ContextPath CPatternPath(Value[x])->recordField(things)
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecordWithNestedStuff]
+Path someRecordWithNestedStuff
+CPPipe env:CompletionInferValues
+Path Js.String2.slic
+[{
+    "label": "Js.String2.sliceToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(str, from:n)` returns the substring of `str` starting at character\n`n` to the end of the string.\n- If `n` is negative, then it is evaluated as `length(str - n)`.\n- If `n` is greater than the length of `str`, then sliceToEnd returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.sliceToEnd(\"abcdefg\", ~from=4) == \"efg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=-2) == \"fg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=7) == \"\"\n```\n"}
+  }, {
+    "label": "Js.String2.slice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int, ~to_: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 94:82
+posCursor:[94:82] posNoWhite:[94:81] Found expr:[94:70->94:82]
+Completable: Cpath Value[someInt]->toS
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someInt]->toS
+ContextPath Value[someInt]
+Path someInt
+ContextPath CPatternPath(Value[x])->recordField(someInt)
+ContextPath Value[x]
+Path x
+ContextPath Type[someRecordWithNestedStuff]
+Path someRecordWithNestedStuff
+CPPipe env:CompletionInferValues
+Path Belt.Int.toS
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 98:109
+posCursor:[98:109] posNoWhite:[98:108] Found expr:[98:97->98:109]
+Completable: Cpath Value[someInt]->toS
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someInt]->toS
+ContextPath Value[someInt]
+Path someInt
+ContextPath CPatternPath(Value[someTuple])->tuple($1)
+ContextPath Value[someTuple]
+Path someTuple
+ContextPath CPatternPath(Value[x])->recordField(someTuple)
+ContextPath Value[x]
+Path x
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+CPPipe env:CompletionInferValues
+Path Belt.Int.toS
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 102:102
+posCursor:[102:102] posNoWhite:[102:101] Found expr:[102:57->102:102]
+posCursor:[102:102] posNoWhite:[102:101] Found expr:[102:90->102:102]
+Completable: Cpath Value[someInt]->toS
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someInt]->toS
+ContextPath Value[someInt]
+Path someInt
+ContextPath CPatternPath(Value[someTuple])->tuple($1)
+ContextPath Value[someTuple]
+Path someTuple
+ContextPath CPatternPath(Value[x])->recordField(someTuple)
+ContextPath Value[x]
+Path x
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+CPPipe env:CompletionInferValues
+Path Belt.Int.toS
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 106:88
+posCursor:[106:88] posNoWhite:[106:87] Found expr:[106:79->106:88]
+Completable: Cpath Value[str]->slic
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[str]->slic
+ContextPath Value[str]
+Path str
+ContextPath CPatternPath(Value[x])->recordField(someTuple)->tuple($0)->variantPayload::Three($1)
+ContextPath Value[x]
+Path x
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+CPPipe env:CompletionInferValues
+Path Js.String2.slic
+[{
+    "label": "Js.String2.sliceToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(str, from:n)` returns the substring of `str` starting at character\n`n` to the end of the string.\n- If `n` is negative, then it is evaluated as `length(str - n)`.\n- If `n` is greater than the length of `str`, then sliceToEnd returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.sliceToEnd(\"abcdefg\", ~from=4) == \"efg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=-2) == \"fg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=7) == \"\"\n```\n"}
+  }, {
+    "label": "Js.String2.slice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int, ~to_: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 110:89
+posCursor:[110:89] posNoWhite:[110:88] Found expr:[110:80->110:89]
+Completable: Cpath Value[str]->slic
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[str]->slic
+ContextPath Value[str]
+Path str
+ContextPath CPatternPath(Value[x])->recordField(someTuple)->tuple($2)->polyvariantPayload::three($1)
+ContextPath Value[x]
+Path x
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+CPPipe env:CompletionInferValues
+Path Js.String2.slic
+[{
+    "label": "Js.String2.sliceToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(str, from:n)` returns the substring of `str` starting at character\n`n` to the end of the string.\n- If `n` is negative, then it is evaluated as `length(str - n)`.\n- If `n` is greater than the length of `str`, then sliceToEnd returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.sliceToEnd(\"abcdefg\", ~from=4) == \"efg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=-2) == \"fg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=7) == \"\"\n```\n"}
+  }, {
+    "label": "Js.String2.slice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int, ~to_: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 114:80
+posCursor:[114:80] posNoWhite:[114:79] Found expr:[114:70->114:80]
+Completable: Cpath Value[name]->slic
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[name]->slic
+ContextPath Value[name]
+Path name
+ContextPath CPatternPath(Value[x])->recordField(optRecord)->variantPayload::Some($0)->recordField(name)
+ContextPath Value[x]
+Path x
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+CPPipe env:CompletionInferValues
+Path Js.String2.slic
+[{
+    "label": "Js.String2.sliceToEnd",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`sliceToEnd(str, from:n)` returns the substring of `str` starting at character\n`n` to the end of the string.\n- If `n` is negative, then it is evaluated as `length(str - n)`.\n- If `n` is greater than the length of `str`, then sliceToEnd returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.sliceToEnd(\"abcdefg\", ~from=4) == \"efg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=-2) == \"fg\"\nJs.String2.sliceToEnd(\"abcdefg\", ~from=7) == \"\"\n```\n"}
+  }, {
+    "label": "Js.String2.slice",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, ~from: int, ~to_: int) => t",
+    "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 118:67
+posCursor:[118:67] posNoWhite:[118:66] Found expr:[118:60->118:67]
+Pexp_field [118:60->118:65] s:[118:66->118:67]
+Completable: Cpath Value[inner].s
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[inner].s
+ContextPath Value[inner]
+Path inner
+ContextPath CPatternPath(Value[x])->array
+ContextPath Value[x]
+Path x
+ContextPath array<Type[otherNestedRecord]>
+ContextPath Type[otherNestedRecord]
+Path otherNestedRecord
+[{
+    "label": "someRecord",
+    "kind": 5,
+    "tags": [],
+    "detail": "someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype otherNestedRecord = {someRecord: someRecord, someTuple: (someVariant, int, somePolyVariant), optRecord: option<someRecord>}\n```"}
+  }, {
+    "label": "someTuple",
+    "kind": 5,
+    "tags": [],
+    "detail": "(someVariant, int, somePolyVariant)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeTuple: (someVariant, int, somePolyVariant)\n```\n\n```rescript\ntype otherNestedRecord = {someRecord: someRecord, someTuple: (someVariant, int, somePolyVariant), optRecord: option<someRecord>}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 122:53
+posCursor:[122:53] posNoWhite:[122:52] Found expr:[122:46->122:53]
+Completable: Cpath Value[v]->toSt
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]->toSt
+ContextPath Value[v]
+Path v
+ContextPath Value[x]
+Path x
+ContextPath int
+CPPipe env:CompletionInferValues
+Path Belt.Int.toSt
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }]
+
+Complete src/CompletionInferValues.res 130:26
+posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:3->130:37]
+Pexp_apply ...[130:3->130:23] (...[130:24->130:36])
+posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:24->130:36]
+posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:25->130:36]
+posCursor:[130:26] posNoWhite:[130:25] Found pattern:[130:25->130:27]
+posCursor:[130:26] posNoWhite:[130:25] Found pattern:[130:25->130:27]
+Completable: Cpattern CArgument CArgument Value[fnWithRecordCallback]($0)($0)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument CArgument Value[fnWithRecordCallback]($0)($0)
+ContextPath CArgument Value[fnWithRecordCallback]($0)
+ContextPath Value[fnWithRecordCallback]
+Path fnWithRecordCallback
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 137:30
+posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:3->137:33]
+Pexp_apply ...[137:3->137:6] (~cb137:8->137:10=...[137:11->137:32])
+posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:11->137:32]
+posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:12->137:32]
+posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:24->0:-1]
+posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:24->0:-1]
+Completable: Cpath Value[root]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[root]->
+ContextPath Value[root]
+Path root
+ContextPath CPatternPath(CArgument CArgument Value[fn2](~cb)($0))->recordField(root)
+ContextPath CArgument CArgument Value[fn2](~cb)($0)
+ContextPath CArgument Value[fn2](~cb)
+ContextPath Value[fn2]
+Path fn2
+CPPipe env:CompletionInferValues
+CPPipe type path:ReactDOM.Client.Root.t
+CPPipe pathFromEnv:ReactDOM.Client.Root found:false
+Path ReactDOM.Client.Root.
+[{
+    "label": "ReactDOM.Client.Root.unmount",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, unit) => unit",
+    "documentation": null
+  }, {
+    "label": "ReactDOM.Client.Root.render",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, React.element) => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionInferValues.res 146:30
+posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:3->146:33]
+Pexp_apply ...[146:3->146:6] (~cb146:8->146:10=...[146:11->146:32])
+posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:11->146:32]
+posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:12->146:32]
+posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:24->0:-1]
+posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:24->0:-1]
+Completable: Cpath Value[root]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[root]->
+ContextPath Value[root]
+Path root
+ContextPath CPatternPath(CArgument CArgument Value[fn3](~cb)($0))->recordField(root)
+ContextPath CArgument CArgument Value[fn3](~cb)($0)
+ContextPath CArgument Value[fn3](~cb)
+ContextPath Value[fn3]
+Path fn3
+CPPipe env:CompletionInferValues
+CPPipe type path:CompletionSupport.Test.t
+CPPipe pathFromEnv:CompletionSupport.Test found:false
+Path CompletionSupport.Test.
+[{
+    "label": "CompletionSupport.Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.addSelf",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionInferValues.res 150:47
+XXX Not found!
+Completable: Cpattern Value[Belt, Int, toString](Nolabel)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Belt, Int, toString](Nolabel)
+ContextPath Value[Belt, Int, toString]
+Path Belt.Int.toString
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "\"$0\"",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionInferValues.res 154:70
+XXX Not found!
+Completable: Cpattern Value[Js, String2, split](Nolabel, Nolabel)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Js, String2, split](Nolabel, Nolabel)
+ContextPath Value[Js, String2, split]
+Path Js.String2.split
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "t",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionInferValues.res 158:105
+posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:18->158:110]
+Pexp_apply ...[158:18->158:49] (~prepare158:51->158:58=...[158:59->158:72], ~render158:74->158:80=...[158:81->158:106], ...[158:107->158:109])
+posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:81->158:106]
+posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:82->158:106]
+posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:97->158:105]
+Pexp_field [158:97->158:104] _:[158:105->158:105]
+Completable: Cpath Value[support].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[support].""
+ContextPath Value[support]
+Path support
+ContextPath CPatternPath(CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0))->recordField(support)
+ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0)
+ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render)
+ContextPath Value[CompletionSupport2, makeRenderer]
+Path CompletionSupport2.makeRenderer
+[{
+    "label": "root",
+    "kind": 5,
+    "tags": [],
+    "detail": "ReactDOM.Client.Root.t",
+    "documentation": {"kind": "markdown", "value": "```rescript\nroot: ReactDOM.Client.Root.t\n```\n\n```rescript\ntype config = {root: ReactDOM.Client.Root.t}\n```"}
+  }]
+
+Complete src/CompletionInferValues.res 162:110
+posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:18->162:115]
+Pexp_apply ...[162:18->162:49] (~prepare162:51->162:58=...[162:59->162:72], ~render162:74->162:80=...[162:81->162:111], ...[162:112->162:114])
+posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:81->162:111]
+posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:82->162:111]
+posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:104->0:-1]
+posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:104->0:-1]
+Completable: Cpath Value[root]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[root]->
+ContextPath Value[root]
+Path root
+ContextPath CPatternPath(CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0))->recordField(support)->recordField(root)
+ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0)
+ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render)
+ContextPath Value[CompletionSupport2, makeRenderer]
+Path CompletionSupport2.makeRenderer
+CPPipe env:CompletionInferValues envFromCompletionItem:CompletionSupport2.Internal
+CPPipe type path:ReactDOM.Client.Root.t
+CPPipe pathFromEnv:ReactDOM.Client.Root found:false
+Path ReactDOM.Client.Root.
+[{
+    "label": "ReactDOM.Client.Root.unmount",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, unit) => unit",
+    "documentation": null
+  }, {
+    "label": "ReactDOM.Client.Root.render",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, React.element) => unit",
+    "documentation": null
+  }]
+
+Hover src/CompletionInferValues.res 167:27
+Nothing at that position. Now trying to use completion.
+posCursor:[167:27] posNoWhite:[167:26] Found expr:[167:25->167:28]
+Pexp_ident res:[167:25->167:28]
+Completable: Cpath Value[res]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt
new file mode 100644
index 0000000000..cbb97152b8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt
@@ -0,0 +1,600 @@
+Complete src/CompletionJsx.res 3:17
+posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:3->3:17]
+Completable: Cpath Value[someString]->st
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someString]->st
+ContextPath Value[someString]
+Path someString
+CPPipe env:CompletionJsx
+Path Js.String2.st
+[{
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 13:21
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[8:13->33:3]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[8:14->33:3]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[9:4->32:10]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[10:4->32:10]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[11:4->32:10]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[12:4->32:10]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->32:10]
+posCursor:[13:21] posNoWhite:[13:20] Found expr:[13:7->13:21]
+Completable: Cpath Value[someString]->st <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someString]->st <<jsx>>
+ContextPath Value[someString]
+Path someString
+CPPipe env:CompletionJsx
+Path Js.String2.st
+[{
+    "label": "React.string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 18:24
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[8:13->33:3]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[8:14->33:3]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[9:4->32:10]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[10:4->32:10]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[11:4->32:10]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[12:4->32:10]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[15:8->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[16:7->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[17:7->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->32:4]
+posCursor:[18:24] posNoWhite:[18:23] Found expr:[18:10->18:24]
+Completable: Cpath Value[someString]->st <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someString]->st <<jsx>>
+ContextPath Value[someString]
+Path someString
+CPPipe env:CompletionJsx
+Path Js.String2.st
+[{
+    "label": "React.string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 20:27
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[8:13->33:3]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[8:14->33:3]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[9:4->32:10]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[10:4->32:10]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[11:4->32:10]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[12:4->32:10]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[15:8->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[16:7->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[17:7->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->32:4]
+posCursor:[20:27] posNoWhite:[20:26] Found expr:[20:10->20:27]
+Completable: Cpath string->st <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath string->st <<jsx>>
+ContextPath string
+CPPipe env:CompletionJsx
+Path Js.String2.st
+[{
+    "label": "React.string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 22:44
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[8:13->33:3]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[8:14->33:3]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[9:4->32:10]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[10:4->32:10]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[11:4->32:10]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[12:4->32:10]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[15:8->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[16:7->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[17:7->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->32:4]
+posCursor:[22:44] posNoWhite:[22:43] Found expr:[22:10->22:44]
+Completable: Cpath Value[Js, String2, trim](Nolabel)->st <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Js, String2, trim](Nolabel)->st <<jsx>>
+ContextPath Value[Js, String2, trim](Nolabel)
+ContextPath Value[Js, String2, trim]
+Path Js.String2.trim
+CPPipe env:CompletionJsx envFromCompletionItem:Js_string2
+Path Js.String2.st
+[{
+    "label": "React.string",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.String2.startsWith",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"}
+  }, {
+    "label": "Js.String2.startsWithFrom",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, t, int) => bool",
+    "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 24:19
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[8:13->33:3]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[8:14->33:3]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[9:4->32:10]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[10:4->32:10]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[11:4->32:10]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[12:4->32:10]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[15:8->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[16:7->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[17:7->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->32:4]
+posCursor:[24:19] posNoWhite:[24:18] Found expr:[24:10->0:-1]
+Completable: Cpath Value[someInt]-> <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someInt]-> <<jsx>>
+ContextPath Value[someInt]
+Path someInt
+CPPipe env:CompletionJsx
+Path Belt.Int.
+[{
+    "label": "React.int",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Belt.Int.fromString",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => option<int>",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.*",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nMultiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 * 2 === 4) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int./",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nDivision of two `int` values. Same as the division from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(4 / 2 === 2); /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => float",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.fromFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => int",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.-",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nSubtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 - 1 === 1) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.+",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nAddition of two `int` values. Same as the addition from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 + 2 === 4) /* true */\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 26:14
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[8:13->33:3]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[8:14->33:3]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[9:4->32:10]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[10:4->32:10]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[11:4->32:10]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[12:4->32:10]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[15:8->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[16:7->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[17:7->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->32:4]
+posCursor:[26:14] posNoWhite:[26:13] Found expr:[26:10->0:-1]
+Completable: Cpath int-> <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath int-> <<jsx>>
+ContextPath int
+CPPipe env:CompletionJsx
+Path Belt.Int.
+[{
+    "label": "React.int",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Belt.Int.fromString",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => option<int>",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.*",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nMultiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 * 2 === 4) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int./",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nDivision of two `int` values. Same as the division from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(4 / 2 === 2); /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => float",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.fromFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => int",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.-",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nSubtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 - 1 === 1) /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.+",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, int) => int",
+    "documentation": {"kind": "markdown", "value": "\nAddition of two `int` values. Same as the addition from `Pervasives`.\n\n## Examples\n\n```rescript\nopen Belt.Int\nJs.log(2 + 2 === 4) /* true */\n```\n"}
+  }]
+
+Complete src/CompletionJsx.res 28:20
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[8:13->33:3]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[8:14->33:3]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[9:4->32:10]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[10:4->32:10]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[11:4->32:10]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[12:4->32:10]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[15:8->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[16:7->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[17:7->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->32:4]
+posCursor:[28:20] posNoWhite:[28:19] Found expr:[28:10->28:20]
+Completable: Cpath Value[someArr]->a <<jsx>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someArr]->a <<jsx>>
+ContextPath Value[someArr]
+Path someArr
+CPPipe env:CompletionJsx
+Path Js.Array2.a
+[{
+    "label": "React.array",
+    "kind": 12,
+    "tags": [],
+    "detail": "array<React.element>",
+    "documentation": {"kind": "markdown", "value": "Turns `array` into a JSX element so it can be used inside of JSX."},
+    "sortText": "A",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Array2.append",
+    "kind": 12,
+    "tags": [1],
+    "detail": "(t<'a>, 'a) => t<'a>",
+    "documentation": {"kind": "markdown", "value": "Deprecated: `append` is not type-safe. Use `concat` instead.\n\n"}
+  }]
+
+Complete src/CompletionJsx.res 30:12
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[8:13->33:3]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[8:14->33:3]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[9:4->32:10]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[10:4->32:10]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[11:4->32:10]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[12:4->32:10]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[15:5->32:10]
+JSX <div:[15:5->15:8] > _children:15:8
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[15:8->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[16:7->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[17:7->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[17:7->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->33:2]
+posCursor:[30:12] posNoWhite:[30:11] Found expr:[30:10->32:10]
+JSX <di:[30:10->30:12] div[32:6->32:9]=...[32:6->32:9]> _children:32:9
+Completable: ChtmlElement <di
+Package opens Pervasives.JsxModules.place holder
+[{
+    "label": "<dialog>",
+    "kind": 4,
+    "tags": [],
+    "detail": "Defines a dialog box or subwindow.",
+    "documentation": {"kind": "markdown", "value": "Defines a dialog box or subwindow."},
+    "insertText": "dialog"
+  }, {
+    "label": "<dir>",
+    "kind": 4,
+    "tags": [1],
+    "detail": "Defines a directory list. Use <ul> instead.",
+    "documentation": {"kind": "markdown", "value": "Deprecated: true\n\nDefines a directory list. Use <ul> instead."},
+    "insertText": "dir"
+  }, {
+    "label": "<div>",
+    "kind": 4,
+    "tags": [],
+    "detail": "Specifies a division or a section in a document.",
+    "documentation": {"kind": "markdown", "value": "Specifies a division or a section in a document."},
+    "insertText": "div"
+  }]
+
+Complete src/CompletionJsx.res 45:23
+posCursor:[45:23] posNoWhite:[45:22] Found expr:[45:4->45:23]
+JSX <CompWithoutJsxPpx:[45:4->45:21] n[45:22->45:23]=...[45:22->45:23]> _children:None
+Completable: Cjsx([CompWithoutJsxPpx], n, [n])
+Package opens Pervasives.JsxModules.place holder
+Path CompWithoutJsxPpx.make
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsx.res 48:27
+posCursor:[48:27] posNoWhite:[48:26] Found expr:[48:4->48:28]
+JSX <SomeComponent:[48:4->48:17] someProp[48:18->48:26]=...[48:18->48:26]> _children:None
+Completable: Cexpression CJsxPropValue [SomeComponent] someProp
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [SomeComponent] someProp
+Path SomeComponent.make
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "{\"$0\"}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsx.res 51:11
+posCursor:[51:11] posNoWhite:[51:10] Found expr:[51:4->51:11]
+JSX <h1:[51:4->51:6] hidd[51:7->51:11]=...[51:7->51:11]> _children:None
+Completable: Cjsx([h1], hidd, [hidd])
+Package opens Pervasives.JsxModules.place holder
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[{
+    "label": "hidden",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsx.res 61:30
+posCursor:[61:30] posNoWhite:[61:28] Found expr:[61:4->61:29]
+JSX <IntrinsicElementLowercase:[61:4->61:29] > _children:None
+Completable: Cjsx([IntrinsicElementLowercase], "", [])
+Package opens Pervasives.JsxModules.place holder
+Path IntrinsicElementLowercase.make
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<string>",
+    "documentation": null
+  }, {
+    "label": "age",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<int>",
+    "documentation": null
+  }, {
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsx.res 73:36
+posCursor:[73:36] posNoWhite:[73:35] Found expr:[73:4->73:41]
+JSX <MultiPropComp:[73:4->73:17] name[73:18->73:22]=...[73:23->73:30] time[73:31->73:35]=...[73:37->73:40]> _children:None
+Completable: Cexpression CJsxPropValue [MultiPropComp] time
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [MultiPropComp] time
+Path MultiPropComp.make
+[{
+    "label": "Now",
+    "kind": 4,
+    "tags": [],
+    "detail": "Now",
+    "documentation": {"kind": "markdown", "value": "```rescript\nNow\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Now}",
+    "insertTextFormat": 2
+  }, {
+    "label": "Later",
+    "kind": 4,
+    "tags": [],
+    "detail": "Later",
+    "documentation": {"kind": "markdown", "value": "```rescript\nLater\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Later}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsx.res 76:36
+posCursor:[76:36] posNoWhite:[76:35] Found expr:[76:4->76:40]
+JSX <MultiPropComp:[76:4->76:17] name[76:18->76:22]=...[76:23->76:30] time[76:31->76:35]=...[76:37->76:40]> _children:None
+Completable: Cexpression CJsxPropValue [MultiPropComp] time
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [MultiPropComp] time
+Path MultiPropComp.make
+[{
+    "label": "Now",
+    "kind": 4,
+    "tags": [],
+    "detail": "Now",
+    "documentation": {"kind": "markdown", "value": "```rescript\nNow\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Now}",
+    "insertTextFormat": 2
+  }, {
+    "label": "Later",
+    "kind": 4,
+    "tags": [],
+    "detail": "Later",
+    "documentation": {"kind": "markdown", "value": "```rescript\nLater\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Later}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsx.res 79:28
+posCursor:[79:28] posNoWhite:[79:27] Found expr:[79:4->79:32]
+JSX <MultiPropComp:[79:4->79:17] name[79:18->79:22]=...[79:18->79:22] time[79:23->79:27]=...[79:29->79:32]> _children:None
+Completable: Cexpression CJsxPropValue [MultiPropComp] time
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [MultiPropComp] time
+Path MultiPropComp.make
+[{
+    "label": "Now",
+    "kind": 4,
+    "tags": [],
+    "detail": "Now",
+    "documentation": {"kind": "markdown", "value": "```rescript\nNow\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Now}",
+    "insertTextFormat": 2
+  }, {
+    "label": "Later",
+    "kind": 4,
+    "tags": [],
+    "detail": "Later",
+    "documentation": {"kind": "markdown", "value": "```rescript\nLater\n```\n\n```rescript\ntype time = Now | Later\n```"},
+    "insertText": "{Later}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsx.res 89:26
+posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:4->89:27]
+JSX <Info:[89:4->89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:89:26
+Completable: Cjsx([Info], "", [_type])
+Package opens Pervasives.JsxModules.place holder
+Path Info.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
new file mode 100644
index 0000000000..c4b4a49a7c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
@@ -0,0 +1,366 @@
+Complete src/CompletionJsxProps.res 0:47
+posCursor:[0:47] posNoWhite:[0:46] Found expr:[0:12->0:47]
+JSX <CompletionSupport.TestComponent:[0:12->0:43] on[0:44->0:46]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] on
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsxProps.res 3:48
+posCursor:[3:48] posNoWhite:[3:47] Found expr:[3:12->3:48]
+JSX <CompletionSupport.TestComponent:[3:12->3:43] on[3:44->3:46]=...[3:47->3:48]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on=t
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] on
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsxProps.res 6:50
+posCursor:[6:50] posNoWhite:[6:49] Found expr:[6:12->6:50]
+JSX <CompletionSupport.TestComponent:[6:12->6:43] test[6:44->6:48]=...[6:49->6:50]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] test=T
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] test
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "Two",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "sortText": "A Two",
+    "insertText": "{Two}",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(int)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(int)\n```\n\n```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "sortText": "A Three(_)",
+    "insertText": "{Three($0)}",
+    "insertTextFormat": 2
+  }, {
+    "label": "TableclothMap",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TableclothMap",
+    "documentation": null,
+    "data": {
+      "modulePath": "TableclothMap",
+      "filePath": "src/CompletionJsxProps.res"
+    }
+  }, {
+    "label": "Type",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Type",
+    "documentation": null,
+    "data": {
+      "modulePath": "Type",
+      "filePath": "src/CompletionJsxProps.res"
+    }
+  }, {
+    "label": "TypeAtPosCompletion",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypeAtPosCompletion",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypeAtPosCompletion",
+      "filePath": "src/CompletionJsxProps.res"
+    }
+  }, {
+    "label": "TypeDefinition",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypeDefinition",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypeDefinition",
+      "filePath": "src/CompletionJsxProps.res"
+    }
+  }, {
+    "label": "TypedArray",
+    "kind": 9,
+    "tags": [],
+    "detail": "module TypedArray",
+    "documentation": null,
+    "data": {
+      "modulePath": "TypedArray",
+      "filePath": "src/CompletionJsxProps.res"
+    }
+  }]
+
+Complete src/CompletionJsxProps.res 9:52
+posCursor:[9:52] posNoWhite:[9:51] Found expr:[9:12->9:52]
+JSX <CompletionSupport.TestComponent:[9:12->9:43] polyArg[9:44->9:51]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] polyArg
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{#one}",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(int, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(int, bool)\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{#three(${1:_}, ${2:_})}",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{#two}",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two2",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two2",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two2\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{#two2}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 12:54
+posCursor:[12:54] posNoWhite:[12:53] Found expr:[12:12->12:54]
+JSX <CompletionSupport.TestComponent:[12:12->12:43] polyArg[12:44->12:51]=...[12:52->12:54]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg=#t
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] polyArg
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(int, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(int, bool)\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{three(${1:_}, ${2:_})}",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{two}",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two2",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two2",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two2\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "{two2}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 15:22
+posCursor:[15:22] posNoWhite:[15:21] Found expr:[15:12->15:25]
+JSX <div:[15:12->15:15] muted[15:16->15:21]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [div] muted
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [div] muted
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsxProps.res 18:29
+posCursor:[18:29] posNoWhite:[18:28] Found expr:[18:12->18:32]
+JSX <div:[18:12->18:15] onMouseEnter[18:16->18:28]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [div] onMouseEnter
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [div] onMouseEnter
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[{
+    "label": "event => event",
+    "kind": 12,
+    "tags": [],
+    "detail": "JsxEvent.Mouse.t => unit",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "{${1:event} => ${0:event}}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 22:52
+posCursor:[22:52] posNoWhite:[22:51] Found expr:[22:12->22:52]
+JSX <CompletionSupport.TestComponent:[22:12->22:43] testArr[22:44->22:51]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] testArr
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] testArr
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "testVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "sortText": "A",
+    "insertText": "{[$0]}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 26:54
+posCursor:[26:54] posNoWhite:[26:53] Found expr:[26:12->26:56]
+JSX <CompletionSupport.TestComponent:[26:12->26:43] testArr[26:44->26:51]=...[26:53->26:55]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] testArr->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] testArr
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "insertText": "Two",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(int)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(int)\n```\n\n```rescript\ntype testVariant = One | Two | Three(int)\n```"},
+    "insertText": "Three($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 31:53
+posCursor:[31:53] posNoWhite:[31:52] Found expr:[31:12->31:54]
+JSX <CompletionSupport.TestComponent:[31:12->31:43] polyArg[31:44->31:51]=...[31:52->31:54]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyArg->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] polyArg
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(int, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(int, bool)\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "#three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "#two",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two2",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two2",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two2\n```\n\n```rescript\n[#one | #three(int, bool) | #two | #two2]\n```"},
+    "insertText": "#two2",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionJsxProps.res 34:49
+posCursor:[34:49] posNoWhite:[34:48] Found expr:[34:12->34:50]
+JSX <CompletionSupport.TestComponent:[34:12->34:43] on[34:44->34:46]=...[34:48->34:49]> _children:None
+Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] on=t->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletionSupport, TestComponent] on
+Path CompletionSupport.TestComponent.make
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "tsomeVar",
+    "kind": 12,
+    "tags": [],
+    "detail": "[> #two]",
+    "documentation": null
+  }]
+
+Complete src/CompletionJsxProps.res 44:44
+posCursor:[44:44] posNoWhite:[44:43] Found expr:[44:12->44:44]
+JSX <CompletableComponentLazy:[44:12->44:36] status[44:37->44:43]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [CompletableComponentLazy] status
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [CompletableComponentLazy] status
+Path CompletableComponentLazy.make
+[{
+    "label": "On",
+    "kind": 4,
+    "tags": [],
+    "detail": "On",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOn\n```\n\n```rescript\ntype status = On | Off\n```"},
+    "insertText": "{On}",
+    "insertTextFormat": 2
+  }, {
+    "label": "Off",
+    "kind": 4,
+    "tags": [],
+    "detail": "Off",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOff\n```\n\n```rescript\ntype status = On | Off\n```"},
+    "insertText": "{Off}",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionPattern.res.txt b/tests/analysis_tests/tests/src/expected/CompletionPattern.res.txt
new file mode 100644
index 0000000000..2e4d21643c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionPattern.res.txt
@@ -0,0 +1,1173 @@
+Complete src/CompletionPattern.res 7:13
+posCursor:[7:13] posNoWhite:[7:12] Found expr:[7:3->7:13]
+[]
+
+Complete src/CompletionPattern.res 10:15
+XXX Not found!
+Completable: Cpattern Value[v]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "(_, _, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(bool, option<bool>, (bool, bool))",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_}, ${3:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 13:18
+posCursor:[13:18] posNoWhite:[13:17] Found pattern:[13:16->13:22]
+posCursor:[13:18] posNoWhite:[13:17] Found pattern:[13:17->13:18]
+Completable: Cpattern Value[v]=t->tuple($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 16:25
+posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:16->16:30]
+posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:23->16:29]
+posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:24->16:25]
+Completable: Cpattern Value[v]=f->tuple($2), tuple($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 21:15
+XXX Not found!
+Completable: Cpattern Value[x]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 24:17
+posCursor:[24:17] posNoWhite:[24:16] Found pattern:[24:16->24:17]
+Completable: Cpattern Value[x]=t
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 46:15
+XXX Not found!
+Completable: Cpattern Value[f]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 49:17
+posCursor:[49:17] posNoWhite:[49:16] Found pattern:[49:16->49:18]
+Completable: Cpattern Value[f]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "first",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfirst: int\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "second",
+    "kind": 5,
+    "tags": [],
+    "detail": "(bool, option<someRecord>)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsecond: (bool, option<someRecord>)\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 52:24
+posCursor:[52:24] posNoWhite:[52:22] Found pattern:[52:16->52:35]
+Completable: Cpattern Value[f]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 55:19
+posCursor:[55:19] posNoWhite:[55:18] Found pattern:[55:16->55:20]
+posCursor:[55:19] posNoWhite:[55:18] Found pattern:[55:17->55:19]
+Completable: Cpattern Value[f]=fi->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "first",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfirst: int\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 58:19
+posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:16->58:24]
+posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:17->58:20]
+posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:18->58:19]
+Completable: Cpattern Value[z]=o->tuple($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 61:22
+posCursor:[61:22] posNoWhite:[61:21] Found pattern:[61:16->61:25]
+Completable: Cpattern Value[f]->recordField(nest)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype nestedRecord = {nested: bool}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 64:24
+posCursor:[64:24] posNoWhite:[64:23] Found pattern:[64:16->64:26]
+posCursor:[64:24] posNoWhite:[64:23] Found pattern:[64:23->64:25]
+Completable: Cpattern Value[f]->recordField(nest), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: bool\n```\n\n```rescript\ntype nestedRecord = {nested: bool}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 70:22
+posCursor:[70:22] posNoWhite:[70:21] Found expr:[69:2->72:13]
+posCursor:[70:22] posNoWhite:[70:21] Found expr:[70:5->72:13]
+posCursor:[70:22] posNoWhite:[70:21] Found pattern:[70:21->70:23]
+Completable: Cpattern Value[nest]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[nest]
+Path nest
+[{
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: bool\n```\n\n```rescript\ntype nestedRecord = {nested: bool}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 76:8
+posCursor:[76:8] posNoWhite:[76:7] Found pattern:[76:7->76:9]
+Completable: Cpattern Value[f]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "first",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfirst: int\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "second",
+    "kind": 5,
+    "tags": [],
+    "detail": "(bool, option<someRecord>)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsecond: (bool, option<someRecord>)\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 79:16
+posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:7->79:18]
+posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:14->79:17]
+posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:15->79:16]
+Completable: Cpattern Value[f]=n->recordField(nest), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[f]
+Path f
+[{
+    "label": "nested",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnested: bool\n```\n\n```rescript\ntype nestedRecord = {nested: bool}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 87:20
+posCursor:[87:20] posNoWhite:[87:19] Found pattern:[87:16->87:21]
+Ppat_construct Two:[87:16->87:19]
+posCursor:[87:20] posNoWhite:[87:19] Found pattern:[87:19->87:21]
+Ppat_construct ():[87:19->87:21]
+Completable: Cpattern Value[z]->variantPayload::Two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 90:21
+posCursor:[90:21] posNoWhite:[90:20] Found pattern:[90:16->90:22]
+Ppat_construct Two:[90:16->90:19]
+posCursor:[90:21] posNoWhite:[90:20] Found pattern:[90:20->90:21]
+Completable: Cpattern Value[z]=t->variantPayload::Two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 93:23
+posCursor:[93:23] posNoWhite:[93:22] Found pattern:[93:16->93:25]
+Ppat_construct Three:[93:16->93:21]
+posCursor:[93:23] posNoWhite:[93:22] Found pattern:[93:22->93:24]
+Completable: Cpattern Value[z]->variantPayload::Three($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "first",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfirst: int\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "second",
+    "kind": 5,
+    "tags": [],
+    "detail": "(bool, option<someRecord>)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsecond: (bool, option<someRecord>)\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 96:27
+posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:16->96:28]
+Ppat_construct Three:[96:16->96:21]
+posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:21->96:29]
+posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:26->96:27]
+Completable: Cpattern Value[z]=t->variantPayload::Three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 103:21
+posCursor:[103:21] posNoWhite:[103:20] Found pattern:[103:16->103:22]
+posCursor:[103:21] posNoWhite:[103:20] Found pattern:[103:20->103:21]
+Ppat_construct ():[103:20->103:21]
+Completable: Cpattern Value[b]->polyvariantPayload::two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 106:22
+posCursor:[106:22] posNoWhite:[106:21] Found pattern:[106:16->106:23]
+posCursor:[106:22] posNoWhite:[106:21] Found pattern:[106:21->106:22]
+Completable: Cpattern Value[b]=t->polyvariantPayload::two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 109:24
+posCursor:[109:24] posNoWhite:[109:23] Found pattern:[109:16->109:26]
+posCursor:[109:24] posNoWhite:[109:23] Found pattern:[109:23->109:25]
+Completable: Cpattern Value[b]->polyvariantPayload::three($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "first",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nfirst: int\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "second",
+    "kind": 5,
+    "tags": [],
+    "detail": "(bool, option<someRecord>)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsecond: (bool, option<someRecord>)\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 112:28
+posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:16->112:29]
+posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:22->112:29]
+posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:27->112:28]
+Completable: Cpattern Value[b]=t->polyvariantPayload::three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 118:15
+XXX Not found!
+Completable: Cpattern Value[c]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[c]
+Path c
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 121:17
+posCursor:[121:17] posNoWhite:[121:16] Found pattern:[121:16->121:18]
+Completable: Cpattern Value[c]->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[c]
+Path c
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 127:21
+posCursor:[127:21] posNoWhite:[127:20] Found pattern:[127:16->127:22]
+Ppat_construct Some:[127:16->127:20]
+posCursor:[127:21] posNoWhite:[127:20] Found pattern:[127:20->127:22]
+Ppat_construct ():[127:20->127:22]
+Completable: Cpattern Value[o]->variantPayload::Some($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[o]
+Path o
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 134:23
+posCursor:[134:23] posNoWhite:[134:22] Found pattern:[134:16->134:25]
+Ppat_construct Test:[134:16->134:20]
+Completable: Cpattern Value[p]->variantPayload::Test($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[p]
+Path p
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 137:29
+posCursor:[137:29] posNoWhite:[137:28] Found pattern:[137:16->137:31]
+Ppat_construct Test:[137:16->137:20]
+posCursor:[137:29] posNoWhite:[137:28] Found pattern:[137:20->137:32]
+Completable: Cpattern Value[p]->variantPayload::Test($2)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[p]
+Path p
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 140:23
+posCursor:[140:23] posNoWhite:[140:22] Found pattern:[140:16->140:31]
+Ppat_construct Test:[140:16->140:20]
+posCursor:[140:23] posNoWhite:[140:22] Found pattern:[140:20->140:32]
+Completable: Cpattern Value[p]->variantPayload::Test($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[p]
+Path p
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 143:35
+posCursor:[143:35] posNoWhite:[143:34] Found pattern:[143:16->143:37]
+Ppat_construct Test:[143:16->143:20]
+posCursor:[143:35] posNoWhite:[143:34] Found pattern:[143:20->143:38]
+Completable: Cpattern Value[p]->variantPayload::Test($3)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[p]
+Path p
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 150:24
+posCursor:[150:24] posNoWhite:[150:23] Found pattern:[150:16->150:26]
+Completable: Cpattern Value[v]->polyvariantPayload::test($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 153:30
+posCursor:[153:30] posNoWhite:[153:29] Found pattern:[153:16->153:32]
+posCursor:[153:30] posNoWhite:[153:29] Found pattern:[153:21->153:32]
+Completable: Cpattern Value[v]->polyvariantPayload::test($2)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 156:24
+posCursor:[156:24] posNoWhite:[156:23] Found pattern:[156:16->156:32]
+posCursor:[156:24] posNoWhite:[156:23] Found pattern:[156:21->156:32]
+Completable: Cpattern Value[v]->polyvariantPayload::test($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 159:36
+posCursor:[159:36] posNoWhite:[159:35] Found pattern:[159:16->159:38]
+posCursor:[159:36] posNoWhite:[159:35] Found pattern:[159:21->159:38]
+Completable: Cpattern Value[v]->polyvariantPayload::test($3)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[v]
+Path v
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 164:17
+posCursor:[164:17] posNoWhite:[164:16] Found pattern:[164:16->164:18]
+Ppat_construct ():[164:16->164:18]
+Completable: Cpattern Value[s]->tuple($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 167:23
+posCursor:[167:23] posNoWhite:[167:21] Found pattern:[167:16->167:24]
+Completable: Cpattern Value[s]->tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 170:22
+posCursor:[170:22] posNoWhite:[170:21] Found pattern:[170:16->170:28]
+Completable: Cpattern Value[s]->tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 173:35
+XXX Not found!
+Completable: Cpattern Value[s]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "(_, _, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(bool, option<bool>, array<bool>)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_}, ${3:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 176:41
+posCursor:[176:41] posNoWhite:[176:40] Found pattern:[176:35->176:47]
+Completable: Cpattern Value[s]->tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 179:21
+XXX Not found!
+Completable: Cpattern Value[z]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 182:32
+posCursor:[182:32] posNoWhite:[182:31] Found pattern:[182:16->182:34]
+posCursor:[182:32] posNoWhite:[182:31] Found pattern:[182:22->182:34]
+Ppat_construct Two:[182:22->182:25]
+Completable: Cpattern Value[z]->variantPayload::Two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 185:48
+posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:16->185:50]
+posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:22->185:50]
+Ppat_construct Three:[185:22->185:27]
+posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:27->185:53]
+Completable: Cpattern Value[z]->variantPayload::Three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[z]
+Path z
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 188:34
+posCursor:[188:34] posNoWhite:[188:33] Found pattern:[188:16->188:36]
+posCursor:[188:34] posNoWhite:[188:33] Found pattern:[188:23->188:36]
+Completable: Cpattern Value[b]->polyvariantPayload::two($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 191:50
+posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:16->191:52]
+posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:23->191:52]
+posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:29->191:52]
+Completable: Cpattern Value[b]->polyvariantPayload::three($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[b]
+Path b
+[{
+    "label": "true",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "false",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 194:24
+posCursor:[194:24] posNoWhite:[194:23] Found pattern:[194:16->194:29]
+posCursor:[194:24] posNoWhite:[194:23] Found pattern:[194:23->194:24]
+Completable: Cpattern Value[s]->tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[s]
+Path s
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionPattern.res 201:25
+posCursor:[201:25] posNoWhite:[201:24] Found pattern:[201:17->201:28]
+Completable: Cpattern Value[ff]->recordField(someFn)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[ff]
+Path ff
+[]
+
+Complete src/CompletionPattern.res 206:16
+XXX Not found!
+Completable: Cpattern Value[xn]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[xn]
+Path xn
+[{
+    "label": "Js.Exn.Error(error)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Catches errors from JavaScript errors.",
+    "documentation": {"kind": "markdown", "value": "Matches on a JavaScript error. Read more in the [documentation on catching JS exceptions](https://rescript-lang.org/docs/manual/latest/exception#catching-js-exceptions)."}
+  }]
+
+Complete src/CompletionPattern.res 211:30
+XXX Not found!
+Completable: Cpattern await Value[getThing](Nolabel)
+Package opens Pervasives.JsxModules.place holder
+ContextPath await Value[getThing](Nolabel)
+ContextPath Value[getThing](Nolabel)
+ContextPath Value[getThing]
+Path getThing
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 216:21
+posCursor:[216:21] posNoWhite:[216:20] Found pattern:[216:18->216:22]
+Ppat_construct Ok:[216:18->216:20]
+posCursor:[216:21] posNoWhite:[216:20] Found pattern:[216:20->216:22]
+Ppat_construct ():[216:20->216:22]
+Completable: Cpattern Value[res]->variantPayload::Ok($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 219:24
+posCursor:[219:24] posNoWhite:[219:23] Found pattern:[219:18->219:25]
+Ppat_construct Error:[219:18->219:23]
+posCursor:[219:24] posNoWhite:[219:23] Found pattern:[219:23->219:25]
+Ppat_construct ():[219:23->219:25]
+Completable: Cpattern Value[res]->variantPayload::Error($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(someRecord, bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two(bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#two(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionPattern.res 227:25
+posCursor:[227:25] posNoWhite:[227:24] Found expr:[223:11->231:1]
+posCursor:[227:25] posNoWhite:[227:24] Found expr:[223:12->231:1]
+posCursor:[227:25] posNoWhite:[227:24] Found expr:[226:4->227:28]
+posCursor:[227:25] posNoWhite:[227:24] Found pattern:[227:18->227:27]
+Completable: Cpattern Value[r]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[r]
+Path r
+[{
+    "label": "second",
+    "kind": 5,
+    "tags": [],
+    "detail": "(bool, option<someRecord>)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsecond: (bool, option<someRecord>)\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "optThird",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<[#first | #second(someRecord)]>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noptThird: option<[#first | #second(someRecord)]>\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }, {
+    "label": "nest",
+    "kind": 5,
+    "tags": [],
+    "detail": "nestedRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
+  }]
+
+Complete src/CompletionPattern.res 242:33
+posCursor:[242:33] posNoWhite:[242:32] Found pattern:[242:7->242:35]
+Completable: Cpattern Value[hitsUse](Nolabel)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[hitsUse](Nolabel)
+ContextPath Value[hitsUse]
+Path hitsUse
+[{
+    "label": "hits",
+    "kind": 5,
+    "tags": [],
+    "detail": "array<string>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nhits: array<string>\n```\n\n```rescript\ntype hitsUse = {results: results, hits: array<string>}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt b/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt
new file mode 100644
index 0000000000..24521a5cf3
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt
@@ -0,0 +1,556 @@
+Complete src/CompletionPipeChain.res 27:16
+posCursor:[27:16] posNoWhite:[27:15] Found expr:[27:11->0:-1]
+Completable: Cpath Value[int]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[int]->
+ContextPath Value[int]
+Path int
+CPPipe env:CompletionPipeChain
+CPPipe type path:Integer.t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 30:23
+posCursor:[30:23] posNoWhite:[30:22] Found expr:[30:11->0:-1]
+Completable: Cpath Value[toFlt](Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[toFlt](Nolabel)->
+ContextPath Value[toFlt](Nolabel)
+ContextPath Value[toFlt]
+Path toFlt
+CPPipe env:CompletionPipeChain
+CPPipe type path:SuperFloat.t
+CPPipe pathFromEnv:SuperFloat found:true
+Path SuperFloat.
+[{
+    "label": "SuperFloat.fromInteger",
+    "kind": 12,
+    "tags": [],
+    "detail": "Integer.t => t",
+    "documentation": null
+  }, {
+    "label": "SuperFloat.toInteger",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => Integer.t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 33:38
+posCursor:[33:38] posNoWhite:[33:37] Found expr:[33:11->0:-1]
+Completable: Cpath Value[Integer, increment](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Integer, increment](Nolabel, Nolabel)->
+ContextPath Value[Integer, increment](Nolabel, Nolabel)
+ContextPath Value[Integer, increment]
+Path Integer.increment
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer
+CPPipe type path:t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 36:38
+posCursor:[36:38] posNoWhite:[36:37] Found expr:[36:11->0:-1]
+Completable: Cpath Value[Integer, increment](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Integer, increment](Nolabel, Nolabel)->
+ContextPath Value[Integer, increment](Nolabel, Nolabel)
+ContextPath Value[Integer, increment]
+Path Integer.increment
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer
+CPPipe type path:t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 39:47
+posCursor:[39:47] posNoWhite:[39:46] Found expr:[39:11->0:-1]
+Completable: Cpath Value[Integer, decrement](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Integer, decrement](Nolabel, Nolabel)->
+ContextPath Value[Integer, decrement](Nolabel, Nolabel)
+ContextPath Value[Integer, decrement]
+Path Integer.decrement
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer
+CPPipe type path:t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 42:69
+posCursor:[42:69] posNoWhite:[42:68] Found expr:[42:11->0:-1]
+Completable: Cpath Value[Integer, decrement](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Integer, decrement](Nolabel, Nolabel)->
+ContextPath Value[Integer, decrement](Nolabel, Nolabel)
+ContextPath Value[Integer, decrement]
+Path Integer.decrement
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer
+CPPipe type path:t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 45:62
+posCursor:[45:62] posNoWhite:[45:61] Found expr:[45:11->0:-1]
+Completable: Cpath Value[SuperFloat, fromInteger](Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[SuperFloat, fromInteger](Nolabel)->
+ContextPath Value[SuperFloat, fromInteger](Nolabel)
+ContextPath Value[SuperFloat, fromInteger]
+Path SuperFloat.fromInteger
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.SuperFloat
+CPPipe type path:t
+CPPipe pathFromEnv:SuperFloat found:true
+Path SuperFloat.
+[{
+    "label": "SuperFloat.fromInteger",
+    "kind": 12,
+    "tags": [],
+    "detail": "Integer.t => t",
+    "documentation": null
+  }, {
+    "label": "SuperFloat.toInteger",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => Integer.t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 48:63
+posCursor:[48:63] posNoWhite:[48:62] Found expr:[48:11->48:63]
+Completable: Cpath Value[SuperFloat, fromInteger](Nolabel)->t
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[SuperFloat, fromInteger](Nolabel)->t
+ContextPath Value[SuperFloat, fromInteger](Nolabel)
+ContextPath Value[SuperFloat, fromInteger]
+Path SuperFloat.fromInteger
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.SuperFloat
+CPPipe type path:t
+CPPipe pathFromEnv:SuperFloat found:true
+Path SuperFloat.t
+[{
+    "label": "SuperFloat.toInteger",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => Integer.t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 51:82
+posCursor:[51:82] posNoWhite:[51:81] Found expr:[51:11->0:-1]
+Completable: Cpath Value[CompletionSupport, Test, make](Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[CompletionSupport, Test, make](Nolabel)->
+ContextPath Value[CompletionSupport, Test, make](Nolabel)
+ContextPath Value[CompletionSupport, Test, make]
+Path CompletionSupport.Test.make
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Test
+CPPipe type path:t
+CPPipe pathFromEnv:Test found:true
+Path CompletionSupport.Test.
+[{
+    "label": "CompletionSupport.Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.addSelf",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 54:78
+posCursor:[54:78] posNoWhite:[54:77] Found expr:[54:11->0:-1]
+Completable: Cpath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel)->
+ContextPath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel)
+ContextPath Value[CompletionSupport, Test, addSelf]
+Path CompletionSupport.Test.addSelf
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Test
+CPPipe type path:t
+CPPipe pathFromEnv:Test found:true
+Path CompletionSupport.Test.
+[{
+    "label": "CompletionSupport.Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.addSelf",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 58:5
+posCursor:[58:5] posNoWhite:[58:4] Found expr:[57:8->0:-1]
+Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Js, Array2, forEach](Nolabel, Nolabel)->
+ContextPath Value[Js, Array2, forEach](Nolabel, Nolabel)
+ContextPath Value[Js, Array2, forEach]
+Path Js.Array2.forEach
+CPPipe env:CompletionPipeChain envFromCompletionItem:Js_array2
+CPPipe type path:unit
+CPPipe pathFromEnv: found:true
+[]
+
+Complete src/CompletionPipeChain.res 62:6
+posCursor:[62:6] posNoWhite:[62:5] Found expr:[61:8->62:6]
+Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)->t
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)->t
+ContextPath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)
+ContextPath Value[Belt, Array, reduce]
+Path Belt.Array.reduce
+CPPipe env:CompletionPipeChain envFromCompletionItem:Belt_Array
+Path Belt.Int.t
+[{
+    "label": "Belt.Int.toString",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => string",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"}
+  }, {
+    "label": "Belt.Int.toFloat",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => float",
+    "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"}
+  }]
+
+Complete src/CompletionPipeChain.res 70:12
+posCursor:[70:12] posNoWhite:[70:11] Found expr:[70:3->0:-1]
+Completable: Cpath Value[aliased]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[aliased]->
+ContextPath Value[aliased]
+Path aliased
+CPPipe env:CompletionPipeChain
+CPPipe type path:CompletionSupport.Test.t
+CPPipe pathFromEnv:CompletionSupport.Test found:false
+Path CompletionSupport.Test.
+[{
+    "label": "CompletionSupport.Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.addSelf",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 73:15
+posCursor:[73:15] posNoWhite:[73:14] Found expr:[73:3->0:-1]
+Completable: Cpath Value[notAliased]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[notAliased]->
+ContextPath Value[notAliased]
+Path notAliased
+CPPipe env:CompletionPipeChain
+CPPipe type path:CompletionSupport.Test.t
+CPPipe pathFromEnv:CompletionSupport.Test found:false
+Path CompletionSupport.Test.
+[{
+    "label": "CompletionSupport.Test.add",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.addSelf",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => t",
+    "documentation": null
+  }, {
+    "label": "CompletionSupport.Test.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 82:30
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[76:15->93:1]
+Pexp_apply ...[76:15->76:46] (~prepare77:3->77:10=...[77:11->77:24], ~render78:3->78:9=...[78:10->91:3], ...[92:2->92:4])
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[78:10->91:3]
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[78:10->91:3]
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[79:4->90:14]
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[82:7->90:14]
+posCursor:[82:30] posNoWhite:[82:29] Found expr:[82:7->82:30]
+Completable: Cpath Value[props].support.root->ren
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[props].support.root->ren
+ContextPath Value[props].support.root
+ContextPath Value[props].support
+ContextPath Value[props]
+Path props
+CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Nested
+CPPipe type path:ReactDOM.Client.Root.t
+CPPipe pathFromEnv:ReactDOM.Client.Root found:false
+Path ReactDOM.Client.Root.ren
+[{
+    "label": "ReactDOM.Client.Root.render",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, React.element) => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 88:16
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[76:15->93:1]
+Pexp_apply ...[76:15->76:46] (~prepare77:3->77:10=...[77:11->77:24], ~render78:3->78:9=...[78:10->91:3], ...[92:2->92:4])
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[78:10->91:3]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[78:10->91:3]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[79:4->90:14]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[84:4->90:14]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[85:4->90:14]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[88:7->90:14]
+posCursor:[88:16] posNoWhite:[88:15] Found expr:[88:7->88:16]
+Completable: Cpath Value[root]->ren
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[root]->ren
+ContextPath Value[root]
+Path root
+CPPipe env:CompletionPipeChain
+CPPipe type path:ReactDOM.Client.Root.t
+CPPipe pathFromEnv:ReactDOM.Client.Root found:false
+Path ReactDOM.Client.Root.ren
+[{
+    "label": "ReactDOM.Client.Root.render",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, React.element) => unit",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 95:20
+posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:3->95:21]
+Pexp_apply ...[95:3->95:14] (...[95:15->0:-1])
+posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:15->0:-1]
+posCursor:[95:20] posNoWhite:[95:19] Found expr:[95:15->0:-1]
+Completable: Cpath Value[int]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[int]->
+ContextPath Value[int]
+Path int
+CPPipe env:CompletionPipeChain
+CPPipe type path:Integer.t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }, {
+    "label": "Integer.increment",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.decrement",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t, int => int) => t",
+    "documentation": null
+  }, {
+    "label": "Integer.make",
+    "kind": 12,
+    "tags": [],
+    "detail": "int => t",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 98:21
+posCursor:[98:21] posNoWhite:[98:20] Found expr:[98:3->98:22]
+Pexp_apply ...[98:3->98:14] (...[98:15->98:21])
+posCursor:[98:21] posNoWhite:[98:20] Found expr:[98:15->98:21]
+Completable: Cpath Value[int]->t
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[int]->t
+ContextPath Value[int]
+Path int
+CPPipe env:CompletionPipeChain
+CPPipe type path:Integer.t
+CPPipe pathFromEnv:Integer found:true
+Path Integer.t
+[{
+    "label": "Integer.toInt",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": null
+  }]
+
+Complete src/CompletionPipeChain.res 103:8
+posCursor:[103:8] posNoWhite:[103:7] Found expr:[103:3->103:8]
+Completable: Cpath Value[r]->la
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[r]->la
+ContextPath Value[r]
+Path r
+CPPipe env:CompletionPipeChain
+Path Js.Re.la
+[{
+    "label": "Js.Re.lastIndex",
+    "kind": 12,
+    "tags": [],
+    "detail": "t => int",
+    "documentation": {"kind": "markdown", "value": "\nReturns the index where the next match will start its search. This property\nwill be modified when the RegExp object is used, if the global (\"g\") flag is\nset.\n\n## Examples\n\n```rescript\nlet re = /ab*TODO/g\nlet str = \"abbcdefabh\"\n\nlet break = ref(false)\nwhile !break.contents {\n  switch Js.Re.exec_(re, str) {\n  | Some(result) => Js.Nullable.iter(Js.Re.captures(result)[0], (. match_) => {\n      let next = Belt.Int.toString(Js.Re.lastIndex(re))\n      Js.log(\"Found \" ++ (match_ ++ (\". Next match starts at \" ++ next)))\n    })\n  | None => break := true\n  }\n}\n```\n\nSee\n[`RegExp: lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex)\non MDN.\n"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionPipeSubmodules.res.txt b/tests/analysis_tests/tests/src/expected/CompletionPipeSubmodules.res.txt
new file mode 100644
index 0000000000..f046f4142b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionPipeSubmodules.res.txt
@@ -0,0 +1,93 @@
+Complete src/CompletionPipeSubmodules.res 12:20
+posCursor:[12:20] posNoWhite:[12:19] Found expr:[12:11->20:8]
+Completable: Cpath Value[A, B1, xx]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[A, B1, xx]->
+ContextPath Value[A, B1, xx]
+Path A.B1.xx
+CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A.B1
+CPPipe type path:b1
+CPPipe pathFromEnv:A.B1 found:true
+Path A.B1.
+[{
+    "label": "A.B1.xx",
+    "kind": 12,
+    "tags": [],
+    "detail": "b1",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype b1 = B1\n```"}
+  }, {
+    "label": "A.B1.B1",
+    "kind": 4,
+    "tags": [],
+    "detail": "B1",
+    "documentation": {"kind": "markdown", "value": "```rescript\nB1\n```\n\n```rescript\ntype b1 = B1\n```"}
+  }]
+
+Complete src/CompletionPipeSubmodules.res 16:18
+posCursor:[16:18] posNoWhite:[16:17] Found expr:[16:11->20:8]
+Completable: Cpath Value[A, x].v->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[A, x].v->
+ContextPath Value[A, x].v
+ContextPath Value[A, x]
+Path A.x
+CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A
+CPPipe type path:B1.b1
+CPPipe pathFromEnv:A.B1 found:true
+Path A.B1.
+[{
+    "label": "A.B1.xx",
+    "kind": 12,
+    "tags": [],
+    "detail": "b1",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype b1 = B1\n```"}
+  }, {
+    "label": "A.B1.B1",
+    "kind": 4,
+    "tags": [],
+    "detail": "B1",
+    "documentation": {"kind": "markdown", "value": "```rescript\nB1\n```\n\n```rescript\ntype b1 = B1\n```"}
+  }]
+
+Complete src/CompletionPipeSubmodules.res 38:20
+posCursor:[38:20] posNoWhite:[38:19] Found expr:[38:11->0:-1]
+Completable: Cpath Value[E, e].v.v->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[E, e].v.v->
+ContextPath Value[E, e].v.v
+ContextPath Value[E, e].v
+ContextPath Value[E, e]
+Path E.e
+CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D
+CPPipe type path:C.t
+CPPipe pathFromEnv:C found:false
+Path C.
+[{
+    "label": "C.C",
+    "kind": 4,
+    "tags": [],
+    "detail": "C",
+    "documentation": {"kind": "markdown", "value": "```rescript\nC\n```\n\n```rescript\ntype t = C\n```"}
+  }]
+
+Complete src/CompletionPipeSubmodules.res 42:21
+posCursor:[42:21] posNoWhite:[42:20] Found expr:[42:11->0:-1]
+Completable: Cpath Value[E, e].v.v2->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[E, e].v.v2->
+ContextPath Value[E, e].v.v2
+ContextPath Value[E, e].v
+ContextPath Value[E, e]
+Path E.e
+CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D
+CPPipe type path:C2.t2
+CPPipe pathFromEnv:D.C2 found:true
+Path D.C2.
+[{
+    "label": "D.C2.C2",
+    "kind": 4,
+    "tags": [],
+    "detail": "C2",
+    "documentation": {"kind": "markdown", "value": "```rescript\nC2\n```\n\n```rescript\ntype t2 = C2\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionResolve.res.txt b/tests/analysis_tests/tests/src/expected/CompletionResolve.res.txt
new file mode 100644
index 0000000000..d0492d217b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionResolve.res.txt
@@ -0,0 +1,6 @@
+Completion resolve: Belt_Array
+"\nUtilities for `Array` functions.\n\n### Note about index syntax\n\nCode like `arr[0]` does *not* compile to JavaScript `arr[0]`. Reason transforms\nthe `[]` index syntax into a function: `Array.get(arr, 0)`. By default, this\nuses the default standard library's `Array.get` function, which may raise an\nexception if the index isn't found. If you `open Belt`, it will use the\n`Belt.Array.get` function which returns options instead of raising exceptions. \n[See this for more information](../belt.mdx#array-access-runtime-safety).\n"
+
+Completion resolve: ModuleStuff
+" This is a top level module doc. "
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionSupport.res.txt b/tests/analysis_tests/tests/src/expected/CompletionSupport.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/CompletionSupport2.res.txt b/tests/analysis_tests/tests/src/expected/CompletionSupport2.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/CompletionTypeAnnotation.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTypeAnnotation.res.txt
new file mode 100644
index 0000000000..12df6fe3b8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionTypeAnnotation.res.txt
@@ -0,0 +1,387 @@
+Complete src/CompletionTypeAnnotation.res 9:22
+XXX Not found!
+Completable: Cexpression Type[someRecord]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someRecord]
+Path someRecord
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someRecord = {age: int, name: string}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 12:24
+XXX Not found!
+Completable: Cexpression Type[someRecord]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someRecord]
+Path someRecord
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {age: int, name: string}\n```"}
+  }, {
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {age: int, name: string}\n```"}
+  }]
+
+Complete src/CompletionTypeAnnotation.res 15:23
+XXX Not found!
+Completable: Cexpression Type[someVariant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 18:25
+XXX Not found!
+Completable: Cexpression Type[someVariant]=O
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "sortText": "A One",
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Obj",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Obj",
+    "documentation": null,
+    "data": {
+      "modulePath": "Obj",
+      "filePath": "src/CompletionTypeAnnotation.res"
+    }
+  }, {
+    "label": "Object",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Object",
+    "documentation": null,
+    "data": {
+      "modulePath": "Object",
+      "filePath": "src/CompletionTypeAnnotation.res"
+    }
+  }, {
+    "label": "Objects",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Objects",
+    "documentation": null,
+    "data": {
+      "modulePath": "Objects",
+      "filePath": "src/CompletionTypeAnnotation.res"
+    }
+  }, {
+    "label": "Option",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Option",
+    "documentation": null,
+    "data": {
+      "modulePath": "Option",
+      "filePath": "src/CompletionTypeAnnotation.res"
+    }
+  }, {
+    "label": "Ordering",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Ordering",
+    "documentation": null,
+    "data": {
+      "modulePath": "Ordering",
+      "filePath": "src/CompletionTypeAnnotation.res"
+    }
+  }]
+
+Complete src/CompletionTypeAnnotation.res 21:27
+XXX Not found!
+Completable: Cexpression Type[somePolyVariant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[somePolyVariant]
+Path somePolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #two(bool)]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two(bool)\n```\n\n```rescript\n[#one | #two(bool)]\n```"},
+    "insertText": "#two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 24:30
+XXX Not found!
+Completable: Cexpression Type[somePolyVariant]=#o
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[somePolyVariant]
+Path somePolyVariant
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #two(bool)]\n```"},
+    "insertText": "one",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 29:20
+XXX Not found!
+Completable: Cexpression Type[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someFunc]
+Path someFunc
+[{
+    "label": "(v1, v2) => {}",
+    "kind": 12,
+    "tags": [],
+    "detail": "(int, string) => bool",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "(${1:v1}, ${2:v2}) => {${0:()}}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 34:21
+XXX Not found!
+Completable: Cexpression Type[someTuple]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someTuple]
+Path someTuple
+[{
+    "label": "(_, _)",
+    "kind": 12,
+    "tags": [],
+    "detail": "(bool, option<bool>)",
+    "documentation": null,
+    "insertText": "(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 37:28
+XXX Not found!
+Completable: Cexpression Type[someTuple]->tuple($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[someTuple]
+Path someTuple
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null,
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(true)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "Some(false)",
+    "kind": 4,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }]
+
+Complete src/CompletionTypeAnnotation.res 40:31
+XXX Not found!
+Completable: Cexpression option<Type[someVariant]>
+Package opens Pervasives.JsxModules.place holder
+ContextPath option<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two(bool)\n```"}
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Some($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(One)",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Some(One)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(Two(_))",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Some(Two($0))",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 43:37
+XXX Not found!
+Completable: Cexpression option<Type[someVariant]>->variantPayload::Some($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath option<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 46:30
+XXX Not found!
+Completable: Cexpression array<Type[someVariant]>
+Package opens Pervasives.JsxModules.place holder
+ContextPath array<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 49:32
+XXX Not found!
+Completable: Cexpression array<Type[someVariant]>->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath array<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Two($0)",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 52:38
+XXX Not found!
+Completable: Cexpression array<option<Type[someVariant]>>
+Package opens Pervasives.JsxModules.place holder
+ContextPath array<option<Type[someVariant]>>
+ContextPath option<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "[]",
+    "kind": 12,
+    "tags": [],
+    "detail": "option<someVariant>",
+    "documentation": {"kind": "markdown", "value": "```rescript\noption<someVariant>\n```"},
+    "sortText": "A",
+    "insertText": "[$0]",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeAnnotation.res 55:45
+XXX Not found!
+Completable: Cexpression option<array<Type[someVariant]>>->variantPayload::Some($0), array
+Package opens Pervasives.JsxModules.place holder
+ContextPath option<array<Type[someVariant]>>
+ContextPath array<Type[someVariant]>
+ContextPath Type[someVariant]
+Path someVariant
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool)\n```"},
+    "insertText": "Two($0)",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/CompletionTypeT.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTypeT.res.txt
new file mode 100644
index 0000000000..975d97d3e9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CompletionTypeT.res.txt
@@ -0,0 +1,110 @@
+Complete src/CompletionTypeT.res 4:26
+XXX Not found!
+Completable: Cpattern Value[date]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[date]
+Path date
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "Js.Date.t",
+    "documentation": null
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "Js.Date.t",
+    "documentation": null,
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(date)",
+    "kind": 4,
+    "tags": [],
+    "detail": "date",
+    "documentation": null,
+    "insertText": "Some(${0:date})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/CompletionTypeT.res 7:27
+XXX Not found!
+Completable: Cexpression Type[withDate]->recordField(date)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[withDate]
+Path withDate
+[{
+    "label": "Js.Date.makeWithYMD()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~year: float, ~month: float, ~date: float, unit) => t",
+    "documentation": null,
+    "insertText": "Js.Date.makeWithYMD($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.makeWithYMDHM()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  ~year: float,\n  ~month: float,\n  ~date: float,\n  ~hours: float,\n  ~minutes: float,\n  unit,\n) => t",
+    "documentation": null,
+    "insertText": "Js.Date.makeWithYMDHM($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.make()",
+    "kind": 12,
+    "tags": [],
+    "detail": "unit => t",
+    "documentation": null,
+    "insertText": "Js.Date.make($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.fromString()",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => t",
+    "documentation": null,
+    "insertText": "Js.Date.fromString($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.fromFloat()",
+    "kind": 12,
+    "tags": [],
+    "detail": "float => t",
+    "documentation": null,
+    "insertText": "Js.Date.fromFloat($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.parse()",
+    "kind": 12,
+    "tags": [],
+    "detail": "string => t",
+    "documentation": null,
+    "insertText": "Js.Date.parse($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.makeWithYM()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(~year: float, ~month: float, unit) => t",
+    "documentation": null,
+    "insertText": "Js.Date.makeWithYM($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.makeWithYMDHMS()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  ~year: float,\n  ~month: float,\n  ~date: float,\n  ~hours: float,\n  ~minutes: float,\n  ~seconds: float,\n  unit,\n) => t",
+    "documentation": null,
+    "insertText": "Js.Date.makeWithYMDHMS($0)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Js.Date.makeWithYMDH()",
+    "kind": 12,
+    "tags": [],
+    "detail": "(\n  ~year: float,\n  ~month: float,\n  ~date: float,\n  ~hours: float,\n  unit,\n) => t",
+    "documentation": null,
+    "insertText": "Js.Date.makeWithYMDH($0)",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/Component.res.txt b/tests/analysis_tests/tests/src/expected/Component.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/Component.resi.txt b/tests/analysis_tests/tests/src/expected/Component.resi.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt
new file mode 100644
index 0000000000..4e12129485
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt
@@ -0,0 +1,112 @@
+Create Interface src/CreateInterface.res
+type r = {name: string, age: int}
+let add: (~x: int, ~y: int) => int
+@react.component
+let make: (~name: string) => React.element
+module Other: {
+  @react.component
+  let otherComponentName: (~name: string) => React.element
+}
+module Mod: {
+  @react.component
+  let make: (~name: string) => React.element
+}
+module type ModTyp = {
+  @react.component
+  let make: (~name: string) => React.element
+}
+@module("path") external dirname: string => string = "dirname"
+@module("path") @variadic
+external join: array<string> => string = "join"
+@val
+external padLeft: (
+  string,
+  @unwrap
+  [
+    | #Str(string)
+    | #Int(int)
+  ],
+) => string = "padLeft"
+@inline
+let f1: int
+@inline let f2: string
+@genType @inline
+let f3: int
+@genType @inline
+let f4: string
+@genType @inline let f5: float
+module RFS: {
+  @module("fs")
+  external readFileSync: (
+    ~name: string,
+    @string
+    [
+      | #utf8
+      | @as("ascii") #useAscii
+    ],
+  ) => string = "readFileSync"
+}
+module Functor: () =>
+{
+  @react.component
+  let make: unit => React.element
+}
+module type FT = {
+  module Functor: (
+    X: {
+      let a: int
+      @react.component
+      let make: (~name: string) => React.element
+      let b: int
+    },
+    Y: ModTyp,
+  ) =>
+  {
+    @react.component
+    let make: (~name: string) => React.element
+  }
+}
+module NormaList = List
+module BeltList = Belt.List
+module type MT2 = ModTyp
+module rec RM: ModTyp
+and D: ModTyp
+module type OptT = {
+  @react.component
+  let withOpt1: (~x: int=?, ~y: int) => int
+  module type Opt2 = {
+    @react.component
+    let withOpt2: (~x: int=?, ~y: int) => int
+  }
+  module type Opt3 = {
+    @react.component
+    let withOpt3: (~x: option<int>, ~y: int) => int
+  }
+}
+module Opt: {
+  @react.component
+  let withOpt1: (~x: int=?, ~y: int) => int
+  module Opt2: {
+    @react.component
+    let withOpt2: (~x: int=?, ~y: int) => int
+  }
+  module type Opt2 = {
+    @react.component
+    let withOpt2: (~x: int=?, ~y: int) => int
+  }
+  module Opt3: {
+    @react.component
+    let withOpt3: (~x: option<int>, ~y: int) => int
+  }
+  module type Opt3 = {
+    @react.component
+    let withOpt3: (~x: option<int>, ~y: int) => int
+  }
+}
+module Opt2: OptT
+module Opt3 = Opt
+module Memo: {
+  @react.component
+  let make: (~name: string) => React.element
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/Cross.res.txt b/tests/analysis_tests/tests/src/expected/Cross.res.txt
new file mode 100644
index 0000000000..aa2d598ff4
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Cross.res.txt
@@ -0,0 +1,107 @@
+References src/Cross.res 0:17
+[
+{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}},
+{"uri": "Cross.res", "range": {"start": {"line": 3, "character": 16}, "end": {"line": 3, "character": 26}}},
+{"uri": "Cross.res", "range": {"start": {"line": 5, "character": 13}, "end": {"line": 5, "character": 23}}},
+{"uri": "Cross.res", "range": {"start": {"line": 7, "character": 16}, "end": {"line": 7, "character": 26}}},
+{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
+]
+
+References src/Cross.res 9:31
+[
+{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 28}, "end": {"line": 9, "character": 51}}},
+{"uri": "Cross.res", "range": {"start": {"line": 12, "character": 29}, "end": {"line": 12, "character": 52}}},
+{"uri": "Cross.res", "range": {"start": {"line": 14, "character": 26}, "end": {"line": 14, "character": 49}}},
+{"uri": "Cross.res", "range": {"start": {"line": 16, "character": 29}, "end": {"line": 16, "character": 52}}},
+{"uri": "ReferencesWithInterface.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}},
+{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
+]
+
+Rename src/Cross.res 18:13 RenameWithInterfacePrime
+[
+{
+  "kind": "rename",
+  "oldUri": "RenameWithInterface.resi",
+  "newUri": "RenameWithInterfacePrime.resi"
+},
+{
+  "kind": "rename",
+  "oldUri": "RenameWithInterface.res",
+  "newUri": "RenameWithInterfacePrime.res"
+},
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Cross.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 18, "character": 8}, "end": {"line": 18, "character": 27}},
+  "newText": "RenameWithInterfacePrime"
+  }, {
+  "range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 27}},
+  "newText": "RenameWithInterfacePrime"
+  }]
+  }
+]
+
+Rename src/Cross.res 21:28 xPrime
+[
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.resi"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "xPrime"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "xPrime"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Cross.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}},
+  "newText": "xPrime"
+  }, {
+  "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}},
+  "newText": "xPrime"
+  }]
+  }
+]
+
+TypeDefinition src/Cross.res 24:5
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 28}}}
+
+Definition src/Cross.res 27:32
+{"uri": "DefinitionWithInterface.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}}
+
+Definition src/Cross.res 30:36
+{"uri": "DefinitionWithInterface.res", "range": {"start": {"line": 3, "character": 5}, "end": {"line": 3, "character": 6}}}
+
+TypeDefinition src/Cross.res 33:37
+{"uri": "DefinitionWithInterface.resi", "range": {"start": {"line": 3, "character": 0}, "end": {"line": 3, "character": 6}}}
+
+Complete src/Cross.res 36:28
+posCursor:[36:28] posNoWhite:[36:27] Found expr:[36:3->36:28]
+Pexp_ident DefinitionWithInterface.a:[36:3->36:28]
+Completable: Cpath Value[DefinitionWithInterface, a]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[DefinitionWithInterface, a]
+Path DefinitionWithInterface.a
+[]
+
+Definition src/Cross.res 39:39
+{"uri": "DefinitionWithInterface.res", "range": {"start": {"line": 9, "character": 6}, "end": {"line": 9, "character": 7}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/Dce.res.txt b/tests/analysis_tests/tests/src/expected/Dce.res.txt
new file mode 100644
index 0000000000..58c835d7a2
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Dce.res.txt
@@ -0,0 +1,3 @@
+DCE src/Dce.res
+issues:1
+
diff --git a/tests/analysis_tests/tests/src/expected/Debug.res.txt b/tests/analysis_tests/tests/src/expected/Debug.res.txt
new file mode 100644
index 0000000000..e79d5485d9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Debug.res.txt
@@ -0,0 +1,27 @@
+Definition src/Debug.res 2:27
+{"uri": "ShadowedBelt.res", "range": {"start": {"line": 1, "character": 6}, "end": {"line": 1, "character": 9}}}
+
+Complete src/Debug.res 11:8
+posCursor:[11:8] posNoWhite:[11:7] Found expr:[11:5->11:8]
+Pexp_ident eqN:[11:5->11:8]
+Completable: Cpath Value[eqN]
+Raw opens: 1 Js.place holder
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 Js
+ContextPath Value[eqN]
+Path eqN
+[{
+    "label": "eqNullable",
+    "kind": 12,
+    "tags": [],
+    "detail": "('a, nullable<'a>) => bool",
+    "documentation": null
+  }, {
+    "label": "eqNull",
+    "kind": 12,
+    "tags": [],
+    "detail": "('a, null<'a>) => bool",
+    "documentation": null
+  }]
+
+
diff --git a/tests/analysis_tests/tests/src/expected/Definition.res.txt b/tests/analysis_tests/tests/src/expected/Definition.res.txt
new file mode 100644
index 0000000000..024a9d7ba6
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Definition.res.txt
@@ -0,0 +1,18 @@
+Definition src/Definition.res 2:8
+{"uri": "Definition.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 6}}}
+
+Definition src/Definition.res 10:23
+{"uri": "Definition.res", "range": {"start": {"line": 6, "character": 7}, "end": {"line": 6, "character": 13}}}
+
+Hover src/Definition.res 14:14
+{"contents": {"kind": "markdown", "value": "```rescript\n(List.t<'a>, 'a => 'b) => List.t<'b>\n```\n\n---\n\n```\n \n```\n```rescript\ntype List.t<'a> = list<'a>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22List.res%22%2C34%2C0%5D)\n\n---\n\n`map(list, f)` returns a new list with `f` applied to each element of `list`.\n\n## Examples\n\n```rescript\nlist{1, 2}->List.map(x => x + 1) // list{3, 4}\n```\n"}}
+
+Hover src/Definition.res 18:14
+{"contents": {"kind": "markdown", "value": "```rescript\n('a => 'b, List.t<'a>) => List.t<'b>\n```\n\n---\n\n```\n \n```\n```rescript\ntype List.t<'a> = list<'a>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22List.res%22%2C34%2C0%5D)\n"}}
+
+Hover src/Definition.res 23:3
+{"contents": {"kind": "markdown", "value": "```rescript\n(int, int) => int\n```"}}
+
+Definition src/Definition.res 26:3
+{"uri": "Definition.res", "range": {"start": {"line": 21, "character": 4}, "end": {"line": 21, "character": 13}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.res.txt b/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.res.txt
new file mode 100644
index 0000000000..f8d85032d2
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.res.txt
@@ -0,0 +1,6 @@
+Definition src/DefinitionWithInterface.res 0:4
+{"uri": "DefinitionWithInterface.resi", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}}
+
+Definition src/DefinitionWithInterface.res 9:6
+{"uri": "DefinitionWithInterface.resi", "range": {"start": {"line": 6, "character": 2}, "end": {"line": 6, "character": 12}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.resi.txt b/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.resi.txt
new file mode 100644
index 0000000000..10bc343390
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DefinitionWithInterface.resi.txt
@@ -0,0 +1,6 @@
+Definition src/DefinitionWithInterface.resi 0:4
+{"uri": "DefinitionWithInterface.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}}
+
+Definition src/DefinitionWithInterface.resi 6:6
+{"uri": "DefinitionWithInterface.res", "range": {"start": {"line": 9, "character": 6}, "end": {"line": 9, "character": 7}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/Destructuring.res.txt b/tests/analysis_tests/tests/src/expected/Destructuring.res.txt
new file mode 100644
index 0000000000..3cafe0a912
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Destructuring.res.txt
@@ -0,0 +1,94 @@
+Complete src/Destructuring.res 4:11
+posCursor:[4:11] posNoWhite:[4:9] Found pattern:[4:4->4:12]
+Completable: Cpattern Value[x]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }]
+
+Complete src/Destructuring.res 7:8
+posCursor:[7:8] posNoWhite:[7:7] Found pattern:[7:7->7:9]
+Completable: Cpattern Value[x]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }]
+
+Complete src/Destructuring.res 11:13
+posCursor:[11:13] posNoWhite:[11:11] Found expr:[10:8->14:1]
+posCursor:[11:13] posNoWhite:[11:11] Found expr:[10:9->14:1]
+posCursor:[11:13] posNoWhite:[11:11] Found expr:[11:2->13:6]
+posCursor:[11:13] posNoWhite:[11:11] Found pattern:[11:6->11:14]
+Completable: Cpattern Value[x]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }]
+
+Complete src/Destructuring.res 17:10
+posCursor:[17:10] posNoWhite:[17:9] Found expr:[16:9->20:1]
+posCursor:[17:10] posNoWhite:[17:9] Found expr:[16:10->20:1]
+posCursor:[17:10] posNoWhite:[17:9] Found expr:[17:5->19:11]
+posCursor:[17:10] posNoWhite:[17:9] Found pattern:[17:9->17:11]
+Completable: Cpattern Value[x]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }, {
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype x = {name: string, age: int}\n```"}
+  }]
+
+Complete src/Destructuring.res 31:8
+posCursor:[31:8] posNoWhite:[31:7] Found pattern:[31:7->31:9]
+Completable: Cpattern Value[x]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]
+Path x
+[{
+    "label": "someField",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nsomeField: int\n```\n\n```rescript\ntype recordWithOptField = {someField: int, someOptField: option<bool>}\n```"}
+  }, {
+    "label": "?someOptField",
+    "kind": 5,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": {"kind": "markdown", "value": "someOptField is an optional field, and needs to be destructured using '?'.\n\n```rescript\n?someOptField: option<bool>\n```\n\n```rescript\ntype recordWithOptField = {someField: int, someOptField: option<bool>}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/Div.res.txt b/tests/analysis_tests/tests/src/expected/Div.res.txt
new file mode 100644
index 0000000000..dd4336444a
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Div.res.txt
@@ -0,0 +1,18 @@
+Hover src/Div.res 0:10
+{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
+
+Complete src/Div.res 3:17
+posCursor:[3:17] posNoWhite:[3:16] Found expr:[3:4->3:17]
+JSX <div:[3:4->3:7] dangerous[3:8->3:17]=...[3:8->3:17]> _children:None
+Completable: Cjsx([div], dangerous, [dangerous])
+Package opens Pervasives.JsxModules.place holder
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[{
+    "label": "dangerouslySetInnerHTML",
+    "kind": 4,
+    "tags": [],
+    "detail": "{\"__html\": string}",
+    "documentation": null
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/DocComments.res.txt b/tests/analysis_tests/tests/src/expected/DocComments.res.txt
new file mode 100644
index 0000000000..1f8ab304de
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DocComments.res.txt
@@ -0,0 +1,15 @@
+Hover src/DocComments.res 9:9
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n  Doc comment with a triple-backquote example\\n  \\n  ```res example\\n    let a = 10\\n    /*\\n     * stuff\\n     */\\n  ```\\n"}}
+
+Hover src/DocComments.res 22:6
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n\n  Doc comment with a triple-backquote example\n  \n  ```res example\n    let a = 10\n    /*\n     * stuff\n     */\n  ```\n"}}
+
+Hover src/DocComments.res 33:9
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n  Doc comment with a triple-backquote example\\n  \\n  ```res example\\n    let a = 10\\n    let b = 20\\n  ```\\n"}}
+
+Hover src/DocComments.res 44:6
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n\n  Doc comment with a triple-backquote example\n  \n  ```res example\n    let a = 10\n    let b = 20\n  ```\n"}}
+
+Hover src/DocComments.res 48:5
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\nNew doc comment format"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/DocExtraction2.res.txt b/tests/analysis_tests/tests/src/expected/DocExtraction2.res.txt
new file mode 100644
index 0000000000..74f2e9cc1c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DocExtraction2.res.txt
@@ -0,0 +1,45 @@
+Documentation extraction src/DocExtraction2.res
+extracting docs for src/DocExtraction2.res
+preferring found resi file for impl: src/DocExtraction2.resi
+
+{
+  "name": "DocExtraction2",
+  "docstrings": ["Module level doc here."],
+  "items": [
+  {
+    "id": "DocExtraction2.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t",
+    "docstrings": ["Type t is pretty cool."]
+  }, 
+  {
+    "id": "DocExtraction2.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: unit => t",
+    "docstrings": ["Makerz of stuffz."]
+  }, 
+  {
+    "id": "DocExtraction2.InnerModule",
+    "name": "InnerModule",
+    "kind": "module",
+    "docstrings": [],
+    "items": [
+    {
+      "id": "DocExtraction2.InnerModule.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["This type is also t."]
+    }, 
+    {
+      "id": "DocExtraction2.InnerModule.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["Maker of tea."]
+    }]
+  }]
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/DocExtraction2.resi.txt b/tests/analysis_tests/tests/src/expected/DocExtraction2.resi.txt
new file mode 100644
index 0000000000..074ce4ccf1
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DocExtraction2.resi.txt
@@ -0,0 +1,44 @@
+Documentation extraction src/DocExtraction2.resi
+extracting docs for src/DocExtraction2.resi
+
+{
+  "name": "DocExtraction2",
+  "docstrings": ["Module level doc here."],
+  "items": [
+  {
+    "id": "DocExtraction2.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t",
+    "docstrings": ["Type t is pretty cool."]
+  }, 
+  {
+    "id": "DocExtraction2.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: unit => t",
+    "docstrings": ["Makerz of stuffz."]
+  }, 
+  {
+    "id": "DocExtraction2.InnerModule",
+    "name": "InnerModule",
+    "kind": "module",
+    "docstrings": [],
+    "items": [
+    {
+      "id": "DocExtraction2.InnerModule.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["This type is also t."]
+    }, 
+    {
+      "id": "DocExtraction2.InnerModule.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["Maker of tea."]
+    }]
+  }]
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/DocExtractionRes.res.txt b/tests/analysis_tests/tests/src/expected/DocExtractionRes.res.txt
new file mode 100644
index 0000000000..a197ecf562
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DocExtractionRes.res.txt
@@ -0,0 +1,187 @@
+Documentation extraction src/DocExtractionRes.res
+extracting docs for src/DocExtractionRes.res
+
+{
+  "name": "DocExtractionRes",
+  "docstrings": ["Module level documentation goes here."],
+  "items": [
+  {
+    "id": "DocExtractionRes.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t = {name: string, online: bool}",
+    "docstrings": ["This type represents stuff."],
+    "detail": 
+    {
+      "kind": "record",
+      "items": [{
+        "name": "name",
+        "optional": false,
+        "docstrings": ["The name of the stuff."],
+        "signature": "string"
+      }, {
+        "name": "online",
+        "optional": false,
+        "docstrings": ["Whether stuff is online."],
+        "signature": "bool"
+      }]
+    }
+  }, 
+  {
+    "id": "DocExtractionRes.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: string => t",
+    "docstrings": ["Create stuff.\n\n```rescript example\nlet stuff = make(\"My name\")\n```"]
+  }, 
+  {
+    "id": "DocExtractionRes.asOffline",
+    "kind": "value",
+    "name": "asOffline",
+    "signature": "let asOffline: t => t",
+    "docstrings": ["Stuff goes offline."]
+  }, 
+  {
+    "id": "DocExtractionRes.SomeConstant\\",
+    "kind": "value",
+    "name": "SomeConstant\\",
+    "signature": "let SomeConstant\\: int",
+    "docstrings": ["exotic identifier"]
+  }, 
+  {
+    "id": "DocExtractionRes.SomeInnerModule",
+    "name": "SomeInnerModule",
+    "kind": "module",
+    "docstrings": ["Another module level docstring here."],
+    "items": [
+    {
+      "id": "DocExtractionRes.SomeInnerModule.status",
+      "kind": "type",
+      "name": "status",
+      "signature": "type status = Started(t) | Stopped | Idle",
+      "docstrings": [],
+      "detail": 
+      {
+        "kind": "variant",
+        "items": [
+        {
+          "name": "Started",
+          "docstrings": ["If this is started or not"],
+          "signature": "Started(t)"
+        }, 
+        {
+          "name": "Stopped",
+          "docstrings": ["Stopped?"],
+          "signature": "Stopped"
+        }, 
+        {
+          "name": "Idle",
+          "docstrings": ["Now idle."],
+          "signature": "Idle"
+        }]
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.SomeInnerModule.validInputs",
+      "kind": "type",
+      "name": "validInputs",
+      "signature": "type validInputs = [\n  | #\"needs-escaping\"\n  | #something\n  | #status(status)\n  | #withPayload(int)\n]",
+      "docstrings": ["These are all the valid inputs."]
+    }, 
+    {
+      "id": "DocExtractionRes.SomeInnerModule.callback",
+      "kind": "type",
+      "name": "callback",
+      "signature": "type callback = (t, ~status: status) => unit",
+      "docstrings": []
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.AnotherModule",
+    "name": "AnotherModule",
+    "kind": "module",
+    "docstrings": ["Mighty fine module here too!"],
+    "items": [
+    {
+      "id": "DocExtractionRes.LinkedModule",
+      "kind": "moduleAlias",
+      "name": "LinkedModule",
+      "docstrings": ["This links another module. Neat."],
+      "items": []
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.callback",
+      "kind": "type",
+      "name": "callback",
+      "signature": "type callback = SomeInnerModule.status => unit",
+      "docstrings": ["Testing what this looks like."]
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.isGoodStatus",
+      "kind": "value",
+      "name": "isGoodStatus",
+      "signature": "let isGoodStatus: SomeInnerModule.status => bool",
+      "docstrings": []
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.someVariantWithInlineRecords",
+      "kind": "type",
+      "name": "someVariantWithInlineRecords",
+      "signature": "type someVariantWithInlineRecords =\n  | SomeStuff({offline: bool, online?: bool})",
+      "docstrings": ["Trying how it looks with an inline record in a variant."],
+      "detail": 
+      {
+        "kind": "variant",
+        "items": [
+        {
+          "name": "SomeStuff",
+          "docstrings": ["This has inline records..."],
+          "signature": "SomeStuff({offline: bool, online?: bool})",
+          "payload": {
+            "kind": "inlineRecord",
+            "fields": [{
+              "name": "offline",
+              "optional": false,
+              "docstrings": [],
+              "signature": "bool"
+            }, {
+              "name": "online",
+              "optional": true,
+              "docstrings": ["Is the user online?"],
+              "signature": "option<bool>"
+            }]
+          }
+        }]
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.domRoot",
+      "kind": "type",
+      "name": "domRoot",
+      "signature": "type domRoot = unit => ReactDOM.Client.Root.t",
+      "docstrings": ["Callback to get the DOM root..."]
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported",
+    "name": "ModuleWithThingsThatShouldNotBeExported",
+    "kind": "module",
+    "docstrings": [],
+    "items": [
+    {
+      "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["The type t is stuff."]
+    }, 
+    {
+      "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["The maker of stuff!"]
+    }]
+  }]
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt b/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt
new file mode 100644
index 0000000000..561093483a
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/DocumentSymbol.res.txt
@@ -0,0 +1,105 @@
+DocumentSymbol src/DocumentSymbol.res
+[
+{
+  "name": "MyList",
+  "kind": 2,
+  "range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 25}},
+  "selectionRange": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 25}}
+},
+{
+  "name": "Dep",
+  "kind": 2,
+  "range": {"start": {"line": 2, "character": 7}, "end": {"line": 7, "character": 1}},
+  "selectionRange": {"start": {"line": 2, "character": 7}, "end": {"line": 7, "character": 1}},
+  "children": [
+  {
+    "name": "customDouble",
+    "kind": 13,
+    "range": {"start": {"line": 6, "character": 2}, "end": {"line": 6, "character": 35}},
+    "selectionRange": {"start": {"line": 6, "character": 2}, "end": {"line": 6, "character": 35}}
+  }]
+},
+{
+  "name": "Lib",
+  "kind": 2,
+  "range": {"start": {"line": 9, "character": 7}, "end": {"line": 12, "character": 1}},
+  "selectionRange": {"start": {"line": 9, "character": 7}, "end": {"line": 12, "character": 1}},
+  "children": [
+  {
+    "name": "foo",
+    "kind": 13,
+    "range": {"start": {"line": 10, "character": 2}, "end": {"line": 10, "character": 54}},
+    "selectionRange": {"start": {"line": 10, "character": 2}, "end": {"line": 10, "character": 54}}
+  },
+  {
+    "name": "next",
+    "kind": 13,
+    "range": {"start": {"line": 11, "character": 2}, "end": {"line": 11, "character": 48}},
+    "selectionRange": {"start": {"line": 11, "character": 2}, "end": {"line": 11, "character": 48}}
+  }]
+},
+{
+  "name": "op",
+  "kind": 13,
+  "range": {"start": {"line": 14, "character": 0}, "end": {"line": 14, "character": 16}},
+  "selectionRange": {"start": {"line": 14, "character": 0}, "end": {"line": 14, "character": 16}}
+},
+{
+  "name": "ForAuto",
+  "kind": 2,
+  "range": {"start": {"line": 16, "character": 7}, "end": {"line": 20, "character": 1}},
+  "selectionRange": {"start": {"line": 16, "character": 7}, "end": {"line": 20, "character": 1}},
+  "children": [
+  {
+    "name": "t",
+    "kind": 26,
+    "range": {"start": {"line": 17, "character": 2}, "end": {"line": 17, "character": 14}},
+    "selectionRange": {"start": {"line": 17, "character": 2}, "end": {"line": 17, "character": 14}}
+  },
+  {
+    "name": "abc",
+    "kind": 13,
+    "range": {"start": {"line": 18, "character": 2}, "end": {"line": 18, "character": 32}},
+    "selectionRange": {"start": {"line": 18, "character": 2}, "end": {"line": 18, "character": 32}}
+  },
+  {
+    "name": "abd",
+    "kind": 13,
+    "range": {"start": {"line": 19, "character": 2}, "end": {"line": 19, "character": 32}},
+    "selectionRange": {"start": {"line": 19, "character": 2}, "end": {"line": 19, "character": 32}}
+  }]
+},
+{
+  "name": "fa",
+  "kind": 16,
+  "range": {"start": {"line": 22, "character": 0}, "end": {"line": 22, "character": 22}},
+  "selectionRange": {"start": {"line": 22, "character": 0}, "end": {"line": 22, "character": 22}}
+},
+{
+  "name": "O",
+  "kind": 2,
+  "range": {"start": {"line": 24, "character": 7}, "end": {"line": 29, "character": 1}},
+  "selectionRange": {"start": {"line": 24, "character": 7}, "end": {"line": 29, "character": 1}},
+  "children": [
+  {
+    "name": "Comp",
+    "kind": 2,
+    "range": {"start": {"line": 25, "character": 9}, "end": {"line": 28, "character": 3}},
+    "selectionRange": {"start": {"line": 25, "character": 9}, "end": {"line": 28, "character": 3}},
+    "children": [
+    {
+      "name": "make",
+      "kind": 13,
+      "range": {"start": {"line": 26, "character": 4}, "end": {"line": 27, "character": 97}},
+      "selectionRange": {"start": {"line": 26, "character": 4}, "end": {"line": 27, "character": 97}}
+    }]
+  }]
+},
+{
+  "name": "zzz",
+  "kind": 16,
+  "range": {"start": {"line": 31, "character": 0}, "end": {"line": 31, "character": 12}},
+  "selectionRange": {"start": {"line": 31, "character": 0}, "end": {"line": 31, "character": 12}}
+}
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/EnvCompletion.res.txt b/tests/analysis_tests/tests/src/expected/EnvCompletion.res.txt
new file mode 100644
index 0000000000..3fdaf5c010
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/EnvCompletion.res.txt
@@ -0,0 +1,364 @@
+Complete src/EnvCompletion.res 10:17
+XXX Not found!
+Completable: Cpattern Value[res]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "Okay(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Okay('a)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOkay('a)\n```\n\n```rescript\ntype someResult<'a, 'b> = Okay('a) | Failure('b)\n```"},
+    "insertText": "Okay(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Failure(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Failure('b)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFailure('b)\n```\n\n```rescript\ntype someResult<'a, 'b> = Okay('a) | Failure('b)\n```"},
+    "insertText": "Failure(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 13:23
+posCursor:[13:23] posNoWhite:[13:22] Found pattern:[13:18->13:24]
+Ppat_construct Okay:[13:18->13:22]
+posCursor:[13:23] posNoWhite:[13:22] Found pattern:[13:22->13:24]
+Ppat_construct ():[13:22->13:24]
+Completable: Cpattern Value[res]->variantPayload::Okay($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype things = One | Two\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo\n```\n\n```rescript\ntype things = One | Two\n```"},
+    "insertText": "Two",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 16:26
+posCursor:[16:26] posNoWhite:[16:25] Found pattern:[16:18->16:27]
+Ppat_construct Failure:[16:18->16:25]
+posCursor:[16:26] posNoWhite:[16:25] Found pattern:[16:25->16:27]
+Ppat_construct ():[16:25->16:27]
+Completable: Cpattern Value[res]->variantPayload::Failure($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "\"$0\"",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 19:19
+XXX Not found!
+Completable: Cpattern Value[use](Nolabel)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "EnvCompletionOtherFile.response",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype EnvCompletionOtherFile.response = {stuff: theVariant, res: someResult<theVariant, string>}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 22:21
+posCursor:[22:21] posNoWhite:[22:20] Found pattern:[22:20->22:22]
+Completable: Cpattern Value[use](Nolabel)->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "stuff",
+    "kind": 5,
+    "tags": [],
+    "detail": "theVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\nstuff: theVariant\n```\n\n```rescript\ntype EnvCompletionOtherFile.response = {stuff: theVariant, res: someResult<theVariant, string>}\n```"}
+  }, {
+    "label": "res",
+    "kind": 5,
+    "tags": [],
+    "detail": "someResult<theVariant, string>",
+    "documentation": {"kind": "markdown", "value": "```rescript\nres: someResult<theVariant, string>\n```\n\n```rescript\ntype EnvCompletionOtherFile.response = {stuff: theVariant, res: someResult<theVariant, string>}\n```"}
+  }]
+
+Complete src/EnvCompletion.res 25:27
+posCursor:[25:27] posNoWhite:[25:26] Found pattern:[25:20->25:31]
+Completable: Cpattern Value[use](Nolabel)->recordField(stuff)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "First",
+    "kind": 4,
+    "tags": [],
+    "detail": "First",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFirst\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "First",
+    "insertTextFormat": 2
+  }, {
+    "label": "Second(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Second(r1)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nSecond(r1)\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "Second(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 28:35
+posCursor:[28:35] posNoWhite:[28:34] Found pattern:[28:20->28:38]
+posCursor:[28:35] posNoWhite:[28:34] Found pattern:[28:28->28:36]
+Ppat_construct Second:[28:28->28:34]
+posCursor:[28:35] posNoWhite:[28:34] Found pattern:[28:34->28:36]
+Ppat_construct ():[28:34->28:36]
+Completable: Cpattern Value[use](Nolabel)->recordField(stuff), variantPayload::Second($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "r1",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype r1 = {age: int}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 31:36
+posCursor:[31:36] posNoWhite:[31:35] Found pattern:[31:20->31:40]
+posCursor:[31:36] posNoWhite:[31:35] Found pattern:[31:28->31:38]
+Ppat_construct Second:[31:28->31:34]
+posCursor:[31:36] posNoWhite:[31:35] Found pattern:[31:35->31:37]
+Completable: Cpattern Value[use](Nolabel)->recordField(stuff), variantPayload::Second($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype r1 = {age: int}\n```"}
+  }]
+
+Complete src/EnvCompletion.res 34:25
+posCursor:[34:25] posNoWhite:[34:24] Found pattern:[34:20->34:29]
+Completable: Cpattern Value[use](Nolabel)->recordField(res)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "Okay(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Okay('a)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOkay('a)\n```\n\n```rescript\ntype someResult<'a, 'b> = Okay('a) | Failure('b)\n```"},
+    "insertText": "Okay(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Failure(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Failure('b)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFailure('b)\n```\n\n```rescript\ntype someResult<'a, 'b> = Okay('a) | Failure('b)\n```"},
+    "insertText": "Failure(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 37:31
+posCursor:[37:31] posNoWhite:[37:30] Found pattern:[37:20->37:34]
+posCursor:[37:31] posNoWhite:[37:30] Found pattern:[37:26->37:32]
+Ppat_construct Okay:[37:26->37:30]
+posCursor:[37:31] posNoWhite:[37:30] Found pattern:[37:30->37:32]
+Ppat_construct ():[37:30->37:32]
+Completable: Cpattern Value[use](Nolabel)->recordField(res), variantPayload::Okay($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "First",
+    "kind": 4,
+    "tags": [],
+    "detail": "First",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFirst\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "First",
+    "insertTextFormat": 2
+  }, {
+    "label": "Second(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Second(r1)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nSecond(r1)\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "Second(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 40:38
+posCursor:[40:38] posNoWhite:[40:37] Found pattern:[40:20->40:42]
+posCursor:[40:38] posNoWhite:[40:37] Found pattern:[40:26->40:40]
+Ppat_construct Okay:[40:26->40:30]
+posCursor:[40:38] posNoWhite:[40:37] Found pattern:[40:31->40:39]
+Ppat_construct Second:[40:31->40:37]
+posCursor:[40:38] posNoWhite:[40:37] Found pattern:[40:37->40:39]
+Ppat_construct ():[40:37->40:39]
+Completable: Cpattern Value[use](Nolabel)->recordField(res), variantPayload::Okay($0), variantPayload::Second($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "r1",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype r1 = {age: int}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 43:39
+posCursor:[43:39] posNoWhite:[43:38] Found pattern:[43:20->43:44]
+posCursor:[43:39] posNoWhite:[43:38] Found pattern:[43:26->43:42]
+Ppat_construct Okay:[43:26->43:30]
+posCursor:[43:39] posNoWhite:[43:38] Found pattern:[43:31->43:41]
+Ppat_construct Second:[43:31->43:37]
+posCursor:[43:39] posNoWhite:[43:38] Found pattern:[43:38->43:40]
+Completable: Cpattern Value[use](Nolabel)->recordField(res), variantPayload::Okay($0), variantPayload::Second($0), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[use](Nolabel)
+ContextPath Value[use]
+Path use
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype r1 = {age: int}\n```"}
+  }]
+
+Complete src/EnvCompletion.res 52:18
+XXX Not found!
+Completable: Cpattern Value[res2]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res2]
+Path res2
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "EnvCompletionOtherFile.someRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype EnvCompletionOtherFile.someRecord = {name: string, theThing: 'thing, theVariant: theVariant}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 55:20
+posCursor:[55:20] posNoWhite:[55:19] Found pattern:[55:19->55:21]
+Completable: Cpattern Value[res2]->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res2]
+Path res2
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype EnvCompletionOtherFile.someRecord = {name: string, theThing: 'thing, theVariant: theVariant}\n```"}
+  }, {
+    "label": "theThing",
+    "kind": 5,
+    "tags": [],
+    "detail": "'thing",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntheThing: 'thing\n```\n\n```rescript\ntype EnvCompletionOtherFile.someRecord = {name: string, theThing: 'thing, theVariant: theVariant}\n```"}
+  }, {
+    "label": "theVariant",
+    "kind": 5,
+    "tags": [],
+    "detail": "theVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntheVariant: theVariant\n```\n\n```rescript\ntype EnvCompletionOtherFile.someRecord = {name: string, theThing: 'thing, theVariant: theVariant}\n```"}
+  }]
+
+Complete src/EnvCompletion.res 58:29
+posCursor:[58:29] posNoWhite:[58:28] Found pattern:[58:19->58:33]
+Completable: Cpattern Value[res2]->recordField(theThing)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res2]
+Path res2
+[{
+    "label": "Four",
+    "kind": 4,
+    "tags": [],
+    "detail": "Four",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFour\n```\n\n```rescript\ntype things2 = Four | Five\n```"},
+    "insertText": "Four",
+    "insertTextFormat": 2
+  }, {
+    "label": "Five",
+    "kind": 4,
+    "tags": [],
+    "detail": "Five",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFive\n```\n\n```rescript\ntype things2 = Four | Five\n```"},
+    "insertText": "Five",
+    "insertTextFormat": 2
+  }]
+
+Complete src/EnvCompletion.res 61:31
+posCursor:[61:31] posNoWhite:[61:30] Found pattern:[61:19->61:35]
+Completable: Cpattern Value[res2]->recordField(theVariant)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res2]
+Path res2
+[{
+    "label": "First",
+    "kind": 4,
+    "tags": [],
+    "detail": "First",
+    "documentation": {"kind": "markdown", "value": "```rescript\nFirst\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "First",
+    "insertTextFormat": 2
+  }, {
+    "label": "Second(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Second(r1)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nSecond(r1)\n```\n\n```rescript\ntype theVariant = First | Second(r1)\n```"},
+    "insertText": "Second(${1:_})",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/EnvCompletionOtherFile.res.txt b/tests/analysis_tests/tests/src/expected/EnvCompletionOtherFile.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/ExhaustiveSwitch.res.txt b/tests/analysis_tests/tests/src/expected/ExhaustiveSwitch.res.txt
new file mode 100644
index 0000000000..471ce4fa3f
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/ExhaustiveSwitch.res.txt
@@ -0,0 +1,171 @@
+Complete src/ExhaustiveSwitch.res 8:24
+XXX Not found!
+Completable: CexhaustiveSwitch Value[withSomeVarian]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[withSomeVarian]
+Path withSomeVarian
+[{
+    "label": "withSomeVariant",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(option<bool>)\n```"}
+  }, {
+    "label": "withSomeVariant (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "withSomeVariant",
+    "insertText": "withSomeVariant {\n   | One => ${1:failwith(\"todo\")}\n   | Two => ${2:failwith(\"todo\")}\n   | Three(_) => ${3:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 11:21
+XXX Not found!
+Completable: CexhaustiveSwitch Value[withSomePol]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[withSomePol]
+Path withSomePol
+[{
+    "label": "withSomePoly",
+    "kind": 12,
+    "tags": [],
+    "detail": "somePolyVariant",
+    "documentation": null
+  }, {
+    "label": "withSomePoly (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "withSomePoly",
+    "insertText": "withSomePoly {\n   | #\"switch\" => ${1:failwith(\"todo\")}\n   | #one => ${2:failwith(\"todo\")}\n   | #three(_) => ${3:failwith(\"todo\")}\n   | #two => ${4:failwith(\"todo\")}\n   | #\"exotic ident\" => ${5:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 14:17
+XXX Not found!
+Completable: CexhaustiveSwitch Value[someBoo]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someBoo]
+Path someBoo
+[{
+    "label": "someBool",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "someBool (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "someBool",
+    "insertText": "someBool {\n   | true => ${1:failwith(\"todo\")}\n   | false => ${2:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 17:16
+XXX Not found!
+Completable: CexhaustiveSwitch Value[someOp]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someOp]
+Path someOp
+[{
+    "label": "someOpt",
+    "kind": 12,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": null
+  }, {
+    "label": "someOpt (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "someOpt",
+    "insertText": "someOpt {\n   | Some($1) => ${2:failwith(\"todo\")}\n   | None => ${3:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Xform src/ExhaustiveSwitch.res 30:13
+posCursor:[30:13] posNoWhite:[30:12] Found expr:[30:3->30:17]
+posCursor:[30:13] posNoWhite:[30:12] Found expr:[30:10->30:17]
+Completable: Cpath Value[x]->
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x]->
+ContextPath Value[x]
+Path x
+CPPipe env:ExhaustiveSwitch
+CPPipe type path:rcrd
+CPPipe pathFromEnv: found:true
+
+Xform src/ExhaustiveSwitch.res start: 33:3, end: 33:10
+found selection: [33:3->33:10] -> [33:6->33:10]
+XXX Not found!
+Completable: Cpath Value[getV](Nolabel)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[getV](Nolabel)
+ContextPath Value[getV]
+Path getV
+Package opens Pervasives.JsxModules.place holder
+Hit: Exhaustive switch
+
+TextDocumentEdit: ExhaustiveSwitch.res
+{"start": {"line": 33, "character": 3}, "end": {"line": 33, "character": 10}}
+newText:
+   <--here
+   switch x->getV {
+   | One => failwith("TODO")
+   | Two => failwith("TODO")
+   | Three(_) => failwith("TODO")
+   }
+
+Xform src/ExhaustiveSwitch.res 36:4
+XXX Not found!
+Completable: Cpath Value[vvv]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[vvv]
+Path vvv
+Package opens Pervasives.JsxModules.place holder
+Hit: Exhaustive switch
+
+TextDocumentEdit: ExhaustiveSwitch.res
+{"start": {"line": 36, "character": 3}, "end": {"line": 36, "character": 6}}
+newText:
+   <--here
+   switch vvv {
+   | None => failwith("TODO")
+   | Some(_) => failwith("TODO")
+   | Some(One) => failwith("TODO")
+   | Some(Two) => failwith("TODO")
+   | Some(Three(_)) => failwith("TODO")
+   }
+
+
+Complete src/ExhaustiveSwitch.res 40:24
+XXX Not found!
+Completable: CexhaustiveSwitch Value[withSomeVarian]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[withSomeVarian]
+Path withSomeVarian
+[{
+    "label": "withSomeVariant",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(option<bool>)\n```"}
+  }, {
+    "label": "withSomeVariant (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "withSomeVariant",
+    "insertText": "withSomeVariant {\n   | One => ${1:%todo}\n   | Two => ${2:%todo}\n   | Three(_) => ${3:%todo}\n   }",
+    "insertTextFormat": 2
+  }]
+
+
diff --git a/tests/analysis_tests/tests/src/expected/Fragment.res.txt b/tests/analysis_tests/tests/src/expected/Fragment.res.txt
new file mode 100644
index 0000000000..c17fd7adc8
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Fragment.res.txt
@@ -0,0 +1,16 @@
+Hover src/Fragment.res 6:19
+{"contents": {"kind": "markdown", "value": "```rescript\nReact.component<SectionHeader.props<React.element>>\n```\n\n---\n\n```\n \n```\n```rescript\ntype React.component<'props> = Jsx.component<'props>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C12%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype SectionHeader.props<'children> = {children: 'children}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Fragment.res%22%2C1%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype React.element = Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C0%2C0%5D)\n"}}
+
+Hover src/Fragment.res 9:56
+Nothing at that position. Now trying to use completion.
+posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:10->9:67]
+posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:13->9:67]
+posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:13->9:66]
+JSX <SectionHeader:[9:13->9:26] > _children:9:26
+posCursor:[9:56] posNoWhite:[9:55] Found expr:__ghost__[9:10->9:67]
+Pexp_construct []:__ghost__[9:10->9:67] None
+Completable: Cexpression CTypeAtPos()=[]->variantPayload::::($1)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CTypeAtPos()
+null
+
diff --git a/tests/analysis_tests/tests/src/expected/Highlight.res.txt b/tests/analysis_tests/tests/src/expected/Highlight.res.txt
new file mode 100644
index 0000000000..a4ab8e1422
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Highlight.res.txt
@@ -0,0 +1,143 @@
+Highlight src/Highlight.res
+structure items:39 diagnostics:0 
+Lident: M 0:7 Namespace
+Lident: C 1:9 Namespace
+Lident: Component 1:13 Namespace
+Variable: _c [4:4->4:6]
+JsxTag <: 4:9
+Lident: Component 4:10 Namespace
+Variable: _mc [6:4->6:7]
+JsxTag <: 6:10
+Ldot: M 6:11 Namespace
+Lident: C 6:13 Namespace
+Variable: _d [8:4->8:6]
+JsxTag <: 8:9
+Lident: div 8:10 JsxLowercase
+Variable: _d2 [10:4->10:7]
+JsxTag <: 11:2
+Lident: div 11:3 JsxLowercase
+Lident: div 16:4 JsxLowercase
+JsxTag >: 11:6
+JsxTag >: 16:7
+Ldot: React 12:5 Namespace
+Lident: string 12:11 Variable
+JsxTag <: 13:4
+Lident: div 13:5 JsxLowercase
+Lident: div 13:34 JsxLowercase
+JsxTag >: 13:8
+JsxTag >: 13:37
+Ldot: React 13:11 Namespace
+Lident: string 13:17 Variable
+Ldot: React 14:5 Namespace
+Lident: string 14:11 Variable
+Ldot: React 15:5 Namespace
+Lident: string 15:11 Variable
+Lident: pair 18:5 Type
+Lident: looooooooooooooooooooooooooooooooooooooong_int 20:5 Type
+Lident: int 20:54 Type
+Lident: looooooooooooooooooooooooooooooooooooooong_string 22:5 Type
+Lident: string 22:57 Type
+Lident: pairIntString 24:5 Type
+Lident: list 24:21 Type
+TypeArg: [25:2->28:3]
+Lident: pair 25:2 Type
+TypeArg: [26:4->26:50]
+TypeArg: [27:4->27:53]
+Lident: looooooooooooooooooooooooooooooooooooooong_int 26:4 Type
+Lident: looooooooooooooooooooooooooooooooooooooong_string 27:4 Type
+Binary operator < [31:12->31:13]
+Binary operator > [31:22->31:23]
+Lident: MT 33:12 Type
+Lident: DDF 34:9 Namespace
+Lident: DDF 39:7 Namespace
+Lident: DDF 40:9 Namespace
+Lident: MT 39:12 Type
+Lident: XX 45:7 Namespace
+Lident: YY 46:9 Namespace
+Lident: t 47:9 Type
+Lident: int 47:13 Type
+Ldot: XX 51:5 Namespace
+Lident: YY 51:8 Namespace
+Lident: tt 53:5 Type
+Lident: t 53:10 Type
+Lident: T 57:7 Namespace
+Lident: someRecord 58:7 Type
+Lident: someField 59:4 Property
+Lident: int 59:15 Type
+Lident: someOtherField 60:4 Property
+Lident: string 60:20 Type
+Lident: theParam 61:4 Property
+Lident: someEnum 64:7 Type
+Lident: A 64:18 EnumMember
+Lident: B 64:22 EnumMember
+Lident: C 64:26 EnumMember
+Variable: foo [67:4->67:7]
+Variable: x [67:10->67:11]
+Ldot: T 67:17 Namespace
+Lident: someField 67:19 Property
+Lident: x 67:15 Variable
+Variable: add [69:4->69:7]
+Variable: x [69:21->69:22]
+Variable: world [69:24->69:30]
+Lident: x 69:35 Variable
+Lident: world 69:39 Variable
+Lident: add 71:21 Variable
+JsxTag <: 73:8
+Lident: div 73:9 JsxLowercase
+Lident: div 73:36 JsxLowercase
+JsxTag >: 73:24
+JsxTag >: 73:39
+JsxTag <: 73:26
+Lident: div 73:27 JsxLowercase
+Lident: SomeComponent 75:7 Namespace
+Lident: Nested 76:9 Namespace
+Variable: make [78:8->78:12]
+Variable: children [78:16->78:25]
+Lident: children 79:10 Variable
+JsxTag <: 84:8
+Ldot: SomeComponent 84:9 Namespace
+Lident: Nested 84:23 Namespace
+Ldot: SomeComponent 84:41 Namespace
+Lident: Nested 84:55 Namespace
+JsxTag >: 84:29
+JsxTag >: 84:61
+JsxTag <: 84:31
+Lident: div 84:32 JsxLowercase
+Variable: toAs [90:4->90:8]
+Variable: x [90:19->90:20]
+Lident: x 90:25 Variable
+Variable: _toEquals [91:4->91:13]
+Lident: toAs 91:16 Variable
+Variable: to [93:4->93:6]
+Lident: to 94:9 Variable
+Lident: to 94:14 Variable
+Lident: to 94:20 Variable
+Lident: to 94:25 Variable
+Lident: ToAsProp 98:7 Namespace
+Variable: make [100:6->100:10]
+Variable: to [100:14->100:17]
+Ldot: React 101:8 Namespace
+Lident: int 101:14 Variable
+Lident: to 101:18 Variable
+JsxTag <: 104:8
+Lident: ToAsProp 104:9 Namespace
+Variable: true [107:4->107:11]
+Lident: true 108:8->108:15 Variable
+Variable: enumInModule [110:4->110:16]
+Ldot: T 110:19 Namespace
+Lident: A 110:21 EnumMember
+Lident: typeInModule 112:5 Type
+Ldot: XX 112:20 Namespace
+Ldot: YY 112:23 Namespace
+Lident: t 112:26 Type
+Lident: QQ 114:7 Namespace
+Lident: somePolyEnumType 115:7 Type
+Lident: list 118:29 Type
+TypeArg: [118:34->118:37]
+Lident: int 118:34 Type
+Variable: x [123:8->123:9]
+Lident: x 124:9 Variable
+Ldot: QQ 126:8 Namespace
+Lident: somePolyEnumType 126:11 Type
+Lident: abc 133:9->133:14 Property
+
diff --git a/tests/analysis_tests/tests/src/expected/Hover.res.txt b/tests/analysis_tests/tests/src/expected/Hover.res.txt
new file mode 100644
index 0000000000..5b461ca751
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Hover.res.txt
@@ -0,0 +1,246 @@
+Hover src/Hover.res 0:4
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Hover src/Hover.res 3:5
+{"contents": {"kind": "markdown", "value": "```rescript\ntype t = (int, float)\n```"}}
+
+Hover src/Hover.res 6:7
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule Id: {\n  type x = int\n}\n```"}}
+
+Hover src/Hover.res 19:11
+{"contents": {"kind": "markdown", "value": "\nThis module is commented\n```rescript\nmodule Dep: {\n  let customDouble: int => int\n}\n```"}}
+
+Hover src/Hover.res 22:11
+{"contents": {"kind": "markdown", "value": "```rescript\nint => int\n```\n---\nSome doc comment"}}
+
+Hover src/Hover.res 26:6
+getLocItem #8: heuristic for JSX with at most one child
+heuristic for: [makeProps, make, createElement], give the loc of `make` 
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Hover src/Hover.res 33:4
+{"contents": {"kind": "markdown", "value": "```rescript\nunit => int\n```\n---\nDoc comment for functionWithTypeAnnotation"}}
+
+Hover src/Hover.res 37:13
+{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
+
+Hover src/Hover.res 42:15
+{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
+
+Hover src/Hover.res 46:10
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Hover src/Hover.res 49:13
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule type Logger = {\n  let log: string => unit\n}\n```"}}
+
+Hover src/Hover.res 54:7
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule type Logger = {\n  let log: string => unit\n}\n```"}}
+
+Definition src/Hover.res 60:14
+{"uri": "Hover.res", "range": {"start": {"line": 49, "character": 12}, "end": {"line": 49, "character": 18}}}
+
+Hover src/Hover.res 63:9
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule IdDefinedTwice: {\n  let y: int\n  let _x: int\n}\n```"}}
+
+Hover src/Hover.res 74:7
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule A: {\n  let x: int\n}\n```"}}
+
+Hover src/Hover.res 77:7
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule A: {\n  let x: int\n}\n```"}}
+
+Hover src/Hover.res 91:10
+Nothing at that position. Now trying to use completion.
+posCursor:[91:10] posNoWhite:[91:8] Found expr:[88:3->91:9]
+JSX <Comp:[88:3->88:7] > _children:88:7
+null
+
+Hover src/Hover.res 98:10
+Nothing at that position. Now trying to use completion.
+posCursor:[98:10] posNoWhite:[98:9] Found expr:[95:3->98:10]
+JSX <Comp1:[95:3->95:8] > _children:95:8
+null
+
+Hover src/Hover.res 103:25
+{"contents": {"kind": "markdown", "value": "```rescript\nfloat\n```"}}
+
+Hover src/Hover.res 106:21
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Hover src/Hover.res 116:16
+{"contents": {"kind": "markdown", "value": "```rescript\nAA.cond<([< #str(string)] as 'a)> => AA.cond<'a>\n```\n\n---\n\n```\n \n```\n```rescript\ntype AA.cond<'a> = 'a\n  constraint 'a = [< #str(string)]\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C110%2C2%5D)\n"}}
+
+Hover src/Hover.res 119:25
+{"contents": {"kind": "markdown", "value": "```rescript\nAA.cond<([< #str(string)] as 'a)> => AA.cond<'a>\n```\n\n---\n\n```\n \n```\n```rescript\ntype AA.cond<'a> = 'a\n  constraint 'a = [< #str(string)]\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C110%2C2%5D)\n"}}
+
+Hover src/Hover.res 122:3
+Nothing at that position. Now trying to use completion.
+Attribute id:live:[122:0->122:5] label:live
+Completable: Cdecorator(live)
+Package opens Pervasives.JsxModules.place holder
+{"contents": {"kind": "markdown", "value": "The `@live` decorator is for reanalyze, a static analysis tool for ReScript that can do dead code analysis.\n\n`@live` tells the dead code analysis that the value should be considered live, even though it might appear to be dead. This is typically used in case of FFI where there are indirect ways to access values. It can be added to everything that could otherwise be considered unused by the dead code analysis - values, functions, arguments, records, individual record fields, and so on.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#live-decorator).\n\nHint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!"}}
+
+Hover src/Hover.res 125:4
+{"contents": {"kind": "markdown", "value": "```rescript\nunit => unit => int\n```"}}
+
+Hover src/Hover.res 131:4
+{"contents": {"kind": "markdown", "value": "```rescript\n(unit, unit) => int\n```"}}
+
+Hover src/Hover.res 134:4
+{"contents": {"kind": "markdown", "value": "```rescript\n(unit, unit) => int\n```"}}
+
+Hover src/Hover.res 137:5
+{"contents": {"kind": "markdown", "value": "```rescript\nunit => unit => int\n```"}}
+
+Hover src/Hover.res 144:9
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\ndoc comment 1"}}
+
+Hover src/Hover.res 148:6
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n doc comment 2 "}}
+
+Hover src/Hover.res 165:23
+{"contents": {"kind": "markdown", "value": "```rescript\nfoo<bar>\n```\n\n---\n\n```\n \n```\n```rescript\ntype foo<'a> = {content: 'a, zzz: string}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C161%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype bar = {age: int}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C162%2C2%5D)\n"}}
+
+Hover src/Hover.res 167:22
+{"contents": {"kind": "markdown", "value": "```rescript\nfoobar\n```\n\n---\n\n```\n \n```\n```rescript\ntype foobar = foo<bar>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C163%2C2%5D)\n"}}
+
+Complete src/Hover.res 170:16
+posCursor:[170:16] posNoWhite:[170:15] Found expr:[170:5->170:16]
+Pexp_field [170:5->170:15] _:[176:2->170:16]
+Completable: Cpath Value[x1].content.""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x1].content.""
+ContextPath Value[x1].content
+ContextPath Value[x1]
+Path x1
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype bar = {age: int}\n```"}
+  }]
+
+Complete src/Hover.res 173:16
+posCursor:[173:16] posNoWhite:[173:15] Found expr:[173:5->173:16]
+Pexp_field [173:5->173:15] _:[176:2->173:16]
+Completable: Cpath Value[x2].content.""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x2].content.""
+ContextPath Value[x2].content
+ContextPath Value[x2]
+Path x2
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype bar = {age: int}\n```"}
+  }]
+
+Complete src/Hover.res 182:16
+posCursor:[182:16] posNoWhite:[182:15] Found expr:[182:5->182:16]
+Pexp_field [182:5->182:15] _:[187:0->182:16]
+Completable: Cpath Value[y1].content.""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[y1].content.""
+ContextPath Value[y1].content
+ContextPath Value[y1]
+Path y1
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype bar = {age: int}\n```"}
+  }]
+
+Complete src/Hover.res 185:16
+posCursor:[185:16] posNoWhite:[185:15] Found expr:[185:5->185:16]
+Pexp_field [185:5->185:15] _:[187:0->185:16]
+Completable: Cpath Value[y2].content.""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[y2].content.""
+ContextPath Value[y2].content
+ContextPath Value[y2]
+Path y2
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype bar = {age: int}\n```"}
+  }]
+
+Hover src/Hover.res 197:4
+{"contents": {"kind": "markdown", "value": "```rescript\nCompV4.props<int, string> => React.element\n```\n\n---\n\n```\n \n```\n```rescript\ntype CompV4.props<'n, 's> = {n?: 'n, s: 's}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C190%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype React.element = Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C0%2C0%5D)\n"}}
+
+Hover src/Hover.res 202:16
+{"contents": {"kind": "markdown", "value": "```rescript\nuseR\n```\n\n---\n\n```\n \n```\n```rescript\ntype useR = {x: int, y: list<option<r<float>>>}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C200%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype r<'a> = {i: 'a, f: float}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C101%2C0%5D)\n"}}
+
+Hover src/Hover.res 210:13
+Nothing at that position. Now trying to use completion.
+posCursor:[210:13] posNoWhite:[210:12] Found expr:[210:11->210:14]
+Pexp_ident usr:[210:11->210:14]
+Completable: Cpath Value[usr]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[usr]
+Path usr
+Package opens Pervasives.JsxModules.place holder
+{"contents": {"kind": "markdown", "value": "```rescript\nuseR\n```\n\n---\n\n```\n \n```\n```rescript\ntype useR = {x: int, y: list<option<r<float>>>}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C200%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype r<'a> = {i: 'a, f: float}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C101%2C0%5D)\n"}}
+
+Hover src/Hover.res 230:20
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n More Stuff "}}
+
+Hover src/Hover.res 233:17
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n More Stuff "}}
+
+Hover src/Hover.res 245:6
+Nothing at that position. Now trying to use completion.
+posCursor:[245:6] posNoWhite:[245:5] Found expr:[245:3->245:14]
+Pexp_field [245:3->245:4] someField:[245:5->245:14]
+Completable: Cpath Value[x].someField
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x].someField
+ContextPath Value[x]
+Path x
+Package opens Pervasives.JsxModules.place holder
+{"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}}
+
+Hover src/Hover.res 248:19
+{"contents": {"kind": "markdown", "value": "```rescript\nbool\n```\n---\n Mighty fine field here. "}}
+
+Hover src/Hover.res 253:20
+{"contents": {"kind": "markdown", "value": "```rescript\nvariant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n\n---\n```rescript\nCoolVariant\n```\n---\n Cool variant! "}}
+
+Hover src/Hover.res 257:23
+Nothing at that position. Now trying to use completion.
+posCursor:[257:23] posNoWhite:[257:22] Found expr:[257:22->257:25]
+Pexp_ident fff:[257:22->257:25]
+Completable: Cpath Value[fff]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fff]
+Path fff
+Package opens Pervasives.JsxModules.place holder
+ContextPath string
+{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
+
+Hover src/Hover.res 260:33
+Nothing at that position. Now trying to use completion.
+posCursor:[260:33] posNoWhite:[260:32] Found expr:[260:31->260:40]
+Pexp_ident someField:[260:31->260:40]
+Completable: Cpath Value[someField]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someField]
+Path someField
+Package opens Pervasives.JsxModules.place holder
+ContextPath CPatternPath(Value[x])->recordField(someField)
+ContextPath Value[x]
+Path x
+{"contents": {"kind": "markdown", "value": "```rescript\nbool\n```"}}
+
+Hover src/Hover.res 263:8
+{"contents": {"kind": "markdown", "value": "\n [`Belt.Array`]()\n\n  **mutable array**: Utilities functions\n\n```rescript\nmodule Array: {\n  module Id\n  module Array\n  module SortArray\n  module MutableQueue\n  module MutableStack\n  module List\n  module Range\n  module Set\n  module Map\n  module MutableSet\n  module MutableMap\n  module HashSet\n  module HashMap\n  module Option\n  module Result\n  module Int\n  module Float\n}\n```"}}
+
+Hover src/Hover.res 266:6
+{"contents": {"kind": "markdown", "value": "```rescript\ntype aliased = variant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/InlayHint.res.txt b/tests/analysis_tests/tests/src/expected/InlayHint.res.txt
new file mode 100644
index 0000000000..aad649db3b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/InlayHint.res.txt
@@ -0,0 +1,81 @@
+Inlay Hint src/InlayHint.res 1:34
+[{
+    "position": {"line": 33, "character": 14},
+    "label": ": int",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 33, "character": 9},
+    "label": ": string",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 28, "character": 9},
+    "label": ": foo",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 26, "character": 23},
+    "label": ": (string, string)",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 18, "character": 9},
+    "label": ": string",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 16, "character": 9},
+    "label": ": (string, string)",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 10, "character": 10},
+    "label": ": (~xx: int) => int",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 8, "character": 10},
+    "label": ": int",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 6, "character": 7},
+    "label": ": (int, int) => int",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 4, "character": 8},
+    "label": ": char",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 3, "character": 9},
+    "label": ": float",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 2, "character": 10},
+    "label": ": int",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}, {
+    "position": {"line": 1, "character": 10},
+    "label": ": string",
+    "kind": 1,
+    "paddingLeft": true,
+    "paddingRight": false
+}]
+
diff --git a/tests/analysis_tests/tests/src/expected/Jsx2.res.txt b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt
new file mode 100644
index 0000000000..b88345193c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Jsx2.res.txt
@@ -0,0 +1,535 @@
+Definition src/Jsx2.res 5:9
+{"uri": "Jsx2.res", "range": {"start": {"line": 2, "character": 6}, "end": {"line": 2, "character": 10}}}
+
+Complete src/Jsx2.res 8:15
+posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:4->8:15]
+JSX <M:[8:4->8:5] second[8:6->8:12]=...[8:13->8:15]> _children:None
+Completable: Cexpression CJsxPropValue [M] second=fi
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [M] second
+Path M.make
+[]
+
+Complete src/Jsx2.res 11:20
+posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:4->11:20]
+JSX <M:[11:4->11:5] second[11:6->11:12]=...[11:13->11:18] f[11:19->11:20]=...[11:19->11:20]> _children:None
+Completable: Cjsx([M], f, [second, f])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "first",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }, {
+    "label": "fun",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<string>",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 14:13
+posCursor:[14:13] posNoWhite:[14:12] Found expr:[14:12->14:13]
+JSX <M:[14:12->14:13] > _children:None
+Completable: Cpath Module[M]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[M]
+Path M
+[{
+    "label": "M",
+    "kind": 9,
+    "tags": [],
+    "detail": "module M",
+    "documentation": null
+  }, {
+    "label": "Map",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Map",
+    "documentation": null,
+    "data": {
+      "modulePath": "Map",
+      "filePath": "src/Jsx2.res"
+    }
+  }, {
+    "label": "Math",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Math",
+    "documentation": null,
+    "data": {
+      "modulePath": "Math",
+      "filePath": "src/Jsx2.res"
+    }
+  }, {
+    "label": "ModuleStuff",
+    "kind": 9,
+    "tags": [],
+    "detail": "module ModuleStuff",
+    "documentation": null,
+    "data": {
+      "modulePath": "ModuleStuff",
+      "filePath": "src/Jsx2.res"
+    }
+  }]
+
+Complete src/Jsx2.res 22:19
+posCursor:[22:19] posNoWhite:[22:18] Found expr:[22:4->22:19]
+JSX <M:[22:4->22:5] prop[22:6->22:10]=...[22:12->22:16] k[22:18->22:19]=...[22:18->22:19]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 25:17
+posCursor:[25:17] posNoWhite:[25:16] Found expr:[25:4->25:17]
+JSX <M:[25:4->25:5] prop[25:6->25:10]=...[25:11->25:15] k[25:16->25:17]=...[25:16->25:17]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 28:21
+posCursor:[28:21] posNoWhite:[28:20] Found expr:[28:4->28:21]
+JSX <M:[28:4->28:5] prop[28:6->28:10]=...[28:11->28:19] k[28:20->28:21]=...[28:20->28:21]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 31:24
+posCursor:[31:24] posNoWhite:[31:23] Found expr:[31:4->31:24]
+JSX <M:[31:4->31:5] prop[31:6->31:10]=...[31:11->31:22] k[31:23->31:24]=...[31:23->31:24]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 34:18
+posCursor:[34:18] posNoWhite:[34:17] Found expr:[34:4->34:18]
+JSX <M:[34:4->34:5] prop[34:6->34:10]=...[34:12->34:16] k[34:17->34:18]=...[34:17->34:18]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 37:16
+posCursor:[37:16] posNoWhite:[37:15] Found expr:[37:4->37:16]
+JSX <M:[37:4->37:5] prop[37:6->37:10]=...[37:11->37:14] k[37:15->37:16]=...[37:15->37:16]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 40:17
+posCursor:[40:17] posNoWhite:[40:16] Found expr:[40:4->40:17]
+JSX <M:[40:4->40:5] prop[40:6->40:10]=...[40:11->40:15] k[40:16->40:17]=...[40:16->40:17]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 43:18
+posCursor:[43:18] posNoWhite:[43:17] Found expr:[43:4->43:18]
+JSX <M:[43:4->43:5] prop[43:6->43:10]=...[43:11->43:16] k[43:17->43:18]=...[43:17->43:18]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 46:16
+posCursor:[46:16] posNoWhite:[46:15] Found expr:[46:4->46:16]
+JSX <M:[46:4->46:5] prop[46:6->46:10]=...[46:11->46:14] k[46:15->46:16]=...[46:15->46:16]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 49:27
+posCursor:[49:27] posNoWhite:[49:26] Found expr:[49:4->49:27]
+JSX <M:[49:4->49:5] prop[49:6->49:10]=...[49:11->49:25] k[49:26->49:27]=...[49:26->49:27]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 52:38
+posCursor:[52:38] posNoWhite:[52:37] Found expr:[52:4->52:38]
+JSX <M:[52:4->52:5] prop[52:6->52:10]=...[52:11->52:36] k[52:37->52:38]=...[52:37->52:38]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 55:25
+posCursor:[55:25] posNoWhite:[55:24] Found expr:[55:4->55:25]
+JSX <M:[55:4->55:5] prop[55:6->55:10]=...[55:11->55:23] k[55:24->55:25]=...[55:24->55:25]> _children:None
+Completable: Cjsx([M], k, [prop, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Definition src/Jsx2.res 58:11
+{"uri": "Component.res", "range": {"start": {"line": 1, "character": 4}, "end": {"line": 1, "character": 8}}}
+
+Complete src/Jsx2.res 68:10
+posCursor:[68:10] posNoWhite:[68:9] Found expr:[68:4->68:10]
+JSX <Ext:[68:4->68:7] al[68:8->68:10]=...[68:8->68:10]> _children:None
+Completable: Cjsx([Ext], al, [al])
+Package opens Pervasives.JsxModules.place holder
+Path Ext.make
+[{
+    "label": "align",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<string>",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 71:11
+posCursor:[71:11] posNoWhite:[71:10] Found expr:[71:4->71:11]
+JSX <M:[71:4->71:5] first[71:6->71:11]=...[71:6->71:11]> _children:None
+Completable: Cjsx([M], first, [first])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[]
+
+Complete src/Jsx2.res 74:16
+posCursor:[74:16] posNoWhite:[74:15] Found expr:[74:4->74:16]
+JSX <M:[74:4->74:5] first[74:6->74:11]=...[74:12->74:14] k[74:15->74:16]=...[74:15->74:16]> _children:None
+Completable: Cjsx([M], k, [first, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 77:23
+posCursor:[77:23] posNoWhite:[77:22] Found expr:[77:4->77:23]
+JSX <M:[77:4->77:5] first[77:6->77:11]=...[77:19->77:21] k[77:22->77:23]=...[77:22->77:23]> _children:None
+Completable: Cjsx([M], k, [first, k])
+Package opens Pervasives.JsxModules.place holder
+Path M.make
+[{
+    "label": "key",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 80:6
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->85:69]
+Pexp_apply ...[83:20->83:21] (...[80:4->83:19], ...[84:2->85:69])
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->83:19]
+JSX <M:[80:4->80:5] > _children:80:5
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:5->83:20]
+posCursor:[80:6] posNoWhite:[80:5] Found expr:__ghost__[80:5->83:20]
+Pexp_construct []:__ghost__[80:5->83:20] None
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:4->83:19]
+JSX <M:[80:4->80:5] > _children:80:5
+posCursor:[80:6] posNoWhite:[80:5] Found expr:[80:5->83:20]
+posCursor:[80:6] posNoWhite:[80:5] Found expr:__ghost__[80:5->83:20]
+Pexp_construct []:__ghost__[80:5->83:20] None
+[]
+
+Complete src/Jsx2.res 89:16
+posCursor:[89:16] posNoWhite:[89:15] Found expr:[89:4->89:16]
+JSX <WithChildren:[89:4->89:16] > _children:None
+Completable: Cpath Module[WithChildren]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[WithChildren]
+Path WithChildren
+[{
+    "label": "WithChildren",
+    "kind": 9,
+    "tags": [],
+    "detail": "module WithChildren",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 91:18
+posCursor:[91:18] posNoWhite:[91:17] Found expr:[91:4->91:18]
+JSX <WithChildren:[91:4->91:16] n[91:17->91:18]=...[91:17->91:18]> _children:None
+Completable: Cjsx([WithChildren], n, [n])
+Package opens Pervasives.JsxModules.place holder
+Path WithChildren.make
+[{
+    "label": "name",
+    "kind": 4,
+    "tags": [],
+    "detail": "string",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 94:18
+posCursor:[94:18] posNoWhite:[94:17] Found pattern:[94:7->94:18]
+posCursor:[94:18] posNoWhite:[94:17] Found type:[94:11->94:18]
+Ptyp_constr React.e:[94:11->94:18]
+Completable: Cpath Type[React, e]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[React, e]
+Path React.e
+[{
+    "label": "element",
+    "kind": 22,
+    "tags": [],
+    "detail": "type element",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype element = Jsx.element\n```"}
+  }]
+
+Complete src/Jsx2.res 96:20
+posCursor:[96:20] posNoWhite:[96:19] Found pattern:[96:7->99:6]
+posCursor:[96:20] posNoWhite:[96:19] Found type:[96:11->99:6]
+Ptyp_constr ReactDOMR:[96:11->99:6]
+Completable: Cpath Type[ReactDOMR]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[ReactDOMR]
+Path ReactDOMR
+[{
+    "label": "ReactDOMRe",
+    "kind": 9,
+    "tags": [],
+    "detail": "module ReactDOMRe",
+    "documentation": null,
+    "data": {
+      "modulePath": "ReactDOMRe",
+      "filePath": "src/Jsx2.res"
+    }
+  }]
+
+Complete src/Jsx2.res 102:21
+posCursor:[102:21] posNoWhite:[102:20] Found expr:[102:13->102:21]
+Pexp_apply ...[102:15->102:16] (...[102:13->102:14], ...[102:17->102:21])
+posCursor:[102:21] posNoWhite:[102:20] Found expr:[102:17->102:21]
+Pexp_field [102:17->102:18] th:[102:19->102:21]
+Completable: Cpath Value[x].th
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[x].th
+ContextPath Value[x]
+Path x
+[]
+
+Complete src/Jsx2.res 106:28
+posCursor:[106:28] posNoWhite:[106:27] Found expr:[106:11->106:28]
+Pexp_ident DefineSomeFields.:[106:11->106:28]
+Completable: Cpath Value[DefineSomeFields, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[DefineSomeFields, ""]
+Path DefineSomeFields.
+[{
+    "label": "thisValue",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 108:36
+posCursor:[108:36] posNoWhite:[108:35] Found expr:[108:11->108:36]
+Pexp_apply ...[108:13->108:14] (...[108:11->108:12], ...[108:15->108:36])
+posCursor:[108:36] posNoWhite:[108:35] Found expr:[108:15->108:36]
+Pexp_field [108:15->108:16] DefineSomeFields.th:[108:17->108:36]
+Completable: Cpath Module[DefineSomeFields].th
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[DefineSomeFields].th
+Path DefineSomeFields.th
+[{
+    "label": "thisField",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nthisField: int\n```\n\n```rescript\ntype r = {thisField: int, thatField: string}\n```"}
+  }, {
+    "label": "thatField",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nthatField: string\n```\n\n```rescript\ntype r = {thisField: int, thatField: string}\n```"}
+  }]
+
+Complete src/Jsx2.res 122:20
+posCursor:[122:20] posNoWhite:[122:19] Found expr:[121:3->125:4]
+JSX <div:[121:3->121:6] x[122:5->122:6]=...[122:7->122:20] name[124:4->124:8]=...[124:9->124:11]> _children:125:2
+posCursor:[122:20] posNoWhite:[122:19] Found expr:[122:7->122:20]
+Pexp_ident Outer.Inner.h:[122:7->122:20]
+Completable: Cpath Value[Outer, Inner, h]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Outer, Inner, h]
+Path Outer.Inner.h
+[{
+    "label": "hello",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 129:19
+posCursor:[129:19] posNoWhite:[129:18] Found expr:[128:3->131:9]
+JSX <div:[128:3->128:6] x[129:5->129:6]=...[129:7->131:8]> _children:None
+posCursor:[129:19] posNoWhite:[129:18] Found expr:[129:7->131:8]
+Pexp_ident Outer.Inner.:[129:7->131:8]
+Completable: Cpath Value[Outer, Inner, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Outer, Inner, ""]
+Path Outer.Inner.
+[{
+    "label": "hello",
+    "kind": 12,
+    "tags": [],
+    "detail": "int",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 136:7
+posCursor:[136:7] posNoWhite:[136:6] Found expr:[135:3->138:9]
+JSX <div:[135:3->135:6] x[136:5->136:6]=...[138:4->138:8]> _children:None
+Completable: Cexpression CJsxPropValue [div] x
+Package opens Pervasives.JsxModules.place holder
+ContextPath CJsxPropValue [div] x
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[{
+    "label": "\"\"",
+    "kind": 12,
+    "tags": [],
+    "detail": "string",
+    "documentation": null,
+    "sortText": "A",
+    "insertText": "{\"$0\"}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Jsx2.res 150:21
+posCursor:[150:21] posNoWhite:[150:20] Found expr:[150:12->150:32]
+JSX <Nested.Co:[150:12->150:21] name[150:22->150:26]=...[150:27->150:29]> _children:150:30
+Completable: Cpath Module[Nested, Co]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[Nested, Co]
+Path Nested.Co
+[{
+    "label": "Comp",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Comp",
+    "documentation": null
+  }]
+
+Complete src/Jsx2.res 153:19
+posCursor:[153:19] posNoWhite:[153:18] Found expr:[153:12->153:25]
+JSX <Nested.:[153:12->153:24] > _children:None
+Completable: Cpath Module[Nested, ""]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[Nested, ""]
+Path Nested.
+[{
+    "label": "Comp",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Comp",
+    "documentation": null
+  }]
+
+Hover src/Jsx2.res 162:12
+Nothing at that position. Now trying to use completion.
+posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:3->162:21]
+posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:6->162:21]
+posCursor:[162:12] posNoWhite:[162:11] Found expr:[162:6->162:20]
+JSX <Comp:[162:6->162:10] age[162:11->162:14]=...[162:15->162:17]> _children:162:18
+Completable: Cjsx([Comp], age, [age])
+Package opens Pervasives.JsxModules.place holder
+Path Comp.make
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Hover src/Jsx2.res 167:16
+Nothing at that position. Now trying to use completion.
+posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:3->167:30]
+posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:7->167:30]
+posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:7->167:25]
+posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:10->167:25]
+posCursor:[167:16] posNoWhite:[167:15] Found expr:[167:10->167:24]
+JSX <Comp:[167:10->167:14] age[167:15->167:18]=...[167:19->167:21]> _children:167:22
+Completable: Cjsx([Comp], age, [age])
+Package opens Pervasives.JsxModules.place holder
+Path Comp.make
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/Jsx2.resi.txt b/tests/analysis_tests/tests/src/expected/Jsx2.resi.txt
new file mode 100644
index 0000000000..97d8216b91
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Jsx2.resi.txt
@@ -0,0 +1,36 @@
+Hover src/Jsx2.resi 1:4
+{"contents": {"kind": "markdown", "value": "```rescript\nprops<string>\n```\n\n---\n\n```\n \n```\n```rescript\ntype props<'first> = {first: 'first}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx2.resi%22%2C0%2C0%5D)\n"}}
+
+Hover src/Jsx2.resi 4:4
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
+Complete src/Jsx2.resi 7:19
+posCursor:[7:19] posNoWhite:[7:18] Found type:[7:12->7:19]
+Ptyp_constr React.e:[7:12->7:19]
+Completable: Cpath Type[React, e]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[React, e]
+Path React.e
+[{
+    "label": "element",
+    "kind": 22,
+    "tags": [],
+    "detail": "type element",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype element = Jsx.element\n```"}
+  }]
+
+Complete src/Jsx2.resi 10:18
+posCursor:[10:18] posNoWhite:[10:17] Found type:[10:11->10:18]
+Ptyp_constr React.e:[10:11->10:18]
+Completable: Cpath Type[React, e]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[React, e]
+Path React.e
+[{
+    "label": "element",
+    "kind": 22,
+    "tags": [],
+    "detail": "type element",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype element = Jsx.element\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt
new file mode 100644
index 0000000000..8a1abb7796
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt
@@ -0,0 +1,34 @@
+Definition src/JsxV4.res 8:9
+{"uri": "JsxV4.res", "range": {"start": {"line": 5, "character": 6}, "end": {"line": 5, "character": 10}}}
+
+Complete src/JsxV4.res 11:20
+posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:4->11:20]
+JSX <M4:[11:4->11:6] first[11:7->11:12]=...[11:13->11:18] f[11:19->11:20]=...[11:19->11:20]> _children:None
+Completable: Cjsx([M4], f, [first, f])
+Package opens Pervasives.JsxModules.place holder
+Path M4.make
+[{
+    "label": "fun",
+    "kind": 4,
+    "tags": [],
+    "detail": "option<string>",
+    "documentation": null
+  }]
+
+Hover src/JsxV4.res 14:9
+{"contents": {"kind": "markdown", "value": "```rescript\nReact.component<M4.props<string, string, string>>\n```\n\n---\n\n```\n \n```\n```rescript\ntype React.component<'props> = Jsx.component<'props>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C12%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype M4.props<'first, 'fun, 'second> = {\n  first: 'first,\n  fun?: 'fun,\n  second?: 'second,\n}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxV4.res%22%2C3%2C2%5D)\n\n---\n Doc Comment For M4 "}}
+
+Create Interface src/JsxV4.res
+module M4: {
+  @react.component
+  let make: (~first: string, ~fun: string=?, ~second: string=?) => React.element
+}
+module MM: {
+  @react.component
+  let make: unit => React.element
+}
+module Other: {
+  @react.component
+  let make: (~name: string) => React.element
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/LongIdentTest.res.txt b/tests/analysis_tests/tests/src/expected/LongIdentTest.res.txt
new file mode 100644
index 0000000000..1c12fccf8e
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/LongIdentTest.res.txt
@@ -0,0 +1,3 @@
+Hover src/LongIdentTest.res 2:13
+{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/ModuleStuff.res.txt b/tests/analysis_tests/tests/src/expected/ModuleStuff.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/Objects.res.txt b/tests/analysis_tests/tests/src/expected/Objects.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/Patterns.res.txt b/tests/analysis_tests/tests/src/expected/Patterns.res.txt
new file mode 100644
index 0000000000..43cb4b1fc2
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Patterns.res.txt
@@ -0,0 +1,12 @@
+Definition src/Patterns.res 19:10
+{"uri": "Patterns.res", "range": {"start": {"line": 3, "character": 7}, "end": {"line": 3, "character": 10}}}
+
+Definition src/Patterns.res 24:11
+{"uri": "Patterns.res", "range": {"start": {"line": 9, "character": 7}, "end": {"line": 9, "character": 11}}}
+
+Definition src/Patterns.res 27:11
+{"uri": "Patterns.res", "range": {"start": {"line": 11, "character": 7}, "end": {"line": 11, "character": 8}}}
+
+Definition src/Patterns.res 30:11
+{"uri": "Patterns.res", "range": {"start": {"line": 15, "character": 9}, "end": {"line": 15, "character": 11}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/PolyRec.res.txt b/tests/analysis_tests/tests/src/expected/PolyRec.res.txt
new file mode 100644
index 0000000000..64c790174b
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/PolyRec.res.txt
@@ -0,0 +1,3 @@
+Hover src/PolyRec.res 12:10
+{"contents": {"kind": "markdown", "value": "```rescript\n([#Leaf | #Node(int, 'a, 'a)] as 'a)\n```"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/QueryFile.res.txt b/tests/analysis_tests/tests/src/expected/QueryFile.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/RecModules.res.txt b/tests/analysis_tests/tests/src/expected/RecModules.res.txt
new file mode 100644
index 0000000000..62e3e825ce
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/RecModules.res.txt
@@ -0,0 +1,6 @@
+Hover src/RecModules.res 18:12
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule C: {\n  type t\n  let createA: t => A.t\n}\n```"}}
+
+Hover src/RecModules.res 20:12
+{"contents": {"kind": "markdown", "value": "```rescript\nmodule A: {\n  type t\n  let child: t => B.t\n}\n```"}}
+
diff --git a/tests/analysis_tests/tests/src/expected/RecordCompletion.res.txt b/tests/analysis_tests/tests/src/expected/RecordCompletion.res.txt
new file mode 100644
index 0000000000..f19e179217
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/RecordCompletion.res.txt
@@ -0,0 +1,79 @@
+Complete src/RecordCompletion.res 8:9
+posCursor:[8:9] posNoWhite:[8:8] Found expr:[8:3->8:9]
+Completable: Cpath Value[t].n->m
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[t].n->m
+ContextPath Value[t].n
+ContextPath Value[t]
+Path t
+CPPipe env:RecordCompletion
+Path Js.Array2.m
+[{
+    "label": "Js.Array2.mapi",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, ('a, int) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}
+  }, {
+    "label": "Js.Array2.map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}
+  }]
+
+Complete src/RecordCompletion.res 11:13
+posCursor:[11:13] posNoWhite:[11:12] Found expr:[11:3->11:13]
+Completable: Cpath Value[t2].n2.n->m
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[t2].n2.n->m
+ContextPath Value[t2].n2.n
+ContextPath Value[t2].n2
+ContextPath Value[t2]
+Path t2
+CPPipe env:RecordCompletion
+Path Js.Array2.m
+[{
+    "label": "Js.Array2.mapi",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, ('a, int) => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}
+  }, {
+    "label": "Js.Array2.map",
+    "kind": 12,
+    "tags": [],
+    "detail": "(t<'a>, 'a => 'b) => t<'b>",
+    "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}
+  }]
+
+Complete src/RecordCompletion.res 19:7
+posCursor:[19:7] posNoWhite:[19:6] Found expr:[19:3->19:7]
+Pexp_field [19:3->19:4] R.:[19:5->19:7]
+Completable: Cpath Module[R].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[R].""
+Path R.
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"}
+  }]
+
+Complete src/RecordCompletion.res 22:7
+posCursor:[22:7] posNoWhite:[22:6] Found expr:[22:3->22:10]
+Pexp_field [22:3->22:4] R.xx:[22:5->22:10]
+Completable: Cpath Module[R].""
+Package opens Pervasives.JsxModules.place holder
+ContextPath Module[R].""
+Path R.
+[{
+    "label": "name",
+    "kind": 5,
+    "tags": [],
+    "detail": "string",
+    "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"}
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt b/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt
new file mode 100644
index 0000000000..042b5569db
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/RecoveryOnProp.res.txt
@@ -0,0 +1,69 @@
+Complete src/RecoveryOnProp.res 6:26
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[3:3->11:8]
+JSX <div:[3:3->3:6] onClick[4:4->4:11]=...[4:13->0:-1]> _children:None
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[4:13->8:6]
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[4:13->8:6]
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[5:6->8:5]
+posCursor:[6:26] posNoWhite:[6:25] Found expr:[6:16->8:5]
+posCursor:[6:26] posNoWhite:[6:25] Found pattern:[6:20->8:5]
+posCursor:[6:26] posNoWhite:[6:25] Found type:[6:23->8:5]
+Ptyp_constr Res:[6:23->8:5]
+posCursor:[6:26] posNoWhite:[6:25] Found pattern:[6:20->8:5]
+posCursor:[6:26] posNoWhite:[6:25] Found type:[6:23->8:5]
+Ptyp_constr Res:[6:23->8:5]
+Completable: Cpath Type[Res]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Type[Res]
+Path Res
+[{
+    "label": "RescriptReactErrorBoundary",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptReactErrorBoundary",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptReactErrorBoundary",
+      "filePath": "src/RecoveryOnProp.res"
+    }
+  }, {
+    "label": "RescriptReactRouter",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptReactRouter",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptReactRouter",
+      "filePath": "src/RecoveryOnProp.res"
+    }
+  }, {
+    "label": "RescriptTools",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptTools",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptTools",
+      "filePath": "src/RecoveryOnProp.res"
+    }
+  }, {
+    "label": "RescriptTools_Docgen",
+    "kind": 9,
+    "tags": [],
+    "detail": "module RescriptTools_Docgen",
+    "documentation": null,
+    "data": {
+      "modulePath": "RescriptTools_Docgen",
+      "filePath": "src/RecoveryOnProp.res"
+    }
+  }, {
+    "label": "Result",
+    "kind": 9,
+    "tags": [],
+    "detail": "module Result",
+    "documentation": null,
+    "data": {
+      "modulePath": "Result",
+      "filePath": "src/RecoveryOnProp.res"
+    }
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/References.res.txt b/tests/analysis_tests/tests/src/expected/References.res.txt
new file mode 100644
index 0000000000..842250d452
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/References.res.txt
@@ -0,0 +1,31 @@
+References src/References.res 0:4
+[
+{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 26}, "end": {"line": 0, "character": 27}}},
+{"uri": "Cross.res", "range": {"start": {"line": 3, "character": 27}, "end": {"line": 3, "character": 28}}},
+{"uri": "Cross.res", "range": {"start": {"line": 7, "character": 27}, "end": {"line": 7, "character": 28}}},
+{"uri": "References.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}},
+{"uri": "References.res", "range": {"start": {"line": 3, "character": 8}, "end": {"line": 3, "character": 9}}},
+{"uri": "References.res", "range": {"start": {"line": 7, "character": 8}, "end": {"line": 7, "character": 9}}}
+]
+
+References src/References.res 9:19
+[
+{"uri": "References.res", "range": {"start": {"line": 9, "character": 11}, "end": {"line": 9, "character": 14}}},
+{"uri": "References.res", "range": {"start": {"line": 9, "character": 19}, "end": {"line": 9, "character": 21}}}
+]
+
+References src/References.res 20:12
+[
+{"uri": "References.res", "range": {"start": {"line": 13, "character": 2}, "end": {"line": 13, "character": 13}}},
+{"uri": "References.res", "range": {"start": {"line": 18, "character": 11}, "end": {"line": 18, "character": 13}}},
+{"uri": "References.res", "range": {"start": {"line": 20, "character": 11}, "end": {"line": 20, "character": 13}}}
+]
+
+References src/References.res 23:15
+[
+{"uri": "ReferencesInner.res", "range": {"start": {"line": 1, "character": 28}, "end": {"line": 1, "character": 32}}},
+{"uri": "References.res", "range": {"start": {"line": 23, "character": 19}, "end": {"line": 23, "character": 23}}},
+{"uri": "ComponentInner.res", "range": {"start": {"line": 1, "character": 4}, "end": {"line": 1, "character": 8}}},
+{"uri": "ComponentInner.resi", "range": {"start": {"line": 1, "character": 4}, "end": {"line": 1, "character": 8}}}
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.res.txt b/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.res.txt
new file mode 100644
index 0000000000..33f2d105d6
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.res.txt
@@ -0,0 +1,9 @@
+References src/ReferencesWithInterface.res 0:4
+[
+{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 52}, "end": {"line": 9, "character": 53}}},
+{"uri": "Cross.res", "range": {"start": {"line": 12, "character": 53}, "end": {"line": 12, "character": 54}}},
+{"uri": "Cross.res", "range": {"start": {"line": 16, "character": 53}, "end": {"line": 16, "character": 54}}},
+{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}},
+{"uri": "ReferencesWithInterface.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}}
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.resi.txt b/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.resi.txt
new file mode 100644
index 0000000000..3e96fbc75c
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/ReferencesWithInterface.resi.txt
@@ -0,0 +1,9 @@
+References src/ReferencesWithInterface.resi 0:4
+[
+{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 52}, "end": {"line": 9, "character": 53}}},
+{"uri": "Cross.res", "range": {"start": {"line": 12, "character": 53}, "end": {"line": 12, "character": 54}}},
+{"uri": "Cross.res", "range": {"start": {"line": 16, "character": 53}, "end": {"line": 16, "character": 54}}},
+{"uri": "ReferencesWithInterface.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}},
+{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}}
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/Rename.res.txt b/tests/analysis_tests/tests/src/expected/Rename.res.txt
new file mode 100644
index 0000000000..5cd2adfee4
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Rename.res.txt
@@ -0,0 +1,37 @@
+Rename src/Rename.res 0:4 y
+[
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Rename.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "y"
+  }, {
+  "range": {"start": {"line": 3, "character": 8}, "end": {"line": 3, "character": 9}},
+  "newText": "y"
+  }, {
+  "range": {"start": {"line": 7, "character": 8}, "end": {"line": 7, "character": 9}},
+  "newText": "y"
+  }]
+  }
+]
+
+Rename src/Rename.res 9:19 yy
+[
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Rename.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 9, "character": 11}, "end": {"line": 9, "character": 14}},
+  "newText": "yy"
+  }, {
+  "range": {"start": {"line": 9, "character": 19}, "end": {"line": 9, "character": 21}},
+  "newText": "yy"
+  }]
+  }
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/RenameWithInterface.res.txt b/tests/analysis_tests/tests/src/expected/RenameWithInterface.res.txt
new file mode 100644
index 0000000000..a13988fa94
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/RenameWithInterface.res.txt
@@ -0,0 +1,37 @@
+Rename src/RenameWithInterface.res 0:4 y
+[
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.resi"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "y"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "y"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Cross.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}},
+  "newText": "y"
+  }, {
+  "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}},
+  "newText": "y"
+  }]
+  }
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/RenameWithInterface.resi.txt b/tests/analysis_tests/tests/src/expected/RenameWithInterface.resi.txt
new file mode 100644
index 0000000000..2a1dabb444
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/RenameWithInterface.resi.txt
@@ -0,0 +1,37 @@
+Rename src/RenameWithInterface.resi 0:4 y
+[
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.resi"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "y"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "RenameWithInterface.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
+  "newText": "y"
+  }]
+  },
+{
+  "textDocument": {
+  "version": null,
+  "uri": "Cross.res"
+  },
+  "edits": [{
+  "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}},
+  "newText": "y"
+  }, {
+  "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}},
+  "newText": "y"
+  }]
+  }
+]
+
diff --git a/tests/analysis_tests/tests/src/expected/Reprod.res.txt b/tests/analysis_tests/tests/src/expected/Reprod.res.txt
new file mode 100644
index 0000000000..bbe3d02558
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Reprod.res.txt
@@ -0,0 +1,224 @@
+Complete src/Reprod.res 7:53
+posCursor:[7:53] posNoWhite:[7:52] Found expr:[7:11->7:56]
+Pexp_apply ...[7:11->7:20] (~variables7:22->7:31=...[7:32->7:55])
+Completable: Cexpression CArgument Value[Query, use](~variables)->recordField(location), variantPayload::ByAddress($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath CArgument Value[Query, use](~variables)
+ContextPath Value[Query, use]
+Path Query.use
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "input_ByAddress",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype input_ByAddress = {city: string}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 33:28
+posCursor:[33:28] posNoWhite:[33:27] Found pattern:[33:21->33:31]
+Completable: Cpattern Value[record]->recordField(first)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[record]
+Path record
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 36:29
+posCursor:[36:29] posNoWhite:[36:28] Found pattern:[36:21->36:32]
+Completable: Cpattern Value[record]->recordField(second)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[record]
+Path record
+[{
+    "label": "{}",
+    "kind": 22,
+    "tags": [],
+    "detail": "SchemaAssets.input_ByAddress",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype SchemaAssets.input_ByAddress = {city: string}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 43:21
+posCursor:[43:21] posNoWhite:[43:20] Found pattern:[43:18->43:22]
+Ppat_construct Ok:[43:18->43:20]
+posCursor:[43:21] posNoWhite:[43:20] Found pattern:[43:20->43:22]
+Ppat_construct ():[43:20->43:22]
+Completable: Cpattern Value[res]->variantPayload::Ok($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 46:24
+posCursor:[46:24] posNoWhite:[46:23] Found pattern:[46:18->46:25]
+Ppat_construct Error:[46:18->46:23]
+posCursor:[46:24] posNoWhite:[46:23] Found pattern:[46:23->46:25]
+Ppat_construct ():[46:23->46:25]
+Completable: Cpattern Value[res]->variantPayload::Error($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[res]
+Path res
+[{
+    "label": "#one",
+    "kind": 4,
+    "tags": [],
+    "detail": "#one",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#one\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#one",
+    "insertTextFormat": 2
+  }, {
+    "label": "#three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#three(someRecord, bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "#two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "#two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\n#two(bool)\n```\n\n```rescript\n[#one | #three(someRecord, bool) | #two(bool)]\n```"},
+    "insertText": "#two(${1:_})",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 51:24
+posCursor:[51:24] posNoWhite:[51:23] Found pattern:[51:21->51:25]
+Ppat_construct Ok:[51:21->51:23]
+posCursor:[51:24] posNoWhite:[51:23] Found pattern:[51:23->51:25]
+Ppat_construct ():[51:23->51:25]
+Completable: Cpattern Value[resOpt]->variantPayload::Ok($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[resOpt]
+Path resOpt
+[{
+    "label": "None",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"}
+  }, {
+    "label": "Some(_)",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Some(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(One)",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Some(One)",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(Two(_))",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Some(Two(${1:_}))",
+    "insertTextFormat": 2
+  }, {
+    "label": "Some(Three(_, _))",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Some(Three(${1:_}, ${2:_}))",
+    "insertTextFormat": 2
+  }]
+
+Complete src/Reprod.res 54:29
+posCursor:[54:29] posNoWhite:[54:28] Found pattern:[54:21->54:31]
+Ppat_construct Ok:[54:21->54:23]
+posCursor:[54:29] posNoWhite:[54:28] Found pattern:[54:24->54:30]
+Ppat_construct Some:[54:24->54:28]
+posCursor:[54:29] posNoWhite:[54:28] Found pattern:[54:28->54:30]
+Ppat_construct ():[54:28->54:30]
+Completable: Cpattern Value[resOpt]->variantPayload::Ok($0), variantPayload::Some($0)
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[resOpt]
+Path resOpt
+[{
+    "label": "One",
+    "kind": 4,
+    "tags": [],
+    "detail": "One",
+    "documentation": {"kind": "markdown", "value": "```rescript\nOne\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "One",
+    "insertTextFormat": 2
+  }, {
+    "label": "Two(_)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Two(bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nTwo(bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Two(${1:_})",
+    "insertTextFormat": 2
+  }, {
+    "label": "Three(_, _)",
+    "kind": 4,
+    "tags": [],
+    "detail": "Three(someRecord, bool)",
+    "documentation": {"kind": "markdown", "value": "```rescript\nThree(someRecord, bool)\n```\n\n```rescript\ntype someVariant = One | Two(bool) | Three(someRecord, bool)\n```"},
+    "insertText": "Three(${1:_}, ${2:_})",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/SchemaAssets.res.txt b/tests/analysis_tests/tests/src/expected/SchemaAssets.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/ShadowedBelt.res.txt b/tests/analysis_tests/tests/src/expected/ShadowedBelt.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/SignatureHelp.res.txt b/tests/analysis_tests/tests/src/expected/SignatureHelp.res.txt
new file mode 100644
index 0000000000..09576665b9
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/SignatureHelp.res.txt
@@ -0,0 +1,578 @@
+Signature help src/SignatureHelp.res 16:20
+posCursor:[16:19] posNoWhite:[16:18] Found expr:[16:11->16:20]
+Pexp_apply ...[16:11->16:19] (...[46:0->16:20])
+posCursor:[16:19] posNoWhite:[16:18] Found expr:[16:11->16:19]
+Pexp_ident someFunc:[16:11->16:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: unlabelled<0>
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 19:21
+posCursor:[19:19] posNoWhite:[19:18] Found expr:[19:11->19:21]
+Pexp_apply ...[19:11->19:19] (...[19:20->19:21])
+posCursor:[19:19] posNoWhite:[19:18] Found expr:[19:11->19:19]
+Pexp_ident someFunc:[19:11->19:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: unlabelled<0>
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 22:29
+posCursor:[22:19] posNoWhite:[22:18] Found expr:[22:11->22:29]
+Pexp_apply ...[22:11->22:19] (...[22:20->22:23], ~two22:26->22:29=...[22:26->22:29])
+posCursor:[22:19] posNoWhite:[22:18] Found expr:[22:11->22:19]
+Pexp_ident someFunc:[22:11->22:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: ~two
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 25:33
+posCursor:[25:19] posNoWhite:[25:18] Found expr:[25:11->25:35]
+Pexp_apply ...[25:11->25:19] (...[25:20->25:23], ~two25:26->25:29=...[25:30->25:35])
+posCursor:[25:19] posNoWhite:[25:18] Found expr:[25:11->25:19]
+Pexp_ident someFunc:[25:11->25:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: ~two
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 28:38
+posCursor:[28:19] posNoWhite:[28:18] Found expr:[28:11->28:42]
+Pexp_apply ...[28:11->28:19] (...[28:20->28:23], ~two28:26->28:29=...[28:30->28:35], ~four28:38->28:42=...[28:38->28:42])
+posCursor:[28:19] posNoWhite:[28:18] Found expr:[28:11->28:19]
+Pexp_ident someFunc:[28:11->28:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: ~four
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 3
+}
+
+Signature help src/SignatureHelp.res 31:42
+posCursor:[31:19] posNoWhite:[31:18] Found expr:[31:11->31:44]
+Pexp_apply ...[31:11->31:19] (...[31:20->31:23], ~two31:26->31:29=...[31:30->31:35], ~four31:38->31:42=...[31:43->31:44])
+posCursor:[31:19] posNoWhite:[31:18] Found expr:[31:11->31:19]
+Pexp_ident someFunc:[31:11->31:19]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: ~four
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 3
+}
+
+Signature help src/SignatureHelp.res 34:21
+posCursor:[34:20] posNoWhite:[34:19] Found expr:[34:11->34:21]
+Pexp_apply ...[34:11->34:20] (...[46:0->34:21])
+posCursor:[34:20] posNoWhite:[34:19] Found expr:[34:11->34:20]
+Pexp_ident otherFunc:[34:11->34:20]
+Completable: Cpath Value[otherFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[otherFunc]
+Path otherFunc
+argAtCursor: unlabelled<0>
+extracted params: 
+[string, int, float]
+{
+  "signatures": [{
+    "label": "(string, int, float) => unit",
+    "parameters": [{"label": [1, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [9, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 19], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 37:24
+posCursor:[37:20] posNoWhite:[37:19] Found expr:[37:11->37:26]
+Pexp_apply ...[37:11->37:20] (...[37:21->37:26])
+posCursor:[37:20] posNoWhite:[37:19] Found expr:[37:11->37:20]
+Pexp_ident otherFunc:[37:11->37:20]
+Completable: Cpath Value[otherFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[otherFunc]
+Path otherFunc
+argAtCursor: unlabelled<0>
+extracted params: 
+[string, int, float]
+{
+  "signatures": [{
+    "label": "(string, int, float) => unit",
+    "parameters": [{"label": [1, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [9, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 19], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 40:35
+posCursor:[40:20] posNoWhite:[40:19] Found expr:[40:11->40:39]
+Pexp_apply ...[40:11->40:20] (...[40:21->40:26], ...[40:28->40:31], ...[40:33->40:38])
+posCursor:[40:20] posNoWhite:[40:19] Found expr:[40:11->40:20]
+Pexp_ident otherFunc:[40:11->40:20]
+Completable: Cpath Value[otherFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[otherFunc]
+Path otherFunc
+argAtCursor: unlabelled<2>
+extracted params: 
+[string, int, float]
+{
+  "signatures": [{
+    "label": "(string, int, float) => unit",
+    "parameters": [{"label": [1, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [9, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 19], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 2
+}
+
+Signature help src/SignatureHelp.res 43:33
+posCursor:[43:29] posNoWhite:[43:28] Found expr:[43:11->43:34]
+Pexp_apply ...[43:11->43:29] (~age43:31->43:34=...[43:31->43:34])
+posCursor:[43:29] posNoWhite:[43:28] Found expr:[43:11->43:29]
+Pexp_ident Completion.Lib.foo:[43:11->43:29]
+Completable: Cpath Value[Completion, Lib, foo]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[Completion, Lib, foo]
+Path Completion.Lib.foo
+argAtCursor: ~age
+extracted params: 
+[~age: int, ~name: string]
+{
+  "signatures": [{
+    "label": "(~age: int, ~name: string) => string",
+    "parameters": [{"label": [1, 10], "documentation": {"kind": "markdown", "value": ""}}, {"label": [12, 25], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 50:24
+posCursor:[50:23] posNoWhite:[50:22] Found expr:[50:11->50:24]
+Pexp_apply ...[50:11->50:23] (...[56:0->50:24])
+posCursor:[50:23] posNoWhite:[50:22] Found expr:[50:11->50:23]
+Pexp_ident iAmSoSpecial:[50:11->50:23]
+Completable: Cpath Value[iAmSoSpecial]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[iAmSoSpecial]
+Path iAmSoSpecial
+argAtCursor: unlabelled<0>
+extracted params: 
+[string]
+{
+  "signatures": [{
+    "label": "string => unit",
+    "parameters": [{"label": [0, 6], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 53:31
+posCursor:[53:29] posNoWhite:[53:28] Found expr:[53:11->53:31]
+posCursor:[53:29] posNoWhite:[53:28] Found expr:[53:20->53:31]
+Pexp_apply ...[53:20->53:29] (...[53:30->53:31])
+posCursor:[53:29] posNoWhite:[53:28] Found expr:[53:20->53:29]
+Pexp_ident otherFunc:[53:20->53:29]
+Completable: Cpath Value[otherFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[otherFunc]
+Path otherFunc
+argAtCursor: unlabelled<1>
+extracted params: 
+[string, int, float]
+{
+  "signatures": [{
+    "label": "(string, int, float) => unit",
+    "parameters": [{"label": [1, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [9, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 19], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 62:17
+posCursor:[62:13] posNoWhite:[62:12] Found expr:[62:11->62:19]
+Pexp_apply ...[62:11->62:13] (...[62:14->62:16])
+posCursor:[62:13] posNoWhite:[62:12] Found expr:[62:11->62:13]
+Pexp_ident fn:[62:11->62:13]
+Completable: Cpath Value[fn]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fn]
+Path fn
+argAtCursor: unlabelled<1>
+extracted params: 
+[int, string, int]
+{
+  "signatures": [{
+    "label": "(int, string, int) => unit",
+    "parameters": [{"label": [1, 4], "documentation": {"kind": "markdown", "value": ""}}, {"label": [6, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 17], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 65:17
+posCursor:[65:13] posNoWhite:[65:12] Found expr:[65:11->65:25]
+Pexp_apply ...[65:11->65:13] (...[65:14->65:16], ...[65:20->65:24])
+posCursor:[65:13] posNoWhite:[65:12] Found expr:[65:11->65:13]
+Pexp_ident fn:[65:11->65:13]
+Completable: Cpath Value[fn]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fn]
+Path fn
+argAtCursor: unlabelled<1>
+extracted params: 
+[int, string, int]
+{
+  "signatures": [{
+    "label": "(int, string, int) => unit",
+    "parameters": [{"label": [1, 4], "documentation": {"kind": "markdown", "value": ""}}, {"label": [6, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 17], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 68:26
+posCursor:[68:13] posNoWhite:[68:12] Found expr:[68:11->68:28]
+Pexp_apply ...[68:11->68:13] (...[68:14->68:16], ...[68:18->68:25])
+posCursor:[68:13] posNoWhite:[68:12] Found expr:[68:11->68:13]
+Pexp_ident fn:[68:11->68:13]
+Completable: Cpath Value[fn]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[fn]
+Path fn
+argAtCursor: unlabelled<2>
+extracted params: 
+[int, string, int]
+{
+  "signatures": [{
+    "label": "(int, string, int) => unit",
+    "parameters": [{"label": [1, 4], "documentation": {"kind": "markdown", "value": ""}}, {"label": [6, 12], "documentation": {"kind": "markdown", "value": ""}}, {"label": [14, 17], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 2
+}
+
+Signature help src/SignatureHelp.res 71:29
+posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:11->71:33]
+Pexp_apply ...[71:11->71:13] (...[71:16->71:30])
+posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:16->71:30]
+Pexp_apply ...[71:16->71:28] (...[71:29->71:30])
+posCursor:[71:28] posNoWhite:[71:27] Found expr:[71:16->71:28]
+Pexp_ident iAmSoSpecial:[71:16->71:28]
+Completable: Cpath Value[iAmSoSpecial]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[iAmSoSpecial]
+Path iAmSoSpecial
+argAtCursor: unlabelled<0>
+extracted params: 
+[string]
+{
+  "signatures": [{
+    "label": "string => unit",
+    "parameters": [{"label": [0, 6], "documentation": {"kind": "markdown", "value": ""}}]
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 74:40
+posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:11->74:47]
+Pexp_apply ...[74:11->74:13] (...[74:16->74:44])
+posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:16->74:44]
+Pexp_apply ...[74:16->74:28] (...[74:31->74:41])
+posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:31->74:41]
+Pexp_apply ...[74:31->74:39] (...[74:40->74:41])
+posCursor:[74:39] posNoWhite:[74:38] Found expr:[74:31->74:39]
+Pexp_ident someFunc:[74:31->74:39]
+Completable: Cpath Value[someFunc]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[someFunc]
+Path someFunc
+argAtCursor: unlabelled<0>
+extracted params: 
+[int, ~two: string=?, ~three: unit => unit, ~four: someVariant, unit]
+{
+  "signatures": [{
+    "label": "(\n  int,\n  ~two: string=?,\n  ~three: unit => unit,\n  ~four: someVariant,\n  unit,\n) => unit",
+    "parameters": [{"label": [4, 7], "documentation": {"kind": "markdown", "value": ""}}, {"label": [11, 25], "documentation": {"kind": "markdown", "value": ""}}, {"label": [29, 49], "documentation": {"kind": "markdown", "value": ""}}, {"label": [53, 71], "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C0%2C0%5D)"}}, {"label": [75, 79], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Does stuff. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 85:16
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": -1
+}
+
+Signature help src/SignatureHelp.res 88:18
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 91:23
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 2
+}
+
+Signature help src/SignatureHelp.res 94:15
+{
+  "signatures": [{
+    "label": "Two(mySpecialThing)",
+    "parameters": [{"label": [4, 18], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}],
+    "documentation": {"kind": "markdown", "value": " Two is fun! "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 97:19
+{
+  "signatures": [{
+    "label": "Three(mySpecialThing, array<option<string>>)",
+    "parameters": [{"label": [6, 20], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}, {"label": [22, 43], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Three is... three "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 100:24
+{
+  "signatures": [{
+    "label": "Three(mySpecialThing, array<option<string>>)",
+    "parameters": [{"label": [6, 20], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}, {"label": [22, 43], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Three is... three "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 105:9
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": -1
+}
+
+Signature help src/SignatureHelp.res 113:42
+argAtCursor: unlabelled<1>
+extracted params: 
+[array<int>, int => int]
+{
+  "signatures": [{
+    "label": "(array<int>, int => int) => array<int>",
+    "parameters": [{"label": [1, 11], "documentation": {"kind": "markdown", "value": ""}}, {"label": [13, 23], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": "\n`map(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`.\n\nSee [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on MDN.\n\n## Examples\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\nlet mappedArray = array->Array.map(greeting => greeting ++ \" to you\")\n\nConsole.log(mappedArray) // [\"Hello to you\", \"Hi to you\", \"Good bye to you\"]\n```\n"}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 132:18
+argAtCursor: unlabelled<0>
+extracted params: 
+[x, tt]
+{
+  "signatures": [{
+    "label": "(x, tt) => string",
+    "parameters": [{"label": [1, 2], "documentation": {"kind": "markdown", "value": "```rescript\ntype x = {age?: int, name?: string}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C117%2C0%5D)"}}, {"label": [4, 6], "documentation": {"kind": "markdown", "value": "```rescript\ntype tt = One\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C123%2C0%5D)"}}],
+    "documentation": {"kind": "markdown", "value": " Some stuff "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 135:22
+argAtCursor: unlabelled<1>
+extracted params: 
+[x, tt]
+{
+  "signatures": [{
+    "label": "(x, tt) => string",
+    "parameters": [{"label": [1, 2], "documentation": {"kind": "markdown", "value": "```rescript\ntype x = {age?: int, name?: string}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C117%2C0%5D)"}}, {"label": [4, 6], "documentation": {"kind": "markdown", "value": "```rescript\ntype tt = One\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C123%2C0%5D)"}}],
+    "documentation": {"kind": "markdown", "value": " Some stuff "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 139:8
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 2
+}
+
+Signature help src/SignatureHelp.res 141:7
+{
+  "signatures": [{
+    "label": "One({miss?: bool, hit?: bool, stuff?: string})",
+    "parameters": [{"label": [0, 0], "documentation": {"kind": "markdown", "value": ""}}, {"label": [5, 16], "documentation": {"kind": "markdown", "value": ""}}, {"label": [18, 28], "documentation": {"kind": "markdown", "value": ""}}, {"label": [30, 44], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " One is cool. "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 143:7
+{
+  "signatures": [{
+    "label": "Two(mySpecialThing)",
+    "parameters": [{"label": [4, 18], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}],
+    "documentation": {"kind": "markdown", "value": " Two is fun! "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 145:9
+{
+  "signatures": [{
+    "label": "Three(mySpecialThing, array<option<string>>)",
+    "parameters": [{"label": [6, 20], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}, {"label": [22, 43], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Three is... three "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 147:12
+{
+  "signatures": [{
+    "label": "Three(mySpecialThing, array<option<string>>)",
+    "parameters": [{"label": [6, 20], "documentation": {"kind": "markdown", "value": "```rescript\ntype mySpecialThing = string\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22SignatureHelp.res%22%2C78%2C0%5D)"}}, {"label": [22, 43], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": " Three is... three "}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 1
+}
+
+Signature help src/SignatureHelp.res 151:14
+{
+  "signatures": [{
+    "label": "Ok(bool)",
+    "parameters": [{"label": [3, 7], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": "```rescript\nresult<bool, 'a>\n```"}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 154:19
+{
+  "signatures": [{
+    "label": "Error(string)",
+    "parameters": [{"label": [6, 12], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": "```rescript\nresult<'a, string>\n```"}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
+Signature help src/SignatureHelp.res 157:16
+{
+  "signatures": [{
+    "label": "Some(bool)",
+    "parameters": [{"label": [5, 9], "documentation": {"kind": "markdown", "value": ""}}],
+    "documentation": {"kind": "markdown", "value": "```rescript\noption<bool>\n```"}
+  }],
+  "activeSignature": 0,
+  "activeParameter": 0
+}
+
diff --git a/tests/analysis_tests/tests/src/expected/TableclothMap.res.txt b/tests/analysis_tests/tests/src/expected/TableclothMap.res.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/TableclothMap.resi.txt b/tests/analysis_tests/tests/src/expected/TableclothMap.resi.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/expected/TypeAtPosCompletion.res.txt b/tests/analysis_tests/tests/src/expected/TypeAtPosCompletion.res.txt
new file mode 100644
index 0000000000..1d6de28853
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/TypeAtPosCompletion.res.txt
@@ -0,0 +1,56 @@
+Complete src/TypeAtPosCompletion.res 7:17
+posCursor:[7:17] posNoWhite:[7:15] Found expr:[6:16->9:1]
+Completable: Cexpression CTypeAtPos()->recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CTypeAtPos()
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage?: int\n```\n\n```rescript\ntype optRecord = {name: string, age: option<int>, online: option<bool>}\n```"}
+  }, {
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline?: bool\n```\n\n```rescript\ntype optRecord = {name: string, age: option<int>, online: option<bool>}\n```"}
+  }]
+
+Complete src/TypeAtPosCompletion.res 16:18
+posCursor:[16:18] posNoWhite:[16:16] Found expr:[13:8->19:1]
+Pexp_construct One:[13:8->13:11] [13:11->19:1]
+posCursor:[16:18] posNoWhite:[16:16] Found expr:[15:2->18:3]
+Completable: Cexpression CTypeAtPos()->variantPayload::One($1), recordBody
+Package opens Pervasives.JsxModules.place holder
+ContextPath CTypeAtPos()
+[{
+    "label": "age",
+    "kind": 5,
+    "tags": [],
+    "detail": "int",
+    "documentation": {"kind": "markdown", "value": "```rescript\nage?: int\n```\n\n```rescript\ntype optRecord = {name: string, age: option<int>, online: option<bool>}\n```"}
+  }, {
+    "label": "online",
+    "kind": 5,
+    "tags": [],
+    "detail": "bool",
+    "documentation": {"kind": "markdown", "value": "```rescript\nonline?: bool\n```\n\n```rescript\ntype optRecord = {name: string, age: option<int>, online: option<bool>}\n```"}
+  }]
+
+Complete src/TypeAtPosCompletion.res 22:12
+posCursor:[22:12] posNoWhite:[22:11] Found expr:[21:10->24:1]
+Completable: Cexpression CTypeAtPos()->array
+Package opens Pervasives.JsxModules.place holder
+ContextPath CTypeAtPos()
+[{
+    "label": "{}",
+    "kind": 12,
+    "tags": [],
+    "detail": "optRecord",
+    "documentation": {"kind": "markdown", "value": "```rescript\ntype optRecord = {name: string, age: option<int>, online: option<bool>}\n```"},
+    "sortText": "A",
+    "insertText": "{$0}",
+    "insertTextFormat": 2
+  }]
+
diff --git a/tests/analysis_tests/tests/src/expected/TypeDefinition.res.txt b/tests/analysis_tests/tests/src/expected/TypeDefinition.res.txt
new file mode 100644
index 0000000000..46a968e8f3
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/TypeDefinition.res.txt
@@ -0,0 +1,18 @@
+TypeDefinition src/TypeDefinition.res 2:9
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 2, "character": 5}, "end": {"line": 2, "character": 11}}}
+
+TypeDefinition src/TypeDefinition.res 5:4
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 24}}}
+
+TypeDefinition src/TypeDefinition.res 8:4
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 28}}}
+
+TypeDefinition src/TypeDefinition.res 13:4
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 11, "character": 0}, "end": {"line": 11, "character": 26}}}
+
+TypeDefinition src/TypeDefinition.res 16:13
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 28}}}
+
+TypeDefinition src/TypeDefinition.res 20:9
+{"uri": "TypeDefinition.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 24}}}
+
diff --git a/tests/analysis_tests/tests/src/expected/Xform.res.txt b/tests/analysis_tests/tests/src/expected/Xform.res.txt
new file mode 100644
index 0000000000..b37acf31db
--- /dev/null
+++ b/tests/analysis_tests/tests/src/expected/Xform.res.txt
@@ -0,0 +1,337 @@
+Xform src/Xform.res 6:5
+posCursor:[6:3] posNoWhite:[6:1] Found expr:[6:0->11:1]
+Completable: Cpath Value[kind]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[kind]
+Path kind
+Package opens Pervasives.JsxModules.place holder
+Hit: Replace with switch
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 6, "character": 0}, "end": {"line": 11, "character": 1}}
+newText:
+<--here
+switch kind {
+| First =>
+  // ^xfm
+  ret("First")
+| _ => ret("Not First")
+}
+
+Xform src/Xform.res 13:15
+Hit: Replace with switch
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 13, "character": 0}, "end": {"line": 13, "character": 79}}
+newText:
+<--here
+switch kind {
+| #kind("First", {name: "abc", age: 3}) => ret("First")
+| _ => ret("Not First")
+}
+
+Xform src/Xform.res 16:5
+Hit: Add type annotation
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 16, "character": 8}, "end": {"line": 16, "character": 8}}
+newText:
+        <--here
+        : string
+Hit: Add Documentation template
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 16, "character": 0}, "end": {"line": 16, "character": 18}}
+newText:
+<--here
+/**
+
+*/
+let name = "hello"
+
+Xform src/Xform.res 19:5
+Hit: Add Documentation template
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 19, "character": 0}, "end": {"line": 19, "character": 23}}
+newText:
+<--here
+/**
+
+*/
+let annotated: int = 34
+
+Xform src/Xform.res 26:10
+Hit: Add type annotation
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 26, "character": 10}, "end": {"line": 26, "character": 11}}
+newText:
+          <--here
+          (x: option<T.r>)
+
+Xform src/Xform.res 30:9
+Hit: Add braces to function
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 26, "character": 0}, "end": {"line": 32, "character": 3}}
+newText:
+<--here
+let foo = x => {
+  //      ^xfm
+  switch x {
+  | None => 33
+  | Some(q) => q.T.a + 1
+  //     ^xfm
+  }
+}
+
+Xform src/Xform.res 34:21
+Hit: Add type annotation
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 34, "character": 24}, "end": {"line": 34, "character": 24}}
+newText:
+                        <--here
+                        : int
+
+Xform src/Xform.res 38:5
+Hit: Add Documentation template
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 37, "character": 0}, "end": {"line": 38, "character": 40}}
+newText:
+<--here
+/**
+
+*/
+@react.component
+let make = (~name) => React.string(name)
+
+Xform src/Xform.res 41:9
+Hit: Add type annotation
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 41, "character": 11}, "end": {"line": 41, "character": 11}}
+newText:
+           <--here
+           : int
+
+Xform src/Xform.res 48:21
+posCursor:[48:21] posNoWhite:[48:19] Found expr:[48:15->48:25]
+posCursor:[48:21] posNoWhite:[48:19] Found expr:[48:15->48:25]
+Completable: Cpath Value[name]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[name]
+Path name
+Package opens Pervasives.JsxModules.place holder
+Hit: Add braces to function
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 48, "character": 0}, "end": {"line": 48, "character": 25}}
+newText:
+<--here
+let noBraces = () => {
+  name
+}
+
+Xform src/Xform.res 52:34
+Hit: Add braces to function
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 51, "character": 0}, "end": {"line": 54, "character": 1}}
+newText:
+<--here
+let nested = () => {
+  let _noBraces = (_x, _y, _z) => {
+    "someNewFunc"
+  }
+  //                              ^xfm
+}
+
+Xform src/Xform.res 62:6
+Hit: Add braces to function
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 58, "character": 4}, "end": {"line": 62, "character": 7}}
+newText:
+    <--here
+    let foo = (_x, y, _z) => {
+      switch y {
+      | #some => 3
+      | #stuff => 4
+      }
+    }
+
+Xform src/Xform.res 72:5
+Hit: Extract local module "ExtractableModule" to file "ExtractableModule.res"
+
+CreateFile: ExtractableModule.res
+
+TextDocumentEdit: ExtractableModule.res
+{"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}
+newText:
+<--here
+/** Doc comment. */
+type t = int
+// A comment here
+let doStuff = a => a + 1
+// ^xfm
+
+
+TextDocumentEdit: src/Xform.res
+{"start": {"line": 68, "character": 0}, "end": {"line": 74, "character": 1}}
+newText:
+<--here
+
+
+Xform src/Xform.res 80:4
+posCursor:[78:16] posNoWhite:[78:14] Found expr:[78:9->82:1]
+Completable: Cpath Value[variant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variant]
+Path variant
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 80, "character": 2}, "end": {"line": 80, "character": 3}}
+newText:
+  <--here
+  Second | Third | Fourth(_)
+
+Xform src/Xform.res 86:4
+posCursor:[84:16] posNoWhite:[84:14] Found expr:[84:9->88:1]
+Completable: Cpath Value[variant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variant]
+Path variant
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 86, "character": 2}, "end": {"line": 86, "character": 3}}
+newText:
+  <--here
+  Third | Fourth(_)
+
+Xform src/Xform.res 93:4
+posCursor:[90:16] posNoWhite:[90:14] Found expr:[90:9->95:1]
+Completable: Cpath Value[variant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variant]
+Path variant
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 93, "character": 2}, "end": {"line": 93, "character": 3}}
+newText:
+  <--here
+  First | Third | Fourth(_)
+
+Xform src/Xform.res 101:4
+posCursor:[99:16] posNoWhite:[99:14] Found expr:[99:9->103:1]
+Completable: Cpath Value[polyvariant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[polyvariant]
+Path polyvariant
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 101, "character": 2}, "end": {"line": 101, "character": 3}}
+newText:
+  <--here
+  #second | #"illegal identifier" | #third(_)
+
+Xform src/Xform.res 107:4
+posCursor:[105:16] posNoWhite:[105:14] Found expr:[105:9->109:1]
+Completable: Cpath Value[polyvariant]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[polyvariant]
+Path polyvariant
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 107, "character": 2}, "end": {"line": 107, "character": 3}}
+newText:
+  <--here
+  #"illegal identifier" | #third(_)
+
+Xform src/Xform.res 115:4
+posCursor:[113:16] posNoWhite:[113:14] Found expr:[113:9->117:1]
+Completable: Cpath Value[variantOpt]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variantOpt]
+Path variantOpt
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 115, "character": 2}, "end": {"line": 115, "character": 3}}
+newText:
+  <--here
+  Some(Second | Third | Fourth(_)) | None
+
+Xform src/Xform.res 121:4
+posCursor:[119:16] posNoWhite:[119:14] Found expr:[119:9->123:1]
+Completable: Cpath Value[variantOpt]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variantOpt]
+Path variantOpt
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 121, "character": 2}, "end": {"line": 121, "character": 3}}
+newText:
+  <--here
+  Some(Third | Fourth(_)) | None
+
+Xform src/Xform.res 127:4
+posCursor:[125:16] posNoWhite:[125:14] Found expr:[125:9->129:1]
+Completable: Cpath Value[variantOpt]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[variantOpt]
+Path variantOpt
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 127, "character": 2}, "end": {"line": 127, "character": 3}}
+newText:
+  <--here
+  Some(Third | Fourth(_)) | None
+
+Xform src/Xform.res 136:4
+posCursor:[133:16] posNoWhite:[133:14] Found expr:[133:9->138:1]
+Completable: Cpath Value[polyvariantOpt]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[polyvariantOpt]
+Path polyvariantOpt
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 136, "character": 2}, "end": {"line": 136, "character": 3}}
+newText:
+  <--here
+  Some(#"illegal identifier" | #second | #third(_))
+
+Xform src/Xform.res 142:4
+posCursor:[140:16] posNoWhite:[140:14] Found expr:[140:9->144:1]
+Completable: Cpath Value[polyvariantOpt]
+Package opens Pervasives.JsxModules.place holder
+ContextPath Value[polyvariantOpt]
+Path polyvariantOpt
+Package opens Pervasives.JsxModules.place holder
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 142, "character": 2}, "end": {"line": 142, "character": 3}}
+newText:
+  <--here
+  Some(#"illegal identifier" | #third(_)) | None
+
diff --git a/tests/analysis_tests/tests/src/inner/ComponentInner.res b/tests/analysis_tests/tests/src/inner/ComponentInner.res
new file mode 100644
index 0000000000..aa3f50cb0d
--- /dev/null
+++ b/tests/analysis_tests/tests/src/inner/ComponentInner.res
@@ -0,0 +1,2 @@
+@react.component
+let make = () => React.null
diff --git a/tests/analysis_tests/tests/src/inner/ComponentInner.resi b/tests/analysis_tests/tests/src/inner/ComponentInner.resi
new file mode 100644
index 0000000000..1ca44ce260
--- /dev/null
+++ b/tests/analysis_tests/tests/src/inner/ComponentInner.resi
@@ -0,0 +1,2 @@
+@react.component
+let make: unit => React.element
diff --git a/tests/analysis_tests/tests/src/inner/ReferencesInner.res b/tests/analysis_tests/tests/src/inner/ReferencesInner.res
new file mode 100644
index 0000000000..261d0b3966
--- /dev/null
+++ b/tests/analysis_tests/tests/src/inner/ReferencesInner.res
@@ -0,0 +1,2 @@
+@react.component
+let make = () => <ComponentInner/>
\ No newline at end of file
diff --git a/tests/analysis_tests/tests/src/inner/ReferencesInner.resi b/tests/analysis_tests/tests/src/inner/ReferencesInner.resi
new file mode 100644
index 0000000000..1ca44ce260
--- /dev/null
+++ b/tests/analysis_tests/tests/src/inner/ReferencesInner.resi
@@ -0,0 +1,2 @@
+@react.component
+let make: unit => React.element
diff --git a/tests/analysis_tests/tests/src/test.json b/tests/analysis_tests/tests/src/test.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/src/tst.js b/tests/analysis_tests/tests/src/tst.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/analysis_tests/tests/test.sh b/tests/analysis_tests/tests/test.sh
new file mode 100755
index 0000000000..a30bc3af9d
--- /dev/null
+++ b/tests/analysis_tests/tests/test.sh
@@ -0,0 +1,30 @@
+for file in src/*.{res,resi}; do
+  output="$(dirname $file)/expected/$(basename $file).txt"
+  ../../../_build/install/default/bin/rescript-editor-analysis test $file &> $output
+  # CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+  if [ "$RUNNER_OS" == "Windows" ]; then
+    perl -pi -e 's/\r\n/\n/g' -- $output
+  fi
+done
+
+for file in not_compiled/*.{res,resi}; do
+  output="$(dirname $file)/expected/$(basename $file).txt"
+  ../../../_build/install/default/bin/rescript-editor-analysis test $file &> $output
+  # CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+  if [ "$RUNNER_OS" == "Windows" ]; then
+    perl -pi -e 's/\r\n/\n/g' -- $output
+  fi
+done
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified src/expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff src/expected
+  exit 1
+fi
diff --git a/tests/ounit_tests/dune b/tests/ounit_tests/dune
index eaaf28ccfc..446de39f4d 100644
--- a/tests/ounit_tests/dune
+++ b/tests/ounit_tests/dune
@@ -6,6 +6,7 @@
 (executable
  (name ounit_tests_main)
  (public_name ounit_tests)
+ (package rescript)
  (enabled_if
   (<> %{profile} browser))
  (flags
diff --git a/tests/syntax_benchmarks/dune b/tests/syntax_benchmarks/dune
index d45805d291..ac9a3ac8c1 100644
--- a/tests/syntax_benchmarks/dune
+++ b/tests/syntax_benchmarks/dune
@@ -6,6 +6,7 @@
 (executable
  (name benchmark)
  (public_name syntax_benchmarks)
+ (package rescript)
  (enabled_if
   (and
    (<> %{profile} browser)
diff --git a/tests/syntax_tests/dune b/tests/syntax_tests/dune
index 9bd2b4199a..310ffdf07f 100644
--- a/tests/syntax_tests/dune
+++ b/tests/syntax_tests/dune
@@ -6,6 +6,7 @@
 (executable
  (name res_test)
  (public_name syntax_tests)
+ (package rescript)
  (enabled_if
   (<> %{profile} browser))
  (flags
diff --git a/tests/tools_tests/Makefile b/tests/tools_tests/Makefile
new file mode 100644
index 0000000000..5084c67abd
--- /dev/null
+++ b/tests/tools_tests/Makefile
@@ -0,0 +1,17 @@
+SHELL = /bin/bash
+
+node_modules/.bin/rescript:
+	npm install
+
+build: node_modules/.bin/rescript
+	node_modules/.bin/rescript
+
+test: build
+	./test.sh
+
+clean:
+	rm -r node_modules lib
+
+.DEFAULT_GOAL := test
+
+.PHONY: clean test
diff --git a/tests/tools_tests/package-lock.json b/tests/tools_tests/package-lock.json
new file mode 100644
index 0000000000..a8a3fb40a7
--- /dev/null
+++ b/tests/tools_tests/package-lock.json
@@ -0,0 +1,103 @@
+{
+  "name": "docstrings-test",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "docstrings-test",
+      "version": "0.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "@rescript/react": "^0.13.0",
+        "rescript": "../.."
+      }
+    },
+    "../..": {
+      "name": "rescript",
+      "version": "12.0.0-alpha.5",
+      "hasInstallScript": true,
+      "license": "SEE LICENSE IN LICENSE",
+      "bin": {
+        "bsc": "cli/bsc",
+        "bstracing": "lib/bstracing",
+        "rescript": "cli/rescript",
+        "rewatch": "cli/rewatch"
+      },
+      "devDependencies": {
+        "@biomejs/biome": "1.8.3",
+        "mocha": "10.1.0",
+        "nyc": "15.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@rescript/react": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@rescript/react/-/react-0.13.0.tgz",
+      "integrity": "sha512-YSIWIyMlyF9ZaP6Q3hScl1h3wRbdIP4+Cb7PlDt7Y1PG8M8VWYhLoIgLb78mbBHcwFbZu0d5zAt1LSX5ilOiWQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=18.0.0",
+        "react-dom": ">=18.0.0"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "peer": true
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      },
+      "peerDependencies": {
+        "react": "^18.2.0"
+      }
+    },
+    "node_modules/rescript": {
+      "resolved": "../..",
+      "link": true
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "peer": true,
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    }
+  }
+}
diff --git a/tests/tools_tests/package.json b/tests/tools_tests/package.json
new file mode 100644
index 0000000000..f08451e657
--- /dev/null
+++ b/tests/tools_tests/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "docstrings-test",
+  "version": "0.0.0",
+  "scripts": {
+    "res:build": "rescript",
+    "res:clean": "rescript clean",
+    "res:dev": "rescript -w"
+  },
+  "keywords": [
+    "rescript"
+  ],
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "@rescript/react": "^0.13.0",
+    "rescript": "../.."
+  }
+}
diff --git a/tests/tools_tests/rescript.json b/tests/tools_tests/rescript.json
new file mode 100644
index 0000000000..c7968f1bf4
--- /dev/null
+++ b/tests/tools_tests/rescript.json
@@ -0,0 +1,13 @@
+{
+  "name": "docstrings-test",
+  "sources": {
+    "dir": "src",
+    "subdirs": true
+  },
+  "package-specs": {
+    "module": "commonjs",
+    "in-source": true
+  },
+  "suffix": ".res.js",
+  "bs-dependencies": ["@rescript/react"]
+}
diff --git a/tests/tools_tests/src/DocExtraction2.res b/tests/tools_tests/src/DocExtraction2.res
new file mode 100644
index 0000000000..3733153e0b
--- /dev/null
+++ b/tests/tools_tests/src/DocExtraction2.res
@@ -0,0 +1,12 @@
+type t = string
+
+let getStr = () => "123"
+
+let make = getStr
+
+module InnerModule = {
+  type t = unit
+  let make = () => ()
+}
+
+// ^dex
diff --git a/tests/tools_tests/src/DocExtraction2.resi b/tests/tools_tests/src/DocExtraction2.resi
new file mode 100644
index 0000000000..b653bdcabd
--- /dev/null
+++ b/tests/tools_tests/src/DocExtraction2.resi
@@ -0,0 +1,19 @@
+/*** Module level doc here.*/
+
+/** Type t is pretty cool.*/
+type t
+
+/** Makerz of stuffz. */
+let make: unit => t
+
+module InnerModule: {
+  /*** This inner module is nice...*/
+
+  /** This type is also t. */
+  type t
+
+  /** Maker of tea.*/
+  let make: unit => t
+}
+
+// ^dex
diff --git a/tests/tools_tests/src/DocExtractionRes.res b/tests/tools_tests/src/DocExtractionRes.res
new file mode 100644
index 0000000000..b9c7f25cf8
--- /dev/null
+++ b/tests/tools_tests/src/DocExtractionRes.res
@@ -0,0 +1,145 @@
+/***Module level documentation goes here. */
+
+/** This type represents stuff. */
+type t = {
+  /** The name of the stuff.*/
+  name: string,
+  /** Whether stuff is online.*/
+  online: bool,
+}
+
+/** Create stuff.
+
+```rescript example
+let stuff = make("My name")
+```
+*/
+let make = name => {
+  name,
+  online: true,
+}
+
+/** Stuff goes offline.*/
+let asOffline = (t: t) => {...t, online: false}
+
+/** exotic identifier */
+let \"SomeConstant" = 12
+
+module SomeInnerModule = {
+  /*** Another module level docstring here.*/
+  type status =
+    | /** If this is started or not */ Started(t) | /** Stopped? */ Stopped | /** Now idle.*/ Idle
+
+  /** These are all the valid inputs.*/
+  type validInputs = [#something | #"needs-escaping" | #withPayload(int) | #status(status)]
+
+  type callback = (t, ~status: status) => unit
+}
+
+module AnotherModule = {
+  /*** Mighty fine module here too!*/
+
+  /** This links another module. Neat. */
+  module LinkedModule = SomeInnerModule
+
+  /**
+  Testing what this looks like.*/
+  type callback = SomeInnerModule.status => unit
+
+  let isGoodStatus = (status: SomeInnerModule.status) => status == Stopped
+
+  /** Trying how it looks with an inline record in a variant. */
+  type someVariantWithInlineRecords =
+    | /** This has inline records...*/
+    SomeStuff({
+        offline: bool,
+        /** Is the user online? */ online?: bool,
+      })
+
+  open ReactDOM
+
+  /**Callback to get the DOM root...*/
+  type domRoot = unit => Client.Root.t
+}
+
+module ModuleWithThingsThatShouldNotBeExported: {
+  /*** BROKEN: This docstring isn't picked up Doesn't seem to be parsed at all, no attributes found.*/
+
+  /** The type t is stuff. */
+  type t
+
+  /** The maker of stuff!*/
+  let make: unit => t
+} = {
+  /*** Mighty fine module here too!*/
+  type t = string
+  type x = int
+  type f = bool
+
+  let m1 = (x: x) => {
+    x + 1
+  }
+
+  let m2 = (f: f) =>
+    if f {
+      true
+    } else {
+      false
+    }
+
+  let make = () => {
+    if m2(true) && m1(1) > 2 {
+      "1"
+    } else {
+      "2"
+    }
+  }
+}
+
+module type Example = {
+  /***
+  this is an example module type 
+  */
+
+  /**
+  main type of this module 
+  */
+  type t
+
+  /**
+  function from t to t
+  */
+  let f: t => t
+}
+
+module M: Example = {
+  /***
+  implementation of Example module type
+  */
+
+  /**
+  main type 
+  */
+  type t = int
+
+  /**
+  identity function
+  */
+  let f = (x: int) => x
+}
+
+module type MT = {
+  let x: int
+}
+
+module A: MT = {
+  let x = 42
+}
+
+module C = {
+  module D: MT = {
+    let x = 42
+  }
+}
+
+// ^dex
diff --git a/tests/tools_tests/src/ModC.res b/tests/tools_tests/src/ModC.res
new file mode 100644
index 0000000000..3c6892919c
--- /dev/null
+++ b/tests/tools_tests/src/ModC.res
@@ -0,0 +1,6 @@
+/**
+User Module
+*/
+module User = {
+  let name = "ReScript"
+}
diff --git a/tests/tools_tests/src/ModC.resi b/tests/tools_tests/src/ModC.resi
new file mode 100644
index 0000000000..c113149b47
--- /dev/null
+++ b/tests/tools_tests/src/ModC.resi
@@ -0,0 +1,6 @@
+/**
+User Module from interface file
+*/
+module User: {
+  let name: string
+}
diff --git a/tests/tools_tests/src/expected/DocExtraction2.res.json b/tests/tools_tests/src/expected/DocExtraction2.res.json
new file mode 100644
index 0000000000..224daefdc6
--- /dev/null
+++ b/tests/tools_tests/src/expected/DocExtraction2.res.json
@@ -0,0 +1,71 @@
+
+{
+  "name": "DocExtraction2",
+  "docstrings": ["Module level doc here."],
+  "source": {
+    "filepath": "src/DocExtraction2.resi",
+    "line": 1,
+    "col": 1
+  },
+  "items": [
+  {
+    "id": "DocExtraction2.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t",
+    "docstrings": ["Type t is pretty cool."],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 4,
+      "col": 1
+    }
+  }, 
+  {
+    "id": "DocExtraction2.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: unit => t",
+    "docstrings": ["Makerz of stuffz."],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 7,
+      "col": 1
+    }
+  }, 
+  {
+    "id": "DocExtraction2.InnerModule",
+    "name": "InnerModule",
+    "kind": "module",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 9,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "DocExtraction2.InnerModule.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["This type is also t."],
+      "source": {
+        "filepath": "src/DocExtraction2.resi",
+        "line": 13,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtraction2.InnerModule.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["Maker of tea."],
+      "source": {
+        "filepath": "src/DocExtraction2.resi",
+        "line": 15,
+        "col": 3
+      }
+    }]
+  }]
+}
diff --git a/tests/tools_tests/src/expected/DocExtraction2.resi.json b/tests/tools_tests/src/expected/DocExtraction2.resi.json
new file mode 100644
index 0000000000..224daefdc6
--- /dev/null
+++ b/tests/tools_tests/src/expected/DocExtraction2.resi.json
@@ -0,0 +1,71 @@
+
+{
+  "name": "DocExtraction2",
+  "docstrings": ["Module level doc here."],
+  "source": {
+    "filepath": "src/DocExtraction2.resi",
+    "line": 1,
+    "col": 1
+  },
+  "items": [
+  {
+    "id": "DocExtraction2.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t",
+    "docstrings": ["Type t is pretty cool."],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 4,
+      "col": 1
+    }
+  }, 
+  {
+    "id": "DocExtraction2.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: unit => t",
+    "docstrings": ["Makerz of stuffz."],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 7,
+      "col": 1
+    }
+  }, 
+  {
+    "id": "DocExtraction2.InnerModule",
+    "name": "InnerModule",
+    "kind": "module",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtraction2.resi",
+      "line": 9,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "DocExtraction2.InnerModule.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["This type is also t."],
+      "source": {
+        "filepath": "src/DocExtraction2.resi",
+        "line": 13,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtraction2.InnerModule.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["Maker of tea."],
+      "source": {
+        "filepath": "src/DocExtraction2.resi",
+        "line": 15,
+        "col": 3
+      }
+    }]
+  }]
+}
diff --git a/tests/tools_tests/src/expected/DocExtractionRes.res.json b/tests/tools_tests/src/expected/DocExtractionRes.res.json
new file mode 100644
index 0000000000..93a727b3b1
--- /dev/null
+++ b/tests/tools_tests/src/expected/DocExtractionRes.res.json
@@ -0,0 +1,433 @@
+
+{
+  "name": "DocExtractionRes",
+  "docstrings": ["Module level documentation goes here."],
+  "source": {
+    "filepath": "src/DocExtractionRes.res",
+    "line": 1,
+    "col": 1
+  },
+  "items": [
+  {
+    "id": "DocExtractionRes.t",
+    "kind": "type",
+    "name": "t",
+    "signature": "type t = {name: string, online: bool}",
+    "docstrings": ["This type represents stuff."],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 4,
+      "col": 1
+    },
+    "detail": 
+    {
+      "kind": "record",
+      "items": [{
+        "name": "name",
+        "optional": false,
+        "docstrings": ["The name of the stuff."],
+        "signature": "string"
+      }, {
+        "name": "online",
+        "optional": false,
+        "docstrings": ["Whether stuff is online."],
+        "signature": "bool"
+      }]
+    }
+  }, 
+  {
+    "id": "DocExtractionRes.make",
+    "kind": "value",
+    "name": "make",
+    "signature": "let make: string => t",
+    "docstrings": ["Create stuff.\n\n```rescript example\nlet stuff = make(\"My name\")\n```"],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 17,
+      "col": 5
+    }
+  }, 
+  {
+    "id": "DocExtractionRes.asOffline",
+    "kind": "value",
+    "name": "asOffline",
+    "signature": "let asOffline: t => t",
+    "docstrings": ["Stuff goes offline."],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 23,
+      "col": 5
+    }
+  }, 
+  {
+    "id": "DocExtractionRes.SomeConstant",
+    "kind": "value",
+    "name": "SomeConstant",
+    "signature": "let SomeConstant: int",
+    "docstrings": ["exotic identifier"],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 26,
+      "col": 5
+    }
+  }, 
+  {
+    "id": "DocExtractionRes.SomeInnerModule",
+    "name": "SomeInnerModule",
+    "kind": "module",
+    "docstrings": ["Another module level docstring here."],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 28,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.SomeInnerModule.status",
+      "kind": "type",
+      "name": "status",
+      "signature": "type status = Started(t) | Stopped | Idle",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 30,
+        "col": 3
+      },
+      "detail": 
+      {
+        "kind": "variant",
+        "items": [
+        {
+          "name": "Started",
+          "docstrings": ["If this is started or not"],
+          "signature": "Started(t)"
+        }, 
+        {
+          "name": "Stopped",
+          "docstrings": ["Stopped?"],
+          "signature": "Stopped"
+        }, 
+        {
+          "name": "Idle",
+          "docstrings": ["Now idle."],
+          "signature": "Idle"
+        }]
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.SomeInnerModule.validInputs",
+      "kind": "type",
+      "name": "validInputs",
+      "signature": "type validInputs = [\n  | #\"needs-escaping\"\n  | #something\n  | #status(status)\n  | #withPayload(int)\n]",
+      "docstrings": ["These are all the valid inputs."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 34,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.SomeInnerModule.callback",
+      "kind": "type",
+      "name": "callback",
+      "signature": "type callback = (t, ~status: status) => unit",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 36,
+        "col": 3
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.AnotherModule",
+    "name": "AnotherModule",
+    "kind": "module",
+    "docstrings": ["Mighty fine module here too!"],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 39,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.LinkedModule",
+      "kind": "moduleAlias",
+      "name": "LinkedModule",
+      "docstrings": ["This links another module. Neat."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 43,
+        "col": 10
+      },
+      "items": []
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.callback",
+      "kind": "type",
+      "name": "callback",
+      "signature": "type callback = SomeInnerModule.status => unit",
+      "docstrings": ["Testing what this looks like."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 47,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.isGoodStatus",
+      "kind": "value",
+      "name": "isGoodStatus",
+      "signature": "let isGoodStatus: SomeInnerModule.status => bool",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 49,
+        "col": 7
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.someVariantWithInlineRecords",
+      "kind": "type",
+      "name": "someVariantWithInlineRecords",
+      "signature": "type someVariantWithInlineRecords =\n  | SomeStuff({offline: bool, online?: bool})",
+      "docstrings": ["Trying how it looks with an inline record in a variant."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 52,
+        "col": 3
+      },
+      "detail": 
+      {
+        "kind": "variant",
+        "items": [
+        {
+          "name": "SomeStuff",
+          "docstrings": ["This has inline records..."],
+          "signature": "SomeStuff({offline: bool, online?: bool})",
+          "payload": {
+            "kind": "inlineRecord",
+            "fields": [{
+              "name": "offline",
+              "optional": false,
+              "docstrings": [],
+              "signature": "bool"
+            }, {
+              "name": "online",
+              "optional": true,
+              "docstrings": ["Is the user online?"],
+              "signature": "option<bool>"
+            }]
+          }
+        }]
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.AnotherModule.domRoot",
+      "kind": "type",
+      "name": "domRoot",
+      "signature": "type domRoot = unit => ReactDOM.Client.Root.t",
+      "docstrings": ["Callback to get the DOM root..."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 62,
+        "col": 3
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported",
+    "name": "ModuleWithThingsThatShouldNotBeExported",
+    "kind": "module",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 1,
+      "col": 1
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["The type t is stuff."],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 69,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.make",
+      "kind": "value",
+      "name": "make",
+      "signature": "let make: unit => t",
+      "docstrings": ["The maker of stuff!"],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 71,
+        "col": 3
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.Example",
+    "name": "Example",
+    "kind": "moduleType",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 99,
+      "col": 13
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.Example.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t",
+      "docstrings": ["main type of this module"],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 107,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.Example.f",
+      "kind": "value",
+      "name": "f",
+      "signature": "let f: t => t",
+      "docstrings": ["function from t to t"],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 109,
+        "col": 3
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.M",
+    "name": "M",
+    "kind": "module",
+    "moduletypeid": "DocExtractionRes.Example",
+    "docstrings": ["implementation of Example module type"],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 1,
+      "col": 1
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.M.t",
+      "kind": "type",
+      "name": "t",
+      "signature": "type t = int",
+      "docstrings": ["main type"],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 123,
+        "col": 3
+      }
+    }, 
+    {
+      "id": "DocExtractionRes.M.f",
+      "kind": "value",
+      "name": "f",
+      "signature": "let f: int => int",
+      "docstrings": ["identity function"],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 128,
+        "col": 7
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.MT",
+    "name": "MT",
+    "kind": "moduleType",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 131,
+      "col": 13
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.MT.x",
+      "kind": "value",
+      "name": "x",
+      "signature": "let x: int",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 132,
+        "col": 3
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.A",
+    "name": "A",
+    "kind": "module",
+    "moduletypeid": "DocExtractionRes.MT",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 1,
+      "col": 1
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.A.x",
+      "kind": "value",
+      "name": "x",
+      "signature": "let x: int",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 136,
+        "col": 7
+      }
+    }]
+  }, 
+  {
+    "id": "DocExtractionRes.C",
+    "name": "C",
+    "kind": "module",
+    "docstrings": [],
+    "source": {
+      "filepath": "src/DocExtractionRes.res",
+      "line": 139,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "DocExtractionRes.C.D",
+      "name": "D",
+      "kind": "module",
+      "moduletypeid": "DocExtractionRes.MT",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/DocExtractionRes.res",
+        "line": 1,
+        "col": 1
+      },
+      "items": [
+      {
+        "id": "DocExtractionRes.C.D.x",
+        "kind": "value",
+        "name": "x",
+        "signature": "let x: int",
+        "docstrings": [],
+        "source": {
+          "filepath": "src/DocExtractionRes.res",
+          "line": 141,
+          "col": 9
+        }
+      }]
+    }]
+  }]
+}
diff --git a/tests/tools_tests/src/expected/ModC.res.json b/tests/tools_tests/src/expected/ModC.res.json
new file mode 100644
index 0000000000..4f68f61912
--- /dev/null
+++ b/tests/tools_tests/src/expected/ModC.res.json
@@ -0,0 +1,35 @@
+
+{
+  "name": "ModC",
+  "docstrings": [],
+  "source": {
+    "filepath": "src/ModC.resi",
+    "line": 1,
+    "col": 1
+  },
+  "items": [
+  {
+    "id": "ModC.User",
+    "name": "User",
+    "kind": "module",
+    "docstrings": ["User Module from interface file"],
+    "source": {
+      "filepath": "src/ModC.resi",
+      "line": 4,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "ModC.User.name",
+      "kind": "value",
+      "name": "name",
+      "signature": "let name: string",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/ModC.resi",
+        "line": 5,
+        "col": 3
+      }
+    }]
+  }]
+}
diff --git a/tests/tools_tests/src/expected/ModC.resi.json b/tests/tools_tests/src/expected/ModC.resi.json
new file mode 100644
index 0000000000..4f68f61912
--- /dev/null
+++ b/tests/tools_tests/src/expected/ModC.resi.json
@@ -0,0 +1,35 @@
+
+{
+  "name": "ModC",
+  "docstrings": [],
+  "source": {
+    "filepath": "src/ModC.resi",
+    "line": 1,
+    "col": 1
+  },
+  "items": [
+  {
+    "id": "ModC.User",
+    "name": "User",
+    "kind": "module",
+    "docstrings": ["User Module from interface file"],
+    "source": {
+      "filepath": "src/ModC.resi",
+      "line": 4,
+      "col": 8
+    },
+    "items": [
+    {
+      "id": "ModC.User.name",
+      "kind": "value",
+      "name": "name",
+      "signature": "let name: string",
+      "docstrings": [],
+      "source": {
+        "filepath": "src/ModC.resi",
+        "line": 5,
+        "col": 3
+      }
+    }]
+  }]
+}
diff --git a/tests/tools_tests/test.sh b/tests/tools_tests/test.sh
new file mode 100755
index 0000000000..0534552c15
--- /dev/null
+++ b/tests/tools_tests/test.sh
@@ -0,0 +1,21 @@
+for file in src/*.{res,resi}; do
+  output="$(dirname $file)/expected/$(basename $file).json"
+  ../../_build/install/default/bin/rescript-tools doc $file > $output
+  # # CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
+  if [ "$RUNNER_OS" == "Windows" ]; then
+    perl -pi -e 's/\r\n/\n/g' -- $output
+  fi
+done
+
+warningYellow='\033[0;33m'
+successGreen='\033[0;32m'
+reset='\033[0m'
+
+diff=$(git ls-files --modified src/expected)
+if [[ $diff = "" ]]; then
+  printf "${successGreen}✅ No unstaged tests difference.${reset}\n"
+else
+  printf "${warningYellow}⚠️ There are unstaged differences in tests/! Did you break a test?\n${diff}\n${reset}"
+  git --no-pager diff src/expected
+  exit 1
+fi
diff --git a/tools.opam b/tools.opam
new file mode 100644
index 0000000000..814d6b5bef
--- /dev/null
+++ b/tools.opam
@@ -0,0 +1,28 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "ReScript Tools"
+maintainer: ["Hongbo Zhang <bobzhang1988@gmail.com>" "Cristiano Calcagno"]
+authors: ["Hongbo Zhang <bobzhang1988@gmail.com>"]
+license: "LGPL-3.0-or-later"
+homepage: "https://github.com/rescript-lang/rescript-compiler"
+bug-reports: "https://github.com/rescript-lang/rescript-compiler/issues"
+depends: [
+  "ocaml" {>= "4.10"}
+  "cppo" {= "1.6.9"}
+  "analysis"
+  "dune"
+]
+build: [
+  ["dune" "subst"] {pinned}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
diff --git a/tools/CHANGELOG.md b/tools/CHANGELOG.md
new file mode 100644
index 0000000000..401adf5f6a
--- /dev/null
+++ b/tools/CHANGELOG.md
@@ -0,0 +1,86 @@
+# Changelog
+
+> **Tags:**
+>
+> - :boom: [Breaking Change]
+> - :eyeglasses: [Spec Compliance]
+> - :rocket: [New Feature]
+> - :bug: [Bug Fix]
+> - :memo: [Documentation]
+> - :house: [Internal]
+> - :nail_care: [Polish]
+
+## master
+
+## 0.6.4
+
+#### :rocket: New Feature
+
+- Add `moduletypeid` field for explicitly annotated module type. https://github.com/rescript-lang/rescript-vscode/pull/1019
+
+### :bug: Bug Fix
+
+- Print module structure with signature to module path. https://github.com/rescript-lang/rescript-vscode/pull/1018
+
+## 0.6.3
+
+#### :bug: Bug Fix
+
+- Make sure Linux binaries are statically linked.
+
+#### :nail_care: Polish
+
+- Reverse order of extracted embeds, so they're in the correct order.
+
+## 0.6.2
+
+#### :rocket: New Feature
+
+- Ship Linux ARM64 binaries.
+
+## 0.6.1
+
+#### :rocket: New Feature
+
+- Expose `getBinaryPath` JS function that you can import to get the binary to call for the current platform.
+
+## 0.6.0
+
+#### :rocket: New Feature
+
+- _internal_ Add experimental command for extracting (string) contents from extension points.
+
+## 0.5.0
+
+#### :rocket: New Feature
+
+- Add `source` property to type, value, module and module alias. https://github.com/rescript-lang/rescript-vscode/pull/900.
+
+#### :bug: Bug Fix
+
+- Print docstrings for nested submodules. https://github.com/rescript-lang/rescript-vscode/pull/897
+- Print `deprecated` field for module. https://github.com/rescript-lang/rescript-vscode/pull/897
+
+## 0.4.0
+
+#### :bug: Bug Fix
+
+- Support inline record fields in constructors. https://github.com/rescript-lang/rescript-vscode/pull/889
+- Fix docstrings for module alias. Get internal docstrings of module file. https://github.com/rescript-lang/rescript-vscode/pull/878
+- Fix extracted docs of types include escaped linebreaks in signature. https://github.com/rescript-lang/rescript-vscode/pull/891
+
+## 0.3.0
+
+#### :rocket: New Feature
+
+- Expose more `decode` functions. https://github.com/rescript-lang/rescript-vscode/pull/866
+
+#### :house: [Internal]
+
+- Add env var `FROM_COMPILER` to extract docstrings from compiler repo. https://github.com/rescript-lang/rescript-vscode/pull/868
+
+#### :bug: Bug Fix
+
+- Fix tagged variant for `Module` and add attr to interface files. https://github.com/rescript-lang/rescript-vscode/pull/866
+- Fix `rescript-tools --version` command. https://github.com/rescript-lang/rescript-vscode/pull/873
+- Fix output truncate when run `rescript-tools doc path/to/file.res` in a separate process. https://github.com/rescript-lang/rescript-vscode/pull/868
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 0000000000..874e189f6b
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,46 @@
+# ReScript Tools
+
+## Install
+
+```sh
+npm install --save-dev @rescript/tools
+```
+
+## CLI Usage
+
+```sh
+rescript-tools --help
+```
+
+### Generate documentation
+
+Print JSON:
+
+```sh
+rescript-tools doc src/EntryPointLibFile.res
+```
+
+Write JSON:
+
+```sh
+rescript-tools doc src/EntryPointLibFile.res > doc.json
+```
+
+### Reanalyze
+
+```sh
+rescript-tools reanalyze --help
+```
+
+## Decode JSON
+
+Add to `bs-dev-dependencies`:
+
+```json
+"bs-dev-dependencies": ["@rescript/tools"]
+```
+
+```rescript
+// Read JSON file and parse with `Js.Json.parseExn`
+json->RescriptTools.Docgen.decodeFromJson
+```
diff --git a/tools/bin/dune b/tools/bin/dune
new file mode 100644
index 0000000000..674eca5b67
--- /dev/null
+++ b/tools/bin/dune
@@ -0,0 +1,14 @@
+(env
+ (static
+  (flags
+   (:standard -ccopt -static))))
+
+(executable
+ (public_name rescript-tools)
+ (package tools)
+ (modes byte exe)
+ ; The main module that will become the binary.
+ (name main)
+ (libraries tools)
+ (flags
+  (:standard -w "+6+26+27+32+33+39")))
diff --git a/tools/bin/main.ml b/tools/bin/main.ml
new file mode 100644
index 0000000000..06be383b1b
--- /dev/null
+++ b/tools/bin/main.ml
@@ -0,0 +1,63 @@
+let docHelp =
+  {|ReScript Tools
+
+Output documentation to standard output
+
+Usage: rescript-tools doc <FILE>
+
+Example: rescript-tools doc ./path/to/EntryPointLib.res|}
+
+let help =
+  {|ReScript Tools
+
+Usage: rescript-tools [command]
+
+Commands:
+
+doc                   Generate documentation
+reanalyze             Reanalyze
+-v, --version         Print version
+-h, --help            Print help|}
+
+let logAndExit = function
+  | Ok log ->
+    Printf.printf "%s\n" log;
+    exit 0
+  | Error log ->
+    Printf.eprintf "%s\n" log;
+    exit 1
+
+let version = Version.version
+
+let main () =
+  match Sys.argv |> Array.to_list |> List.tl with
+  | "doc" :: rest -> (
+    match rest with
+    | ["-h"] | ["--help"] -> logAndExit (Ok docHelp)
+    | [path] ->
+      (* NOTE: Internal use to generate docs from compiler *)
+      let () =
+        match Sys.getenv_opt "FROM_COMPILER" with
+        | Some "true" -> Analysis.Cfg.isDocGenFromCompiler := true
+        | _ -> ()
+      in
+      logAndExit (Tools.extractDocs ~entryPointFile:path ~debug:false)
+    | _ -> logAndExit (Error docHelp))
+  | "reanalyze" :: _ ->
+    let len = Array.length Sys.argv in
+    for i = 1 to len - 2 do
+      Sys.argv.(i) <- Sys.argv.(i + 1)
+    done;
+    Sys.argv.(len - 1) <- "";
+    Reanalyze.cli ()
+  | "extract-embedded" :: extPointNames :: filename :: _ ->
+    logAndExit
+      (Ok
+         (Tools.extractEmbedded
+            ~extensionPoints:(extPointNames |> String.split_on_char ',')
+            ~filename))
+  | ["-h"] | ["--help"] -> logAndExit (Ok help)
+  | ["-v"] | ["--version"] -> logAndExit (Ok version)
+  | _ -> logAndExit (Error help)
+
+let () = main ()
diff --git a/tools/bin/version.ml b/tools/bin/version.ml
new file mode 100644
index 0000000000..8a00911bbc
--- /dev/null
+++ b/tools/bin/version.ml
@@ -0,0 +1 @@
+let version = "0.6.4"
diff --git a/tools/rescript.json b/tools/rescript.json
new file mode 100644
index 0000000000..26ece932ef
--- /dev/null
+++ b/tools/rescript.json
@@ -0,0 +1,14 @@
+{
+  "name": "@rescript/tools",
+  "version": "0.6.4",
+  "sources": [
+    {
+      "dir": "npm"
+    }
+  ],
+  "suffix": ".bs.js",
+  "package-specs": {
+    "module": "commonjs",
+    "in-source": false
+  }
+}
diff --git a/tools/src/dune b/tools/src/dune
new file mode 100644
index 0000000000..f124a3737a
--- /dev/null
+++ b/tools/src/dune
@@ -0,0 +1,5 @@
+(library
+ (name tools)
+ (flags
+  (-w "+6+26+27+32+33+39"))
+ (libraries analysis))
diff --git a/tools/src/tools.ml b/tools/src/tools.ml
new file mode 100644
index 0000000000..53aa439494
--- /dev/null
+++ b/tools/src/tools.ml
@@ -0,0 +1,569 @@
+open Analysis
+
+type fieldDoc = {
+  fieldName: string;
+  docstrings: string list;
+  signature: string;
+  optional: bool;
+  deprecated: string option;
+}
+
+type constructorPayload = InlineRecord of {fieldDocs: fieldDoc list}
+
+type constructorDoc = {
+  constructorName: string;
+  docstrings: string list;
+  signature: string;
+  deprecated: string option;
+  items: constructorPayload option;
+}
+
+type source = {filepath: string; line: int; col: int}
+
+type docItemDetail =
+  | Record of {fieldDocs: fieldDoc list}
+  | Variant of {constructorDocs: constructorDoc list}
+
+type docItem =
+  | Value of {
+      id: string;
+      docstring: string list;
+      signature: string;
+      name: string;
+      deprecated: string option;
+      source: source;
+    }
+  | Type of {
+      id: string;
+      docstring: string list;
+      signature: string;
+      name: string;
+      deprecated: string option;
+      detail: docItemDetail option;
+      source: source;
+          (** Additional documentation for constructors and record fields, if available. *)
+    }
+  | Module of docsForModule
+  | ModuleType of {
+      id: string;
+      docstring: string list;
+      deprecated: string option;
+      name: string;
+      source: source;
+      items: docItem list;
+    }
+  | ModuleAlias of {
+      id: string;
+      docstring: string list;
+      name: string;
+      source: source;
+      items: docItem list;
+    }
+and docsForModule = {
+  id: string;
+  docstring: string list;
+  deprecated: string option;
+  name: string;
+  moduletypeid: string option;
+  source: source;
+  items: docItem list;
+}
+
+let stringifyDocstrings docstrings =
+  let open Protocol in
+  docstrings
+  |> List.map (fun docstring -> docstring |> String.trim |> wrapInQuotes)
+  |> array
+
+let stringifyFieldDoc ~indentation (fieldDoc : fieldDoc) =
+  let open Protocol in
+  stringifyObject ~indentation:(indentation + 1)
+    [
+      ("name", Some (wrapInQuotes fieldDoc.fieldName));
+      ( "deprecated",
+        match fieldDoc.deprecated with
+        | Some d -> Some (wrapInQuotes d)
+        | None -> None );
+      ("optional", Some (string_of_bool fieldDoc.optional));
+      ("docstrings", Some (stringifyDocstrings fieldDoc.docstrings));
+      ("signature", Some (wrapInQuotes fieldDoc.signature));
+    ]
+
+let stringifyConstructorPayload ~indentation
+    (constructorPayload : constructorPayload) =
+  let open Protocol in
+  match constructorPayload with
+  | InlineRecord {fieldDocs} ->
+    stringifyObject ~indentation:(indentation + 1)
+      [
+        ("kind", Some (wrapInQuotes "inlineRecord"));
+        ( "fields",
+          Some
+            (fieldDocs
+            |> List.map (stringifyFieldDoc ~indentation:(indentation + 1))
+            |> array) );
+      ]
+
+let stringifyDetail ?(indentation = 0) (detail : docItemDetail) =
+  let open Protocol in
+  match detail with
+  | Record {fieldDocs} ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("kind", Some (wrapInQuotes "record"));
+        ( "items",
+          Some (fieldDocs |> List.map (stringifyFieldDoc ~indentation) |> array)
+        );
+      ]
+  | Variant {constructorDocs} ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("kind", Some (wrapInQuotes "variant"));
+        ( "items",
+          Some
+            (constructorDocs
+            |> List.map (fun constructorDoc ->
+                   stringifyObject ~startOnNewline:true
+                     ~indentation:(indentation + 1)
+                     [
+                       ( "name",
+                         Some (wrapInQuotes constructorDoc.constructorName) );
+                       ( "deprecated",
+                         match constructorDoc.deprecated with
+                         | Some d -> Some (wrapInQuotes d)
+                         | None -> None );
+                       ( "docstrings",
+                         Some (stringifyDocstrings constructorDoc.docstrings) );
+                       ( "signature",
+                         Some (wrapInQuotes constructorDoc.signature) );
+                       ( "payload",
+                         match constructorDoc.items with
+                         | None -> None
+                         | Some constructorPayload ->
+                           Some
+                             (stringifyConstructorPayload
+                                ~indentation:(indentation + 1)
+                                constructorPayload) );
+                     ])
+            |> array) );
+      ]
+
+let stringifySource ~indentation source =
+  let open Protocol in
+  stringifyObject ~startOnNewline:false ~indentation
+    [
+      ("filepath", Some (source.filepath |> wrapInQuotes));
+      ("line", Some (source.line |> string_of_int));
+      ("col", Some (source.col |> string_of_int));
+    ]
+
+let rec stringifyDocItem ?(indentation = 0) ~originalEnv (item : docItem) =
+  let open Protocol in
+  match item with
+  | Value {id; docstring; signature; name; deprecated; source} ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("id", Some (wrapInQuotes id));
+        ("kind", Some (wrapInQuotes "value"));
+        ("name", Some (name |> wrapInQuotes));
+        ( "deprecated",
+          match deprecated with
+          | Some d -> Some (wrapInQuotes d)
+          | None -> None );
+        ("signature", Some (signature |> String.trim |> wrapInQuotes));
+        ("docstrings", Some (stringifyDocstrings docstring));
+        ("source", Some (stringifySource ~indentation:(indentation + 1) source));
+      ]
+  | Type {id; docstring; signature; name; deprecated; detail; source} ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("id", Some (wrapInQuotes id));
+        ("kind", Some (wrapInQuotes "type"));
+        ("name", Some (name |> wrapInQuotes));
+        ( "deprecated",
+          match deprecated with
+          | Some d -> Some (wrapInQuotes d)
+          | None -> None );
+        ("signature", Some (signature |> wrapInQuotes));
+        ("docstrings", Some (stringifyDocstrings docstring));
+        ("source", Some (stringifySource ~indentation:(indentation + 1) source));
+        ( "detail",
+          match detail with
+          | None -> None
+          | Some detail ->
+            Some (stringifyDetail ~indentation:(indentation + 1) detail) );
+      ]
+  | Module m ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("id", Some (wrapInQuotes m.id));
+        ("name", Some (wrapInQuotes m.name));
+        ("kind", Some (wrapInQuotes "module"));
+        ( "deprecated",
+          match m.deprecated with
+          | Some d -> Some (wrapInQuotes d)
+          | None -> None );
+        ( "moduletypeid",
+          match m.moduletypeid with
+          | Some path -> Some (wrapInQuotes path)
+          | None -> None );
+        ("docstrings", Some (stringifyDocstrings m.docstring));
+        ( "source",
+          Some (stringifySource ~indentation:(indentation + 1) m.source) );
+        ( "items",
+          Some
+            (m.items
+            |> List.map
+                 (stringifyDocItem ~originalEnv ~indentation:(indentation + 1))
+            |> array) );
+      ]
+  | ModuleType m ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("id", Some (wrapInQuotes m.id));
+        ("name", Some (wrapInQuotes m.name));
+        ("kind", Some (wrapInQuotes "moduleType"));
+        ( "deprecated",
+          match m.deprecated with
+          | Some d -> Some (wrapInQuotes d)
+          | None -> None );
+        ("docstrings", Some (stringifyDocstrings m.docstring));
+        ( "source",
+          Some (stringifySource ~indentation:(indentation + 1) m.source) );
+        ( "items",
+          Some
+            (m.items
+            |> List.map
+                 (stringifyDocItem ~originalEnv ~indentation:(indentation + 1))
+            |> array) );
+      ]
+  | ModuleAlias m ->
+    stringifyObject ~startOnNewline:true ~indentation
+      [
+        ("id", Some (wrapInQuotes m.id));
+        ("kind", Some (wrapInQuotes "moduleAlias"));
+        ("name", Some (wrapInQuotes m.name));
+        ("docstrings", Some (stringifyDocstrings m.docstring));
+        ( "source",
+          Some (stringifySource ~indentation:(indentation + 1) m.source) );
+        ( "items",
+          Some
+            (m.items
+            |> List.map
+                 (stringifyDocItem ~originalEnv ~indentation:(indentation + 1))
+            |> array) );
+      ]
+
+and stringifyDocsForModule ?(indentation = 0) ~originalEnv (d : docsForModule) =
+  let open Protocol in
+  stringifyObject ~startOnNewline:true ~indentation
+    [
+      ("name", Some (wrapInQuotes d.name));
+      ( "deprecated",
+        match d.deprecated with
+        | Some d -> Some (wrapInQuotes d)
+        | None -> None );
+      ("docstrings", Some (stringifyDocstrings d.docstring));
+      ("source", Some (stringifySource ~indentation:(indentation + 1) d.source));
+      ( "items",
+        Some
+          (d.items
+          |> List.map
+               (stringifyDocItem ~originalEnv ~indentation:(indentation + 1))
+          |> array) );
+    ]
+
+let fieldToFieldDoc (field : SharedTypes.field) : fieldDoc =
+  {
+    fieldName = field.fname.txt;
+    docstrings = field.docstring;
+    optional = field.optional;
+    signature = Shared.typeToString field.typ;
+    deprecated = field.deprecated;
+  }
+
+let typeDetail typ ~env ~full =
+  let open SharedTypes in
+  match TypeUtils.extractTypeFromResolvedType ~env ~full typ with
+  | Some (Trecord {fields}) ->
+    Some (Record {fieldDocs = fields |> List.map fieldToFieldDoc})
+  | Some (Tvariant {constructors}) ->
+    Some
+      (Variant
+         {
+           constructorDocs =
+             constructors
+             |> List.map (fun (c : Constructor.t) ->
+                    {
+                      constructorName = c.cname.txt;
+                      docstrings = c.docstring;
+                      signature = CompletionBackEnd.showConstructor c;
+                      deprecated = c.deprecated;
+                      items =
+                        (match c.args with
+                        | InlineRecord fields ->
+                          Some
+                            (InlineRecord
+                               {fieldDocs = fields |> List.map fieldToFieldDoc})
+                        | _ -> None);
+                    });
+         })
+  | _ -> None
+
+let makeId modulePath ~identifier =
+  identifier :: modulePath |> List.rev |> SharedTypes.ident
+
+let getSource ~rootPath ({loc_start} : Location.t) =
+  let line, col = Pos.ofLexing loc_start in
+  let filepath =
+    Files.relpath rootPath loc_start.pos_fname
+    |> Files.split Filename.dir_sep
+    |> String.concat "/"
+  in
+  {filepath; line = line + 1; col = col + 1}
+
+let extractDocs ~entryPointFile ~debug =
+  let path =
+    match Filename.is_relative entryPointFile with
+    | true -> Unix.realpath entryPointFile
+    | false -> entryPointFile
+  in
+  if debug then Printf.printf "extracting docs for %s\n" path;
+  let result =
+    match
+      FindFiles.isImplementation path = false
+      && FindFiles.isInterface path = false
+    with
+    | false -> (
+      let path =
+        if FindFiles.isImplementation path then
+          let pathAsResi =
+            (path |> Filename.dirname) ^ "/"
+            ^ (path |> Filename.basename |> Filename.chop_extension)
+            ^ ".resi"
+          in
+          if Sys.file_exists pathAsResi then (
+            if debug then
+              Printf.printf "preferring found resi file for impl: %s\n"
+                pathAsResi;
+            pathAsResi)
+          else path
+        else path
+      in
+      match Cmt.loadFullCmtFromPath ~path with
+      | None ->
+        Error
+          (Printf.sprintf
+             "error: failed to generate doc for %s, try to build the project"
+             path)
+      | Some full ->
+        let file = full.file in
+        let structure = file.structure in
+        let rootPath = full.package.rootPath in
+        let open SharedTypes in
+        let env = QueryEnv.fromFile file in
+        let rec extractDocsForModule ?(modulePath = [env.file.moduleName])
+            (structure : Module.structure) =
+          {
+            id = modulePath |> List.rev |> ident;
+            docstring = structure.docstring |> List.map String.trim;
+            name = structure.name;
+            moduletypeid = None;
+            deprecated = structure.deprecated;
+            source =
+              {
+                filepath =
+                  (match rootPath = "." with
+                  | true -> file.uri |> Uri.toPath
+                  | false ->
+                    Files.relpath rootPath (file.uri |> Uri.toPath)
+                    |> Files.split Filename.dir_sep
+                    |> String.concat "/");
+                line = 1;
+                col = 1;
+              };
+            items =
+              structure.items
+              |> List.filter_map (fun (item : Module.item) ->
+                     let item =
+                       {
+                         item with
+                         name = Ext_ident.unwrap_uppercase_exotic item.name;
+                       }
+                     in
+                     let source = getSource ~rootPath item.loc in
+                     match item.kind with
+                     | Value typ ->
+                       Some
+                         (Value
+                            {
+                              id = modulePath |> makeId ~identifier:item.name;
+                              docstring = item.docstring |> List.map String.trim;
+                              signature =
+                                "let " ^ item.name ^ ": "
+                                ^ Shared.typeToString typ;
+                              name = item.name;
+                              deprecated = item.deprecated;
+                              source;
+                            })
+                     | Type (typ, _) ->
+                       Some
+                         (Type
+                            {
+                              id = modulePath |> makeId ~identifier:item.name;
+                              docstring = item.docstring |> List.map String.trim;
+                              signature =
+                                typ.decl |> Shared.declToString item.name;
+                              name = item.name;
+                              deprecated = item.deprecated;
+                              detail = typeDetail typ ~full ~env;
+                              source;
+                            })
+                     | Module {type_ = Ident p; isModuleType = false} ->
+                       (* module Whatever = OtherModule *)
+                       let aliasToModule = p |> pathIdentToString in
+                       let id =
+                         (modulePath |> List.rev |> List.hd) ^ "." ^ item.name
+                       in
+                       let items, internalDocstrings =
+                         match
+                           ProcessCmt.fileForModule ~package:full.package
+                             aliasToModule
+                         with
+                         | None -> ([], [])
+                         | Some file ->
+                           let docs =
+                             extractDocsForModule ~modulePath:[id]
+                               file.structure
+                           in
+                           (docs.items, docs.docstring)
+                       in
+                       Some
+                         (ModuleAlias
+                            {
+                              id;
+                              name = item.name;
+                              source;
+                              items;
+                              docstring =
+                                item.docstring @ internalDocstrings
+                                |> List.map String.trim;
+                            })
+                     | Module {type_ = Structure m; isModuleType = false} ->
+                       (* module Whatever = {} in res or module Whatever: {} in resi. *)
+                       let modulePath = m.name :: modulePath in
+                       let docs = extractDocsForModule ~modulePath m in
+                       Some
+                         (Module
+                            {
+                              id = modulePath |> List.rev |> ident;
+                              name = m.name;
+                              moduletypeid = None;
+                              docstring = item.docstring @ m.docstring;
+                              deprecated = item.deprecated;
+                              source;
+                              items = docs.items;
+                            })
+                     | Module {type_ = Structure m; isModuleType = true} ->
+                       (* module type Whatever = {} *)
+                       let modulePath = m.name :: modulePath in
+                       let docs = extractDocsForModule ~modulePath m in
+                       Some
+                         (ModuleType
+                            {
+                              id = modulePath |> List.rev |> ident;
+                              name = m.name;
+                              docstring = item.docstring @ m.docstring;
+                              deprecated = item.deprecated;
+                              source;
+                              items = docs.items;
+                            })
+                     | Module
+                         {
+                           type_ =
+                             Constraint (Structure _impl, Structure interface);
+                         } ->
+                       (* module Whatever: { <interface> } = { <impl> }. Prefer the interface. *)
+                       Some
+                         (Module
+                            (extractDocsForModule
+                               ~modulePath:(interface.name :: modulePath)
+                               interface))
+                     | Module {type_ = Constraint (Structure m, Ident p)} ->
+                       (* module M: T = { <impl> }. Print M *)
+                       let docs =
+                         extractDocsForModule ~modulePath:(m.name :: modulePath)
+                           m
+                       in
+                       let identModulePath = p |> Path.head |> Ident.name in
+
+                       let moduleTypeIdPath =
+                         match
+                           ProcessCmt.fileForModule ~package:full.package
+                             identModulePath
+                           |> Option.is_none
+                         with
+                         | false -> []
+                         | true -> [modulePath |> List.rev |> List.hd]
+                       in
+
+                       Some
+                         (Module
+                            {
+                              docs with
+                              moduletypeid =
+                                Some
+                                  (makeId ~identifier:(Path.name p)
+                                     moduleTypeIdPath);
+                            })
+                     | _ -> None);
+          }
+        in
+        let docs = extractDocsForModule structure in
+        Ok (stringifyDocsForModule ~originalEnv:env docs))
+    | true ->
+      Error
+        (Printf.sprintf
+           "error: failed to read %s, expected an .res or .resi file" path)
+  in
+
+  result
+
+let extractEmbedded ~extensionPoints ~filename =
+  let {Res_driver.parsetree = structure} =
+    Res_driver.parsing_engine.parse_implementation ~for_printer:false ~filename
+  in
+  let content = ref [] in
+  let append item = content := item :: !content in
+  let extension (iterator : Ast_iterator.iterator) (ext : Parsetree.extension) =
+    (match ext with
+    | ( {txt},
+        PStr
+          [
+            {
+              pstr_desc =
+                Pstr_eval
+                  ( {
+                      pexp_loc;
+                      pexp_desc = Pexp_constant (Pconst_string (contents, _));
+                    },
+                    _ );
+            };
+          ] )
+      when extensionPoints |> List.exists (fun v -> v = txt) ->
+      append (pexp_loc, txt, contents)
+    | _ -> ());
+    Ast_iterator.default_iterator.extension iterator ext
+  in
+  let iterator = {Ast_iterator.default_iterator with extension} in
+  iterator.structure iterator structure;
+  let open Analysis.Protocol in
+  !content
+  |> List.map (fun (loc, extensionName, contents) ->
+         stringifyObject
+           [
+             ("extensionName", Some (wrapInQuotes extensionName));
+             ("contents", Some (wrapInQuotes contents));
+             ("loc", Some (Analysis.Utils.cmtLocToRange loc |> stringifyRange));
+           ])
+  |> List.rev |> array