Skip to content
1 change: 0 additions & 1 deletion mordant-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"field_valid_only_when:src/runtime/shell/builtin/rm.rs" = 1
"parallel_vecs:src/runtime/api/html_rewriter.rs" = 1
"parallel_vecs:src/runtime/bake/dev_server/incremental_graph.rs" = 1
"reimplemented_helper:src/runtime/api/bun/Terminal.rs" = 1
"reimplemented_helper:src/runtime/hw_exports.rs" = 1
"same_match_twice:src/runtime/api/bun/h2_frame_parser.rs" = 1
"same_match_twice:src/runtime/bake/production.rs" = 2
Expand Down
11 changes: 10 additions & 1 deletion src/jsc/JSValue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,8 @@ impl JSValue {

/// `JSValue.getOptional` — loose, coercing property fetch.
/// Absent / `undefined` / `null` → `None`; anything else is run through
/// [`coerce`](Self::coerce) (ToNumber for integer `T`). Distinct from
/// [`coerce`](Self::coerce) (ToNumber for integer `T`, returned as-is for
/// `T = JSValue`). Distinct from
/// [`get_optional_int`], which validates the property is already an
/// in-range integer and throws otherwise.
pub fn get_optional<T: CoerceTo>(
Expand Down Expand Up @@ -1900,6 +1901,14 @@ impl PutKey for &str {
pub trait CoerceTo: Sized {
fn coerce_from(v: JSValue, global: &JSGlobalObject) -> JsResult<Self>;
}
/// Identity, so `get_optional::<JSValue>` is "the property unless it is
/// absent, `undefined` or `null`" (Zig's `getOptional(.., JSValue)`).
impl CoerceTo for JSValue {
#[inline]
fn coerce_from(v: JSValue, _global: &JSGlobalObject) -> JsResult<JSValue> {
Ok(v)
}
}
impl CoerceTo for i32 {
fn coerce_from(v: JSValue, global: &JSGlobalObject) -> JsResult<i32> {
// Fast-path numbers via
Expand Down
34 changes: 5 additions & 29 deletions src/runtime/api/bun/Terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,30 +224,6 @@ impl Default for Options {
}
}

// Local extension shims for typed optional property reads. Typed
// `getOptional` is not yet a single inherent generic on `bun_jsc::JSValue`;
// these wrap `get` + the per-type coercion. `withAsyncContextIfNeeded` is the
// inherent `JSValue::with_async_context_if_needed` in `bun_jsc` — call sites
// resolve to that directly, no shim here.
trait JSValueTerminalExt {
fn get_optional_i32(self, global: &JSGlobalObject, name: &[u8]) -> JsResult<Option<i32>>;
fn get_optional_value(self, global: &JSGlobalObject, name: &[u8]) -> JsResult<Option<JSValue>>;
}
impl JSValueTerminalExt for JSValue {
fn get_optional_i32(self, global: &JSGlobalObject, name: &[u8]) -> JsResult<Option<i32>> {
match self.get(global, name)? {
Some(v) if !v.is_undefined_or_null() => Ok(Some(v.coerce::<i32>(global)?)),
_ => Ok(None),
}
}
fn get_optional_value(self, global: &JSGlobalObject, name: &[u8]) -> JsResult<Option<JSValue>> {
match self.get(global, name)? {
Some(v) if !v.is_undefined_or_null() => Ok(Some(v)),
_ => Ok(None),
}
}
}

impl Options {
/// Maximum length for terminal name (e.g., "xterm-256color")
/// Longest known terminfo names are ~23 chars; 128 allows for custom terminals
Expand All @@ -261,13 +237,13 @@ impl Options {
let mut options = Options::default();
// errdefer options.deinit() — handled by Drop on early return.

if let Some(n) = js_options.get_optional_i32(global_object, b"cols")? {
if let Some(n) = js_options.get_optional::<i32>(global_object, b"cols")? {
if n > 0 && n <= 65535 {
options.cols = u16::try_from(n).expect("int cast");
}
}

if let Some(n) = js_options.get_optional_i32(global_object, b"rows")? {
if let Some(n) = js_options.get_optional::<i32>(global_object, b"rows")? {
if n > 0 && n <= 65535 {
options.rows = u16::try_from(n).expect("int cast");
}
Expand All @@ -284,19 +260,19 @@ impl Options {
options.term_name = slice;
}

if let Some(v) = js_options.get_optional_value(global_object, b"data")? {
if let Some(v) = js_options.get_optional::<JSValue>(global_object, b"data")? {
if v.is_cell() && v.is_callable() {
options.data_callback = Some(v.with_async_context_if_needed(global_object));
}
}

if let Some(v) = js_options.get_optional_value(global_object, b"exit")? {
if let Some(v) = js_options.get_optional::<JSValue>(global_object, b"exit")? {
if v.is_cell() && v.is_callable() {
options.exit_callback = Some(v.with_async_context_if_needed(global_object));
}
}

if let Some(v) = js_options.get_optional_value(global_object, b"drain")? {
if let Some(v) = js_options.get_optional::<JSValue>(global_object, b"drain")? {
if v.is_cell() && v.is_callable() {
options.drain_callback = Some(v.with_async_context_if_needed(global_object));
}
Expand Down
29 changes: 9 additions & 20 deletions src/runtime/bake/bake_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,6 @@ fn get_boolean_loose(
}
}

/// `JSValue.getOptional(JSValue, ..)` — local shim: filters undefined/null.
fn get_optional_value(
target: JSValue,
global: &JSGlobalObject,
property: &[u8],
) -> JsResult<Option<JSValue>> {
match target.get(global, property)? {
Some(v) if !v.is_undefined_or_null() => Ok(Some(v)),
_ => Ok(None),
}
}

/// `JSValue.getFunction` — local shim until `bun_jsc` grows it.
fn get_function(
target: JSValue,
Expand Down Expand Up @@ -204,14 +192,14 @@ impl UserOptions {
);
}

if let Some(js_options) = get_optional_value(config, global, b"bundlerOptions")? {
if let Some(server_options) = get_optional_value(js_options, global, b"server")? {
if let Some(js_options) = config.get_optional::<JSValue>(global, b"bundlerOptions")? {
if let Some(server_options) = js_options.get_optional::<JSValue>(global, b"server")? {
bundler_options.server = BuildConfigSubset::from_js(global, server_options)?;
}
if let Some(client_options) = get_optional_value(js_options, global, b"client")? {
if let Some(client_options) = js_options.get_optional::<JSValue>(global, b"client")? {
bundler_options.client = BuildConfigSubset::from_js(global, client_options)?;
}
if let Some(ssr_options) = get_optional_value(js_options, global, b"ssr")? {
if let Some(ssr_options) = js_options.get_optional::<JSValue>(global, b"ssr")? {
bundler_options.ssr = BuildConfigSubset::from_js(global, ssr_options)?;
}
}
Expand Down Expand Up @@ -409,7 +397,7 @@ impl BuildConfigSubset {
let mut options = BuildConfigSubset::default();

'brk: {
let Some(val) = get_optional_value(js_options, global, b"sourcemap")? else {
let Some(val) = js_options.get_optional::<JSValue>(global, b"sourcemap")? else {
break 'brk;
};
if let Some(sourcemap) = source_map_mode_from_js(global, val)? {
Expand All @@ -426,7 +414,8 @@ impl BuildConfigSubset {
}

'brk: {
let Some(minify_options) = get_optional_value(js_options, global, b"minify")? else {
let Some(minify_options) = js_options.get_optional::<JSValue>(global, b"minify")?
else {
break 'brk;
};
if minify_options.is_boolean() && minify_options.as_boolean() {
Expand Down Expand Up @@ -837,7 +826,7 @@ impl Framework {
Some(ServerComponents {
separate_ssr_graph: 'brk: {
// Intentionally not using a truthiness check
let prop = match get_optional_value(sc, global, b"separateSSRGraph")? {
let prop = match sc.get_optional::<JSValue>(global, b"separateSSRGraph")? {
Some(p) => p,
None => {
return Err(global.throw_invalid_arguments(format_args!(
Expand Down Expand Up @@ -1094,7 +1083,7 @@ impl Framework {
built_in_modules,
};

if let Some(plugin_array) = get_optional_value(opts, global, b"plugins")? {
if let Some(plugin_array) = opts.get_optional::<JSValue>(global, b"plugins")? {
bundler_options.parse_plugin_array(plugin_array, global)?;
}

Expand Down
Loading