Skip to content

Commit 7837497

Browse files
Allow to wrap arbitrary Ruby Object by JS's RbValue
When passing a non-interoperated Ruby Object like `Object` to JS method from Ruby world, the object has to be JS object. This patch provides a way to wrap a Ruby object by `RbValue`, which is a Ruby object representation in JS world.
1 parent 6ef5a1c commit 7837497

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

ext/js/bindgen/rb-js-abi-host.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ global-this: function() -> js-abi-value
77
int-to-js-number: function(value: s32) -> js-abi-value
88
string-to-js-string: function(value: string) -> js-abi-value
99
bool-to-js-bool: function(value: bool) -> js-abi-value
10+
rb-object-to-js-rb-value: function(raw-rb-abi-value: u32) -> js-abi-value
1011

1112
js-value-to-string: function(value: js-abi-value) -> string
1213

ext/js/js-core.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ static VALUE _rb_js_is_js(VALUE _, VALUE obj) {
102102
* Try to convert the given object to a JS::Object using <code>to_js</code>
103103
* method. Returns <code>nil</code> if the object cannot be converted.
104104
*
105-
* p JS.try_convert(1) # => 1
106-
* p JS.try_convert("foo") # => "foo"
107-
* p JS.try_convert(Object.new) # => nil
105+
* p JS.try_convert(1) # => JS::Object
106+
* p JS.try_convert("foo") # => JS::Object
107+
* p JS.try_convert(Object.new) # => nil
108+
* p JS.try_convert(JS::Object.wrap(Object.new)) # => JS::Object
108109
*/
109110
VALUE _rb_js_try_convert(VALUE klass, VALUE obj) {
110111
if (_rb_js_is_js(klass, obj)) {
@@ -242,6 +243,17 @@ static VALUE _rb_js_export_to_js(VALUE obj) {
242243
return Qnil;
243244
}
244245

246+
247+
/*
248+
* call-seq:
249+
* JS::Object.wrap(obj) -> JS::Object
250+
*
251+
* Returns +obj+ wrapped by JS class RbValue.
252+
*/
253+
static VALUE _rb_js_obj_wrap(VALUE obj, VALUE wrapping) {
254+
return jsvalue_s_new(rb_js_abi_host_rb_object_to_js_rb_value((uint32_t)wrapping));
255+
}
256+
245257
/*
246258
* call-seq:
247259
* to_js -> JS::Object
@@ -306,6 +318,7 @@ void Init_js() {
306318
rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1);
307319
rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0);
308320
rb_define_method(rb_cJS_Object, "inspect", _rb_js_obj_inspect, 0);
321+
rb_define_singleton_method(rb_cJS_Object, "wrap", _rb_js_obj_wrap, 1);
309322

310323
rb_define_method(rb_cInteger, "to_js", _rb_js_integer_to_js, 0);
311324
rb_define_method(rb_cString, "to_js", _rb_js_string_to_js, 0);

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export class RubyVM {
7272
return Function(code)();
7373
},
7474
isJs: (value) => {
75-
return value == null || !(value instanceof RbValue);
75+
// Just for compatibility with the old JS API
76+
return true;
7677
},
7778
globalThis: () => {
7879
if (typeof globalThis !== "undefined") {
@@ -93,6 +94,10 @@ export class RubyVM {
9394
boolToJsBool: (value) => {
9495
return value;
9596
},
97+
rbObjectToJsRbValue: (rawRbAbiValue) => {
98+
const abiValue = new (RbAbi.RbAbiValue as any)(rawRbAbiValue, this.guest);
99+
return new RbValue(abiValue, this, this.privateObject());
100+
},
96101
jsValueToString: (value) => {
97102
return value.toString();
98103
},
@@ -172,7 +177,11 @@ export class RubyVM {
172177
*
173178
*/
174179
eval(code: string): RbValue {
175-
return evalRbCode(this, { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }, code);
180+
return evalRbCode(this, this.privateObject(), code);
181+
}
182+
183+
private privateObject(): RubyVMPrivate {
184+
return { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }
176185
}
177186
}
178187

packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,22 @@ describe("Manipulation of JS from Ruby", () => {
147147
`);
148148
expect(result.toJS()).toEqual(expected);
149149
});
150+
151+
test("Wrap arbitrary Ruby object to JS::Object", async () => {
152+
const vm = await initRubyVM();
153+
const results = vm.eval(`
154+
require "js"
155+
intrinsics = JS.eval(<<-JS)
156+
return {
157+
identity(v) { return v }
158+
}
159+
JS
160+
o1 = Object.new
161+
o1_clone = intrinsics.call(:identity, JS::Object.wrap(o1))
162+
[o1.object_id, o1_clone.call("call", "object_id").inspect]
163+
`);
164+
const o1 = results.call("at", vm.eval("0"));
165+
const o1Clone = results.call("at", vm.eval("1"));
166+
expect(o1.toString()).toEqual(o1Clone.toString());
167+
});
150168
});

0 commit comments

Comments
 (0)