Skip to content

Commit ad78db8

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

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
@@ -780,7 +780,6 @@ and expression_desc cxt ~(level : int) f x : cxt =
780780
*)
781781
let () =
782782
match delim with
783-
| DStarJ -> P.string f ("\"" ^ txt ^ "\"")
784783
| DNoQuotes -> P.string f txt
785784
| DNone -> Js_dump_string.pp_string f txt
786785
| 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
@@ -1359,7 +1359,7 @@ let rec float_equal ?comment (e0 : t) (e1 : t) : t =
13591359
let int_equal = float_equal
13601360

13611361
let tag_type = function
1362-
| Variant_runtime.String s -> str s ~delim:DStarJ
1362+
| Variant_runtime.String s -> str s
13631363
| Int i -> small_int i
13641364
| Float f -> float f
13651365
| BigInt i ->

compiler/frontend/ast_utf8_string_interp.ml

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ module Delim = struct
274274
let parse_processed = function
275275
| None -> Some External_arg_spec.DNone
276276
| Some "json" -> Some DNoQuotes
277-
| Some "*j" -> Some DStarJ
278277
| Some "bq" -> Some DBackQuotes
279278
| _ -> None
280279

@@ -286,11 +285,16 @@ module Delim = struct
286285
| "js" -> if is_template then BackQuotes else Js
287286
| _ -> Unrecognized
288287

289-
let escaped_j_delimiter = "*j" (* not user level syntax allowed *)
290288
let some_escaped_back_quote_delimiter = Some "bq"
291-
let some_escaped_j_delimiter = Some escaped_j_delimiter
292289
end
293290

291+
(* Scanner string payloads still contain JavaScript escape spelling. Decode an
292+
ordinary string exactly once here, before it reaches typing and matching. *)
293+
let semantic_string loc s =
294+
match String_literal.decode_js_escapes s with
295+
| Some decoded -> decoded
296+
| None -> Location.raise_errorf ~loc "Invalid string escape sequence"
297+
294298
let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
295299
let is_template =
296300
Ext_list.exists e.pexp_attributes (fun ({txt}, _) ->
@@ -300,12 +304,8 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
300304
in
301305
match Delim.parse_unprocessed is_template delim with
302306
| Js ->
303-
let js_str = Ast_utf8_string.transform e.pexp_loc s in
304-
{
305-
e with
306-
pexp_desc =
307-
Pexp_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter));
308-
}
307+
let semantic = semantic_string e.pexp_loc s in
308+
{e with pexp_desc = Pexp_constant (Pconst_string (semantic, None))}
309309
| BackQuotes ->
310310
{
311311
e with
@@ -318,23 +318,16 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
318318
let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern =
319319
match Delim.parse_unprocessed false delim with
320320
| Js ->
321-
let js_str = Ast_utf8_string.transform p.ppat_loc s in
322-
(match String_literal.decode_js_escapes js_str with
323-
| Some _ -> ()
324-
| None ->
325-
Location.raise_errorf ~loc:p.ppat_loc "Invalid string escape sequence");
326-
{
327-
p with
328-
ppat_desc =
329-
Ppat_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter));
330-
}
321+
let semantic = semantic_string p.ppat_loc s in
322+
{p with ppat_desc = Ppat_constant (Pconst_string (semantic, None))}
331323
| BackQuotes ->
332324
{
333325
p with
334326
ppat_desc =
335327
Ppat_constant
336328
(Pconst_string (s, Delim.some_escaped_back_quote_delimiter));
337329
}
330+
| Unrecognized when delim = "INTERNAL_RES_CHAR_CONTENTS" -> p
338331
| Unrecognized ->
339332
Location.raise_errorf ~loc:p.ppat_loc
340333
"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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
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
type cst = Arg_int_lit of int | Arg_string_lit of string * delim
3030

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
type cst = private Arg_int_lit of int | Arg_string_lit of string * delim
2828

compiler/ml/parmatch.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ let const_compare x y =
271271
compare (float_of_string f1) (float_of_string f2)
272272
| Const_bigint (s1, b1), Const_bigint (s2, b2) ->
273273
Bigint_utils.compare (s1, b1) (s2, b2)
274-
| Const_string (s1, delim1), Const_string (s2, delim2) ->
275-
String_literal.compare (s1, delim1) (s2, delim2)
274+
| Const_string (s1, _), Const_string (s2, _) -> String.compare s1 s2
276275
| _, _ -> compare x y
277276

278277
let records_args l1 l2 =

0 commit comments

Comments
 (0)