diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcdea36bc..7f8870b480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Make uncurried functions a subtype of curried functions, and allow application f The `make` function of components is generated as an uncurried function. Use best effort to determine the config when formatting a file. 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 +- 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 #### :boom: Breaking Change diff --git a/jscomp/core/j.ml b/jscomp/core/j.ml index 562246c6b2..815779fa2f 100644 --- a/jscomp/core/j.ml +++ b/jscomp/core/j.ml @@ -151,17 +151,7 @@ and expression_desc = (* | Caml_uninitialized_obj of expression * expression *) (* [tag] and [size] tailed for [Obj.new_block] *) - (* For setter, it still return the value of expression, - we can not use - {[ - type 'a access = Get | Set of 'a - ]} - in another module, since it will break our code generator - [Caml_block_tag] can return [undefined], - you have to use [E.tag] in a safe way - *) - | Caml_block_tag of expression - (* | Caml_block_set_tag of expression * expression *) + | Caml_block_tag of expression * string (* e.tag *) (* | Caml_block_set_length of expression * expression *) (* It will just fetch tag, to make it safe, when creating it, we need apply "|0", we don't do it in the @@ -254,7 +244,7 @@ and case_clause = { comment : string option; } -and string_clause = string * case_clause +and string_clause = Lambda.as_value * case_clause and int_clause = int * case_clause and statement_desc = diff --git a/jscomp/core/js_analyzer.ml b/jscomp/core/js_analyzer.ml index cd4efa8915..a4ca28e105 100644 --- a/jscomp/core/js_analyzer.ml +++ b/jscomp/core/js_analyzer.ml @@ -101,10 +101,9 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) = | Optional_block (x, _) -> no_side_effect x | Object kvs -> Ext_list.for_all_snd kvs no_side_effect | String_append (a, b) | Seq (a, b) -> no_side_effect a && no_side_effect b - | Length (e, _) | Caml_block_tag e | Typeof e -> no_side_effect e + | Length (e, _) | Caml_block_tag (e, _) | Typeof e -> no_side_effect e | Bin (op, a, b) -> op <> Eq && no_side_effect a && no_side_effect b | Js_not _ | Cond _ | FlatCall _ | Call _ | New _ | Raw_js_code _ - (* | Caml_block_set_tag _ *) (* actually true? *) -> false | Await _ -> false diff --git a/jscomp/core/js_dump.ml b/jscomp/core/js_dump.ml index c358a5910b..2a84bfe921 100644 --- a/jscomp/core/js_dump.ml +++ b/jscomp/core/js_dump.ml @@ -762,6 +762,9 @@ and expression_desc cxt ~(level : int) f x : cxt = | Lit n -> Ext_list.mem_string p.optional_labels n | Symbol_name -> false in + let tag_name = match Ast_attributes.process_tag_name p.attrs with + | None -> L.tag + | Some s -> s in let tails = match p.optional_labels with | [] -> tails @@ -771,11 +774,19 @@ and expression_desc cxt ~(level : int) f x : cxt = | Undefined when is_optional f -> None | _ -> Some (f, x)) in - (Js_op.Lit L.tag, E.str p.name) :: tails + ( Js_op.Lit tag_name, (* TAG:xx for inline records *) + match Ast_attributes.process_as_value p.attrs with + | None -> E.str p.name + | Some as_value -> E.as_value as_value ) + :: tails in expression_desc cxt ~level f (Object objs) | Caml_block (el, _, tag, Blk_constructor p) -> let not_is_cons = p.name <> Literals.cons in + let as_value = Ast_attributes.process_as_value p.attrs in + let tag_name = match Ast_attributes.process_tag_name p.attrs with + | None -> L.tag + | Some s -> s in let objs = let tails = Ext_list.mapi_append el @@ -789,14 +800,20 @@ and expression_desc cxt ~(level : int) f x : cxt = [ (name_symbol, E.str p.name) ] else []) in - if not_is_cons = false && p.num_nonconst = 1 then tails + if (as_value = Some AsUnboxed || not_is_cons = false) && p.num_nonconst = 1 then tails else - ( Js_op.Lit L.tag, - E.str p.name - ) + ( Js_op.Lit tag_name, (* TAG:xx *) + match as_value with + | None -> E.str p.name + | Some as_value -> E.as_value as_value ) :: tails in - expression_desc cxt ~level f (Object objs) + let exp = match objs with + | [(_, e)] when as_value = Some AsUnboxed -> e.expression_desc + | _ when as_value = Some AsUnboxed -> assert false (* should not happen *) + (* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *) + | _ -> J.Object objs in + expression_desc cxt ~level f exp | Caml_block ( _, _, @@ -806,11 +823,11 @@ and expression_desc cxt ~(level : int) f x : cxt = assert false | Caml_block (el, mutable_flag, _tag, Blk_tuple) -> expression_desc cxt ~level f (Array (el, mutable_flag)) - | Caml_block_tag e -> + | Caml_block_tag (e, tag) -> P.group f 1 (fun _ -> let cxt = expression ~level:15 cxt f e in P.string f L.dot; - P.string f L.tag; + P.string f tag; cxt) | Array_index (e, p) -> P.cond_paren_group f (level > 15) 1 (fun _ -> @@ -1188,8 +1205,10 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = let cxt = P.paren_group f 1 (fun _ -> expression ~level:0 cxt f e) in P.space f; P.brace_vgroup f 1 (fun _ -> - let pp_string f txt = ignore @@ expression_desc cxt ~level:0 f (Str {txt; delim=DStarJ}) in - let cxt = loop_case_clauses cxt f pp_string cc in + let pp_as_value f (as_value: Lambda.as_value) = + let e = E.as_value as_value in + ignore @@ expression_desc cxt ~level:0 f e.expression_desc in + let cxt = loop_case_clauses cxt f pp_as_value cc in match def with | None -> cxt | Some def -> diff --git a/jscomp/core/js_exp_make.ml b/jscomp/core/js_exp_make.ml index 89fb160063..51305a64cf 100644 --- a/jscomp/core/js_exp_make.ml +++ b/jscomp/core/js_exp_make.ml @@ -316,6 +316,19 @@ let small_int i : t = | 248 -> obj_int_tag_literal | i -> int (Int32.of_int i) +let true_ : t = { comment = None; expression_desc = Bool true } +let false_ : t = { comment = None; expression_desc = Bool false } +let bool v = if v then true_ else false_ + +let as_value = function + | Lambda.AsString s -> str s ~delim:DStarJ + | AsInt i -> small_int i + | AsBool b -> bool b + | AsNull -> nil + | AsUndefined -> undefined + | AsUnboxed -> assert false (* Should not emit tags for unboxed *) + (* TODO: put restriction on the variant definitions allowed, to make sure this never happens. *) + let array_index ?comment (e0 : t) (e1 : t) : t = match (e0.expression_desc, e1.expression_desc) with | Array (l, _), Number (Int { i; _ }) @@ -540,13 +553,6 @@ let obj ?comment properties : t = (* currently only in method call, no dependency introduced *) -(* Static_index .....................**) - -(* var (Jident.create_js "true") *) -let true_ : t = { comment = None; expression_desc = Bool true } -let false_ : t = { comment = None; expression_desc = Bool false } -let bool v = if v then true_ else false_ - (** Arith operators *) (* Static_index .....................**) @@ -762,8 +768,26 @@ let string_equal ?comment (e0 : t) (e1 : t) : t = let is_type_number ?comment (e : t) : t = string_equal ?comment (typeof e) (str "number") -let is_tag (e : t) : t = - { expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None } +let is_tag ?(has_null_undefined_other=(false, false, false)) (e : t) : t = + let (has_null, has_undefined, has_other) = has_null_undefined_other in + if has_null && (has_undefined = false) && (has_other = false) then (* null *) + { expression_desc = Bin (EqEqEq, e, nil); comment=None } + else if has_null && has_undefined && has_other=false then (* null + undefined *) + { J.expression_desc = Bin + (Or, + { expression_desc = Bin (EqEqEq, e, nil); comment=None }, + { expression_desc = Bin (EqEqEq, e, undefined); comment=None } + ); comment=None } + else if has_null=false && has_undefined && has_other=false then (* undefined *) + { expression_desc = Bin (EqEqEq, e, undefined); comment=None } + else if has_null then (* (null + undefined + other) || (null + other) *) + { J.expression_desc = Bin + (Or, + { expression_desc = Bin (EqEqEq, e, nil); comment=None }, + { expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None } + ); comment=None } + else (* (undefiled + other) || other *) + { expression_desc = Bin (NotEqEq, typeof e, str "object"); comment=None } let is_type_string ?comment (e : t) : t = string_equal ?comment (typeof e) (str "string") @@ -775,8 +799,8 @@ let is_type_object (e : t) : t = string_equal (typeof e) (str "object") call plain [dot] *) -let tag ?comment e : t = - { expression_desc = Caml_block_tag e; comment } +let tag ?comment ?(name=Js_dump_lit.tag) e : t = + { expression_desc = Caml_block_tag (e, name); comment } (* according to the compiler, [Btype.hash_variant], it's reduced to 31 bits for hash diff --git a/jscomp/core/js_exp_make.mli b/jscomp/core/js_exp_make.mli index 00959c53ae..565b03d27b 100644 --- a/jscomp/core/js_exp_make.mli +++ b/jscomp/core/js_exp_make.mli @@ -185,6 +185,8 @@ val assign_by_exp : t -> t -> t -> t val assign : ?comment:string -> t -> t -> t +val as_value : Lambda.as_value -> t + val triple_equal : ?comment:string -> t -> t -> t (* TODO: reduce [triple_equal] use *) @@ -199,7 +201,8 @@ val eq_null_undefined_boolean : ?comment:string -> t -> t -> t val neq_null_undefined_boolean : ?comment:string -> t -> t -> t val is_type_number : ?comment:string -> t -> t -val is_tag : t -> t + +val is_tag : ?has_null_undefined_other:(bool * bool * bool) -> t -> t val is_type_string : ?comment:string -> t -> t @@ -304,7 +307,7 @@ val unit : t val undefined : t -val tag : ?comment:string -> J.expression -> t +val tag : ?comment:string -> ?name:string -> J.expression -> t (** Note that this is coupled with how we encode block, if we use the `Object.defineProperty(..)` since the array already hold the length, diff --git a/jscomp/core/js_fold.ml b/jscomp/core/js_fold.ml index b29368f8f9..42d8410ba6 100644 --- a/jscomp/core/js_fold.ml +++ b/jscomp/core/js_fold.ml @@ -162,7 +162,7 @@ class fold = let _self = list (fun _self -> _self#expression) _self _x0 in let _self = _self#expression _x2 in _self - | Caml_block_tag _x0 -> + | Caml_block_tag (_x0, _tag) -> let _self = _self#expression _x0 in _self | Number _ -> _self diff --git a/jscomp/core/js_of_lam_variant.ml b/jscomp/core/js_of_lam_variant.ml index 1f2bbc0235..6c3b6fb499 100644 --- a/jscomp/core/js_of_lam_variant.ml +++ b/jscomp/core/js_of_lam_variant.ml @@ -39,8 +39,8 @@ let eval (arg : J.expression) (dispatches : (string * string) list) : E.t = E.of_block [ S.string_switch arg - (Ext_list.map dispatches (fun (i, r) -> - ( i, + (Ext_list.map dispatches (fun (s, r) -> + ( Lambda.AsString s, J. { switch_body = [ S.return_stmt (E.str r) ]; @@ -79,8 +79,8 @@ let eval_as_event (arg : J.expression) [ S.string_switch (E.poly_var_tag_access arg) - (Ext_list.map dispatches (fun (i, r) -> - ( i, + (Ext_list.map dispatches (fun (s, r) -> + ( Lambda.AsString s, J. { 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 = E.of_block [ S.string_switch arg - (Ext_list.map dispatches (fun (i, r) -> - ( i, + (Ext_list.map dispatches (fun (s, r) -> + ( Lambda.AsString s, J. { switch_body = diff --git a/jscomp/core/js_record_fold.ml b/jscomp/core/js_record_fold.ml index 8ed0ab31eb..04b4f546ba 100644 --- a/jscomp/core/js_record_fold.ml +++ b/jscomp/core/js_record_fold.ml @@ -168,7 +168,7 @@ let expression_desc : 'a. ('a, expression_desc) fn = let st = list _self.expression _self st _x0 in let st = _self.expression _self st _x2 in st - | Caml_block_tag _x0 -> + | Caml_block_tag (_x0, _tag) -> let st = _self.expression _self st _x0 in st | Number _ -> st diff --git a/jscomp/core/js_record_iter.ml b/jscomp/core/js_record_iter.ml index cd93ddf495..e7a0cdfddd 100644 --- a/jscomp/core/js_record_iter.ml +++ b/jscomp/core/js_record_iter.ml @@ -128,7 +128,7 @@ let expression_desc : expression_desc fn = | Caml_block (_x0, _x1, _x2, _x3) -> list _self.expression _self _x0; _self.expression _self _x2 - | Caml_block_tag _x0 -> _self.expression _self _x0 + | Caml_block_tag (_x0, _tag) -> _self.expression _self _x0 | Number _ -> () | Object _x0 -> property_map _self _x0 | Undefined -> () diff --git a/jscomp/core/js_record_map.ml b/jscomp/core/js_record_map.ml index a4d3335b63..85a8ee2e5c 100644 --- a/jscomp/core/js_record_map.ml +++ b/jscomp/core/js_record_map.ml @@ -166,9 +166,9 @@ let expression_desc : expression_desc fn = let _x0 = list _self.expression _self _x0 in let _x2 = _self.expression _self _x2 in Caml_block (_x0, _x1, _x2, _x3) - | Caml_block_tag _x0 -> + | Caml_block_tag (_x0, tag) -> let _x0 = _self.expression _self _x0 in - Caml_block_tag _x0 + Caml_block_tag (_x0, tag) | Number _ as v -> v | Object _x0 -> let _x0 = property_map _self _x0 in diff --git a/jscomp/core/js_stmt_make.ml b/jscomp/core/js_stmt_make.ml index 4a8968375c..7af2c46912 100644 --- a/jscomp/core/js_stmt_make.ml +++ b/jscomp/core/js_stmt_make.ml @@ -129,13 +129,16 @@ let int_switch ?(comment : string option) let string_switch ?(comment : string option) ?(declaration : (J.property * Ident.t) option) ?(default : J.block option) - (e : J.expression) (clauses : (string * J.case_clause) list) : t = + (e : J.expression) (clauses : (Lambda.as_value * J.case_clause) list) : t = match e.expression_desc with | Str {txt} -> ( let continuation = match Ext_list.find_opt clauses (fun (switch_case, x) -> - if switch_case = txt then Some x.switch_body else None) + match switch_case with + | AsString s -> + if s = txt then Some x.switch_body else None + | AsInt _ | AsBool _ | AsNull | AsUnboxed | AsUndefined -> None) with | Some case -> case | None -> ( match default with Some x -> x | None -> assert false) diff --git a/jscomp/core/js_stmt_make.mli b/jscomp/core/js_stmt_make.mli index 2b800c966f..adda763c25 100644 --- a/jscomp/core/js_stmt_make.mli +++ b/jscomp/core/js_stmt_make.mli @@ -77,7 +77,7 @@ val string_switch : ?declaration:Lam_compat.let_kind * Ident.t -> ?default:J.block -> J.expression -> - (string * J.case_clause) list -> + (Lambda.as_value * J.case_clause) list -> t val declare_variable : diff --git a/jscomp/core/lam_compile.ml b/jscomp/core/lam_compile.ml index 06c3055d0d..189bd1559b 100644 --- a/jscomp/core/lam_compile.ml +++ b/jscomp/core/lam_compile.ml @@ -141,9 +141,29 @@ let default_action ~saturated failaction = let get_const_name i (sw_names : Lambda.switch_names option) = match sw_names with None -> None | Some { consts } -> Some consts.(i) -let get_block_name i (sw_names : Lambda.switch_names option) = +let get_block i (sw_names : Lambda.switch_names option) = match sw_names with None -> None | Some { blocks } -> Some blocks.(i) +let get_tag_name (sw_names : Lambda.switch_names option) = + match sw_names with + | None -> Js_dump_lit.tag + | Some { blocks } -> + (match Array.find_opt (fun {Lambda.tag_name} -> tag_name <> None) blocks with + | Some {tag_name = Some s} -> s + | _ -> Js_dump_lit.tag + ) + +let has_null_undefined_other (sw_names : Lambda.switch_names option) = + let (null, undefined, other) = (ref false, ref false, ref false) in + (match sw_names with + | None -> () + | Some { consts } -> + Ext_array.iter consts (fun x -> match x.as_value with + | Some AsUndefined -> undefined := true + | Some AsNull -> null := true + | _ -> other := true)); + (!null, !undefined, !other) + let no_effects_const = lazy true (* let has_effects_const = lazy false *) @@ -453,8 +473,8 @@ and compile_recursive_lets cxt id_args : Js_output.t = Js_output.append_output acc (compile_recursive_lets_aux cxt x))) and compile_general_cases : - 'a. - ('a -> string option) -> + 'a . + ('a -> Lambda.cstr_name option) -> ('a -> J.expression) -> (J.expression -> J.expression -> J.expression) -> Lam_compile_context.t -> @@ -467,7 +487,7 @@ and compile_general_cases : ('a * Lam.t) list -> default_case -> J.block = - fun (make_comment : _ -> string option) (make_exp : _ -> J.expression) + fun (get_cstr_name : _ -> Lambda.cstr_name option) (make_exp : _ -> J.expression) (eq_exp : J.expression -> J.expression -> J.expression) (cxt : Lam_compile_context.t) (switch : @@ -531,6 +551,9 @@ and compile_general_cases : | Default lam -> Some (Js_output.output_as_block (compile_lambda cxt lam)) in + let make_comment i = match get_cstr_name i with + | None -> None + | Some {name} -> Some name in let body = group_apply cases (fun last (switch_case, lam) -> if last then @@ -567,23 +590,27 @@ and compile_general_cases : [ switch ?default ?declaration switch_exp body ]) +and all_cases_have_name table get_name = + List.fold_right (fun (i, lam) acc -> + match get_name i, acc with + | Some {Lambda.as_value= Some as_value}, Some string_table -> Some ((as_value, lam) :: string_table) + | Some {name; as_value = None}, Some string_table -> Some ((AsString name, lam) :: string_table) + | _, _ -> None + ) table (Some []) and compile_cases cxt (switch_exp : E.t) table default get_name = - let string_table = table |> List.filter_map (fun (i, lam) -> match get_name i - with None -> None - | Some n -> Some (n, lam)) in - if List.length string_table = List.length table - then - compile_string_cases cxt switch_exp string_table default - else - compile_general_cases get_name - (fun i -> match get_name i with - | None -> E.small_int i - | Some name -> E.str name) - E.int_equal cxt - (fun ?default ?declaration e clauses -> - S.int_switch ?default ?declaration e clauses) - switch_exp table default - + match all_cases_have_name table get_name with + | Some string_table -> compile_string_cases cxt switch_exp string_table default + | None -> + compile_general_cases get_name + (fun i -> match get_name i with + | None -> E.small_int i + | Some {as_value = Some(AsString s)} -> E.str s + | Some {name} -> E.str name) + E.int_equal cxt + (fun ?default ?declaration e clauses -> + S.int_switch ?default ?declaration e clauses) + switch_exp table default + and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch) (lambda_cxt : Lam_compile_context.t) = (* TODO: if default is None, we can do some optimizations @@ -609,7 +636,11 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch) default_action ~saturated:sw_blocks_full sw_failaction in let get_const_name i = get_const_name i sw_names in - let get_block_name i = get_block_name i sw_names in + let get_block i = get_block i sw_names in + let get_block_name i = match get_block i with + | None -> None + | Some {cstr_name} -> Some cstr_name in + let tag_name = get_tag_name sw_names in let compile_whole (cxt : Lam_compile_context.t) = match compile_lambda { cxt with continuation = NeedValue Not_tail } switch_arg @@ -619,17 +650,17 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch) block @ if sw_consts_full && sw_consts = [] then - compile_cases cxt (E.tag e) sw_blocks sw_blocks_default get_block_name + compile_cases cxt (E.tag ~name:tag_name e) sw_blocks sw_blocks_default get_block_name else if sw_blocks_full && sw_blocks = [] then compile_cases cxt e sw_consts sw_num_default get_const_name else (* [e] will be used twice *) let dispatch e = - S.if_ (E.is_tag e) + S.if_ (E.is_tag ~has_null_undefined_other:(has_null_undefined_other sw_names) e) (compile_cases cxt e sw_consts sw_num_default get_const_name) (* default still needed, could simplified*) ~else_: - (compile_cases cxt (E.tag e) sw_blocks sw_blocks_default + (compile_cases cxt (E.tag ~name:tag_name e) sw_blocks sw_blocks_default get_block_name) in match e.expression_desc with @@ -660,7 +691,8 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch) and compile_string_cases cxt switch_exp table default = compile_general_cases (fun _ -> None) - (fun str -> E.str str ~delim:DStarJ) E.string_equal cxt + E.as_value + E.string_equal cxt (fun ?default ?declaration e clauses -> S.string_switch ?default ?declaration e clauses) switch_exp table default @@ -672,6 +704,7 @@ and compile_stringswitch l cases default (lambda_cxt : Lam_compile_context.t) = Be careful: we should avoid multiple evaluation of l, The [gen] can be elimiated when number of [cases] is less than 3 *) + let cases = cases |> List.map (fun (s,l) -> Lambda.AsString s, l) in match compile_lambda { lambda_cxt with continuation = NeedValue Not_tail } l with diff --git a/jscomp/core/lam_compile_const.ml b/jscomp/core/lam_compile_const.ml index 75aec054e0..538f209113 100644 --- a/jscomp/core/lam_compile_const.ml +++ b/jscomp/core/lam_compile_const.ml @@ -47,8 +47,10 @@ and translate (x : Lam_constant.t) : J.expression = | Const_js_false -> E.bool false | Const_js_null -> E.nil | Const_js_undefined -> E.undefined - | Const_int { i; comment = Pt_constructor {name}} when name <> "[]" -> + | Const_int { i; comment = Pt_constructor {cstr_name={name; as_value=None}}} when name <> "[]" -> E.str name + | Const_int { i; comment = Pt_constructor {cstr_name={as_value = Some as_value}}} -> + E.as_value as_value | Const_int { i; comment } -> E.int i ?comment:(Lam_constant.string_of_pointer_info comment) | Const_char i -> Js_of_lam_string.const_char i diff --git a/jscomp/core/lam_compile_primitive.ml b/jscomp/core/lam_compile_primitive.ml index 17932ff6db..6a54005e6a 100644 --- a/jscomp/core/lam_compile_primitive.ml +++ b/jscomp/core/lam_compile_primitive.ml @@ -300,7 +300,7 @@ let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t) (* 2 ^ 32 - 1*) | Backend_type -> E.make_block E.zero_int_literal - (Blk_constructor { name = "Other"; num_nonconst = 1; tag = 0 }) + (Blk_constructor { name = "Other"; num_nonconst = 1; tag = 0; attrs = [] }) [ E.str "BS" ] Immutable) | Pduprecord -> Lam_dispatch_primitive.translate loc "?obj_dup" args | Plazyforce diff --git a/jscomp/core/lam_constant_convert.ml b/jscomp/core/lam_constant_convert.ml index 50b76e2ee2..7fe9a4ca51 100644 --- a/jscomp/core/lam_constant_convert.ml +++ b/jscomp/core/lam_constant_convert.ml @@ -48,11 +48,12 @@ let rec convert_constant (const : Lambda.structured_constant) : Lam_constant.t = | Pt_shape_none -> Lam_constant.lam_none | Pt_assertfalse -> Const_int { i = Int32.of_int i; comment = Pt_assertfalse } - | Pt_constructor { name; const; non_const } -> + | Pt_constructor { name; const; non_const; attrs } -> + let as_value = Ast_attributes.process_as_value attrs in Const_int { i = Int32.of_int i; - comment = Pt_constructor { name; const; non_const }; + comment = Pt_constructor { cstr_name={name; as_value}; const; non_const }; } | Pt_variant { name } -> if Ext_string.is_valid_hash_number name then diff --git a/jscomp/core/lam_convert.ml b/jscomp/core/lam_convert.ml index 87266c46d3..e3d0b1d4f1 100644 --- a/jscomp/core/lam_convert.ml +++ b/jscomp/core/lam_convert.ml @@ -473,7 +473,7 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : | "#makemutablelist" -> Pmakeblock ( 0, - Blk_constructor { name = "::"; num_nonconst = 1; tag = 0 }, + Blk_constructor { name = "::"; num_nonconst = 1; tag = 0; attrs = [] }, Mutable ) | "#undefined_to_opt" -> Pundefined_to_opt | "#nullable_to_opt" -> Pnull_undefined_to_opt diff --git a/jscomp/core/lam_print.ml b/jscomp/core/lam_print.ml index b6cb43989a..16200fd72c 100644 --- a/jscomp/core/lam_print.ml +++ b/jscomp/core/lam_print.ml @@ -313,14 +313,14 @@ let lambda ppf v = (fun (n, l) -> if !spc then fprintf ppf "@ " else spc := true; fprintf ppf "@[case int %i %S:@ %a@]" n - (match sw.sw_names with None -> "" | Some x -> x.consts.(n)) + (match sw.sw_names with None -> "" | Some x -> x.consts.(n).name) lam l) sw.sw_consts; List.iter (fun (n, l) -> if !spc then fprintf ppf "@ " else spc := true; fprintf ppf "@[case tag %i %S:@ %a@]" n - (match sw.sw_names with None -> "" | Some x -> x.blocks.(n)) + (match sw.sw_names with None -> "" | Some x -> x.blocks.(n).cstr_name.name) lam l) sw.sw_blocks; match sw.sw_failaction with diff --git a/jscomp/core/matching_polyfill.ml b/jscomp/core/matching_polyfill.ml index ba32293fb2..2b6cc9e6bb 100644 --- a/jscomp/core/matching_polyfill.ml +++ b/jscomp/core/matching_polyfill.ml @@ -27,11 +27,18 @@ let is_nullary_variant (x : Types.constructor_arguments) = let names_from_construct_pattern (pat : Typedtree.pattern) = let names_from_type_variant (cstrs : Types.constructor_declaration list) = + let get_cstr_name (cstr: Types.constructor_declaration) = + { Lambda.name = Ident.name cstr.cd_id; + as_value = Ast_attributes.process_as_value cstr.cd_attributes } in + let get_tag_name (cstr: Types.constructor_declaration) = + Ast_attributes.process_tag_name cstr.cd_attributes in + let get_block cstr : Lambda.block = + {cstr_name = get_cstr_name cstr; tag_name = get_tag_name cstr} in let consts, blocks = Ext_list.fold_left cstrs ([], []) (fun (consts, blocks) cstr -> if is_nullary_variant cstr.cd_args then - (Ident.name cstr.cd_id :: consts, blocks) - else (consts, Ident.name cstr.cd_id :: blocks)) + (get_cstr_name cstr :: consts, blocks) + else (consts, get_block cstr :: blocks)) in Some { @@ -45,7 +52,6 @@ let names_from_construct_pattern (pat : Typedtree.pattern) = | { type_kind = Type_abstract; type_manifest = Some t; _ } -> ( match (Ctype.unalias t).desc with | Tconstr (pathn, _, _) -> - (* Format.eprintf "XXX path%d:%s path%d:%s@." n (Path.name path) (n+1) (Path.name pathn); *) resolve_path (n + 1) pathn | _ -> None) | { type_kind = Type_abstract; type_manifest = None; _ } -> None diff --git a/jscomp/frontend/ast_attributes.ml b/jscomp/frontend/ast_attributes.ml index 4fa0ac3792..b9ffe51bff 100644 --- a/jscomp/frontend/ast_attributes.ml +++ b/jscomp/frontend/ast_attributes.ml @@ -255,7 +255,6 @@ let iter_process_bs_string_as (attrs : t) : string option = else Bs_syntaxerr.err loc Duplicated_bs_as | _ -> ()); !st - let has_bs_optional (attrs : t) : bool = Ext_list.exists attrs (fun (({ txt }, _) as attr) -> match txt with @@ -335,6 +334,62 @@ let iter_process_bs_string_or_int_as (attrs : Parsetree.attributes) = | _ -> ()); !st +let process_as_value (attrs : t) = + let st : Lambda.as_value option ref = ref None in + Ext_list.iter attrs (fun (({ txt; loc }, payload) as attr) -> + match txt with + | "bs.as" | "as" -> + if !st = None then ( + (match Ast_payload.is_single_string payload with + | None -> () + | Some (s, _dec) -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some (AsString s)); + (match Ast_payload.is_single_int payload with + | None -> () + | Some i -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some (AsInt i)); + (match Ast_payload.is_single_bool payload with + | None -> () + | Some b -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some (AsBool b)); + (match Ast_payload.is_single_ident payload with + | None -> () + | Some Lident "null" -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some AsNull + | Some Lident "undefined" -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some AsUndefined + | Some Lident "unboxed" -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some AsUnboxed + | Some _ -> Bs_syntaxerr.err loc InvalidVariantAsAnnotation); + if !st = None then Bs_syntaxerr.err loc InvalidVariantAsAnnotation + ) + else Bs_syntaxerr.err loc Duplicated_bs_as + | _ -> ()); + !st + +let process_tag_name (attrs : t) = + let st = ref None in + Ext_list.iter attrs (fun (({ txt; loc }, payload) as attr) -> + match txt with + | "tag" -> + if !st = None then ( + (match Ast_payload.is_single_string payload with + | None -> () + | Some (s, _dec) -> + Bs_ast_invariant.mark_used_bs_attribute attr; + st := Some s); + if !st = None then Bs_syntaxerr.err loc InvalidVariantTagAnnotation + ) + else Bs_syntaxerr.err loc Duplicated_bs_as + | _ -> ()); + !st + let locg = Location.none (* let bs : attr = {txt = "bs" ; loc = locg}, Ast_payload.empty *) diff --git a/jscomp/frontend/ast_attributes.mli b/jscomp/frontend/ast_attributes.mli index 9897790dfc..c495fff9a9 100644 --- a/jscomp/frontend/ast_attributes.mli +++ b/jscomp/frontend/ast_attributes.mli @@ -91,3 +91,7 @@ val internal_expansive : attr val rs_externals : t -> string list -> bool val process_send_pipe : t -> (Parsetree.core_type * t) option + +val process_as_value : t -> Lambda.as_value option + +val process_tag_name : t -> string option \ No newline at end of file diff --git a/jscomp/frontend/ast_payload.ml b/jscomp/frontend/ast_payload.ml index 5a635b304e..afb5d333e9 100644 --- a/jscomp/frontend/ast_payload.ml +++ b/jscomp/frontend/ast_payload.ml @@ -69,6 +69,34 @@ let is_single_int (x : t) : int option = Some (int_of_string name) | _ -> None +let is_single_bool (x : t) : bool option = + match x with + | PStr + [ + { + pstr_desc = + Pstr_eval + ({ pexp_desc = Pexp_construct ({ txt = Lident ("true" | "false" as b)}, _); _ }, _); + _; + }; + ] -> + Some (b = "true") + | _ -> None + +let is_single_ident (x : t) = match x with + | PStr + [ + { + pstr_desc = + Pstr_eval + ({ pexp_desc = Pexp_ident lid }, _); + _; + }; + ] -> + Some lid.txt + | _ -> None + + let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) : Parsetree.expression option = match x with diff --git a/jscomp/frontend/ast_payload.mli b/jscomp/frontend/ast_payload.mli index d3264b94a3..96c3d9e1d7 100644 --- a/jscomp/frontend/ast_payload.mli +++ b/jscomp/frontend/ast_payload.mli @@ -39,6 +39,10 @@ val is_single_string_as_ast : t -> Parsetree.expression option val is_single_int : t -> int option +val is_single_bool : t -> bool option + +val is_single_ident : t -> Longident.t option + val raw_as_string_exp_exn : kind:Js_raw_info.raw_kind -> ?is_function:bool ref -> diff --git a/jscomp/frontend/bs_syntaxerr.ml b/jscomp/frontend/bs_syntaxerr.ml index 2065ac2f60..4ba70fcf3d 100644 --- a/jscomp/frontend/bs_syntaxerr.ml +++ b/jscomp/frontend/bs_syntaxerr.ml @@ -51,6 +51,8 @@ type error = | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Bs_uncurried_arity_too_large + | InvalidVariantAsAnnotation + | InvalidVariantTagAnnotation let pp_error fmt err = Format.pp_print_string fmt @@ -80,7 +82,7 @@ let pp_error fmt err = | Duplicated_bs_deriving -> "duplicate bs.deriving attribute" | Conflict_attributes -> "conflicting attributes " | Expect_string_literal -> "expect string literal " - | Duplicated_bs_as -> "duplicate %@as " + | Duplicated_bs_as -> "duplicate @as " | Expect_int_literal -> "expect int literal " | Expect_int_or_string_or_json_literal -> "expect int, string literal or json literal {json|text here|json} " @@ -96,7 +98,12 @@ let pp_error fmt err = each constructor must have an argument." | Conflict_ffi_attribute str -> "Conflicting attributes: " ^ str | Bs_this_simple_pattern -> - "%@this expect its pattern variable to be simple form") + "%@this expect its pattern variable to be simple form" + | InvalidVariantAsAnnotation -> + "A variant case annotation @as(...) must be a string or integer, boolean, null, undefined" + | InvalidVariantTagAnnotation -> + "A variant tag annotation @tag(...) must be a string" + ) type exn += Error of Location.t * error diff --git a/jscomp/frontend/bs_syntaxerr.mli b/jscomp/frontend/bs_syntaxerr.mli index 675a34cef6..15c39baee0 100644 --- a/jscomp/frontend/bs_syntaxerr.mli +++ b/jscomp/frontend/bs_syntaxerr.mli @@ -51,6 +51,8 @@ type error = | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Bs_uncurried_arity_too_large + | InvalidVariantAsAnnotation + | InvalidVariantTagAnnotation val err : Location.t -> error -> 'a diff --git a/jscomp/frontend/lam_constant.ml b/jscomp/frontend/lam_constant.ml index 9c87a24cdb..d0a4986e0b 100644 --- a/jscomp/frontend/lam_constant.ml +++ b/jscomp/frontend/lam_constant.ml @@ -22,7 +22,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -type constructor_tag = { name : string; const : int; non_const : int } +type constructor_tag = { cstr_name : Lambda.cstr_name; const : int; non_const : int } type pointer_info = | None @@ -32,7 +32,7 @@ type pointer_info = let string_of_pointer_info (x : pointer_info) : string option = match x with - | Some name | Pt_constructor { name; _ } -> Some name + | Some name | Pt_constructor { cstr_name={name}; _ } -> Some name | Pt_assertfalse -> Some "assert_false" | None -> None diff --git a/jscomp/frontend/lam_constant.mli b/jscomp/frontend/lam_constant.mli index 51639dd4e0..a12ba99389 100644 --- a/jscomp/frontend/lam_constant.mli +++ b/jscomp/frontend/lam_constant.mli @@ -22,7 +22,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -type constructor_tag = { name : string; const : int; non_const : int } +type constructor_tag = { cstr_name : Lambda.cstr_name; const : int; non_const : int } type pointer_info = | None diff --git a/jscomp/ml/datarepr.ml b/jscomp/ml/datarepr.ml index 105462df08..8412621f5c 100644 --- a/jscomp/ml/datarepr.ml +++ b/jscomp/ml/datarepr.ml @@ -142,7 +142,7 @@ let constructor_descrs ty_path decl cstrs = let representation = if decl.type_unboxed.unboxed then Record_unboxed true - else Record_inlined {tag = idx_nonconst; name = cstr_name; num_nonconsts = !num_nonconsts; optional_labels} + else Record_inlined {tag = idx_nonconst; name = cstr_name; num_nonconsts = !num_nonconsts; optional_labels; attrs = cd_attributes} in constructor_args decl.type_private cd_args cd_res (Path.Pdot (ty_path, cstr_name, Path.nopos)) representation diff --git a/jscomp/ml/lambda.ml b/jscomp/ml/lambda.ml index 9437253a63..cacbf4af3f 100644 --- a/jscomp/ml/lambda.ml +++ b/jscomp/ml/lambda.ml @@ -38,9 +38,12 @@ type record_repr = | Record_regular | Record_optional +type as_value = AsString of string | AsInt of int | AsBool of bool | AsNull | AsUndefined | AsUnboxed +type cstr_name = {name: string; as_value: as_value option} + type tag_info = - | Blk_constructor of {name : string ; num_nonconst : int ; tag : int } - | Blk_record_inlined of { name : string ; num_nonconst : int; tag : int; optional_labels: string list; fields : string array; mutable_flag : Asttypes.mutable_flag } + | Blk_constructor of {name : string ; num_nonconst : int ; tag : int; attrs : Parsetree.attributes } + | Blk_record_inlined of { name : string ; num_nonconst : int; tag : int; optional_labels: string list; fields : string array; mutable_flag : Asttypes.mutable_flag; attrs : Parsetree.attributes } | Blk_tuple | Blk_poly_var of string | Blk_record of {fields : string array; mutable_flag : Asttypes.mutable_flag; record_repr : record_repr} @@ -96,9 +99,9 @@ let blk_record_ext = ref (fun fields mutable_flag -> Blk_record_ext {fields = all_labels_info; mutable_flag } ) -let blk_record_inlined = ref (fun fields name num_nonconst optional_labels ~tag mutable_flag -> +let blk_record_inlined = ref (fun fields name num_nonconst optional_labels ~tag ~attrs mutable_flag -> let fields = fields |> Array.map (fun (x,_) -> x.Types.lbl_name) in - Blk_record_inlined {fields; name; num_nonconst; tag; mutable_flag; optional_labels} + Blk_record_inlined {fields; name; num_nonconst; tag; mutable_flag; optional_labels; attrs } ) let ref_tag_info : tag_info = @@ -233,12 +236,11 @@ and raise_kind = | Raise_reraise | Raise_notrace -type pointer_info = - | Pt_constructor of {name : string; const : int ; non_const : int } - | Pt_variant of {name : string} - | Pt_module_alias - - | Pt_shape_none +type pointer_info = + | Pt_constructor of {name: string; const: int; non_const: int; attrs: Parsetree.attributes} + | Pt_variant of {name: string} + | Pt_module_alias + | Pt_shape_none | Pt_assertfalse @@ -271,7 +273,8 @@ type function_attribute = { return_unit : bool; async : bool; } -type switch_names = {consts: string array; blocks: string array} +type block = {cstr_name: cstr_name; tag_name: string option} +type switch_names = {consts: cstr_name array; blocks: block array} type lambda = Lvar of Ident.t @@ -323,7 +326,9 @@ and lambda_switch = not necessary "()", it can be used as a place holder for module alias etc. *) -let const_unit = Const_pointer(0, Pt_constructor{name = "()"; const = 1; non_const = 0}) +let const_unit = + Const_pointer + (0, Pt_constructor {name = "()"; const = 1; non_const = 0; attrs = []}) let lambda_assert_false = Lconst (Const_pointer(0, Pt_assertfalse)) diff --git a/jscomp/ml/lambda.mli b/jscomp/ml/lambda.mli index 2be51ec909..2945e22095 100644 --- a/jscomp/ml/lambda.mli +++ b/jscomp/ml/lambda.mli @@ -38,9 +38,12 @@ type record_repr = | Record_regular | Record_optional +type as_value = AsString of string | AsInt of int | AsBool of bool | AsNull | AsUndefined | AsUnboxed +type cstr_name = {name:string; as_value: as_value option} + type tag_info = - | Blk_constructor of {name : string ; num_nonconst : int; tag : int} - | Blk_record_inlined of { name : string ; num_nonconst : int ; tag : int; optional_labels: string list; fields : string array; mutable_flag : mutable_flag } + | Blk_constructor of { name : string ; num_nonconst : int; tag : int; attrs : Parsetree.attributes } + | Blk_record_inlined of { name : string ; num_nonconst : int ; tag : int; optional_labels: string list; fields : string array; mutable_flag : mutable_flag; attrs : Parsetree.attributes } | Blk_tuple | Blk_poly_var of string | Blk_record of {fields : string array; mutable_flag : mutable_flag; record_repr : record_repr } @@ -86,7 +89,8 @@ val blk_record_inlined : string -> int -> string list -> - tag:int -> + tag:int -> + attrs:Parsetree.attributes -> mutable_flag -> tag_info ) ref @@ -134,15 +138,13 @@ type is_safe = | Safe | Unsafe -type pointer_info = - | Pt_constructor of {name : string; const : int ; non_const : int} - | Pt_variant of {name : string} - | Pt_module_alias - | Pt_shape_none +type pointer_info = + | Pt_constructor of {name: string; const: int; non_const: int; attrs: Parsetree.attributes} + | Pt_variant of {name: string} + | Pt_module_alias + | Pt_shape_none | Pt_assertfalse - - type primitive = | Pidentity | Pbytes_to_string @@ -274,7 +276,8 @@ type function_attribute = { async : bool; } -type switch_names = {consts: string array; blocks: string array} +type block = {cstr_name: cstr_name; tag_name: string option} +type switch_names = {consts: cstr_name array; blocks: block array} type lambda = Lvar of Ident.t diff --git a/jscomp/ml/matching.ml b/jscomp/ml/matching.ml index 558c64e335..8925357c3f 100644 --- a/jscomp/ml/matching.ml +++ b/jscomp/ml/matching.ml @@ -1330,7 +1330,11 @@ let make_constr_matching p def ctx = function | ((arg, _mut) :: argl) -> let cstr = pat_as_constr p in let newargs = - if cstr.cstr_inlined <> None then + if cstr.cstr_inlined <> None || + Ext_list.exists cstr.cstr_attributes (function + | ({txt="as"}, PStr [{pstr_desc = Pstr_eval + ({pexp_desc = Pexp_ident {txt= Lident "unboxed"}}, _)}]) -> true + | _ -> false) then (arg, Alias) :: argl else match cstr.cstr_tag with | Cstr_block _ when diff --git a/jscomp/ml/transl_recmodule.ml b/jscomp/ml/transl_recmodule.ml index 99425fbff1..3e709b6dc3 100644 --- a/jscomp/ml/transl_recmodule.ml +++ b/jscomp/ml/transl_recmodule.ml @@ -31,10 +31,10 @@ let init_shape modl = (Blk_tuple, [ x; Const_base (Const_string (Ident.name id, None)) ]) in let module_tag_info : Lambda.tag_info = - Blk_constructor { name = "Module"; num_nonconst = 2; tag = 0 } + Blk_constructor { name="Module"; num_nonconst = 2; tag = 0; attrs = [] } in let value_tag_info : Lambda.tag_info = - Blk_constructor { name = "value"; num_nonconst = 2; tag = 1 } + Blk_constructor { name = "value"; num_nonconst = 2; tag = 1; attrs = [] } in let rec init_shape_mod env mty = match Mtype.scrape env mty with @@ -61,6 +61,7 @@ let init_shape modl = name = "Function"; const = cstr_const; non_const = cstr_non_const; + attrs = []; } ) | { desc = Tconstr (p, _, _) } when Path.same p Predef.path_lazy_t -> Const_pointer @@ -70,6 +71,7 @@ let init_shape modl = name = "Lazy"; const = cstr_const; non_const = cstr_non_const; + attrs = []; } ) | _ -> raise Not_found in diff --git a/jscomp/ml/translcore.ml b/jscomp/ml/translcore.ml index 0854908dd4..470c1008e4 100644 --- a/jscomp/ml/translcore.ml +++ b/jscomp/ml/translcore.ml @@ -816,6 +816,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = name = cstr.cstr_name; const = cstr.cstr_consts; non_const = cstr.cstr_nonconsts; + attrs = cstr.cstr_attributes; } )) | Cstr_unboxed -> ( match ll with [ v ] -> v | _ -> assert false) | Cstr_block n -> ( @@ -834,6 +835,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = name = cstr.cstr_name; num_nonconst = cstr.cstr_nonconsts; tag = n; + attrs = cstr.cstr_attributes; } in try Lconst (Const_block (tag_info, List.map extract_constant ll)) @@ -1155,10 +1157,10 @@ and transl_record loc env fields repres opt_init_expr = | Record_optional_labels _ -> Lconst (Const_block (!Lambda.blk_record fields mut Record_optional, cl)) - | Record_inlined { tag; name; num_nonconsts; optional_labels } -> + | Record_inlined { tag; name; num_nonconsts; optional_labels; attrs } -> Lconst (Const_block - ( !Lambda.blk_record_inlined fields name num_nonconsts optional_labels ~tag + ( !Lambda.blk_record_inlined fields name num_nonconsts optional_labels ~tag ~attrs mut, cl )) | Record_unboxed _ -> @@ -1177,10 +1179,10 @@ and transl_record loc env fields repres opt_init_expr = ll, loc ) | Record_float_unused -> assert false - | Record_inlined { tag; name; num_nonconsts; optional_labels } -> + | Record_inlined { tag; name; num_nonconsts; optional_labels; attrs } -> Lprim ( Pmakeblock - (!Lambda.blk_record_inlined fields name num_nonconsts optional_labels ~tag + (!Lambda.blk_record_inlined fields name num_nonconsts optional_labels ~tag ~attrs mut), ll, loc ) diff --git a/jscomp/ml/typedecl.ml b/jscomp/ml/typedecl.ml index 36097256f8..c1a1e825c6 100644 --- a/jscomp/ml/typedecl.ml +++ b/jscomp/ml/typedecl.ml @@ -379,6 +379,9 @@ let transl_declaration env sdecl id = raise(Error(sdecl.ptype_loc, Duplicate_constructor name)); all_constrs := StringSet.add name !all_constrs) scstrs; + let copy_tag_attr_from_decl attr = + let tag_attr = Ext_list.filter sdecl.ptype_attributes (fun ({txt}, _) -> txt = "tag") in + if tag_attr = [] then attr else tag_attr @ attr in let make_cstr scstr = let name = Ident.create scstr.pcd_name.txt in let targs, tret_type, args, ret_type, _cstr_params = @@ -391,14 +394,14 @@ let transl_declaration env sdecl id = cd_args = targs; cd_res = tret_type; cd_loc = scstr.pcd_loc; - cd_attributes = scstr.pcd_attributes } + cd_attributes = scstr.pcd_attributes |> copy_tag_attr_from_decl } in let cstr = { Types.cd_id = name; cd_args = args; cd_res = ret_type; cd_loc = scstr.pcd_loc; - cd_attributes = scstr.pcd_attributes } + cd_attributes = scstr.pcd_attributes |> copy_tag_attr_from_decl } in tcstr, cstr in diff --git a/jscomp/ml/types.ml b/jscomp/ml/types.ml index 1dea0bec6d..0c94b4bc67 100644 --- a/jscomp/ml/types.ml +++ b/jscomp/ml/types.ml @@ -154,7 +154,7 @@ and record_representation = | Record_float_unused (* Was: all fields are floats. Now: unused *) | Record_unboxed of bool (* Unboxed single-field record, inlined or not *) | Record_inlined of (* Inlined record *) - { tag : int ; name : string; num_nonconsts : int; optional_labels : string list} + { tag : int ; name : string; num_nonconsts : int; optional_labels : string list; attrs: Parsetree.attributes} | Record_extension (* Inlined record under extension *) | Record_optional_labels of string list (* List of optional labels *) diff --git a/jscomp/ml/types.mli b/jscomp/ml/types.mli index e87361929d..eacf0b7d2b 100644 --- a/jscomp/ml/types.mli +++ b/jscomp/ml/types.mli @@ -301,7 +301,7 @@ and record_representation = | Record_float_unused (* Was: all fields are floats. Now: unused *) | Record_unboxed of bool (* Unboxed single-field record, inlined or not *) | Record_inlined of (* Inlined record *) - { tag : int ; name : string; num_nonconsts : int; optional_labels : string list} + { tag : int ; name : string; num_nonconsts : int; optional_labels : string list; attrs: Parsetree.attributes } | Record_extension (* Inlined record under extension *) | Record_optional_labels of string list (* List of optional labels *) diff --git a/jscomp/others/belt_internals.mli b/jscomp/others/belt_internals.mli index c79167851f..402d46011c 100644 --- a/jscomp/others/belt_internals.mli +++ b/jscomp/others/belt_internals.mli @@ -58,8 +58,6 @@ module Obj : sig external field : t -> int -> t = "%obj_field" external set_field : t -> int -> t -> unit = "%obj_set_field" external tag : t -> int = "?obj_tag" - (* The compiler ensures (|0) operation *) - external set_tag : t -> int -> unit = "TAG" [@@bs.set] external repr : 'a -> t = "%identity" external obj : t -> 'a = "%identity" external magic : 'a -> 'b = "%identity" diff --git a/jscomp/runtime/bs_stdlib_mini.mli b/jscomp/runtime/bs_stdlib_mini.mli index c79167851f..402d46011c 100644 --- a/jscomp/runtime/bs_stdlib_mini.mli +++ b/jscomp/runtime/bs_stdlib_mini.mli @@ -58,8 +58,6 @@ module Obj : sig external field : t -> int -> t = "%obj_field" external set_field : t -> int -> t -> unit = "%obj_set_field" external tag : t -> int = "?obj_tag" - (* The compiler ensures (|0) operation *) - external set_tag : t -> int -> unit = "TAG" [@@bs.set] external repr : 'a -> t = "%identity" external obj : t -> 'a = "%identity" external magic : 'a -> 'b = "%identity" diff --git a/jscomp/stdlib-406/camlinternalLazy.ml b/jscomp/stdlib-406/camlinternalLazy.ml index 5a3efc6c41..2f2f4545b1 100644 --- a/jscomp/stdlib-406/camlinternalLazy.ml +++ b/jscomp/stdlib-406/camlinternalLazy.ml @@ -46,7 +46,6 @@ exception Undefined let%private forward_with_closure (type a ) (blk : a t) (closure : unit -> a [@bs]) : a = let result = closure () [@bs] in - (* do set_field BEFORE set_tag *) blk.value <- result; blk.tag<- true; result diff --git a/jscomp/stdlib-406/obj.mli b/jscomp/stdlib-406/obj.mli index 0725df2e19..429a38cea4 100644 --- a/jscomp/stdlib-406/obj.mli +++ b/jscomp/stdlib-406/obj.mli @@ -42,12 +42,7 @@ external field : t -> int -> t = "%obj_field" [set_field] MUST NOT be called on immutable blocks. (Blocks allocated in C stubs, or with [new_block] below, are always considered mutable.) - - The same goes for [set_double_field] and [set_tag]. However, for - [set_tag], in the case of immutable blocks where the middle-end optimizers - never see code that discriminates on their tag (for example records), the - operation should be safe. Such uses are nonetheless discouraged. - + For experts only: [set_field] et al can be made safe by first wrapping the block in {!Sys.opaque_identity}, so any information about its contents will not diff --git a/jscomp/test/method_chain.js b/jscomp/test/method_chain.js deleted file mode 100644 index eba6dfaa50..0000000000 --- a/jscomp/test/method_chain.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - - -function f(obj, x, y) { - return obj.paint(x, y).draw(x, y).bark(x, y); -} - -exports.f = f; -/* No side effect */ diff --git a/jscomp/test/variantsMatching.js b/jscomp/test/variantsMatching.js index 0f01f32e67..a3e4ba3132 100644 --- a/jscomp/test/variantsMatching.js +++ b/jscomp/test/variantsMatching.js @@ -134,6 +134,260 @@ function third2(l) { } } +function foo(x) { + if (typeof x !== "object") { + switch (x) { + case "dd" : + return 1; + case 12 : + return 2; + case false : + return 3; + + } + } else { + switch (x.TAG) { + case "qq" : + return 4; + case 42 : + return 5; + case "F" : + return 6; + + } + } +} + +var CustomizeTags_d = { + TAG: "qq", + _0: 42 +}; + +var CustomizeTags_e = { + TAG: 42, + _0: 0 +}; + +var CustomizeTags = { + foo: foo, + a: "dd", + b: 12, + c: false, + d: CustomizeTags_d, + e: CustomizeTags_e +}; + +function isUndefined(x) { + return x === undefined; +} + +function plus(x, y) { + if (x === undefined) { + return y; + } else if (y === undefined) { + return x; + } else { + return x + y | 0; + } +} + +var MyUndefined = { + $$undefined: undefined, + isUndefined: isUndefined, + plus: plus +}; + +function isNull(x) { + return x === null; +} + +function plus$1(x, y) { + if (x === null) { + return y; + } else if (y === null) { + return x; + } else { + return x + y | 0; + } +} + +var MyNull_null = null; + +var MyNull = { + $$null: MyNull_null, + isNull: isNull, + plus: plus$1 +}; + +function isNull$1(x) { + return x === null; +} + +function isUndefined$1(x) { + return x === undefined; +} + +function plus$2(x, y) { + if (x === null || x === undefined) { + return y; + } else if (y === null || y === undefined) { + return x; + } else { + return x + y | 0; + } +} + +function kind(x) { + if (x === null || x === undefined) { + if (x === null) { + return "null"; + } else { + return "undefined"; + } + } else { + return "present"; + } +} + +var expectSeven = plus$2(3, 4); + +console.log("expect 7:", expectSeven); + +var MyNullable_null = null; + +var MyNullable = { + $$null: MyNullable_null, + $$undefined: undefined, + isNull: isNull$1, + isUndefined: isUndefined$1, + plus: plus$2, + kind: kind, + expectSeven: expectSeven +}; + +function isNull$2(x) { + return x === null; +} + +function isUndefined$2(x) { + return x === undefined; +} + +function isWhyNot(x) { + return x === "WhyNotAnotherOne"; +} + +function plus$3(x, y) { + if (x === null || typeof x !== "object") { + switch (x) { + case null : + case undefined : + return y; + case "WhyNotAnotherOne" : + break; + + } + } else if (!(y === null || typeof y !== "object")) { + return { + x: x.x + y.x, + y: x.y + y.y + }; + } + if (!(y === null || typeof y !== "object")) { + return "WhyNotAnotherOne"; + } + switch (y) { + case null : + case undefined : + return x; + case "WhyNotAnotherOne" : + return "WhyNotAnotherOne"; + + } +} + +function kind$1(x) { + if (!(x === null || typeof x !== "object")) { + return "present"; + } + switch (x) { + case null : + return "null"; + case undefined : + return "undefined"; + case "WhyNotAnotherOne" : + return "whynot"; + + } +} + +var expectSeven$1 = plus$3({ + x: 4, + y: 3 + }, { + x: 3, + y: 4 + }); + +console.log("expect {x:7, y:7}:", expectSeven$1); + +var MyNullableExtended_null = null; + +var MyNullableExtended = { + $$null: MyNullableExtended_null, + $$undefined: undefined, + whynot: "WhyNotAnotherOne", + isNull: isNull$2, + isUndefined: isUndefined$2, + isWhyNot: isWhyNot, + plus: plus$3, + kind: kind$1, + expectSeven: expectSeven$1 +}; + +function area(shape) { + switch (shape.kind) { + case 1 : + return Math.PI * Math.pow(shape.radius, 2); + case "square" : + return Math.pow(shape.sideLength, 2); + case "rectangle" : + return shape.width * shape.height; + + } +} + +var TaggedUnions_circle = { + kind: 1, + radius: 10 +}; + +var TaggedUnions_square = { + kind: "square", + sideLength: 10 +}; + +var TaggedUnions = { + area: area, + circle: TaggedUnions_circle, + square: TaggedUnions_square +}; + +var CustomTagNotInline_a = { + "custom-tag": "A", + _0: 10 +}; + +var CustomTagNotInline_b = { + "custom-tag": "B", + _0: 20 +}; + +var CustomTagNotInline = { + a: CustomTagNotInline_a, + b: CustomTagNotInline_b +}; + exports.toEnum = toEnum; exports.toString = toString; exports.bar = bar; @@ -144,4 +398,11 @@ exports.st = st; exports.showToJs = showToJs; exports.third = third; exports.third2 = third2; -/* No side effect */ +exports.CustomizeTags = CustomizeTags; +exports.MyUndefined = MyUndefined; +exports.MyNull = MyNull; +exports.MyNullable = MyNullable; +exports.MyNullableExtended = MyNullableExtended; +exports.TaggedUnions = TaggedUnions; +exports.CustomTagNotInline = CustomTagNotInline; +/* expectSeven Not a pure module */ diff --git a/jscomp/test/variantsMatching.res b/jscomp/test/variantsMatching.res index a95c89c8ed..4bec11859b 100644 --- a/jscomp/test/variantsMatching.res +++ b/jscomp/test/variantsMatching.res @@ -79,3 +79,186 @@ let third2 = l => | Cons(1, Cons(2, Cons(3, Empty))) => true | _ => false } + +module CustomizeTags = { + type t = | @as("dd") A | @as(12) B | @as(false) C | @as("qq") D(int) | @as(42) E(int) | F(string) + + let foo = x => + switch x { + | A => 1 + | B => 2 + | C => 3 + | D(_) => 4 + | E(_) => 5 + | F(_) => 6 + } + + let a = A + let b = B + let c = C + let d = D(42) + let e = E(0) +} + +module MyUndefined = { + type t<'a> = | @as(undefined) Undefined | @as(unboxed) Present('a) + // Note: 'a must not have undefined as value + // There can be only one with payload, with 1 argument, to use unboxed + + let undefined = Undefined + + let isUndefined = x => x == Undefined + + let plus = (x, y) => + switch (x, y) { + | (Undefined, _) => y + | (_, Undefined) => x + | (Present(n), Present(m)) => Present(n + m) + } +} + +module MyNull = { + type t<'a> = | @as(null) Null | @as(unboxed) Present('a) + // Note: 'a must not have null as value + // There can be only one with payload, with 1 argument, to use unboxed + + let null = Null + + let isNull = x => x == Null + + let plus = (x, y) => + switch (x, y) { + | (Null, _) => y + | (_, Null) => x + | (Present(n), Present(m)) => Present(n + m) + } +} + +module MyNullable = { + type t<'a> = + | @as(null) Null + | @as(undefined) Undefined + | @as(unboxed) Present('a) + // Note: 'a must not have null or undefined as value + // There can be only one with payload, with 1 argument, to use unboxed + + let null = Null + let undefined = Undefined + + let isNull = x => x == Null + let isUndefined = x => x == Undefined + + let plus = (x, y) => + switch (x, y) { + | (Null | Undefined, _) => y + | (_, Null | Undefined) => x + | (Present(x), Present(y)) => Present(x + y) + } + + let kind = x => + switch x { + | Null => "null" + | Undefined => "undefined" + | Present(_) => "present" + } + + let expectSeven = plus(Present(3), Present(4)) + Js.log2("expect 7:", expectSeven) +} + +module MyNullableExtended = { + type t<'a> = + | @as(null) Null + | @as(undefined) Undefined + | @as(unboxed) Present('a) + | WhyNotAnotherOne + // Note: 'a must be a not have null or something that's not an object as value + // There can be only one with payload, with 1 argument, to use unboxed + + let null = Null + let undefined = Undefined + let whynot = WhyNotAnotherOne + + let isNull = x => x == Null + let isUndefined = x => x == Undefined + let isWhyNot = x => x == WhyNotAnotherOne + + type vector = {x: float, y: float} + + let plus = (x, y) => + switch (x, y) { + | (Null | Undefined, _) => y + | (_, Null | Undefined) => x + | (WhyNotAnotherOne, _) | (_, WhyNotAnotherOne) => WhyNotAnotherOne + | (Present({x: x1, y: y1}), Present({x: x2, y: y2})) => Present({x: x1 +. x2, y: y1 +. y2}) + } + + let kind = x => + switch x { + | Null => "null" + | Undefined => "undefined" + | Present(_) => "present" + | WhyNotAnotherOne => "whynot" + } + + let expectSeven = plus(Present({x: 4., y: 3.}), Present({x: 3., y: 4.})) + Js.log2("expect {x:7, y:7}:", expectSeven) +} + +module TaggedUnions = { + /* + type Circle = { + kind: 1; // Number literal + radius: number; + }; + + type Square = { + kind: "square"; // String literal + sideLength: number; + }; + + type Rectangle = { + kind: "rectangle"; // String literal + width: number; + height: number; + }; + + type Shape = Circle | Square | Rectangle; + + function area(shape: Shape): number { + switch (shape.kind) { + case 1: // Circle + return Math.PI * shape.radius ** 2; + case "square": // Square + return shape.sideLength ** 2; + case "rectangle": // Rectangle + return shape.width * shape.height; + default: + throw new Error("Invalid shape kind"); + } + } +*/ + @tag("kind") + type shape = + | @as(1) Circle({radius: float}) + | @as("square") Square({sideLength: float}) + | @as("rectangle") Rectangle({width: float, height: float}) + + let area = (shape: shape): float => { + switch shape { + | Circle({radius}) => Js.Math._PI *. radius ** 2. + | Square({sideLength}) => sideLength ** 2. + | Rectangle({width, height}) => width *. height + } + } + + let circle = Circle({radius: 10.}) + let square = Square({sideLength: 10.}) +} + +module CustomTagNotInline = { + @tag("custom-tag") + type t = A(int) | B(int) + let a = A(10) + let b = B(20) +}