Skip to content

Commit b0211f3

Browse files
authored
Unified operators (#7057)
Introduce unified operators, the ad-hoc specialization for primitive operators. For example adding two values, we have `+` for ints, `+.` for floats, and `++` for strings. That is 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: #6525 Unified ops mitigate the problem by adding ad-hoc translation rules on applications of the core built-in operators which have a form of binary ('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 backward compatibility. You can find related definitions on `ml/unified_ops.ml` file. The actual implementation of translation is colocated into other modules. - Type-level : `ml/typecore.ml` - IR-level : `ml/translcore.ml` You can find it with the function name `translate_unified_ops` Resolved #6477
1 parent 48f30e5 commit b0211f3

15 files changed

+495
-115
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
# 12.0.0-alpha.5 (Unreleased)
1414

15+
#### :rocket: New Feature
16+
17+
- Introduce "Unified operators" for arithmetic operators (`+`, `-`, `*`, `/`, `mod`). See https://github.com/rescript-lang/rescript-compiler/pull/7057
18+
1519
# 12.0.0-alpha.4
1620

1721
#### :boom: Breaking Change

compiler/ml/translcore.ml

+79-31
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,33 @@ let transl_extension_constructor env path ext =
4949

5050
(* Translation of primitives *)
5151

52+
(** This is ad-hoc translation for unifying specific primitive operations
53+
See [Unified_ops] module for detailed explanation.
54+
*)
55+
let translate_unified_ops (prim : Primitive.description) (env : Env.t)
56+
(lhs_type : type_expr) : Lambda.primitive option =
57+
(* lhs_type is already unified in type-level *)
58+
let entry = Hashtbl.find_opt Unified_ops.index_by_name prim.prim_name in
59+
match entry with
60+
| Some {specialization} -> (
61+
match specialization with
62+
| {int}
63+
when is_base_type env lhs_type Predef.path_int
64+
|| maybe_pointer_type env lhs_type = Immediate ->
65+
Some int
66+
| {float = Some float} when is_base_type env lhs_type Predef.path_float ->
67+
Some float
68+
| {bigint = Some bigint} when is_base_type env lhs_type Predef.path_bigint
69+
->
70+
Some bigint
71+
| {string = Some string} when is_base_type env lhs_type Predef.path_string
72+
->
73+
Some string
74+
| {bool = Some bool} when is_base_type env lhs_type Predef.path_bool ->
75+
Some bool
76+
| {int} -> Some int)
77+
| _ -> None
78+
5279
type specialized = {
5380
objcomp: Lambda.primitive;
5481
intcomp: Lambda.primitive;
@@ -394,12 +421,21 @@ let specialize_comparison
394421
raise Not_found if primitive is unknown *)
395422

396423
let specialize_primitive p env ty (* ~has_constant_constructor *) =
397-
try
398-
let table = Hashtbl.find comparisons_table p.prim_name in
399-
match is_function_type env ty with
400-
| Some (lhs, _rhs) -> specialize_comparison table env lhs
401-
| None -> table.objcomp
402-
with Not_found -> find_primitive p.prim_name
424+
let fn_expr = is_function_type env ty in
425+
let unified =
426+
match fn_expr with
427+
| Some (lhs, _) -> translate_unified_ops p env lhs
428+
| None -> None
429+
in
430+
match unified with
431+
| Some primitive -> primitive
432+
| None -> (
433+
try
434+
let table = Hashtbl.find comparisons_table p.prim_name in
435+
match fn_expr with
436+
| Some (lhs, _rhs) -> specialize_comparison table env lhs
437+
| None -> table.objcomp
438+
with Not_found -> find_primitive p.prim_name)
403439

404440
(* Eta-expand a primitive *)
405441

@@ -458,32 +494,44 @@ let transl_primitive loc p env ty =
458494

459495
let transl_primitive_application loc prim env ty args =
460496
let prim_name = prim.prim_name in
461-
try
497+
let unified =
462498
match args with
463-
| [arg1; _]
464-
when is_base_type env arg1.exp_type Predef.path_bool
465-
&& Hashtbl.mem comparisons_table prim_name ->
466-
(Hashtbl.find comparisons_table prim_name).boolcomp
467-
| _ ->
468-
let has_constant_constructor =
469-
match args with
470-
| [_; {exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}]
471-
| [{exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}; _]
472-
| [_; {exp_desc = Texp_variant (_, None)}]
473-
| [{exp_desc = Texp_variant (_, None)}; _] ->
474-
true
475-
| _ -> false
476-
in
477-
if has_constant_constructor then
478-
match Hashtbl.find_opt comparisons_table prim_name with
479-
| Some table when table.simplify_constant_constructor -> table.intcomp
480-
| Some _ | None -> specialize_primitive prim env ty
481-
(* ~has_constant_constructor*)
482-
else specialize_primitive prim env ty
483-
with Not_found ->
484-
if String.length prim_name > 0 && prim_name.[0] = '%' then
485-
raise (Error (loc, Unknown_builtin_primitive prim_name));
486-
Pccall prim
499+
| [arg1] | [arg1; _] -> translate_unified_ops prim env arg1.exp_type
500+
| _ -> None
501+
in
502+
match unified with
503+
| Some primitive -> primitive
504+
| None -> (
505+
try
506+
match args with
507+
| [arg1; _]
508+
when is_base_type env arg1.exp_type Predef.path_bool
509+
&& Hashtbl.mem comparisons_table prim_name ->
510+
(Hashtbl.find comparisons_table prim_name).boolcomp
511+
| _ ->
512+
let has_constant_constructor =
513+
match args with
514+
| [
515+
_; {exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)};
516+
]
517+
| [
518+
{exp_desc = Texp_construct (_, {cstr_tag = Cstr_constant _}, _)}; _;
519+
]
520+
| [_; {exp_desc = Texp_variant (_, None)}]
521+
| [{exp_desc = Texp_variant (_, None)}; _] ->
522+
true
523+
| _ -> false
524+
in
525+
if has_constant_constructor then
526+
match Hashtbl.find_opt comparisons_table prim_name with
527+
| Some table when table.simplify_constant_constructor -> table.intcomp
528+
| Some _ | None -> specialize_primitive prim env ty
529+
(* ~has_constant_constructor*)
530+
else specialize_primitive prim env ty
531+
with Not_found ->
532+
if String.length prim_name > 0 && prim_name.[0] = '%' then
533+
raise (Error (loc, Unknown_builtin_primitive prim_name));
534+
Pccall prim)
487535

