diff --git a/AGENTS.md b/AGENTS.md index 937ed9585d..71c9851c97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# CLAUDE.md +# AGENTS.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Shared guidance for code agents (Claude, Gemini, Codex, etc.) working in this repository. The files `CLAUDE.md` and `GEMINI.md` are symlinks to this document. For Codex‑specific workflow details also see `CODEX.md`. ## Project Overview @@ -136,4 +136,24 @@ The compiler is designed for fast feedback loops and scales to large codebases. - Avoid introducing meaningless symbols - Maintain readable JavaScript output - Consider compilation speed impact -- Use appropriate optimization passes in the Lambda and JS IRs \ No newline at end of file +- Use appropriate optimization passes in the Lambda and JS IRs + +## Agent Notes and Gotchas + +- **Node version features:** Runtime docstring tests conditionally enable features by Node major version: + - 20+: array `toReversed`/`toSorted` + - 22+: new `Set` APIs and `Promise.withResolvers` + - 24+: `RegExp.escape` + Tests auto‑skip unsupported features based on `process.version`. +- **CPU count in sandboxes:** Some CI/sandboxed environments report `os.cpus().length === 0`. + - Clamp concurrency/batch size to at least 1 when using `os.cpus()`. + - This pattern is used in `tests/docstring_tests/DocTest.res` and `cli/rescript-legacy/format.js`. +- **Formatting in tests:** `make test` checks formatting (OCaml, ReScript, JS, Rust). If it fails locally, run `make format` and re‑run tests. +- **Executables location:** Build copies platform binaries into `packages/@rescript//bin/` and convenience folders like `darwinarm64/`. +- **Direct dune usage:** You can use `dune build`/`dune build -w`, but prefer `make` targets which also copy executables. + +## References + +- `CODEX.md`: detailed setup, build, testing, and workflows for agented development +- `README.md`: high‑level repo overview and usage +- `Makefile`: authoritative list of build/test targets diff --git a/CHANGELOG.md b/CHANGELOG.md index 66de9f1385..52fdaed3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Fix generation of interfaces for module types containing multiple type constraints. https://github.com/rescript-lang/rescript/pull/7825 - JSX preserve mode: fix "make is not a valid component name". https://github.com/rescript-lang/rescript/pull/7831 - Rewatch: include parser arguments of experimental features. https://github.com/rescript-lang/rescript/pull/7836 +- Suppress spurious “constructor … is never used” warnings when constructors are introduced via variant coercions; still warn for constructors only present on the target side. https://github.com/rescript-lang/rescript/pull/7839 #### :memo: Documentation diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 90522f13d2..87d28a77b4 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -3018,6 +3018,36 @@ and type_expect_ ~context ?in_function ?(recarg = Rejected) env sexp ty_expected raise (Error (loc, env, Not_subtype (tr1, tr2, ctx)))); (arg, ty', cty') in + (* After a successful coercion, if we coerced between concrete variant + types, mark the intersection of constructors as positively used for the + target type to avoid spurious "constructor ... is never used" warnings + when values are introduced via coercions. *) + (try + let _p_src, _p_src_conc, src_decl = + Ctype.extract_concrete_typedecl env arg.exp_type + in + let _, p_tgt_conc, tgt_decl = Ctype.extract_concrete_typedecl env ty' in + match (src_decl.type_kind, tgt_decl.type_kind) with + | Type_variant src_cons, Type_variant tgt_cons -> + let module StringSet = Set.Make (String) in + let src_set = + List.fold_left + (fun acc (c : Types.constructor_declaration) -> + StringSet.add (Ident.name c.cd_id) acc) + StringSet.empty src_cons + in + let has_src name = StringSet.mem name src_set in + let tgt_ty_name_conc = Path.last p_tgt_conc in + (* Mark usage for the concrete target decl (implementation view). *) + List.iter + (fun (c : Types.constructor_declaration) -> + let cname = Ident.name c.cd_id in + if has_src cname then + Env.mark_constructor_used Env.Positive env tgt_ty_name_conc + tgt_decl cname) + tgt_cons + | _ -> () + with Not_found -> ()); rue { exp_desc = arg.exp_desc; diff --git a/tests/build_tests/super_errors/expected/VariantCoercionWarnOnC.res.expected b/tests/build_tests/super_errors/expected/VariantCoercionWarnOnC.res.expected new file mode 100644 index 0000000000..433d638209 --- /dev/null +++ b/tests/build_tests/super_errors/expected/VariantCoercionWarnOnC.res.expected @@ -0,0 +1,13 @@ + + Warning number 37 + /.../fixtures/VariantCoercionWarnOnC.res:9:3-11:7 + + 7 │ let log: a => unit + 8 │ } = { + 9 │ type b = + 10 │  | ...a + 11 │  | C + 12 │ + 13 │ let log = (x: a) => Js.log((x :> b)) + + unused constructor C. diff --git a/tests/build_tests/super_errors/fixtures/VariantCoercionWarnOnC.res b/tests/build_tests/super_errors/fixtures/VariantCoercionWarnOnC.res new file mode 100644 index 0000000000..78eafc9a98 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/VariantCoercionWarnOnC.res @@ -0,0 +1,16 @@ +// Ensure unused-constructor warning still fires for new members introduced +// only on the target side of a coercion. + +type a = A | B + +module M: { + let log: a => unit +} = { + type b = + | ...a + | C + + let log = (x: a) => Js.log((x :> b)) +} + +let _ = M.log(A) diff --git a/tests/docstring_tests/DocTest.res b/tests/docstring_tests/DocTest.res index dff139cf0f..df893a0be2 100644 --- a/tests/docstring_tests/DocTest.res +++ b/tests/docstring_tests/DocTest.res @@ -62,7 +62,11 @@ let extractDocFromFile = async file => { } } -let batchSize = OS.cpus()->Array.length +// Some environments may report 0 CPUs; clamp to at least 1 to avoid zero-sized batches +let batchSize = switch OS.cpus()->Array.length { +| n if n > 0 => n +| _ => 1 +} let runtimePath = Path.join(["packages", "@rescript", "runtime"]) diff --git a/tests/docstring_tests/DocTest.res.js b/tests/docstring_tests/DocTest.res.js index 12b54bf200..0e42d8c0b9 100644 --- a/tests/docstring_tests/DocTest.res.js +++ b/tests/docstring_tests/DocTest.res.js @@ -67,7 +67,9 @@ async function extractDocFromFile(file) { } } -let batchSize = Nodeos.cpus().length; +let n = Nodeos.cpus().length; + +let batchSize = n > 0 ? n : 1; let runtimePath = Nodepath.join("packages", "@rescript", "runtime"); diff --git a/tests/tests/src/VariantCoercionConstructUsed.mjs b/tests/tests/src/VariantCoercionConstructUsed.mjs new file mode 100644 index 0000000000..b2a95aab6e --- /dev/null +++ b/tests/tests/src/VariantCoercionConstructUsed.mjs @@ -0,0 +1,17 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function log(x) { + console.log(x); +} + +let DoNotWarn = { + log: log +}; + +console.log("A"); + +export { + DoNotWarn, +} +/* Not a pure module */ diff --git a/tests/tests/src/VariantCoercionConstructUsed.res b/tests/tests/src/VariantCoercionConstructUsed.res new file mode 100644 index 0000000000..7aeada489a --- /dev/null +++ b/tests/tests/src/VariantCoercionConstructUsed.res @@ -0,0 +1,15 @@ +// Repro for incorrect "constructor ... is never used" warning with coercions +// This should compile cleanly without warnings when coercing from a -> b. + +type a = A | B +module DoNotWarn: { + let log: a => unit +} = { + type b = + | ...a + | C + + let log = (x: a) => Js.log((x :> b)) +} + +let _ = DoNotWarn.log(A)