Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3245b09
Classify erasing builtins in the primitive table
cristianoc Aug 31, 2026
29ba6e8
Give Lambda the JavaScript null and undefined constants
cristianoc Aug 31, 2026
c50631e
Produce the polymorphic variant runtime name at translation
cristianoc Aug 31, 2026
60d65b7
Check polymorphic variant name range in the type checker
cristianoc Aug 31, 2026
2c9a0bf
Own the constant representation in Lambda and drop the converter
cristianoc Sep 1, 2026
0ca8d9a
Delete the curried-application machinery
cristianoc Sep 1, 2026
a1b1687
Remove the %function_arity primitive
cristianoc Sep 1, 2026
4235bab
Rename Lam_pass_alpha_conversion to Lam_pass_apply_arity
cristianoc Sep 1, 2026
0d55ded
Query the callee's arity where it is used
cristianoc Sep 1, 2026
0d29fd5
Delete Lam_pass_apply_arity and Lam_eta_conversion
cristianoc Sep 1, 2026
c6b836b
Drop the two collect_info calls that nothing can observe
cristianoc Sep 1, 2026
b4bec2a
Record identifier info with replace instead of add
cristianoc Sep 1, 2026
2b19e30
Give Lambda the isout offset and the Lam nullable names
cristianoc Sep 1, 2026
9082652
Fix a recursive module with an empty signature losing its effects
cristianoc Sep 1, 2026
45ce2b5
Derive a block's mutability from its shape
cristianoc Sep 1, 2026
df2dc39
Fix Int.Ref.increment evaluating its argument twice
cristianoc Sep 1, 2026
80f7dea
Delete Poffsetref
cristianoc Sep 1, 2026
a05d98b
Delete Poffsetint and the %succint / %predint builtins
cristianoc Sep 1, 2026
243e4d6
Make a module reference a name in Lambda too
cristianoc Sep 1, 2026
a1c9dea
Read the required modules off the Lambda term
cristianoc Sep 1, 2026
abdcff1
Make conversion a 1-1 rebuild
cristianoc Sep 1, 2026
8634b23
Drop the value_kind slot from Lambda's Llet
cristianoc Sep 1, 2026
cd8a8d8
Drop the arity field and the switch locations
cristianoc Sep 1, 2026
f3aaa48
Give Lambda's Lprim the same record as Lam's
cristianoc Sep 1, 2026
3fe8f58
Group Lambda's apply location and inline attribute
cristianoc Sep 1, 2026
d89e024
Fold the switcher's range guard into the switch at production
cristianoc Sep 1, 2026
3eed0fc
Remove the Pisout primitive
cristianoc Sep 1, 2026
61833b1
Remove the Switch functor
cristianoc Sep 1, 2026
d765b50
Make Lam_primitive.t a re-export of Lambda.primitive
cristianoc Sep 1, 2026
eadd03d
Make Lambda private, with a constructor per variant
cristianoc Sep 1, 2026
9aefec8
Add sharing variants of the list and option maps
cristianoc Sep 1, 2026
1a5c53f
Give Lambda's constructors the folds Lam's have
cristianoc Sep 1, 2026
0ef1dcf
Move the raise-guard rewrite out of Lam.if_ into a pass
cristianoc Sep 1, 2026
fb89d6c
Make Lam.t an alias of Lambda.lambda and drop the conversion
cristianoc Sep 1, 2026
87ecd27
Delete the Lam re-export shells
cristianoc Sep 1, 2026
72b340c
Retire Lam_free_variables in favour of Lambda's
cristianoc Sep 1, 2026
03f074f
Express subst_lambda through shallow_map_sharing and retire Lam_subst
cristianoc Sep 1, 2026
5e249ce
Retire Lam_iter and derive Lambda.iter from a shallow exists
cristianoc Sep 1, 2026
4419677
Delete the Lam module
cristianoc Sep 1, 2026
c7d3285
Rename Lambda.lambda to Lambda.t
cristianoc Sep 1, 2026
c415457
Clear the remaining references to Lam
cristianoc Sep 1, 2026
b4bc9e8
Merge the two Lambda printers into Printlambda
cristianoc Sep 1, 2026
4872308
Fold required_modules into its only caller
cristianoc Sep 1, 2026
6f54af0
Consolidate the four ident sets into Set_ident
cristianoc Sep 1, 2026
87d243c
Add changelog entries for the Lambda/Lam merge
cristianoc Sep 1, 2026
a0c916b
Point the changelog entries at the actual PR number
cristianoc Sep 1, 2026
afd4041
Update artifact list
cknitt Sep 2, 2026
d5ad1f6
Condense Lambda merge changelog entry
cristianoc Sep 2, 2026
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
6 changes: 2 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ The Makefile’s targets build on each other in this order:

