Skip to content

Commit e1adb1a

Browse files
committed
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.
1 parent 5d0edd3 commit e1adb1a

16 files changed

+401
-35
lines changed

jscomp/core/js_dump.ml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -774,13 +774,13 @@ and expression_desc cxt ~(level : int) f x : cxt =
774774
( Js_op.Lit L.tag,
775775
match Ast_attributes.process_as_value p.attrs with
776776
| None -> E.str p.name
777-
| Some (AsString s) -> E.str s
778-
| Some (AsInt i) -> E.small_int i )
777+
| Some as_value -> E.as_value as_value )
779778
:: tails
780779
in
781780
expression_desc cxt ~level f (Object objs)
782781
| Caml_block (el, _, tag, Blk_constructor p) ->
783782
let not_is_cons = p.name <> Literals.cons in
783+
let as_value = Ast_attributes.process_as_value p.attrs in
784784
let objs =
785785
let tails =
786786
Ext_list.mapi_append el
@@ -794,16 +794,19 @@ and expression_desc cxt ~(level : int) f x : cxt =
794794
[ (name_symbol, E.str p.name) ]
795795
else [])
796796
in
797-
if not_is_cons = false && p.num_nonconst = 1 then tails
797+
if (as_value = Some AsUnboxed || not_is_cons = false) && p.num_nonconst = 1 then tails
798798
else
799799
( Js_op.Lit L.tag,
800-
match Ast_attributes.process_as_value p.attrs with
800+
match as_value with
801801
| None -> E.str p.name
802-
| Some (AsString s) -> E.str s
803-
| Some (AsInt i) -> E.small_int i )
802+
| Some as_value -> E.as_value as_value )
804803
:: tails
805804
in
806-
expression_desc cxt ~level f (Object objs)
805+
let exp = match objs with
806+
| [(_, e)] when as_value = Some AsUnboxed -> e.expression_desc
807+
| _ when as_value = Some AsUnboxed -> assert false (* should not happen *)
808+
| _ -> J.Object objs in
809+
expression_desc cxt ~level f exp
807810
| Caml_block
808811
( _,
809812
_,
@@ -1195,12 +1198,10 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
11951198
let cxt = P.paren_group f 1 (fun _ -> expression ~level:0 cxt f e) in
11961199
P.space f;
11971200
P.brace_vgroup f 1 (fun _ ->
1198-
let pp_string f (as_value: Lambda.as_value) =
1199-
let e = match as_value with
1200-
| AsString txt -> E.str txt ~delim:DStarJ
1201-
| AsInt i -> E.small_int i in
1201+
let pp_as_value f (as_value: Lambda.as_value) =
1202+
let e = E.as_value as_value in
12021203
ignore @@ expression_desc cxt ~level:0 f e.expression_desc in
1203-
let cxt = loop_case_clauses cxt f pp_string cc in
1204+
let cxt = loop_case_clauses cxt f pp_as_value cc in
12041205
match def with
12051206
| None -> cxt
12061207
| Some def ->

jscomp/core/js_exp_make.ml

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

319+
let as_value = function
320+
| Lambda.AsString s -> str s ~delim:DStarJ
321+
| AsInt i -> small_int i
322+
| AsNull -> nil
323+
| AsUndefined -> undefined
324+
| AsUnboxed -> assert false (* Should not emit tags for unboxed *)
325+
319326
let array_index ?comment (e0 : t) (e1 : t) : t =
320327
match (e0.expression_desc, e1.expression_desc) with
321328
| Array (l, _), Number (Int { i; _ })
@@ -762,8 +769,26 @@ let string_equal ?comment (e0 : t) (e1 : t) : t =
762769
let is_type_number ?comment (e : t) : t =
763770
string_equal ?comment (typeof e) (str "number")
764771

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

768793
let is_type_string ?comment (e : t) : t =
769794
string_equal ?comment (typeof e) (str "string")

jscomp/core/js_exp_make.mli

Lines changed: 4 additions & 1 deletion
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

jscomp/core/js_stmt_make.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ let string_switch ?(comment : string option)
138138
match switch_case with
139139
| AsString s ->
140140
if s = txt then Some x.switch_body else None
141-
| AsInt _ -> None)
141+
| AsInt _ | AsNull | AsUnboxed | AsUndefined -> None)
142142
with
143143
| Some case -> case
144144
| None -> ( match default with Some x -> x | None -> assert false)

jscomp/core/lam_compile.ml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ let get_const_name i (sw_names : Lambda.switch_names option) =
144144
let get_block_name i (sw_names : Lambda.switch_names option) =
145145
match sw_names with None -> None | Some { blocks } -> Some blocks.(i)
146146

147+
148+
let has_null_undefined_other (sw_names : Lambda.switch_names option) =
149+
let (null, undefined, other) = (ref false, ref false, ref false) in
150+
(match sw_names with
151+
| None -> ()
152+
| Some { consts } ->
153+
Ext_array.iter consts (fun x -> match x.as_value with
154+
| Some AsUndefined -> undefined := true
155+
| Some AsNull -> null := true
156+
| _ -> other := true));
157+
(!null, !undefined, !other)
158+
147159
let no_effects_const = lazy true
148160
(* let has_effects_const = lazy false *)
149161

@@ -632,7 +644,7 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch)
632644
else
633645
(* [e] will be used twice *)
634646
let dispatch e =
635-
S.if_ (E.is_tag e)
647+
S.if_ (E.is_tag ~has_null_undefined_other:(has_null_undefined_other sw_names) e)
636648
(compile_cases cxt e sw_consts sw_num_default get_const_name)
637649
(* default still needed, could simplified*)
638650
~else_:
@@ -667,9 +679,7 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch)
667679
and compile_string_cases cxt switch_exp table default =
668680
compile_general_cases
669681
(fun _ -> None)
670-
(fun (as_value: Lambda.as_value) -> match as_value with
671-
| AsString s -> E.str s ~delim:DStarJ
672-
| AsInt i -> E.small_int i)
682+
E.as_value
673683
E.string_equal cxt
674684
(fun ?default ?declaration e clauses ->
675685
S.string_switch ?default ?declaration e clauses)

jscomp/core/lam_compile_const.ml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ and translate (x : Lam_constant.t) : J.expression =
5050
| Const_int { i; comment = Pt_constructor {cstr_name={name; as_value=None}}} when name <> "[]" ->
5151
E.str name
5252
| Const_int { i; comment = Pt_constructor {cstr_name={as_value = Some as_value}}} ->
53-
( match as_value with
54-
| AsString s -> E.str s
55-
| AsInt i -> E.small_int i)
53+
E.as_value as_value
5654
| Const_int { i; comment } ->
5755
E.int i ?comment:(Lam_constant.string_of_pointer_info comment)
5856
| Const_char i -> Js_of_lam_string.const_char i

jscomp/frontend/ast_attributes.ml

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,39 @@ let iter_process_bs_string_or_int_as (attrs : Parsetree.attributes) =
334334
| _ -> ());
335335
!st
336336

337-
let process_as_value attrs : Lambda.as_value option =
338-
match iter_process_bs_string_or_int_as attrs with
339-
| None -> None
340-
| Some (Str (s, _)) -> Some (AsString s)
341-
| Some (Int i) -> Some (AsInt i)
342-
337+
let process_as_value (attrs : t) =
338+
let st : Lambda.as_value option ref = ref None in
339+
Ext_list.iter attrs (fun (({ txt; loc }, payload) as attr) ->
340+
match txt with
341+
| "bs.as" | "as" ->
342+
if !st = None then (
343+
(match Ast_payload.is_single_string payload with
344+
| None -> ()
345+
| Some (s, _dec) ->
346+
Bs_ast_invariant.mark_used_bs_attribute attr;
347+
st := Some (AsString s));
348+
(match Ast_payload.is_single_int payload with
349+
| None -> ()
350+
| Some i ->
351+
Bs_ast_invariant.mark_used_bs_attribute attr;
352+
st := Some (AsInt i));
353+
(match Ast_payload.is_single_ident payload with
354+
| None -> ()
355+
| Some Lident "null" ->
356+
Bs_ast_invariant.mark_used_bs_attribute attr;
357+
st := Some AsNull
358+
| Some Lident "undefined" ->
359+
Bs_ast_invariant.mark_used_bs_attribute attr;
360+
st := Some AsUndefined
361+
| Some Lident "unboxed" ->
362+
Bs_ast_invariant.mark_used_bs_attribute attr;
363+
st := Some AsUnboxed
364+
| Some _ -> Bs_syntaxerr.err loc InvalidVariantAsAnnotation);
365+
if !st = None then Bs_syntaxerr.err loc InvalidVariantAsAnnotation
366+
)
367+
else Bs_syntaxerr.err loc Duplicated_bs_as
368+
| _ -> ());
369+
!st
343370

