Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
- 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/<platform>/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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 46 additions & 0 deletions compiler/ml/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3018,6 +3018,52 @@ 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, 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;
(* If the target type path differs from its concrete decl (e.g.,
when a signature exposes a private or abstract alias), also mark
usage on the exposed target declaration so scheduled warnings
attached to that declaration are cleared. *)
if not (Path.same p_tgt p_tgt_conc) then
let exposed_ty_name = Path.last p_tgt in
let exposed_decl = Env.find_type p_tgt env in
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 exposed_ty_name
exposed_decl cname)
tgt_cons
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cristianoc what does this mean in practice? I don't get it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex what's the answer here?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code around line 3064 handles cases where the type path used in the code (p_tgt) differs from the path of its concrete declaration (p_tgt_conc). This occurs when a module signature exposes a variant type via a private or abstract alias. When such a coercion happens, the code marks the overlapping constructors as “used” on both the concrete declaration and the exposed alias so that “constructor … is never used” warnings are cleared for both locations compiler/ml/typecore.mlL3051-L3064

View task →

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex explain more in detail the terms used in your response and the mechanism to mark a constructor never used.
Don't assume familiarity with the representation of types and type environments, but introduce enough context to understand this change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Expanded the comments around coercion handling to define p_tgt (path used in source code) versus p_tgt_conc (resolved declaration path) and to explain how Env.mark_constructor_used records constructor usage on both the concrete type and any exposed alias so “constructor … is never used” warnings are cleared compiler/ml/typecore.mlL3051-L3064

Testing

  • ⚠️ make format (fails: dune: command not found)
  • ⚠️ make test (fails: KeyboardInterrupt during ninja build)

Notes

  • apt-get update failed with 403 errors, so required dependencies (e.g., dune) could not be installed

View task →

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove this special case code, does the fix still work? If so I think we should remove it, and then add it again in the future if we discover real issues it would fix.

