Skip to content

jsc: let get_optional::<JSValue> replace the Terminal and bake get_optional_value shims - #39150

Closed
robobun wants to merge 7 commits into
mainfrom
farm/0dfb4353/dedupe-get-optional-value
Closed

jsc: let get_optional::<JSValue> replace the Terminal and bake get_optional_value shims#39150
robobun wants to merge 7 commits into
mainfrom
farm/0dfb4353/dedupe-get-optional-value

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • mordant reimplemented_helper on src/runtime/api/bun/Terminal.rs: <JSValue as JSValueTerminalExt>::get_optional_value and bake_body::get_optional_value have the same signature and body (get the property, treat undefined/null as absent, return the raw JSValue). A fix to one would miss the other.
  • The shared version already exists: JSValue::get_optional<T: CoerceTo> in src/jsc/JSValue.rs does exactly this and then runs the value through coerce::<T>(). Both shims describe themselves as stand-ins for it. It was unusable here only because CoerceTo is implemented for i32 and i64 and nothing else.

Fix

  • impl CoerceTo for JSValue as the identity, so get_optional::<JSValue>(global, name) returns the property unless it is absent, undefined or null. Same filter as both shims (get() already maps absent and undefined to None; get_optional adds the null check).
  • bake_body.rs: the free function is deleted and its 8 call sites use get_optional::<JSValue>.
  • Terminal.rs: the JSValueTerminalExt trait is deleted. get_optional_value goes the same way; its other method, get_optional_i32, was line for line get_optional::<i32> (filter, then coerce::<i32>), so those 2 call sites switch too instead of leaving a one-method trait behind.
  • No behavior change. The other bake shims (get_optional_slice, get_function, ...) intentionally differ from the bun_jsc methods of the same name (they coerce or filter where bun_jsc throws) and are left alone.
  • mordant-baseline.toml: regenerating with bun run rust:mordant:baseline on this branch produces current main's file minus the reimplemented_helper:src/runtime/api/bun/Terminal.rs line, so that is the only baseline change here.
  • Tests pin the contract the rewired sites now get from the shared helper. They pass before and after this change (it is a refactor, so there is nothing for them to fail on); what they guard is the helper's null and coercion semantics at these sites, which previously had no direct coverage:
    • test/bake/app-options.test.ts (new; no existing file covers app option parsing): null for bundlerOptions, its server/client/ssr, client.sourcemap, client.minify and framework.plugins is accepted as "not set", separateSSRGraph: null reports the key as missing while 0 is rejected as a non-boolean and false is consumed as a value, and sourcemap: 0 is still rejected. The sourcemap: null and separateSSRGraph: null rows are the ones that would change if the helper stopped filtering null.
    • test/js/bun/terminal/terminal-spawn.test.ts: cols/rows of null leave the 80x24 default and numeric strings are coerced (the get_optional::<i32> path), checked through the size the subprocess sees.
  • Also run on a debug build: the rest of test/js/bun/terminal/, test/bake/dev/production.test.ts (bundlerOptions with real values), test/bake/dev/plugins.test.ts and test/bake/dev/bundle.test.ts (separateSSRGraph true/false), all passing. cargo fmt --all -- --check clean.

Background

  • JSValue::get returns Ok(None) when the property is missing or undefined, otherwise Ok(Some(value)); null comes back as a value. Option parsing code usually wants null treated like missing, which is what these helpers add.
  • CoerceTo is the dispatch trait behind JSValue::coerce::<T>(); get_optional<T> is "get, drop undefined/null, then coerce::<T>". With the identity impl, T = JSValue is the no-coercion case, mirroring the Zig getOptional(.., JSValue) these shims were ported from.
  • mordant is the dylint lint pack CI runs as a ratchet: mordant-baseline.toml holds per-(lint, file) counts of pre-existing findings and a PR fails only if it adds one, so fixing a site means regenerating the file to delete its entry.

