Skip to content

Make functions and arrow types n-ary in the parsetree - #8566

Open
cristianoc wants to merge 1 commit into
masterfrom
codex/nary-parsetree
Open

Make functions and arrow types n-ary in the parsetree#8566
cristianoc wants to merge 1 commit into
masterfrom
codex/nary-parsetree

Conversation

@cristianoc

Copy link
Copy Markdown
Collaborator

Part of the n-ary functions series tracked in #8557 (item 4; follows the merged #8559, #8561, #8563).

What

Replace the curried one-parameter-per-node encoding in the parsetree with n-ary nodes:

Ptyp_arrow of {params: arg list; ret: core_type}
Pexp_fun of {params: fun_param list; body: expression; async: bool}

where fun_param carries per-parameter attributes, label, default, and pattern. The arity annotation is gone from the parsetree: a function's arity is List.length params, unrepresentable wrong. ast_uncurried.ml is deleted; Ast_helper.Typ.arrow and Exp.fun_ are list-first and assert non-empty parameter lists.

Contract

  • Typed layers unchanged: typetexp folds the params list into the existing curried Tarrow/Ttyp_arrow chains (Some arity on the head, None inside), and typecore peels parameters one at a time, reproducing the legacy per-level type_function calls; synthesized rest-functions carry an internal #res.fun_rest attribute consumed immediately on re-entry.
  • PPX wire format byte-identical: the Parsetree0 bridge re-curries on the way out and gathers Has_arityN / res.arity groups back into one node on the way in; bare PPX-fabricated v0 funs decode as one-parameter functions instead of the old, mostly unusable arity-None encoding.
  • Printing byte-identical across the syntax test corpus; generated JavaScript byte-identical across the test suite except UncurriedExternals.res, where @this this => async arg => ... now honors the written nesting (a method returning an async function) instead of absorbing the nested lambda's parameter into the method — the group-boundary ambiguity this representation removes.
  • Attribute contract: in-parens parameter attributes stay on the patterns (as before); arrow-level attributes (@attr (a, b) => ...) live on the function node; p_attrs is populated only by the PPX bridge.

Tooling fixes included (each pinned by a test)

  • Signature help no longer includes the opening paren in the first parameter's highlight range, and no longer walks into the result type: for let f: (a, b) => c => d, help on f(...) lists two parameters, not three.
  • Completion: with the cursor in parameter N's pattern, earlier parameters are no longer pattern-completed, so their comma-recovery can no longer shadow the correct result (e.g. (Some(x), Blah(a, <cursor>)) => ... now completes Blah's payload).
  • Printer: an attribute on a function nested under a newtype wrapper (constructible only via PPX; the bridge deliberately preserves it) is no longer silently dropped when the printer merges the two into one parameter list.
  • Completion debug traces lose their synthetic chain-node lines.

Also fixes three latent Typ.arrow-on-empty-params paths (zero-argument externals, @deriving(accessors) zero-argument constructors in signatures, parser error recovery) that the old arrows helper silently absorbed.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dfbd748cd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/frontend/ast_uncurry_gen.ml
Comment thread compiler/ml/ast_mapper_to0.ml Outdated

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Two intended changes here. The range shifts (e.g. [0, 7][4, 7]) are the first parameter's highlight no longer swallowing the opening paren, and the parameter list in the debug header no longer renders a stray ( and ~ prefixes. The new makeAdder case at the bottom pins the other fix: signature help no longer walks into the result type, so a function of type (int, int) => int => int lists two parameters, not three.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Only debug-trace lines change here: the old curried encoding created one nested Pexp_fun node per parameter and the iterator logged a "Found expr" line for each; the n-ary node is visited once. No completion results change in this file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same as CompletionInferValues.res.txt: one synthetic chain-node debug line gone, no result changes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

New regression case (see the fixture comment): with the cursor in the second parameter's pattern, the first parameter is no longer pattern-completed, so its comma recovery can't shadow the correct result — completion now targets Blah's second payload (bool).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No dead/live verdicts change. The location diffs are record-label declarations now pointing at the label itself (e.g. 7:158:11), and the Root (annotated) listing reorders because the underlying iteration is now over one parameter list instead of a nested chain.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These parsing dumps print the recovered AST in ML syntax, so the n-ary node renders as fun [arity:3]a b c -> instead of a chain of single-parameter funs. Same reason for the other parsing/**/expected files in this PR — the dump format follows the representation; the parsed programs are unchanged.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Besides the n-ary dump flattening, error-recovery functions now carry an explicit [arity:1] where they previously had none — recovery goes through the same non-empty-params Exp.fun_ as regular parsing now (one of the three latent empty-params paths fixed in this PR).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The one deliberate generated-JS change in this PR: @this this => async arg => ... now means what it says — a method returning an async function — instead of the old chain-walk absorbing the nested lambda's parameter into the method. The group-boundary ambiguity that allowed the old reading is exactly what the n-ary representation removes.

