diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba7e01aa1..087b226904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Remove `Stdlib_Char` module for now. https://github.com/rescript-lang/rescript/pull/7367 - Convert internal JavaScript codebase into ESM, ReScript package itself is now ESM (`"type": "module"`). https://github.com/rescript-lang/rescript/pull/6899 +- Add built-in support for the JavaScript `in` operator. https://github.com/rescript-lang/rescript/pull/7342 # 12.0.0-alpha.10 diff --git a/compiler/core/j.ml b/compiler/core/j.ml index c578f54703..f7d67e47e4 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -86,6 +86,7 @@ and expression_desc = [typeof] is an operator *) | Typeof of expression + | In of expression * expression (* prop in obj *) | Js_not of expression (* !v *) (* TODO: Add some primitives so that [js inliner] can do a better job *) | Seq of expression * expression diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index 385da84843..7d666cd486 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -111,6 +111,7 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) = && Ext_list.for_all strings no_side_effect && Ext_list.for_all values no_side_effect | Js_not e -> no_side_effect e + | In (prop, obj) -> no_side_effect prop && no_side_effect obj | Cond (a, b, c) -> no_side_effect a && no_side_effect b && no_side_effect c | Call ({expression_desc = Str {txt = "Array.isArray"}}, [e], _) -> no_side_effect e @@ -227,7 +228,7 @@ let rec eq_expression ({expression_desc = x0} : J.expression) eq_expression_list ls0 ls1 && flag0 = flag1 && eq_expression tag0 tag1 | _ -> false) | Length _ | Is_null_or_undefined _ | String_append _ | Typeof _ | Js_not _ - | Cond _ | FlatCall _ | New _ | Fun _ | Raw_js_code _ | Array _ + | In _ | Cond _ | FlatCall _ | New _ | Fun _ | Raw_js_code _ | Array _ | Caml_block_tag _ | Object _ | Tagged_template _ | Await _ -> false | Spread _ -> false diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 6980da2538..bc174e7dd5 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -166,8 +166,8 @@ let rec exp_need_paren ?(arrow = false) (e : J.expression) = | Length _ | Call _ | Caml_block_tag _ | Seq _ | Static_index _ | Cond _ | Bin _ | Is_null_or_undefined _ | String_index _ | Array_index _ | String_append _ | Var _ | Undefined _ | Null | Str _ | Array _ - | Caml_block _ | FlatCall _ | Typeof _ | Number _ | Js_not _ | Bool _ | New _ - -> + | Caml_block _ | FlatCall _ | Typeof _ | Number _ | Js_not _ | In _ | Bool _ + | New _ -> false | Await _ -> false | Spread _ -> false @@ -677,6 +677,11 @@ and expression_desc cxt ~(level : int) f x : cxt = P.cond_paren_group f (level > 13) (fun _ -> P.string f "!"; expression ~level:13 cxt f e) + | In (prop, obj) -> + P.cond_paren_group f (level > 12) (fun _ -> + let cxt = expression ~level:0 cxt f prop in + P.string f " in "; + expression ~level:0 cxt f obj) | Typeof e -> P.string f "typeof"; P.space f; diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 7ab4cf29b6..e2e63885cf 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -1128,6 +1128,9 @@ let or_ ?comment (e1 : t) (e2 : t) = | Some e -> e | None -> {expression_desc = Bin (Or, e1, e2); comment}) +let in_ (prop : t) (obj : t) : t = + {expression_desc = In (prop, obj); comment = None} + let not (e : t) : t = match e.expression_desc with | Number (Int {i; _}) -> bool (i = 0l) diff --git a/compiler/core/js_exp_make.mli b/compiler/core/js_exp_make.mli index 778845bdba..6fa1296273 100644 --- a/compiler/core/js_exp_make.mli +++ b/compiler/core/js_exp_make.mli @@ -356,6 +356,8 @@ val and_ : ?comment:string -> t -> t -> t val or_ : ?comment:string -> t -> t -> t +val in_ : t -> t -> t + (** we don't expose a general interface, since a general interface is generally not safe *) val dummy_obj : ?comment:string -> Lam_tag_info.t -> t diff --git a/compiler/core/js_fold.ml b/compiler/core/js_fold.ml index c8d50669f2..71109c9966 100644 --- a/compiler/core/js_fold.ml +++ b/compiler/core/js_fold.ml @@ -103,6 +103,10 @@ class fold = | Js_not _x0 -> let _self = _self#expression _x0 in _self + | In (_x0, _x1) -> + let _self = _self#expression _x0 in + let _self = _self#expression _x1 in + _self | Seq (_x0, _x1) -> let _self = _self#expression _x0 in let _self = _self#expression _x1 in diff --git a/compiler/core/js_record_fold.ml b/compiler/core/js_record_fold.ml index 2f4ade7a61..1756daaee6 100644 --- a/compiler/core/js_record_fold.ml +++ b/compiler/core/js_record_fold.ml @@ -109,6 +109,10 @@ let expression_desc : 'a. ('a, expression_desc) fn = | Js_not _x0 -> let st = _self.expression _self st _x0 in st + | In (_x0, _x1) -> + let st = _self.expression _self st _x0 in + let st = _self.expression _self st _x1 in + st | Seq (_x0, _x1) -> let st = _self.expression _self st _x0 in let st = _self.expression _self st _x1 in diff --git a/compiler/core/js_record_iter.ml b/compiler/core/js_record_iter.ml index 6d30efef1d..c43f41ff1b 100644 --- a/compiler/core/js_record_iter.ml +++ b/compiler/core/js_record_iter.ml @@ -91,6 +91,9 @@ let expression_desc : expression_desc fn = | Bool _ -> () | Typeof _x0 -> _self.expression _self _x0 | Js_not _x0 -> _self.expression _self _x0 + | In (_x0, _x1) -> + _self.expression _self _x0; + _self.expression _self _x1 | Seq (_x0, _x1) -> _self.expression _self _x0; _self.expression _self _x1 diff --git a/compiler/core/js_record_map.ml b/compiler/core/js_record_map.ml index 0a9ba771b4..8c63435eaa 100644 --- a/compiler/core/js_record_map.ml +++ b/compiler/core/js_record_map.ml @@ -109,6 +109,10 @@ let expression_desc : expression_desc fn = | Js_not _x0 -> let _x0 = _self.expression _self _x0 in Js_not _x0 + | In (_x0, _x1) -> + let _x0 = _self.expression _self _x0 in + let _x1 = _self.expression _self _x1 in + In (_x0, _x1) | Seq (_x0, _x1) -> let _x0 = _self.expression _self _x0 in let _x1 = _self.expression _self _x1 in diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index 93bcd496ee..793d83b4ad 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -78,7 +78,7 @@ let rec no_side_effects (lam : Lam.t) : bool = (* list primitives *) | Pmakelist (* dict primitives *) - | Pmakedict + | Pmakedict | Pdict_has (* Test if the argument is a block or an immediate integer *) | Pisint | Pis_poly_var_block (* Test if the (integer) argument is outside an interval *) diff --git a/compiler/core/lam_compile_primitive.ml b/compiler/core/lam_compile_primitive.ml index ca99989f2b..aac979d926 100644 --- a/compiler/core/lam_compile_primitive.ml +++ b/compiler/core/lam_compile_primitive.ml @@ -574,6 +574,13 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) Some (Js_op.Lit txt, expr) | _ -> None)) | _ -> assert false) + | Pdict_has -> ( + match args with + | [obj; prop] -> E.in_ prop obj + | _ -> + Location.raise_errorf ~loc + "Invalid external \"%%dict_has\" type signature. Expected to have two \ + arguments.") | Parraysetu -> ( match args with (* wrong*) diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 8694474e37..3f252011ef 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -314,6 +314,7 @@ let lam_prim ~primitive:(p : Lambda.primitive) ~args loc : Lam.t = | Parraysets -> prim ~primitive:Parraysets ~args loc | Pmakelist _mutable_flag (*FIXME*) -> prim ~primitive:Pmakelist ~args loc | Pmakedict -> prim ~primitive:Pmakedict ~args loc + | Pdict_has -> prim ~primitive:Pdict_has ~args loc | Pawait -> prim ~primitive:Pawait ~args loc | Pimport -> prim ~primitive:Pimport ~args loc | Pinit_mod -> ( diff --git a/compiler/core/lam_primitive.ml b/compiler/core/lam_primitive.ml index d3759d9c6c..e28c652cd9 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -137,6 +137,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict + | Pdict_has (* promise *) | Pawait (* etc or deprecated *) @@ -215,7 +216,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) = (* List primitives *) | Pmakelist (* dict primitives *) - | Pmakedict + | Pmakedict | Pdict_has (* promise *) | Pawait (* etc *) diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index f130334732..460ef392c4 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -132,6 +132,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict + | Pdict_has (* promise *) | Pawait (* etc or deprecated *) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index a9efe966fe..d82956cc93 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -194,6 +194,7 @@ let primitive ppf (prim : Lam_primitive.t) = | Pmakearray -> fprintf ppf "makearray" | Pmakelist -> fprintf ppf "makelist" | Pmakedict -> fprintf ppf "makedict" + | Pdict_has -> fprintf ppf "dict.has" | Parrayrefu -> fprintf ppf "array.unsafe_get" | Parraysetu -> fprintf ppf "array.unsafe_set" | Parrayrefs -> fprintf ppf "array.get" diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index d2f02f9a91..26aa8a8c74 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -271,6 +271,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict + | Pdict_has (* promise *) | Pawait (* module *) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index 49035249df..9e1c9b9d7c 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -238,6 +238,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict + | Pdict_has (* promise *) | Pawait (* modules *) diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 68967026fd..f0ad4698bb 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -229,6 +229,7 @@ let primitive ppf = function | Pmakelist Mutable -> fprintf ppf "makelist" | Pmakelist Immutable -> fprintf ppf "makelist_imm" | Pmakedict -> fprintf ppf "makedict" + | Pdict_has -> fprintf ppf "dict.has" | Pisint -> fprintf ppf "isint" | Pisout -> fprintf ppf "isout" | Pisnullable -> fprintf ppf "isnullable" diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index db9624b841..4cdeb34aa5 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -346,6 +346,7 @@ let primitives_table = ("%array_unsafe_set", Parraysetu); (* dict primitives *) ("%makedict", Pmakedict); + ("%dict_has", Pdict_has); (* promise *) ("%await", Pawait); (* module *) diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index 9a472ed5c4..51a70b3144 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -22,13 +22,10 @@ function mapValues(dict, f) { return target; } -let has = ((dict, key) => key in dict); - export { $$delete$1 as $$delete, forEach, forEachWithKey, mapValues, - has, } /* No side effect */ diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index 28788f4ec1..ecb9c90844 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -22,11 +22,8 @@ function mapValues(dict, f) { return target; } -let has = ((dict, key) => key in dict); - exports.$$delete = $$delete$1; exports.forEach = forEach; exports.forEachWithKey = forEachWithKey; exports.mapValues = mapValues; -exports.has = has; /* No side effect */ diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 2101382fa0..7604962689 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -41,6 +41,6 @@ let mapValues = (dict, f) => { target } -let has: (dict<'a>, string) => bool = %raw(`(dict, key) => key in dict`) +external has: (dict<'a>, string) => bool = "%dict_has" external ignore: dict<'a> => unit = "%ignore" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index 329bce3698..604c5d4de9 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -246,17 +246,20 @@ let mapValues: (dict<'a>, 'a => 'b) => dict<'b> /** `has(dictionary, "key")` returns true if the "key" is present in the dictionary. +Be aware that it uses the JavaScript `in` operator under the hood. + ## Examples ```rescript let dict = dict{"key1": Some(1), "key2": None} -dict->Dict.has("key1") // true -dict->Dict.has("key2") // true -dict->Dict.has("key3") // false +dict->Dict.has("key1")->assertEqual(true) +dict->Dict.has("key2")->assertEqual(true) +dict->Dict.has("key3")->assertEqual(false) +dict->Dict.has("toString")->assertEqual(true) ``` */ -let has: (dict<'a>, string) => bool +external has: (dict<'a>, string) => bool = "%dict_has" /** `ignore(dict)` ignores the provided dict and returns unit. diff --git a/tests/tests/src/DictTests.mjs b/tests/tests/src/DictTests.mjs deleted file mode 100644 index b57778e88f..0000000000 --- a/tests/tests/src/DictTests.mjs +++ /dev/null @@ -1,100 +0,0 @@ -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Stdlib_Dict from "rescript/lib/es6/Stdlib_Dict.js"; - -let someString = "hello"; - -let createdDict = { - name: "hello", - age: "what", - more: "stuff", - otherStr: someString -}; - -let intDict = { - one: 1, - two: 2, - three: 3 -}; - -function inferDictByPattern(dict) { - if (dict.one === 1 && dict.three === 3 && dict.four === 4) { - dict["five"] = 5; - return; - } - if (dict.two !== 1) { - console.log("not one"); - } else { - console.log("two"); - } -} - -function constrainedAsDict(dict) { - if (dict.one !== 1) { - console.log("not one"); - } else { - console.log("one"); - } -} - -let PatternMatching = { - inferDictByPattern: inferDictByPattern, - constrainedAsDict: constrainedAsDict -}; - -let dict = { - key1: 1, - key2: undefined -}; - -if (Stdlib_Dict.has(dict, "key1") !== true) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 43, - 2 - ], - Error: new Error() - }; -} - -if (Stdlib_Dict.has(dict, "key2") !== true) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 44, - 2 - ], - Error: new Error() - }; -} - -if (Stdlib_Dict.has(dict, "key3") !== false) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 45, - 2 - ], - Error: new Error() - }; -} - -let DictHas = { - dict: dict -}; - -let three = 3; - -export { - someString, - createdDict, - three, - intDict, - PatternMatching, - DictHas, -} -/* Not a pure module */ diff --git a/tests/tests/src/DictTests.res b/tests/tests/src/DictTests.res deleted file mode 100644 index feb62ade9f..0000000000 --- a/tests/tests/src/DictTests.res +++ /dev/null @@ -1,46 +0,0 @@ -let someString = "hello" - -let createdDict = dict{ - "name": "hello", - "age": "what", - "more": "stuff", - "otherStr": someString, -} - -let three = 3 - -let intDict = dict{ - "one": 1, - "two": 2, - "three": three, -} - -module PatternMatching = { - let inferDictByPattern = dict => - switch dict { - | dict{"one": 1, "three": 3, "four": 4} => - // Make sure that the dict is of correct type - dict->Js.Dict.set("five", 5) - | dict{"two": 1} => Js.log("two") - | _ => Js.log("not one") - } - - let constrainedAsDict = (dict: dict) => - switch dict { - | dict{"one": 1} => - let _d: dict = dict - Js.log("one") - | _ => Js.log("not one") - } -} - -module DictHas = { - let dict = dict{ - "key1": Some(1), - "key2": None, - } - - assert(dict->Dict.has("key1") === true) - assert(dict->Dict.has("key2") === true) - assert(dict->Dict.has("key3") === false) -} diff --git a/tests/tests/src/core/Core_DictTests.mjs b/tests/tests/src/core/Core_DictTests.mjs index 8efe14aada..b91a957032 100644 --- a/tests/tests/src/core/Core_DictTests.mjs +++ b/tests/tests/src/core/Core_DictTests.mjs @@ -5,10 +5,50 @@ import * as Primitive_object from "rescript/lib/es6/Primitive_object.js"; let eq = Primitive_object.equal; +let someString = "hello"; + +let createdDict = { + name: "hello", + age: "what", + more: "stuff", + otherStr: someString +}; + +let intDict = { + one: 1, + two: 2, + three: 3 +}; + +function inferDictByPattern(dict) { + if (dict.one === 1 && dict.three === 3 && dict.four === 4) { + dict["five"] = 5; + return; + } + if (dict.two !== 1) { + console.log("not one"); + } else { + console.log("two"); + } +} + +function constrainedAsDict(dict) { + if (dict.one !== 1) { + console.log("not one"); + } else { + console.log("one"); + } +} + +let PatternMatching = { + inferDictByPattern: inferDictByPattern, + constrainedAsDict: constrainedAsDict +}; + Test.run([ [ "Core_DictTests.res", - 3, + 39, 20, 26 ], @@ -18,7 +58,7 @@ Test.run([ Test.run([ [ "Core_DictTests.res", - 5, + 41, 20, 31 ], @@ -31,7 +71,7 @@ Test.run([ Test.run([ [ "Core_DictTests.res", - 8, + 44, 13, 35 ], @@ -44,14 +84,81 @@ Test.run([ Test.run([ [ "Core_DictTests.res", - 14, + 50, 13, 34 ], "getUnsafe - missing" ], ({})["foo"], eq, undefined); +let dict = { + key1: false, + key2: undefined +}; + +Test.run([ + [ + "Core_DictTests.res", + 62, + 22, + 38 + ], + "has - existing" +], "key1" in dict, eq, true); + +Test.run([ + [ + "Core_DictTests.res", + 63, + 22, + 43 + ], + "has - existing None" +], "key2" in dict, eq, true); + +Test.run([ + [ + "Core_DictTests.res", + 64, + 22, + 37 + ], + "has - missing" +], "key3" in dict, eq, false); + +Test.run([ + [ + "Core_DictTests.res", + 65, + 22, + 39 + ], + "has - prototype" +], "toString" in dict, eq, true); + +Test.run([ + [ + "Core_DictTests.res", + 67, + 15, + 51 + ], + "has - parantesis in generated code" +], typeof ("key1" in dict), eq, "boolean"); + +let Has = { + dict: dict +}; + +let three = 3; + export { eq, + someString, + createdDict, + three, + intDict, + PatternMatching, + Has, } /* Not a pure module */ diff --git a/tests/tests/src/core/Core_DictTests.res b/tests/tests/src/core/Core_DictTests.res index 968dbab9ad..bc642621c8 100644 --- a/tests/tests/src/core/Core_DictTests.res +++ b/tests/tests/src/core/Core_DictTests.res @@ -1,5 +1,41 @@ let eq = (a, b) => a == b +let someString = "hello" + +let createdDict = dict{ + "name": "hello", + "age": "what", + "more": "stuff", + "otherStr": someString, +} + +let three = 3 + +let intDict = dict{ + "one": 1, + "two": 2, + "three": three, +} + +module PatternMatching = { + let inferDictByPattern = dict => + switch dict { + | dict{"one": 1, "three": 3, "four": 4} => + // Make sure that the dict is of correct type + dict->Js.Dict.set("five", 5) + | dict{"two": 1} => Js.log("two") + | _ => Js.log("not one") + } + + let constrainedAsDict = (dict: dict) => + switch dict { + | dict{"one": 1} => + let _d: dict = dict + Js.log("one") + | _ => Js.log("not one") + } +} + Test.run(__POS_OF__("make"), Dict.make(), eq, %raw(`{}`)) Test.run(__POS_OF__("fromArray"), Dict.fromArray([("foo", "bar")]), eq, %raw(`{foo: "bar"}`)) @@ -16,3 +52,21 @@ Test.run( eq, %raw(`undefined`), ) + +module Has = { + let dict = dict{ + "key1": Some(false), + "key2": None, + } + + Test.run(__POS_OF__("has - existing"), dict->Dict.has("key1"), eq, true) + Test.run(__POS_OF__("has - existing None"), dict->Dict.has("key2"), eq, true) + Test.run(__POS_OF__("has - missing"), dict->Dict.has("key3"), eq, false) + Test.run(__POS_OF__("has - prototype"), dict->Dict.has("toString"), eq, true) + Test.run( + __POS_OF__("has - parantesis in generated code"), + typeof(dict->Dict.has("key1")), + eq, + #boolean, + ) +} diff --git a/tests/tests/src/core/Core_TestSuite.mjs b/tests/tests/src/core/Core_TestSuite.mjs index 5cdea30649..a378afbfc5 100644 --- a/tests/tests/src/core/Core_TestSuite.mjs +++ b/tests/tests/src/core/Core_TestSuite.mjs @@ -72,6 +72,18 @@ let decodeJsonTest = Core_JsonTests.decodeJsonTest; let shouldHandleNullableValues = Core_NullableTests.shouldHandleNullableValues; +let someString = Core_DictTests.someString; + +let createdDict = Core_DictTests.createdDict; + +let three = Core_DictTests.three; + +let intDict = Core_DictTests.intDict; + +let PatternMatching = Core_DictTests.PatternMatching; + +let Has = Core_DictTests.Has; + let eq = Core_IteratorTests.eq; let iterator = Core_IteratorTests.iterator; @@ -112,6 +124,12 @@ export { o, decodeJsonTest, shouldHandleNullableValues, + someString, + createdDict, + three, + intDict, + PatternMatching, + Has, eq, iterator, syncResult,