no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/terminal/terminal-spawn.test.ts

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fd9743b3-7c72-45b7-a2e7-c4c5c80fb7c5

📥 Commits

Reviewing files that changed from the base of the PR and between c6a26e8 and 9060c66.

📒 Files selected for processing (2)
  • mordant-baseline.toml
  • test/js/bun/terminal/terminal-spawn.test.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e4e93b3e-74b4-46dd-9969-c2c718bec7f5

📥 Commits

Reviewing files that changed from the base of the PR and between bcab5ed and c6a26e8.

📒 Files selected for processing (6)
  • mordant-baseline.toml
  • src/jsc/JSValue.rs
  • src/runtime/api/bun/Terminal.rs
  • src/runtime/bake/bake_body.rs
  • test/bake/app-options.test.ts
  • test/js/bun/terminal/terminal-spawn.test.ts
💤 Files with no reviewable changes (1)
  • mordant-baseline.toml

Walkthrough

The change adds identity coercion for JSValue and replaces local optional-property helpers in terminal and bake option parsing. Tests cover null handling, validation, default terminal dimensions, and numeric-string coercion.

Changes

Typed optional access

Layer / File(s) Summary
JSValue identity coercion
src/jsc/JSValue.rs
get_optional now supports identity conversion for T = JSValue.
Terminal option parsing and coverage
src/runtime/api/bun/Terminal.rs, test/js/bun/terminal/terminal-spawn.test.ts, mordant-baseline.toml
Terminal parsing uses generic optional access for dimensions and callbacks. Tests cover null defaults and numeric-string dimensions.
Bake option parsing and validation
src/runtime/bake/bake_body.rs, test/bake/app-options.test.ts
Bake parsing uses typed optional access for bundler, build, server-components, and plugin options. Tests cover null and invalid values.

Suggested reviewers: dylan-conway, jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: replacing Terminal and bake optional-value shims with the shared helper.
Description check ✅ Passed The description explains the problem, implementation, behavior preservation, tests, formatting checks, and deferred platform-specific verification.

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

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:07 PM PT - Aug 15th, 2026

