Skip to content

Commit 2e7f5e3

Browse files
authored
Introduce untagged variants. (rescript-lang#6103)
* Towards prototyping untagged variants. Just disable the type error for now. * First few examples working. * No need for @as(unboxed) anymore. * Move type defs around. * Unify type definitions for untagged. * Example of boolean config. * Add handling of float. * Add example with 2 object types. * Add partial treatment of unknown. wip * Clean up names of types. * Rename: literal * More renaming. * rename: Block * Handle unknown case. * Add Object. * Add typeof to body of switch. * Complete classification of blocks. * Check that the type def is in one of the forms allowed. * Fix well-formedness test no other blocks when there's an unknown * rename * More renaming. * Add specific function compile_untagged_cases * Add example with only blocks. * Add support for array types. * Simplify well-formedness check. * Add Json example. And add built-in knowledge that Js.Dict.t is an object. * Fix instanceof array. * flip * Fix compilation of unknown. * Add untagged variant support to genType. * Update CHANGELOG.md
1 parent 9870370 commit 2e7f5e3

35 files changed

+827
-134
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ 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
3333
- 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
34+
- Introduce untagged variants https://github.com/rescript-lang/rescript-compiler/pull/6103
3435

3536
#### :boom: Breaking Change
3637

jscomp/core/j.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ and for_ident = ident
7676
and for_direction = Js_op.direction_flag
7777
and property_map = (property_name * expression) list
7878
and length_object = Js_op.length_object
79-
and delim = External_arg_spec.delim = | DNone | DStarJ | DJson
79+
and delim = External_arg_spec.delim = | DNone | DStarJ | DNoQuotes
8080

8181
and expression_desc =
8282
| Length of expression * length_object
@@ -244,7 +244,7 @@ and case_clause = {
244244
comment : string option;
245245
}
246246

247-
and string_clause = Lambda.as_value * case_clause
247+
and string_clause = Lambda.literal * case_clause
248248
and int_clause = int * case_clause
249249

250250
and statement_desc =

jscomp/core/js_dump.ml

+19-14
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
600600
let () =
601601
match delim with
602602
| DStarJ -> P.string f ("\"" ^ txt ^ "\"")
603-
| DJson -> P.string f txt
603+
| DNoQuotes -> P.string f txt
604604
| DNone -> Js_dump_string.pp_string f txt
605605
in
606606
cxt
@@ -751,6 +751,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
751751
| Caml_block (el, _, _, ((Blk_extension | Blk_record_ext _) as ext)) ->
752752
expression_desc cxt ~level f (exn_block_as_obj ~stack:false el ext)
753753
| Caml_block (el, _, tag, Blk_record_inlined p) ->
754+
let untagged = Ast_attributes.process_untagged p.attrs in
754755
let objs =
755756
let tails =
756757
Ext_list.combine_array_append p.fields el
@@ -774,16 +775,20 @@ and expression_desc cxt ~(level : int) f x : cxt =
774775
| Undefined when is_optional f -> None
775776
| _ -> Some (f, x))
776777
in
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
778+
if untagged then
779+
tails
780+
else
781+
(Js_op.Lit tag_name, (* TAG:xx for inline records *)
782+
match Ast_attributes.process_as_value p.attrs with
783+
| None -> E.str p.name
784+
| Some literal -> E.literal literal )
785+
:: tails
782786
in
783787
expression_desc cxt ~level f (Object objs)
784788
| Caml_block (el, _, tag, Blk_constructor p) ->
785789
let not_is_cons = p.name <> Literals.cons in
786-
let as_value = Ast_attributes.process_as_value p.attrs in
790+
let literal = Ast_attributes.process_as_value p.attrs in
791+
let untagged = Ast_attributes.process_untagged p.attrs in
787792
let tag_name = match Ast_attributes.process_tag_name p.attrs with
788793
| None -> L.tag
789794
| Some s -> s in
@@ -800,17 +805,17 @@ and expression_desc cxt ~(level : int) f x : cxt =
800805
[ (name_symbol, E.str p.name) ]
801806
else [])
802807
in
803-
if (as_value = Some AsUnboxed || not_is_cons = false) && p.num_nonconst = 1 then tails
808+
if untagged || (not_is_cons = false) && p.num_nonconst = 1 then tails
804809
else
805810
( Js_op.Lit tag_name, (* TAG:xx *)
806-
match as_value with
811+
match literal with
807812
| None -> E.str p.name
808-
| Some as_value -> E.as_value as_value )
813+
| Some literal -> E.literal literal )
809814
:: tails
810815
in
811816
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 *)
817+
| [(_, e)] when untagged -> e.expression_desc
818+
| _ when untagged -> assert false (* should not happen *)
814819
(* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *)
815820
| _ -> J.Object objs in
816821
expression_desc cxt ~level f exp
@@ -1205,8 +1210,8 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
12051210
let cxt = P.paren_group f 1 (fun _ -> expression ~level:0 cxt f e) in
12061211
P.space f;
12071212
P.brace_vgroup f 1 (fun _ ->
1208-
let pp_as_value f (as_value: Lambda.as_value) =
1209-
let e = E.as_value as_value in
1213+
let pp_as_value f (literal: Lambda.literal) =
1214+
let e = E.literal literal in
12101215
ignore @@ expression_desc cxt ~level:0 f e.expression_desc in
12111216
let cxt = loop_case_clauses cxt f pp_as_value cc in
12121217
match def with

jscomp/core/js_exp_make.ml

+47-10
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ let typeof ?comment (e : t) : t =
173173
| Bool _ -> str ?comment L.js_type_boolean
174174
| _ -> { expression_desc = Typeof e; comment }
175175

176+
let instanceof ?comment (e0 : t) (e1: t) : t =
177+
{ expression_desc = Bin (InstanceOf, e0, e1); comment }
178+
176179
let new_ ?comment e0 args : t =
177180
{ expression_desc = New (e0, Some args); comment }
178181

@@ -328,15 +331,21 @@ let zero_float_lit : t =
328331
let float_mod ?comment e1 e2 : J.expression =
329332
{ comment; expression_desc = Bin (Mod, e1, e2) }
330333

331-
let as_value = function
332-
| Lambda.AsString s -> str s ~delim:DStarJ
333-
| AsInt i -> small_int i
334-
| AsFloat f -> float f
335-
| AsBool b -> bool b
336-
| AsNull -> nil
337-
| AsUndefined -> undefined
338-
| AsUnboxed -> assert false (* Should not emit tags for unboxed *)
339-
(* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *)
334+
let literal = function
335+
| Lambda.String s -> str s ~delim:DStarJ
336+
| Int i -> small_int i
337+
| Float f -> float f
338+
| Bool b -> bool b
339+
| Null -> nil
340+
| Undefined -> undefined
341+
| Block IntType -> str "number"
342+
| Block FloatType -> str "number"
343+
| Block StringType -> str "string"
344+
| Block Array -> str "Array" ~delim:DNoQuotes
345+
| Block Object -> str "object"
346+
| Block Unknown ->
347+
(* TODO: clean up pattern mathing algo whih confuses literal with blocks *)
348+
assert false
340349

341350
let array_index ?comment (e0 : t) (e1 : t) : t =
342351
match (e0.expression_desc, e1.expression_desc) with
@@ -762,7 +771,35 @@ let string_equal ?comment (e0 : t) (e1 : t) : t =
762771
let is_type_number ?comment (e : t) : t =
763772
string_equal ?comment (typeof e) (str "number")
764773

765-
let is_tag ?(has_null_undefined_other=(false, false, false)) (e : t) : t =
774+
let rec is_a_literal_case ~(literal_cases : Lambda.literal list) ~block_cases (e:t) : t =
775+
let is_literal_case (l:Lambda.literal) : t = bin EqEqEq e (literal l) in
776+
let is_block_case (c:Lambda.block_type) : t = match c with
777+
| StringType -> bin NotEqEq (typeof e) (str "string")
778+
| IntType -> bin NotEqEq (typeof e) (str "number")
779+
| FloatType -> bin NotEqEq (typeof e) (str "number")
780+
| Array -> not (bin InstanceOf e (str "Array" ~delim:DNoQuotes))
781+
| Object -> { expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None }
782+
| Unknown ->
783+
(* We don't know the type of unknown, so we need to express:
784+
this is not one of the literals *)
785+
(match literal_cases with
786+
| [] ->
787+
(* this should not happen *)
788+
assert false
789+
| l1 :: others ->
790+
let is_literal_1 = is_literal_case l1 in
791+
Ext_list.fold_right others is_literal_1 (fun literal_n acc ->
792+
bin Or (is_literal_case literal_n) acc
793+
)
794+
)
795+
in
796+
match block_cases with
797+
| [c] -> is_block_case c
798+
| c1 :: (_::_ as rest) ->
799+
bin And (is_block_case c1) (is_a_literal_case ~literal_cases ~block_cases:rest e)
800+
| [] -> assert false
801+
802+
let is_int_tag ?(has_null_undefined_other=(false, false, false)) (e : t) : t =
766803
let (has_null, has_undefined, has_other) = has_null_undefined_other in
767804
if has_null && (has_undefined = false) && (has_other = false) then (* null *)
768805
{ expression_desc = Bin (EqEqEq, e, nil); comment=None }

jscomp/core/js_exp_make.mli

+5-2
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ 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
188+
val literal : Lambda.literal -> t
189189

190190
val triple_equal : ?comment:string -> t -> t -> t
191191
(* TODO: reduce [triple_equal] use *)
@@ -202,13 +202,16 @@ val neq_null_undefined_boolean : ?comment:string -> t -> t -> t
202202

203203
val is_type_number : ?comment:string -> t -> t
204204

205-
val is_tag : ?has_null_undefined_other:(bool * bool * bool) -> t -> t
205+
val is_int_tag : ?has_null_undefined_other:(bool * bool * bool) -> t -> t
206+
207+
val is_a_literal_case : literal_cases:Lambda.literal list -> block_cases:Lambda.block_type list -> t -> t
206208

207209
val is_type_string : ?comment:string -> t -> t
208210

209211
val is_type_object : t -> t
210212

211213
val typeof : ?comment:string -> t -> t
214+
val instanceof : ?comment:string -> t -> t -> t
212215

213216
val to_int32 : ?comment:string -> t -> t
214217

jscomp/core/js_of_lam_variant.ml

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ let eval (arg : J.expression) (dispatches : (string * string) list) : E.t =
4040
[
4141
S.string_switch arg
4242
(Ext_list.map dispatches (fun (s, r) ->
43-
( Lambda.AsString s,
43+
( Lambda.String s,
4444
J.
4545
{
4646
switch_body = [ S.return_stmt (E.str r) ];
@@ -80,7 +80,7 @@ let eval_as_event (arg : J.expression)
8080
S.string_switch
8181
(E.poly_var_tag_access arg)
8282
(Ext_list.map dispatches (fun (s, r) ->
83-
( Lambda.AsString s,
83+
( Lambda.String s,
8484
J.
8585
{
8686
switch_body = [ S.return_stmt (E.str r) ];
@@ -108,7 +108,7 @@ let eval_as_int (arg : J.expression) (dispatches : (string * int) list) : E.t =
108108
[
109109
S.string_switch arg
110110
(Ext_list.map dispatches (fun (s, r) ->
111-
( Lambda.AsString s,
111+
( Lambda.String s,
112112
J.
113113
{
114114
switch_body =

jscomp/core/js_op.ml

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type binop =
4848
| Mul
4949
| Div
5050
| Mod
51+
| InstanceOf
5152

5253
(**
5354
note that we don't need raise [Div_by_zero] in ReScript

jscomp/core/js_op_util.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let op_prec (op : Js_op.binop) =
3434
| Or -> (3, 3, 3)
3535
| And -> (4, 4, 4)
3636
| EqEqEq | NotEqEq -> (8, 8, 9)
37-
| Gt | Ge | Lt | Le (* | InstanceOf *) -> (9, 9, 10)
37+
| Gt | Ge | Lt | Le | InstanceOf -> (9, 9, 10)
3838
| Bor -> (5, 5, 5)
3939
| Bxor -> (6, 6, 6)
4040
| Band -> (7, 7, 7)
@@ -73,7 +73,7 @@ let op_str (op : Js_op.binop) =
7373
| Le -> "<="
7474
| Gt -> ">"
7575
| Ge -> ">="
76-
(* | InstanceOf -> "instanceof" *)
76+
| InstanceOf -> "instanceof"
7777

7878
let op_int_str (op : Js_op.int_op) =
7979
match op with

jscomp/core/js_stmt_make.ml

+5-3
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,18 @@ 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 : (Lambda.as_value * J.case_clause) list) : t =
132+
(e : J.expression) (clauses : (Lambda.literal * 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) ->
138138
match switch_case with
139-
| AsString s ->
139+
| String s ->
140140
if s = txt then Some x.switch_body else None
141-
| AsInt _ | AsFloat _| AsBool _ | AsNull | AsUnboxed | AsUndefined -> None)
141+
| Int _ | Float _| Bool _ | Null
142+
| Undefined
143+
| Block _ -> None)
142144
with
143145
| Some case -> case
144146
| None -> ( match default with Some x -> x | None -> assert false)

jscomp/core/js_stmt_make.mli

+1-1
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-
(Lambda.as_value * J.case_clause) list ->
80+
(Lambda.literal * J.case_clause) list ->
8181
t
8282

8383
val declare_variable :

0 commit comments

Comments
 (0)