488536
(* To propagate structured constants *)
489537

compiler/ml/typecore.ml

+98-1
Original file line numberDiff line numberDiff line change
@@ -2458,7 +2458,9 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp
24582458
in
24592459
let type_clash_context = type_clash_context_from_function sexp sfunct in
24602460
let args, ty_res, fully_applied =
2461-
type_application ?type_clash_context uncurried env funct sargs
2461+
match translate_unified_ops env funct sargs with
2462+
| Some (targs, result_type) -> (targs, result_type, true)
2463+
| None -> type_application ?type_clash_context uncurried env funct sargs
24622464
in
24632465
end_def ();
24642466
unify_var env (newvar ()) funct.exp_type;
@@ -3561,6 +3563,101 @@ and is_automatic_curried_application env funct =
35613563
| Tarrow _ -> true
35623564
| _ -> false
35633565
3566+
(** This is ad-hoc translation for unifying specific primitive operations
3567+
See [Unified_ops] module for detailed explanation.
3568+
*)
3569+
and translate_unified_ops (env : Env.t) (funct : Typedtree.expression)
3570+
(sargs : sargs) : (targs * Types.type_expr) option =
3571+
match funct.exp_desc with
3572+
| Texp_ident (path, _, _) -> (
3573+
let entry = Hashtbl.find_opt Unified_ops.index_by_path (Path.name path) in
3574+
match (entry, sargs) with
3575+
| Some {form = Unary; specialization}, [(lhs_label, lhs_expr)] ->
3576+
let lhs = type_exp env lhs_expr in
3577+
let lhs_type = expand_head env lhs.exp_type in
3578+
let result_type =
3579+
match (lhs_type.desc, specialization) with
3580+
| Tconstr (path, _, _), _ when Path.same path Predef.path_int ->
3581+
Predef.type_int
3582+
| Tconstr (path, _, _), {bool = Some _}
3583+
when Path.same path Predef.path_bool ->
3584+
Predef.type_bool
3585+
| Tconstr (path, _, _), {float = Some _}
3586+
when Path.same path Predef.path_float ->
3587+
Predef.type_float
3588+
| Tconstr (path, _, _), {bigint = Some _}
3589+
when Path.same path Predef.path_bigint ->
3590+
Predef.type_bigint
3591+
| Tconstr (path, _, _), {string = Some _}
3592+
when Path.same path Predef.path_string ->
3593+
Predef.type_string
3594+
| _ ->
3595+
unify env lhs_type Predef.type_int;
3596+
Predef.type_int
3597+
in
3598+
let targs = [(lhs_label, Some lhs)] in
3599+
Some (targs, result_type)
3600+
| ( Some {form = Binary; specialization},
3601+
[(lhs_label, lhs_expr); (rhs_label, rhs_expr)] ) ->
3602+
let lhs = type_exp env lhs_expr in
3603+
let lhs_type = expand_head env lhs.exp_type in
3604+
let rhs = type_exp env rhs_expr in
3605+
let rhs_type = expand_head env rhs.exp_type in
3606+
let lhs, rhs, result_type =
3607+
(* Rule 1. Try unifying to lhs *)
3608+
match (lhs_type.desc, specialization) with
3609+
| Tconstr (path, _, _), _ when Path.same path Predef.path_int ->
3610+
let rhs = type_expect env rhs_expr Predef.type_int in
3611+
(lhs, rhs, Predef.type_int)
3612+
| Tconstr (path, _, _), {bool = Some _}
3613+
when Path.same path Predef.path_bool ->
3614+
let rhs = type_expect env rhs_expr Predef.type_bool in
3615+
(lhs, rhs, Predef.type_bool)
3616+
| Tconstr (path, _, _), {float = Some _}
3617+
when Path.same path Predef.path_float ->
3618+
let rhs = type_expect env rhs_expr Predef.type_float in
3619+
(lhs, rhs, Predef.type_float)
3620+
| Tconstr (path, _, _), {bigint = Some _}
3621+
when Path.same path Predef.path_bigint ->
3622+
let rhs = type_expect env rhs_expr Predef.type_bigint in
3623+
(lhs, rhs, Predef.type_bigint)
3624+
| Tconstr (path, _, _), {string = Some _}
3625+
when Path.same path Predef.path_string ->
3626+
let rhs = type_expect env rhs_expr Predef.type_string in
3627+
(lhs, rhs, Predef.type_string)
3628+
| _ -> (
3629+
(* Rule 2. Try unifying to rhs *)
3630+
match (rhs_type.desc, specialization) with
3631+
| Tconstr (path, _, _), _ when Path.same path Predef.path_int ->
3632+
let lhs = type_expect env lhs_expr Predef.type_int in
3633+
(lhs, rhs, Predef.type_int)
3634+
| Tconstr (path, _, _), {bool = Some _}
3635+
when Path.same path Predef.path_bool ->
3636+
let lhs = type_expect env lhs_expr Predef.type_bool in
3637+
(lhs, rhs, Predef.type_bool)
3638+
| Tconstr (path, _, _), {float = Some _}
3639+
when Path.same path Predef.path_float ->
3640+
let lhs = type_expect env lhs_expr Predef.type_float in
3641+
(lhs, rhs, Predef.type_float)
3642+
| Tconstr (path, _, _), {bigint = Some _}
3643+
when Path.same path Predef.path_bigint ->
3644+
let lhs = type_expect env lhs_expr Predef.type_bigint in
3645+
(lhs, rhs, Predef.type_bigint)
3646+
| Tconstr (path, _, _), {string = Some _}
3647+
when Path.same path Predef.path_string ->
3648+
let lhs = type_expect env lhs_expr Predef.type_string in
3649+
(lhs, rhs, Predef.type_string)
3650+
| _ ->
3651+
(* Rule 3. Fallback to int *)
3652+
let lhs = type_expect env lhs_expr Predef.type_int in
3653+
let rhs = type_expect env rhs_expr Predef.type_int in
3654+
(lhs, rhs, Predef.type_int))
3655+
in
3656+
let targs = [(lhs_label, Some lhs); (rhs_label, Some rhs)] in
3657+
Some (targs, result_type)
3658+
| _ -> None)
3659+
| _ -> None
3660+
35643661
and type_application ?type_clash_context uncurried env funct (sargs : sargs) :
35653662
targs * Types.type_expr * bool =
35663663
(* funct.exp_type may be generic *)

0 commit comments

Comments
 (0)