Skip to content

Commit 28b2c5b

Browse files
Merge pull request #28 from ruby/pr-e47671d83d6d3c970cc5f6f57ba175d4f2e4d6fb
Add == / eql? / strictly_eql? methods
2 parents e9c306e + 839b137 commit 28b2c5b

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import-js-value-from-host: function() -> js-abi-value
1616

1717
js-value-typeof: function(value: js-abi-value) -> string
1818

19+
js-value-equal: function(lhs: js-abi-value, rhs: js-abi-value) -> bool
20+
js-value-strictly-equal: function(lhs: js-abi-value, rhs: js-abi-value) -> bool
21+
1922
reflect-apply: function(target: js-abi-value, this-argument: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value
2023
reflect-construct: function(target: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value
2124
reflect-delete-property: function(target: js-abi-value, property-key: string) -> bool

ext/js/js-core.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,45 @@ static VALUE _rb_js_obj_aset(VALUE obj, VALUE key, VALUE val) {
186186
return val;
187187
}
188188

189+
/*
190+
* call-seq:
191+
* js_value.strictly_eql?(other) -> boolean
192+
*
193+
* Performs "===" comparison, a.k.a the "Strict Equality Comparison"
194+
* algorithm defined in the ECMAScript.
195+
* https://262.ecma-international.org/11.0/#sec-strict-equality-comparison
196+
*/
197+
static VALUE _rb_js_obj_strictly_eql(VALUE obj, VALUE other) {
198+
struct jsvalue *lhs = check_jsvalue(obj);
199+
struct jsvalue *rhs = check_jsvalue(other);
200+
bool result = rb_js_abi_host_js_value_strictly_equal(lhs->abi, rhs->abi);
201+
return RBOOL(result);
202+
}
203+
204+
/*
205+
* call-seq:
206+
* js_value.==(other) -> boolean
207+
* js_value.eql?(other) -> boolean
208+
*
209+
* Performs "==" comparison, a.k.a the "Abstract Equality Comparison"
210+
* algorithm defined in the ECMAScript.
211+
* https://262.ecma-international.org/11.0/#sec-abstract-equality-comparison
212+
*/
213+
static VALUE _rb_js_obj_eql(VALUE obj, VALUE other) {
214+
struct jsvalue *lhs = check_jsvalue(obj);
215+
struct jsvalue *rhs = check_jsvalue(other);
216+
bool result = rb_js_abi_host_js_value_equal(lhs->abi, rhs->abi);
217+
return RBOOL(result);
218+
}
219+
220+
/*
221+
* :nodoc: all
222+
*/
223+
static VALUE _rb_js_obj_hash(VALUE obj) {
224+
// TODO(katei): Track the JS object id in JS side as Pyodide and Swift JavaScriptKit do.
225+
return Qnil;
226+
}
227+
189228
/*
190229
* call-seq:
191230
* js_value.call(name, *args) -> JS::Object
@@ -337,6 +376,10 @@ void Init_js() {
337376
rb_define_alloc_func(rb_cJS_Object, jsvalue_s_allocate);
338377
rb_define_method(rb_cJS_Object, "[]", _rb_js_obj_aref, 1);
339378
rb_define_method(rb_cJS_Object, "[]=", _rb_js_obj_aset, 2);
379+
rb_define_method(rb_cJS_Object, "strictly_eql?", _rb_js_obj_strictly_eql, 1);
380+
rb_define_method(rb_cJS_Object, "eql?", _rb_js_obj_eql, 1);
381+
rb_define_method(rb_cJS_Object, "==", _rb_js_obj_eql, 1);
382+
rb_define_method(rb_cJS_Object, "hash", _rb_js_obj_hash, 0);
340383
rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1);
341384
rb_define_method(rb_cJS_Object, "typeof", _rb_js_obj_typeof, 0);
342385
rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ export class RubyVM {
123123
jsValueTypeof(value) {
124124
return typeof value;
125125
},
126+
jsValueEqual(lhs, rhs) {
127+
return lhs == rhs;
128+
},
129+
jsValueStrictlyEqual(lhs, rhs) {
130+
return lhs === rhs;
131+
},
126132
reflectApply: function (target, thisArgument, args) {
127133
return Reflect.apply(target as any, thisArgument, args);
128134
},

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,33 @@ describe("Manipulation of JS from Ruby", () => {
3636
expect(vm.eval(code).toString()).toBe(String(props.result));
3737
});
3838

39+
test.each([
40+
{ lhs: `24`, rhs: `24`, result: true },
41+
{ lhs: `null`, rhs: `null`, result: true },
42+
{ lhs: `undefined`, rhs: `undefined`, result: true },
43+
{ lhs: `"str"`, rhs: `"str"`, result: true },
44+
{ lhs: `48`, rhs: `24`, result: false },
45+
{ lhs: `NaN`, rhs: `NaN`, result: false },
46+
])("JS::Object#== (%s)", async (props) => {
47+
const vm = await initRubyVM();
48+
const methodResult = `require "js"; JS.eval('return ${props.lhs}').eql?(JS.eval('return ${props.rhs}'))`;
49+
expect(vm.eval(methodResult).toString()).toBe(String(props.result));
50+
51+
const operatorResult = `require "js"; JS.eval('return ${props.lhs}') == JS.eval('return ${props.rhs}')`;
52+
expect(vm.eval(operatorResult).toString()).toBe(String(props.result));
53+
});
54+
55+
test.each([
56+
{ lhs: `24`, rhs: `24`, result: true },
57+
{ lhs: `null`, rhs: `null`, result: true },
58+
{ lhs: `undefined`, rhs: `undefined`, result: true },
59+
{ lhs: `new String("str")`, rhs: `"str"`, result: false },
60+
])("JS::Object#strictly_eql? (%s)", async (props) => {
61+
const vm = await initRubyVM();
62+
const result = `require "js"; JS.eval('return ${props.lhs}').strictly_eql?(JS.eval('return ${props.rhs}'))`;
63+
expect(vm.eval(result).toString()).toBe(String(props.result));
64+
});
65+
3966
test.each([
4067
{ expr: "JS.global[:Object]", result: Object },
4168
{ expr: "JS.global[:Object][:keys]", result: Object.keys },
@@ -180,4 +207,21 @@ describe("Manipulation of JS from Ruby", () => {
180207
const o1Clone = results.call("at", vm.eval("1"));
181208
expect(o1.toString()).toEqual(o1Clone.toString());
182209
});
210+
211+
test("Guard null", async () => {
212+
const vm = await initRubyVM();
213+
const result = vm.eval(`
214+
require "js"
215+
intrinsics = JS.eval(<<-JS)
216+
return {
217+
returnNull(v) { return null },
218+
returnUndef(v) { return undefined },
219+
}
220+
JS
221+
js_null = JS.eval("return null")
222+
o1 = intrinsics.call(:returnNull)
223+
o1 == js_null
224+
`);
225+
expect(result.toString()).toEqual("true");
226+
});
183227
});

0 commit comments

Comments
 (0)