Skip to content

Commit 96fdd85

Browse files
authored
Fix issue with pattern match escape. (#6062)
* Fix issue with pattern match escape. Fixes #6060 The compiler's ppx was missing the same processing on patterns it performs on expressions in string constants: handles "js" string transformation. (On expressions it also handles "j" transformations). Remove compiler error when it encounters pattern matching with processed string. Changed compiler compilation to assume the cases in string switch are processed string. There's still the overarching questions of whether the transformation should be performed in the parser instead. * Remove unused file.
1 parent a8e7876 commit 96fdd85

19 files changed

+268
-243
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#### :bug: Bug Fix
1616
- Fix implementation of directives https://github.com/rescript-lang/rescript-compiler/pull/6052
1717
- Fix issue if the `lib` dir is included in the sources of bsconfig.json https://github.com/rescript-lang/rescript-compiler/pull/6055
18+
- Fix issue with string escape in pattern match https://github.com/rescript-lang/rescript-compiler/pull/6062
1819

1920
#### :rocket: New Feature
2021
- Add support for toplevel `await` https://github.com/rescript-lang/rescript-compiler/pull/6054

jscomp/core/js_dump.ml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,8 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
11971197
let cxt = P.paren_group f 1 (fun _ -> expression ~level:0 cxt f e) in
11981198
P.space f;
11991199
P.brace_vgroup f 1 (fun _ ->
1200-
let cxt = loop_case_clauses cxt f Js_dump_string.pp_string cc in
1200+
let pp_string f txt = ignore @@ expression_desc cxt ~level:0 f (Str {txt; delim=DStarJ}) in
1201+
let cxt = loop_case_clauses cxt f pp_string cc in
12011202
match def with
12021203
| None -> cxt
12031204
| Some def ->

jscomp/core/lam_compile.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ and compile_switch (switch_arg : Lam.t) (sw : Lam.lambda_switch)
651651
and compile_string_cases cxt switch_exp table default =
652652
compile_general_cases
653653
(fun _ -> None)
654-
E.str E.string_equal cxt
654+
(fun str -> E.str str ~delim:DStarJ) E.string_equal cxt
655655
(fun ?default ?declaration e clauses ->
656656
S.string_switch ?default ?declaration e clauses)
657657
switch_exp table default

jscomp/frontend/ast_utf8_string_interp.ml

+23-7
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,14 @@ module Delim = struct
330330
| Some "*j" -> Some DStarJ
331331
| _ -> None
332332

333+
type interpolation =
334+
| Js (* string interpolation *)
335+
| J (* old unsafe interpolation *)
336+
| Unrecognized (* no interpolation: delimiter not recognized *)
333337
let parse_unprocessed = function
334-
| "js" -> `string_interpolation
335-
| "j" -> `old_unsafe_interpolation
336-
| _ -> `no_interpolation
338+
| "js" -> Js
339+
| "j" -> J
340+
| _ -> Unrecognized
337341

338342
let escaped_j_delimiter = "*j" (* not user level syntax allowed *)
339343
let unescaped_j_delimiter = "j"
@@ -397,16 +401,28 @@ let transform_interp loc s =
397401
with Error (start, pos, error) ->
398402
Location.raise_errorf ~loc:(update border start pos loc) "%a" pp_error error
399403

400-
let transform (e : Parsetree.expression) s delim : Parsetree.expression =
404+
let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression =
401405
match Delim.parse_unprocessed delim with
402-
| `string_interpolation ->
406+
| Js ->
403407
let js_str = Ast_utf8_string.transform e.pexp_loc s in
404408
{
405409
e with
406410
pexp_desc = Pexp_constant (Pconst_string (js_str, Delim.escaped));
407411
}
408-
| `old_unsafe_interpolation -> transform_interp e.pexp_loc s
409-
| `no_interpolation -> e
412+
| J -> transform_interp e.pexp_loc s
413+
| Unrecognized -> e
414+
415+
416+
let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern =
417+
match Delim.parse_unprocessed delim with
418+
| Js ->
419+
let js_str = Ast_utf8_string.transform p.ppat_loc s in
420+
{
421+
p with
422+
ppat_desc = Ppat_constant (Pconst_string (js_str, Delim.escaped));
423+
}
424+
| J (* No j interpolation on patterns *)
425+
| Unrecognized -> p
410426

411427
let is_unicode_string opt = Ext_string.equal opt Delim.escaped_j_delimiter
412428

jscomp/frontend/ast_utf8_string_interp.mli

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ type exn += Error of pos * pos * error
5555

5656
val empty_segment : segment -> bool
5757
val transform_test : string -> segment list
58-
val transform : Parsetree.expression -> string -> string -> Parsetree.expression
58+
val transform_exp : Parsetree.expression -> string -> string -> Parsetree.expression
59+
val transform_pat : Parsetree.pattern -> string -> string -> Parsetree.pattern
5960
val is_unicode_string : string -> bool
6061
val is_unescaped : string -> bool
6162
val parse_processed_delim : string option -> J.delim option

jscomp/frontend/bs_ast_invariant.ml

+7-14
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,12 @@ type iterator = Ast_iterator.iterator
7070

7171
let super = Ast_iterator.default_iterator
7272

73-
let check_constant loc kind (const : Parsetree.constant) =
73+
let check_constant loc (const : Parsetree.constant) =
7474
match const with
75-
| Pconst_string (_, Some s) -> (
76-
match kind with
77-
| `expr ->
78-
if Ast_utf8_string_interp.is_unescaped s then
79-
Bs_warnings.error_unescaped_delimiter loc s
80-
| `pat ->
81-
if Ast_utf8_string_interp.parse_processed_delim (Some s) = Some DStarJ
82-
then
83-
Location.raise_errorf ~loc
84-
"Unicode string is not allowed in pattern match")
85-
| Pconst_integer (s, None) -> (
75+
| Pconst_string (_, Some s) ->
76+
if Ast_utf8_string_interp.is_unescaped s then
77+
Bs_warnings.error_unescaped_delimiter loc s
78+
| Pconst_integer (s, None) -> (
8679
(* range check using int32
8780
It is better to give a warning instead of error to avoid make people unhappy.
8881
It also has restrictions in which platform bsc is running on since it will
@@ -126,7 +119,7 @@ let emit_external_warnings : iterator =
126119
expr =
127120
(fun self ({ pexp_loc = loc } as a) ->
128121
match a.pexp_desc with
129-
| Pexp_constant const -> check_constant loc `expr const
122+
| Pexp_constant const -> check_constant loc const
130123
| Pexp_object _ | Pexp_new _ ->
131124
Location.raise_errorf ~loc "OCaml style objects are not supported"
132125
| Pexp_variant (s, None) when Ext_string.is_valid_hash_number s -> (
@@ -174,7 +167,7 @@ let emit_external_warnings : iterator =
174167
pat =
175168
(fun self (pat : Parsetree.pattern) ->
176169
match pat.ppat_desc with
177-
| Ppat_constant constant -> check_constant pat.ppat_loc `pat constant
170+
| Ppat_constant constant -> check_constant pat.ppat_loc constant
178171
| Ppat_record ([], _) ->
179172
Location.raise_errorf ~loc:pat.ppat_loc
180173
"Empty record pattern is not supported"

jscomp/frontend/bs_builtin_ppx.ml

+7-5
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ let default_mapper = Bs_ast_mapper.default_mapper
6464
let default_expr_mapper = Bs_ast_mapper.default_mapper.expr
6565
let default_pat_mapper = Bs_ast_mapper.default_mapper.pat
6666

67-
let pat_mapper (self : mapper) (e : Parsetree.pattern) =
68-
match e.ppat_desc with
67+
let pat_mapper (self : mapper) (p : Parsetree.pattern) =
68+
match p.ppat_desc with
6969
| Ppat_constant (Pconst_integer (s, Some 'l')) ->
70-
{ e with ppat_desc = Ppat_constant (Pconst_integer (s, None)) }
71-
| _ -> default_pat_mapper self e
70+
{ p with ppat_desc = Ppat_constant (Pconst_integer (s, None)) }
71+
| Ppat_constant (Pconst_string (s, Some delim)) ->
72+
Ast_utf8_string_interp.transform_pat p s delim
73+
| _ -> default_pat_mapper self p
7274

7375
let expr_mapper ~async_context ~in_function_def (self : mapper)
7476
(e : Parsetree.expression) =
@@ -98,7 +100,7 @@ let expr_mapper ~async_context ~in_function_def (self : mapper)
98100
(Nolabel, expr);
99101
])
100102
| Pexp_constant (Pconst_string (s, Some delim)) ->
101-
Ast_utf8_string_interp.transform e s delim
103+
Ast_utf8_string_interp.transform_exp e s delim
102104
| Pexp_constant (Pconst_integer (s, Some 'l')) ->
103105
{ e with pexp_desc = Pexp_constant (Pconst_integer (s, None)) }
104106
(* End rewriting *)

jscomp/main/native_ppx_main.ml

-87
This file was deleted.

jscomp/main/native_ppx_main.mli

Whitespace-only changes.

jscomp/test/build.ninja

+3-2
Large diffs are not rendered by default.

jscomp/test/switch_case_test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ function eq(loc, x, y) {
2929

3030
function f(x) {
3131
switch (x) {
32-
case "xx\"" :
33-
return 1;
3432
case "xx'''" :
3533
return 0;
34+
case "xx\"" :
35+
return 1;
3636
case "xx\\\"" :
3737
return 2;
3838
case "xx\\\"\"" :
@@ -42,13 +42,13 @@ function f(x) {
4242
}
4343
}
4444

45-
eq("File \"switch_case_test.ml\", line 19, characters 7-14", f("xx'''"), 0);
45+
eq("File \"switch_case_test.res\", line 19, characters 5-12", f("xx'''"), 0);
4646

47-
eq("File \"switch_case_test.ml\", line 20, characters 7-14", f("xx\""), 1);
47+
eq("File \"switch_case_test.res\", line 20, characters 5-12", f("xx\""), 1);
4848

49-
eq("File \"switch_case_test.ml\", line 21, characters 7-14", f("xx\\\""), 2);
49+
eq("File \"switch_case_test.res\", line 21, characters 5-12", f("xx\\\""), 2);
5050

51-
eq("File \"switch_case_test.ml\", line 22, characters 7-14", f("xx\\\"\""), 3);
51+
eq("File \"switch_case_test.res\", line 22, characters 5-12", f("xx\\\"\""), 3);
5252

5353
Mt.from_pair_suites("Switch_case_test", suites.contents);
5454

jscomp/test/switch_case_test.ml

-24
This file was deleted.

jscomp/test/switch_case_test.res

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
let suites: ref<Mt.pair_suites> = ref(list{})
2+
let test_id = ref(0)
3+
let eq = (loc, x, y) => {
4+
incr(test_id)
5+
suites :=
6+
list{(loc ++ (" id " ++ string_of_int(test_id.contents)), _ => Mt.Eq(x, y)), ...suites.contents}
7+
}
8+
9+
let f = x =>
10+
switch x {
11+
| "xx'''" => 0
12+
| "xx\"" => 1
13+
| `xx\\"` => 2
14+
| `xx\\""` => 3
15+
| _ => 4
16+
}
17+
18+
let () = {
19+
eq(__LOC__, f("xx'''"), 0)
20+
eq(__LOC__, f("xx\""), 1)
21+
eq(__LOC__, f(`xx\\"`), 2)
22+
eq(__LOC__, f(`xx\\""`), 3)
23+
}
24+
let () = Mt.from_pair_suites(__MODULE__, suites.contents)

jscomp/test/switch_string.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
4+
function foo(x) {
5+
if (x === "\"") {
6+
return "\"";
7+
} else {
8+
return "";
9+
}
10+
}
11+
12+
function bar(x) {
13+
switch (x) {
14+
case "\\" :
15+
return "\\";
16+
case "😀" :
17+
return "😀";
18+
default:
19+
return "";
20+
}
21+
}
22+
23+
var s = "😀";
24+
25+
exports.foo = foo;
26+
exports.s = s;
27+
exports.bar = bar;
28+
/* No side effect */

jscomp/test/switch_string.res

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
let foo = x =>
2+
switch x {
3+
| "\"" => "\""
4+
| _ => ""
5+
}
6+
7+
let s = "😀"
8+
9+
let bar = x => switch x {
10+
| "\\" => "\\"
11+
| "😀" => "😀"
12+
| _ => ""
13+
}

0 commit comments

Comments
 (0)