Skip to content

Commit f96987f

Browse files
committed
Normalize string literals before matching
Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent a8794b0 commit f96987f

32 files changed

Lines changed: 322 additions & 3716 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#### :boom: Breaking Change
1616

17+
- Reject tagged template literals in patterns. Patterns cannot invoke their tag; previously their raw payload was compiled as a plain string comparison. https://github.com/rescript-lang/rescript/pull/8603
1718
- Remove runtime APIs that were deprecated for removal in ReScript 13, including the `Char` module, unsafe `Obj` operations, legacy `Pervasives` helpers, and `Array.unsafe_get`. https://github.com/rescript-lang/rescript/pull/8564
1819
- Remove the deprecated `Js` namespace and its runtime modules. https://github.com/rescript-lang/rescript/pull/8531
1920
- Move Belt into the separately installed `@rescript/belt` package. Projects using Belt must install the package and list it in their `rescript.json` dependencies. https://github.com/rescript-lang/rescript/pull/8554
@@ -25,6 +26,7 @@
2526

2627
#### :rocket: New Feature
2728

29+
- Support UTF-16 surrogate-pair escapes such as `"\uD83D\uDE00"` in ordinary string literals. https://github.com/rescript-lang/rescript/pull/8603
2830
- Support dynamic imports of external bindings annotated with `@scope`; the generated import follows the complete property path. These imports were previously rejected. https://github.com/rescript-lang/rescript/pull/8582
2931
- Add `@res.hoistedFunction` for emitting nested module functions as flat JavaScript exports. https://github.com/rescript-lang/rescript/pull/8402
3032
- Add source map support with linked, inline, and hidden modes. https://github.com/rescript-lang/rescript/pull/8393

compiler/core/j.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ and exception_ident = ident
7272
and for_ident = ident
7373
and for_direction = Js_op.direction_flag
7474
and property_map = (property_name * expression) list
75-
and delim = External_arg_spec.delim = DNone | DStarJ | DNoQuotes | DBackQuotes
75+
and delim = External_arg_spec.delim = DNone | DNoQuotes | DBackQuotes
7676

7777
and record_rest_field = {
7878
record_rest_label: string;

compiler/core/js_dump.ml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,6 @@ and expression_desc cxt ~(level : int) f x : cxt =
771771
*)
772772
let () =
773773
match delim with
774-
| DStarJ -> P.string f ("\"" ^ txt ^ "\"")
775774
| DNoQuotes -> P.string f txt
776775
| DNone -> Js_dump_string.pp_string f txt
777776
| DBackQuotes -> P.string f ("`" ^ txt ^ "`")

compiler/core/js_exp_make.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ let rec float_equal ?comment (e0 : t) (e1 : t) : t =
13531353
let int_equal = float_equal
13541354

13551355
let tag_type = function
1356-
| Variant_runtime.String s -> str s ~delim:DStarJ
1356+
| Variant_runtime.String s -> str s
13571357
| Int i -> small_int i
13581358
| Float f -> float f
13591359
| BigInt i ->

compiler/frontend/ast_utf8_string_interp.ml

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,16 @@ module Delim = struct
279279
| "js" -> if is_template then BackQuotes else Js
280280
| _ -> Unrecognized
281281

282-
let escaped_j_delimiter = "*j" (* not user level syntax allowed *)
283282
let some_escaped_back_quote_delimiter = Some "bq"
284-
let some_escaped_j_delimiter = Some escaped_j_delimiter
285283
end
286284

285+
(* Scanner string payloads still contain JavaScript escape spelling. Decode an
286+
ordinary string exactly once here, before it reaches typing and matching. *)
287+
let semantic_string loc s =
288+
match String_literal.decode_js_escapes s with
289+
| Some decoded -> decoded
290+
| None -> Location.raise_errorf ~loc "Invalid string escape sequence"
291+
287292
let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
288293
let is_template =
289294
Ext_list.exists e.pexp_attributes (fun ({txt}, _) ->
@@ -293,12 +298,8 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
293298
in
294299
match Delim.parse_unprocessed is_template delim with
295300
| Js ->
296-
let js_str = Ast_utf8_string.transform e.pexp_loc s in
297-
{
298-
e with
299-
pexp_desc =
300-
Pexp_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter));
301-
}
301+
let semantic = semantic_string e.pexp_loc s in
302+
{e with pexp_desc = Pexp_constant (Pconst_string (semantic, None))}
302303
| BackQuotes ->
303304
{
304305
e with
@@ -311,23 +312,16 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
311312
let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern =
312313
match Delim.parse_unprocessed false delim with
313314
| Js ->
314-
let js_str = Ast_utf8_string.transform p.ppat_loc s in
315-
(match String_literal.decode_js_escapes js_str with
316-
| Some _ -> ()
317-
| None ->
318-
Location.raise_errorf ~loc:p.ppat_loc "Invalid string escape sequence");
319-
{
320-
p with
321-
ppat_desc =
322-
Ppat_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter));
323-
}
315+
let semantic = semantic_string p.ppat_loc s in
316+
{p with ppat_desc = Ppat_constant (Pconst_string (semantic, None))}
324317
| BackQuotes ->
325318
{
326319
p with
327320
ppat_desc =
328321
Ppat_constant
329322
(Pconst_string (s, Delim.some_escaped_back_quote_delimiter));
330323
}
324+
| Unrecognized when delim = "INTERNAL_RES_CHAR_CONTENTS" -> p
331325
| Unrecognized ->
332326
Location.raise_errorf ~loc:p.ppat_loc
333327
"Tagged template literals are not supported in patterns"

