Repository navigation
Choose record key order from the orders values are built in - #1398
Merged
Merged
Conversation
Each record shape's enumeration table now follows the key order its object literals, spreads, rest bindings and record copies create, and appends optional keys assigned after construction. Shapes whose values are built in more than one order keep their declaration order and SC6002. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A record enumerates its keys (
Object.keys/values/entries,for…in,JSON.stringify,util.inspect,console.log, record→dyn conversion) through one table per shape,IrRecordShape.declaredOrder. Until now that table was the checker's property order of the first type interned to the shape, so every value built in another order printed differently from Node, silently in most cases (scriptc's ownRuntimeFeaturesrecord broke the self-hosting comparison this way in #1348).This change picks each shape's table from the orders its values are actually created in. It changes table contents only: storage layout, shape identity, and the structure of every helper stay the same, so there is no new runtime work and no new per-object state.
Stage 1: construction witnesses.
dynCheck: typedJSON.parse, values from JavaScript) pin today's table. Checked conversions already keep their own order through the record-source table, and a different table would make values parsed in today's order keep a source view.Stage 2: late-write witnesses.
What keeps today's table. Shapes whose witnesses conflict keep today's table and today's
SC6002warnings, and so does every shape copied from them. Conflicts come from values built in two orders, structural interning of differently ordered types, delete then re-add, or late keys written through different bindings. Shapes that no order surface observes also keep today's table.Placement. The pass (
frontend/lowering/key-order-witness.ts) runs once retained reachability has settled, after the shape-order metadata finalizers and before the helper finalizers that rebuild the keys/values/inspect/merge helpers. It uses the typed IR traversal, and the self-hosted compiler lowers it.SC6002consults the re-chosen table, so warnings disappear exactly where the native order now matches Node.Native inspect fix.
lower-inspect.tstyped its helper function as an inferred object literal. Natively, pushing that intoliftedFns: IrFunction[]copied it, so the shape-order finalizer replaced the body of the original instead of the listed copy. This was latent: the finalizer rarely changed an order before. It is now typedIrFunction. Without this fix, the native self-lowering diverged from the Node seed.Evidence
Probes (design-key-order probes, Node 24.15 vs native):
These still differ, as on main, and are stage 3 or out of scope: p03, p07, p08, p10, p11, p14, p23, p24, p32, p33, p40, p41, p50, p53, p60. p21b and p48 stay refused (SC1052, SC1090). p08 and p40 go through a checked conversion, which pins the table on purpose. p60 builds one JSDoc type in two orders.
Corpus (Node vs native, byte-identical, both lanes):
key-order-literal-flows.ts,key-order-spread-copies.ts,key-order-late-optional.ts. All three fail on main.Unit tests:
key-order-witness.test.tscovers agreement, conflict, pins, inheritance, late writes, loops and re-adds.Zero-cost gate (
scripts/zero-cost-gate.mjs, bench, self and the whole corpus, baseline = origin/main):bench/thread-slotslinking dynamic-tier symbols, is not from this change: that executable is byte-identical to the baseline's.5047-generator-delegation-iterables-typed.ts: the iterator-result shape{done, value}is built as{ done, value }by the program'snext()methods, and its table goes fromvalue,donetodone,value. Nine functions change: JSON writers, one record→dyn converter and the record-view materializers.6183-interface-overload-flow.ts: the shape{emit, label}goes fromemit,labeltolabel,emit, and one JSON writer changes.emitis a function, so the output is identical.code, category, severity, scope, file, line, column, …), compile-option records and lowering-position records.%obj.keysand%obj.valueshelpers.Test plan
pnpm test:ts7 --baselines-only(3 new baselines recorded)NEXT_DIST_DIR=.next-check pnpm check)🤖 Generated with Claude Code