From 4103f47b4c7258cb46099d9694b0e374353f66af Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Sat, 14 Feb 2026 00:07:34 +0100 Subject: [PATCH 1/2] Fix Parmatch crash on empty record head patterns --- compiler/ml/parmatch.ml | 2 +- .../parmatch_empty_record_pattern/input.js | 7 ++++ .../rescript.json | 5 +++ .../src/repro.js | 41 +++++++++++++++++++ .../src/repro.res | 26 ++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/build_tests/parmatch_empty_record_pattern/input.js create mode 100644 tests/build_tests/parmatch_empty_record_pattern/rescript.json create mode 100644 tests/build_tests/parmatch_empty_record_pattern/src/repro.js create mode 100644 tests/build_tests/parmatch_empty_record_pattern/src/repro.res diff --git a/compiler/ml/parmatch.ml b/compiler/ml/parmatch.ml index 9e66800c8b4..ebb6903c661 100644 --- a/compiler/ml/parmatch.ml +++ b/compiler/ml/parmatch.ml @@ -512,6 +512,7 @@ let extract_fields omegas arg = let all_record_args lbls = match lbls with + | [] -> [] | (_, {lbl_all}, _, opt) :: _ -> let t = Array.map @@ -560,7 +561,6 @@ let all_record_args lbls = t.(lbl.lbl_pos) <- x) lbls; Array.to_list t - | _ -> fatal_error "Parmatch.all_record_args" (* Build argument list when p2 >= p1, where p1 is a simple pattern *) let rec simple_match_args p1 p2 = diff --git a/tests/build_tests/parmatch_empty_record_pattern/input.js b/tests/build_tests/parmatch_empty_record_pattern/input.js new file mode 100644 index 00000000000..50a4ceabd18 --- /dev/null +++ b/tests/build_tests/parmatch_empty_record_pattern/input.js @@ -0,0 +1,7 @@ +// @ts-check + +import { setup } from "#dev/process"; + +const { execBuild } = setup(import.meta.dirname); + +await execBuild(); diff --git a/tests/build_tests/parmatch_empty_record_pattern/rescript.json b/tests/build_tests/parmatch_empty_record_pattern/rescript.json new file mode 100644 index 00000000000..a7e19c26539 --- /dev/null +++ b/tests/build_tests/parmatch_empty_record_pattern/rescript.json @@ -0,0 +1,5 @@ +{ + "name": "parmatch_empty_record_pattern", + "sources": ["src"], + "dependencies": [] +} diff --git a/tests/build_tests/parmatch_empty_record_pattern/src/repro.js b/tests/build_tests/parmatch_empty_record_pattern/src/repro.js new file mode 100644 index 00000000000..b443a600d3e --- /dev/null +++ b/tests/build_tests/parmatch_empty_record_pattern/src/repro.js @@ -0,0 +1,41 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function classify(request) { + if (request.method !== "Post") { + return 0; + } + if (request.headers.Authorization !== "Token") { + return 0; + } + let match = request.body._0; + if (typeof match !== "object" || match === null || Array.isArray(match)) { + return 0; + } + let match$1 = match.request; + if (typeof match$1 !== "string") { + return 0; + } + if (match$1 !== "READ") { + return 0; + } + let match$2 = match.payload; + if (typeof match$2 !== "object" || match$2 === null || Array.isArray(match$2)) { + return 0; + } + let match$3 = match$2["list-a"]; + if (typeof match$3 !== "object" || match$3 === null || Array.isArray(match$3)) { + return 0; + } + let tmp = match$3.last_known_events; + if (typeof tmp === "object" && tmp !== null && !Array.isArray(tmp) && request.url.includes("/todo/events")) { + return 1; + } else { + return 0; + } +} + +export { + classify, +} +/* No side effect */ diff --git a/tests/build_tests/parmatch_empty_record_pattern/src/repro.res b/tests/build_tests/parmatch_empty_record_pattern/src/repro.res new file mode 100644 index 00000000000..536cb02259c --- /dev/null +++ b/tests/build_tests/parmatch_empty_record_pattern/src/repro.res @@ -0,0 +1,26 @@ +type method_ = Post | Get + +type body = Json(JSON.t) + +type request = { + method: method_, + url: string, + headers: dict, + body: body, +} + +let classify = request => + switch request { + | { + method: Post, + url, + headers: dict{"Authorization": "Token"}, + body: Json(JSON.Object(dict{ + "request": JSON.String("READ"), + "payload": JSON.Object(dict{ + "list-a": JSON.Object(dict{"last_known_events": JSON.Object(dict{})}), + }), + })), + } if url->String.includes("/todo/events") => 1 + | _ => 0 + } From 8a2e1dda88b69ca1ee8c6cb89b6a37d9227c9af2 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 16 Feb 2026 09:50:34 +0100 Subject: [PATCH 2/2] Simpler test --- .../parmatch_empty_record_pattern/input.js | 7 ---- .../rescript.json | 5 --- .../src/repro.js | 41 ------------------- .../src/repro.res | 26 ------------ .../parmatch_empty_record_pattern_test.mjs | 13 ++++++ .../parmatch_empty_record_pattern_test.res | 4 ++ 6 files changed, 17 insertions(+), 79 deletions(-) delete mode 100644 tests/build_tests/parmatch_empty_record_pattern/input.js delete mode 100644 tests/build_tests/parmatch_empty_record_pattern/rescript.json delete mode 100644 tests/build_tests/parmatch_empty_record_pattern/src/repro.js delete mode 100644 tests/build_tests/parmatch_empty_record_pattern/src/repro.res create mode 100644 tests/tests/src/parmatch_empty_record_pattern_test.mjs create mode 100644 tests/tests/src/parmatch_empty_record_pattern_test.res diff --git a/tests/build_tests/parmatch_empty_record_pattern/input.js b/tests/build_tests/parmatch_empty_record_pattern/input.js deleted file mode 100644 index 50a4ceabd18..00000000000 --- a/tests/build_tests/parmatch_empty_record_pattern/input.js +++ /dev/null @@ -1,7 +0,0 @@ -// @ts-check - -import { setup } from "#dev/process"; - -const { execBuild } = setup(import.meta.dirname); - -await execBuild(); diff --git a/tests/build_tests/parmatch_empty_record_pattern/rescript.json b/tests/build_tests/parmatch_empty_record_pattern/rescript.json deleted file mode 100644 index a7e19c26539..00000000000 --- a/tests/build_tests/parmatch_empty_record_pattern/rescript.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "parmatch_empty_record_pattern", - "sources": ["src"], - "dependencies": [] -} diff --git a/tests/build_tests/parmatch_empty_record_pattern/src/repro.js b/tests/build_tests/parmatch_empty_record_pattern/src/repro.js deleted file mode 100644 index b443a600d3e..00000000000 --- a/tests/build_tests/parmatch_empty_record_pattern/src/repro.js +++ /dev/null @@ -1,41 +0,0 @@ -// Generated by ReScript, PLEASE EDIT WITH CARE - - -function classify(request) { - if (request.method !== "Post") { - return 0; - } - if (request.headers.Authorization !== "Token") { - return 0; - } - let match = request.body._0; - if (typeof match !== "object" || match === null || Array.isArray(match)) { - return 0; - } - let match$1 = match.request; - if (typeof match$1 !== "string") { - return 0; - } - if (match$1 !== "READ") { - return 0; - } - let match$2 = match.payload; - if (typeof match$2 !== "object" || match$2 === null || Array.isArray(match$2)) { - return 0; - } - let match$3 = match$2["list-a"]; - if (typeof match$3 !== "object" || match$3 === null || Array.isArray(match$3)) { - return 0; - } - let tmp = match$3.last_known_events; - if (typeof tmp === "object" && tmp !== null && !Array.isArray(tmp) && request.url.includes("/todo/events")) { - return 1; - } else { - return 0; - } -} - -export { - classify, -} -/* No side effect */ diff --git a/tests/build_tests/parmatch_empty_record_pattern/src/repro.res b/tests/build_tests/parmatch_empty_record_pattern/src/repro.res deleted file mode 100644 index 536cb02259c..00000000000 --- a/tests/build_tests/parmatch_empty_record_pattern/src/repro.res +++ /dev/null @@ -1,26 +0,0 @@ -type method_ = Post | Get - -type body = Json(JSON.t) - -type request = { - method: method_, - url: string, - headers: dict, - body: body, -} - -let classify = request => - switch request { - | { - method: Post, - url, - headers: dict{"Authorization": "Token"}, - body: Json(JSON.Object(dict{ - "request": JSON.String("READ"), - "payload": JSON.Object(dict{ - "list-a": JSON.Object(dict{"last_known_events": JSON.Object(dict{})}), - }), - })), - } if url->String.includes("/todo/events") => 1 - | _ => 0 - } diff --git a/tests/tests/src/parmatch_empty_record_pattern_test.mjs b/tests/tests/src/parmatch_empty_record_pattern_test.mjs new file mode 100644 index 00000000000..a1d4eda9c80 --- /dev/null +++ b/tests/tests/src/parmatch_empty_record_pattern_test.mjs @@ -0,0 +1,13 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +let match = {}; + +let emptyDictPatternShouldCompile; + +emptyDictPatternShouldCompile = typeof match === "object" && match !== null && !Array.isArray(match); + +export { + emptyDictPatternShouldCompile, +} +/* emptyDictPatternShouldCompile Not a pure module */ diff --git a/tests/tests/src/parmatch_empty_record_pattern_test.res b/tests/tests/src/parmatch_empty_record_pattern_test.res new file mode 100644 index 00000000000..0b0fcad5958 --- /dev/null +++ b/tests/tests/src/parmatch_empty_record_pattern_test.res @@ -0,0 +1,4 @@ +let emptyDictPatternShouldCompile = switch JSON.Object(dict{}) { +| JSON.Object(dict{}) => true +| _ => false +}