- **Don't use unit `()` with mandatory labeled arguments** - When a function has a mandatory labeled argument (like `~config`), don't add a trailing `()` parameter. The labeled argument already prevents accidental partial application. Only use `()` when all parameters are optional and you need to force evaluation. Example: `let forceDelayedItems ~config = ...` not `let forceDelayedItems ~config () = ...`

- **Be careful with similar constructor names across different IRs** - Note that `Lam` (Lambda IR) and `Lambda` (typed lambda) have variants with similar constructor names like `Ltrywith`, but they represent different things in different compilation phases.

- **Avoid warning suppressions** - Never use `[@@warning "..."]` to silence warnings. Instead, fix the underlying issue properly
- **Skip trailing `; _` in record patterns** - The warning it targets is disabled in this codebase, so prefer `{field = x}` over `{field = x; _}`.

Expand Down Expand Up @@ -116,8 +114,8 @@ Read the area guide before changing a compiler subsystem:
printing, and JSX transformation
- [`compiler/ml/README.md`](compiler/ml/README.md) for the type checker and
typed tree
- [`compiler/core/README.md`](compiler/core/README.md) for Lambda, Lam, and
JavaScript generation
- [`compiler/core/README.md`](compiler/core/README.md) for Lambda
optimization and JavaScript generation
- [`analysis/README.md`](analysis/README.md) for editor analysis
- [`rewatch/README.md`](rewatch/README.md) for the build system
- [`tools/README.md`](tools/README.md) for `rescript-tools`
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@

#### :bug: Bug fix

- Fix a recursive module with an empty signature discarding its right-hand side. Lambda-to-Lam conversion rewrote `Pupdate_mod` to unit when the module's shape had no fields, dropping the primitive's arguments - one of which is the right-hand side - so `module rec M: {} = { let () = Console.log("effect") }` emitted nothing for `M`. The elision now happens where the bindings are produced, with the right-hand side still in hand. https://github.com/rescript-lang/rescript/pull/8608
- Fix `Int.Ref.increment` and `Int.Ref.decrement` evaluating their argument twice: `Int.Ref.increment(mkRef())` emitted `mkRef().contents = mkRef().contents + 1 | 0`. The `%incr` and `%decr` builtins lowered to an assignment that repeated the argument expression; they now bind the reference before the read-modify-write. Inlining decisions around an increment are taken on the code it stands for rather than on a single primitive node. https://github.com/rescript-lang/rescript/pull/8608
- Fix a compiler crash on a polymorphic variant whose numeric name exceeds the `int32` range. `#99999999999("a")` and the same name in a pattern failed with `Failure("Int32.of_string")` and no location, because the range check ran in the frontend AST pass and matched only payload-free expressions. It now runs in `Typecore`, next to the integer literal decoding whose overflow error it mirrors, and covers both label positions. A bare `type t = [#99999999999]` still compiles, since nothing decodes a row field name. https://github.com/rescript-lang/rescript/pull/8608
- Object typing errors now describe fields directly: assigning to a field without `@set` reports that the field is not settable and suggests the annotation, and missing-property errors name the field instead of a phantom `"x#="` member. https://github.com/rescript-lang/rescript/pull/8597
- Fix signature inclusion rejecting equivalent object externals after type-alias expansion. https://github.com/rescript-lang/rescript/pull/8581
- Fix externals whose result type is an alias of `unit` so they use the same unit-return behavior as externals declared to return `unit`. https://github.com/rescript-lang/rescript/pull/8581
Expand Down Expand Up @@ -60,6 +63,7 @@

#### :house: Internal

- Merge the duplicate Lam intermediate representation into Lambda, removing the conversion layer and obsolete supporting infrastructure. Lambda is now a single private, normalized representation, with generated JavaScript remaining semantically unchanged. https://github.com/rescript-lang/rescript/pull/8608
- Rework the object-type representation end to end: object rows are plain field chains carrying a per-field mutability state (no phantom setter members), object literals are typed directly and property access and assignment are first-class AST and Lambda nodes shared between the Lambda and JS pipelines, and dead class-system remnants (the field-presence lattice, the class-abbreviation memo on object types, method-send typing) are removed. https://github.com/rescript-lang/rescript/pull/8597
- Upgrade the development toolchain and primary CI builds to OCaml 5.5 while retaining OCaml 5.0 as the minimum supported version. https://github.com/rescript-lang/rescript/pull/8589
- Upgrade the vendored Flow parser from 0.267.0 to 0.320.0, the final release of the OCaml implementation. https://github.com/rescript-lang/rescript/pull/8588
Expand Down
4 changes: 2 additions & 2 deletions compiler/bsc/rescript_compiler_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,10 @@ let command_line_flags : (string * Bsc_args.spec * string) array =
"*internal* Disable cross module inlining(experimental)" );
( "-debug-ir",
set Js_config.debug_ir,
"*internal* Dump compiler IR and enable Lam invariant checks" );
"*internal* Dump compiler IR and enable Lambda invariant checks" );
( "-check-lam",
set Js_config.check_lam,
"*internal* Check Lam invariants after optimization passes" );
"*internal* Check Lambda invariants after optimization passes" );
( "-bs-no-check-div-by-zero",
clear Js_config.check_div_by_zero,
"*internal* unsafe mode, don't check div by zero and mod by zero" );
Expand Down
2 changes: 1 addition & 1 deletion compiler/common/js_config.mli
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ val debug_ir : bool ref
(** dump intermediate representations and related diagnostics *)

val check_lam : bool ref
(** check Lam invariants after optimization passes *)
(** check Lambda invariants after optimization passes *)

val no_builtin_ppx : bool ref
(** options for builtin ppx *)
Expand Down
28 changes: 15 additions & 13 deletions compiler/core/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Lambda, Lam, and JavaScript generation
# Lambda optimization and JavaScript generation

This directory contains the compiler backend after typedtree translation. It
owns ReScript's Lam representation, Lam optimization passes, JavaScript IR,
and JavaScript output.
owns the Lambda optimization passes, the JavaScript IR, and JavaScript output.
Lambda itself is defined in [`../ml/lambda.mli`](../ml/lambda.mli).

## Pipeline and code map

Expand All @@ -11,16 +11,16 @@ Typedtree translation in `compiler/ml/translcore.ml` and
`compiler/ml/lambda.mli`.

[`lam_convert.ml`](lam_convert.ml)
: Converts `Lambda.lambda` to the ReScript-specific [`Lam.t`](lam.mli),
normalizes aliases, and collects potential module dependencies.
: Collects the modules a compilation unit depends on, read off the Lambda
term.

`lam_pass_*.ml` and the other `lam_*.ml` modules
: Analyze and transform Lam. [`lam_compile_main.ml`](lam_compile_main.ml)
: Analyze and transform Lambda. [`lam_compile_main.ml`](lam_compile_main.ml)
coordinates the backend pass sequence; read it before inserting or
reordering a pass.

[`lam_compile.ml`](lam_compile.ml)
: Lowers Lam to JavaScript IR. Primitive-specific and FFI lowering is split
: Lowers Lambda to JavaScript IR. Primitive-specific and FFI lowering is split
into `lam_compile_primitive.ml`, `lam_compile_external_call.ml`, and related
modules.

Expand All @@ -34,12 +34,14 @@ Typedtree translation in `compiler/ml/translcore.ml` and

## Changing a representation

`Lambda` and `Lam` have similarly named constructors but are distinct IRs.
When adding or changing one, search every producer, traversal, optimizer,
printer, serializer, and consumer of that specific type. Do not assume a match
on the other representation covers it.
`Lambda.t` is private: every term is built through the constructors in
[`../ml/lambda.mli`](../ml/lambda.mli), six of which normalize as they build.
A constructor may replace a node with an equivalent one, but may not move code
between branches - that is what a pass is for. When adding or changing a
constructor, search every producer, traversal, optimizer, printer, serializer,
and consumer.

Check persistence boundaries as part of the change. `Lam.t` can be stored in
Check persistence boundaries as part of the change. `Lambda.t` can be stored in
`.cmj` data through `js_cmj_format`; a constructor or payload change therefore
changes cached compiler data even when generated JavaScript is unchanged.

Expand All @@ -65,5 +67,5 @@ compiler flags below to compare intermediate forms for a small source file:
./cli/bsc.js -drawlambda example.res
```

For Lam-specific debugging, use [`lam_print.ml`](lam_print.ml) at the relevant
For backend debugging, use [`lam_print.ml`](lam_print.ml) at the relevant
pass boundary and remove temporary output before committing.
6 changes: 3 additions & 3 deletions compiler/core/ir_diagnostics.ml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ let next_path diagnostics ~kind ~pass ~extension =

let dump_lam diagnostics ~pass lam =
let path = next_path diagnostics ~kind:"lam" ~pass ~extension:".lam" in
Ext_log.dwarn ~__POS__ "Dumping Lam pass %s to %s" pass path;
Lam_print.serialize path lam
Ext_log.dwarn ~__POS__ "Dumping pass %s to %s" pass path;
Printlambda.serialize path lam

let dump_groups diagnostics groups =
let path =
next_path diagnostics ~kind:"lam" ~pass:"groups" ~extension:".lambda"
in
Ext_log.dwarn ~__POS__ "Dumping Lam groups to %s" path;
Ext_log.dwarn ~__POS__ "Dumping groups to %s" path;
Ext_fmt.with_file_as_pp path (fun fmt ->
Format.pp_print_list ~pp_sep:Format.pp_print_newline Lam_group.pp_group
fmt groups)
Expand Down
2 changes: 1 addition & 1 deletion compiler/core/ir_diagnostics.mli
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type t

val create : output_prefix:string -> t
val dump_lam : t -> pass:string -> Lam.t -> unit
val dump_lam : t -> pass:string -> Lambda.t -> unit
val dump_groups : t -> Lam_group.t list -> unit
val dump_js : t -> pass:string -> J.program -> unit
3 changes: 0 additions & 3 deletions compiler/core/js_block_runtime.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@

let option_id = Ident.create_persistent Primitive_modules.option

let curry_id = Ident.create_persistent Primitive_modules.curry

let check_additional_id (x : J.expression) : Ident.t option =
match x.expression_desc with
| Optional_block (_, false) -> Some option_id
| Call (_, _, {arity = NA}) -> Some curry_id
| _ -> None
13 changes: 4 additions & 9 deletions compiler/core/js_call_info.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)

type arity = Full | NA

type call_info =
| Call_ml (* called by plain ocaml expression *)
| Call_builtin_runtime (* built-in externals *)
Expand All @@ -33,15 +31,12 @@ type call_info =
{[ fun x y -> (f x y) === f ]} when [f] is an atom
*)

type t = {call_info: call_info; arity: arity; call_transformed_jsx: bool}

let dummy = {arity = NA; call_info = Call_na; call_transformed_jsx = false}
type t = {call_info: call_info; call_transformed_jsx: bool}

let builtin_runtime_call =
{arity = Full; call_info = Call_builtin_runtime; call_transformed_jsx = false}
{call_info = Call_builtin_runtime; call_transformed_jsx = false}

let ml_full_call =
{arity = Full; call_info = Call_ml; call_transformed_jsx = false}
let ml_full_call = {call_info = Call_ml; call_transformed_jsx = false}

let na_full_call transformed_jsx =
{arity = Full; call_info = Call_na; call_transformed_jsx = transformed_jsx}
{call_info = Call_na; call_transformed_jsx = transformed_jsx}
6 changes: 1 addition & 5 deletions compiler/core/js_call_info.mli
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

(** Type for collecting call site information, used in JS IR *)

type arity = Full | NA

type call_info =
| Call_ml (* called by plain ocaml expression *)
| Call_builtin_runtime (* built-in externals *)
Expand All @@ -35,9 +33,7 @@ type call_info =
{[ fun x y -> f x y === f ]} when [f] is an atom
*)

type t = {call_info: call_info; arity: arity; call_transformed_jsx: bool}

val dummy : t
type t = {call_info: call_info; call_transformed_jsx: bool}

val builtin_runtime_call : t

Expand Down
4 changes: 2 additions & 2 deletions compiler/core/js_cmj_format.ml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type arity = Single of Lam_arity.t | Submodule of Lam_arity.t array
(* TODO: add a magic number *)
type cmj_value = {
arity: arity;
persistent_closed_lambda: Lam.t option;
persistent_closed_lambda: Lambda.t option;
(** Either constant or closed functor *)
}

Expand All @@ -40,7 +40,7 @@ let single_na = Single Lam_arity.na
type keyed_cmj_value = {
name: string;
arity: arity;
persistent_closed_lambda: Lam.t option;
persistent_closed_lambda: Lambda.t option;
}

type keyed_cmj_values = keyed_cmj_value array
Expand Down
5 changes: 3 additions & 2 deletions compiler/core/js_cmj_format.mli
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,16 @@ type arity = Single of Lam_arity.t | Submodule of Lam_arity.t array

type cmj_value = {
arity: arity;
persistent_closed_lambda: Lam.t option; (* Either constant or closed functor *)
persistent_closed_lambda: Lambda.t option;
(* Either constant or closed functor *)
}

type effect_ = string option

type keyed_cmj_value = {
name: string;
arity: arity;
persistent_closed_lambda: Lam.t option;
persistent_closed_lambda: Lambda.t option;
}

type hoisted_export = {
Expand Down
Loading
Loading