Replace the curried one-parameter-per-node encoding with n-ary nodes:

  Ptyp_arrow of {params: arg list; ret: core_type}
  Pexp_fun of {params: fun_param list; body: expression; async: bool}

where fun_param carries per-parameter attributes, label, default, and
pattern. The arity annotation is gone from the parsetree: a function's
arity is List.length params, unrepresentable wrong. ast_uncurried.ml is
deleted; Ast_helper.Typ.arrow and Exp.fun_ are list-first and assert
non-empty parameter lists.

The typed layers are unchanged: typetexp folds the params list into the
existing curried Tarrow/Ttyp_arrow chains (Some arity on the head, None
inside), and typecore peels parameters one at a time, reproducing the
legacy per-level type_function calls; synthesized rest-functions carry
an internal #res.fun_rest attribute consumed immediately on re-entry.

The Parsetree0 bridge re-curries on the way out (byte-identical wire
format for external PPXes) and gathers Has_arityN / res.arity groups
back into one node on the way in; bare PPX-fabricated v0 funs decode as
one-parameter functions instead of the old, mostly unusable arity-None
encoding.

Attribute contract: in-parens parameter attributes stay on the
patterns (as before); arrow-level attributes (@attr (a, b) => ...) live
on the function node; p_attrs is populated only by the PPX bridge.

Printing is byte-identical across the syntax test corpus; generated
JavaScript is byte-identical across the test suite except
UncurriedExternals.res, where `@this this => async arg => ...` now
honors the written nesting (a method returning an async function)
instead of absorbing the nested lambda's parameter into the method -
the group-boundary ambiguity this representation removes. Signature
help no longer includes the opening paren in the first parameter's
highlight range, and completion debug traces lose their synthetic
chain-node lines.

Also fixes three latent Typ.arrow-on-empty-params paths (zero-argument
externals, @deriving(accessors) zero-argument constructors in
signatures, parser error recovery) that the old arrows helper silently
absorbed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.10938% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.86%. Comparing base (110534a) to head (63be65d).

Files with missing lines Patch % Lines
compiler/ml/printast.ml 0.00% 14 Missing ⚠️
compiler/syntax/src/jsx_v4.ml 84.84% 10 Missing ⚠️
analysis/src/completion_front_end.ml 76.92% 6 Missing ⚠️
compiler/frontend/bs_ast_mapper.ml 33.33% 6 Missing ⚠️
compiler/ml/typecore.ml 89.58% 5 Missing ⚠️
compiler/ml/ast_mapper_from0.ml 90.00% 4 Missing ⚠️
tools/src/transforms.ml 0.00% 4 Missing ⚠️
compiler/syntax/src/res_comments_table.ml 78.57% 3 Missing ⚠️
compiler/syntax/src/res_core.ml 85.71% 3 Missing ⚠️
compiler/frontend/ast_core_type.ml 75.00% 2 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8566      +/-   ##
==========================================
+ Coverage   75.83%   75.86%   +0.03%     
==========================================
  Files         476      475       -1     
  Lines       62715    62835     +120     
==========================================
+ Hits        47560    47672     +112     
- Misses      15155    15163       +8     
Files with missing lines Coverage Δ
analysis/src/signature_help.ml 78.70% <100.00%> (+0.04%) ⬆️
analysis/src/xform.ml 88.37% <100.00%> (-0.32%) ⬇️
compiler/frontend/ast_compatible.ml 88.88% <100.00%> (ø)
compiler/frontend/ast_core_type_class_type.ml 74.41% <100.00%> (+1.24%) ⬆️
compiler/frontend/ast_derive_abstract.ml 92.85% <100.00%> (ø)
compiler/frontend/ast_derive_js_mapper.ml 84.44% <100.00%> (ø)
compiler/frontend/ast_derive_projector.ml 90.47% <100.00%> (+1.41%) ⬆️
compiler/frontend/ast_exp_handle_external.ml 76.74% <100.00%> (ø)
compiler/frontend/ast_typ_uncurry.ml 100.00% <100.00%> (ø)
compiler/frontend/bs_builtin_ppx.ml 90.62% <100.00%> (+0.07%) ⬆️
... and 28 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Aug 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@8566

@rescript/belt

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/belt@8566

@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@8566

@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@8566

@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@8566

@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@8566

@rescript/runtime

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/runtime@8566

@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@8566

commit: 63be65d

@cristianoc
cristianoc force-pushed the codex/nary-parsetree branch from 270640e to 63be65d Compare August 19, 2026 14:46
@cristianoc
cristianoc requested a review from cknitt August 19, 2026 14:56
@github-actions

Copy link
Copy Markdown

@cknitt cknitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good as far as I can tell! 👍
I also retested against a large company project - compiled fine without any output changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants