From a1b3d752ce9bf60c0b22eb6338196b3bca7aad30 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 4 Mar 2026 11:20:01 +0000 Subject: [PATCH 1/5] type-constrained alias patterns must be parenthesized --- src/reason-parser/reason_pprint_ast.ml | 13 ++++++++++++- test/value-constraint-alias-pattern.t/input.re | 4 ++++ test/value-constraint-alias-pattern.t/run.t | 13 +++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 test/value-constraint-alias-pattern.t/input.re create mode 100644 test/value-constraint-alias-pattern.t/run.t diff --git a/src/reason-parser/reason_pprint_ast.ml b/src/reason-parser/reason_pprint_ast.ml index a77846dbf..4281d537c 100644 --- a/src/reason-parser/reason_pprint_ast.ml +++ b/src/reason-parser/reason_pprint_ast.ml @@ -6244,7 +6244,18 @@ let createFormatter () = ~loc:pat.ppat_loc (match vbct with | Some _ -> - self#pattern_with_precedence ~attrs:pat.ppat_attributes pat + (match pat.ppat_desc with + | Ppat_alias _ -> + makeList + ~wrap:("(", ")") + [ self#pattern_with_precedence + ~attrs:pat.ppat_attributes + pat + ] + | _ -> + self#pattern_with_precedence + ~attrs:pat.ppat_attributes + pat) | None -> self#pattern pat) in let appTerms = self#unparseExprApplicationItems expr in diff --git a/test/value-constraint-alias-pattern.t/input.re b/test/value-constraint-alias-pattern.t/input.re new file mode 100644 index 000000000..c08c35669 --- /dev/null +++ b/test/value-constraint-alias-pattern.t/input.re @@ -0,0 +1,4 @@ +let (x as y): t = x; +let ({url, mode, protocol} as target): TargetT.Safe.t = + multiTarget->MultiTargetT.toIgnorableTargetT; +let ({url, mode}): t = x; diff --git a/test/value-constraint-alias-pattern.t/run.t b/test/value-constraint-alias-pattern.t/run.t new file mode 100644 index 000000000..a27230781 --- /dev/null +++ b/test/value-constraint-alias-pattern.t/run.t @@ -0,0 +1,13 @@ + $ refmt ./input.re | tee formatted.re + let (x as y): t = x; + let ({ url, mode, protocol } as target): TargetT.Safe.t = + multiTarget->MultiTargetT.toIgnorableTargetT; + let { url, mode }: t = x; + + $ refmt ./formatted.re | tee formatted_back.re + let (x as y): t = x; + let ({ url, mode, protocol } as target): TargetT.Safe.t = + multiTarget->MultiTargetT.toIgnorableTargetT; + let { url, mode }: t = x; + + $ diff formatted.re formatted_back.re From ba8471a5da7a849c9fcf845a9abf91ce44c70ad1 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 4 Mar 2026 11:34:38 +0000 Subject: [PATCH 2/5] add more cases on value-constraint-alias --- .../value-constraint-alias-pattern.t/input.re | 10 ++++++++++ test/value-constraint-alias-pattern.t/run.t | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/test/value-constraint-alias-pattern.t/input.re b/test/value-constraint-alias-pattern.t/input.re index c08c35669..403a2745b 100644 --- a/test/value-constraint-alias-pattern.t/input.re +++ b/test/value-constraint-alias-pattern.t/input.re @@ -1,4 +1,14 @@ +let x: t = x; let (x as y): t = x; +let ((x, y) as pair): t = x; +let (Some(x) as opt): t = opt; +let ({url, mode} as target): t = x; let ({url, mode, protocol} as target): TargetT.Safe.t = multiTarget->MultiTargetT.toIgnorableTargetT; let ({url, mode}): t = x; +let ({url: u, mode: m} as target): t = x; +let Foo.{url, mode}: t = x; +let (Foo.{url, mode} as target): t = x; +let ([x, y] as listPair): t = value; +let (_ as anyValue): t = value; +let ((x as y: u): t) = value; diff --git a/test/value-constraint-alias-pattern.t/run.t b/test/value-constraint-alias-pattern.t/run.t index a27230781..ecc06d761 100644 --- a/test/value-constraint-alias-pattern.t/run.t +++ b/test/value-constraint-alias-pattern.t/run.t @@ -1,13 +1,33 @@ $ refmt ./input.re | tee formatted.re + let x: t = x; let (x as y): t = x; + let ((x, y) as pair): t = x; + let (Some(x) as opt): t = opt; + let ({ url, mode } as target): t = x; let ({ url, mode, protocol } as target): TargetT.Safe.t = multiTarget->MultiTargetT.toIgnorableTargetT; let { url, mode }: t = x; + let ({ url: u, mode: m } as target): t = x; + let Foo.{ url, mode } : t = x; + let (Foo.{ url, mode } as target): t = x; + let ([x, y] as listPair): t = value; + let (_ as anyValue): t = value; + let ((x as y: u): t) = value; $ refmt ./formatted.re | tee formatted_back.re + let x: t = x; let (x as y): t = x; + let ((x, y) as pair): t = x; + let (Some(x) as opt): t = opt; + let ({ url, mode } as target): t = x; let ({ url, mode, protocol } as target): TargetT.Safe.t = multiTarget->MultiTargetT.toIgnorableTargetT; let { url, mode }: t = x; + let ({ url: u, mode: m } as target): t = x; + let Foo.{ url, mode } : t = x; + let (Foo.{ url, mode } as target): t = x; + let ([x, y] as listPair): t = value; + let (_ as anyValue): t = value; + let ((x as y: u): t) = value; $ diff formatted.re formatted_back.re From dacf9565617e0537d0a5547a492dcb66a1497374 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 5 Mar 2026 10:28:50 +0000 Subject: [PATCH 3/5] Fix assertion failure when expression and binding both have type constraints --- src/reason-parser/reason_pprint_ast.ml | 3 +- test/expr-constraint-with-vbct.t/input.re | 15 +++++++++ test/expr-constraint-with-vbct.t/run.t | 41 +++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 test/expr-constraint-with-vbct.t/input.re create mode 100644 test/expr-constraint-with-vbct.t/run.t diff --git a/src/reason-parser/reason_pprint_ast.ml b/src/reason-parser/reason_pprint_ast.ml index 4281d537c..c3c81914b 100644 --- a/src/reason-parser/reason_pprint_ast.ml +++ b/src/reason-parser/reason_pprint_ast.ml @@ -5913,8 +5913,7 @@ let createFormatter () = (pattern :: patternAux) in match argsList, return.pexp_desc with - | [], Pexp_constraint (e, ct) -> - assert (vbct = None); + | [], Pexp_constraint (e, ct) when vbct = None -> let typeLayout = source_map ~loc:ct.ptyp_loc diff --git a/test/expr-constraint-with-vbct.t/input.re b/test/expr-constraint-with-vbct.t/input.re new file mode 100644 index 000000000..a7293183d --- /dev/null +++ b/test/expr-constraint-with-vbct.t/input.re @@ -0,0 +1,15 @@ +/* Array literal with type constraint */ +let x: array(int) = ([|1, 2|]: array(int)); + +/* List literal with type constraint */ +let x: list(int) = ([1, 2, 3]: list(int)); + +/* Tuple with type constraint */ +let x: (int, string) = ((1, "a"): (int, string)); + +/* Function expression with type constraint */ +let f: int => int = ((x => x + 1): int => int); + +/* Record with type constraint */ +type t = {a: int}; +let x: t = ({a: 1}: t); diff --git a/test/expr-constraint-with-vbct.t/run.t b/test/expr-constraint-with-vbct.t/run.t new file mode 100644 index 000000000..060f3379e --- /dev/null +++ b/test/expr-constraint-with-vbct.t/run.t @@ -0,0 +1,41 @@ +Format expression type constraints when binding also has a type constraint + $ refmt ./input.re | tee formatted.re + /* Array literal with type constraint */ + let x: array(int) = ([|1, 2|]: array(int)); + + /* List literal with type constraint */ + let x: list(int) = ([1, 2, 3]: list(int)); + + /* Tuple with type constraint */ + let x: (int, string) = ( + (1, "a"): (int, string) + ); + + /* Function expression with type constraint */ + let f: int => int = (x => x + 1: int => int); + + /* Record with type constraint */ + type t = {a: int}; + let x: t = ({ a: 1 }: t); + +Idempotency check + $ refmt ./formatted.re | tee formatted_back.re + /* Array literal with type constraint */ + let x: array(int) = ([|1, 2|]: array(int)); + + /* List literal with type constraint */ + let x: list(int) = ([1, 2, 3]: list(int)); + + /* Tuple with type constraint */ + let x: (int, string) = ( + (1, "a"): (int, string) + ); + + /* Function expression with type constraint */ + let f: int => int = (x => x + 1: int => int); + + /* Record with type constraint */ + type t = {a: int}; + let x: t = ({ a: 1 }: t); + + $ diff formatted.re formatted_back.re From 7d57719c2f05c250ab866a175138bab72b4bd71b Mon Sep 17 00:00:00 2001 From: David Sancho Date: Thu, 5 Mar 2026 11:58:45 +0100 Subject: [PATCH 4/5] Fix rtopIntegration test for Windows (#2914) * test(rtop): capture stderr in integration test for Windows * ci: setup tmate * test(rtop): avoid running rtop in windows 4.14.* * ci: enable 5.4.0 in windows * ci: remove action-tmate * ci: revert all changes, keep building * ci: add -preasonrtop in esy-ci * ci: update cache keys * ci: fix Windows CI failures - Disable rtopIntegration test on Windows due to lambda-term segfault when stdout is not a real console handle (ConPTY issue on GH Actions). See: https://github.com/ocaml-community/lambda-term/pull/125 - Add setup-mingw step in esy-ci for Windows to provide x86_64-w64-mingw32-gcc needed to compile lambda-term C stubs. - Bump esy cache keys to v0.0.2 to avoid stale caches. * ci: replace setup-mingw action with direct choco install The egor-tensin/setup-mingw@v2 action fails on MinGW 15.2.0 because its cleanup script tries to remove libpthread.dll.a which no longer exists in UCRT-based builds. Use choco directly and add the bin directory to PATH manually. * ci: esy-ci doesn't run rtop tests on windows --- .github/workflows/esy-ci.yml | 17 ++++++++++++----- .github/workflows/opam-ci.yml | 9 +++++---- test/dune | 5 +++++ test/rtopIntegration.t | 4 ++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/esy-ci.yml b/.github/workflows/esy-ci.yml index 2783730b4..124f31998 100644 --- a/.github/workflows/esy-ci.yml +++ b/.github/workflows/esy-ci.yml @@ -36,6 +36,13 @@ jobs: with: node-version: 20 + - name: Set up MinGW (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + choco upgrade mingw -y --no-progress + echo "C:\ProgramData\mingw64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install esy run: npm install -g esy@0.9.0-beta.1 @@ -44,7 +51,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ~/.esy/source - key: esy-source-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} + key: v0.0.2-esy-source-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} - name: Print esy cache id: print_esy_cache @@ -57,8 +64,8 @@ jobs: path: | ${{ steps.print_esy_cache.outputs.ESY_CACHE }} _export - key: esy-build-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} - restore-keys: esy-build-${{ matrix.os }}- + key: v0.0.2-esy-build-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} + restore-keys: v0.0.2-esy-build-${{ matrix.os }}- - name: Install dependencies run: esy install @@ -94,7 +101,7 @@ jobs: if: steps.global-cache.outputs.cache-hit != 'true' with: path: ~/.esy/source - key: esy-source-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} + key: v0.0.2-esy-source-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} - name: Save dependencies cache if: steps.deps-cache.outputs.cache-hit != 'true' @@ -103,7 +110,7 @@ jobs: path: | ${{ steps.print_esy_cache.outputs.ESY_CACHE }} _export - key: esy-build-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} + key: v0.0.2-esy-build-${{ matrix.os }}-${{ matrix.ocaml-compiler }}-${{ hashFiles('esy.lock.json') }} # Cleanup build cache in case dependencies have changed - name: Cleanup diff --git a/.github/workflows/opam-ci.yml b/.github/workflows/opam-ci.yml index e4e8a1e25..f0ebe912d 100644 --- a/.github/workflows/opam-ci.yml +++ b/.github/workflows/opam-ci.yml @@ -32,6 +32,7 @@ jobs: # OCaml >= 4.13 # https://github.com/ocaml/setup-ocaml/issues/822#issuecomment-2215525942 - {ocaml-compiler: '4.14.x', os: windows-latest} + - {ocaml-compiler: 'ocaml-base-compiler.5.4.0', os: windows-latest} runs-on: ${{ matrix.setup.os }} @@ -51,7 +52,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ~/.opam - key: opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('*.opam') }} + key: v0.0.1-opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('*.opam') }} - name: Load opam cache when Windows if: runner.os == 'Windows' @@ -59,7 +60,7 @@ jobs: uses: actions/cache/restore@v4 with: path: _opam - key: opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} + key: v0.0.1-opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} - name: Install dependencies run: opam install . --deps-only @@ -87,12 +88,12 @@ jobs: if: steps.opam-cache.outputs.cache-hit != 'true' && runner.os != 'Windows' with: path: ~/.opam - key: opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} + key: v0.0.1-opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} - name: Save cache when Windows uses: actions/cache/save@v4 if: steps.opam-cache-windows.outputs.cache-hit != 'true' && runner.os == 'Windows' with: path: _opam - key: opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} + key: v0.0.1-opam-${{ matrix.setup.os }}-${{ matrix.setup.ocaml-compiler }}-${{ hashFiles('**.opam') }} diff --git a/test/dune b/test/dune index 7c1e528e0..418b8bf80 100644 --- a/test/dune +++ b/test/dune @@ -12,7 +12,12 @@ ./lib/outcometreePrinter.exe ./lib/fdLeak.exe)) +; rtopIntegration is disabled on Windows due to a segfault in lambda-term's +; Windows C stubs when stdout is not a real console handle (ConPTY issue). +; See: https://github.com/ocaml-community/lambda-term/pull/125 +; Re-enable on Windows once lambda-term ships the fix. (cram (applies_to rtopIntegration) (package rtop) + (enabled_if (= %{os_type} Unix)) (deps %{bin:ocamlc} %{bin:rtop})) diff --git a/test/rtopIntegration.t b/test/rtopIntegration.t index 66f9be4a3..1cac5f452 100644 --- a/test/rtopIntegration.t +++ b/test/rtopIntegration.t @@ -11,8 +11,8 @@ always error. Given the above, we're gonna test that utop integration works by piping code into it and asserting the existence of some output. - $ echo "let f = a => a;" | rtop | grep -o "let f: 'a => 'a = ;" + $ echo "let f = a => a;" | rtop 2>&1 | grep -o "let f: 'a => 'a = ;" let f: 'a => 'a = ; - $ echo "let f = (a) => 1 + \"hi\";" | rtop | grep -o "has type" + $ echo "let f = (a) => 1 + \"hi\";" | rtop 2>&1 | grep -o "has type" has type From 54033235fff6390dc3d4a6374df0d7d855c98350 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 5 Mar 2026 12:58:31 +0000 Subject: [PATCH 5/5] test: remove comment for rtopIntegration --- test/dune | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/dune b/test/dune index 418b8bf80..196ecd701 100644 --- a/test/dune +++ b/test/dune @@ -12,10 +12,6 @@ ./lib/outcometreePrinter.exe ./lib/fdLeak.exe)) -; rtopIntegration is disabled on Windows due to a segfault in lambda-term's -; Windows C stubs when stdout is not a real console handle (ConPTY issue). -; See: https://github.com/ocaml-community/lambda-term/pull/125 -; Re-enable on Windows once lambda-term ships the fix. (cram (applies_to rtopIntegration) (package rtop)