🔄 @robobun, the build for your commit 9060c663 (Build #98729) was cancelled — waiting for the next build...

…tional_value shims

Terminal.rs and bake_body.rs each carried a private get_optional_value
(get the property, treat undefined/null as absent, hand back the raw
JSValue). bun_jsc already has the shared version of this as
JSValue::get_optional<T: CoerceTo>, but CoerceTo was only implemented
for i32 and i64, so neither site could use it for the untyped case.

Implement CoerceTo for JSValue as the identity and switch both files to
get_optional::<JSValue>. Terminal's get_optional_i32 was the same code as
get_optional::<i32>, so the whole JSValueTerminalExt trait goes away.

No behavior change. mordant-baseline.toml loses the reimplemented_helper
entry for Terminal.rs, which is what regenerating it produces now.
@robobun
robobun force-pushed the farm/0dfb4353/dedupe-get-optional-value branch from 20b8090 to e95c234 Compare August 15, 2026 17:24

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical dedup collapsing two local get_optional_value shims onto the shared JSValue::get_optional::<T> via an identity CoerceTo for JSValue impl.

What was reviewed:

  • Confirmed get_optional<T> (JSValue.rs:1532) is get → drop undefined/nullT::coerce_from, and the new CoerceTo for JSValue returns Ok(v), so get_optional::<JSValue> is byte-equivalent to both deleted shims.
  • Confirmed get_optional_i32 was already exactly get_optional::<i32> (same filter, same coerce::<i32> dispatch), so the two Terminal cols/rows sites are unchanged in behavior.
  • The mordant-baseline entry removal only tightens the ratchet; the other bake shims (get_optional_slice, get_boolean_*, get_function) are correctly left alone since they differ from the bun_jsc versions.
Extended reasoning...

Overview

This PR removes two duplicate helpers — the JSValueTerminalExt extension trait in Terminal.rs (with get_optional_value/get_optional_i32) and the free get_optional_value in bake_body.rs — by adding an identity impl CoerceTo for JSValue in src/jsc/JSValue.rs. That makes the existing generic JSValue::get_optional::<T> usable with T = JSValue (returning the property unless absent/undefined/null), which is exactly what both shims did. 5 call sites in Terminal.rs and 8 in bake_body.rs switch to the shared method; one mordant-baseline.toml reimplemented_helper entry is dropped.

Security risks

None. This is pure Rust-side option-parsing plumbing with no change to what values are accepted, how they're validated, or what side effects run. The identity coercion cannot throw or run user JS.

Level of scrutiny

Low. The change is a textbook "move the guard into the shared helper" dedup. I read get_optional at JSValue.rs:1532–1544 and coerce at :834 to confirm the composed behavior is identical to the deleted match arms (get already maps absent/undefinedNone; get_optional adds the is_undefined_or_null() filter; coerce::<JSValue> is now Ok(v)). The i32 path was already the same body as CoerceTo for i32::coerce_from composed with the same filter.

Other factors

The PR intentionally leaves the remaining bake shims (get_optional_slice, get_boolean_strict/loose, get_function) in place because they differ semantically from the bun_jsc methods of the same name — I spot-checked get_optional_slice (calls to_slice, coercing) vs bun_jsc's stricter variant and agree. The mordant baseline change is deletion-only, which can never loosen the ratchet. The author reports the terminal and bake test suites pass on a debug build. No prior human review comments to address.

These are the call sites that now read through JSValue::get_optional:
null must keep meaning "not set", false must still be a value, and
Terminal cols/rows must still be coerced rather than type-checked.
Comment thread test/js/bun/terminal/terminal-spawn.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the ConPTY early-resolve concern from the previous pass is addressed in c6a26e8 (the helper now matches and resolves on the full [\d+x\d+] token).

What was reviewed:

  • get_optional<T> body vs. the deleted get_optional_value/get_optional_i32 shims — same get → null-filter → coerce sequence, so all 13 rewired sites are behavior-identical.
  • The identity impl CoerceTo for JSValue — no prior callers of coerce::<JSValue>, so no unintended side effects.
  • New tests: app-options.test.ts is hermetic (subprocess, port: 0, tempDir); sizeSeenBySubprocess now keys on a child-unique token like the neighboring tests.
Extended reasoning...

Overview

This PR removes two duplicate local shims (JSValueTerminalExt::{get_optional_value,get_optional_i32} in Terminal.rs and the free get_optional_value in bake_body.rs) by adding an identity impl CoerceTo for JSValue so the existing shared JSValue::get_optional::<T> covers the "return the raw value unless absent/undefined/null" case. Thirteen call sites are rewired to the shared helper, one mordant reimplemented_helper baseline line is dropped, and two tests pin the null/coercion contract at the rewired sites.

I confirmed get_optional at src/jsc/JSValue.rs:1532 does exactly what both shims did: self.get() (already maps absent/undefined → None), then is_undefined_or_null() filter, then coerce::<T>(). With CoerceTo for JSValue as Ok(v), the T = JSValue path is byte-for-byte equivalent to the deleted shims, and T = i32 was already what get_optional_i32 open-coded.

Security risks

None. Option-parsing plumbing only; no auth, crypto, filesystem paths, or untrusted-input validation semantics changed. The identity coercion cannot throw and the null filter is unchanged.

Level of scrutiny

Low-to-medium. This is a mechanical dedup driven by a mordant lint hit; the shared helper already existed and was already used for i32/i64. The only new code is an 8-line trait impl that returns its input. The rewired call sites in bake_body.rs and Terminal.rs are option-parsing paths with existing test coverage plus the new pinning tests.

Other factors

  • Prior review addressed: my earlier inline comment flagged sizeSeenBySubprocess resolving on bare ] (which ConPTY OSC sequences also contain). Commit c6a26e8 changed the resolve gate to output.match(/\[\d+x\d+\]/) and resolves with the matched token directly — the concern no longer applies and the thread is marked resolved.
  • Test quality: the new app-options.test.ts follows harness conventions (tempDir, bunEnv, bunExe, subprocess with piped stdout/stderr drained via Promise.all, port: 0, server.stop(true)). The terminal tests use await using for cleanup and await the actual observable condition.
  • No CODEOWNERS gate the touched paths.
  • The PR description explicitly enumerates which sibling bake shims (get_optional_slice, get_function, etc.) were left alone and why — they intentionally differ from the bun_jsc methods of the same name.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready to merge from my side, blocked only on CI bookkeeping.

  • Diff against main: the CoerceTo for JSValue impl, the two shim removals, the two test additions, and exactly one line removed from mordant-baseline.toml (reimplemented_helper:src/runtime/api/bun/Terminal.rs). The commits after the approval were test-only review follow-ups; all review threads are resolved.
  • CI: both full runs of this branch (https://buildkite.com/bun/bun/builds/98483 on c6a26e8 and https://buildkite.com/bun/bun/builds/98729 on 9060c66) passed 177 of 179 jobs and were canceled while the last two darwin aarch64 - test-bun shards were still queued, so the Buildkite status reads as failed with no test failure behind it. The files Buildkite flagged as flaky (bun-patch, bun-lock, bun-install-registry, malformed-integrity-base64) all passed on retry and are install tests unrelated to this change.
  • The two new test files also pass locally on a Linux debug build and on Windows with the current canary.
  • I have not pushed a retrigger, since that queues another full build behind the darwin backlog. Retrying just the two canceled darwin jobs on build 98729 would complete the run; otherwise this needs a re-approval and a merge on the 177/179 result.

alii
alii previously approved these changes Aug 15, 2026
@alii
alii enabled auto-merge (squash) August 15, 2026 20:08

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the ConPTY resolve-gate concern from my earlier review is addressed in the current sizeSeenBySubprocess (matches the full /\[\d+x\d+\]/ token and resolves with it).

What was reviewed:

  • get_optional<T> at src/jsc/JSValue.rs:1532 — with the identity CoerceTo for JSValue, it is byte-for-byte equivalent to both deleted get_optional_value shims; get_optional::<i32> was already identical to the deleted get_optional_i32.
  • All 8 rewired bake_body.rs sites and 5 Terminal.rs sites — same filter, same coercion, no behavior change.
  • New tests: app-options.test.ts pins null-as-absent for the rewired bake keys; terminal-spawn.test.ts covers null defaults and string coercion for cols/rows.
Extended reasoning...

Overview

Mechanical dedup that resolves a mordant reimplemented_helper finding. Adds an identity impl CoerceTo for JSValue (8 lines in src/jsc/JSValue.rs) so the existing JSValue::get_optional<T> becomes usable with T = JSValue, then deletes the two local shims that duplicated it: the JSValueTerminalExt trait in Terminal.rs (both get_optional_value and get_optional_i32) and the free get_optional_value in bake_body.rs. 13 call sites are rewired 1:1. One line removed from mordant-baseline.toml. Two test additions pin the null/coercion contract at the rewired sites.

Behavioral equivalence

I read get_optional at JSValue.rs:1532-1544: it does get() → return None on absent/undefined → return None on is_undefined_or_null()coerce::<T>(). With CoerceTo for JSValue returning Ok(v), this is line-for-line the deleted get_optional_value bodies (match self.get(...)? { Some(v) if !v.is_undefined_or_null() => Ok(Some(v)), _ => Ok(None) }). The deleted get_optional_i32 was already identical to get_optional::<i32> (same filter + coerce::<i32>). Adding a new CoerceTo impl cannot change any existing call site — it only makes T = JSValue compile where it previously would not.

Prior review follow-up

My earlier inline comment flagged that sizeSeenBySubprocess resolved on a bare ], which ConPTY OSC sequences also contain. The current diff gates on output.match(/\[\d+x\d+\]/) and resolves with seen[0], so the promise cannot settle before the child's payload arrives. That thread is resolved and the fix matches what I recommended.

Security risks

None. Option-parsing helper consolidation; no new inputs accepted, no validation loosened, no auth/crypto/permission code touched.

Level of scrutiny

Low-to-moderate. The Rust change is a pure refactor with a trivially-correct identity impl and mechanical call-site rewires; the deleted code and its replacement are provably equivalent by reading the shared helper. Test additions follow harness conventions (tempDir, bunEnv, drain pipes concurrently, port: 0, await using). CI on the pre-merge commit had 177/179 jobs pass with the remaining two canceled (not failed).

Other factors

No outstanding reviewer comments. The PR description explicitly scopes out the other bake shims (get_optional_slice, get_function) as intentionally different from their bun_jsc namesakes, which I confirmed (bun_jsc's get_optional_slice throws on non-string; bake's coerces).

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the earlier ConPTY ]-matching concern was addressed in c6a26e8, and the remaining inline note is a diagnosability nit that matches the file's existing convention.

Extended reasoning...

Overview

This PR adds an identity impl CoerceTo for JSValue in src/jsc/JSValue.rs, then deletes two local shims that reimplemented the same filter-then-return pattern: the JSValueTerminalExt trait (get_optional_value + get_optional_i32) in Terminal.rs and the free get_optional_value in bake_body.rs. All 13 call sites are rewired to get_optional::<JSValue> / get_optional::<i32>. One line is removed from mordant-baseline.toml. Two test additions pin the null/coercion contract at the rewired sites.

