resolver: avoid unconditional deep clone of exports Entry in visit() - #31036
Conversation
The EObject arm of the exports-tree builder pushed a deep clone of every (key, value) into map_data and then conditionally moved the original into expansion_keys. Because Entry/EntryData is recursively owned in Rust (Box<[u8]>/Vec<MapEntry>), the unconditional clone walks the entire subtree for every key. Swap the order: clone into expansion_keys only when the key is a pattern (ends in '/' or contains '*'), and always move into map_data. map_data push order is unchanged; expansion_keys is sorted immediately after so its push order is irrelevant. The Zig sibling does a shallow struct copy here (borrowed slices), so no deep duplication was ever intended.
|
Updated 5:42 AM PT - May 19th, 2026
❌ @Jarred-Sumner, your commit 830515c has 3 failures in
🧪 To try this PR locally: bunx bun-pr 31036That installs a local version of the PR into your bun-31036 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThis PR optimizes ownership handling in JSON object visitation within the package resolver. The change moves ChangesJSON object property ownership
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
LGTM — pure reordering so the clone only happens in the conditional expansion_keys branch.
Extended reasoning...
Overview
This PR touches a single function in src/resolver/package_json.rs — the EObject arm of Visitor::visit() that parses package.json exports maps. Previously, every iteration cloned key and value (a recursive Entry tree) into map_data first, then conditionally moved the originals into expansion_keys. The change swaps the order: the conditional expansion_keys.push now does the clone, and the unconditional map_data.push consumes the originals by move. Net effect: clones only occur for keys ending in / or containing *, instead of for every key.
Security risks
None. No new inputs, no parsing changes, no control-flow changes — just which side of the two pushes pays the clone cost.
Level of scrutiny
Low. This is a mechanical refactor with identical observable behavior. Both map_data and expansion_keys end up with the same MapEntry contents in the same order as before. The only thing that could go wrong is if something between the old and new push positions depended on map_data already containing the current entry — I checked, and the only map_data indexed access (map_data[i - 1] at line 1835) happens earlier in the loop body and references the previous iteration's entry, which is unaffected.
Other factors
The key borrow used in the strings::ends_with / contains_char check still works because key hasn't been moved yet at that point. key_range is Copy. No CODEOWNERS entry covers this path. The change matches the PR description's stated intent (avoid ~193 recursive clones on the rolldown bundle) exactly.
package.json exports-map
visit()(EObject arm) deep-cloned eachEntry(recursiveBox<[Entry]>tree) before deciding whether to keep it. Borrow first, clone only when retained. ~193 recursive clones / ~8 KB on the rolldown 20K bundle.Found via runtime clone tracer (claude/clone-tracer).