JSX dictionary evaluation-order regression: fixed by #8754; test coverage requested
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- Ein halber Tag
- Anfängerfreundlichkeit
- 58/100
- Issue-Typ
- Feature
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- javascript, ocaml
- Bereich
- compilers, testing-qa
Rechercherichtung
The fix is already merged, so the work is only a regression test. Start with the existing cases in tests/build_tests/jsx_preserve_semantics/cases/, especially Dict.res and Order.res, and the printer path in compiler/core/js_dump.ml that the report cites. Add a case with a dictionary spread whose children and a later title are both effectful, and assert the evaluation trace as well as the props. Done means the new case passes on the current revision and would fail on the parent of #8747.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
JSX dictionary evaluation-order regression: introduced by #8747, fixed by #8754
This bug is fixed at the audited revision a7721303a. The report identifies a regression introduced since the last analysis and repaired during the same period. It does not identify an unfixed compiler bug. The remaining request is a regression test for this exact case.
This finding comes from semantic drift analysis across all 45 upstream commits since ece8b148777a9a75ee221578612a6b2ec1839ede, through a7721303a16325512c06e2e712f8d8a2b776dd3c.
In #8747, JSX-preserve compilation began evaluating title before children in the example below, reversing their source order. Ordinary compilation kept the correct order. #8754 restores the correct order in JSX-preserve mode. The audited revision was checked on 2026-10-10; this example needs no further compiler fix.
Minimal example
Save as DictOrder.res:
@@jsxConfig({version: 4, module_: "MyJsx"})
module MyJsx = {
type element = Jsx.element
type component<'props> = Jsx.component<'props>
@module("react/jsx-runtime")
external jsx: (component<'props>, 'props) => element = "jsx"
}
module Comp = {
@val external make: MyJsx.component<dict<string>> = "SomeComponent"
}
@val external note: string => string = "note"
let base: dict<string> = dict{}
let element = <Comp {...dict{...base, "children": note("children"), "title": note("title")}} />
There are no casts or Obj.magic. The JSX component accepts a dict<string>, and its properties are evaluated while constructing that dictionary.
With a compiler built at one of the revisions below and runtime CMIs available:
# BSC: the bsc executable for the selected revision.
# RUNTIME: directory containing the built @rescript/runtime CMIs.
"$BSC" -I "$RUNTIME" -bs-jsx 4 -bs-jsx-preserve \
-o DictOrder.cmj DictOrder.res > preserved.jsx
"$BSC" -I "$RUNTIME" -bs-jsx 4 \
-o DictOrder.cmj DictOrder.res > ordinary.js
The compiler build target used for each upstream revision was:
dune build -j 4 compiler/bsc/rescript_compiler_main.exe
# BSC is _build/default/compiler/bsc/rescript_compiler_main.exe
This small runner uses TypeScript's JSX transform and a recording runtime. Save as verify.cjs, in a directory where require("typescript") resolves; TypeScript 6.0.3 was used in verification.
const fs = require("node:fs");
const ts = require("typescript");
for (const file of process.argv.slice(2)) {
const code = ts.transpileModule(fs.readFileSync(file, "utf8"), {
fileName: "probe.jsx",
compilerOptions: {
jsx: ts.JsxEmit.ReactJSX,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
},
}).outputText;
const order = [];
const runtime = {jsx: (_tag, props) => props};
new Function("require", "exports", "SomeComponent", "note", code)(
name => {
if (name !== "react/jsx-runtime") throw Error(name);
return runtime;
},
{},
() => {},
value => { order.push(value); return value; },
);
console.log(file, JSON.stringify(order));
}
Run node verify.cjs ordinary.js preserved.jsx. Expected for both: ["children","title"]. The affected revisions print ["title","children"] for preserved.jsx. This observation concerns evaluation of the props expression, before any real React rendering behavior.
Historical comparison
| Exact source revision | Ordinary | JSX preserve |
|---|---|---|
ece8b148777a9a75ee221578612a6b2ec1839ede, upstream analysis base |
children, title | children, title |
ce7d6eb0ff8c56311f7e2b02225c7550328d185d, immediate parent of #8747 |
children, title | children, title |
388cb1a24a65503d688b7e28b1800cf06b42e180, #8747 |
children, title | title, children |
354d5a8844beb98401aff086d9879ad8e5224f97, immediately before #8754 |
children, title | title, children |
a7721303a16325512c06e2e712f8d8a2b776dd3c, #8754 |
children, title | children, title |
The intervening commits retain the responsible printer path. Builds and executions establish the introduction boundary and the failure immediately before its repair. The comparisons above use exact upstream source trees.
All ten compilations/executions across the five listed versions and two modes succeed; the observation is a changed effect order, not a rejected program. Existing runtime CMIs were reused for the compiler probes.
Cause and repair
The affected output is equivalent to:
<SomeComponent {...base} title={note("title")}>
{note("children")}
</SomeComponent>
#8747 makes the dictionary an object-spread expression. The printer's object-spread JSX branch turns its fields into separate JSX props. It extracts children, then prints the remaining attributes before those children. That moves note("children") after note("title").
#8754's explicit JSX lowering retains this props expression as one spread. Its output is equivalent to:
<SomeComponent {...{
...base,
children: note("children"),
title: note("title")
}} />
Suggested regression coverage
Add the effectful dictionary case above to the plain/preserve semantic comparison, asserting the trace as well as the returned props. The current Dict.res fixture covers dictionary spreads, special keys and repeated children, using values that do not expose this relative effect order. Order.res covers tag/record-prop order. Combining dictionary children with a later effectful property protects a different interaction.
Likely reason the regression happened — best guess
My best guess is that #8747 was treated as a local change to how dictionary spreads are represented, and the effect on JSX printing was missed during implementation and review. That representation change made the dictionary reach an existing printer path that extracts children and emits it after the other properties. The resulting JSX can have the right property values while executing their expressions in the wrong order.
A likely testing blind spot is the combination of dictionary spreads, an effectful children property, and a later effectful property. Tests using constant property values cannot reveal this reversal. The cited dictionary and ordering fixtures cover related behavior separately, which makes this interaction a plausible omission.
This is only a best guess about why the mistake escaped the initial checks. The code and reproductions establish the ordering error and its repair; they do not establish the author's intent, how carefully anyone reviewed the change, or whether a coding agent was involved. #8754 repairs the behavior; the proposed test would protect this particular combination.
Searches of open and closed issues/PRs for JSX/order and dict/JSX, plus #8747 and #8754's descriptions and #8754 review discussions, found the related general fixes and other ordering discussions but no standalone report of this exact dictionary-children-before-title probe. This request complements the already merged fix with a specific regression test.
- Vorherrschende Sprache
- OCaml
- Sterne
- 7.5k
- Forks
- 484
- Ø Merge
- 19 Std. 46 Min.
- Gemergte PRs (30 T.)
- 81
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Keine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus rescript-lang/rescript
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 40/100
rescript-lang/rescript#8753 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 22/100
rescript-lang/rescript#8733 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 35/100
rescript-lang/rescript#8727 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 35/100
rescript-lang/rescript#8726 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Integer range patterns ending at 2147483647 generate incorrect JavaScriptEvtl. vergeben @fhammerschmidt hat das vor 9 Tagen übernommen. Offen
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 70/100
rescript-lang/rescript#8716 ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in rescript-lang/rescript
Ähnliche Issues
-
bug derived types format I/O medium priority semantics
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
bytecodealliance/wasm-tools#2768 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
antlr/grammars-v4#5035 ·
Maintainer antworten meist innerhalb von 5 Tagen
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
Maintainer antworten meist innerhalb von 1 Tag