Behavior preservation

I compared the shared get_optional<T> body (src/jsc/JSValue.rs:1532-1544) against both deleted shims. get_optional::<JSValue> is byte-for-byte equivalent to get_optional_value (get → filter is_undefined_or_null → return); get_optional::<i32> is byte-for-byte equivalent to get_optional_i32 (same filter, then coerce::<i32>). The identity CoerceTo impl is Ok(v), so no coercion side effect is introduced. No behavior change.

Security risks

None. Option-parsing dedup only; no new user-controlled paths, no auth/crypto/permission surface.

Level of scrutiny

Low-to-moderate: a mechanical helper consolidation driven by a mordant lint. The prior review's ConPTY early-resolve issue was fixed (the helper now matches the full [\d+x\d+] token and resolves with it). The current run's only finding is a nit that sizeSeenBySubprocess doesn't wire exitreject; this matches the neighboring tests in the same file and the failure mode is still a harness-timeout failure, not a silent pass.

Other factors

The new test/bake/app-options.test.ts covers the null-as-absent contract at every rewired bake site including the separateSSRGraph boolean-strict path, and the terminal test covers both get_optional::<i32> sites. Other bake shims (get_optional_slice, get_function, get_boolean_*) are correctly left in place since they differ from the bun_jsc methods of the same name.