| _ -> ()
with Not_found -> ());
rue
{
exp_desc = arg.exp_desc;
Expand Down
10 changes: 10 additions & 0 deletions example.res
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed, irrelevant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex remove this

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Removed the redundant example.res file as it was no longer relevant to the pull request

Testing

  • ⚠️ make format (dune: command not found; internal Biome error)
  • ⚠️ make test (dune: command not found)

View task →

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type a = A | B

// b spreads a and adds one more constructor
type b =
| ...a
| C

let upcast = (x: a): b => (x :> b)

let _ = upcast(A)
46 changes: 46 additions & 0 deletions issue.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remember to remove this before merging.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex remove

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Removed the temporary issue.txt debugging log from the repository

Testing

  • ⚠️ make format (fails: dune: command not found)
  • ⚠️ make test (fails: dune: command not found)

View task →

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex perhaps AGENTS.md needs to be changed with instruction on how to set up opam and dune?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Added an “Installing opam and dune” section to AGENTS.md with step-by-step commands for setting up the OCaml toolchain so dune is available for build and test targets AGENTS.mdL111-L131
  • Removed the temporary issue.txt debugging log from the repository

Testing

  • ⚠️ apt-get update (repository InRelease files not signed, 403 errors)
  • ⚠️ make format (dune: command not found; cargo: command not found)
  • ⚠️ make test (cargo: command not found)

Notes

  • Package repository errors prevented installing opam, leaving dune and cargo unavailable for formatting and tests

View task →

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
found a compiler bug: https://github.com/rescript-lang/rescript/issues/7838 (due to the coercing between variants)
GitHub
Incorrect compiler warning (constructor is never used to build valu...
https://rescript-lang.org/try?version=v11.1.4&amp;module=esmodule&amp;code=LYewJgrgNgpgBAUQG4wHYBc4F44G8BQcc6AngA7yY4A+iKGAgvoXLJgBYCGqYs2cAChj10ALmIBKbAD48LIgGcA7gEt0AY3ZxhaTASJFayXQxmCJ8uAF8WNm-l...
Incorrect compiler warning (constructor is never used to build valu...
[09:34]Gabriel Nordeborn: What is wrong here...?
[09:35]jaap: The warning?
[09:36]jaap: The variants are constructed in handle
[09:37]jaap: I mean, the coercion constructs the variants
[09:38]jaap: so if we have variantA :> variantB, we can mark that variantB is always constructed
[09:38]Gabriel Nordeborn: Right, so if you adapt the example to not use coercion, it marks them as constructed?
[09:39]Gabriel Nordeborn: It's good if you write this in the PR issue
[09:39]Gabriel Nordeborn: Right now, just looking at the example, I did not understand what you're referring to
[09:41]jaap: Yes
[09:41]jaap: https://rescript-lang.org/try?version=v11.1.4&module=esmodule&code=LYewJgrgNgpgBAUQG4wHYBc4F44G8BQcc6AngA7yY4A+iKGAgvoXLJgBYCGqYs2cAChj10ALmIBKbAD48LIgGcA7gEt0AY3ZxhaTASJFayXQxmCJ8uAF8WNm-lCQ+AZRgAnFG+MYA4iHDi+qwwmGgAjhAwkeLe6AB0VLIQqGr4VvxBpBTE2Ja0cQWxCcxEbHBcPHw4nAokqOqCOhji6FJYskGKqhpaTXp5dCZmAhYG1rYsZeGRkfxCIjEiCW2yFbwwAspqmtoicoaDjGaxTERWEgC00ioA5qggbjBpzI7Q8K4e7rEAQpxggZMQtpUBEojBFroEmZkql0jhMuRKLkxvlCkt0CVghxuOt+DU6g15roWit9gYtj1droyQcTsNRgYbGdAaEQTN4Dgic1DvFWmY1rAuZhRLJWldbvdHs8gA
ReScript Documentation
ReScript Playground
Try ReScript in the browser
ReScript Playground
[09:41]jaap: here with two examples
[09:41]jaap: also updated the ticket
[09:44]cristianoc: The type system was created with no coercion in mind.
[09:45]cristianoc: So fix warning —-> compiler error?
[09:47]Gabriel Nordeborn: @jaap is this not a better example? https://rescript-lang.org/try?version=v11.1.4&module=esmodule&code=LYewJgrgNgpgBAUQG4wHYBc4F44G8BQcc6AngA7yY7JroCC+hcsmAFgIapizZwAUMFBgBcxAJTYAfHiZEAzgHcAlugDGrOINoyiRAD6Ih9KfzGy4AXyZWr+UJB4BlGACcULmhgDiIcKIJEpBTEvAYAdBGe6GHojEQsmqgAjhAwqaJU0hCoKvgWvAHE5JShcBFhUTFxzDBsnNzwOOxyJKiq-Foi4iaF8spqGp2YvXAGUXQmfGa6ltZMCWgpaY0dRhkSWNIcXLACRmIAtJJKAOaoIC4weYz20PDObq5RAELsYP7ztYlL6Ya0MSZsrl8jhCkESjhwpEjFVPnUditmq12ntaOseuZFCp1JojDpdGMjBNNqZzFYiOSaphFqlUrxUV1KugNlt6rsGZhhNJmYdjmcLlcrEA
ReScript Documentation
ReScript Playground
Try ReScript in the browser
ReScript Playground
[09:47]Gabriel Nordeborn: Using the switch fixes the warning of course because you're now constructing it explicitly
[09:47]Gabriel Nordeborn: If you were to add the switch instead of coercion that'd work as well
[09:48]Gabriel Nordeborn: But from what I understand the problem is that there's a value of a type that gets coerced to another type, and that other type is not marked as used
[09:49]Gabriel Nordeborn: The value that's not coerced is marked, but the new type from the coercion is not marked
[10:35]jaap: @cristianoc there is no way to fix this warning - you need to convert it to a switch expression, but that is a lot of redudant code potentially
[10:36]cristianoc: That’s actually better. So the message is at least not inconsistent.
[10:36]jaap: but the tricky part is that when coercing, the members that are in variantA need to marked as used, but not the new ones
[10:36]cristianoc: I was worried: you remove the case, then get a compile error
[10:37]jaap: Yes you do get an error - so it's not swallowing that
[10:37]jaap: if you include it (warning) if you remove it (error)
[10:38]cristianoc: There’s no such thing in the compiler. As ocaml does not have that. So it will be a genuine extension to handle this.
And I don’t even know if possible.
Is this global property? If so the. Compiler can’t do it.
[10:38]jaap: but yeah seems to be a gap in the type system after adding the nice spreading of variants and coercing
[10:39]jaap: No it's local - in the example you see the module signature added, so it only happens if you make the type private, and then coerce it
[10:40]jaap: I guess there is some mechanism to mark members as constructed - and coercion is not doing that
[10:40]cristianoc: Ok this is enough discussion.
Paste it in a prompt and see if it gets close.
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions tests/build_tests/super_errors/fixtures/VariantCoercionWarnOnC.res
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 5 additions & 1 deletion tests/docstring_tests/DocTest.res
Original file line number Diff line number Diff line change
Expand Up @@ -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"])

Expand Down
4 changes: 3 additions & 1 deletion tests/docstring_tests/DocTest.res.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions tests/tests/src/VariantCoercionConstructUsed.mjs
Original file line number Diff line number Diff line change
@@ -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 */
15 changes: 15 additions & 0 deletions tests/tests/src/VariantCoercionConstructUsed.res
Original file line number Diff line number Diff line change
@@ -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)