Skip to content

refactor(stdlib): remove decimal.js/big.js/bignumber.js native binding - #10704

Draft
proggeramlug wants to merge 3 commits into
mainfrom
wip/10684-remove-decimaljs-binding
Draft

proggeramlug wants to merge 3 commits into
mainfrom
wip/10684-remove-decimaljs-binding

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #10684 — the removal is the fix.

Removes the native decimal.js binding (and big.js/bignumber.js, which share the exact same
crate, runtime symbols, and defects) so import Decimal from "decimal.js" (no
perry.compilePackages entry) resolves to the real npm package, compiled from source, per the
owner's decision to stop shipping hand-written Rust reimplementations of npm packages.

⚠️ Base branch — do not merge before #10699

This PR is based on fix/10439-native-binding-import-provenance (#10699), not main. Without
#10699's fix, new Decimal(...) chained directly onto a method call is intercepted by class-name
spelling regardless of perry.compilePackages, so decimal.js's real source is unreachable at its
documented import name. This PR is not independently mergeable — it must land after #10699.

The defects this closes (#10684)

  • Wrong arithmetic, silently: new Decimal(1).dividedBy(3) returned "1" (real: "0.3333…").
    new Decimal(10).dividedBy(4) returned "1" (real: "2.5").
  • Ordinary multiplication aborts the process: new Decimal("123456789123456789").times("987654321987654321")
    panics with Multiplication overflowed in rust_decimal — SIGABRT. Root cause is structural: the
    binding backs an arbitrary-precision JS library with rust_decimal's fixed 96-bit decimal
    type, so sufficiently large operands necessarily overflow.
  • instanceof/constructor.name broken — the handle isn't a real class object (same class of
    bug as lru-cache's).

Scope note: big.js / bignumber.js go with it

decimal.js, big.js, and bignumber.js all route through the same perry-ext-decimal crate,
the same js_decimal_* runtime symbols, and the same HIR recognition (detect_native_instance_expr
and its siblings treat Big/Decimal/BigNumber as one group). They can't be split — removing
decimal.js's binding necessarily removes big.js/bignumber.js's too. Same backing type, same
structural overflow defect. LRUCache/Command recognition in the same shared functions is
untouched; those are #10685/#10686.

What was found and removed (both copies, per #10678)

  • crates/perry-ext-decimal/ (crate deleted; governance-tracked, well_known_bindings.toml's
    [bindings."decimal.js"] + [bindings."bignumber.js"])
  • crates/perry-stdlib/src/decimal.rs (506 lines, feature-gated bundled-decimal, exporting the
    exact same js_decimal_* symbols) + the feature itself and its rust_decimal dependency
  • HIR recognition of Big/Decimal/BigNumber (leaving LRUCache/Command alone) across
    lower_patterns.rs (detect_native_instance_expr), native_new.rs, native_fetch.rs,
    module_decl.rs, static_and_instance.rs (the math-lib fluent-chain continuation), and
    js_transform/imports.rs's NATIVE_CODEGEN_CLASSES
  • The "Decimal" construction arm + collision guard in crates/perry-codegen/src/lower_call/builtin.rs
  • The 25 decimal.js NativeModSig rows in native_table/async_decimal.rs — that file also held
    unrelated async_hooks.AsyncLocalStorage rows (a prior file-size split bundled two unrelated
    families under one name); trimmed to just those and renamed to async_hooks.rs
    (ASYNC_DECIMAL_ROWSASYNC_HOOKS_ROWS)
  • The decimal.js manifest rows (perry-api-manifest's part_1.rs, the bignumber.js alias class
    in part_4.rs, both NATIVE_MODULES entries in entries.rs)
  • feature_detect.rs's native-module scan list, stdlib_features.rs's feature-flip mapping
  • 15 Android stub exports (js_decimal_*) in perry-ui-android/src/stdlib_stubs.rs
  • The perry-ext-decimal workspace member + path dependency in the root Cargo.toml
  • workspace-architecture.json's entry (workspace_members 83→82, externalize 33→32)
  • The stale entry in scripts/unrooted_local_shape_baseline.json
  • scripts/native_result_ledger.{tsv,py} — 12 js_decimal_* NR_HANDLE_ID provider rows
    (arithmetic ops that return a new handle), EXPECTED_ROWS/EXPECTED_PROVIDERS 371/322 → 359/310.
    This gate reads crates/perry-codegen/src/lower_call/native_table/*.rs + the TSV directly and
    fails hard if either side drifts — confirmed green after the edit.
  • Docs: docs/src/stdlib/overview.md, docs/src/stdlib/other.md (+ its
    docs/examples/stdlib/other/snippets.ts anchor), docs/src/native-libraries/governance.md,
    docs/src/api/reference.md, docs/api/perry.d.ts

Left alone, deliberately: docs/audits/rust-dependency-decisions-2026-09-14.{md,json} (dated,
frozen audit snapshots, same convention as leaving CHANGELOG.md alone) and
test-files/test_parity_decimal.ts, which imports the real package with no node_modules of its
own — it was already quarantined pre-existing and unrelated to this PR: test-parity/known_failures.json
tracks it under #8271 since 2026-08-17 ("Node 26.5.1 exits ERR_MODULE_NOT_FOUND for 'decimal.js'
… absent from package.json/package-lock.json"), it's in test-parity/parity_matrix_baseline.json's
allowed_statuses: [parity_fail], and it's already in test.yml's SKIP_TESTS. Left as the #8271
audit trail's problem, not this removal's.

A pre-existing red test found on the base branch, not caused by this PR: perry-hir's
fluent_chain_lowering.rs had native_fluent_chain_still_dispatches_through_native_methods
(new Decimal(1).plus(2).times(3).toString(), no import), asserting the exact ambient/no-import,
spelling-based dispatch #10699 itself eliminated. Bisected — it's already red on #10699's own tip
(08325f1e6), passing only on #10699's parent commit. Confirmed the same failure mode holds for
every one of the 5 names (tested Command), so there's no still-present sibling to repoint it at.
Deleted with an explanatory comment; flagged on #10699 directly
(#10699 (comment)) since it's that PR's own
regression, not this removal's.

Acceptance test: real arithmetic + instanceof + the two named defects, no compilePackages entry

Built on perrymaster (--profile perry-dev, -p perry -p perry-runtime-static -p perry-stdlib-static),
confirmed .a mtimes moved. Test project:

{ "dependencies": { "decimal.js": "^10.6.0" }, "type": "module" }
import Decimal from "decimal.js";
// 1/3, 10/4, the large multiplication that aborts on main, instanceof,
// constructor.name, sqrt, pow, toFixed, toPrecision, cmp, chained arithmetic.

No perry.compilePackages entry at all. Compile log: Compile package wildcard: expanded to 1 installed package(s) — real AOT compile from source, exactly as #10699 unblocks.

Diffed the compiled binary's output against node --experimental-strip-types (Node 26.5.1, the
pinned oracle):

1/3: 0.33333333333333333333        (both)
10/4: 2.5                          (both)
big mul: 1.2193263135650053135e+35 (both — this call ABORTS the process on main)
instanceof: true                   (both)
sqrt / pow / toFixed / toPrecision / cmp / chained: all match Node exactly

One residual mismatch, found by this acceptance test and not caused by this PR:
d.constructor.name is "" in Perry vs. "Decimal" in Node. Traced it to the real decimal.js
source (clone()-built ES5 function constructor, module.exports = Decimal, no class at all) —
isolated repros show a same-file factory-built function constructor's .name is fine, and the exact
prototype-replacement shape is fine, but the same shape imported cross-module via CJS interop loses
the function's own .name before .constructor even enters into it. Not fixed by, and not caused
by, this removal — everything else checked matches Node exactly. Filed as #10702.

Verification

  • cargo check --workspace --all-targets (excluding the cross-host UI crates per this repo's own
    exclusion list) under RUSTFLAGS="-D warnings": clean.
  • cargo test -p perry-hir --tests: 459+ lib tests + all integration binaries, 0 failures (after
    removing the pre-existing-red test above).
  • cargo test -p perry-codegen --tests: 1632 lib tests + all integration binaries incl.
    manifest_consistency, 0 failures.
  • cargo test -p perry-api-manifest --tests: 39+4+other binaries, 0 failures.
  • cargo test -p perry --test issue_10439_native_binding_import_provenance: all 5 pass, including
    decimal_default_name_reaches_real_source_under_compile_packages.
  • python3 scripts/native_result_ledger.py: passes at the new 359/310 counts.
  • python3 scripts/binding_governance.py --check: OK (39 extension crates, was 40).
  • node scripts/binding_pins.mjs --check: OK (36 pinned, was 37).
  • python3 scripts/workspace_architecture.py --check: OK.
  • cargo fmt --all -- --check: clean.
  • SKIP_COMPILE_GATES=1 ./scripts/run_lint_gates.sh: 77 of 79 passed (compile tier skipped). The
    one non-pre-existing failure (cargo fmt) was fixed in this PR; the other (Public benchmark evidence freshness) is the documented pre-existing red on every PR in this repo.
  • Real decimal.js round-trip + the two named defects + instanceof: see above.

Not run / out of scope

Umbrella-feature coupling check

bundled-decimal is referenced only by perry-stdlib's full feature list, which this PR
already updates. Checked every other feature umbrella in crates/perry-stdlib/Cargo.toml (crypto,
database, ids, etc.) — none references bundled-decimal. This removal doesn't touch any feature
bundled-lru-cache/bundled-commander need either. The three removal PRs (this one, #10685, #10686)
are independent on this axis and can merge in any order relative to each other.

Rebased onto main @ 91a566c8af (train 226, v0.5.1605) — genuine rebase, not a merge

This PR was stacked on fix/10439-native-binding-import-provenance (#10699's branch), which
squash-merged into main, so its original base commits are no longer reachable from main as such.
Identified the 2 commits genuinely unique to this branch (git log <10699-branch-tip>..<this-branch>)
and replayed only those via git rebase --onto origin/main <10699-branch-tip> <this-branch>, the same
technique #10679 (axios, stacked on #10673's branch) used.

Recomputed triple: workspace_members = 77, externalize = 28, keep = 44 (merge=1, remove=1,
review=3 unchanged; sum 77), independently reproduced by workspace_architecture.py --check --print-summary from the resolved tree — not the stale 82/32/45 this PR previously recorded (that was
against a base of 83, several trains back). Main's baseline confirmed before deriving: 78/29/44
(train 226, post-axios).

Hit the recurring false-merge trap on workspace-architecture.json for real this time — meaning
the opposite of the last several rebases: git raised genuine conflict markers (HEAD=78/29/44 vs
theirs=82/32/45, textually different enough that the 3-way merge couldn't silently collapse them).
Resolved to 77/28/44 by hand, confirmed by the script. The crate-map deletion of perry-ext-decimal
itself applied cleanly with no conflict.

A new instance of the same silent-wrong-merge class showed up on docs/api/perry.d.ts and
docs/src/api/reference.md
— both auto-merged with zero conflict markers, and the body content
was correctly stripped of decimal.js sections, but the header count lines silently kept main's stale
value (2067/132 and 3009/134 — main's exact current numbers, decimal.js not yet subtracted). Caught by
regenerating both from a freshly built perry-dev binary rather than trusting the clean merge:
corrected to 2066/131 and 2983/132.

scripts/native_result_ledger.py's EXPECTED_ROWS/EXPECTED_PROVIDERS recounted from the resolved
tree, not adjusted arithmetically.
This PR's original branch recorded 359/310 against a base of
371/322; main is now green at 376/326 (post-#10738). Running the script on the actual resolved tree
(after all other conflicts were resolved) gives the real current values: 364 rows / 314 providers
— not derivable by subtracting this PR's old delta (17/16) from main's new base, since that delta was
measured against a different tree shape. Confirmed by running the script twice: once to discover the
real row count (temporarily setting EXPECTED_ROWS and re-running to surface the real provider count),
then setting both and confirming a clean pass with no stale-provider or kind-mismatch errors.

scripts/unrooted_local_shape_baseline.json re-derived, not left at a technically-passing stale
value.
--check reported 577 < baseline 578 (an improvement, so it passed) — but per the standing
instruction to re-derive rather than leave a passing-but-stale number, ran --update-baseline to bring
the file to 577.

Also resolved: Cargo.lock (took main's side, regenerated via cargo metadata --offline, zero
markers, zero perry-ext-decimal entries), Cargo.toml version (main's, 0.5.1605),
crates/perry-api-manifest/src/entries/part_1.rs (main already lacks the uuid/jsonwebtoken rows this
PR's stale diff still carried; resolved to neither those nor decimal.js's rows),
crates/perry-stdlib/Cargo.toml's full feature list (main already lacks validation; removed
bundled-decimal from main's current list rather than reintroducing validation),
crates/perry-hir/tests/fluent_chain_lowering.rs (a comment-only conflict — main already carries
#10699's own consolidated explanation for why this test was removed, from a follow-up fix landed
directly on the shared branch tip before this PR's own base; took main's version rather than this PR's
redundant restatement of the same fact). Confirmed the file rename this branch performs
(async_decimal.rsasync_hooks.rs, since only async_hooks dispatch rows remain in that file after
decimal's rows are stripped) is intentional and that native_table/mod.rs references the new name
correctly.

None of #10750's at-cap files (perry-codegen/src/stmt/let_stmt.rs, perry-hir/src/lower/stmt_loops.rs,
perry-runtime/src/gc/tests/copying.rs, perry-runtime/…/module_keys.rs, perry/…/cjs_wrap/tests.rs,
perry-codegen/src/rooting/mod.rs) are touched by this diff — confirmed via git diff against main,
not assumed from the removal's usual shape.

Gates re-run on the rebased tree: cargo fmt --check clean, cargo check --workspace --all-targets -D warnings clean (no perry-ext-decimal, no perry-ext-axios — landed; perry-ext-dotenv still present
#10691 hasn't landed yet), check_file_size.sh OK, run_lint_gates.sh SKIP_COMPILE_GATES=1 78/79
(only the known public-baseline red), git diff --stat empty after every gate run. No re-run of the
acceptance test — nothing in train 226 changes what decimal.js/big.js/bignumber.js do; the acceptance
result above still stands.

Left as draft per instruction — this PR is a pathfinder for the two structurally identical PRs
(#10708, #10712) also stacked on the same now-squash-merged branch with the same stale counts; they are
not touched by this work.


Rebase note (2026-09-20)

Rebased onto main @ b9ba951ff861c61afb845bfbdfa574cb0fa4080e (train 239) as part of a
4-PR sequential rebase campaign together with #10795, #10677, #10680 — all four
independently rebased onto this same main SHA and pushed together.

Base-pointer check, since the brief flagged this PR's as possibly wrong: verified
directly — this branch's actual fork point (chore: release merge train 226,
91a566c8af5) is an ancestor of current main, 55 commits behind. It is not stacked on
the unmerged tip of fix/10439-native-binding-import-provenance the way #10677/#10680 were
stacked on their shared branches; #10699 (which carries #10439's fix) landed on main via
the normal merge-train process before this branch's own fork point. So this needed only a
plain git rebase origin/main, not --onto surgery — the two-step unstack described in the
campaign brief did not apply here after all. baseRefName was already main.

Conflicts were the heaviest of the four (91 total lines of conflict markers across 21
files, since this branch was 55 commits stale) but all fell into two shapes already seen on
the other three PRs: (a) main had independently removed something adjacent (uuid/qs,
fastify, lru-cache/commander, dayjs/date-fns) at the same list position this PR's
decimal.js/big.js/bignumber.js entries occupied — resolved against current main's
actual content, not either conflict side blindly; (b) generated files (docs, ledgers,
baselines, governance table) — fully regenerated from the resolved tree via their own
tools.

Two things found only by rebasing, not visible on either parent:

  1. crates/perry-hir/src/lower_patterns.rs's detect_native_instance_expr went fully
    dead
    and tripped -D warnings' unreachable_code lint. Its new-expression match arm
    used to recognize five names (Big/Decimal/BigNumber from this PR,
    LRUCache/Command from the already-landed refactor(stdlib): remove lru-cache native binding #10708/refactor(stdlib): remove commander native binding #10712) — with all five gone
    the match had zero live arms, so the two-stage let module = match … { _ => return None }; match ctx.lookup_native_module(…) {…} dance became provably unreachable.
    Simplified the arm to what it always evaluates to and rewrote the doc comment. This is
    the sequencing interaction the campaign brief warns about (each one's correct numbers depend on which land before it) showing up as a real compile warning, not just a stale
    count: neither this PR's diff nor refactor(stdlib): remove lru-cache native binding #10708/refactor(stdlib): remove commander native binding #10712's touched this exact file's other half,
    so nothing before rebase-time could see the combination.
  2. test-files/test_parity_decimal.ts + its test-parity/known_failures.json entry
    were left behind by the original PR (unlike refactor(stdlib): remove cron/exponential-backoff/moment/node-forge native bindings #10795, which deleted its own moment/cron/
    backoff fixtures as part of the same removal). Deleted both — decimal.js has no
    Perry-specific behavior left to validate, and the fixture was already skip-listed as a
    broken oracle (Node itself can't resolve decimal.js post-npm ci, parity: 2026-08-17 dark-debt audit — 93 parity + 27 compile failures unlisted after six dark weeks (90.7% aggregate) #8271) before this
    PR. Confirmed via parity_known_failures.py --audit (part of run_lint_gates.sh) that
    removing the entry doesn't orphan anything.

Verified crates/perry/tests/issue_10439_native_binding_import_provenance.rs still
passes
— its decimal_default_name_reaches_real_source_under_compile_packages test
compiles a fake decimal.js package via perry.compilePackages and asserts the real
compiled source runs, not a native-handle interception. With the native binding gone there
is nothing left to intercept, so this is now testing the same (already-true-on-main)
shape as the file's own lru_cache_.../commander_... sibling tests. Ran all three: cargo test --profile perry-dev -p perry --test issue_10439_native_binding_import_provenance
3 passed, 0 failed. (First attempt reported all three failing with an identical "runtime
library does not match this Perry compiler" error — a stale-archive artifact from building
perry-runtime-static and perry in overlapping invocations while iterating in the same
target dir, not a real regression; cargo clean -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static + a fresh combined build resolved it and
all three passed.) This crate's suite wasn't named by this PR's diff, so per the brief:
flagging explicitly that this PR's change affects a test in a suite it doesn't touch.

Recomputed triple: workspace_members=69 (decision_counts: externalize=20, keep=44,
merge=1, remove=1, review=3) — needed a full recompute; a stray "perry-ext-decimal" entry
also survived the auto-merge in the per-crate map, same shape as #10677/#10680;
native_result_ledger: 344 rows / 295 providers (reflects both the decimal.js row removal
and the async_decimal.rsasync_hooks.rs split the original PR's own commit performed —
the same file used to carry both decimal and async_hooks NativeModSig rows together);
unrooted-local-shape total: 554.

These numbers assume main is still at the stated SHA — whichever of the four PRs lands
first moves the ground under the other three's counts.

Shared-crate check: decimal.js and bignumber.js already shared perry-ext-decimal
within this PR's own scope (by design, per the PR body above); big.js was never a
separate [bindings.*] entry (routes through the same class-name recognition without its
own well-known-bindings row). No sharing with anything outside this PR's own removal set.

Gates: cargo fmt --all -- --check OK; cargo check --workspace --all-targets under
-D warnings on the default dev profile (excl. perry-ui-gtk4) — clean, 0 warnings (after
the lower_patterns.rs fix above); run_lint_gates.sh SKIP_COMPILE_GATES=1 — 78 of 79
passed (1 pre-existing, #10707, not chased); binding_governance.py --check OK;
binding_pins.mjs --check under Node 26.5.1 OK; check_file_size.sh OK. Compile tier not
run. No gap sweep run. No acceptance re-run for the package-compile behavior itself (only
the issue_10439 integration-test re-run above, which was necessary given the dead-code
finding).

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
…wering

native_fluent_chain_still_dispatches_through_native_methods asserted the
pre-fix, spelling-based, no-import native dispatch that this PR's own
detect_native_instance_expr change deliberately eliminates. With no import
at all, `new Decimal(1)` (or Command/LRUCache/Big/BigNumber) now correctly
falls through to an unresolved-global reference -- matching Node's
ReferenceError on a genuinely undefined global -- instead of silently
reaching the native handle by name. The test predates this change and was
never updated for it, so it went red on this same commit without this PR's
diff touching that file: only the sweep's `cargo test --workspace` would
have caught it, hours later and attributed to a time window rather than
this PR.

Removed with the rationale recorded inline, matching the identical
resolution three PRs stacked on this branch (#10704, #10708, #10712) each
carried independently -- landing it here so none of them has to repeat it.

crates/perry-hir/tests/fluent_chain_lowering.rs now runs 2/2; the crate's
full test suite (`cargo test -p perry-hir --tests`) is green.
proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
…wering

native_fluent_chain_still_dispatches_through_native_methods asserted the
pre-fix, spelling-based, no-import native dispatch that this PR's own
detect_native_instance_expr change deliberately eliminates. With no import
at all, `new Decimal(1)` (or Command/LRUCache/Big/BigNumber) now correctly
falls through to an unresolved-global reference -- matching Node's
ReferenceError on a genuinely undefined global -- instead of silently
reaching the native handle by name. The test predates this change and was
never updated for it, so it went red on this same commit without this PR's
diff touching that file: only the sweep's `cargo test --workspace` would
have caught it, hours later and attributed to a time window rather than
this PR.

Removed with the rationale recorded inline, matching the identical
resolution three PRs stacked on this branch (#10704, #10708, #10712) each
carried independently -- landing it here so none of them has to repeat it.

crates/perry-hir/tests/fluent_chain_lowering.rs now runs 2/2; the crate's
full test suite (`cargo test -p perry-hir --tests`) is green.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Flagging a counting problem this PR shares with its two siblings, because it will fail a required gate rather than show up in review.

All three of #10704, #10708 and #10712 record the identical transition workspace_members 83→82 and decision_counts.externalize 33→32. They cannot all be right. Each removes a different crate and they land sequentially, so from a common base of 83 they would have to read 83→82, then 82→81, then 81→80. As it stands, whichever of the three lands first leaves the other two recording a from value that no longer exists, and workspace_architecture.py --check fails on the second one.

It is already moot in any case: the chain has moved on. Main is now at 79 members / externalize 30 / keep 44 after the validator and dotenv removals, with uuid in flight. These are absolute recorded baselines, not deltas.

So at rebase time, for each of the three: recompute from the resolved tree and have workspace_architecture.py --check --print-summary independently reproduce the number. Do not derive it by arithmetic from 83, and do not copy the sibling's figure. scripts/native_result_ledger.tsv carries the same absolute-count hazard.

Two related notes:

Finally, for whoever runs the acceptance check: #10735 is live on main — require.main === module is true in every compiled CommonJS module, so any package with a CLI entry guard runs its CLI branch when merely imported. A fix is in flight. If acceptance fails in a way that looks like the package misbehaving at import time, test a dependency-free fixture that never mentions the package before attributing it to the removal.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Correction to my comment above: I gave main's baseline as 79 members / externalize 30 / keep 44. That is wrong — 79/30 is the figure after the uuid removal, not main's. Main (023dc0b653) reads 80 members / externalize 31 / keep 44.

The attribution was wrong too: I said "after validator and dotenv". Only #10690 (validator) has landed; #10691 (dotenv) is still open, and a jsonwebtoken removal landed instead.

This does not change the advice, and the advice is the point: recompute from the resolved tree at rebase time and have workspace_architecture.py --check --print-summary reproduce it — do not copy a number out of a comment, including this one. Main moved twice while I was writing these, which is exactly why any figure quoted here goes stale. The defect I flagged stands unchanged: five queued PRs record the identical 83→82 / 33→32, and at most one of them can be right.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Base pointer retargeted to main (gh pr edit 10704 --base main) — this now reports MERGEABLE with headRefOid unchanged at 700cb8e0f3.

The content rebase onto origin/main was already correct and gates were green; the CONFLICTING status came solely from the PR still pointing at fix/10439-native-binding-import-provenance, so mergeability was being computed against that stale branch. Since #10699 squash-merged, the branch still exists while appearing nowhere in main's history, which is what makes the stale pointer resolve to nonsense rather than simply erroring.

Recomputed triple is 77/28/44 against main's 78/29/44, reproduced independently by workspace_architecture.py --check --print-summary. Ledger recounted from the resolved tree to 364 rows / 314 providers — note that value is not derivable by subtracting this PR's old delta from main's new base, because the tree shape differs.

Left as draft deliberately: it went first as a pathfinder for #10708 and #10712, which are structurally identical and both still carry the stale base pointer. The full recipe is now on each of them.

perry-bot and others added 3 commits September 20, 2026 13:22
Fixes #10684 -- the removal is the fix. Native division returned "1" for
both 1/3 and 10/4, and new Decimal("123456789123456789").times("987654321987654321")
aborted the process (Multiplication overflowed in rust_decimal -- a fixed
96-bit type backing an arbitrary-precision library). instanceof and
constructor.name were also broken, the same way as lru-cache's.

Removes both copies (crates/perry-ext-decimal/ and the feature-gated
crates/perry-stdlib/src/decimal.rs), the dedicated HIR/codegen recognition
for Big/Decimal/BigNumber (they share one binding/crate with decimal.js),
and every registry row (well_known_bindings.toml, NATIVE_MODULES, the API
manifest, stdlib_features.rs, native_result_ledger, workspace-architecture.json,
Android stubs). big.js/bignumber.js go with it -- same crate, same defects.

Based on PR #10699's branch (fix/10439-native-binding-import-provenance):
without that fix, decimal.js/big.js/bignumber.js at their default import
name are unreachable regardless of perry.compilePackages, so this removal
is not independently mergeable.

# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/perry-api-manifest/src/entries.rs
#	crates/perry-api-manifest/src/entries/part_4.rs
#	crates/perry-codegen/src/lower_call/builtin.rs
#	crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
#	crates/perry-hir/src/destructuring/var_decl/native_fetch.rs
#	crates/perry-hir/src/destructuring/var_decl/native_new.rs
#	crates/perry-hir/src/js_transform/imports.rs
#	crates/perry-hir/src/lower/expr_call/static_and_instance.rs
#	crates/perry-hir/src/lower/module_decl.rs
#	crates/perry-hir/src/lower_patterns.rs
#	crates/perry-stdlib/Cargo.toml
#	crates/perry-stdlib/src/lib.rs
#	crates/perry-ui-android/src/stdlib_stubs.rs
#	crates/perry/src/commands/compile/collect_modules/feature_detect.rs
#	crates/perry/src/commands/stdlib_features.rs
#	crates/perry/well_known_bindings.toml
#	docs/api/perry.d.ts
#	docs/examples/stdlib/other/snippets.ts
#	docs/src/api/reference.md
#	docs/src/native-libraries/governance.md
#	docs/src/stdlib/other.md
#	docs/src/stdlib/overview.md
#	scripts/native_result_ledger.py
#	scripts/unrooted_local_shape_baseline.json
#	workspace-architecture.json
… orphaned decimal.js parity fixture

Removes the stray perry-ext-decimal crate entry that survived the rebase's
auto-merge in workspace-architecture.json, resyncs Cargo.lock, and
recomputes native_result_ledger EXPECTED_ROWS/PROVIDERS (344 rows, 295
providers), the unrooted-local-shape baseline, the generated
binding-governance table, and docs/api/perry.d.ts + docs/src/api/reference.md
from a fresh perry-dev build.

crates/perry-hir/src/lower_patterns.rs: detect_native_instance_expr's
new-expression arm went dead. Its match on class_name used to have five live
arms (Big/Decimal/BigNumber from this PR, LRUCache/Command from the already-
landed #10708/#10712) -- with all five gone the fallback-only match triggered
rustc's unreachable_code lint under -D warnings. Simplified the arm to what
it now always evaluates to (None after the local-class shadow check), and
rewrote the function doc comment to explain why the stub is kept rather than
deleted. This is a sequencing interaction the brief calls out explicitly:
this file wasn't touched by mysql2/pg/cron's diffs, but decimal.js landing
after commander/lru-cache emptied a match neither PR could see on its own.

test-files/test_parity_decimal.ts + its test-parity/known_failures.json
entry: the original PR left this fixture behind (unlike #10795, which
deleted its own moment/cron/backoff test files as part of the same removal).
The fixture is now double-dead: decimal.js has no Perry-specific behavior
left to validate, and the file was already skip-listed as a broken oracle
(node itself can't resolve decimal.js post-npm-ci, #8271) before this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants