diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 863598dc56..828dfb44e8 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -112,6 +112,7 @@ let identifyPexp pexp = | Pexp_open _ -> "Pexp_open" | Pexp_await _ -> "Pexp_await" | Pexp_jsx_element _ -> "Pexp_jsx_element" + | Pexp_template _ -> "Pexp_template" let identifyPpat pat = match pat with diff --git a/compiler/core/j.ml b/compiler/core/j.ml index dc5aa2514d..bbcc3a95f9 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -116,6 +116,7 @@ and expression_desc = This can be constructed either in a static way [E.array_index_by_int] or a dynamic way [E.array_index] *) + | Template_literal of expression list * expression list | Tagged_template of expression * expression list * expression list | Static_index of expression * string * int32 option (* The third argument bool indicates whether we should diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index d728ae4f9c..a01a5f3e7c 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -106,6 +106,9 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) = | 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 | Bin (op, a, b) -> op <> Eq && no_side_effect a && no_side_effect b + | Template_literal (strings, values) -> + Ext_list.for_all strings no_side_effect + && Ext_list.for_all values no_side_effect | Tagged_template (call_expr, strings, values) -> no_side_effect call_expr && Ext_list.for_all strings no_side_effect @@ -229,7 +232,8 @@ let rec eq_expression ({expression_desc = x0} : J.expression) | _ -> false) | Length _ | Is_null_or_undefined _ | String_append _ | Typeof _ | Js_not _ | Js_bnot _ | In _ | Cond _ | FlatCall _ | New _ | Fun _ | Raw_js_code _ - | Array _ | Caml_block_tag _ | Object _ | Tagged_template _ | Await _ -> + | Array _ | Caml_block_tag _ | Object _ | Template_literal _ + | Tagged_template _ | Await _ -> false | Spread _ -> false diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 43967a3f1c..374de85371 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -171,6 +171,7 @@ let rec exp_need_paren ?(arrow = false) (e : J.expression) = false | Await _ -> false | Spread _ -> false + | Template_literal _ -> false | Tagged_template _ -> false | Optional_block (e, true) when arrow -> exp_need_paren ~arrow e | Optional_block _ -> false @@ -713,6 +714,23 @@ and expression_desc cxt ~(level : int) f x : cxt = aux cxt string_args value_args; P.string f "`"; cxt + | Template_literal (string_args, value_args) -> + P.string f "`"; + let rec aux cxt xs ys = + match (xs, ys) with + | [], [] -> () + | [{J.expression_desc = Str {txt; _}}], [] -> P.string f txt + | {J.expression_desc = Str {txt; _}} :: x_rest, y :: y_rest -> + P.string f txt; + P.string f "${"; + let cxt = expression cxt ~level f y in + P.string f "}"; + aux cxt x_rest y_rest + | _ -> assert false + in + aux cxt string_args value_args; + P.string f "`"; + cxt | String_index (a, b) -> P.group f 1 (fun _ -> let cxt = expression ~level:15 cxt f a in diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 210c0a58dd..53998ddb27 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -79,6 +79,9 @@ let tagged_template ?comment call_expr string_args value_args : t = comment; } +let template_literal ?comment string_args value_args : t = + {expression_desc = Template_literal (string_args, value_args); comment} + let runtime_var_dot ?comment (x : string) (e1 : string) : J.expression = { expression_desc = diff --git a/compiler/core/js_exp_make.mli b/compiler/core/js_exp_make.mli index ec208532a5..0e72f3add2 100644 --- a/compiler/core/js_exp_make.mli +++ b/compiler/core/js_exp_make.mli @@ -310,6 +310,7 @@ val call : ?comment:string -> info:Js_call_info.t -> t -> t list -> t val flat_call : ?comment:string -> t -> t -> t val tagged_template : ?comment:string -> t -> t list -> t list -> t +val template_literal : ?comment:string -> t list -> t list -> t val new_ : ?comment:string -> J.expression -> J.expression list -> t diff --git a/compiler/core/js_fold.ml b/compiler/core/js_fold.ml index 1ffa5e0270..a5b98aeb2d 100644 --- a/compiler/core/js_fold.ml +++ b/compiler/core/js_fold.ml @@ -131,6 +131,10 @@ class fold = let _self = _self#expression _x0 in let _self = list (fun _self -> _self#expression) _self _x1 in _self + | Template_literal (_x0, _x1) -> + let _self = list (fun _self -> _self#expression) _self _x0 in + let _self = list (fun _self -> _self#expression) _self _x1 in + _self | Tagged_template (_x0, _x1, _x2) -> let _self = _self#expression _x0 in let _self = list (fun _self -> _self#expression) _self _x1 in diff --git a/compiler/core/js_record_fold.ml b/compiler/core/js_record_fold.ml index fe71e6f5f1..0ca3652ee9 100644 --- a/compiler/core/js_record_fold.ml +++ b/compiler/core/js_record_fold.ml @@ -137,6 +137,10 @@ let expression_desc : 'a. ('a, expression_desc) fn = let st = _self.expression _self st _x0 in let st = list _self.expression _self st _x1 in st + | Template_literal (_x0, _x1) -> + let st = list _self.expression _self st _x0 in + let st = list _self.expression _self st _x1 in + st | Tagged_template (_xo, _x1, _x2) -> let st = _self.expression _self st _xo in let st = list _self.expression _self st _x1 in diff --git a/compiler/core/js_record_iter.ml b/compiler/core/js_record_iter.ml index e6c9ab9646..f60b99af70 100644 --- a/compiler/core/js_record_iter.ml +++ b/compiler/core/js_record_iter.ml @@ -111,6 +111,9 @@ let expression_desc : expression_desc fn = | Call (_x0, _x1, _x2) -> _self.expression _self _x0; list _self.expression _self _x1 + | Template_literal (_x0, _x1) -> + list _self.expression _self _x0; + list _self.expression _self _x1 | Tagged_template (_x0, _x1, _x2) -> _self.expression _self _x0; list _self.expression _self _x1; diff --git a/compiler/core/js_record_map.ml b/compiler/core/js_record_map.ml index b13fdb2a55..164dde9b34 100644 --- a/compiler/core/js_record_map.ml +++ b/compiler/core/js_record_map.ml @@ -137,6 +137,10 @@ let expression_desc : expression_desc fn = let _x0 = _self.expression _self _x0 in let _x1 = list _self.expression _self _x1 in Call (_x0, _x1, _x2) + | Template_literal (_x0, _x1) -> + let _x0 = list _self.expression _self _x0 in + let _x1 = list _self.expression _self _x1 in + Template_literal (_x0, _x1) | Tagged_template (_x0, _x1, _x2) -> let _x0 = _self.expression _self _x0 in let _x1 = list _self.expression _self _x1 in diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index a4b78bea0e..ba58d92430 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -72,7 +72,7 @@ let rec no_side_effects (lam : Lam.t) : bool = | Pbigintcomp _ | Pbigintorder | Pbigintmin | Pbigintmax (* string primitives *) | Pstringlength | Pstringrefu | Pstringrefs | Pstringcomp _ | Pstringorder - | Pstringmin | Pstringmax + | Pstringmin | Pstringmax | Pstringtemplate _ (* array primitives *) | Pmakearray | Parraylength | Parrayrefu | Parrayrefs (* list primitives *) diff --git a/compiler/core/lam_compile_primitive.ml b/compiler/core/lam_compile_primitive.ml index e7c377e97a..1cb532feef 100644 --- a/compiler/core/lam_compile_primitive.ml +++ b/compiler/core/lam_compile_primitive.ml @@ -146,6 +146,9 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) match args with | [a; b] -> E.string_append a b | _ -> assert false) + | Pstringtemplate strings -> + let string_args = List.map E.str strings in + E.template_literal string_args args | Pinit_mod -> E.runtime_call Primitive_modules.module_ "init" args | Pupdate_mod -> E.runtime_call Primitive_modules.module_ "update" args | Psome -> ( diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 2e88a3b703..5266aa0b4c 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -243,6 +243,8 @@ let lam_prim ~primitive:(p : Lambda.primitive) ~args loc : Lam.t = | Pstringmin -> prim ~primitive:Pstringmin ~args loc | Pstringmax -> prim ~primitive:Pstringmax ~args loc | Pstringadd -> prim ~primitive:Pstringadd ~args loc + | Pstringtemplate strings -> + prim ~primitive:(Pstringtemplate strings) ~args loc | Pabsfloat -> assert false | Pstringrefs -> prim ~primitive:Pstringrefs ~args loc | Pisint -> prim ~primitive:Pisint ~args loc diff --git a/compiler/core/lam_primitive.ml b/compiler/core/lam_primitive.ml index 2135293c85..6980be54e2 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -125,6 +125,7 @@ type t = | Pstringrefu | Pstringrefs | Pstringadd + | Pstringtemplate of string list | Pstringcomp of Lam_compat.comparison | Pstringorder | Pstringmin @@ -213,8 +214,8 @@ let eq_primitive_approx (lhs : t) (rhs : t) = | Ppowbigint | Pnotbigint | Pandbigint | Porbigint | Pxorbigint | Plslbigint | Pasrbigint | Pbigintorder | Pbigintmin | Pbigintmax (* string primitives *) - | Pstringlength | Pstringrefu | Pstringrefs | Pstringadd | Pstringcomp _ - | Pstringorder | Pstringmin | Pstringmax + | Pstringlength | Pstringrefu | Pstringrefs | Pstringadd | Pstringtemplate _ + | Pstringcomp _ | Pstringorder | Pstringmin | Pstringmax (* List primitives *) | Pmakelist (* dict primitives *) diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index 879f03a412..0702d69da0 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -120,6 +120,7 @@ type t = | Pstringrefu | Pstringrefs | Pstringadd + | Pstringtemplate of string list | Pstringcomp of Lam_compat.comparison | Pstringorder | Pstringmin diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 172e219abb..72f85381b1 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -110,6 +110,7 @@ let primitive ppf (prim : Lam_primitive.t) = | Pnegint -> fprintf ppf "~-" | Paddint -> fprintf ppf "+" | Pstringadd -> fprintf ppf "+*" + | Pstringtemplate _ -> fprintf ppf "template" | Psubint -> fprintf ppf "-" | Pmulint -> fprintf ppf "*" | Pdivint -> fprintf ppf "/" diff --git a/compiler/frontend/ast_attributes.ml b/compiler/frontend/ast_attributes.ml index b02733aa4c..3d22abb55a 100644 --- a/compiler/frontend/ast_attributes.ml +++ b/compiler/frontend/ast_attributes.ml @@ -224,6 +224,13 @@ type as_const_payload = Int of int | Str of string * External_arg_spec.delim let iter_process_bs_string_or_int_as (attrs : Parsetree.attributes) = let st = ref None in + let string_payload_loc payload = + match payload with + | Parsetree.PStr [{pstr_desc = Parsetree.Pstr_eval ({pexp_loc; _}, _); _}] + -> + Some pexp_loc + | _ -> None + in Ext_list.iter attrs (fun (({txt; loc}, payload) as attr) -> match txt with | "as" -> @@ -231,41 +238,31 @@ let iter_process_bs_string_or_int_as (attrs : Parsetree.attributes) = Used_attributes.mark_used_attribute attr; match Ast_payload.is_single_int payload with | None -> ( - match payload with - | PStr - [ - { - pstr_desc = - Pstr_eval - ( { - pexp_desc = Pexp_constant (Pconst_string (s, delim_)); - pexp_loc; - _; - }, - _ ); - _; - }; - ] - when Ast_utf8_string_interp.parse_processed_delim delim_ <> None - -> ( - let delim = - match Ast_utf8_string_interp.parse_processed_delim delim_ with - | None -> assert false - | Some delim -> delim - in - st := Some (Str (s, delim)); - if delim = DNoQuotes then - (* check that it is a valid object literal *) - match - Classify_function.classify - ~check:(pexp_loc, Bs_flow_ast_utils.flow_deli_offset delim_) - s - with - | Js_literal _ -> () - | _ -> - Location.raise_errorf ~loc:pexp_loc - "an object literal expected") - | _ -> Bs_syntaxerr.err loc Expect_int_or_string_or_json_literal) + match Ast_payload.is_single_string payload with + | Some (s, delim_) -> ( + match Ast_utf8_string_interp.parse_processed_delim delim_ with + | None -> + Bs_syntaxerr.err loc Expect_int_or_string_or_json_literal + | Some delim -> ( + let payload_loc = + match string_payload_loc payload with + | Some payload_loc -> payload_loc + | None -> loc + in + st := Some (Str (s, delim)); + if delim = DNoQuotes then + (* check that it is a valid object literal *) + match + Classify_function.classify + ~check: + (payload_loc, Bs_flow_ast_utils.flow_deli_offset delim_) + s + with + | Js_literal _ -> () + | _ -> + Location.raise_errorf ~loc:payload_loc + "an object literal expected")) + | None -> Bs_syntaxerr.err loc Expect_int_or_string_or_json_literal) | Some v -> st := Some (Int v)) else raise (Ast_untagged_variants.Error (loc, Duplicated_bs_as)) | _ -> ()); diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index 292e199b5a..c4542a1780 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -406,6 +406,10 @@ module E = struct jsx_container_element ~loc ~attrs name (map_jsx_props sub props) ote (map_jsx_children sub children) closing_tag + | Pexp_template {tag; prefix; strings; expressions} -> + let tag = map_opt (sub.expr sub) tag in + let expressions = List.map (sub.expr sub) expressions in + template ~loc ~attrs {tag; prefix; strings; expressions} end module P = struct diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 2fae640eb0..599466ec6d 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -159,6 +159,7 @@ module Exp = struct let ident ?loc ?attrs a = mk ?loc ?attrs (Pexp_ident a) let constant ?loc ?attrs a = mk ?loc ?attrs (Pexp_constant a) + let template ?loc ?attrs a = mk ?loc ?attrs (Pexp_template a) let let_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_let (a, b, c)) let fun_ ?loc ?attrs ?(async = false) ~arity a b c d = mk ?loc ?attrs diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 11227b903a..c9910d49b3 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -122,6 +122,7 @@ module Exp : sig val ident : ?loc:loc -> ?attrs:attrs -> lid -> expression val constant : ?loc:loc -> ?attrs:attrs -> constant -> expression + val template : ?loc:loc -> ?attrs:attrs -> template_literal -> expression val let_ : ?loc:loc -> ?attrs:attrs -> diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index a430bb0b7b..670a28e049 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -358,6 +358,9 @@ module E = struct iter_loc sub lid; sub.expr sub e | Pexp_extension x -> sub.extension sub x + | Pexp_template {tag; expressions; _} -> + iter_opt (sub.expr sub) tag; + List.iter (sub.expr sub) expressions | Pexp_await e -> sub.expr sub e | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) -> iter_jsx_children sub children diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 673465477b..6f23f00bcd 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -343,6 +343,10 @@ module E = struct | Pexp_open (ovf, lid, e) -> open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e) | Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x) + | Pexp_template {tag; prefix; strings; expressions} -> + let tag = map_opt (sub.expr sub) tag in + let expressions = List.map (sub.expr sub) expressions in + template ~loc ~attrs {tag; prefix; strings; expressions} | Pexp_await e -> await ~loc ~attrs (sub.expr sub e) | Pexp_jsx_element (Jsx_fragment diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 3f91d6ac1e..4cd1d242e1 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -310,6 +310,88 @@ module E = struct | _ -> true) attrs + let has_template_literal_attr attrs = + List.exists + (function + | {Location.txt = "res.template"}, _ -> true + | _ -> false) + attrs + + let remove_template_literal_attr attrs = + List.filter + (function + | {Location.txt = "res.template"}, _ -> false + | _ -> true) + attrs + + let has_tagged_template_literal_attr attrs = + List.exists + (function + | {Location.txt = "res.taggedTemplate"}, _ -> true + | _ -> false) + attrs + + let remove_tagged_template_literal_attr attrs = + List.filter + (function + | {Location.txt = "res.taggedTemplate"}, _ -> false + | _ -> true) + attrs + + type template_part = + | Template_string of string * string option + | Template_expr of expression + + let is_concat_operator expr = + match expr.pexp_desc with + | Pexp_ident {txt = Longident.Lident ("^" | "++")} -> true + | _ -> false + + let rec collect_template_parts expr = + match expr.pexp_desc with + | Pexp_apply (op, [(Nolabel, lhs); (Nolabel, rhs)]) + when is_concat_operator op -> + collect_template_parts lhs @ collect_template_parts rhs + | Pexp_constant (Pconst_string (txt, delim)) -> + [Template_string (txt, delim)] + | _ -> [Template_expr expr] + + let template_literal_of_parts sub parts = + let prefix = ref None in + let strings_rev = ref [] in + let expressions_rev = ref [] in + let last_was_expr = ref false in + let add_string s = + match (!strings_rev, !last_was_expr) with + | last :: rest, false -> strings_rev := (last ^ s) :: rest + | _ -> strings_rev := s :: !strings_rev + in + let add_expr e = + if !strings_rev = [] then strings_rev := "" :: !strings_rev; + if !last_was_expr then strings_rev := "" :: !strings_rev; + expressions_rev := sub.expr sub e :: !expressions_rev; + last_was_expr := true + in + let record_prefix = function + | Some "json" when !prefix = None -> prefix := Some "json" + | _ -> () + in + List.iter + (function + | Template_string (txt, delim) -> + record_prefix delim; + add_string txt; + last_was_expr := false + | Template_expr expr -> add_expr expr) + parts; + if !last_was_expr then strings_rev := "" :: !strings_rev; + { + Parsetree.tag = None; + prefix = !prefix; + strings = List.rev !strings_rev; + expressions = List.rev !expressions_rev; + } + let map_jsx_children sub (e : expression) : Pt.jsx_children = let rec visit (e : expression) : Pt.expression list = match e.pexp_desc with @@ -381,6 +463,42 @@ module E = struct let attrs = remove_await_attribute e.pexp_attributes in let e = sub.expr sub {e with pexp_attributes = attrs} in await ~loc e + | Pexp_apply (call_expr, args) when has_tagged_template_literal_attr attrs + -> ( + let attrs = remove_tagged_template_literal_attr attrs in + match args with + | [ + (Nolabel, {pexp_desc = Pexp_array string_exprs}); + (Nolabel, {pexp_desc = Pexp_array value_exprs}); + ] -> ( + let rec collect_strings acc = function + | [] -> Some (List.rev acc) + | {pexp_desc = Pexp_constant (Pconst_string (txt, _)); _} :: rest -> + collect_strings (txt :: acc) rest + | _ -> None + in + match collect_strings [] string_exprs with + | Some strings -> + let tag_expr = sub.expr sub call_expr in + let values = List.map (sub.expr sub) value_exprs in + template ~loc ~attrs + {tag = Some tag_expr; prefix = None; strings; expressions = values} + | None -> + apply ~loc ~attrs (sub.expr sub call_expr) + (List.map + (fun (lbl, e) -> (Asttypes.to_arg_label lbl, sub.expr sub e)) + args)) + | _ -> + apply ~loc ~attrs (sub.expr sub call_expr) + (List.map + (fun (lbl, e) -> (Asttypes.to_arg_label lbl, sub.expr sub e)) + args)) + | _ when has_template_literal_attr attrs -> + let attrs = remove_template_literal_attr attrs in + let template_literal = + template_literal_of_parts sub (collect_template_parts e) + in + template ~loc ~attrs template_literal | Pexp_ident x -> ident ~loc ~attrs (map_loc sub x) | Pexp_constant x -> constant ~loc ~attrs (map_constant x) | Pexp_let (r, vbs, e) -> diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index d0ac43d737..9d1fc87bc6 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -344,6 +344,66 @@ module E = struct let list_expr = Ast_helper.Exp.make_list_expression loc xs None in sub.expr sub list_expr + let template_literal_attr = (Location.mknoloc "res.template", Pt.PStr []) + + let tagged_template_literal_attr = + (Location.mknoloc "res.taggedTemplate", Pt.PStr []) + + let make_string_expr ~loc ?prefix txt = + Exp.constant ~loc (Pt.Pconst_string (txt, prefix)) + + let rec interleave_string_value_parts strings values = + match (strings, values) with + | [], [] -> [] + | string_expr :: rest_strings, value_expr :: rest_values -> + string_expr :: value_expr + :: interleave_string_value_parts rest_strings rest_values + | string_expr :: rest_strings, [] -> string_expr :: rest_strings + | [], _ -> [] + + let rec fold_string_concat ~loc op_expr parts = + match parts with + | [] -> make_string_expr ~loc "" + | [part] -> part + | part1 :: part2 :: rest -> + let expr = + Exp.apply ~loc op_expr + [(Asttypes.Noloc.Nolabel, part1); (Asttypes.Noloc.Nolabel, part2)] + in + fold_string_concat ~loc op_expr (expr :: rest) + + let map_template_literal sub ~loc ~attrs + {Parsetree.tag; prefix; strings; expressions} = + match tag with + | Some tag_expr -> + let call_expr = sub.expr sub tag_expr in + let string_exprs = + List.map (make_string_expr ~loc ?prefix:None) strings + in + let value_exprs = List.map (sub.expr sub) expressions in + let strings_array = Exp.array ~loc string_exprs in + let values_array = Exp.array ~loc value_exprs in + Exp.apply ~loc + ~attrs:(tagged_template_literal_attr :: attrs) + call_expr + [ + (Asttypes.Noloc.Nolabel, strings_array); + (Asttypes.Noloc.Nolabel, values_array); + ] + | None -> + let string_exprs = + match strings with + | [] -> [make_string_expr ~loc ?prefix ""] + | first :: rest -> + make_string_expr ~loc ?prefix first + :: List.map (make_string_expr ~loc ?prefix:None) rest + in + let value_exprs = List.map (sub.expr sub) expressions in + let parts = interleave_string_value_parts string_exprs value_exprs in + let concat_op = Exp.ident ~loc {txt = Longident.Lident "^"; loc} in + let expr = fold_string_concat ~loc concat_op parts in + {expr with pexp_attributes = template_literal_attr :: attrs} + (* Value expressions for the core language *) let map sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} = @@ -351,6 +411,8 @@ module E = struct let loc = sub.location sub loc in let attrs = sub.attributes sub attrs in match desc with + | Pexp_template template_literal -> + map_template_literal sub ~loc ~attrs template_literal | Pexp_ident x -> ident ~loc ~attrs (map_loc sub x) | Pexp_constant x -> constant ~loc ~attrs (map_constant x) | Pexp_let (r, vbs, e) -> diff --git a/compiler/ml/ast_payload.ml b/compiler/ml/ast_payload.ml index eb953cd583..cae1d2e96c 100644 --- a/compiler/ml/ast_payload.ml +++ b/compiler/ml/ast_payload.ml @@ -24,6 +24,10 @@ type t = Parsetree.payload +let template_literal_delim = function + | None -> Some "bq" + | Some prefix -> Some prefix + let is_single_string (x : t) = match x with (* TODO also need detect empty phrase case *) @@ -37,6 +41,22 @@ let is_single_string (x : t) = }; ] -> Some (name, dec) + | PStr + [ + { + pstr_desc = + Pstr_eval + ( { + pexp_desc = + Pexp_template + {tag = None; prefix; strings = [name]; expressions = []}; + _; + }, + _ ); + _; + }; + ] -> + Some (name, template_literal_delim prefix) | _ -> None let is_single_string_as_ast (x : t) : Parsetree.expression option = @@ -52,6 +72,27 @@ let is_single_string_as_ast (x : t) : Parsetree.expression option = }; ] -> Some e + | PStr + [ + { + pstr_desc = + Pstr_eval + ( ({ + pexp_desc = + Pexp_template + {tag = None; prefix; strings = [name]; expressions = []}; + pexp_loc = _; + } as e), + _ ); + _; + }; + ] -> + Some + { + e with + pexp_desc = + Pexp_constant (Pconst_string (name, template_literal_delim prefix)); + } | _ -> None let is_single_int (x : t) : int option = diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index e5e39eb4b5..8889d39efa 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -287,6 +287,9 @@ let rec add_expr bv exp = | Pstr_eval ({pexp_desc = Pexp_construct (c, None)}, _) -> add bv c | _ -> handle_extension e) | Pexp_extension e -> handle_extension e + | Pexp_template {tag; expressions; _} -> + Ext_option.iter tag (add_expr bv); + List.iter (add_expr bv) expressions | Pexp_await e -> add_expr bv e | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) -> add_jsx_children bv children diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index db810d4f91..c0aade1a23 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -259,6 +259,7 @@ type primitive = | Pstringmin | Pstringmax | Pstringadd + | Pstringtemplate of string list (* Array operations *) | Pmakearray of Asttypes.mutable_flag | Parraylength diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index d8eaf57be6..91d9a17b43 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -228,6 +228,7 @@ type primitive = | Pstringmin | Pstringmax | Pstringadd + | Pstringtemplate of string list (* Array operations *) | Pmakearray of mutable_flag | Parraylength diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 78c9899f74..b502dc3ac7 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -219,6 +219,7 @@ and expression_desc = M.x *) | Pexp_constant of constant (* 1, 'a', "true", 1.0, 1l, 1L, 1n *) + | Pexp_template of template_literal | Pexp_let of rec_flag * value_binding list * expression (* let P1 = E1 and ... and Pn = EN in E (flag = Nonrecursive) let rec P1 = E1 and ... and Pn = EN in E (flag = Recursive) @@ -317,6 +318,13 @@ and expression_desc = | Pexp_await of expression | Pexp_jsx_element of jsx_element +and template_literal = { + tag: expression option; + prefix: string option; + strings: string list; + expressions: expression list; +} + (* an element of a record pattern or expression *) and 'a record_element = {lid: Longident.t loc; x: 'a; opt: bool (* optional *)} diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index 585ac64b81..9937ce1bfc 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -137,6 +137,56 @@ let is_simple_construct : construct -> bool = function | `nil | `tuple | `list _ | `simple _ -> true | `cons _ | `normal -> false +let template_literal_attr = (Location.mknoloc "res.template", Parsetree.PStr []) + +let tagged_template_literal_attr = + (Location.mknoloc "res.taggedTemplate", Parsetree.PStr []) + +let make_string_expr ~loc ?prefix txt = + Exp.constant ~loc (Pconst_string (txt, prefix)) + +let rec interleave_string_value_parts strings values = + match (strings, values) with + | [], [] -> [] + | string_expr :: rest_strings, value_expr :: rest_values -> + string_expr :: value_expr + :: interleave_string_value_parts rest_strings rest_values + | string_expr :: rest_strings, [] -> string_expr :: rest_strings + | [], _ -> [] + +let rec fold_string_concat ~loc op_expr parts = + match parts with + | [] -> make_string_expr ~loc "" + | [part] -> part + | part1 :: part2 :: rest -> + let expr = Exp.apply ~loc op_expr [(Nolabel, part1); (Nolabel, part2)] in + fold_string_concat ~loc op_expr (expr :: rest) + +let expand_template_literal ~loc ~attrs + (template_literal : Parsetree.template_literal) = + let {tag; prefix; strings; expressions} = template_literal in + match tag with + | Some tag_expr -> + let string_exprs = List.map (make_string_expr ~loc ?prefix:None) strings in + let strings_array = Exp.array ~loc string_exprs in + let values_array = Exp.array ~loc expressions in + Exp.apply ~loc + ~attrs:(tagged_template_literal_attr :: attrs) + tag_expr + [(Nolabel, strings_array); (Nolabel, values_array)] + | None -> + let string_exprs = + match strings with + | [] -> [make_string_expr ~loc ?prefix ""] + | first :: rest -> + make_string_expr ~loc ?prefix first + :: List.map (make_string_expr ~loc ?prefix:None) rest + in + let parts = interleave_string_value_parts string_exprs expressions in + let concat_op = Exp.ident ~loc {txt = Longident.Lident "++"; loc} in + let expr = fold_string_concat ~loc concat_op parts in + {expr with pexp_attributes = template_literal_attr :: attrs} + let pp = fprintf type ctxt = {pipe: bool; semi: bool; ifthenelse: bool} @@ -610,6 +660,9 @@ and expression ctxt f x = paren true (expression reset_ctxt) f x | (Pexp_ifthenelse _ | Pexp_sequence _) when ctxt.ifthenelse -> paren true (expression reset_ctxt) f x + | Pexp_template template_literal -> + expression ctxt f + (expand_template_literal ~loc:x.pexp_loc ~attrs:[] template_literal) | (Pexp_let _ | Pexp_letmodule _ | Pexp_open _ | Pexp_letexception _) when ctxt.semi -> paren true (expression reset_ctxt) f x diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 44d699eb38..25cf125b7f 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -350,6 +350,18 @@ and expression i ppf x = | Pexp_extension (s, arg) -> line i ppf "Pexp_extension \"%s\"\n" s.txt; payload i ppf arg + | Pexp_template {tag; prefix; strings; expressions} -> + line i ppf "Pexp_template\n"; + (match prefix with + | None -> () + | Some prefix -> line (i + 1) ppf "prefix \"%s\"\n" prefix); + (match tag with + | None -> () + | Some expr -> + line (i + 1) ppf "tag\n"; + expression (i + 2) ppf expr); + List.iter (fun s -> line (i + 1) ppf "string %S\n" s) strings; + List.iter (expression (i + 1) ppf) expressions | Pexp_await e -> line i ppf "Pexp_await\n"; expression i ppf e diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 0282f6e113..0a80c158e5 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -220,6 +220,7 @@ let primitive ppf = function | Pstringmin -> fprintf ppf "min" | Pstringmax -> fprintf ppf "max" | Pstringadd -> fprintf ppf "string.concat" + | Pstringtemplate _ -> fprintf ppf "string.template" | Parraylength -> fprintf ppf "array.length" | Pmakearray Mutable -> fprintf ppf "makearray" | Pmakearray Immutable -> fprintf ppf "makearray_imm" diff --git a/compiler/ml/printtyped.ml b/compiler/ml/printtyped.ml index 6e36b4276c..df03a64f81 100644 --- a/compiler/ml/printtyped.ml +++ b/compiler/ml/printtyped.ml @@ -274,6 +274,9 @@ and expression i ppf x = match x.exp_desc with | Texp_ident (li, _, _) -> line i ppf "Texp_ident %a\n" fmt_path li | Texp_constant c -> line i ppf "Texp_constant %a\n" fmt_constant c + | Texp_template {expressions; _} -> + line i ppf "Texp_template\n"; + List.iter (expression (i + 1) ppf) expressions | Texp_let (rf, l, e) -> line i ppf "Texp_let %a\n" fmt_rec_flag rf; list i value_binding ppf l; diff --git a/compiler/ml/rec_check.ml b/compiler/ml/rec_check.ml index 3d016a7438..fa472a4054 100644 --- a/compiler/ml/rec_check.ml +++ b/compiler/ml/rec_check.ml @@ -197,7 +197,7 @@ let rec classify_expression : Typedtree.expression -> sd = | Texp_ident _ | Texp_for _ | Texp_constant _ | Texp_tuple _ | Texp_array _ | Texp_construct _ | Texp_variant _ | Texp_record _ | Texp_setfield _ | Texp_while _ | Texp_pack _ | Texp_function _ | Texp_extension_constructor _ - -> + | Texp_template _ -> Static | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}} when is_ref vd -> Static @@ -291,6 +291,7 @@ let rec expression : Env.env -> Typedtree.expression -> Use.t = | Texp_function {case = case_} -> Use.delay (list (case ~scrutinee:Use.empty) env [case_]) | Texp_extension_constructor _ -> Use.empty + | Texp_template {expressions} -> Use.guard (list expression env expressions) and option : 'a. (Env.env -> 'a -> Use.t) -> Env.env -> 'a option -> Use.t = fun f env -> value_default (f env) ~default:Use.empty diff --git a/compiler/ml/tast_iterator.ml b/compiler/ml/tast_iterator.ml index 5c12d3da4d..07d19ed3f2 100644 --- a/compiler/ml/tast_iterator.ml +++ b/compiler/ml/tast_iterator.ml @@ -148,6 +148,7 @@ let expr sub {exp_extra; exp_desc; exp_env; _} = match exp_desc with | Texp_ident _ -> () | Texp_constant _ -> () + | Texp_template {expressions; _} -> List.iter (sub.expr sub) expressions | Texp_let (rec_flag, list, exp) -> sub.value_bindings sub (rec_flag, list); sub.expr sub exp diff --git a/compiler/ml/tast_mapper.ml b/compiler/ml/tast_mapper.ml index 54eba02869..95fcaeca94 100644 --- a/compiler/ml/tast_mapper.ml +++ b/compiler/ml/tast_mapper.ml @@ -192,6 +192,9 @@ let expr sub x = let exp_desc = match x.exp_desc with | (Texp_ident _ | Texp_constant _) as d -> d + | Texp_template {prefix; strings; expressions} -> + Texp_template + {prefix; strings; expressions = List.map (sub.expr sub) expressions} | Texp_let (rec_flag, list, exp) -> let rec_flag, list = sub.value_bindings sub (rec_flag, list) in Texp_let (rec_flag, list, sub.expr sub exp) diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 078cbf133a..8fef47b330 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -663,6 +663,9 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | Texp_ident (path, _, {val_kind = Val_reg}) -> transl_value_path ~loc:e.exp_loc e.exp_env path | Texp_constant cst -> Lconst (Const_base cst) + | Texp_template {strings; expressions; _} -> + let args = List.map transl_exp expressions in + Lprim (Pstringtemplate strings, args, e.exp_loc) | Texp_let (rec_flag, pat_expr_list, body) -> transl_let rec_flag pat_expr_list (transl_exp body) | Texp_function {arg_label = _; arity; param; case; partial; async} -> ( diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index ee304dff7d..dcda9a4e5b 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -164,7 +164,8 @@ let iter_expression f e = | Pexp_match (e, pel) | Pexp_try (e, pel) -> expr e; List.iter case pel - | Pexp_array el | Pexp_tuple el -> List.iter expr el + | Pexp_array el | Pexp_tuple el | Pexp_template {expressions = el} -> + List.iter expr el | Pexp_construct (_, eo) | Pexp_variant (_, eo) -> may expr eo | Pexp_record (iel, eo) -> may expr eo; @@ -1799,6 +1800,7 @@ let rec is_nonexpansive exp = match exp.exp_desc with | Texp_ident (_, _, _) -> true | Texp_constant _ -> true + | Texp_template {expressions; _} -> List.for_all is_nonexpansive expressions | Texp_let (_rec_flag, pat_exp_list, body) -> List.for_all (fun vb -> is_nonexpansive vb.vb_expr) pat_exp_list && is_nonexpansive body @@ -2341,6 +2343,37 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) exp_attributes = sexp.pexp_attributes; exp_env = env; } + | Pexp_template {tag = Some tag_expr; prefix = _; strings; expressions} -> + let string_exprs = + List.map + (fun txt -> Ast_helper.Exp.constant ~loc (Pconst_string (txt, None))) + strings + in + let strings_array = Ast_helper.Exp.array ~loc string_exprs in + let values_array = Ast_helper.Exp.array ~loc expressions in + let apply_expr = + Ast_helper.Exp.apply ~loc tag_expr + [(Nolabel, strings_array); (Nolabel, values_array)] + in + type_expect ?deprecated_context ~context ?in_function env + {apply_expr with pexp_attributes = sexp.pexp_attributes} + ty_expected + | Pexp_template {tag = None; prefix; strings; expressions} -> + unify_exp_types ~context:None loc env Predef.type_string ty_expected; + let exprs = + List.map + (fun expr -> type_expect ~context:None env expr Predef.type_string) + expressions + in + rue + { + exp_desc = Texp_template {prefix; strings; expressions = exprs}; + exp_loc = loc; + exp_extra = []; + exp_type = instance env Predef.type_string; + exp_attributes = sexp.pexp_attributes; + exp_env = env; + } | Pexp_let ( Nonrecursive, [{pvb_pat = spat; pvb_expr = sval; pvb_attributes = []}], diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index e7274f1245..206c724b48 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -73,6 +73,7 @@ and exp_extra = and expression_desc = | Texp_ident of Path.t * Longident.t loc * Types.value_description | Texp_constant of constant + | Texp_template of template_literal | Texp_let of rec_flag * value_binding list * expression | Texp_function of { arg_label: arg_label; @@ -124,6 +125,12 @@ and expression_desc = | Texp_pack of module_expr | Texp_extension_constructor of Longident.t loc * Path.t +and template_literal = { + prefix: string option; + strings: string list; + expressions: expression list; +} + and meth = Tmeth_name of string and case = {c_lhs: pattern; c_guard: expression option; c_rhs: expression} diff --git a/compiler/ml/typedtree.mli b/compiler/ml/typedtree.mli index b1e7083fc7..d4e18ff24b 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -123,6 +123,7 @@ and expression_desc = M.x *) | Texp_constant of constant (** 1, 'a', "true", 1.0, 1l, 1L, 1n *) + | Texp_template of template_literal | Texp_let of rec_flag * value_binding list * expression (** let P1 = E1 and ... and Pn = EN in E (flag = Nonrecursive) let rec P1 = E1 and ... and Pn = EN in E (flag = Recursive) @@ -225,6 +226,12 @@ and expression_desc = | Texp_pack of module_expr | Texp_extension_constructor of Longident.t loc * Path.t +and template_literal = { + prefix: string option; + strings: string list; + expressions: expression list; +} + and meth = Tmeth_name of string and case = {c_lhs: pattern; c_guard: expression option; c_rhs: expression} diff --git a/compiler/ml/typedtreeIter.ml b/compiler/ml/typedtreeIter.ml index 9a31b9b5b9..e0b58b6b6c 100644 --- a/compiler/ml/typedtreeIter.ml +++ b/compiler/ml/typedtreeIter.ml @@ -223,6 +223,7 @@ end = struct (match exp.exp_desc with | Texp_ident _ -> () | Texp_constant _ -> () + | Texp_template {expressions; _} -> List.iter iter_expression expressions | Texp_let (rec_flag, list, exp) -> iter_bindings rec_flag list; iter_expression exp diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index 52a57b89c5..866fde5d3f 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -546,6 +546,19 @@ module SexpAst = struct | Pexp_ident longident_loc -> Sexp.list [Sexp.atom "Pexp_ident"; longident longident_loc.Asttypes.txt] | Pexp_constant c -> Sexp.list [Sexp.atom "Pexp_constant"; constant c] + | Pexp_template {tag; prefix; strings; expressions} -> + Sexp.list + [ + Sexp.atom "Pexp_template"; + (match tag with + | None -> Sexp.atom "None" + | Some expr -> Sexp.list [Sexp.atom "Some"; expression expr]); + (match prefix with + | None -> Sexp.atom "None" + | Some prefix -> Sexp.list [Sexp.atom "Some"; string prefix]); + Sexp.list (map_empty ~f:string strings); + Sexp.list (map_empty ~f:expression expressions); + ] | Pexp_let (flag, vbs, expr) -> Sexp.list [ diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index 6774b2bc2b..54d7de9519 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1239,7 +1239,7 @@ and walk_expression expr t comments = | None -> attach t.trailing longident.loc trailing) | Pexp_variant (_label, None) -> () | Pexp_variant (_label, Some expr) -> walk_expression expr t comments - | Pexp_array exprs | Pexp_tuple exprs -> + | Pexp_array exprs | Pexp_tuple exprs | Pexp_template {expressions = exprs} -> walk_list (exprs |> List.map (fun e -> Expression e)) t comments | Pexp_record (rows, spread_expr) -> if rows = [] then attach t.inside expr.pexp_loc comments diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index a3dd8f981e..4a7ddc61db 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -228,9 +228,6 @@ let template_literal_attr = (Location.mknoloc "res.template", Parsetree.PStr []) let make_pat_variant_spread_attr = (Location.mknoloc "res.patVariantSpread", Parsetree.PStr []) -let tagged_template_literal_attr = - (Location.mknoloc "res.taggedTemplate", Parsetree.PStr []) - let spread_attr = (Location.mknoloc "res.spread", Parsetree.PStr []) type argument = {label: Asttypes.arg_label; expr: Parsetree.expression} @@ -2273,14 +2270,17 @@ and parse_primary_expr ~operand ?(no_call = false) p = when no_call = false && p.prev_end_pos.pos_lnum == p.start_pos.pos_lnum -> ( match expr.pexp_desc with - | Pexp_ident long_ident -> parse_template_expr ~prefix:long_ident p + | Pexp_ident long_ident -> + let template_expr = parse_template_expr ~prefix:long_ident p in + {template_expr with pexp_loc = mk_loc start_pos p.prev_end_pos} | _ -> Parser.err ~start_pos:expr.pexp_loc.loc_start ~end_pos:expr.pexp_loc.loc_end p (Diagnostics.message "Tagged template literals are currently restricted to names like: \ myTagFunction`foo ${bar}`."); - parse_template_expr p) + let template_expr = parse_template_expr p in + {template_expr with pexp_loc = mk_loc start_pos p.prev_end_pos}) | _ -> expr in loop p operand @@ -2446,96 +2446,56 @@ and parse_binary_expr ?(context = OrdinaryExpr) ?a p prec = (* ) *) and parse_template_expr ?prefix p = - let part_prefix = - (* we could stop treating json prefix as something special - but we would first need to remove @as(json`true`) feature *) - match prefix with - | Some {txt = Longident.Lident ("json" as prefix); _} -> Some prefix - | _ -> Some "js" - in - let parse_parts p = - let rec aux acc = - let start_pos = p.Parser.start_pos in + let rec aux strings values = Parser.next_template_literal_token p; match p.token with - | TemplateTail (txt, last_pos) -> + | TemplateTail (txt, _last_pos) -> Parser.next p; - let loc = mk_loc start_pos last_pos in - let str = - Ast_helper.Exp.constant ~attrs:[template_literal_attr] ~loc - (Pconst_string (txt, part_prefix)) - in - List.rev ((str, None) :: acc) - | TemplatePart (txt, last_pos) -> + (List.rev (txt :: strings), List.rev values) + | TemplatePart (txt, _last_pos) -> Parser.next p; - let loc = mk_loc start_pos last_pos in let expr = parse_expr_block p in - let str = - Ast_helper.Exp.constant ~attrs:[template_literal_attr] ~loc - (Pconst_string (txt, part_prefix)) - in - aux ((str, Some expr) :: acc) + aux (txt :: strings) (expr :: values) | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - [] + ([""], []) in - aux [] + aux [] [] in - let parts = parse_parts p in - let strings = List.map fst parts in - let values = Ext_list.filter_map parts snd in - - let gen_tagged_template_call (lident_loc : Longident.t Location.loc) = - let ident = Ast_helper.Exp.ident ~attrs:[] ~loc:lident_loc.loc lident_loc in - let strings_array = - Ast_helper.Exp.array ~attrs:[] ~loc:Location.none strings - in - let values_array = - Ast_helper.Exp.array ~attrs:[] ~loc:Location.none values - in - Ast_helper.Exp.apply - ~attrs:[tagged_template_literal_attr] - ~loc:lident_loc.loc ident - [(Nolabel, strings_array); (Nolabel, values_array)] - in - - let hidden_operator = - let op = Location.mknoloc (Longident.Lident "++") in - Ast_helper.Exp.ident op - in - let concat (e1 : Parsetree.expression) (e2 : Parsetree.expression) = - let loc = mk_loc e1.pexp_loc.loc_start e2.pexp_loc.loc_end in - Ast_helper.Exp.apply ~attrs:[template_literal_attr] ~loc hidden_operator - [(Nolabel, e1); (Nolabel, e2)] - in - let gen_interpolated_string () = - let subparts = - List.flatten - (List.map - (fun part -> - match part with - | s, Some v -> [s; v] - | s, None -> [s]) - parts) - in - let expr_option = - List.fold_left - (fun acc subpart -> - Some - (match acc with - | Some expr -> concat expr subpart - | None -> subpart)) - None subparts + let strings, values = parse_parts p in + let prefix_value = + match prefix with + | Some {txt = Longident.Lident "json"; _} -> Some "json" + | _ -> None + in + let template_literal_const txt = + let delim = + match prefix_value with + | None -> Some "js" + | Some prefix -> Some prefix in - match expr_option with - | Some expr -> expr - | None -> Ast_helper.Exp.constant (Pconst_string ("", None)) + let expr = Ast_helper.Exp.constant (Pconst_string (txt, delim)) in + {expr with pexp_attributes = [template_literal_attr]} in - match prefix with - | Some {txt = Longident.Lident "json"; _} | None -> gen_interpolated_string () - | Some lident_loc -> gen_tagged_template_call lident_loc + | Some {txt = Longident.Lident "json"; _} | None -> ( + match values with + | [] -> ( + match strings with + | [txt] -> template_literal_const txt + | _ -> + Ast_helper.Exp.template + {tag = None; prefix = prefix_value; strings; expressions = values}) + | _ -> + Ast_helper.Exp.template + {tag = None; prefix = prefix_value; strings; expressions = values}) + | Some lident_loc -> + let tag_expr = + Ast_helper.Exp.ident ~attrs:[] ~loc:lident_loc.loc lident_loc + in + Ast_helper.Exp.template + {tag = Some tag_expr; prefix = None; strings; expressions = values} (* Overparse: let f = a : int => a + 1, is it (a : int) => or (a): int => * Also overparse constraints: diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index 391e51bede..6ffe4f259d 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -690,6 +690,7 @@ let has_tagged_template_literal_attr attrs = let is_template_literal expr = match expr.pexp_desc with + | Pexp_template {tag = None; _} -> true | Pexp_apply { funct = {pexp_desc = Pexp_ident {txt = Longident.Lident "++"}}; @@ -703,6 +704,7 @@ let is_template_literal expr = let is_tagged_template_literal expr = match expr with + | {pexp_desc = Pexp_template {tag = Some _; _}; _} -> true | {pexp_desc = Pexp_apply _; pexp_attributes = attrs} -> has_tagged_template_literal_attr attrs | _ -> false diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index a2780aaf7e..839d52acd8 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3305,13 +3305,15 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = {funct = e; args = [(Nolabel, {pexp_desc = Pexp_array sub_lists})]} when ParsetreeViewer.is_spread_belt_list_concat e -> print_belt_list_concat_apply ~state sub_lists cmt_tbl + | Pexp_template template_literal -> + print_template_literal ~state template_literal cmt_tbl | Pexp_apply {funct = call_expr; args} -> if ParsetreeViewer.is_unary_expression e then print_unary_expression ~state e cmt_tbl else if ParsetreeViewer.is_template_literal e then - print_template_literal ~state e cmt_tbl + print_template_literal_expr ~state e cmt_tbl else if ParsetreeViewer.is_tagged_template_literal e then - print_tagged_template_literal ~state call_expr args cmt_tbl + print_tagged_template_literal_expr ~state call_expr args cmt_tbl else if ParsetreeViewer.is_binary_expression e then print_binary_expression ~state e cmt_tbl else print_pexp_apply ~state e cmt_tbl @@ -3687,7 +3689,7 @@ and print_set_field_expr ~state attrs lhs longident_loc rhs loc cmt_tbl = in print_comments doc cmt_tbl loc -and print_template_literal ~state expr cmt_tbl = +and print_template_literal_expr ~state expr cmt_tbl = let tag = ref "js" in let rec walk_expr expr = let open Parsetree in @@ -3722,7 +3724,7 @@ and print_template_literal ~state expr cmt_tbl = Doc.text "`"; ] -and print_tagged_template_literal ~state call_expr args cmt_tbl = +and print_tagged_template_literal_expr ~state call_expr args cmt_tbl = let strings_list, values_list = match args with | [ @@ -3769,6 +3771,44 @@ and print_tagged_template_literal ~state call_expr args cmt_tbl = let tag = print_expression_with_comments ~state call_expr cmt_tbl in Doc.concat [tag; Doc.text "`"; content; Doc.text "`"] +and print_template_literal ~state + (template_literal : Parsetree.template_literal) cmt_tbl = + let {Parsetree.tag; prefix; strings; expressions} = template_literal in + let string_docs = List.map print_string_contents strings in + let value_docs = + List.map + (fun expr -> + let doc = print_expression_with_comments ~state expr cmt_tbl in + let doc = + match Parens.expr expr with + | Parens.Parenthesized -> add_parens doc + | Braced braces -> print_braces doc expr braces + | Nothing -> doc + in + Doc.group (Doc.concat [Doc.text "${"; Doc.indent doc; Doc.rbrace])) + expressions + in + let rec interleave acc strings values = + match (strings, values) with + | [], [] -> acc + | [string_doc], [] -> Doc.concat [acc; string_doc] + | string_doc :: rest_strings, value_doc :: rest_values -> + interleave + (Doc.concat [acc; string_doc; value_doc]) + rest_strings rest_values + | _ -> assert false + in + let content = interleave Doc.nil string_docs value_docs in + let tag_doc = + match tag with + | Some tag_expr -> print_expression_with_comments ~state tag_expr cmt_tbl + | None -> ( + match prefix with + | None -> Doc.nil + | Some prefix -> Doc.text prefix) + in + Doc.concat [tag_doc; Doc.text "`"; content; Doc.text "`"] + and print_unary_expression ~state expr cmt_tbl = let print_unary_operator op = Doc.text @@ -3921,14 +3961,6 @@ and print_binary_expression ~state (expr : Parsetree.expression) cmt_tbl = | _ -> assert false else match expr.pexp_desc with - | Pexp_apply - { - funct = {pexp_desc = Pexp_ident {txt = Longident.Lident "++"; loc}}; - args = [(Nolabel, _); (Nolabel, _)]; - } - when loc.loc_ghost -> - let doc = print_template_literal ~state expr cmt_tbl in - print_comments doc cmt_tbl expr.Parsetree.pexp_loc | Pexp_setfield (lhs, field, rhs) -> let doc = print_set_field_expr ~state expr.pexp_attributes lhs field rhs diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_Error.js b/packages/@rescript/runtime/lib/es6/Stdlib_Error.js index cbeb0edadb..62b0525d7c 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_Error.js +++ b/packages/@rescript/runtime/lib/es6/Stdlib_Error.js @@ -22,7 +22,7 @@ let $$TypeError = {}; let $$URIError = {}; function panic(msg) { - throw new Error(`Panic! ` + msg); + throw new Error(`Panic! ${msg}`); } export { diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_JsError.js b/packages/@rescript/runtime/lib/es6/Stdlib_JsError.js index b97281d70a..27c38895d0 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_JsError.js +++ b/packages/@rescript/runtime/lib/es6/Stdlib_JsError.js @@ -54,7 +54,7 @@ let $$URIError$1 = { }; function panic(msg) { - throw new Error(`Panic! ` + msg); + throw new Error(`Panic! ${msg}`); } export { diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_Option.js b/packages/@rescript/runtime/lib/es6/Stdlib_Option.js index 375b5cd59b..101af14dc5 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_Option.js +++ b/packages/@rescript/runtime/lib/es6/Stdlib_Option.js @@ -1,6 +1,5 @@ -import * as Stdlib_JsError from "./Stdlib_JsError.js"; import * as Primitive_option from "./Primitive_option.js"; function filter(opt, p) { @@ -18,9 +17,9 @@ function forEach(opt, f) { function getOrThrow(x, message) { if (x !== undefined) { return Primitive_option.valFromOption(x); - } else { - return Stdlib_JsError.panic(message !== undefined ? message : "Option.getOrThrow called for None value"); } + let msg = message !== undefined ? message : "Option.getOrThrow called for None value"; + throw new Error(`Panic! ${msg}`); } function mapOr(opt, $$default, f) { diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_Result.js b/packages/@rescript/runtime/lib/es6/Stdlib_Result.js index 1ad161f4c1..b93ac4d4c1 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib_Result.js +++ b/packages/@rescript/runtime/lib/es6/Stdlib_Result.js @@ -1,13 +1,12 @@ -import * as Stdlib_JsError from "./Stdlib_JsError.js"; function getOrThrow(x, message) { if (x.TAG === "Ok") { return x._0; - } else { - return Stdlib_JsError.panic(message !== undefined ? message : "Result.getOrThrow called for Error value"); } + let msg = message !== undefined ? message : "Result.getOrThrow called for Error value"; + throw new Error(`Panic! ${msg}`); } function mapOr(opt, $$default, f) { diff --git a/packages/@rescript/runtime/lib/js/Stdlib_Error.js b/packages/@rescript/runtime/lib/js/Stdlib_Error.js index 1421bc57c6..dd2d5db85b 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_Error.js +++ b/packages/@rescript/runtime/lib/js/Stdlib_Error.js @@ -22,7 +22,7 @@ let $$TypeError = {}; let $$URIError = {}; function panic(msg) { - throw new Error(`Panic! ` + msg); + throw new Error(`Panic! ${msg}`); } exports.fromException = fromException; diff --git a/packages/@rescript/runtime/lib/js/Stdlib_JsError.js b/packages/@rescript/runtime/lib/js/Stdlib_JsError.js index 9aaf7a5d37..4dd4a35510 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_JsError.js +++ b/packages/@rescript/runtime/lib/js/Stdlib_JsError.js @@ -54,7 +54,7 @@ let $$URIError$1 = { }; function panic(msg) { - throw new Error(`Panic! ` + msg); + throw new Error(`Panic! ${msg}`); } exports.$$EvalError = $$EvalError$1; diff --git a/packages/@rescript/runtime/lib/js/Stdlib_Option.js b/packages/@rescript/runtime/lib/js/Stdlib_Option.js index fa977293fe..7319450397 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_Option.js +++ b/packages/@rescript/runtime/lib/js/Stdlib_Option.js @@ -1,6 +1,5 @@ 'use strict'; -let Stdlib_JsError = require("./Stdlib_JsError.js"); let Primitive_option = require("./Primitive_option.js"); function filter(opt, p) { @@ -18,9 +17,9 @@ function forEach(opt, f) { function getOrThrow(x, message) { if (x !== undefined) { return Primitive_option.valFromOption(x); - } else { - return Stdlib_JsError.panic(message !== undefined ? message : "Option.getOrThrow called for None value"); } + let msg = message !== undefined ? message : "Option.getOrThrow called for None value"; + throw new Error(`Panic! ${msg}`); } function mapOr(opt, $$default, f) { diff --git a/packages/@rescript/runtime/lib/js/Stdlib_Result.js b/packages/@rescript/runtime/lib/js/Stdlib_Result.js index fd577f3983..590300c7f3 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib_Result.js +++ b/packages/@rescript/runtime/lib/js/Stdlib_Result.js @@ -1,13 +1,12 @@ 'use strict'; -let Stdlib_JsError = require("./Stdlib_JsError.js"); function getOrThrow(x, message) { if (x.TAG === "Ok") { return x._0; - } else { - return Stdlib_JsError.panic(message !== undefined ? message : "Result.getOrThrow called for Error value"); } + let msg = message !== undefined ? message : "Result.getOrThrow called for Error value"; + throw new Error(`Panic! ${msg}`); } function mapOr(opt, $$default, f) { diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index df30210ef2..e5b65d839b 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -1464,9 +1464,6 @@ Path ForAuto.a Complete src/Completion.res 234:34 posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:36] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:34], ...[234:34->234:35]) -posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:34] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:30], ...[234:32->234:34]) posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:32->234:34] Pexp_ident na:[234:32->234:34] Completable: Cpath Value[na] @@ -2216,9 +2213,6 @@ Path AndThatOther.T Complete src/Completion.res 381:24 posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:12->381:26] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:24], ...[381:24->381:25]) -posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:12->381:24] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:14], ...[381:16->381:24]) posCursor:[381:24] posNoWhite:[381:23] Found expr:[381:16->381:24] Pexp_ident ForAuto.:[381:16->381:24] Completable: Cpath Value[ForAuto, ""] @@ -2243,9 +2237,6 @@ Path ForAuto. Complete src/Completion.res 384:38 posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:12->384:41] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:12->384:39], ...[384:39->384:40]) -posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:12->384:39] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:12->384:17], ...[384:19->384:39]) posCursor:[384:38] posNoWhite:[384:37] Found expr:[384:19->384:39] Pexp_send [384:38->384:38] e:[384:19->384:36] Completable: Cpath Value[FAO, forAutoObject][""] @@ -2271,9 +2262,6 @@ Path FAO.forAutoObject Complete src/Completion.res 387:24 posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:11->387:26] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[387:11->387:24], ...[387:24->387:25]) -posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:11->387:24] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[387:11->387:12], ...[387:14->387:24]) posCursor:[387:24] posNoWhite:[387:23] Found expr:[387:14->387:24] Pexp_field [387:14->387:23] _:[387:24->387:24] Completable: Cpath Value[funRecord]."" @@ -2332,9 +2320,6 @@ Path ma Complete src/Completion.res 399:14 posCursor:[399:14] posNoWhite:[399:13] Found expr:[398:14->399:20] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[398:14->399:16], ...[399:16->399:19]) -posCursor:[399:14] posNoWhite:[399:13] Found expr:[398:14->399:16] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[398:14->399:11], ...[399:13->399:16]) posCursor:[399:14] posNoWhite:[399:13] Found expr:[399:13->399:16] Pexp_ident red:[399:13->399:16] Completable: Cpath Value[red] @@ -2353,9 +2338,6 @@ Path red Complete src/Completion.res 404:25 posCursor:[404:25] posNoWhite:[404:24] Found expr:[402:14->404:31] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[402:14->404:27], ...[404:27->404:30]) -posCursor:[404:25] posNoWhite:[404:24] Found expr:[402:14->404:27] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[402:14->404:22], ...[404:24->404:27]) posCursor:[404:25] posNoWhite:[404:24] Found expr:[404:24->404:27] Pexp_ident red:[404:24->404:27] Completable: Cpath Value[red] @@ -2374,9 +2356,6 @@ Path red Complete src/Completion.res 407:22 posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:11->485:0] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[407:11->425:17], ...[430:0->485:0]) -posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:11->425:17] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[407:11->407:19], ...[407:21->425:17]) posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:21->425:17] posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:21->407:22] Pexp_ident r:[407:21->407:22] diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt index 2cfc84238d..fbf1559755 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -52,56 +52,12 @@ Path }] Complete src/CompletionTaggedTemplate.res 16:20 -posCursor:[16:20] posNoWhite:[16:19] Found expr:[16:11->0:-1] -Completable: Cpath Value[meh](Nolabel, Nolabel)."" +posCursor:[16:20] posNoWhite:[16:19] Found expr:[16:19->16:20] +Pexp_ident .:[16:19->16:20] +Completable: Cpath Value[.] Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib -ContextPath Value[meh](Nolabel, Nolabel)."" -ContextPath Value[meh](Nolabel, Nolabel) -ContextPath Value[meh] -Path meh -ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> -ContextPath Value[meh](Nolabel, Nolabel, Nolabel) -ContextPath Value[meh] -Path meh -CPPipe pathFromEnv:M found:true -Path M. -Path -[{ - "label": "->M.xyz", - "kind": 12, - "tags": [], - "detail": "(t, int) => int", - "documentation": null, - "sortText": "xyz", - "insertText": "->M.xyz", - "additionalTextEdits": [{ - "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, - "newText": "" - }] - }, { - "label": "->M.b", - "kind": 12, - "tags": [], - "detail": "t => string", - "documentation": null, - "sortText": "b", - "insertText": "->M.b", - "additionalTextEdits": [{ - "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, - "newText": "" - }] - }, { - "label": "->M.a", - "kind": 12, - "tags": [], - "detail": "t => int", - "documentation": null, - "sortText": "a", - "insertText": "->M.a", - "additionalTextEdits": [{ - "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, - "newText": "" - }] - }] +ContextPath Value[.] +Path . +[] diff --git a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js deleted file mode 100644 index ebaf897020..0000000000 --- a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Foo from "Foo"; - -let React = {}; - -let Test = {}; - -function test(className) { - return Foo; -} - -export { - React, - Test, - test, -} -/* Foo Not a pure module */ diff --git a/tests/docstring_tests/DocTest.res.js b/tests/docstring_tests/DocTest.res.js index 04766264ff..b4cfcc41d6 100644 --- a/tests/docstring_tests/DocTest.res.js +++ b/tests/docstring_tests/DocTest.res.js @@ -70,7 +70,7 @@ async function extractDocFromFile(file) { let e = Primitive_exceptions.internalToException(raw_e); if (e.RE_EXN_ID === "JsExn") { console.error(e._1); - return Stdlib_JsError.panic(`Failed to extract code blocks from ` + file); + return Stdlib_JsError.panic(`Failed to extract code blocks from ${file}`); } throw e; } @@ -93,7 +93,7 @@ async function extractExamples() { return false; } }); - console.log(`Extracting examples from ` + docFiles.length.toString() + ` runtime files...`); + console.log(`Extracting examples from ${docFiles.length.toString()} runtime files...`); let examples = []; await ArrayUtils.forEachAsyncInBatches(docFiles, batchSize, async f => { let doc = await extractDocFromFile(Nodepath.join(runtimePath, f)); @@ -102,7 +102,7 @@ async function extractExamples() { return; } console.error(doc._0); - return Stdlib_JsError.panic(`Error extracting code blocks for ` + f); + return Stdlib_JsError.panic(`Error extracting code blocks for ${f}`); }); examples.sort((a, b) => Primitive_string.compare(a.id, b.id)); return examples; @@ -130,23 +130,23 @@ async function main() { } }); if (ignoreExample) { - console.warn(`Ignoring ` + example.id + ` tests. Not supported by Node ` + nodeVersion.toString()); + console.warn(`Ignoring ${example.id} tests. Not supported by Node ${nodeVersion.toString()}`); return; } let code = example.code; if (code.length === 0) { return; } else if (code.includes("await")) { - return `testAsync("` + example.name + `", async () => { + return `testAsync("${example.name}", async () => { module Test = { - ` + code + ` + ${code} } () })`; } else { - return `test("` + example.name + `", () => { + return `test("${example.name}", () => { module Test = { - ` + code + ` + ${code} } () })`; @@ -155,8 +155,8 @@ async function main() { if (codeExamples.length === 0) { return; } - let content = `describe("` + key + `", () => { -` + codeExamples.join("\n") + ` + let content = `describe("${key}", () => { +${codeExamples.join("\n")} })`; output.push(content); }); @@ -165,7 +165,7 @@ async function main() { let fileContent = `open Mocha @@warning("-32-34-60-37-109-3-44") -` + output.join("\n"); +${output.join("\n")}`; return await Promises.writeFile(filepath, fileContent); } diff --git a/tests/ounit_tests/ounit_template_literal_tests.ml b/tests/ounit_tests/ounit_template_literal_tests.ml new file mode 100644 index 0000000000..ba8a7caeaa --- /dev/null +++ b/tests/ounit_tests/ounit_template_literal_tests.ml @@ -0,0 +1,47 @@ +let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) + +let lambda_for_template_literal () = + let prev_dont_write_files = !Clflags.dont_write_files in + let prev_assume_no_mli = !Clflags.assume_no_mli in + Clflags.dont_write_files := true; + Clflags.assume_no_mli := Clflags.Mli_non_exists; + Res_compmisc.init_path (); + let module_name = "TemplateLiteralTest" in + let env = Res_compmisc.initial_env ~modulename:module_name () in + Env.set_unit_name module_name; + let template_expr = + Ast_helper.Exp.template + { + tag = None; + prefix = None; + strings = ["Hello "; ""]; + expressions = + [Ast_helper.Exp.constant (Parsetree.Pconst_string ("world", None))]; + } + in + let binding = + Ast_helper.Vb.mk + (Ast_helper.Pat.var (Location.mknoloc "greeting")) + template_expr + in + let structure = [Ast_helper.Str.value Asttypes.Nonrecursive [binding]] in + let typed, coercion, _, _ = + Typemod.type_implementation_more "TemplateLiteralTest.res" module_name + module_name env structure + in + let lambda, _exports = + Translmod.transl_implementation module_name (typed, coercion) + in + Clflags.dont_write_files := prev_dont_write_files; + Clflags.assume_no_mli := prev_assume_no_mli; + lambda + +let suites = + __FILE__ + >::: [ + ( __LOC__ >:: fun _ -> + let lambda = lambda_for_template_literal () in + let printed = Format.asprintf "%a" Printlambda.lambda lambda in + OUnit.assert_bool "expected string.template in lambda" + (Ext_string.contain_substring printed "string.template") ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index 37a1d7e597..bac932da5e 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -21,6 +21,7 @@ let suites = Ounit_bsb_regex_tests.suites; Ounit_bsb_pkg_tests.suites; Ounit_util_tests.suites; + Ounit_template_literal_tests.suites; ] let _ = OUnit.run_test_tt_main suites diff --git a/tests/syntax_tests/data/parsing/errors/structure/expected/gh16B.res.txt b/tests/syntax_tests/data/parsing/errors/structure/expected/gh16B.res.txt index 58a09da053..ef71e61bbf 100644 --- a/tests/syntax_tests/data/parsing/errors/structure/expected/gh16B.res.txt +++ b/tests/syntax_tests/data/parsing/errors/structure/expected/gh16B.res.txt @@ -15,16 +15,13 @@ open Ws let wss = Server.make { port = 82 } let address = wss -> Server.address let log [arity:1]msg = - Js.log - (((((({js|> Server: |js})[@res.template ]) ++ msg)[@res.template ]) ++ - (({js||js})[@res.template ]))[@res.template ]) + Js.log (((("> Server: " ++ msg) ++ "")[@res.template ])) ;;log - (((((((((((((({js|Running on: |js})[@res.template ]) ++ address.address) - [@res.template ]) ++ (({js|:|js})[@res.template ])) - [@res.template ]) ++ (address.port -> string_of_int)) - [@res.template ]) ++ (({js| (|js})[@res.template ])) - [@res.template ]) ++ address.family) - [@res.template ]) ++ (({js|)|js})[@res.template ]))[@res.template ]) + (((((((("Running on: " ++ address.address) ++ ":") ++ + (address.port -> string_of_int)) + ++ " (") + ++ address.family) + ++ ")")[@res.template ])) module ClientSet = struct module T = diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/es6template.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/es6template.res.txt index f88334a2e2..ae718812d6 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/es6template.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/es6template.res.txt @@ -4,75 +4,23 @@ let s = (({js|multi string |js})[@res.template ]) -let s = - (((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ (({js||js}) - [@res.template ])) - [@res.template ]) -let s = - (((((({js|before|js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js||js})[@res.template ])) +let s = ((("" ++ foo) ++ "")[@res.template ]) +let s = ((("before" ++ foo) ++ "")[@res.template ]) +let s = ((("before " ++ foo) ++ "")[@res.template ]) +let s = ((("before " ++ foo) ++ "")[@res.template ]) +let s = ((("" ++ foo) ++ "after")[@res.template ]) +let s = ((("" ++ foo) ++ " after")[@res.template ]) +let s = ((("" ++ foo) ++ " after")[@res.template ]) +let s = ((((("" ++ foo) ++ "") ++ bar) ++ "")[@res.template ]) +let s = ((((((("" ++ foo) ++ "") ++ bar) ++ "") ++ baz) ++ "") [@res.template ]) -let s = - (((((({js|before |js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js||js})[@res.template ])) - [@res.template ]) -let s = - (((((({js|before |js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js||js})[@res.template ])) +let s = ((((("" ++ foo) ++ " ") ++ bar) ++ "")[@res.template ]) +let s = ((((((("" ++ foo) ++ " ") ++ bar) ++ " ") ++ baz) ++ "") [@res.template ]) -let s = - (((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js|after|js})[@res.template ])) - [@res.template ]) -let s = - (((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| after|js})[@res.template ])) - [@res.template ]) -let s = - (((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| after|js})[@res.template ])) - [@res.template ]) -let s = - (((((((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js||js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js||js})[@res.template ])) - [@res.template ]) -let s = - (((((((((((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js||js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js||js})[@res.template ])) - [@res.template ]) ++ baz) - [@res.template ]) ++ (({js||js})[@res.template ])) - [@res.template ]) -let s = - (((((((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| |js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js||js})[@res.template ])) - [@res.template ]) -let s = - (((((((((((((({js||js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| |js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js| |js})[@res.template ])) - [@res.template ]) ++ baz) - [@res.template ]) ++ (({js||js})[@res.template ])) - [@res.template ]) -let s = - (((((((((({js| before |js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| |js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js| after |js})[@res.template ])) +let s = (((((" before " ++ foo) ++ " ") ++ bar) ++ " after ") [@res.template ]) let s = - (((((((((((((({js|before |js})[@res.template ]) ++ foo)[@res.template ]) ++ - (({js| middle |js})[@res.template ])) - [@res.template ]) ++ bar) - [@res.template ]) ++ (({js| |js})[@res.template ])) - [@res.template ]) ++ baz) - [@res.template ]) ++ (({js| wow |js})[@res.template ])) + ((((((("before " ++ foo) ++ " middle ") ++ bar) ++ " ") ++ baz) ++ " wow ") [@res.template ]) let s = (({js| @@ -92,16 +40,10 @@ let s = let s = (({js|$dollar without $braces $interpolation|js})[@res.template ]) let s = (({json|null|json})[@res.template ]) let x = (({js|foo\`bar\$\\foo|js})[@res.template ]) -let x = - (((((((((({js|foo\`bar\$\\foo|js})[@res.template ]) ++ a)[@res.template ]) - ++ (({js| \` |js})[@res.template ])) - [@res.template ]) ++ b) - [@res.template ]) ++ (({js| \` xx|js})[@res.template ])) +let x = ((((("foo\\`bar\\$\\\\foo" ++ a) ++ " \\` ") ++ b) ++ " \\` xx") [@res.template ]) let thisIsFine = (({js|$something|js})[@res.template ]) let thisIsAlsoFine = (({js|fine\$|js})[@res.template ]) let isThisFine = (({js|shouldBeFine$|js})[@res.template ]) -;;(((((({js|$|js})[@res.template ]) ++ dollarAmountInt)[@res.template ]) ++ - (({js||js})[@res.template ]))[@res.template ]) -;;(((((({js|\$|js})[@res.template ]) ++ dollarAmountInt)[@res.template ]) ++ - (({js||js})[@res.template ]))[@res.template ]) \ No newline at end of file +;;((("$" ++ dollarAmountInt) ++ "")[@res.template ]) +;;((("\\$" ++ dollarAmountInt) ++ "")[@res.template ]) \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt index cbff81b679..34cd4449ad 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt @@ -252,9 +252,8 @@ let _ =