diff --git a/CHANGELOG.md b/CHANGELOG.md index 977198bd4c..bf761eae4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ # 12.0.0-alpha.5 (Unreleased) +#### :rocket: New Feature + +- Introduce "Unified operators" for arithmetic operators (`+`, `-`, `*`, `/`, `mod`). See https://github.com/rescript-lang/rescript-compiler/pull/7057 + # 12.0.0-alpha.4 #### :boom: Breaking Change diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 04c80e8e09..a3f61e8990 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -49,6 +49,33 @@ let transl_extension_constructor env path ext = (* Translation of primitives *) +(** This is ad-hoc translation for unifying specific primitive operations + See [Unified_ops] module for detailed explanation. + *) +let translate_unified_ops (prim : Primitive.description) (env : Env.t) + (lhs_type : type_expr) : Lambda.primitive option = + (* lhs_type is already unified in type-level *) + let entry = Hashtbl.find_opt Unified_ops.index_by_name prim.prim_name in + match entry with + | Some {specialization} -> ( + match specialization with + | {int} + when is_base_type env lhs_type Predef.path_int + || maybe_pointer_type env lhs_type = Immediate -> + Some int + | {float = Some float} when is_base_type env lhs_type Predef.path_float -> + Some float + | {bigint = Some bigint} when is_base_type env lhs_type Predef.path_bigint + -> + Some bigint + | {string = Some string} when is_base_type env lhs_type Predef.path_string + -> + Some string + | {bool = Some bool} when is_base_type env lhs_type Predef.path_bool -> + Some bool + | {int} -> Some int) + | _ -> None + type specialized = { objcomp: Lambda.primitive; intcomp: Lambda.primitive; @@ -394,12 +421,21 @@ let specialize_comparison raise Not_found if primitive is unknown *) let specialize_primitive p env ty (* ~has_constant_constructor *) = - try - let table = Hashtbl.find comparisons_table p.prim_name in - match is_function_type env ty with - | Some (lhs, _rhs) -> specialize_comparison table env lhs - | None -> table.objcomp - with Not_found -> find_primitive p.prim_name + let fn_expr = is_function_type env ty in + let unified = + match fn_expr with + | Some (lhs, _) -> translate_unified_ops p env lhs + | None -> None + in + match unified with + | Some primitive -> primitive + | None -> ( + try + let table = Hashtbl.find comparisons_table p.prim_name in + match fn_expr with + | Some (lhs, _rhs) -> specialize_comparison table env lhs + | None -> table.objcomp + with Not_found -> find_primitive p.prim_name) (* Eta-expand a primitive *) @@ -458,32 +494,44 @@ let transl_primitive loc p env ty = let transl_primitive_application loc prim env ty args = let prim_name = prim.prim_name in - try + let unified = match args with - | [arg1; _] - when is_base_type env arg1.exp_type Predef.path_bool - && Hashtbl.mem comparisons_table prim_name -> - (Hashtbl.find comparisons_table prim_name).boolcomp - | _ -> - let has_constant_constructor = - match args with - | [_; {exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}] - | [{exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}; _] - | [_; {exp_desc = Texp_variant (_, None)}] - | [{exp_desc = Texp_variant (_, None)}; _] -> - true - | _ -> false - in - if has_constant_constructor then - match Hashtbl.find_opt comparisons_table prim_name with - | Some table when table.simplify_constant_constructor -> table.intcomp - | Some _ | None -> specialize_primitive prim env ty - (* ~has_constant_constructor*) - else specialize_primitive prim env ty - with Not_found -> - if String.length prim_name > 0 && prim_name.[0] = '%' then - raise (Error (loc, Unknown_builtin_primitive prim_name)); - Pccall prim + | [arg1] | [arg1; _] -> translate_unified_ops prim env arg1.exp_type + | _ -> None + in + match unified with + | Some primitive -> primitive + | None -> ( + try + match args with + | [arg1; _] + when is_base_type env arg1.exp_type Predef.path_bool + && Hashtbl.mem comparisons_table prim_name -> + (Hashtbl.find comparisons_table prim_name).boolcomp + | _ -> + let has_constant_constructor = + match args with + | [ + _; {exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}; + ] + | [ + {exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}; _; + ] + | [_; {exp_desc = Texp_variant (_, None)}] + | [{exp_desc = Texp_variant (_, None)}; _] -> + true + | _ -> false + in + if has_constant_constructor then + match Hashtbl.find_opt comparisons_table prim_name with + | Some table when table.simplify_constant_constructor -> table.intcomp + | Some _ | None -> specialize_primitive prim env ty + (* ~has_constant_constructor*) + else specialize_primitive prim env ty + with Not_found -> + if String.length prim_name > 0 && prim_name.[0] = '%' then + raise (Error (loc, Unknown_builtin_primitive prim_name)); + Pccall prim) (* To propagate structured constants *) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index f4334ba9bb..404bb70f7c 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2458,7 +2458,9 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp in let type_clash_context = type_clash_context_from_function sexp sfunct in let args, ty_res, fully_applied = - type_application ?type_clash_context uncurried env funct sargs + match translate_unified_ops env funct sargs with + | Some (targs, result_type) -> (targs, result_type, true) + | None -> type_application ?type_clash_context uncurried env funct sargs in end_def (); unify_var env (newvar ()) funct.exp_type; @@ -3561,6 +3563,101 @@ and is_automatic_curried_application env funct = | Tarrow _ -> true | _ -> false +(** This is ad-hoc translation for unifying specific primitive operations + See [Unified_ops] module for detailed explanation. + *) +and translate_unified_ops (env : Env.t) (funct : Typedtree.expression) + (sargs : sargs) : (targs * Types.type_expr) option = + match funct.exp_desc with + | Texp_ident (path, _, _) -> ( + let entry = Hashtbl.find_opt Unified_ops.index_by_path (Path.name path) in + match (entry, sargs) with + | Some {form = Unary; specialization}, [(lhs_label, lhs_expr)] -> + let lhs = type_exp env lhs_expr in + let lhs_type = expand_head env lhs.exp_type in + let result_type = + match (lhs_type.desc, specialization) with + | Tconstr (path, _, _), _ when Path.same path Predef.path_int -> + Predef.type_int + | Tconstr (path, _, _), {bool = Some _} + when Path.same path Predef.path_bool -> + Predef.type_bool + | Tconstr (path, _, _), {float = Some _} + when Path.same path Predef.path_float -> + Predef.type_float + | Tconstr (path, _, _), {bigint = Some _} + when Path.same path Predef.path_bigint -> + Predef.type_bigint + | Tconstr (path, _, _), {string = Some _} + when Path.same path Predef.path_string -> + Predef.type_string + | _ -> + unify env lhs_type Predef.type_int; + Predef.type_int + in + let targs = [(lhs_label, Some lhs)] in + Some (targs, result_type) + | ( Some {form = Binary; specialization}, + [(lhs_label, lhs_expr); (rhs_label, rhs_expr)] ) -> + let lhs = type_exp env lhs_expr in + let lhs_type = expand_head env lhs.exp_type in + let rhs = type_exp env rhs_expr in + let rhs_type = expand_head env rhs.exp_type in + let lhs, rhs, result_type = + (* Rule 1. Try unifying to lhs *) + match (lhs_type.desc, specialization) with + | Tconstr (path, _, _), _ when Path.same path Predef.path_int -> + let rhs = type_expect env rhs_expr Predef.type_int in + (lhs, rhs, Predef.type_int) + | Tconstr (path, _, _), {bool = Some _} + when Path.same path Predef.path_bool -> + let rhs = type_expect env rhs_expr Predef.type_bool in + (lhs, rhs, Predef.type_bool) + | Tconstr (path, _, _), {float = Some _} + when Path.same path Predef.path_float -> + let rhs = type_expect env rhs_expr Predef.type_float in + (lhs, rhs, Predef.type_float) + | Tconstr (path, _, _), {bigint = Some _} + when Path.same path Predef.path_bigint -> + let rhs = type_expect env rhs_expr Predef.type_bigint in + (lhs, rhs, Predef.type_bigint) + | Tconstr (path, _, _), {string = Some _} + when Path.same path Predef.path_string -> + let rhs = type_expect env rhs_expr Predef.type_string in + (lhs, rhs, Predef.type_string) + | _ -> ( + (* Rule 2. Try unifying to rhs *) + match (rhs_type.desc, specialization) with + | Tconstr (path, _, _), _ when Path.same path Predef.path_int -> + let lhs = type_expect env lhs_expr Predef.type_int in + (lhs, rhs, Predef.type_int) + | Tconstr (path, _, _), {bool = Some _} + when Path.same path Predef.path_bool -> + let lhs = type_expect env lhs_expr Predef.type_bool in + (lhs, rhs, Predef.type_bool) + | Tconstr (path, _, _), {float = Some _} + when Path.same path Predef.path_float -> + let lhs = type_expect env lhs_expr Predef.type_float in + (lhs, rhs, Predef.type_float) + | Tconstr (path, _, _), {bigint = Some _} + when Path.same path Predef.path_bigint -> + let lhs = type_expect env lhs_expr Predef.type_bigint in + (lhs, rhs, Predef.type_bigint) + | Tconstr (path, _, _), {string = Some _} + when Path.same path Predef.path_string -> + let lhs = type_expect env lhs_expr Predef.type_string in + (lhs, rhs, Predef.type_string) + | _ -> + (* Rule 3. Fallback to int *) + let lhs = type_expect env lhs_expr Predef.type_int in + let rhs = type_expect env rhs_expr Predef.type_int in + (lhs, rhs, Predef.type_int)) + in + let targs = [(lhs_label, Some lhs); (rhs_label, Some rhs)] in + Some (targs, result_type) + | _ -> None) + | _ -> None + and type_application ?type_clash_context uncurried env funct (sargs : sargs) : targs * Types.type_expr * bool = (* funct.exp_type may be generic *) diff --git a/compiler/ml/unified_ops.ml b/compiler/ml/unified_ops.ml new file mode 100644 index 0000000000..3aaa4dd021 --- /dev/null +++ b/compiler/ml/unified_ops.ml @@ -0,0 +1,157 @@ +open Misc + +(* + Unified_ops is for specialization of some primitive operators. + + For example adding two values. We have `+` for ints, `+.` for floats, and `++` for strings. + That because we don't allow implicit conversion or overloading for operations. + + It is a fundamental property of the ReScript language, but it is far from the best DX we can think of, + and it became a problem when new primitives like bigint were introduced. + + See discussion: https://github.com/rescript-lang/rescript-compiler/issues/6525 + + Unified ops mitigate the problem by adding ad-hoc translation rules on applications of the core built-in operators + which have form of binary infix ('a -> 'a -> 'a) or unary ('a -> 'a) + + Translation rules should be applied in its application, in both type-level and IR(lambda)-level. + + The rules: + + 1. If the lhs type is a primitive type, unify the rhs and the result type to the lhs type. + 2. If the lhs type is not a primitive type but the rhs type is, unify lhs and the result type to the rhs type. + 3. If both lhs type and rhs type is not a primitive type, unify the whole types to the int. + + Since these are simple ad-hoc translations for primitive applications, we cannot use the result type defined in other contexts. + So falling back to int type is the simplest behavior that ensures backwards compatibility. + + Actual implementations of translation are colocated into core modules + + You can find it in: + - Type-level : ml/typecore.ml + - IR-level : ml/translcore.ml + + With function name "translate_unified_ops" +*) + +type form = Unary | Binary + +(* Note: unified op must support int type *) +type specialization = { + int: Lambda.primitive; + bool: Lambda.primitive option; + float: Lambda.primitive option; + bigint: Lambda.primitive option; + string: Lambda.primitive option; +} + +type entry = { + path: string; + (** TODO: Maybe it can be a Path.t in Predef instead of string *) + name: string; + form: form; + specialization: specialization; +} + +let builtin x = Primitive_modules.pervasives ^ "." ^ x + +let entries = + [| + { + path = builtin "~+"; + name = "%plus"; + form = Unary; + specialization = + { + int = Pidentity; + bool = None; + float = Some Pidentity; + bigint = Some Pidentity; + string = None; + }; + }; + { + path = builtin "~-"; + name = "%neg"; + form = Unary; + specialization = + { + int = Pnegint; + bool = None; + float = Some Pnegfloat; + bigint = Some Pnegbigint; + string = None; + }; + }; + { + path = builtin "+"; + name = "%add"; + form = Binary; + specialization = + { + int = Paddint; + bool = None; + float = Some Paddfloat; + bigint = Some Paddbigint; + string = Some Pstringadd; + }; + }; + { + path = builtin "-"; + name = "%sub"; + form = Binary; + specialization = + { + int = Psubint; + bool = None; + float = Some Psubfloat; + bigint = Some Psubbigint; + string = None; + }; + }; + { + path = builtin "*"; + name = "%mul"; + form = Binary; + specialization = + { + int = Pmulint; + bool = None; + float = Some Pmulfloat; + bigint = Some Pmulbigint; + string = None; + }; + }; + { + path = builtin "/"; + name = "%div"; + form = Binary; + specialization = + { + int = Pdivint Safe; + bool = None; + float = Some Pdivfloat; + bigint = Some Pdivbigint; + string = None; + }; + }; + { + path = builtin "mod"; + name = "%mod"; + form = Binary; + specialization = + { + int = Pmodint Safe; + bool = None; + float = Some Pmodfloat; + bigint = Some Pmodbigint; + string = None; + }; + }; + |] + +let index_by_path = + entries |> Array.map (fun entry -> (entry.path, entry)) |> create_hashtable + +let index_by_name = + entries |> Array.map (fun entry -> (entry.name, entry)) |> create_hashtable diff --git a/compiler/ml/unified_ops.mli b/compiler/ml/unified_ops.mli new file mode 100644 index 0000000000..b52e052a55 --- /dev/null +++ b/compiler/ml/unified_ops.mli @@ -0,0 +1,20 @@ +type form = Unary | Binary + +type specialization = { + int: Lambda.primitive; + bool: Lambda.primitive option; + float: Lambda.primitive option; + bigint: Lambda.primitive option; + string: Lambda.primitive option; +} + +type entry = { + path: string; + name: string; + form: form; + specialization: specialization; +} + +val index_by_path : (string, entry) Hashtbl.t + +val index_by_name : (string, entry) Hashtbl.t diff --git a/runtime/Pervasives.res b/runtime/Pervasives.res index 8f05695ddc..f3552aae89 100644 --- a/runtime/Pervasives.res +++ b/runtime/Pervasives.res @@ -40,7 +40,19 @@ external __LOC_OF__: 'a => (string, 'a) = "%loc_LOC" external __LINE_OF__: 'a => (int, 'a) = "%loc_LINE" external __POS_OF__: 'a => ((string, int, int, int), 'a) = "%loc_POS" +/* Unified operations */ + +external \"~+": 'a => 'a = "%plus" +external \"~-": 'a => 'a = "%neg" + +external \"+": ('a, 'a) => 'a = "%add" +external \"-": ('a, 'a) => 'a = "%sub" +external \"*": ('a, 'a) => 'a = "%mul" +external \"/": ('a, 'a) => 'a = "%div" +external mod: ('a, 'a) => 'a = "%mod" + /* Comparisons */ +/* Note: Later comparisons will be converted to unified operations too */ external \"=": ('a, 'a) => bool = "%equal" external \"<>": ('a, 'a) => bool = "%notequal" @@ -64,15 +76,8 @@ external \"||": (bool, bool) => bool = "%sequor" /* Integer operations */ -external \"~-": int => int = "%negint" -external \"~+": int => int = "%identity" external succ: int => int = "%succint" external pred: int => int = "%predint" -external \"+": (int, int) => int = "%addint" -external \"-": (int, int) => int = "%subint" -external \"*": (int, int) => int = "%mulint" -external \"/": (int, int) => int = "%divint" -external mod: (int, int) => int = "%modint" @deprecated("Use Core instead. This will be removed in v13") let abs = x => diff --git a/runtime/Pervasives_mini.res b/runtime/Pervasives_mini.res index 9c62e80ffe..d38b877a32 100644 --- a/runtime/Pervasives_mini.res +++ b/runtime/Pervasives_mini.res @@ -13,7 +13,25 @@ external __LOC_OF__: 'a => (string, 'a) = "%loc_LOC" external __LINE_OF__: 'a => (int, 'a) = "%loc_LINE" external __POS_OF__: 'a => ((string, int, int, int), 'a) = "%loc_POS" +/* Unified operations */ +/* + Note: + + Unified operations only work on `Pervasives`. + That means we can't rely on it when building stdlib until we remove the `Pervasives_mini`. +*/ + +external \"~+": int => int = "%identity" +external \"~-": int => int = "%negint" + +external \"+": (int, int) => int = "%addint" +external \"-": (int, int) => int = "%subint" +external \"*": (int, int) => int = "%mulint" +external \"/": (int, int) => int = "%divint" +external mod: (int, int) => int = "%modint" + /* Comparisons */ +/* Note: Later comparisons will be converted to unified operations too */ external \"=": ('a, 'a) => bool = "%equal" external \"<>": ('a, 'a) => bool = "%notequal" @@ -37,15 +55,8 @@ external \"||": (bool, bool) => bool = "%sequor" /* Integer operations */ -external \"~-": int => int = "%negint" -external \"~+": int => int = "%identity" external succ: int => int = "%succint" external pred: int => int = "%predint" -external \"+": (int, int) => int = "%addint" -external \"-": (int, int) => int = "%subint" -external \"*": (int, int) => int = "%mulint" -external \"/": (int, int) => int = "%divint" -external mod: (int, int) => int = "%modint" external land: (int, int) => int = "%andint" external lor: (int, int) => int = "%orint" diff --git a/runtime/rescript.json b/runtime/rescript.json index abb683bc73..b39c522cb5 100644 --- a/runtime/rescript.json +++ b/runtime/rescript.json @@ -19,4 +19,4 @@ "-w -3+50", "-warn-error A" ] -} \ No newline at end of file +} diff --git a/tests/build_tests/super_errors/expected/math_operator_constant.res.expected b/tests/build_tests/super_errors/expected/math_operator_constant.res.expected index f2251eee15..741b07af7a 100644 --- a/tests/build_tests/super_errors/expected/math_operator_constant.res.expected +++ b/tests/build_tests/super_errors/expected/math_operator_constant.res.expected @@ -7,14 +7,8 @@ 3 │ let x = num + 12. 4 │ - This value has type: float - But it's being used with the + operator, which works on: int - - Floats and ints have their own mathematical operators. This means you cannot add a float and an int without converting between the two. - - Possible solutions: - - Ensure all values in this calculation has the type int. You can convert between floats and ints via Belt.Float.toInt and Belt.Int.fromFloat. - - Make 12. an int by removing the dot or explicitly converting to int + This has type: float + But it's expected to have type: int You can convert float to int with Belt.Float.toInt. If this is a literal, try a number without a trailing dot (e.g. 20). \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/math_operator_int.res.expected b/tests/build_tests/super_errors/expected/math_operator_int.res.expected index ebccfbecb7..e69de29bb2 100644 --- a/tests/build_tests/super_errors/expected/math_operator_int.res.expected +++ b/tests/build_tests/super_errors/expected/math_operator_int.res.expected @@ -1,20 +0,0 @@ - - We've found a bug for you! - /.../fixtures/math_operator_int.res:3:9-11 - - 1 │ let num = 0. - 2 │ - 3 │ let x = num + 12. - 4 │ - - This has type: float - But it's being used with the + operator, which works on: int - - Floats and ints have their own mathematical operators. This means you cannot add a float and an int without converting between the two. - - Possible solutions: - - Ensure all values in this calculation has the type int. You can convert between floats and ints via Belt.Float.toInt and Belt.Int.fromFloat. - - Change the operator to +., which works on float - - You can convert float to int with Belt.Float.toInt. - If this is a literal, try a number without a trailing dot (e.g. 20). \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/math_operator_string.res.expected b/tests/build_tests/super_errors/expected/math_operator_string.res.expected index cb03dfac3a..e69de29bb2 100644 --- a/tests/build_tests/super_errors/expected/math_operator_string.res.expected +++ b/tests/build_tests/super_errors/expected/math_operator_string.res.expected @@ -1,16 +0,0 @@ - - We've found a bug for you! - /.../fixtures/math_operator_string.res:1:9-15 - - 1 │ let x = "hello" + "what" - 2 │ - - This has type: string - But it's being used with the + operator, which works on: int - - Are you looking to concatenate strings? Use the operator ++, which concatenates strings. - - Possible solutions: - - Change the + operator to ++ to concatenate strings instead. - - You can convert string to int with Belt.Int.fromString. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/primitives1.res.expected b/tests/build_tests/super_errors/expected/primitives1.res.expected index b1a303eccb..cc2946f160 100644 --- a/tests/build_tests/super_errors/expected/primitives1.res.expected +++ b/tests/build_tests/super_errors/expected/primitives1.res.expected @@ -1,19 +1,13 @@ We've found a bug for you! - /.../fixtures/primitives1.res:2:1-2 + /.../fixtures/primitives1.res:2:6 1 │ /* got float, wanted int */ - 2 │ 2. + 2 + 2 │ 2. + 2 3 │ - This value has type: float - But it's being used with the + operator, which works on: int - - Floats and ints have their own mathematical operators. This means you cannot add a float and an int without converting between the two. - - Possible solutions: - - Ensure all values in this calculation has the type int. You can convert between floats and ints via Belt.Float.toInt and Belt.Int.fromFloat. - - Make 2. an int by removing the dot or explicitly converting to int + This has type: int + But it's expected to have type: float - You can convert float to int with Belt.Float.toInt. - If this is a literal, try a number without a trailing dot (e.g. 20). \ No newline at end of file + You can convert int to float with Belt.Int.toFloat. + If this is a literal, try a number with a trailing dot (e.g. 20.). \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/type1.res.expected b/tests/build_tests/super_errors/expected/type1.res.expected index 036daa2550..6bc3692c57 100644 --- a/tests/build_tests/super_errors/expected/type1.res.expected +++ b/tests/build_tests/super_errors/expected/type1.res.expected @@ -1,18 +1,12 @@ We've found a bug for you! - /.../fixtures/type1.res:1:9-10 + /.../fixtures/type1.res:1:14 - 1 │ let x = 2. + 2 + 1 │ let x = 2. + 2 2 │ - This value has type: float - But it's being used with the + operator, which works on: int - - Floats and ints have their own mathematical operators. This means you cannot add a float and an int without converting between the two. - - Possible solutions: - - Ensure all values in this calculation has the type int. You can convert between floats and ints via Belt.Float.toInt and Belt.Int.fromFloat. - - Make 2. an int by removing the dot or explicitly converting to int + This has type: int + But it's expected to have type: float - You can convert float to int with Belt.Float.toInt. - If this is a literal, try a number without a trailing dot (e.g. 20). \ No newline at end of file + You can convert int to float with Belt.Int.toFloat. + If this is a literal, try a number with a trailing dot (e.g. 20.). \ No newline at end of file diff --git a/tests/tests/src/unified_ops_test.mjs b/tests/tests/src/unified_ops_test.mjs new file mode 100644 index 0000000000..03f68c97fd --- /dev/null +++ b/tests/tests/src/unified_ops_test.mjs @@ -0,0 +1,73 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +let float = 1 + 2; + +let string = "12"; + +let bigint = 1n + 2n; + +function unknown(a, b) { + return a + b | 0; +} + +function lhsint(a, b) { + return a + b | 0; +} + +function lhsfloat(a, b) { + return a + b; +} + +function lhsbigint(a, b) { + return a + b; +} + +function lhsstring(a, b) { + return a + b; +} + +function rhsint(a, b) { + return a + b | 0; +} + +function rhsfloat(a, b) { + return a + b; +} + +function rhsbigint(a, b) { + return a + b; +} + +function rhsstring(a, b) { + return a + b; +} + +function case1(a) { + return 1 + a | 0; +} + +function case2(a, b) { + return a + "test" + b; +} + +let int = 3; + +export { + int, + float, + string, + bigint, + unknown, + lhsint, + lhsfloat, + lhsbigint, + lhsstring, + rhsint, + rhsfloat, + rhsbigint, + rhsstring, + case1, + case2, +} +/* No side effect */ diff --git a/tests/tests/src/unified_ops_test.res b/tests/tests/src/unified_ops_test.res new file mode 100644 index 0000000000..610a527a97 --- /dev/null +++ b/tests/tests/src/unified_ops_test.res @@ -0,0 +1,19 @@ +let int = 1 + 2 +let float = 1. + 2. +let string = "1" + "2" +let bigint = 1n + 2n + +let unknown = (a, b) => a + b + +let lhsint = (a: int, b) => a + b +let lhsfloat = (a: float, b) => a + b +let lhsbigint = (a: bigint, b) => a + b +let lhsstring = (a: string, b) => a + b + +let rhsint = (a, b: int) => a + b +let rhsfloat = (a, b: float) => a + b +let rhsbigint = (a, b: bigint) => a + b +let rhsstring = (a, b: string) => a + b + +let case1 = a => 1 + a +let case2 = (a, b) => a + "test" + b