344371
let locg = Location.none
345372
(* let bs : attr

jscomp/frontend/ast_payload.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ let is_single_int (x : t) : int option =
6969
Some (int_of_string name)
7070
| _ -> None
7171

72+
let is_single_ident (x : t) = match x with
73+
| PStr
74+
[
75+
{
76+
pstr_desc =
77+
Pstr_eval
78+
({ pexp_desc = Pexp_ident lid }, _);
79+
_;
80+
};
81+
] ->
82+
Some lid.txt
83+
| _ -> None
84+
85+
7286
let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) :
7387
Parsetree.expression option =
7488
match x with

jscomp/frontend/ast_payload.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ val is_single_string_as_ast : t -> Parsetree.expression option
3939

4040
val is_single_int : t -> int option
4141

42+
val is_single_ident : t -> Longident.t option
43+
4244
val raw_as_string_exp_exn :
4345
kind:Js_raw_info.raw_kind ->
4446
?is_function:bool ref ->

jscomp/frontend/bs_syntaxerr.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type error =
5151
| Optional_in_uncurried_bs_attribute
5252
| Bs_this_simple_pattern
5353
| Bs_uncurried_arity_too_large
54+
| InvalidVariantAsAnnotation
5455

5556
let pp_error fmt err =
5657
Format.pp_print_string fmt
@@ -80,7 +81,7 @@ let pp_error fmt err =
8081
| Duplicated_bs_deriving -> "duplicate bs.deriving attribute"
8182
| Conflict_attributes -> "conflicting attributes "
8283
| Expect_string_literal -> "expect string literal "
83-
| Duplicated_bs_as -> "duplicate %@as "
84+
| Duplicated_bs_as -> "duplicate @as "
8485
| Expect_int_literal -> "expect int literal "
8586
| Expect_int_or_string_or_json_literal ->
8687
"expect int, string literal or json literal {json|text here|json} "
@@ -96,7 +97,10 @@ let pp_error fmt err =
9697
each constructor must have an argument."
9798
| Conflict_ffi_attribute str -> "Conflicting attributes: " ^ str
9899
| Bs_this_simple_pattern ->
99-
"%@this expect its pattern variable to be simple form")
100+
"%@this expect its pattern variable to be simple form"
101+
| InvalidVariantAsAnnotation ->
102+
"A variant case annotation @as(...) must be a string or integer or null"
103+
)
100104

101105
type exn += Error of Location.t * error
102106

jscomp/frontend/bs_syntaxerr.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type error =
5151
| Optional_in_uncurried_bs_attribute
5252
| Bs_this_simple_pattern
5353
| Bs_uncurried_arity_too_large
54+
| InvalidVariantAsAnnotation
5455

5556
val err : Location.t -> error -> 'a
5657

jscomp/ml/lambda.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type record_repr =
3838
| Record_regular
3939
| Record_optional
4040

41-
type as_value = AsString of string | AsInt of int
41+
type as_value = AsString of string | AsInt of int | AsNull | AsUndefined | AsUnboxed
4242
type cstr_name = {name: string; as_value: as_value option}
4343

4444
type tag_info =

jscomp/ml/lambda.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type record_repr =
3838
| Record_regular
3939
| Record_optional
4040

41-
type as_value = AsString of string | AsInt of int
41+
type as_value = AsString of string | AsInt of int | AsNull | AsUndefined | AsUnboxed
4242
type cstr_name = {name:string; as_value: as_value option}
4343

4444
type tag_info =

jscomp/ml/matching.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1330,7 +1330,11 @@ let make_constr_matching p def ctx = function
13301330
| ((arg, _mut) :: argl) ->
13311331
let cstr = pat_as_constr p in
13321332
let newargs =
1333-
if cstr.cstr_inlined <> None then
1333+
if cstr.cstr_inlined <> None ||
1334+
Ext_list.exists cstr.cstr_attributes (function
1335+
| ({txt="as"}, PStr [{pstr_desc = Pstr_eval
1336+
({pexp_desc = Pexp_ident {txt= Lident "unboxed"}}, _)}]) -> true
1337+
| _ -> false) then
13341338
(arg, Alias) :: argl
13351339
else match cstr.cstr_tag with
13361340
| Cstr_block _ when

0 commit comments

Comments
 (0)