compiler/ml/ast_mapper_from0.ml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,23 @@ let map_tuple3 f1 f2 f3 (x, y, z) = (f1 x, f2 y, f3 z)
8282
let map_opt f = function
8383
| None -> None
8484
| Some x -> Some (f x)
85-
let map_constant = function
85+
let is_template attrs =
86+
Ext_list.exists attrs (fun ({txt}, _) ->
87+
match txt with
88+
| "res.template" | "res.taggedTemplate" -> true
89+
| _ -> false)
90+
91+
let decode_js_string ~loc s =
92+
match String_literal.decode_js_escapes s with
93+
| Some s -> s
94+
| None -> Location.raise_errorf ~loc "Invalid string escape sequence"
95+
96+
let map_constant ~loc ~is_template = function
8697
| Pconst_integer (s, suffix) -> Pt.Pconst_integer (s, suffix)
8798
| Pconst_char c -> Pconst_char c
99+
| Pconst_string (s, Some "js") when is_template -> Pconst_string (s, Some "bq")
100+
| Pconst_string (s, Some ("js" | "*j")) ->
101+
Pconst_string (decode_js_string ~loc s, None)
88102
| Pconst_string (s, q) -> Pconst_string (s, q)
89103
| Pconst_float (s, suffix) -> Pconst_float (s, suffix)
90104

@@ -511,7 +525,9 @@ module E = struct
511525
let inner = sub.expr sub {e with pexp_attributes = inner_attrs0} in
512526
await ~loc ~attrs:(sub.attributes sub await_attrs0) inner
513527
| Pexp_ident x -> ident ~loc ~attrs (map_loc sub x)
514-
| Pexp_constant x -> constant ~loc ~attrs (map_constant x)
528+
| Pexp_constant x ->
529+
constant ~loc ~attrs
530+
(map_constant ~loc ~is_template:(is_template attrs) x)
515531
| Pexp_let (r, vbs, e) ->
516532
let_ ~loc ~attrs r (List.map (sub.value_binding sub) vbs) (sub.expr sub e)
517533
| Pexp_fun (lab, def, p, e) ->
@@ -879,9 +895,12 @@ module P = struct
879895
| Ppat_any -> any ~loc ~attrs ()
880896
| Ppat_var s -> var ~loc ~attrs (map_loc sub s)
881897
| Ppat_alias (p, s) -> alias ~loc ~attrs (sub.pat sub p) (map_loc sub s)
882-
| Ppat_constant c -> constant ~loc ~attrs (map_constant c)
898+
| Ppat_constant c ->
899+
constant ~loc ~attrs (map_constant ~loc ~is_template:false c)
883900
| Ppat_interval (c1, c2) ->
884-
interval ~loc ~attrs (map_constant c1) (map_constant c2)
901+
interval ~loc ~attrs
902+
(map_constant ~loc ~is_template:false c1)
903+
(map_constant ~loc ~is_template:false c2)
885904
| Ppat_tuple pl -> tuple ~loc ~attrs (List.map (sub.pat sub) pl)
886905
| Ppat_construct (l, p) ->
887906
construct ~loc ~attrs (map_loc sub l) (map_opt (sub.pat sub) p)

compiler/ml/ast_mapper_to0.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ let map_opt f = function
7979
let map_constant = function
8080
| Pconst_integer (s, suffix) -> Pt.Pconst_integer (s, suffix)
8181
| Pconst_char c -> Pconst_char c
82+
(* The PPX bridge uses parser-form ast0, where template segments are [js]
83+
strings distinguished by a template attribute. *)
84+
| Pconst_string (s, Some "bq") -> Pconst_string (s, Some "js")
8285
| Pconst_string (s, q) -> Pconst_string (s, q)
8386
| Pconst_float (s, suffix) -> Pconst_float (s, suffix)
8487

compiler/ml/external_arg_spec.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@
2424

2525
(** type definitions for arguments to a function declared external *)
2626

27-
type delim = DNone | DStarJ | DNoQuotes | DBackQuotes
27+
type delim = DNone | DNoQuotes | DBackQuotes
2828

2929
let parse_processed_delim = function
3030
| None -> Some DNone
3131
| Some "json" -> Some DNoQuotes
32-
| Some "*j" -> Some DStarJ
3332
| Some "bq" -> Some DBackQuotes
3433
| _ -> None
3534

compiler/ml/external_arg_spec.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* along with this program; if not, write to the Free Software
2323
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
2424

25-
type delim = DNone | DStarJ | DNoQuotes | DBackQuotes
25+
type delim = DNone | DNoQuotes | DBackQuotes
2626

2727
val parse_processed_delim : string option -> delim option
2828

compiler/ml/parmatch.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,7 @@ let const_compare x y =
269269
compare (float_of_string f1) (float_of_string f2)
270270
| Const_bigint (s1, b1), Const_bigint (s2, b2) ->
271271
Bigint_utils.compare (s1, b1) (s2, b2)
272-
| Const_string (s1, delim1), Const_string (s2, delim2) ->
273-
String_literal.compare (s1, delim1) (s2, delim2)
272+
| Const_string (s1, _), Const_string (s2, _) -> String.compare s1 s2
274273
| _, _ -> compare x y
275274

276275
let records_args l1 l2 =

0 commit comments

Comments
 (0)