From 5aafa890b0d1a47b982a144794db046a52bd8dea Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 19 Aug 2026 11:17:25 +0200 Subject: [PATCH 01/10] Fix rewatch warning replay after early compile errors (#8408) Backport the fix by tracking modules that actually recompiled, so stored warnings are replayed for dependents skipped after an earlier error. (cherry picked from commit 56d4ed5fe678bef15e9828fe60e2e91643e589ed) Signed-off-by: Christoph Knittel --- CHANGELOG.md | 6 ++++++ rewatch/src/build/compile.rs | 10 ++++++---- rewatch/tests/watch.sh | 36 +++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0aa152dca..22949a89a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ > - :nail_care: [Polish] > - :house: [Internal] +# 12.3.1 (Unreleased) + +#### :bug: Bug fix + +- Fix rewatch warning replay after early compile errors. https://github.com/rescript-lang/rescript/pull/8408 + # 12.3.0 No changes compared to 12.3.0-beta.1. diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index 4c735df6218..827bc322e59 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -113,6 +113,7 @@ pub fn compile( let mut compile_errors = "".to_string(); let mut compile_warnings = "".to_string(); let mut num_compiled_modules = 0; + let mut recompiled_modules = AHashSet::::new(); let mut sorted_modules = build_state.module_names.iter().collect::>(); sorted_modules.sort(); @@ -271,6 +272,7 @@ pub fn compile( if *is_compiled { num_compiled_modules += 1; + recompiled_modules.insert(module_name.to_string()); } files_current_loop_count += 1; @@ -443,11 +445,11 @@ pub fn compile( }; } - // Collect warnings from modules that were not recompiled in this build - // but still have stored warnings from a previous compilation. - // This ensures warnings are not lost during incremental builds in watch mode. + // Collect warnings from modules that were not recompiled in this build but still have stored + // warnings from a previous compilation. This includes modules in the compile universe that + // were never reached because an earlier module failed. for (module_name, module) in build_state.modules.iter() { - if compile_universe.contains(module_name) { + if recompiled_modules.contains(module_name) { continue; } if let SourceType::SourceFile(ref source_file) = module.source_type { diff --git a/rewatch/tests/watch.sh b/rewatch/tests/watch.sh index bcd0158956e..f5898a4e47e 100755 --- a/rewatch/tests/watch.sh +++ b/rewatch/tests/watch.sh @@ -39,6 +39,40 @@ else exit 1 fi +bold "Test: Stored warnings are replayed after an early compile error" +warning_count=$(grep -c "unusedValue" rewatch.log || true) +echo 'B.world()' >> ./packages/watch-warnings/src/ModuleA.res +timeout=20 +while [ "$(grep -c "unusedValue" rewatch.log || true)" -le "$warning_count" ] && [ "$timeout" -gt 0 ]; do + sleep 1 + timeout=$((timeout - 1)) +done +if [ "$timeout" -eq 0 ]; then + error "Expected warning was not emitted before the error test" + git checkout -- ./packages/watch-warnings/src/ModuleA.res + exit_watcher + exit 1 +fi + +error_log_start=$(($(wc -l < rewatch.log) + 1)) +echo 'let broken: int = "broken"' >> ./packages/watch-warnings/src/B.res +timeout=20 +while ! tail -n +"$error_log_start" rewatch.log | grep -q 'let broken' && [ "$timeout" -gt 0 ]; do + sleep 1 + timeout=$((timeout - 1)) +done +warning_replay_output=$(tail -n +"$error_log_start" rewatch.log) +if [[ "$warning_replay_output" == *"unusedValue"* ]]; then + success "Stored warning was replayed" +else + error "Stored warning was not replayed" + printf "%s\n" "$warning_replay_output" >&2 + git checkout -- ./packages/watch-warnings/src/ModuleA.res ./packages/watch-warnings/src/B.res + exit_watcher + exit 1 +fi +git checkout -- ./packages/watch-warnings/src/ModuleA.res ./packages/watch-warnings/src/B.res + sleep 1 replace '/Js.log("added-by-test")/d' ./packages/main/src/Main.res; @@ -54,4 +88,4 @@ else exit 1 fi -exit_watcher \ No newline at end of file +exit_watcher From 96e18fb589667444b8a74a4d66b051e4bbe3c1ec Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 26 May 2026 15:17:01 +0200 Subject: [PATCH 02/10] Fix formatting of trailing comments before equals (#8444) --- CHANGELOG.md | 1 + compiler/syntax/src/res_printer.ml | 26 ++++++++++++------- .../comments/expected/valueBindings.res.txt | 8 ++++++ .../data/printer/comments/valueBindings.res | 8 ++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22949a89a9c..40c7ad606d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ #### :bug: Bug fix - Fix rewatch warning replay after early compile errors. https://github.com/rescript-lang/rescript/pull/8408 +- Fix formatting of trailing comments before `=` in let bindings. https://github.com/rescript-lang/rescript/pull/8444 # 12.3.0 diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 2010d23f6dd..40c2b33fcf7 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -70,6 +70,11 @@ let has_trailing_single_line_comment tbl loc = | Some (comment :: _) -> Comment.is_single_line_comment comment | _ -> false +let has_any_trailing_line_comment tbl loc = + match Hashtbl.find_opt tbl.CommentTable.trailing loc with + | Some comments -> List.exists Comment.is_single_line_comment comments + | None -> false + let has_comment_below tbl loc = match Hashtbl.find tbl.CommentTable.trailing loc with | comment :: _ -> @@ -2230,7 +2235,15 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl | Braced braces -> print_braces doc expr braces | Nothing -> doc in + let pattern_has_trailing_line_comment = + has_any_trailing_line_comment cmt_tbl vb.pvb_pat.ppat_loc + in let pattern_doc = print_pattern ~state vb.pvb_pat cmt_tbl in + let equal_doc = + if pattern_has_trailing_line_comment then + Doc.indent (Doc.concat [Doc.hard_line; Doc.equal]) + else Doc.text " =" + in (* * we want to optimize the layout of one pipe: * let tbl = data->Js.Array2.reduce((map, curr) => { @@ -2248,21 +2261,14 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl [ Doc.group (Doc.concat - [ - attrs; - header; - pattern_doc; - Doc.text " ="; - Doc.space; - printed_expr; - ]); + [attrs; header; pattern_doc; equal_doc; Doc.space; printed_expr]); Doc.group (Doc.concat [ attrs; header; pattern_doc; - Doc.text " ="; + equal_doc; Doc.indent (Doc.concat [Doc.line; printed_expr]); ]); ] @@ -2294,7 +2300,7 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl attrs; header; pattern_doc; - Doc.text " ="; + equal_doc; (if should_indent then Doc.indent (Doc.concat [Doc.line; printed_expr]) else Doc.concat [Doc.space; printed_expr]); diff --git a/tests/syntax_tests/data/printer/comments/expected/valueBindings.res.txt b/tests/syntax_tests/data/printer/comments/expected/valueBindings.res.txt index c87a9530b1e..e5cac613d77 100644 --- a/tests/syntax_tests/data/printer/comments/expected/valueBindings.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/valueBindings.res.txt @@ -10,6 +10,14 @@ let walkList: 'node. unit = comments => { let x /* comment */ = 0 } +let x // comment before equals + = 1 + +let multilineString // comment before equals + = " +multiline +" + let walkList: 'node. ( ~prevLoc: Location.t=?, ~getLoc: 'node => Location.t, diff --git a/tests/syntax_tests/data/printer/comments/valueBindings.res b/tests/syntax_tests/data/printer/comments/valueBindings.res index c47f0276e6b..b575aee5f4b 100644 --- a/tests/syntax_tests/data/printer/comments/valueBindings.res +++ b/tests/syntax_tests/data/printer/comments/valueBindings.res @@ -10,6 +10,14 @@ let walkList: 'node. unit = comments => { let x /* comment */ = 0 } +let x // comment before equals += 1 + +let multilineString // comment before equals += " +multiline +" + let walkList: 'node. ( ~prevLoc: Location.t=?, ~getLoc: 'node => Location.t, From 5560f61de28ec9cd1f4075d82be800a87ebba549 Mon Sep 17 00:00:00 2001 From: Benjamin Thomas Date: Tue, 2 Jun 2026 00:35:42 +0200 Subject: [PATCH 03/10] analysis: fix namespaced reference lookup (#8455) * analysis: fix namespaced reference lookup * analysis: demonstrate incremental CMT lookup --- CHANGELOG.md | 1 + analysis/src/Cmt.ml | 60 ++++++++++++------- analysis/src/References.ml | 31 +++++++--- tests/analysis_tests/Makefile | 2 + .../tests-namespaced-references/.gitignore | 2 + .../tests-namespaced-references/Makefile | 20 +++++++ .../tests-namespaced-references/package.json | 11 ++++ .../tests-namespaced-references/rescript.json | 17 ++++++ .../src/MyModule1.res | 6 ++ .../src/MyModule2.res | 2 + .../src/expected/MyModule1.res.txt | 9 +++ .../src/expected/MyModule2.res.txt | 5 ++ .../tests-namespaced-references/test.sh | 30 ++++++++++ .../ounit_analysis_references_tests.ml | 25 ++++++++ tests/ounit_tests/ounit_tests_main.ml | 1 + yarn.lock | 8 +++ 16 files changed, 203 insertions(+), 27 deletions(-) create mode 100644 tests/analysis_tests/tests-namespaced-references/.gitignore create mode 100644 tests/analysis_tests/tests-namespaced-references/Makefile create mode 100644 tests/analysis_tests/tests-namespaced-references/package.json create mode 100644 tests/analysis_tests/tests-namespaced-references/rescript.json create mode 100644 tests/analysis_tests/tests-namespaced-references/src/MyModule1.res create mode 100644 tests/analysis_tests/tests-namespaced-references/src/MyModule2.res create mode 100644 tests/analysis_tests/tests-namespaced-references/src/expected/MyModule1.res.txt create mode 100644 tests/analysis_tests/tests-namespaced-references/src/expected/MyModule2.res.txt create mode 100755 tests/analysis_tests/tests-namespaced-references/test.sh create mode 100644 tests/ounit_tests/ounit_analysis_references_tests.ml diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c7ad606d4..d0e49fcf8d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fix rewatch warning replay after early compile errors. https://github.com/rescript-lang/rescript/pull/8408 - Fix formatting of trailing comments before `=` in let bindings. https://github.com/rescript-lang/rescript/pull/8444 +- Fix namespaced reference lookup in editor analysis. https://github.com/rescript-lang/rescript/pull/8455 # 12.3.0 diff --git a/analysis/src/Cmt.ml b/analysis/src/Cmt.ml index ac1d5ae595f..72753b3040a 100644 --- a/analysis/src/Cmt.ml +++ b/analysis/src/Cmt.ml @@ -8,6 +8,32 @@ let fullForCmt ~moduleName ~package ~uri cmt = let extra = ProcessExtra.getExtra ~file ~infos in Some {file; extra; package} +let fullForIncrementalCmt ~package ~moduleName ~uri = + if !Cfg.inIncrementalTypecheckingMode then + let path = Uri.toPath uri in + let incrementalCmtPath = + package.rootPath ^ "/lib/bs/___incremental" ^ "/" ^ moduleName + ^ + match Files.classifySourceFile path with + | Resi -> ".cmti" + | _ -> ".cmt" + in + match fullForCmt ~moduleName ~package ~uri incrementalCmtPath with + | Some cmtInfo -> + if Debug.verbose () then + Printf.printf "[cmt] Found incremental cmt: %s\n" + (Filename.basename incrementalCmtPath); + Some cmtInfo + | None -> None + else None + +let fullFromModuleUri ~package ~moduleName ~uri ~paths = + match fullForIncrementalCmt ~package ~moduleName ~uri with + | Some cmtInfo -> Some cmtInfo + | None -> + let cmt = getCmtPath ~uri paths in + fullForCmt ~moduleName ~package ~uri cmt + let fullFromUri ~uri = let path = Uri.toPath uri in match Packages.getPackage ~uri with @@ -16,22 +42,8 @@ let fullFromUri ~uri = 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 + match fullForIncrementalCmt ~package ~moduleName ~uri with + | Some cmtInfo -> Some cmtInfo | None -> ( match Hashtbl.find_opt package.pathsForModule moduleName with | Some paths -> @@ -41,12 +53,20 @@ let fullFromUri ~uri = prerr_endline ("can't find module " ^ moduleName); None)) +let fullFromModule ~package ~moduleName = + Option.bind (Hashtbl.find_opt package.pathsForModule moduleName) + @@ fun paths -> + let uri = getUri paths in + fullFromModuleUri ~package ~moduleName ~uri ~paths + let fullsFromModule ~package ~moduleName = - if Hashtbl.mem package.pathsForModule moduleName then - let paths = Hashtbl.find package.pathsForModule moduleName in + match Hashtbl.find_opt package.pathsForModule moduleName with + | None -> [] + | Some paths -> let uris = getUris paths in - uris |> List.filter_map (fun uri -> fullFromUri ~uri) - else [] + uris + |> List.filter_map (fun uri -> + fullFromModuleUri ~package ~moduleName ~uri ~paths) let loadFullCmtFromPath ~path = let uri = Uri.fromPath path in diff --git a/analysis/src/References.ml b/analysis/src/References.ml index e047a2ba182..142cbfa2f89 100644 --- a/analysis/src/References.ml +++ b/analysis/src/References.ml @@ -18,6 +18,20 @@ let locItemsForPos ~extra pos = let lineColToCmtLoc ~pos:(line, col) = (line + 1, col) +(** External references in namespaced projects are indexed by the public + * namespace path, e.g. MyNamespace.MyModule1.myFunc1, while definitions live + * in hidden compiled modules like MyModule1-MyNamespace. + * We return the lookup key pair used by the external reference index. + *) +let normalizeExternalReferenceKey ~namespace ~moduleName ~path = + match namespace with + | Some namespace when Utils.endsWith moduleName ("-" ^ namespace) -> + let suffixLen = String.length namespace + 1 in + let sourceModuleLen = String.length moduleName - suffixLen in + let sourceModule = String.sub moduleName 0 sourceModuleLen in + (namespace, sourceModule :: path) + | _ -> (moduleName, path) + 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 @@ -485,6 +499,10 @@ let forLocalStamp ~full:{file; extra; package} stamp (tip : Tip.t) = in maybeLog ("Now checking path " ^ pathToString path); let thisModuleName = file.moduleName in + let normalizedModuleName, normalizedPath = + normalizeExternalReferenceKey ~namespace:package.namespace + ~moduleName:thisModuleName ~path + in let externals = package.projectFiles |> FileSet.elements |> List.filter (fun name -> name <> file.moduleName) @@ -493,14 +511,15 @@ let forLocalStamp ~full:{file; extra; package} stamp (tip : Tip.t) = |> List.map (fun {file; extra} -> match Hashtbl.find_opt extra.externalReferences - thisModuleName + normalizedModuleName with | None -> [] | Some refs -> let locs = refs |> Utils.filterMap (fun (p, t, locs) -> - if p = path && t = tip then Some locs + if p = normalizedPath && t = tip then + Some locs else None) in locs @@ -522,10 +541,8 @@ let allReferencesForLocItem ~full:({file; package} as full) locItem = | 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) + |> Utils.filterMap (fun moduleName -> + Cmt.fullFromModule ~package ~moduleName) |> List.map (fun full -> match Hashtbl.find_opt full.extra.fileReferences moduleName with | None -> [] @@ -563,7 +580,7 @@ let allReferencesForLocItem ~full:({file; package} as full) locItem = match exportedForTip ~env ~path ~package ~tip with | None -> [] | Some (env, _name, stamp) -> ( - match Cmt.fullFromUri ~uri:env.file.uri with + match Cmt.fullFromModule ~package ~moduleName:env.file.moduleName with | None -> [] | Some full -> maybeLog diff --git a/tests/analysis_tests/Makefile b/tests/analysis_tests/Makefile index 0fd0226e6a0..741c90403ec 100644 --- a/tests/analysis_tests/Makefile +++ b/tests/analysis_tests/Makefile @@ -4,6 +4,7 @@ test-analysis-binary: make -C tests test make -C tests-generic-jsx-transform test make -C tests-incremental-typechecking test + make -C tests-namespaced-references test make -C tests-sourcedirs-dependency test test-reanalyze: @@ -15,6 +16,7 @@ clean: make -C tests clean make -C tests-generic-jsx-transform clean make -C tests-incremental-typechecking clean + make -C tests-namespaced-references clean make -C tests-sourcedirs-dependency clean make -C tests-reanalyze clean diff --git a/tests/analysis_tests/tests-namespaced-references/.gitignore b/tests/analysis_tests/tests-namespaced-references/.gitignore new file mode 100644 index 00000000000..3b4e8dcf930 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/.gitignore @@ -0,0 +1,2 @@ +lib/ +node_modules/ diff --git a/tests/analysis_tests/tests-namespaced-references/Makefile b/tests/analysis_tests/tests-namespaced-references/Makefile new file mode 100644 index 00000000000..2b663dd3475 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/Makefile @@ -0,0 +1,20 @@ +SHELL = /bin/bash + +build: + yarn build + + # Simulate editor-written incremental CMTs + mkdir -p lib/bs/___incremental + cp lib/bs/src/MyModule1-MyNamespace.cmt lib/bs/___incremental/MyModule1-MyNamespace.cmt + cp lib/bs/src/MyModule2-MyNamespace.cmt lib/bs/___incremental/MyModule2-MyNamespace.cmt + cp lib/bs/MyNamespace.cmt lib/bs/___incremental/MyNamespace.cmt + +test: build + ./test.sh + +clean: + yarn clean + +.DEFAULT_GOAL := test + +.PHONY: build clean test diff --git a/tests/analysis_tests/tests-namespaced-references/package.json b/tests/analysis_tests/tests-namespaced-references/package.json new file mode 100644 index 00000000000..596b324a429 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/package.json @@ -0,0 +1,11 @@ +{ + "name": "@tests/namespaced-references", + "private": true, + "scripts": { + "build": "rescript build", + "clean": "rescript clean" + }, + "dependencies": { + "rescript": "workspace:^" + } +} diff --git a/tests/analysis_tests/tests-namespaced-references/rescript.json b/tests/analysis_tests/tests-namespaced-references/rescript.json new file mode 100644 index 00000000000..283f6934730 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/rescript.json @@ -0,0 +1,17 @@ +{ + "name": "@tests/namespaced-references", + "namespace": "my-namespace", + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "commonjs", + "in-source": false + } + ], + "suffix": ".res.js" +} diff --git a/tests/analysis_tests/tests-namespaced-references/src/MyModule1.res b/tests/analysis_tests/tests-namespaced-references/src/MyModule1.res new file mode 100644 index 00000000000..d51b53b56f4 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/src/MyModule1.res @@ -0,0 +1,6 @@ +// ^in+ +// ^dv+ +let myFunc1 = () => MyModule2.myFunc2() +// ^ref +// ^dv- +// ^in- diff --git a/tests/analysis_tests/tests-namespaced-references/src/MyModule2.res b/tests/analysis_tests/tests-namespaced-references/src/MyModule2.res new file mode 100644 index 00000000000..4e7d3930e1e --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/src/MyModule2.res @@ -0,0 +1,2 @@ +let myFunc2 = () => 42 +// ^ref diff --git a/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule1.res.txt b/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule1.res.txt new file mode 100644 index 00000000000..ba415fdf9d3 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule1.res.txt @@ -0,0 +1,9 @@ +References src/MyModule1.res 2:30 +[cmt] Found incremental cmt: MyModule1-MyNamespace.cmt +[cmt] Found incremental cmt: MyModule2-MyNamespace.cmt +[cmt] Found incremental cmt: MyModule1-MyNamespace.cmt +[cmt] Found incremental cmt: MyNamespace.cmt +[ +{"uri": "MyModule1.res", "range": {"start": {"line": 2, "character": 30}, "end": {"line": 2, "character": 37}}}, +{"uri": "MyModule2.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 11}}} +] diff --git a/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule2.res.txt b/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule2.res.txt new file mode 100644 index 00000000000..9a42ce08712 --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/src/expected/MyModule2.res.txt @@ -0,0 +1,5 @@ +References src/MyModule2.res 0:4 +[ +{"uri": "MyModule1.res", "range": {"start": {"line": 2, "character": 30}, "end": {"line": 2, "character": 37}}}, +{"uri": "MyModule2.res", "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 11}}} +] diff --git a/tests/analysis_tests/tests-namespaced-references/test.sh b/tests/analysis_tests/tests-namespaced-references/test.sh new file mode 100755 index 00000000000..32801fb411f --- /dev/null +++ b/tests/analysis_tests/tests-namespaced-references/test.sh @@ -0,0 +1,30 @@ +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 + + # Remove unwanted output (machine-specific path) + perl -ni -e 'print unless /^\[getRuntimeDir\]/' -- $output + + # Strip leading newlines caused by ^in+ and ^dv+ marker usage. + perl -0pi -e 's/\A\n+//' -- $output + + # Strip trailing newlines caused by ^dv- and ^in- marker usage. + perl -0pi -e 's/\n+\z/\n/' -- $output +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/ounit_analysis_references_tests.ml b/tests/ounit_tests/ounit_analysis_references_tests.ml new file mode 100644 index 00000000000..a81e69764e1 --- /dev/null +++ b/tests/ounit_tests/ounit_analysis_references_tests.ml @@ -0,0 +1,25 @@ +open OUnit + +let show_reference (module_name, path) = + Printf.sprintf "%s:%s" module_name (String.concat "." path) + +let assert_ref_key_eq ~expected ~actual = + assert_equal ~printer:show_reference expected actual + +let suites = + __FILE__ + >::: [ + ( "without namespace, external reference is unchanged" >:: fun _ -> + assert_ref_key_eq ~expected:("MyModule2", ["myFunc2"]) + ~actual: + (Analysis.References.normalizeExternalReferenceKey + ~namespace:None ~moduleName:"MyModule2" ~path:["myFunc2"]) ); + ( "with namespace, hidden module resolves to public namespace path" + >:: fun _ -> + assert_ref_key_eq + ~expected:("MyNamespace", ["MyModule2"; "myFunc2"]) + ~actual: + (Analysis.References.normalizeExternalReferenceKey + ~namespace:(Some "MyNamespace") + ~moduleName:"MyModule2-MyNamespace" ~path:["myFunc2"]) ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index ee39b4a37f3..f5a676e5fa8 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -22,6 +22,7 @@ let suites = Ounit_bsb_pkg_tests.suites; Ounit_util_tests.suites; Ounit_jsx_loc_tests.suites; + Ounit_analysis_references_tests.suites; ] let _ = OUnit.run_test_tt_main suites diff --git a/yarn.lock b/yarn.lock index 64582a26b57..2f447e924d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -773,6 +773,14 @@ __metadata: languageName: unknown linkType: soft +"@tests/namespaced-references@workspace:tests/analysis_tests/tests-namespaced-references": + version: 0.0.0-use.local + resolution: "@tests/namespaced-references@workspace:tests/analysis_tests/tests-namespaced-references" + dependencies: + rescript: "workspace:^" + languageName: unknown + linkType: soft + "@tests/reanalyze-benchmark@workspace:tests/analysis_tests/tests-reanalyze/deadcode-benchmark": version: 0.0.0-use.local resolution: "@tests/reanalyze-benchmark@workspace:tests/analysis_tests/tests-reanalyze/deadcode-benchmark" From 5e11d18a4cd948705b7d4c1af426e5f9ce97a5db Mon Sep 17 00:00:00 2001 From: Nathan Tranquilla Date: Wed, 19 Aug 2026 11:20:26 +0200 Subject: [PATCH 04/10] Fix build crash when compiler output is not valid UTF-8 (#8482) Adapted from 7f15521f2337ce338d536ab206ae6718a9d41873 for the 12.3.x Rewatch test layout. --- CHANGELOG.md | 1 + rewatch/src/build/compile.rs | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e49fcf8d2..f733c87f50b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Fix rewatch warning replay after early compile errors. https://github.com/rescript-lang/rescript/pull/8408 - Fix formatting of trailing comments before `=` in let bindings. https://github.com/rescript-lang/rescript/pull/8444 - Fix namespaced reference lookup in editor analysis. https://github.com/rescript-lang/rescript/pull/8455 +- Fix build crash when the compiler emits output that is not valid UTF-8, such as a truncated multibyte character in a code frame. https://github.com/rescript-lang/rescript/pull/8482 # 12.3.0 diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index 827bc322e59..2aa4c264a15 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -21,6 +21,12 @@ use std::process::Command; use std::sync::OnceLock; use std::time::SystemTime; +/// Decode captured compiler output without crashing if a code frame truncates a +/// multi-byte character. +fn compiler_output_to_string(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).to_string() +} + /// Execute js-post-build command for a compiled JavaScript file. /// The command runs in the directory containing the rescript.json that defines it. /// The absolute path to the JS file is passed as an argument. @@ -789,9 +795,7 @@ fn compile_file( "Could not compile file. Error: {e}. Path to AST: {ast_path:?}" )), Ok(x) => { - let err = std::str::from_utf8(&x.stderr) - .expect("stdout should be non-null") - .to_string(); + let err = compiler_output_to_string(&x.stderr); let dir = Path::new(implementation_file_path).parent().unwrap(); @@ -962,6 +966,19 @@ pub fn mark_modules_with_deleted_deps_dirty(build_state: &mut BuildState) { }); } +#[cfg(test)] +mod tests { + use super::compiler_output_to_string; + + #[test] + fn compiler_output_to_string_handles_invalid_utf8() { + // Start of an em dash (U+2014), with its third byte missing. + let truncated = [b'W', b'a', b'r', b'n', b'i', b'n', b'g', b' ', 0xe2, 0x80]; + let decoded = compiler_output_to_string(&truncated); + assert!(decoded.starts_with("Warning ")); + } +} + // this happens when a compile is not completed successfully in some way // a dependent module could be compiled with a new interface, but the dependent // modules have not finished compiling. This can cause a stale build. From d0c1f42e0bbce61b3e451d4273e1155cddacd6d6 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 27 Jul 2026 16:27:33 +0200 Subject: [PATCH 05/10] Preserve multibyte characters when wrapping long source lines (#8520) * Preserve multibyte characters when wrapping long source lines in compiler code frames * Changelog --- CHANGELOG.md | 1 + compiler/ml/code_frame.ml | 19 ++++++++++++++----- tests/ounit_tests/ounit_utf8_test.ml | 3 +++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f733c87f50b..335c0bdb1c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Fix formatting of trailing comments before `=` in let bindings. https://github.com/rescript-lang/rescript/pull/8444 - Fix namespaced reference lookup in editor analysis. https://github.com/rescript-lang/rescript/pull/8455 - Fix build crash when the compiler emits output that is not valid UTF-8, such as a truncated multibyte character in a code frame. https://github.com/rescript-lang/rescript/pull/8482 +- Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 # 12.3.0 diff --git a/compiler/ml/code_frame.ml b/compiler/ml/code_frame.ml index 9f75c765ac4..b14b7007af9 100644 --- a/compiler/ml/code_frame.ml +++ b/compiler/ml/code_frame.ml @@ -37,14 +37,23 @@ let leading_space_count str = loop 0 0 let break_long_line max_width line = + let line_length = String.length line in + let rec find_chunk_end pos remaining_width = + if pos = line_length || remaining_width = 0 then pos + else + let char_length = + String.get_utf_8_uchar line pos |> Uchar.utf_decode_length + in + find_chunk_end (pos + char_length) (remaining_width - 1) + in let rec loop pos accum = - if pos = String.length line then accum + if pos = line_length then List.rev accum else - let chunk_length = min max_width (String.length line - pos) in - let chunk = String.sub line pos chunk_length in - loop (pos + chunk_length) (chunk :: accum) + let chunk_end = find_chunk_end pos max_width in + let chunk = String.sub line pos (chunk_end - pos) in + loop chunk_end (chunk :: accum) in - loop 0 [] |> List.rev + loop 0 [] let filter_mapi f l = let rec loop f l i accum = diff --git a/tests/ounit_tests/ounit_utf8_test.ml b/tests/ounit_tests/ounit_utf8_test.ml index 42846b6d049..b94babc4ea1 100644 --- a/tests/ounit_tests/ounit_utf8_test.ml +++ b/tests/ounit_tests/ounit_utf8_test.ml @@ -4,6 +4,7 @@ let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) let ( =~ ) = OUnit.assert_equal + let suites = __FILE__ >::: [ @@ -29,4 +30,6 @@ let suites = 105; ] ); (__LOC__ >:: fun _ -> Ext_utf8.decode_utf8_string "" =~ []); + ( __LOC__ >:: fun _ -> + Code_frame.break_long_line 4 "abc—def" =~ ["abc—"; "def"] ); ] From b69ed1bd2dfa0ae2a83fc89fc22240569bac2110 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 15 Aug 2026 13:28:37 +0200 Subject: [PATCH 06/10] Fix exponentiation precedence for compound exponents (#8550) * Fix exponentiation right operand precedence Signed-off-by: Christoph Knittel * Update changelog for exponentiation precedence fix Signed-off-by: Christoph Knittel --------- Signed-off-by: Christoph Knittel --- CHANGELOG.md | 1 + compiler/core/js_op_util.ml | 2 +- tests/tests/src/exponentiation_test.mjs | 48 ++++++++++++++++++------- tests/tests/src/exponentiation_test.res | 11 ++++++ 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 335c0bdb1c4..c83d6972079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Fix namespaced reference lookup in editor analysis. https://github.com/rescript-lang/rescript/pull/8455 - Fix build crash when the compiler emits output that is not valid UTF-8, such as a truncated multibyte character in a code frame. https://github.com/rescript-lang/rescript/pull/8482 - Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 +- Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550 # 12.3.0 diff --git a/compiler/core/js_op_util.ml b/compiler/core/js_op_util.ml index 8d2d8636396..1ac99090215 100644 --- a/compiler/core/js_op_util.ml +++ b/compiler/core/js_op_util.ml @@ -41,7 +41,7 @@ let op_prec (op : Js_op.binop) = | Lsl | Lsr | Asr -> (10, 10, 11) | Bnot | Plus | Minus -> (11, 11, 12) | Mul | Div | Mod -> (12, 12, 13) - | Pow -> (13, 14, 12) + | Pow -> (13, 14, 13) let op_int_prec (op : Js_op.int_op) = match op with diff --git a/tests/tests/src/exponentiation_test.mjs b/tests/tests/src/exponentiation_test.mjs index c39ea9465db..debb6adbc00 100644 --- a/tests/tests/src/exponentiation_test.mjs +++ b/tests/tests/src/exponentiation_test.mjs @@ -7,25 +7,49 @@ let intPow = ((a, b) => Math.pow(a, b) | 0); let four = 4; +function floatPowDiv(base, numerator, denominator) { + return base ** (numerator / denominator); +} + +function floatPowMul(base, left, right) { + return base ** (left * right); +} + +function floatPowMod(base, value, modulus) { + return base ** (value % modulus); +} + +function bigintPowMul(base, left, right) { + return base ** (left * right); +} + Mocha.describe("Exponentiation_test", () => { Mocha.test("exponentiation operations", () => { - Test_utils.eq("File \"exponentiation_test.res\", line 11, characters 7-14", 2 ** 3 ** 2, Math.pow(2, Math.pow(3, 2))); - Test_utils.eq("File \"exponentiation_test.res\", line 12, characters 7-14", 2 ** (-3) ** 2, Math.pow(2, Math.pow(-3, 2))); - Test_utils.eq("File \"exponentiation_test.res\", line 13, characters 7-14", (2 ** 3) ** 2, Math.pow(Math.pow(2, 3), 2)); - Test_utils.eq("File \"exponentiation_test.res\", line 14, characters 7-14", (-2) ** 2, Math.pow(-2, 2)); - Test_utils.eq("File \"exponentiation_test.res\", line 16, characters 7-14", 512, intPow(2, intPow(3, 2))); - Test_utils.eq("File \"exponentiation_test.res\", line 17, characters 7-14", 512, intPow(2, intPow(-3, 2))); - Test_utils.eq("File \"exponentiation_test.res\", line 18, characters 7-14", 64, intPow(intPow(2, 3), 2)); - Test_utils.eq("File \"exponentiation_test.res\", line 19, characters 7-14", -2147483648, intPow(-2, 31)); - Test_utils.eq("File \"exponentiation_test.res\", line 20, characters 7-14", 0, intPow(2, 32)); - Test_utils.eq("File \"exponentiation_test.res\", line 21, characters 7-14", 0, intPow(2147483647, 2)); - Test_utils.eq("File \"exponentiation_test.res\", line 22, characters 7-14", 0, intPow(-2147483648, 2)); - Test_utils.eq("File \"exponentiation_test.res\", line 24, characters 7-14", 256, four ** four | 0); + Test_utils.eq("File \"exponentiation_test.res\", line 17, characters 7-14", 2 ** 3 ** 2, Math.pow(2, Math.pow(3, 2))); + Test_utils.eq("File \"exponentiation_test.res\", line 18, characters 7-14", 2 ** (-3) ** 2, Math.pow(2, Math.pow(-3, 2))); + Test_utils.eq("File \"exponentiation_test.res\", line 19, characters 7-14", (2 ** 3) ** 2, Math.pow(Math.pow(2, 3), 2)); + Test_utils.eq("File \"exponentiation_test.res\", line 20, characters 7-14", (-2) ** 2, Math.pow(-2, 2)); + Test_utils.eq("File \"exponentiation_test.res\", line 22, characters 7-14", 512, intPow(2, intPow(3, 2))); + Test_utils.eq("File \"exponentiation_test.res\", line 23, characters 7-14", 512, intPow(2, intPow(-3, 2))); + Test_utils.eq("File \"exponentiation_test.res\", line 24, characters 7-14", 64, intPow(intPow(2, 3), 2)); + Test_utils.eq("File \"exponentiation_test.res\", line 25, characters 7-14", -2147483648, intPow(-2, 31)); + Test_utils.eq("File \"exponentiation_test.res\", line 26, characters 7-14", 0, intPow(2, 32)); + Test_utils.eq("File \"exponentiation_test.res\", line 27, characters 7-14", 0, intPow(2147483647, 2)); + Test_utils.eq("File \"exponentiation_test.res\", line 28, characters 7-14", 0, intPow(-2147483648, 2)); + Test_utils.eq("File \"exponentiation_test.res\", line 30, characters 7-14", 256, four ** four | 0); + Test_utils.eq("File \"exponentiation_test.res\", line 32, characters 7-14", 2 ** (0 / 10000), 1); + Test_utils.eq("File \"exponentiation_test.res\", line 33, characters 7-14", 2 ** (3 * 4), 4096); + Test_utils.eq("File \"exponentiation_test.res\", line 34, characters 7-14", 2 ** (5 % 3), 4); + Test_utils.eq("File \"exponentiation_test.res\", line 35, characters 7-14", 2n ** (3n * 2n), 64n); }); }); export { intPow, four, + floatPowDiv, + floatPowMul, + floatPowMod, + bigintPowMul, } /* Not a pure module */ diff --git a/tests/tests/src/exponentiation_test.res b/tests/tests/src/exponentiation_test.res index 19fb9cb7b44..216de749954 100644 --- a/tests/tests/src/exponentiation_test.res +++ b/tests/tests/src/exponentiation_test.res @@ -6,6 +6,12 @@ external jsPow: (float, float) => float = "Math.pow" let intPow: (int, int) => int = %raw(`(a, b) => Math.pow(a, b) | 0`) let four: int = %raw(`4`) +let floatPowDiv = (base: float, numerator: float, denominator: float) => + base ** (numerator /. denominator) +let floatPowMul = (base: float, left: float, right: float) => base ** (left *. right) +let floatPowMod = (base: float, value: float, modulus: float) => base ** (value % modulus) +let bigintPowMul = (base: bigint, left: bigint, right: bigint) => base ** (left * right) + describe(__MODULE__, () => { test("exponentiation operations", () => { eq(__LOC__, 2. ** 3. ** 2., jsPow(2., jsPow(3., 2.))) @@ -22,5 +28,10 @@ describe(__MODULE__, () => { eq(__LOC__, -2147483648 ** 2, intPow(-2147483648, 2)) eq(__LOC__, 4 ** 4, four ** four) + + eq(__LOC__, floatPowDiv(2., 0., 10000.), 1.) + eq(__LOC__, floatPowMul(2., 3., 4.), 4096.) + eq(__LOC__, floatPowMod(2., 5., 3.), 4.) + eq(__LOC__, bigintPowMul(2n, 3n, 2n), 64n) }) }) From 05f816982dcc3e070477f1b0da30cfc56fad41f4 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 19 Aug 2026 12:47:08 +0200 Subject: [PATCH 07/10] Enforce function arity in inclusion, type equality, and coercion (#8559) Backport only the three type-system guards and focused regressions from 8fafaf916de17925afa3a20e42673f0b5bbbeaf7; omit the larger diagnostic-reporting refactor. --- CHANGELOG.md | 1 + compiler/ml/ctype.ml | 12 ++++---- .../coercion_arity_mismatch.res.expected | 9 ++++++ ...dule_sig_value_arity_mismatch.res.expected | 28 +++++++++++++++++++ ..._decl_function_arity_mismatch.res.expected | 28 +++++++++++++++++++ .../fixtures/coercion_arity_mismatch.res | 2 ++ .../module_sig_value_arity_mismatch.res | 5 ++++ .../type_decl_function_arity_mismatch.res | 5 ++++ 8 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res create mode 100644 tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res create mode 100644 tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res diff --git a/CHANGELOG.md b/CHANGELOG.md index c83d6972079..687ba0584de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Fix build crash when the compiler emits output that is not valid UTF-8, such as a truncated multibyte character in a code frame. https://github.com/rescript-lang/rescript/pull/8482 - Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 - Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550 +- Enforce function arity in interface/module inclusion, type equality, and coercion. https://github.com/rescript-lang/rescript/pull/8559 # 12.3.0 diff --git a/compiler/ml/ctype.ml b/compiler/ml/ctype.ml index e15adf31b43..23c95d03394 100644 --- a/compiler/ml/ctype.ml +++ b/compiler/ml/ctype.ml @@ -2914,8 +2914,8 @@ let rec moregen inst_nongen type_pairs env t1 t2 = | Tvar _, _ when may_instantiate inst_nongen t1' -> moregen_occur env t1'.level t2; link_type t1' t2 - | Tarrow (arg1, ret1, _, _), Tarrow (arg2, ret2, _, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, _, a1), Tarrow (arg2, ret2, _, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> moregen inst_nongen type_pairs env arg1.typ arg2.typ; moregen inst_nongen type_pairs env ret1 ret2 | Ttuple tl1, Ttuple tl2 -> @@ -3184,8 +3184,8 @@ let rec eqtype rename type_pairs subst env t1 t2 = if List.exists (fun (_, t) -> t == t2') !subst then raise (Unify []); subst := (t1', t2') :: !subst) - | Tarrow (arg1, ret1, _, _), Tarrow (arg2, ret2, _, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, _, a1), Tarrow (arg2, ret2, _, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> eqtype rename type_pairs subst env arg1.typ arg2.typ; eqtype rename type_pairs subst env ret1 ret2 | Ttuple tl1, Ttuple tl2 -> @@ -3597,8 +3597,8 @@ let rec subtype_rec env trace t1 t2 cstrs = TypePairs.add subtypes (t1, t2) (); match (t1.desc, t2.desc) with | Tvar _, _ | _, Tvar _ -> (trace, t1, t2, !univar_pairs, None) :: cstrs - | Tarrow (arg1, ret1, _, _), Tarrow (arg2, ret2, _, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, _, a1), Tarrow (arg2, ret2, _, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> let cstrs = subtype_rec env ((arg2.typ, arg1.typ) :: trace) diff --git a/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected new file mode 100644 index 00000000000..6fd77c58aa4 --- /dev/null +++ b/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected @@ -0,0 +1,9 @@ + + We've found a bug for you! + /.../fixtures/coercion_arity_mismatch.res:2:10-31 + + 1 │ let f = (x: int) => (y: int) => x + y + 2 │ let g = (f :> (int, int) => int) + 3 │ + + Type int => int => int is not a subtype of (int, int) => int \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected new file mode 100644 index 00000000000..f491e37f771 --- /dev/null +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected @@ -0,0 +1,28 @@ + + We've found a bug for you! + /.../fixtures/module_sig_value_arity_mismatch.res:3:5-5:1 + + 1 │ module M: { + 2 │ let f: (int, int) => int + 3 │ } = { + 4 │  let f = (x: int) => (y: int) => x + y + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + let f: int => int => int +} + is not included in + { + let f: (int, int) => int +} + Values do not match: + let f: int => int => int + is not included in + let f: (int, int) => int + /.../fixtures/module_sig_value_arity_mismatch.res:2:3-26: + Expected declaration + /.../fixtures/module_sig_value_arity_mismatch.res:4:7: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected new file mode 100644 index 00000000000..14e91bcf247 --- /dev/null +++ b/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected @@ -0,0 +1,28 @@ + + We've found a bug for you! + /.../fixtures/type_decl_function_arity_mismatch.res:3:5-5:1 + + 1 │ module M: { + 2 │ type t = (int, int) => int + 3 │ } = { + 4 │  type t = int => int => int + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + type t = int => int => int +} + is not included in + { + type t = (int, int) => int +} + Type declarations do not match: + type t = int => int => int + is not included in + type t = (int, int) => int + /.../fixtures/type_decl_function_arity_mismatch.res:2:3-28: + Expected declaration + /.../fixtures/type_decl_function_arity_mismatch.res:4:3-28: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res new file mode 100644 index 00000000000..93d9111360e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res @@ -0,0 +1,2 @@ +let f = (x: int) => (y: int) => x + y +let g = (f :> (int, int) => int) diff --git a/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res new file mode 100644 index 00000000000..a335585ac0a --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res @@ -0,0 +1,5 @@ +module M: { + let f: (int, int) => int +} = { + let f = (x: int) => (y: int) => x + y +} diff --git a/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res new file mode 100644 index 00000000000..4a831f446dd --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res @@ -0,0 +1,5 @@ +module M: { + type t = (int, int) => int +} = { + type t = int => int => int +} From 1f2caf0fe8304d0212ede02de98a8bc446e611fe Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 19 Aug 2026 12:48:18 +0200 Subject: [PATCH 08/10] Record arity for bare labeled arrow types (#8563) Backport only the independent bare labeled-arrow fix from 110534a62abcd3cc24b70403351bfaaa7f96ce88; omit the broader external phantom-argument parser/printer rewrite. --- CHANGELOG.md | 1 + compiler/syntax/src/res_core.ml | 2 +- .../errors/typexpr/expected/arrow.res.txt | 2 +- .../grammar/typexpr/expected/es6Arrow.res.txt | 16 ++++++++-------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 687ba0584de..1a47d36b4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 - Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550 - Enforce function arity in interface/module inclusion, type equality, and coercion. https://github.com/rescript-lang/rescript/pull/8559 +- Fix bare labeled arrow types (`~x: int => string`) getting no arity and failing to unify with their parenthesized form. https://github.com/rescript-lang/rescript/pull/8563 # 12.3.0 diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 0d7ef55e960..419a2180ea7 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -4726,7 +4726,7 @@ and parse_es6_arrow_type ~attrs p = Parser.expect EqualGreater p; let return_type = parse_typ_expr ~alias:false p in let loc = mk_loc start_pos p.prev_end_pos in - Ast_helper.Typ.arrow ~loc ~arity:None {attrs; lbl; typ} return_type + Ast_helper.Typ.arrow ~loc ~arity:(Some 1) {attrs; lbl; typ} return_type | DocComment _ -> assert false | _ -> let parameters = parse_type_parameters p in diff --git a/tests/syntax_tests/data/parsing/errors/typexpr/expected/arrow.res.txt b/tests/syntax_tests/data/parsing/errors/typexpr/expected/arrow.res.txt index c621d68f7f0..31e172542b1 100644 --- a/tests/syntax_tests/data/parsing/errors/typexpr/expected/arrow.res.txt +++ b/tests/syntax_tests/data/parsing/errors/typexpr/expected/arrow.res.txt @@ -46,5 +46,5 @@ module Error3 = type nonrec observation = { observed: int ; - onStep: currentValue:unit -> [%rescript.typehole ] } + onStep: currentValue:unit -> [%rescript.typehole ] (a:1) } end \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/typexpr/expected/es6Arrow.res.txt b/tests/syntax_tests/data/parsing/grammar/typexpr/expected/es6Arrow.res.txt index 47f23df4998..f302e57ce11 100644 --- a/tests/syntax_tests/data/parsing/grammar/typexpr/expected/es6Arrow.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/typexpr/expected/es6Arrow.res.txt @@ -12,15 +12,15 @@ let (t : a:int -> b:int -> int (a:2)) = xf let (t : ?a:int -> ?b:int -> int (a:2)) = xf let (t : int -> int -> int -> int (a:1) (a:1) (a:1)) = xf let (t : a:int -> b:int -> c:int -> int (a:1) (a:1) (a:1)) = xf -type nonrec t = f:int -> string -type nonrec t = ?f:int -> string -let (f : f:int -> string) = fx -let (f : ?f:int -> string) = fx type nonrec t = f:int -> string (a:1) -type nonrec t = f:int -> string +type nonrec t = ?f:int -> string (a:1) +let (f : f:int -> string (a:1)) = fx +let (f : ?f:int -> string (a:1)) = fx +type nonrec t = f:int -> string (a:1) +type nonrec t = f:int -> string (a:1) +type nonrec t = f:(int -> string (a:1)) -> float (a:1) type nonrec t = f:(int -> string (a:1)) -> float (a:1) -type nonrec t = f:(int -> string (a:1)) -> float -type nonrec t = f:int -> string -> float (a:1) +type nonrec t = f:int -> string -> float (a:1) (a:1) type nonrec t = a:int[@attrBeforeLblA ] -> b:int[@attrBeforeLblB ] -> ((float)[@attr ]) -> unit (a:3) @@ -28,7 +28,7 @@ type nonrec t = ((a:int -> ((b:int -> ((float)[@attr ]) -> unit (a:1) (a:1))[@attrBeforeLblB ]) (a:1)) [@attrBeforeLblA ]) -type nonrec t = a:int[@attr ] -> unit +type nonrec t = a:int[@attr ] -> unit (a:1) type nonrec 'a getInitialPropsFn = < query: string dict ;req: 'a Js.t Js.Nullable.t > -> 'a Js.t Js.Promise.t (a:1) \ No newline at end of file From aba63a00731e86786eab5f2b256f193d22e3f3a3 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 19 Aug 2026 12:49:00 +0200 Subject: [PATCH 09/10] Set version to 12.3.1 --- compiler/common/bs_version.ml | 2 +- package.json | 2 +- packages/@rescript/darwin-arm64/package.json | 2 +- packages/@rescript/darwin-x64/package.json | 2 +- packages/@rescript/linux-arm64/package.json | 2 +- packages/@rescript/linux-x64/package.json | 2 +- packages/@rescript/runtime/package.json | 2 +- packages/@rescript/win32-x64/package.json | 2 +- rewatch/Cargo.lock | 2 +- rewatch/Cargo.toml | 2 +- tests/dependencies/rescript-react/package.json | 2 +- yarn.config.cjs | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/common/bs_version.ml b/compiler/common/bs_version.ml index 9a7ea1c30fd..ef3033dbef5 100644 --- a/compiler/common/bs_version.ml +++ b/compiler/common/bs_version.ml @@ -21,5 +21,5 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -let version = "12.3.0" +let version = "12.3.1" let header = "// Generated by ReScript, PLEASE EDIT WITH CARE" diff --git a/package.json b/package.json index 84d4ac69d9f..1148b49edb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rescript", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript toolchain", "type": "module", "keywords": [ diff --git a/packages/@rescript/darwin-arm64/package.json b/packages/@rescript/darwin-arm64/package.json index f76e3b499c0..b649eb29348 100644 --- a/packages/@rescript/darwin-arm64/package.json +++ b/packages/@rescript/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/darwin-arm64", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript binaries for MacOS ARM64", "type": "module", "license": "(LGPL-3.0-or-later AND MIT)", diff --git a/packages/@rescript/darwin-x64/package.json b/packages/@rescript/darwin-x64/package.json index 34848583cb3..7a1187151fd 100644 --- a/packages/@rescript/darwin-x64/package.json +++ b/packages/@rescript/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/darwin-x64", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript binaries for MacOS x86_64", "type": "module", "license": "(LGPL-3.0-or-later AND MIT)", diff --git a/packages/@rescript/linux-arm64/package.json b/packages/@rescript/linux-arm64/package.json index 3486e6144f1..e2bae4c28f0 100644 --- a/packages/@rescript/linux-arm64/package.json +++ b/packages/@rescript/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/linux-arm64", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript binaries for Linux ARM64", "type": "module", "license": "(LGPL-3.0-or-later AND MIT)", diff --git a/packages/@rescript/linux-x64/package.json b/packages/@rescript/linux-x64/package.json index efcbcdb344a..8b336059459 100644 --- a/packages/@rescript/linux-x64/package.json +++ b/packages/@rescript/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/linux-x64", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript binaries for Linux x86_64", "type": "module", "license": "(LGPL-3.0-or-later AND MIT)", diff --git a/packages/@rescript/runtime/package.json b/packages/@rescript/runtime/package.json index 6020ea0d00d..d2a74bfcf22 100644 --- a/packages/@rescript/runtime/package.json +++ b/packages/@rescript/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/runtime", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript runtime modules", "type": "module", "license": "MIT", diff --git a/packages/@rescript/win32-x64/package.json b/packages/@rescript/win32-x64/package.json index 8a9857c672e..f7fa1c09414 100644 --- a/packages/@rescript/win32-x64/package.json +++ b/packages/@rescript/win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@rescript/win32-x64", - "version": "12.3.0", + "version": "12.3.1", "description": "ReScript binaries for Windows x86_64", "type": "module", "license": "(LGPL-3.0-or-later AND MIT)", diff --git a/rewatch/Cargo.lock b/rewatch/Cargo.lock index 1397372d1c5..a81293a385d 100644 --- a/rewatch/Cargo.lock +++ b/rewatch/Cargo.lock @@ -768,7 +768,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rescript" -version = "12.3.0" +version = "12.3.1" dependencies = [ "ahash", "anyhow", diff --git a/rewatch/Cargo.toml b/rewatch/Cargo.toml index 35576d57196..c05014cb728 100644 --- a/rewatch/Cargo.toml +++ b/rewatch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rescript" -version = "12.3.0" +version = "12.3.1" edition = "2024" rust-version = "1.91" diff --git a/tests/dependencies/rescript-react/package.json b/tests/dependencies/rescript-react/package.json index 6271f397f34..8a863b8ad51 100644 --- a/tests/dependencies/rescript-react/package.json +++ b/tests/dependencies/rescript-react/package.json @@ -1,7 +1,7 @@ { "name": "@rescript/react", "private": true, - "version": "12.3.0", + "version": "12.3.1", "homepage": "https://rescript-lang.org", "bugs": "https://github.com/rescript-lang/rescript/issues", "repository": { diff --git a/yarn.config.cjs b/yarn.config.cjs index d0a45b60606..631382a105f 100644 --- a/yarn.config.cjs +++ b/yarn.config.cjs @@ -14,7 +14,7 @@ const execPromise = util.promisify(exec); * @param {Yarn.Constraints.Context} ctx */ async function enforceCompilerMeta({ Yarn }) { - const EXPECTED_VERSION = "12.3.0"; + const EXPECTED_VERSION = "12.3.1"; for (const workspace of Yarn.workspaces()) { const { ident } = workspace.pkg; From a1ad1df6e3f5d070410661bb647b56e32109b57c Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 19 Aug 2026 13:05:37 +0200 Subject: [PATCH 10/10] Move Rewatch test module after production items --- rewatch/src/build/compile.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index 2aa4c264a15..6052d0c0dbf 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -966,19 +966,6 @@ pub fn mark_modules_with_deleted_deps_dirty(build_state: &mut BuildState) { }); } -#[cfg(test)] -mod tests { - use super::compiler_output_to_string; - - #[test] - fn compiler_output_to_string_handles_invalid_utf8() { - // Start of an em dash (U+2014), with its third byte missing. - let truncated = [b'W', b'a', b'r', b'n', b'i', b'n', b'g', b' ', 0xe2, 0x80]; - let decoded = compiler_output_to_string(&truncated); - assert!(decoded.starts_with("Warning ")); - } -} - // this happens when a compile is not completed successfully in some way // a dependent module could be compiled with a new interface, but the dependent // modules have not finished compiling. This can cause a stale build. @@ -1067,3 +1054,16 @@ pub fn mark_modules_with_expired_deps_dirty(build_state: &mut BuildCommandState) } }); } + +#[cfg(test)] +mod tests { + use super::compiler_output_to_string; + + #[test] + fn compiler_output_to_string_handles_invalid_utf8() { + // Start of an em dash (U+2014), with its third byte missing. + let truncated = [b'W', b'a', b'r', b'n', b'i', b'n', b'g', b' ', 0xe2, 0x80]; + let decoded = compiler_output_to_string(&truncated); + assert!(decoded.starts_with("Warning ")); + } +}