Comment thread test/js/bun/terminal/terminal-spawn.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the refactor is mechanically behavior-preserving. The inline nit about the exit() callback in sizeSeenBySubprocess being unreachable on the pre-created-terminal path is real but non-blocking (worst case remains a harness-timeout failure, and it matches the file's dominant pattern); fine to address as a follow-up or drop.

Extended reasoning...

Overview

This PR adds impl CoerceTo for JSValue (identity) so JSValue::get_optional::<JSValue> becomes usable, then deletes two duplicate local shims — JSValueTerminalExt in Terminal.rs (both get_optional_value and get_optional_i32) and the free get_optional_value in bake_body.rs — rewiring their 5 + 8 call sites onto the shared inherent method. The mordant baseline drops the corresponding reimplemented_helper entry. Two test additions pin the null/coercion contract at the rewired sites.

Behavior equivalence

I compared the removed shims against get_optional<T> at src/jsc/JSValue.rs:1532: both are get() → filter is_undefined_or_null → coerce::<T>, and with the new identity CoerceTo for JSValue the T = JSValue case returns the value unchanged, while T = i32 runs the same coerce::<i32> the old get_optional_i32 called. No semantic drift. The other bake shims (get_optional_slice, get_boolean_*, get_function) are correctly left alone since they diverge from the shared helpers.

Security risks

None. This is option-parsing plumbing with no auth/crypto/untrusted-input surface change; the same values reach the same downstream validators.

Level of scrutiny

Low-to-medium: a mechanical dedup that a lint (reimplemented_helper) flagged, with the shared helper's body verified byte-equivalent. The added tests (app-options.test.ts covering null/0/false for the rewired bake keys; two terminal size tests for null and string coercion on cols/rows) directly exercise the filter/coerce edges.

Other factors

I previously flagged two test issues on this PR; both were addressed. The remaining inline nit points out that 4ca91e1's exit() reject is unreachable on the existing_terminal (non-OWNS_TERMINAL) path, so the diagnosability improvement doesn't actually land. That's accurate but explicitly nit-level: the failure scenario (a bunExe() one-liner crashing on a real PTY) is implausible, several neighboring tests in this file share the same hang-on-failure shape, and the worst case is still a test failure via harness timeout — never a silent pass. It does not affect the refactor's correctness or the tests' ability to catch a regression in get_optional's null/coercion semantics.

Comment thread test/js/bun/terminal/terminal-spawn.test.ts Outdated
…n fire

A terminal created up front and passed to Bun.spawn stays open after the
child exits, and `await using` suppresses the callback on close, so the
exit -> reject wiring never ran. With the inline option the subprocess
owns the terminal and the reader reaches EOF once the child is gone.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — 9060c66 switches the helper to an inline terminal: {...} option so OWNS_TERMINAL is set and the exit callback is now reachable, addressing the last open comment.

What was reviewed:

  • Confirmed get_optional<T>'s body (get → filter undefined/null → coerce::<T>) is byte-for-byte equivalent to both removed shims; the new CoerceTo for JSValue identity impl has no other consumers.
  • Inline-terminal spawn path still routes through TerminalOptions::parse_from_js (js_bun_spawn_bindings.rs:822), so the rewired cols/rows reads are still exercised by the new tests.
  • Happy-path ordering: data fires before exit on both POSIX (drain_and_close_slave_fd reads before closing) and Windows (reader stays ref'd until conhost EOF), so the reject-on-exit is a no-op on a settled promise, not a spurious failure.
Extended reasoning...

Overview

Mechanical dedupe of two local get_optional_value shims (Terminal.rs, bake_body.rs) into the shared JSValue::get_optional::<T> by adding an identity impl CoerceTo for JSValue. Eight call sites in bake_body.rs and five in Terminal.rs are rewired; the JSValueTerminalExt trait and the bake free function are deleted. One line drops from mordant-baseline.toml. Two test additions pin the null/coercion contract at the rewired sites.

Security risks

None. Option-parsing refactor with no change to what values are accepted or how they're validated; the new trait impl is a pure identity.

Level of scrutiny

Low-medium. The Rust change is a mechanical substitution — I diffed get_optional's body against both removed shims and they're equivalent (get → drop undefined/null → coerce, where coerce for JSValue is now identity). The only real review surface was the new test helper, which went through three iterations of feedback (ConPTY ] matching, exit-wired reject, inline vs. pre-created terminal for OWNS_TERMINAL); all are now resolved in 9060c66.

Other factors

All three prior inline comments from this review are addressed and the threads resolved. The bug hunter found nothing new on this revision. I verified the inline-terminal spawn path still calls Options::parse_from_js so the test still covers the changed code, and traced the happy-path callback ordering to confirm the exit-reject on an already-resolved promise is harmless. CoerceTo has no other implementors that could be affected.

@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #39488, which consolidates the open Bake PRs from this period into one branch. It carries this PR's change and its test, reworked where needed (see the commit in #39488 that names this PR). Closing this one in its favor.

@robobun robobun closed this Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants