From 358f37296dd439fa56cd14dcabc654516af25ef5 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Fri, 14 Mar 2025 12:05:04 +0400 Subject: [PATCH 1/6] Add support for in operator --- compiler/core/j.ml | 1 + compiler/core/js_analyzer.ml | 3 ++- compiler/core/js_dump.ml | 9 +++++-- compiler/core/js_exp_make.ml | 3 +++ compiler/core/js_exp_make.mli | 2 ++ compiler/core/js_fold.ml | 4 ++++ compiler/core/js_record_fold.ml | 4 ++++ compiler/core/js_record_iter.ml | 3 +++ compiler/core/js_record_map.ml | 4 ++++ compiler/core/lam_analysis.ml | 2 +- compiler/core/lam_compile_primitive.ml | 7 ++++++ compiler/core/lam_convert.ml | 1 + compiler/core/lam_primitive.ml | 3 ++- compiler/core/lam_primitive.mli | 1 + compiler/core/lam_print.ml | 1 + compiler/ml/lambda.ml | 1 + compiler/ml/lambda.mli | 1 + compiler/ml/printlambda.ml | 1 + compiler/ml/translcore.ml | 1 + lib/es6/Stdlib_Dict.js | 3 --- lib/js/Stdlib_Dict.js | 3 --- runtime/Stdlib_Dict.res | 2 +- runtime/Stdlib_Dict.resi | 3 ++- tests/tests/src/DictTests.mjs | 33 ++++++++++++++++++++++---- tests/tests/src/DictTests.res | 8 ++++--- 25 files changed, 83 insertions(+), 21 deletions(-) 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..89cfd5b02b 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 | Phasin (* 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..a67ca118f1 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) + | Phasin -> ( + match args with + | [obj; prop] -> E.in_ prop obj + | _ -> + Location.raise_errorf ~loc + "Invalid external \"%%has_in\" 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..3ad973dba8 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 + | Phasin -> prim ~primitive:Phasin ~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..2958f2e364 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -137,6 +137,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict + | Phasin (* promise *) | Pawait (* etc or deprecated *) @@ -215,7 +216,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) = (* List primitives *) | Pmakelist (* dict primitives *) - | Pmakedict + | Pmakedict | Phasin (* promise *) | Pawait (* etc *) diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index f130334732..992ba197bd 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -132,6 +132,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict + | Phasin (* promise *) | Pawait (* etc or deprecated *) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index a9efe966fe..59a43edfc3 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" + | Phasin -> fprintf ppf "has_in" | 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..63d643739a 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 + | Phasin (* promise *) | Pawait (* module *) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index 49035249df..f7ba2196d7 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 + | Phasin (* promise *) | Pawait (* modules *) diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 68967026fd..5af6f65992 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" + | Phasin -> fprintf ppf "has_in" | 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..b803b9f89b 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); + ("%has_in", Phasin); (* 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..584d493a37 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 = "%has_in" external ignore: dict<'a> => unit = "%ignore" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index 329bce3698..3ca2de53cf 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -254,9 +254,10 @@ 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("__proto__") // true, since it uses in operator under the hood ``` */ -let has: (dict<'a>, string) => bool +external has: (dict<'a>, string) => bool = "%has_in" /** `ignore(dict)` ignores the provided dict and returns unit. diff --git a/tests/tests/src/DictTests.mjs b/tests/tests/src/DictTests.mjs index b57778e88f..dd0cf2b7b7 100644 --- a/tests/tests/src/DictTests.mjs +++ b/tests/tests/src/DictTests.mjs @@ -1,6 +1,5 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -import * as Stdlib_Dict from "rescript/lib/es6/Stdlib_Dict.js"; let someString = "hello"; @@ -43,11 +42,11 @@ let PatternMatching = { }; let dict = { - key1: 1, + key1: false, key2: undefined }; -if (Stdlib_Dict.has(dict, "key1") !== true) { +if (!("key1" in dict)) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -59,7 +58,7 @@ if (Stdlib_Dict.has(dict, "key1") !== true) { }; } -if (Stdlib_Dict.has(dict, "key2") !== true) { +if (!("key2" in dict)) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -71,7 +70,7 @@ if (Stdlib_Dict.has(dict, "key2") !== true) { }; } -if (Stdlib_Dict.has(dict, "key3") !== false) { +if ("key3" in dict !== false) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -83,6 +82,30 @@ if (Stdlib_Dict.has(dict, "key3") !== false) { }; } +if (!("__proto__" in dict)) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "DictTests.res", + 46, + 2 + ], + Error: new Error() + }; +} + +if (typeof ("key1" in dict) !== "boolean") { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "DictTests.res", + 47, + 2 + ], + Error: new Error() + }; +} + let DictHas = { dict: dict }; diff --git a/tests/tests/src/DictTests.res b/tests/tests/src/DictTests.res index feb62ade9f..df49b3206b 100644 --- a/tests/tests/src/DictTests.res +++ b/tests/tests/src/DictTests.res @@ -36,11 +36,13 @@ module PatternMatching = { module DictHas = { let dict = dict{ - "key1": Some(1), + "key1": Some(false), "key2": None, } - assert(dict->Dict.has("key1") === true) - assert(dict->Dict.has("key2") === true) + assert(dict->Dict.has("key1")) + assert(dict->Dict.has("key2")) assert(dict->Dict.has("key3") === false) + assert(dict->Dict.has("__proto__")) + assert(typeof(dict->Dict.has("key1")) === #boolean) } From f8963e504dc2f809c39f0b2b5375263f19ec8d93 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Fri, 14 Mar 2025 12:19:00 +0400 Subject: [PATCH 2/6] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9facbc69e..e9118fe6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Fix Pervasive.max using boolean comparison for floats. https://github.com/rescript-lang/rescript/pull/7333 - Experimental: Support nested/inline record types - records defined inside of other records, without needing explicit separate type definitions. https://github.com/rescript-lang/rescript/pull/7241 - Add unified exponentiation (`**`) operator for numeric types using ES7 `**`. https://github.com/rescript-lang/rescript-compiler/pull/7153 +- Add built-in support for the JavaScript `in` operator. https://github.com/rescript-lang/rescript/pull/7342 #### :boom: Breaking Change From 04fd8e05c0dcc85c4b70bc96b44d1107e17c2959 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sun, 16 Mar 2025 13:59:43 +0400 Subject: [PATCH 3/6] Fallback to Object.hasOwn --- compiler/core/js_exp_make.ml | 22 ++++++++++++++++++++++ compiler/core/js_exp_make.mli | 2 ++ compiler/core/lam_analysis.ml | 2 +- compiler/core/lam_compile_primitive.ml | 6 +++--- compiler/core/lam_convert.ml | 2 +- compiler/core/lam_primitive.ml | 4 ++-- compiler/core/lam_primitive.mli | 2 +- compiler/core/lam_print.ml | 2 +- compiler/ml/lambda.ml | 2 +- compiler/ml/lambda.mli | 2 +- compiler/ml/printlambda.ml | 2 +- compiler/ml/translcore.ml | 2 +- runtime/Stdlib_Dict.res | 2 +- runtime/Stdlib_Dict.resi | 12 +++++++----- tests/tests/src/DictTests.mjs | 24 ++++++++++++++++++------ tests/tests/src/DictTests.res | 9 ++++++++- 16 files changed, 71 insertions(+), 26 deletions(-) diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index e2e63885cf..b59ad48637 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -1131,6 +1131,28 @@ let or_ ?comment (e1 : t) (e2 : t) = let in_ (prop : t) (obj : t) : t = {expression_desc = In (prop, obj); comment = None} +let has (obj : t) (prop : t) : t = + let non_prototype_prop = + match prop.expression_desc with + | Str + { + txt = + ( "__proto__" | "toString" | "toLocaleString" | "valueOf" + | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable" ); + } -> + false + (* Optimize to use the in operator when property is a known string which is not a prototype property *) + | Str _ -> true + (* We can be sure in this case that the prop is not a prototype property like __proto__ or toString *) + | _ -> false + in + if non_prototype_prop then in_ prop obj + else + call + ~info:{arity = Full; call_info = Call_na} + (js_global "Object.hasOwn") + [obj; prop] + 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 6fa1296273..ccc918b82a 100644 --- a/compiler/core/js_exp_make.mli +++ b/compiler/core/js_exp_make.mli @@ -358,6 +358,8 @@ val or_ : ?comment:string -> t -> t -> t val in_ : t -> t -> t +val has : 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/lam_analysis.ml b/compiler/core/lam_analysis.ml index 89cfd5b02b..08609903a6 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 | Phasin + | Pmakedict | Phas (* 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 a67ca118f1..334770ed88 100644 --- a/compiler/core/lam_compile_primitive.ml +++ b/compiler/core/lam_compile_primitive.ml @@ -574,12 +574,12 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) Some (Js_op.Lit txt, expr) | _ -> None)) | _ -> assert false) - | Phasin -> ( + | Phas -> ( match args with - | [obj; prop] -> E.in_ prop obj + | [obj; prop] -> E.has obj prop | _ -> Location.raise_errorf ~loc - "Invalid external \"%%has_in\" type signature. Expected to have two \ + "Invalid external \"%%has\" type signature. Expected to have two \ arguments.") | Parraysetu -> ( match args with diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 3ad973dba8..9531fb2c64 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -314,7 +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 - | Phasin -> prim ~primitive:Phasin ~args loc + | Phas -> prim ~primitive:Phas ~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 2958f2e364..ca0a5f06ee 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -137,7 +137,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict - | Phasin + | Phas (* promise *) | Pawait (* etc or deprecated *) @@ -216,7 +216,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) = (* List primitives *) | Pmakelist (* dict primitives *) - | Pmakedict | Phasin + | Pmakedict | Phas (* promise *) | Pawait (* etc *) diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index 992ba197bd..45c1c5fd05 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -132,7 +132,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict - | Phasin + | Phas (* promise *) | Pawait (* etc or deprecated *) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 59a43edfc3..8c8a6010fa 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -194,7 +194,7 @@ let primitive ppf (prim : Lam_primitive.t) = | Pmakearray -> fprintf ppf "makearray" | Pmakelist -> fprintf ppf "makelist" | Pmakedict -> fprintf ppf "makedict" - | Phasin -> fprintf ppf "has_in" + | Phas -> fprintf ppf "has_in" | 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 63d643739a..fe84304ce3 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -271,7 +271,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict - | Phasin + | Phas (* promise *) | Pawait (* module *) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index f7ba2196d7..ee343640c1 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -238,7 +238,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict - | Phasin + | Phas (* promise *) | Pawait (* modules *) diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 5af6f65992..b36880d96c 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -229,7 +229,7 @@ let primitive ppf = function | Pmakelist Mutable -> fprintf ppf "makelist" | Pmakelist Immutable -> fprintf ppf "makelist_imm" | Pmakedict -> fprintf ppf "makedict" - | Phasin -> fprintf ppf "has_in" + | Phas -> fprintf ppf "has_in" | 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 b803b9f89b..1bc82a1e62 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -346,7 +346,7 @@ let primitives_table = ("%array_unsafe_set", Parraysetu); (* dict primitives *) ("%makedict", Pmakedict); - ("%has_in", Phasin); + ("%has", Phas); (* promise *) ("%await", Pawait); (* module *) diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 584d493a37..5dfa9bd187 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -41,6 +41,6 @@ let mapValues = (dict, f) => { target } -external has: (dict<'a>, string) => bool = "%has_in" +external has: (dict<'a>, string) => bool = "%has" external ignore: dict<'a> => unit = "%ignore" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index 3ca2de53cf..b8bb8ae885 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -246,18 +246,20 @@ let mapValues: (dict<'a>, 'a => 'b) => dict<'b> /** `has(dictionary, "key")` returns true if the "key" is present in the dictionary. +It uses the `in` operator under the hood and falls back to the `Object.hasOwn` method when safety cannot be guaranteed at compile time. + ## 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("__proto__") // true, since it uses in operator under the hood +dict->Dict.has("key1")->assertEqual(true) +dict->Dict.has("key2")->assertEqual(true) +dict->Dict.has("key3")->assertEqual(false) +dict->Dict.has("toString")->assertEqual(false) ``` */ -external has: (dict<'a>, string) => bool = "%has_in" +external has: (dict<'a>, string) => bool = "%has" /** `ignore(dict)` ignores the provided dict and returns unit. diff --git a/tests/tests/src/DictTests.mjs b/tests/tests/src/DictTests.mjs index dd0cf2b7b7..4256979fb7 100644 --- a/tests/tests/src/DictTests.mjs +++ b/tests/tests/src/DictTests.mjs @@ -51,7 +51,7 @@ if (!("key1" in dict)) { RE_EXN_ID: "Assert_failure", _1: [ "DictTests.res", - 43, + 44, 2 ], Error: new Error() @@ -63,7 +63,7 @@ if (!("key2" in dict)) { RE_EXN_ID: "Assert_failure", _1: [ "DictTests.res", - 44, + 46, 2 ], Error: new Error() @@ -75,19 +75,31 @@ if ("key3" in dict !== false) { RE_EXN_ID: "Assert_failure", _1: [ "DictTests.res", - 45, + 48, 2 ], Error: new Error() }; } -if (!("__proto__" in dict)) { +if (Object.hasOwn(dict, "toString") !== false) { throw { RE_EXN_ID: "Assert_failure", _1: [ "DictTests.res", - 46, + 50, + 2 + ], + Error: new Error() + }; +} + +if (!Object.hasOwn(dict, "key1")) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "DictTests.res", + 52, 2 ], Error: new Error() @@ -99,7 +111,7 @@ if (typeof ("key1" in dict) !== "boolean") { RE_EXN_ID: "Assert_failure", _1: [ "DictTests.res", - 47, + 54, 2 ], Error: new Error() diff --git a/tests/tests/src/DictTests.res b/tests/tests/src/DictTests.res index df49b3206b..23cdf4b8c9 100644 --- a/tests/tests/src/DictTests.res +++ b/tests/tests/src/DictTests.res @@ -40,9 +40,16 @@ module DictHas = { "key2": None, } + // Test success path assert(dict->Dict.has("key1")) + // Test undefined field assert(dict->Dict.has("key2")) + // Test missing field assert(dict->Dict.has("key3") === false) - assert(dict->Dict.has("__proto__")) + // Test prototype field + assert(dict->Dict.has("toString") === false) + // Test without compile time knowledge + assert(dict->Dict.has(%raw(`"key1"`))) + // Test parantesis in generated code assert(typeof(dict->Dict.has("key1")) === #boolean) } From ec99f2682d16afde887f457e8d66dcf615ed2eee Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sun, 16 Mar 2025 14:05:55 +0400 Subject: [PATCH 4/6] Fix print name --- compiler/core/lam_print.ml | 2 +- compiler/ml/printlambda.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 8c8a6010fa..eea3a29896 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -194,7 +194,7 @@ let primitive ppf (prim : Lam_primitive.t) = | Pmakearray -> fprintf ppf "makearray" | Pmakelist -> fprintf ppf "makelist" | Pmakedict -> fprintf ppf "makedict" - | Phas -> fprintf ppf "has_in" + | Phas -> fprintf ppf "has" | Parrayrefu -> fprintf ppf "array.unsafe_get" | Parraysetu -> fprintf ppf "array.unsafe_set" | Parrayrefs -> fprintf ppf "array.get" diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index b36880d96c..a346f163d8 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -229,7 +229,7 @@ let primitive ppf = function | Pmakelist Mutable -> fprintf ppf "makelist" | Pmakelist Immutable -> fprintf ppf "makelist_imm" | Pmakedict -> fprintf ppf "makedict" - | Phas -> fprintf ppf "has_in" + | Phas -> fprintf ppf "has" | Pisint -> fprintf ppf "isint" | Pisout -> fprintf ppf "isout" | Pisnullable -> fprintf ppf "isnullable" From 63563c7d8ac92449d4ac8a7eb0fe42bd314c9f7e Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Mon, 24 Mar 2025 17:33:49 +0300 Subject: [PATCH 5/6] Change %has to %dict_has and make it only use in operator --- compiler/core/js_exp_make.ml | 22 ---------------------- compiler/core/js_exp_make.mli | 2 -- compiler/core/lam_analysis.ml | 2 +- compiler/core/lam_compile_primitive.ml | 6 +++--- compiler/core/lam_convert.ml | 2 +- compiler/core/lam_primitive.ml | 4 ++-- compiler/core/lam_primitive.mli | 2 +- compiler/core/lam_print.ml | 2 +- compiler/ml/lambda.ml | 2 +- compiler/ml/lambda.mli | 2 +- compiler/ml/printlambda.ml | 2 +- compiler/ml/translcore.ml | 2 +- runtime/Stdlib_Dict.res | 2 +- runtime/Stdlib_Dict.resi | 6 +++--- tests/tests/src/DictTests.mjs | 4 ++-- 15 files changed, 19 insertions(+), 43 deletions(-) diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index b59ad48637..e2e63885cf 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -1131,28 +1131,6 @@ let or_ ?comment (e1 : t) (e2 : t) = let in_ (prop : t) (obj : t) : t = {expression_desc = In (prop, obj); comment = None} -let has (obj : t) (prop : t) : t = - let non_prototype_prop = - match prop.expression_desc with - | Str - { - txt = - ( "__proto__" | "toString" | "toLocaleString" | "valueOf" - | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable" ); - } -> - false - (* Optimize to use the in operator when property is a known string which is not a prototype property *) - | Str _ -> true - (* We can be sure in this case that the prop is not a prototype property like __proto__ or toString *) - | _ -> false - in - if non_prototype_prop then in_ prop obj - else - call - ~info:{arity = Full; call_info = Call_na} - (js_global "Object.hasOwn") - [obj; prop] - 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 ccc918b82a..6fa1296273 100644 --- a/compiler/core/js_exp_make.mli +++ b/compiler/core/js_exp_make.mli @@ -358,8 +358,6 @@ val or_ : ?comment:string -> t -> t -> t val in_ : t -> t -> t -val has : 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/lam_analysis.ml b/compiler/core/lam_analysis.ml index 08609903a6..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 | Phas + | 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 334770ed88..aac979d926 100644 --- a/compiler/core/lam_compile_primitive.ml +++ b/compiler/core/lam_compile_primitive.ml @@ -574,12 +574,12 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) Some (Js_op.Lit txt, expr) | _ -> None)) | _ -> assert false) - | Phas -> ( + | Pdict_has -> ( match args with - | [obj; prop] -> E.has obj prop + | [obj; prop] -> E.in_ prop obj | _ -> Location.raise_errorf ~loc - "Invalid external \"%%has\" type signature. Expected to have two \ + "Invalid external \"%%dict_has\" type signature. Expected to have two \ arguments.") | Parraysetu -> ( match args with diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 9531fb2c64..3f252011ef 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -314,7 +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 - | Phas -> prim ~primitive:Phas ~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 ca0a5f06ee..e28c652cd9 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -137,7 +137,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict - | Phas + | Pdict_has (* promise *) | Pawait (* etc or deprecated *) @@ -216,7 +216,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) = (* List primitives *) | Pmakelist (* dict primitives *) - | Pmakedict | Phas + | Pmakedict | Pdict_has (* promise *) | Pawait (* etc *) diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index 45c1c5fd05..460ef392c4 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -132,7 +132,7 @@ type t = | Pmakelist (* dict primitives *) | Pmakedict - | Phas + | Pdict_has (* promise *) | Pawait (* etc or deprecated *) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index eea3a29896..d82956cc93 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -194,7 +194,7 @@ let primitive ppf (prim : Lam_primitive.t) = | Pmakearray -> fprintf ppf "makearray" | Pmakelist -> fprintf ppf "makelist" | Pmakedict -> fprintf ppf "makedict" - | Phas -> fprintf ppf "has" + | 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 fe84304ce3..26aa8a8c74 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -271,7 +271,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict - | Phas + | Pdict_has (* promise *) | Pawait (* module *) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index ee343640c1..9e1c9b9d7c 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -238,7 +238,7 @@ type primitive = | Pmakelist of Asttypes.mutable_flag (* dict primitives *) | Pmakedict - | Phas + | Pdict_has (* promise *) | Pawait (* modules *) diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index a346f163d8..f0ad4698bb 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -229,7 +229,7 @@ let primitive ppf = function | Pmakelist Mutable -> fprintf ppf "makelist" | Pmakelist Immutable -> fprintf ppf "makelist_imm" | Pmakedict -> fprintf ppf "makedict" - | Phas -> fprintf ppf "has" + | 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 1bc82a1e62..4cdeb34aa5 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -346,7 +346,7 @@ let primitives_table = ("%array_unsafe_set", Parraysetu); (* dict primitives *) ("%makedict", Pmakedict); - ("%has", Phas); + ("%dict_has", Pdict_has); (* promise *) ("%await", Pawait); (* module *) diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 5dfa9bd187..7604962689 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -41,6 +41,6 @@ let mapValues = (dict, f) => { target } -external has: (dict<'a>, string) => bool = "%has" +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 b8bb8ae885..604c5d4de9 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -246,7 +246,7 @@ let mapValues: (dict<'a>, 'a => 'b) => dict<'b> /** `has(dictionary, "key")` returns true if the "key" is present in the dictionary. -It uses the `in` operator under the hood and falls back to the `Object.hasOwn` method when safety cannot be guaranteed at compile time. +Be aware that it uses the JavaScript `in` operator under the hood. ## Examples @@ -256,10 +256,10 @@ let dict = dict{"key1": Some(1), "key2": None} dict->Dict.has("key1")->assertEqual(true) dict->Dict.has("key2")->assertEqual(true) dict->Dict.has("key3")->assertEqual(false) -dict->Dict.has("toString")->assertEqual(false) +dict->Dict.has("toString")->assertEqual(true) ``` */ -external has: (dict<'a>, string) => bool = "%has" +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 index 4256979fb7..208a3e95fd 100644 --- a/tests/tests/src/DictTests.mjs +++ b/tests/tests/src/DictTests.mjs @@ -82,7 +82,7 @@ if ("key3" in dict !== false) { }; } -if (Object.hasOwn(dict, "toString") !== false) { +if ("toString" in dict !== false) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -94,7 +94,7 @@ if (Object.hasOwn(dict, "toString") !== false) { }; } -if (!Object.hasOwn(dict, "key1")) { +if (!("key1" in dict)) { throw { RE_EXN_ID: "Assert_failure", _1: [ From 02b569905046717efe5d9523f2ce538c305c0328 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Mon, 31 Mar 2025 20:38:52 +0400 Subject: [PATCH 6/6] Fix tests --- tests/tests/src/DictTests.mjs | 135 ------------------------ tests/tests/src/DictTests.res | 55 ---------- tests/tests/src/core/Core_DictTests.mjs | 115 +++++++++++++++++++- tests/tests/src/core/Core_DictTests.res | 54 ++++++++++ tests/tests/src/core/Core_TestSuite.mjs | 18 ++++ 5 files changed, 183 insertions(+), 194 deletions(-) delete mode 100644 tests/tests/src/DictTests.mjs delete mode 100644 tests/tests/src/DictTests.res diff --git a/tests/tests/src/DictTests.mjs b/tests/tests/src/DictTests.mjs deleted file mode 100644 index 208a3e95fd..0000000000 --- a/tests/tests/src/DictTests.mjs +++ /dev/null @@ -1,135 +0,0 @@ -// Generated by ReScript, PLEASE EDIT WITH CARE - - -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: false, - key2: undefined -}; - -if (!("key1" in dict)) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 44, - 2 - ], - Error: new Error() - }; -} - -if (!("key2" in dict)) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 46, - 2 - ], - Error: new Error() - }; -} - -if ("key3" in dict !== false) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 48, - 2 - ], - Error: new Error() - }; -} - -if ("toString" in dict !== false) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 50, - 2 - ], - Error: new Error() - }; -} - -if (!("key1" in dict)) { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 52, - 2 - ], - Error: new Error() - }; -} - -if (typeof ("key1" in dict) !== "boolean") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "DictTests.res", - 54, - 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 23cdf4b8c9..0000000000 --- a/tests/tests/src/DictTests.res +++ /dev/null @@ -1,55 +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(false), - "key2": None, - } - - // Test success path - assert(dict->Dict.has("key1")) - // Test undefined field - assert(dict->Dict.has("key2")) - // Test missing field - assert(dict->Dict.has("key3") === false) - // Test prototype field - assert(dict->Dict.has("toString") === false) - // Test without compile time knowledge - assert(dict->Dict.has(%raw(`"key1"`))) - // Test parantesis in generated code - assert(typeof(dict->Dict.has("key1")) === #boolean) -} 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,