Stamp parsetree v1: what remains before the AST can be frozen for PPXs
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 28/100
- issue の種類
- リファクタリング
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- ocaml
- 領域
- compilers
調査の方向性
parsetree.ml と提案されている self-containment 作業から始め、続いて analysis/src/xform.ml、res_core.ml、既存の ast-mapping フィクスチャおよび ounit ブリッジテストを調査してください。パーサーの生成側と parsetree のコンストラクターを比較し、必要な設計上の判断を文書化してください。より広範な作業は、v1 サーフェス、wire format、テスト、およびドキュメントが提示された stamp 基準を満たした時点で完了します。
索引モデルが issue の本文から書いたものです。
説明
Context
parsetree0.ml is the frozen v0 AST that external PPXs receive today, and parsetree.ml has moved a long way from it. The bridge (ast_mapper_to0 / ast_mapper_from0) is best-effort and lossy by design. At some point a version of the current AST should be stamped as v1 so PPXs, or a better mechanism inspired by them, can target it directly.
This issue is the audit of what stands between the current tree and that stamp. It reviews what changed since v0, extracts the principles those changes were made under, and applies them to the rest of the surface syntax. File and line references are against the tip of the Lambda stack (#8615–#8620) as of 2026-09-04.
What has changed since v0
| Area | v0 | Now |
|---|---|---|
| Literals | Pconst_char of int, Pconst_string of string * string option |
Pconst_char {source; semantic}, Pconst_string of String_literal.string_literal, new Pconst_json, Pconst_raw_source |
| Functions | unary nested Pexp_fun; Pexp_newtype; Pexp_function |
Pexp_fun {newtypes; params; body; async}, n-ary, invariant params <> [] |
| Application | Pexp_apply of expr * args |
Pexp_apply {funct; args; partial; transformed_jsx} |
| Arrows | unary Ptyp_arrow |
Ptyp_arrow {params: arg list; ret} |
| Records | (lid * expr) list |
record_element list with opt; patterns add record_pat_rest |
| Objects | Pexp_send/new/setinstvar/override |
Pexp_object_get/set/literal |
| New nodes | — | Pexp_await, Pexp_jsx_element and the jsx_* family, Pexp_for_of, Pexp_for_await_of, Pexp_template, Pexp_tagged_template, Pexp_break, Pexp_continue |
| Declarations | attributes only | pld_optional, pld_runtime_name, pcd_runtime_tag with constructor_tag |
| Bindings | — | pvb_constraint: value_constraint option |
| Externals | pval_prim: string list |
pval_prim: primitive_repr option (Prim_name, Prim_ffi, Prim_inline_const) |
| Labels | Noloc.arg_label |
arg_label carrying a loc |
| Removed | Pexp_lazy/poly/object, Ppat_lazy, Ptyp_class, Pstr_class, Psig_class*, Pexp_unreachable |
— |
Plus pc_bar on cases and Lexing.position fields on JSX nodes.
The principles those changes followed
- Structure over attribute. What the compiler decides on becomes a field or constructor; the attribute is surface syntax, reconstructed only for printing and the v0 wire. One extraction point per fact: a smart constructor (
Ast_helper.Type.field) and its inverse (Type.field_attributes), so there is never a second reader of the attribute. - Source and meaning, both explicit.
{source; semantic}on literals,source_segmentson templates,constructor_tagkeeping its spelling. The boundary is stated once: source spelling is discarded after type checking. - ReScript's shape, not OCaml's encoding. N-ary functions, JSX and object operations as nodes, OCaml-only constructs deleted rather than kept as dummies.
- Declaration-shaped, not string-encoded.
primitive_reprinstead ofstring list. - A location for everything printable.
arg_labelgained aloc;pld_runtime_namekeeps the attribute's ownlocso it reprints where it was written. - Bridge, don't break. Every change shipped
to0/from0, a fixture undertests/syntax_tests/data/ast-mapping/, and ounit round-trips.
Audit
v1 is the parser's output
External PPXs run on bsc -bs-ast output, before jsx_v4 and before FFI digestion (cmd_ppx_apply.ml:27-33; Prim_ffi is documented as never observed by PPXs). So the stamp covers exactly what res_core.ml produces. That gives a clean scope: attributes emitted by the parser are in scope; attributes emitted by later passes are not.
A. Semantics still carried by parser-emitted attributes
res_core.ml emits ten res.* markers. Three classes:
Consumed by the type checker (real semantics as attributes, PPX-visible):
| Marker | Consumer |
|---|---|
res.inlineRecordDefinition |
typedecl.ml:1534 |
res.patVariantSpread |
variant_coercion.ml:284 |
res.dictPattern |
dict_type_helpers.ml:37 |
res.braces |
typecore.ml:2404 (error-message context) |
res.ternary |
typecore.ml:3049, next to TODO(attributes) Unify the attribute handling in the parser and rest of the compiler |
res.await on module expressions |
res_core.ml:6937, consumed via Ast_await from bs_builtin_ppx.ml:365-762 |
The last one is a gap left by the Pexp_await work: await e is a node, await M for dynamic import is still an attribute on Pmod.
Layout hints consumed only by the printer (PPX-visible): res.braces/ns.braces, res.ternary, res.iflet, res.doc. Formatting facts inside a semantic tree, the same category pc_bar was moved out of attributes into a field.
Parser desugaring with a reprint marker: res.spread and res.dictSpread. [...a, b] is desugared in the parser to List.spread([...]) (res_core.ml:4381) with the marker on the identifier so the printer can undo it. The AST does not contain the construct; a PPX sees the desugaring.
User-facing attributes in the same position @as was in before #8619: @react.component, @jsx.component, componentWithProps, consumed by jsx_v4.
B. Language features keyed by an extension name
%raw, %re, %ffi, %todo, %debugger are language features selected by a string in Pexp_extension. Pconst_raw_source and Pconst_json exist because they had no node: half a step taken.
C. Post-PPX state stored in the pre-PPX type
Prim_ffi of {name; spec: External_ffi_types.t}inpval_prim: the FFI digestion result lives in the parsetree and is documented as never observed by PPXs. It cannot be both invisible to PPXs and part of the frozen wire.Pexp_apply.transformed_jsx: set byjsx_v4.ml:1295, read bytranslcore.mlandjs_dump.ml:540. Same shape of problem on a PPX-visible node.
D. Self-containment of the type
parsetree.ml depends on Longident, Location, Lexing, String_literal, External_ffi_types, List.
Lexing.positionon 7 fields (JSX,pc_bar) againstLocation.teverywhere else: two position conventions on one wire.mutable pexp_attributes(parsetree.ml:250, "Hack: made pexp_attributes mutable for use in analysis exe"), written byanalysis/src/xform.ml:273.pat_record_label(parsetree.ml:225): zero uses anywhere. Dead.Rinherit/ row fields still tuple-encoded, with an in-treeTODO: switch to a record representation, and keep location(parsetree.ml:176).
E. Constructors the parser never produces
Resolved mechanically: for all 131 constructors in parsetree.ml, grep res_core.ml for the constructor or its Ast_helper builder.
- Dead:
Ppat_open. Zero producers inres_core.mlor anywhere else; the only mentions are consumers inres_ast_debugger.ml,analysis/src/utils.ml,completion_front_end.ml,completion_patterns.ml. Remove. - Everything else is reachable.
Pstr_typext,Psig_typext,Pstr_recmodule,Psig_recmoduleandPexp_jsx_elementare built through differently named helpers;Pct_*throughType.constructor;Prim_ffiandPrim_inline_constare digestion products by design (see C).
F. The wire
- PPXs receive v0 as
output_valueofParsetree0values with magicCaml1999M022. v1 exists only in-process; its magicResImpl01306has been bumped six times since 2026-08-28. Harmless while nothing external reads it, but it means there is no stable v1 yet. - v0 encodings use fake structure a traversing PPX will act on:
for..ofbecomes aforloop with0..0bounds and the iterable hidden in an attribute (ast_mapper_to0.ml:591-603);awaitis an attribute on the inner expression with an ordering trick to split two attribute lists (:703); object set is an application of#=; break and continue are extensions. Marshalties a PPX to the compiler's OCaml version and runtime layout. The "better mechanism" question is the wire-format question, and it constrains the type:Lexing.position,loc_ghost,Longidentall become wire commitments.
G. Test coverage at the bridge
10 fixtures under ast-mapping/ and 22 ounit tests, but none of the fixtures contain break, continue, a record rest pattern, a type a. constraint, dict{}, %raw, a unicode char escape, @optional, @inline, @tag, variant spread, or catch. Coverage is thinnest on exactly the newest v1-only nodes.
What stamping v1 requires
A. Apply principle 1 to the rest of the PPX-visible surface, one PR each, with the recipe from #8619 (smart constructor, _attributes inverse, ast-mapping fixture, ounit round-trip): inline record definitions, dict patterns and spread, variant spread, if let, ternary, await on module expressions, list spread, JSX component attributes, and the five extensions as nodes. The layout hints need a design decision first: a field on the node, or attributes documented as part of v1. Principle 2 argues for the field.
B. Make the type self-contained. Move External_ffi_types out of the parsetree (keep Prim_name; digestion output belongs where digestion runs, as transformed_jsx should). Lexing.position to Location.t. Drop mutable. Delete pat_record_label and Ppat_open. Record-ify row fields.
C. Decide the wire format before freezing, because it constrains the type.
D. Test to the bar of a stamp. A fixture for every v1-only node; a v1-to-wire-to-v1 identity test alongside the v0 one; a check that Ppat_open stays gone, generalized to "the parser cannot emit X".
E. Write it down. The recent nodes in parsetree.ml carry real doc comments; the older ones do not. The whole file needs that standard, plus a magic-number policy and a v0 sunset.
Suggested order: B first, since it is mechanical and shrinks the surface before anything is frozen; then A as a PR series; C in parallel as a design note; D grows with A; E last.
Out of scope for the stamp, still worth fixing
Attributes emitted after PPXs run and consumed downstream, invisible to a PPX but the same disease: res.hoistedFunction (bs_builtin_ppx.ml:68 to translcore.ml:970, lam_compile_main.ml:199), res.jsxComponentProps (jsx_v4.ml:211 to error_message_utils.ml:898), res.patFromVariantSpread and res.constructor_from_spread (internal to variant_type_spread.ml).
- 主要言語
- OCaml
- スター
- 7.5k
- フォーク
- 485
- 平均マージ
- 1日 2時間
- マージ済み PR(30日)
- 55
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
rescript-lang/rescript のほかの issue
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
rescript-lang/rescript#8659 · コメント 2 件 · リアクション 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
rescript-lang/rescript#8647 ·
-
rescript-lang/rescript#8632 · 担当者 1 名 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 30/100
rescript-lang/rescript#8596 · コメント 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
rescript-lang/rescript#8573 · リアクション 1 件 ·
rescript-lang/rescript の issue をすべて見る
似ている issue
-
mlir
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
llvm/llvm-project#224908 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
area-CodeGen-coreclr untriaged
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
secondlife/sl-vscode-plugin#147 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
objectionary/phie#149 ·