From 0fce1b4c7feb6c7614f30c8202a84fb36d0f3c04 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Mon, 17 Aug 2026 09:53:23 +0200 Subject: [PATCH] Eliminate Pjs_fn_make, Pjs_fn_make_unit, and unsafe_adjust_to_arity With structural arity, "function of arity n" is a construction invariant rather than a goal state: translcore builds every Lfunction with exactly the parameters its type declares. The arity-enforcement layer therefore disappears: - Function literals are emitted directly; the Pjs_fn_make wrapper that every function passed through was resolved as a no-op by lam_pass_alpha_conversion, but only *after* deep_flatten, simplify_exits and simplify_alias had run with the function hidden inside an Lprim, acting as an accidental optimization barrier. - Pjs_fn_make_unit was a one-bit metadata channel: its entire effect was setting one_unit_arg so js_exp_make drops the unit parameter. translcore now sets the attribute directly, gated on the parameter pattern binding no identifiers (a () or _ pattern) - a more principled test than the alpha pass's check that the parameter was named "param". - The active-pattern currying split in transl_function is deleted. It preserved pattern-effect timing across curried application steps, which no longer exist: total applications supply all arguments at once and explicit partial application eta-defers the entire call. The old output proves the point - the split's closures were immediately applied by the arity adapter, so only the allocations are gone (see mutable_uncurry_test). - The I unboxed-record producer (the @this method-callback encoding) is removed: the general Record_unboxed translation already returns the single field unboxed, and the wrapped value is a literal of matching arity. With no producers left, both primitive constructors and every consumer arm are deleted, including the 230-line unsafe_adjust_to_arity (its only callers were the two Pjs_fn_make resolution sites). On recursive modules: removing the wrapper lets the static recursive-module compilation path see module members that are plain functions, replacing the Primitive_module.init/update bootstrap with hoisted function declarations. This is safe because the static path's own applicability check now sees the functions it was designed to check - the wrapper was hiding them, pessimizing compilation - and the bootstrap demonstrably remains for members that are not plain functions (rec_module_test keeps its lazy/value cases dynamic). Verified: stdlib byte-identical; full test suite green; JS output changes limited to removed adapter closures, removed no-op module bootstraps, better name preservation, and constant propagation. Keep beta-residue let chains (immutable blocks and aliases feeding a final apply) local in Lam_pass_deep_flatten instead of hoisting them into the enclosing group: with the wrapper gone, beta reduction happens before flatten2, and hoisting the argument bindings to toplevel put them beyond Lam_pass_lets_dce's reach (a_recursive_type kept a named intermediate that master inlined; the snapshot pins the restored form). Co-Authored-By: Claude Fable 5 Signed-off-by: Cristiano Calcagno --- CHANGELOG.md | 1 + compiler/core/design.md | 15 -- compiler/core/lam.ml | 7 - compiler/core/lam_analysis.ml | 3 +- compiler/core/lam_compile.ml | 6 - compiler/core/lam_compile_primitive.ml | 3 +- compiler/core/lam_convert.ml | 2 - compiler/core/lam_eta_conversion.ml | 230 ------------------ compiler/core/lam_eta_conversion.mli | 3 - compiler/core/lam_pass_alpha_conversion.ml | 19 -- compiler/core/lam_pass_deep_flatten.ml | 20 ++ compiler/core/lam_primitive.ml | 10 +- compiler/core/lam_primitive.mli | 2 - compiler/core/lam_print.ml | 2 - compiler/ml/lambda.ml | 2 - compiler/ml/lambda.mli | 2 - compiler/ml/printlambda.ml | 2 - compiler/ml/translattribute.ml | 4 +- compiler/ml/translcore.ml | 73 ++---- tests/belt_tests/src/rec_module_opt.mjs | 73 +----- .../tests/src/DisambiguateOptionalFields.mjs | 4 +- tests/tests/src/UncurriedAlways.mjs | 28 +-- tests/tests/src/ari_regress_test.mjs | 8 +- tests/tests/src/ast_abstract_test.mjs | 8 +- tests/tests/src/async_inline.mjs | 8 +- tests/tests/src/attr_test.mjs | 4 +- tests/tests/src/dollar_escape_test.mjs | 12 +- tests/tests/src/flexible_array_test.mjs | 12 +- tests/tests/src/int_poly_var.mjs | 4 +- tests/tests/src/module_parameter_test.mjs | 2 +- tests/tests/src/mutable_uncurry_test.mjs | 92 +++---- tests/tests/src/pipe_syntax.mjs | 8 +- tests/tests/src/ppx_apply_test.mjs | 17 +- tests/tests/src/pr6726.mjs | 4 +- tests/tests/src/rec_module_test.mjs | 127 ++-------- tests/tests/src/recursive_module_test.mjs | 22 +- tests/tests/src/submodule.mjs | 4 +- tests/tests/src/test_ari.mjs | 4 +- tests/tests/src/test_demo.mjs | 7 +- tests/tests/src/test_google_closure.mjs | 13 +- tests/tests/src/uncurried_pipe.mjs | 32 +-- .../src/expected/TestPpx.res.jsout | 8 +- 42 files changed, 187 insertions(+), 720 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69409487802..3e77acfd55a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Sync the platform npm package's compiler binaries (`packages/@rescript//bin`) via dune promotion on every `dune build`, instead of Makefile/CI copy steps that only ran when make did: a plain `dune build` can no longer leave `cli/*.js` and the test harnesses running a stale compiler. https://github.com/rescript-lang/rescript/pull/8560 - Remove unused compiler IR definitions, modules, helpers, error variants, and Typedtree fields. https://github.com/rescript-lang/rescript/pull/8551 https://github.com/rescript-lang/rescript/pull/8555 +- Eliminate the `Pjs_fn_make`/`Pjs_fn_make_unit` arity-adjustment primitives and the `unsafe_adjust_to_arity` machinery: with structural arity, functions are constructed at their final arity, so the enforcement layer (and the active-pattern currying split it compensated for) is deleted. Generated code improves: no adapter closures for patterns on mutable fields, better constant propagation and name preservation, and recursive modules whose members are plain functions compile statically without the runtime bootstrap. https://github.com/rescript-lang/rescript/pull/8570 - Cleanups enabled by structural arity: remove the unreachable `Too_many_arguments` error and the `?in_function` threading through the type checker that existed only to decorate it; remove the dead `function$`-vs-arrow unification bridge, `Ctype.arity`, and the unused parsetree arity helpers; deduplicate the analysis arrow-flattening helpers. https://github.com/rescript-lang/rescript/pull/8569 - Make the typed layers n-ary as well: `Types.Tarrow` carries a parameter list, `Texp_function` carries typed parameters (label, ident, pattern, per-parameter exhaustiveness) and a body, and `Ttyp_arrow`/`Otyp_arrow` follow. The `arity` annotation and its `int option` phantom state are gone from the compiler entirely; `push_defaults` in translcore and the hand-rolled gather-until-arity walks in gentype, reanalyze, and the outcome printer are deleted. The cmi and cmt magic numbers are bumped (`Caml1999I023`/`Caml1999T023`). Generated JavaScript is byte-identical across the test suite (optional-parameter internals are named `*opt_