Skip to content

Commit c73714f

Browse files
authored
Support @as("foo") to customize the representation of tags. (#6095)
* Emit tags as strings. * Allow more general type for tags. Compile is_tag to `!== "object"` instead of `=== "string"`. * Do not special case variants with only 1 case with payload. Also the comment is not emitted anymore, since there's always a tag. Not special casing means that the representation is uniform, and does not change when the type is extended. This is important with zero cost ffi, where the runtime representation is exposed to the user, to reduce possible surprises. * Support @as("foo") to customize the representation of tags. * Cleanup: chatgpt's suggestion * Add support for custom representation of the form `@as(12)` * Add null, undefined, unboxed customization. null and undefined can only be applied to cases with no payloads unboxed can only be applied when there is exactly one case with payloads, and that case takes exactly one argument Some of those checks are possible statically. Not all of them are implemented. Some checks cannot if one wants to have user-level nullable, null, undefined types with pattern matching. E.g. null type could take null as an argument. * Add tagged unions example from chatgpt. * Use custom tags. * Remove unused set_tag from compiler internals. * Add support for @tag(...) to customize the property used for the tag. * Document places needing restriction. * Add support for @as(true) and @as(false)
1 parent 3d810f2 commit c73714f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+773
-148
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Make uncurried functions a subtype of curried functions, and allow application f
3030
The `make` function of components is generated as an uncurried function.
3131
Use best effort to determine the config when formatting a file.
3232
https://github.com/rescript-lang/rescript-compiler/pull/5968 https://github.com/rescript-lang/rescript-compiler/pull/6080 https://github.com/rescript-lang/rescript-compiler/pull/6086 https://github.com/rescript-lang/rescript-compiler/pull/6087
33+
- Customization of runtime representation of variants. This is work in progress. E.g. some restrictions on the input. See comments of the form "TODO: put restriction on the variant definitions allowed, to make sure this never happens". https://github.com/rescript-lang/rescript-compiler/pull/6095
3334

3435
#### :boom: Breaking Change
3536

jscomp/core/j.ml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,7 @@ and expression_desc =
151151
(* | Caml_uninitialized_obj of expression * expression *)
152152
(* [tag] and [size] tailed for [Obj.new_block] *)
153153

154-
(* For setter, it still return the value of expression,
155-
we can not use
156-
{[
157-
type 'a access = Get | Set of 'a
158-
]}
159-
in another module, since it will break our code generator
160-
[Caml_block_tag] can return [undefined],
161-
you have to use [E.tag] in a safe way
162-
*)
163-
| Caml_block_tag of expression
164-
(* | Caml_block_set_tag of expression * expression *)
154+
| Caml_block_tag of expression * string (* e.tag *)
165155
(* | Caml_block_set_length of expression * expression *)
166156
(* It will just fetch tag, to make it safe, when creating it,
167157
we need apply "|0", we don't do it in the
@@ -254,7 +244,7 @@ and case_clause = {
254244
comment : string option;
255245
}
256246

257-
and string_clause = string * case_clause
247+
and string_clause = Lambda.as_value * case_clause
258248
and int_clause = int * case_clause
259249

260250
and statement_desc =

jscomp/core/js_analyzer.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,9 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) =
101101
| Optional_block (x, _) -> no_side_effect x
102102
| Object kvs -> Ext_list.for_all_snd kvs no_side_effect
103103
| String_append (a, b) | Seq (a, b) -> no_side_effect a && no_side_effect b
104-
| Length (e, _) | Caml_block_tag e | Typeof e -> no_side_effect e
104+
| Length (e, _) | Caml_block_tag (e, _) | Typeof e -> no_side_effect e
105105
| Bin (op, a, b) -> op <> Eq && no_side_effect a && no_side_effect b
106106
| Js_not _ | Cond _ | FlatCall _ | Call _ | New _ | Raw_js_code _
107-
(* | Caml_block_set_tag _ *)
108107
(* actually true? *) ->
109108
false
110109
| Await _ -> false

jscomp/core/js_dump.ml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,9 @@ and expression_desc cxt ~(level : int) f x : cxt =
762762
| Lit n -> Ext_list.mem_string p.optional_labels n
763763
| Symbol_name -> false
764764
in
765+
let tag_name = match Ast_attributes.process_tag_name p.attrs with
766+
| None -> L.tag
767+
| Some s -> s in
765768
let tails =
766769
match p.optional_labels with
767770
| [] -> tails
@@ -771,11 +774,19 @@ and expression_desc cxt ~(level : int) f x : cxt =
771774
| Undefined when is_optional f -> None
772775
| _ -> Some (f, x))
773776
in
774-
(Js_op.Lit L.tag, E.str p.name) :: tails
777+
( Js_op.Lit tag_name, (* TAG:xx for inline records *)
778+
match Ast_attributes.process_as_value p.attrs with
779+
| None -> E.str p.name
780+
| Some as_value -> E.as_value as_value )
781+
:: tails
775782
in
776783
expression_desc cxt ~level f (Object objs)
777784
| Caml_block (el, _, tag, Blk_constructor p) ->
778785
let not_is_cons = p.name <> Literals.cons in
786+
let as_value = Ast_attributes.process_as_value p.attrs in
787+
let tag_name = match Ast_attributes.process_tag_name p.attrs with
788+
| None -> L.tag
789+
| Some s -> s in
779790
let objs =
780791
let tails =
781792
Ext_list.mapi_append el
@@ -789,14 +800,20 @@ and expression_desc cxt ~(level : int) f x : cxt =
789800
[ (name_symbol, E.str p.name) ]
790801
else [])
791802
in
792-
if not_is_cons = false && p.num_nonconst = 1 then tails
803+
if (as_value = Some AsUnboxed || not_is_cons = false) && p.num_nonconst = 1 then tails
793804
else
794-
( Js_op.Lit L.tag,
795-
E.str p.name
796-
)
805+
( Js_op.Lit tag_name, (* TAG:xx *)
806+
match as_value with
807+
| None -> E.str p.name
808+
| Some as_value -> E.as_value as_value )
797809
:: tails
798810
in
799-
expression_desc cxt ~level f (Object objs)
811+
let exp = match objs with
812+
| [(_, e)] when as_value = Some AsUnboxed -> e.expression_desc
813+
| _ when as_value = Some AsUnboxed -> assert false (* should not happen *)
814+
(* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *)
815+
| _ -> J.Object objs in
816+
expression_desc cxt ~level f exp
800817
| Caml_block
801818
( _,
802819
_,
@@ -806,11 +823,11 @@ and expression_desc cxt ~(level : int) f x : cxt =
806823
assert false
807824
| Caml_block (el, mutable_flag, _tag, Blk_tuple) ->
808825
expression_desc cxt ~level f (Array (el, mutable_flag))
809-
| Caml_block_tag e ->
826+
| Caml_block_tag (e, tag) ->
810827
P.group f 1 (fun _ ->
811828
let cxt = expression ~level:15 cxt f e in
812829
P.string f L.dot;
813-
P.string f L.tag;
830+
P.string f tag;
814831
cxt)
815832
| Array_index (e, p) ->
816833
P.cond_paren_group f (level > 15) 1 (fun _ ->
@@ -1188,8 +1205,10 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
11881205
let cxt = P.paren_group f 1 (fun _ -> expression ~level:0 cxt f e) in
11891206
P.space f;
11901207
P.brace_vgroup f 1 (fun _ ->
1191-
let pp_string f txt = ignore @@ expression_desc cxt ~level:0 f (Str {txt; delim=DStarJ}) in
1192-
let cxt = loop_case_clauses cxt f pp_string cc in
1208+
let pp_as_value f (as_value: Lambda.as_value) =
1209+
let e = E.as_value as_value in
1210+
ignore @@ expression_desc cxt ~level:0 f e.expression_desc in
1211+
let cxt = loop_case_clauses cxt f pp_as_value cc in
11931212
match def with
11941213
| None -> cxt
11951214
| Some def ->

jscomp/core/js_exp_make.ml

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,19 @@ let small_int i : t =
316316
| 248 -> obj_int_tag_literal
317317
| i -> int (Int32.of_int i)
318318

319+
let true_ : t = { comment = None; expression_desc = Bool true }
320+
let false_ : t = { comment = None; expression_desc = Bool false }
321+
let bool v = if v then true_ else false_
322+
323+
let as_value = function
324+
| Lambda.AsString s -> str s ~delim:DStarJ
325+
| AsInt i -> small_int i
326+
| AsBool b -> bool b
327+
| AsNull -> nil
328+
| AsUndefined -> undefined
329+
| AsUnboxed -> assert false (* Should not emit tags for unboxed *)
330+
(* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *)
331+
319332
let array_index ?comment (e0 : t) (e1 : t) : t =
320333
match (e0.expression_desc, e1.expression_desc) with
321334
| Array (l, _), Number (Int { i; _ })
@@ -540,13 +553,6 @@ let obj ?comment properties : t =
540553
(* currently only in method call, no dependency introduced
541554
*)
542555

543-
(* Static_index .....................**)
544-
545-
(* var (Jident.create_js "true") *)
546-
let true_ : t = { comment = None; expression_desc = Bool true }
547-
let false_ : t = { comment = None; expression_desc = Bool false }
548-
let bool v = if v then true_ else false_
549-
550556
(** Arith operators *)
551557
(* Static_index .....................**)
552558

@@ -762,8 +768,26 @@ let string_equal ?comment (e0 : t) (e1 : t) : t =
762768
let is_type_number ?comment (e : t) : t =
763769
string_equal ?comment (typeof e) (str "number")
764770

765-
let is_tag (e : t) : t =
766-
{ expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None }
771+
let is_tag ?(has_null_undefined_other=(false, false, false)) (e : t) : t =
772+
let (has_null, has_undefined, has_other) = has_null_undefined_other in
773+
if has_null && (has_undefined = false) && (has_other = false) then (* null *)
774+
{ expression_desc = Bin (EqEqEq, e, nil); comment=None }
775+
else if has_null && has_undefined && has_other=false then (* null + undefined *)
776+
{ J.expression_desc = Bin
777+
(Or,
778+
{ expression_desc = Bin (EqEqEq, e, nil); comment=None },
779+
{ expression_desc = Bin (EqEqEq, e, undefined); comment=None }
780+
); comment=None }
781+
else if has_null=false && has_undefined && has_other=false then (* undefined *)
782+
{ expression_desc = Bin (EqEqEq, e, undefined); comment=None }
783+
else if has_null then (* (null + undefined + other) || (null + other) *)
784+
{ J.expression_desc = Bin
785+
(Or,
786+
{ expression_desc = Bin (EqEqEq, e, nil); comment=None },
787+
{ expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None }
788+
); comment=None }
789+
else (* (undefiled + other) || other *)
790+
{ expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None }
767791

768792
let is_type_string ?comment (e : t) : t =
769793
string_equal ?comment (typeof e) (str "string")
@@ -775,8 +799,8 @@ let is_type_object (e : t) : t = string_equal (typeof e) (str "object")
775799
call plain [dot]
776800
*)
777801

778-
let tag ?comment e : t =
779-
{ expression_desc = Caml_block_tag e; comment }
802+
let tag ?comment ?(name=Js_dump_lit.tag) e : t =
803+
{ expression_desc = Caml_block_tag (e, name); comment }
780804

781805
(* according to the compiler, [Btype.hash_variant],
782806
it's reduced to 31 bits for hash

jscomp/core/js_exp_make.mli

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ val assign_by_exp : t -> t -> t -> t
185185

186186
val assign : ?comment:string -> t -> t -> t
187187

188+
val as_value : Lambda.as_value -> t
189+
188190
val triple_equal : ?comment:string -> t -> t -> t
189191
(* TODO: reduce [triple_equal] use *)
190192

@@ -199,7 +201,8 @@ val eq_null_undefined_boolean : ?comment:string -> t -> t -> t
199201
val neq_null_undefined_boolean : ?comment:string -> t -> t -> t
200202

201203
val is_type_number : ?comment:string -> t -> t
202-
val is_tag : t -> t
204+
205+
val is_tag : ?has_null_undefined_other:(bool * bool * bool) -> t -> t
203206

204207
val is_type_string : ?comment:string -> t -> t
205208

@@ -304,7 +307,7 @@ val unit : t
304307

305308
val undefined : t
306309

307-
val tag : ?comment:string -> J.expression -> t
310+
val tag : ?comment:string -> ?name:string -> J.expression -> t
308311

309312
(** Note that this is coupled with how we encode block, if we use the
310313
`Object.defineProperty(..)` since the array already hold the length,

jscomp/core/js_fold.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ class fold =
162162
let _self = list (fun _self -> _self#expression) _self _x0 in
163163
let _self = _self#expression _x2 in
164164
_self
165-
| Caml_block_tag _x0 ->
165+
| Caml_block_tag (_x0, _tag) ->
166166
let _self = _self#expression _x0 in
167167
_self
168168
| Number _ -> _self

jscomp/core/js_of_lam_variant.ml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ let eval (arg : J.expression) (dispatches : (string * string) list) : E.t =
3939
E.of_block
4040
[
4141
S.string_switch arg
42-
(Ext_list.map dispatches (fun (i, r) ->
43-
( i,
42+
(Ext_list.map dispatches (fun (s, r) ->
43+
( Lambda.AsString s,
4444
J.
4545
{
4646
switch_body = [ S.return_stmt (E.str r) ];
@@ -79,8 +79,8 @@ let eval_as_event (arg : J.expression)
7979
[
8080
S.string_switch
8181
(E.poly_var_tag_access arg)
82-
(Ext_list.map dispatches (fun (i, r) ->
83-
( i,
82+
(Ext_list.map dispatches (fun (s, r) ->
83+
( Lambda.AsString s,
8484
J.
8585
{
8686
switch_body = [ S.return_stmt (E.str r) ];
@@ -107,8 +107,8 @@ let eval_as_int (arg : J.expression) (dispatches : (string * int) list) : E.t =
107107
E.of_block
108108
[
109109
S.string_switch arg
110-
(Ext_list.map dispatches (fun (i, r) ->
111-
( i,
110+
(Ext_list.map dispatches (fun (s, r) ->
111+
( Lambda.AsString s,
112112
J.
113113
{
114114
switch_body =

jscomp/core/js_record_fold.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ let expression_desc : 'a. ('a, expression_desc) fn =
168168
let st = list _self.expression _self st _x0 in
169169
let st = _self.expression _self st _x2 in
170170
st
171-
| Caml_block_tag _x0 ->
171+
| Caml_block_tag (_x0, _tag) ->
172172
let st = _self.expression _self st _x0 in
173173
st
174174
| Number _ -> st

jscomp/core/js_record_iter.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ let expression_desc : expression_desc fn =
128128
| Caml_block (_x0, _x1, _x2, _x3) ->
129129
list _self.expression _self _x0;
130130
_self.expression _self _x2
131-
| Caml_block_tag _x0 -> _self.expression _self _x0
131+
| Caml_block_tag (_x0, _tag) -> _self.expression _self _x0
132132
| Number _ -> ()
133133
| Object _x0 -> property_map _self _x0
134134
| Undefined -> ()

jscomp/core/js_record_map.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ let expression_desc : expression_desc fn =
166166
let _x0 = list _self.expression _self _x0 in
167167
let _x2 = _self.expression _self _x2 in
168168
Caml_block (_x0, _x1, _x2, _x3)
169-
| Caml_block_tag _x0 ->
169+
| Caml_block_tag (_x0, tag) ->
170170
let _x0 = _self.expression _self _x0 in
171-
Caml_block_tag _x0
171+
Caml_block_tag (_x0, tag)
172172
| Number _ as v -> v
173173
| Object _x0 ->
174174
let _x0 = property_map _self _x0 in

jscomp/core/js_stmt_make.ml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,16 @@ let int_switch ?(comment : string option)
129129

130130
let string_switch ?(comment : string option)
131131
?(declaration : (J.property * Ident.t) option) ?(default : J.block option)
132-
(e : J.expression) (clauses : (string * J.case_clause) list) : t =
132+
(e : J.expression) (clauses : (Lambda.as_value * J.case_clause) list) : t =
133133
match e.expression_desc with
134134
| Str {txt} -> (
135135
let continuation =
136136
match
137137
Ext_list.find_opt clauses (fun (switch_case, x) ->
138-
if switch_case = txt then Some x.switch_body else None)
138+
match switch_case with
139+
| AsString s ->
140+
if s = txt then Some x.switch_body else None
141+
| AsInt _ | AsBool _ | AsNull | AsUnboxed | AsUndefined -> None)
139142
with
140143
| Some case -> case
141144
| None -> ( match default with Some x -> x | None -> assert false)

jscomp/core/js_stmt_make.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ val string_switch :
7777
?declaration:Lam_compat.let_kind * Ident.t ->
7878
?default:J.block ->
7979
J.expression ->
80-
(string * J.case_clause) list ->
80+
(Lambda.as_value * J.case_clause) list ->
8181
t
8282

8383
val declare_variable :

0 commit comments

Comments
 (0)