From 5c66d39559692876595aeb682322e06a65581362 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 07:04:23 +0200 Subject: [PATCH 01/10] Fix matching of equivalent string literals Signed-off-by: Christoph Knittel --- compiler/ml/parmatch.ml | 3 +- compiler/ml/string_literal.ml | 106 ++++++++++++++++++ compiler/ml/string_literal.mli | 10 ++ ...11_equivalent_string_patterns.res.expected | 11 ++ .../warning_11_equivalent_string_patterns.res | 6 + .../ounit_tests/ounit_string_literal_tests.ml | 65 +++++++++++ tests/ounit_tests/ounit_tests_main.ml | 1 + tests/tests/src/switch_case_test.mjs | 4 +- tests/tests/src/test_string_switch.mjs | 79 +++++++++++++ tests/tests/src/test_string_switch.res | 18 +++ 10 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 compiler/ml/string_literal.ml create mode 100644 compiler/ml/string_literal.mli create mode 100644 tests/build_tests/super_errors/expected/warning_11_equivalent_string_patterns.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/warning_11_equivalent_string_patterns.res create mode 100644 tests/ounit_tests/ounit_string_literal_tests.ml diff --git a/compiler/ml/parmatch.ml b/compiler/ml/parmatch.ml index a688e05e72f..74665a6b05c 100644 --- a/compiler/ml/parmatch.ml +++ b/compiler/ml/parmatch.ml @@ -269,7 +269,8 @@ let const_compare x y = compare (float_of_string f1) (float_of_string f2) | Const_bigint (s1, b1), Const_bigint (s2, b2) -> Bigint_utils.compare (s1, b1) (s2, b2) - | Const_string (s1, _), Const_string (s2, _) -> String.compare s1 s2 + | Const_string (s1, delim1), Const_string (s2, delim2) -> + String_literal.compare (s1, delim1) (s2, delim2) | _, _ -> compare x y let records_args l1 l2 = diff --git a/compiler/ml/string_literal.ml b/compiler/ml/string_literal.ml new file mode 100644 index 00000000000..3842a225b6d --- /dev/null +++ b/compiler/ml/string_literal.ml @@ -0,0 +1,106 @@ +let hex_value = function + | '0' .. '9' as c -> Char.code c - Char.code '0' + | 'a' .. 'f' as c -> Char.code c - Char.code 'a' + 10 + | 'A' .. 'F' as c -> Char.code c - Char.code 'A' + 10 + | _ -> -1 + +let decode_js_escapes s = + let len = String.length s in + let buf = Buffer.create len in + let add_codepoint codepoint = + if codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff) then + false + else ( + Buffer.add_string buf (Ext_utf8.encode_codepoint codepoint); + true) + in + let decode_fixed_hex start count = + let rec loop index remaining value = + if remaining = 0 then Some value + else if index >= len then None + else + let digit = hex_value s.[index] in + if digit < 0 then None + else loop (index + 1) (remaining - 1) ((value * 16) + digit) + in + loop start count 0 + in + let decode_braced_hex start = + let rec loop index value has_digit = + if index >= len then None + else + match s.[index] with + | '}' when has_digit -> Some (value, index + 1) + | c -> + let digit = hex_value c in + if digit < 0 || value > (0x10ffff - digit) / 16 then None + else loop (index + 1) ((value * 16) + digit) true + in + loop start 0 false + in + let rec loop index = + if index = len then Some (Buffer.contents buf) + else + match s.[index] with + | '\\' when index + 1 >= len -> None + | '\\' -> ( + match s.[index + 1] with + | 'b' -> + Buffer.add_char buf '\b'; + loop (index + 2) + | 'f' -> + Buffer.add_char buf '\012'; + loop (index + 2) + | 'n' -> + Buffer.add_char buf '\n'; + loop (index + 2) + | 'r' -> + Buffer.add_char buf '\r'; + loop (index + 2) + | 't' -> + Buffer.add_char buf '\t'; + loop (index + 2) + | 'v' -> + Buffer.add_char buf '\011'; + loop (index + 2) + | '0' -> + Buffer.add_char buf '\000'; + loop (index + 2) + | '\n' -> loop (index + 2) + | '\r' -> + if index + 2 < len && s.[index + 2] = '\n' then loop (index + 3) + else loop (index + 2) + | 'x' -> ( + match decode_fixed_hex (index + 2) 2 with + | Some codepoint when add_codepoint codepoint -> loop (index + 4) + | Some _ | None -> None) + | 'u' when index + 2 < len && s.[index + 2] = '{' -> ( + match decode_braced_hex (index + 3) with + | Some (codepoint, next) when add_codepoint codepoint -> loop next + | Some _ | None -> None) + | 'u' -> ( + match decode_fixed_hex (index + 2) 4 with + | Some codepoint when add_codepoint codepoint -> loop (index + 6) + | Some _ | None -> None) + | c -> + (* JavaScript non-escape characters, such as [\a], evaluate to the + character following the backslash. This also handles escaped + quotes, backslashes, dollars, backticks, and spaces. *) + Buffer.add_char buf c; + loop (index + 2)) + | c -> + Buffer.add_char buf c; + loop (index + 1) + in + loop 0 + +let runtime_value s delim = + match delim with + | Some ("*j" | "bq") -> ( + match decode_js_escapes s with + | Some decoded -> decoded + | None -> s) + | None | Some _ -> s + +let compare (s1, delim1) (s2, delim2) = + String.compare (runtime_value s1 delim1) (runtime_value s2 delim2) diff --git a/compiler/ml/string_literal.mli b/compiler/ml/string_literal.mli new file mode 100644 index 00000000000..43ee0f078c1 --- /dev/null +++ b/compiler/ml/string_literal.mli @@ -0,0 +1,10 @@ +val runtime_value : string -> string option -> string +(** Return the runtime value represented by a typed string constant. + + Ordinary quoted literals and backquoted literals still contain JavaScript + escape sequences at this point in the pipeline. Other constants already + contain their semantic value. *) + +val compare : string * string option -> string * string option -> int +(** Compare typed string constants by runtime value rather than source + encoding. *) diff --git a/tests/build_tests/super_errors/expected/warning_11_equivalent_string_patterns.res.expected b/tests/build_tests/super_errors/expected/warning_11_equivalent_string_patterns.res.expected new file mode 100644 index 00000000000..54eedb5d7f4 --- /dev/null +++ b/tests/build_tests/super_errors/expected/warning_11_equivalent_string_patterns.res.expected @@ -0,0 +1,11 @@ + + Warning number 11 + /.../fixtures/warning_11_equivalent_string_patterns.res:4:5-10 + + 2 ┆ switch value { + 3 ┆ | "a" => 1 + 4 ┆ | "\x61" => 2 + 5 ┆ | _ => 3 + 6 ┆ } + + this match case is unused. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/warning_11_equivalent_string_patterns.res b/tests/build_tests/super_errors/fixtures/warning_11_equivalent_string_patterns.res new file mode 100644 index 00000000000..ff9150bb9ec --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/warning_11_equivalent_string_patterns.res @@ -0,0 +1,6 @@ +let classify = value => + switch value { + | "a" => 1 + | "\x61" => 2 + | _ => 3 + } diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml new file mode 100644 index 00000000000..f0759b38df1 --- /dev/null +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -0,0 +1,65 @@ +let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) + +let assert_runtime_value ?(delim = Some "*j") ~encoded ~expected () = + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected + (String_literal.runtime_value encoded delim) + +let assert_same_runtime_value left right = + OUnit.assert_equal 0 (String_literal.compare left right) + +let suites = + __FILE__ + >::: [ + ( "plain text" >:: fun _ -> + assert_runtime_value ~encoded:"plain" ~expected:"plain" () ); + ( "named escapes" >:: fun _ -> + assert_runtime_value ~encoded:{|\b\f\n\r\t\v\0|} + ~expected:"\b\012\n\r\t\011\000" () ); + ( "escaped punctuation and non-escapes" >:: fun _ -> + assert_runtime_value ~encoded:{|\\\"\'\ \$\`\a|} + ~expected:{|\"' $`a|} () ); + ( "hex escapes" >:: fun _ -> + assert_runtime_value ~encoded:{|\x61\xE9|} ~expected:"aé" () ); + ( "unicode escapes" >:: fun _ -> + assert_runtime_value ~encoded:{|\u0061\u20AC|} ~expected:"a€" (); + assert_runtime_value ~encoded:{|\u{1f600}|} ~expected:"😀" () ); + ( "line continuations" >:: fun _ -> + assert_runtime_value ~encoded:"a\\\nb" ~expected:"ab" (); + assert_runtime_value ~encoded:"a\\\rb" ~expected:"ab" (); + assert_runtime_value ~encoded:"a\\\r\nb" ~expected:"ab" () ); + ( "processed literal delimiters" >:: fun _ -> + assert_runtime_value ~delim:(Some "*j") ~encoded:{|\x61|} + ~expected:"a" (); + assert_runtime_value ~delim:(Some "bq") ~encoded:{|\x61|} + ~expected:"a" () ); + ( "semantic and unprocessed literals remain unchanged" >:: fun _ -> + assert_runtime_value ~delim:None ~encoded:{|\x61|} ~expected:{|\x61|} + (); + assert_runtime_value ~delim:(Some "json") ~encoded:{|\x61|} + ~expected:{|\x61|} (); + assert_runtime_value ~delim:(Some "unknown") ~encoded:{|\x61|} + ~expected:{|\x61|} () ); + ( "invalid encoded values remain unchanged" >:: fun _ -> + List.iter + (fun encoded -> assert_runtime_value ~encoded ~expected:encoded ()) + [ + {|trailing\|}; + {|\x6|}; + {|\xGG|}; + {|\u061|}; + {|\u{}|}; + {|\u{110000}|}; + {|\uD800|}; + ] ); + ( "comparison uses runtime values" >:: fun _ -> + assert_same_runtime_value ("a", Some "*j") ({|\x61|}, Some "*j"); + assert_same_runtime_value ("😀", None) ({|\u{1f600}|}, Some "*j"); + assert_same_runtime_value ("a\nb", Some "*j") ({|a\x0ab|}, Some "*j"); + OUnit.assert_bool "comparison should use decoded ordering" + (String_literal.compare ({|\x62|}, Some "*j") ("a", Some "*j") > 0) + ); + ( "semantic backslashes remain distinct" >:: fun _ -> + OUnit.assert_bool "semantic backslash must remain distinct" + (String_literal.compare ({|\x61|}, None) ({|\x61|}, Some "*j") <> 0) + ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index ebe2df98590..9c78e5066f7 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -12,6 +12,7 @@ let suites = Ounit_map_tests.suites; Ounit_hashtbl_tests.suites; Ounit_string_tests.suites; + Ounit_string_literal_tests.suites; Ounit_int_vec_tests.suites; Ounit_ident_mask_tests.suites; Ounit_lid_of_path_tests.suites; diff --git a/tests/tests/src/switch_case_test.mjs b/tests/tests/src/switch_case_test.mjs index a704bdbf603..9dcf8695e67 100644 --- a/tests/tests/src/switch_case_test.mjs +++ b/tests/tests/src/switch_case_test.mjs @@ -5,10 +5,10 @@ import * as Test_utils from "./test_utils.mjs"; function f(x) { switch (x) { - case "xx'''" : - return 0; case "xx\"" : return 1; + case "xx'''" : + return 0; case "xx\\\"" : return 2; case "xx\\\"\"" : diff --git a/tests/tests/src/test_string_switch.mjs b/tests/tests/src/test_string_switch.mjs index edad912e6b3..fcc2b515f79 100644 --- a/tests/tests/src/test_string_switch.mjs +++ b/tests/tests/src/test_string_switch.mjs @@ -17,7 +17,86 @@ switch (match) { version = 3; } +function classifyEquivalentEscape(value, selectedCase) { + if (value === "\x61") { + if (selectedCase === 0) { + return 0; + } else if (selectedCase === 1) { + return 1; + } else if (selectedCase === 2) { + return 2; + } else if (selectedCase === 3) { + return 3; + } else { + return 4; + } + } else { + return 5; + } +} + +if (classifyEquivalentEscape("a", 0) !== 0) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 21, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentEscape("a", 1) !== 1) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 22, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentEscape("a", 2) !== 2) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 23, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentEscape("a", 3) !== 3) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 24, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentEscape("a", 4) !== 4) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 25, + 2 + ], + Error: new Error() + }; +} + export { version, + classifyEquivalentEscape, } /* match Not a pure module */ diff --git a/tests/tests/src/test_string_switch.res b/tests/tests/src/test_string_switch.res index 596d12e5072..e577fc9892f 100644 --- a/tests/tests/src/test_string_switch.res +++ b/tests/tests/src/test_string_switch.res @@ -6,3 +6,21 @@ let version = switch platform() { | "darwin" => 2 | _ => 3 } + +let classifyEquivalentEscape = (value, selectedCase) => + switch value { + | "a" if selectedCase == 0 => 0 + | "\x61" if selectedCase == 1 => 1 + | "\u0061" if selectedCase == 2 => 2 + | "\u{61}" if selectedCase == 3 => 3 + | "\x61" => 4 + | _ => 5 + } + +let () = { + assert(classifyEquivalentEscape("a", 0) == 0) + assert(classifyEquivalentEscape("a", 1) == 1) + assert(classifyEquivalentEscape("a", 2) == 2) + assert(classifyEquivalentEscape("a", 3) == 3) + assert(classifyEquivalentEscape("a", 4) == 4) +} From 7ea5df898a0c8776d423d5a255082bdec0d5f508 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 07:04:58 +0200 Subject: [PATCH 02/10] Add changelog entry for #8603 Signed-off-by: Christoph Knittel --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d819e75070a..780571b848e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ #### :bug: Bug fix - Object typing errors now describe fields directly: assigning to a field without `@set` reports that the field is not settable and suggests the annotation, and missing-property errors name the field instead of a phantom `"x#="` member. https://github.com/rescript-lang/rescript/pull/8597 +- Fix pattern matching for string literals with equivalent runtime values but different escape spellings, preserving source order and reporting redundant patterns. https://github.com/rescript-lang/rescript/pull/8603 - Fix signature inclusion rejecting equivalent object externals after type-alias expansion. https://github.com/rescript-lang/rescript/pull/8581 - Fix externals whose result type is an alias of `unit` so they use the same unit-return behavior as externals declared to return `unit`. https://github.com/rescript-lang/rescript/pull/8581 - Fix dynamic imports of external bindings that require FFI argument or result conversions, including `@variadic`, `@unwrap`, polymorphic variant encodings, `@as` phantom arguments, optional labeled arguments, and `@return` wrappers. The imported value now applies the same conversions as a direct external call. https://github.com/rescript-lang/rescript/pull/8582 From e45efcd0d7284b35d1d8a2e4a21771a94b7d71fb Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 07:44:20 +0200 Subject: [PATCH 03/10] Support surrogate-pair string escapes Signed-off-by: Christoph Knittel --- compiler/ml/string_literal.ml | 15 +++++ compiler/syntax/src/res_scanner.ml | 29 ++++++--- .../ounit_tests/ounit_string_literal_tests.ml | 9 ++- tests/tests/src/test_string_switch.mjs | 61 +++++++++++++++++-- tests/tests/src/test_string_switch.res | 11 ++++ 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/compiler/ml/string_literal.ml b/compiler/ml/string_literal.ml index 3842a225b6d..1b305a1b2bf 100644 --- a/compiler/ml/string_literal.ml +++ b/compiler/ml/string_literal.ml @@ -4,6 +4,12 @@ let hex_value = function | 'A' .. 'F' as c -> Char.code c - Char.code 'A' + 10 | _ -> -1 +let is_high_surrogate codepoint = codepoint >= 0xd800 && codepoint <= 0xdbff +let is_low_surrogate codepoint = codepoint >= 0xdc00 && codepoint <= 0xdfff + +let combine_surrogate_pair high low = + 0x10000 + ((high - 0xd800) lsl 10) + (low - 0xdc00) + let decode_js_escapes s = let len = String.length s in let buf = Buffer.create len in @@ -80,6 +86,15 @@ let decode_js_escapes s = | Some _ | None -> None) | 'u' -> ( match decode_fixed_hex (index + 2) 4 with + | Some high when is_high_surrogate high -> + if index + 7 < len && s.[index + 6] = '\\' && s.[index + 7] = 'u' + then + match decode_fixed_hex (index + 8) 4 with + | Some low when is_low_surrogate low -> + let codepoint = combine_surrogate_pair high low in + if add_codepoint codepoint then loop (index + 12) else None + | Some _ | None -> None + else None | Some codepoint when add_codepoint codepoint -> loop (index + 6) | Some _ | None -> None) | c -> diff --git a/compiler/syntax/src/res_scanner.ml b/compiler/syntax/src/res_scanner.ml index 67be55fdc5d..4a58812315b 100644 --- a/compiler/syntax/src/res_scanner.ml +++ b/compiler/syntax/src/res_scanner.ml @@ -346,7 +346,12 @@ let scan_exotic_identifier scanner = else Token.Lident name let scan_string_escape_sequence ~start_pos scanner = - let scan ~n ~base ~max = + let invalid_unicode_code_point () = + let pos = position scanner in + let msg = "escape sequence is invalid unicode code point" in + scanner.err ~start_pos ~end_pos:pos (Diagnostics.message msg) + in + let scan_digits ~n ~base = let rec loop n x = if n == 0 then x else @@ -363,11 +368,11 @@ let scan_string_escape_sequence ~start_pos scanner = let () = next scanner in loop (n - 1) ((x * base) + d) in - let x = loop n 0 in - if x > max || (0xD800 <= x && x < 0xE000) then - let pos = position scanner in - let msg = "escape sequence is invalid unicode code point" in - scanner.err ~start_pos ~end_pos:pos (Diagnostics.message msg) + loop n 0 + in + let scan ~n ~base ~max = + let x = scan_digits ~n ~base in + if x > max || (0xD800 <= x && x < 0xE000) then invalid_unicode_code_point () in match scanner.ch with (* \ already consumed *) @@ -401,7 +406,17 @@ let scan_string_escape_sequence ~start_pos scanner = match scanner.ch with | '}' -> next scanner | _ -> ()) - | _ -> scan ~n:4 ~base:16 ~max:Res_utf8.max) + | _ -> + let high = scan_digits ~n:4 ~base:16 in + if 0xD800 <= high && high <= 0xDBFF then + if scanner.ch = '\\' && peek scanner = 'u' then ( + next scanner; + next scanner; + let low = scan_digits ~n:4 ~base:16 in + if low < 0xDC00 || low > 0xDFFF then invalid_unicode_code_point ()) + else invalid_unicode_code_point () + else if high > Res_utf8.max || (0xDC00 <= high && high <= 0xDFFF) then + invalid_unicode_code_point ()) | _ -> (* unknown escape sequence * TODO: we should warn the user here. Let's not make it a hard error for now, for reason compat *) diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index f0759b38df1..a8fa96b10d7 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -22,7 +22,8 @@ let suites = assert_runtime_value ~encoded:{|\x61\xE9|} ~expected:"aé" () ); ( "unicode escapes" >:: fun _ -> assert_runtime_value ~encoded:{|\u0061\u20AC|} ~expected:"a€" (); - assert_runtime_value ~encoded:{|\u{1f600}|} ~expected:"😀" () ); + assert_runtime_value ~encoded:{|\u{1f600}|} ~expected:"😀" (); + assert_runtime_value ~encoded:{|\uD83D\uDE00|} ~expected:"😀" () ); ( "line continuations" >:: fun _ -> assert_runtime_value ~encoded:"a\\\nb" ~expected:"ab" (); assert_runtime_value ~encoded:"a\\\rb" ~expected:"ab" (); @@ -50,10 +51,16 @@ let suites = {|\u{}|}; {|\u{110000}|}; {|\uD800|}; + {|\uDC00|}; + {|\uD800\u0041|}; + {|\uDC00\uD800|}; ] ); ( "comparison uses runtime values" >:: fun _ -> assert_same_runtime_value ("a", Some "*j") ({|\x61|}, Some "*j"); assert_same_runtime_value ("😀", None) ({|\u{1f600}|}, Some "*j"); + assert_same_runtime_value + ({|\uD83D\uDE00|}, Some "*j") + ({|\u{1f600}|}, Some "*j"); assert_same_runtime_value ("a\nb", Some "*j") ({|a\x0ab|}, Some "*j"); OUnit.assert_bool "comparison should use decoded ordering" (String_literal.compare ({|\x62|}, Some "*j") ("a", Some "*j") > 0) diff --git a/tests/tests/src/test_string_switch.mjs b/tests/tests/src/test_string_switch.mjs index fcc2b515f79..0c6349a97e0 100644 --- a/tests/tests/src/test_string_switch.mjs +++ b/tests/tests/src/test_string_switch.mjs @@ -35,12 +35,26 @@ function classifyEquivalentEscape(value, selectedCase) { } } +function classifyEquivalentSurrogateEscape(value, selectedCase) { + if (value === "\u{1f600}") { + if (selectedCase === 0) { + return 0; + } else if (selectedCase === 1) { + return 1; + } else { + return 2; + } + } else { + return 3; + } +} + if (classifyEquivalentEscape("a", 0) !== 0) { throw { RE_EXN_ID: "Assert_failure", _1: [ "test_string_switch.res", - 21, + 29, 2 ], Error: new Error() @@ -52,7 +66,7 @@ if (classifyEquivalentEscape("a", 1) !== 1) { RE_EXN_ID: "Assert_failure", _1: [ "test_string_switch.res", - 22, + 30, 2 ], Error: new Error() @@ -64,7 +78,7 @@ if (classifyEquivalentEscape("a", 2) !== 2) { RE_EXN_ID: "Assert_failure", _1: [ "test_string_switch.res", - 23, + 31, 2 ], Error: new Error() @@ -76,7 +90,7 @@ if (classifyEquivalentEscape("a", 3) !== 3) { RE_EXN_ID: "Assert_failure", _1: [ "test_string_switch.res", - 24, + 32, 2 ], Error: new Error() @@ -88,7 +102,43 @@ if (classifyEquivalentEscape("a", 4) !== 4) { RE_EXN_ID: "Assert_failure", _1: [ "test_string_switch.res", - 25, + 33, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentSurrogateEscape("😀", 0) !== 0) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 34, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentSurrogateEscape("😀", 1) !== 1) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 35, + 2 + ], + Error: new Error() + }; +} + +if (classifyEquivalentSurrogateEscape("😀", 2) !== 2) { + throw { + RE_EXN_ID: "Assert_failure", + _1: [ + "test_string_switch.res", + 36, 2 ], Error: new Error() @@ -98,5 +148,6 @@ if (classifyEquivalentEscape("a", 4) !== 4) { export { version, classifyEquivalentEscape, + classifyEquivalentSurrogateEscape, } /* match Not a pure module */ diff --git a/tests/tests/src/test_string_switch.res b/tests/tests/src/test_string_switch.res index e577fc9892f..6fb02cba333 100644 --- a/tests/tests/src/test_string_switch.res +++ b/tests/tests/src/test_string_switch.res @@ -17,10 +17,21 @@ let classifyEquivalentEscape = (value, selectedCase) => | _ => 5 } +let classifyEquivalentSurrogateEscape = (value, selectedCase) => + switch value { + | "😀" if selectedCase == 0 => 0 + | "\uD83D\uDE00" if selectedCase == 1 => 1 + | "\u{1f600}" => 2 + | _ => 3 + } + let () = { assert(classifyEquivalentEscape("a", 0) == 0) assert(classifyEquivalentEscape("a", 1) == 1) assert(classifyEquivalentEscape("a", 2) == 2) assert(classifyEquivalentEscape("a", 3) == 3) assert(classifyEquivalentEscape("a", 4) == 4) + assert(classifyEquivalentSurrogateEscape("😀", 0) == 0) + assert(classifyEquivalentSurrogateEscape("😀", 1) == 1) + assert(classifyEquivalentSurrogateEscape("😀", 2) == 2) } From 558b6c11645a0cf8418aeb28dd7326dd670c4166 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 08:27:47 +0200 Subject: [PATCH 04/10] Reject lone surrogates in string patterns Signed-off-by: Christoph Knittel --- compiler/frontend/ast_utf8_string_interp.ml | 4 ++++ compiler/ml/string_literal.mli | 5 +++++ tests/ounit_tests/ounit_string_literal_tests.ml | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/compiler/frontend/ast_utf8_string_interp.ml b/compiler/frontend/ast_utf8_string_interp.ml index 54671716407..843df100fe7 100644 --- a/compiler/frontend/ast_utf8_string_interp.ml +++ b/compiler/frontend/ast_utf8_string_interp.ml @@ -312,6 +312,10 @@ let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern = match Delim.parse_unprocessed false delim with | Js -> let js_str = Ast_utf8_string.transform p.ppat_loc s in + (match String_literal.decode_js_escapes js_str with + | Some _ -> () + | None -> + Location.raise_errorf ~loc:p.ppat_loc "Invalid string escape sequence"); { p with ppat_desc = diff --git a/compiler/ml/string_literal.mli b/compiler/ml/string_literal.mli index 43ee0f078c1..3617974587e 100644 --- a/compiler/ml/string_literal.mli +++ b/compiler/ml/string_literal.mli @@ -1,3 +1,8 @@ +val decode_js_escapes : string -> string option +(** Decode the escape sequences in a JavaScript string-literal body into its + semantic UTF-8 value. Returns [None] for malformed input or unpaired + UTF-16 surrogates. *) + val runtime_value : string -> string option -> string (** Return the runtime value represented by a typed string constant. diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index a8fa96b10d7..51807a1f8ff 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -7,6 +7,18 @@ let assert_runtime_value ?(delim = Some "*j") ~encoded ~expected () = let assert_same_runtime_value left right = OUnit.assert_equal 0 (String_literal.compare left right) +let assert_invalid_backquoted_pattern encoded = + let template_attribute = + (Location.mknoloc "res.template", Parsetree.PStr []) + in + let pattern = + Ast_helper.Pat.constant ~attrs:[template_attribute] + (Parsetree.Pconst_string (encoded, Some "js")) + in + match Ast_utf8_string_interp.transform_pat pattern encoded "js" with + | _ -> OUnit.assert_failure "expected an invalid string escape" + | exception Location.Error _ -> () + let suites = __FILE__ >::: [ @@ -55,6 +67,9 @@ let suites = {|\uD800\u0041|}; {|\uDC00\uD800|}; ] ); + ( "backquoted patterns reject lone surrogate escapes" >:: fun _ -> + assert_invalid_backquoted_pattern {|\uD800|}; + assert_invalid_backquoted_pattern {|\uDC00|} ); ( "comparison uses runtime values" >:: fun _ -> assert_same_runtime_value ("a", Some "*j") ({|\x61|}, Some "*j"); assert_same_runtime_value ("😀", None) ({|\u{1f600}|}, Some "*j"); From a8794b0fd76136f25e5b19aa273714cef4e5dbec Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 09:41:00 +0200 Subject: [PATCH 05/10] Reject tagged template literals in patterns Signed-off-by: Christoph Knittel --- compiler/frontend/ast_utf8_string_interp.ml | 4 +++- tests/ounit_tests/ounit_string_literal_tests.ml | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/compiler/frontend/ast_utf8_string_interp.ml b/compiler/frontend/ast_utf8_string_interp.ml index 843df100fe7..517c340f06a 100644 --- a/compiler/frontend/ast_utf8_string_interp.ml +++ b/compiler/frontend/ast_utf8_string_interp.ml @@ -328,6 +328,8 @@ let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern = Ppat_constant (Pconst_string (s, Delim.some_escaped_back_quote_delimiter)); } - | Unrecognized -> p + | Unrecognized -> + Location.raise_errorf ~loc:p.ppat_loc + "Tagged template literals are not supported in patterns" let parse_processed_delim = External_arg_spec.parse_processed_delim diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index 51807a1f8ff..d562f25d076 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -19,6 +19,14 @@ let assert_invalid_backquoted_pattern encoded = | _ -> OUnit.assert_failure "expected an invalid string escape" | exception Location.Error _ -> () +let assert_invalid_tagged_pattern tag contents = + let pattern = + Ast_helper.Pat.constant (Parsetree.Pconst_string (contents, Some tag)) + in + match Ast_utf8_string_interp.transform_pat pattern contents tag with + | _ -> OUnit.assert_failure "expected a tagged pattern error" + | exception Location.Error _ -> () + let suites = __FILE__ >::: [ @@ -70,6 +78,11 @@ let suites = ( "backquoted patterns reject lone surrogate escapes" >:: fun _ -> assert_invalid_backquoted_pattern {|\uD800|}; assert_invalid_backquoted_pattern {|\uDC00|} ); + ( "patterns reject tagged template literals" >:: fun _ -> + (* A tagged pattern cannot invoke its tag. Treating its raw contents as + a string made json`\x61` collide with the ordinary "\\x61" + pattern during string-switch sorting. *) + assert_invalid_tagged_pattern "json" {|\x61|} ); ( "comparison uses runtime values" >:: fun _ -> assert_same_runtime_value ("a", Some "*j") ({|\x61|}, Some "*j"); assert_same_runtime_value ("😀", None) ({|\u{1f600}|}, Some "*j"); From a3558b3c26644b354005b8d5b3a834cef4f49d67 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 14:55:14 +0200 Subject: [PATCH 06/10] Normalize string literals before matching Signed-off-by: Christoph Knittel --- CHANGELOG.md | 2 + compiler/core/j.ml | 2 +- compiler/core/js_dump.ml | 1 - compiler/core/js_exp_make.ml | 2 +- compiler/frontend/ast_utf8_string_interp.ml | 30 +- compiler/ml/ast_mapper_from0.ml | 27 +- compiler/ml/ast_mapper_to0.ml | 3 + compiler/ml/external_arg_spec.ml | 3 +- compiler/ml/external_arg_spec.mli | 2 +- compiler/ml/parmatch.ml | 3 +- compiler/ml/string_literal.ml | 11 - compiler/ml/string_literal.mli | 11 - compiler/syntax/src/res_outcome_printer.ml | 4 +- compiler/syntax/src/res_printer.ml | 7 + compiler/syntax/src/res_scanner.ml | 3 +- tests/ERROR_VARIANTS.md | 3 +- .../tests/src/expected/DocComments.res.txt | 4 +- .../tagged_template_pattern.res.expected | 11 + .../fixtures/tagged_template_pattern.res | 5 + tests/ounit_tests/ounit_ast_mapper0_tests.ml | 79 + .../ounit_lambda_constant_tests.ml | 1 - .../ounit_tests/ounit_string_literal_tests.ml | 118 +- .../expected/invalidSurrogatePair.res.txt | 12 + .../errors/scanner/invalidSurrogatePair.res | 2 + tests/tests/src/big_polyvar_test.mjs | 3600 ----------------- tests/tests/src/external_ppx2.mjs | 4 +- tests/tests/src/string_constant_compare.mjs | 4 +- .../src/string_literal_normalization_test.mjs | 35 + .../src/string_literal_normalization_test.res | 32 + tests/tests/src/stringmatch_test.mjs | 8 +- tests/tests/src/tagged_template_test.mjs | 2 +- tests/tests/src/test_string_switch.mjs | 4 +- 32 files changed, 318 insertions(+), 3717 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/tagged_template_pattern.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/tagged_template_pattern.res create mode 100644 tests/syntax_tests/data/parsing/errors/scanner/expected/invalidSurrogatePair.res.txt create mode 100644 tests/syntax_tests/data/parsing/errors/scanner/invalidSurrogatePair.res create mode 100644 tests/tests/src/string_literal_normalization_test.mjs create mode 100644 tests/tests/src/string_literal_normalization_test.res diff --git a/CHANGELOG.md b/CHANGELOG.md index 780571b848e..6e47c6ae925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ #### :boom: Breaking Change +- 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 - 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 - Remove the deprecated `Js` namespace and its runtime modules. https://github.com/rescript-lang/rescript/pull/8531 - 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 @@ #### :rocket: New Feature +- Support UTF-16 surrogate-pair escapes such as `"\uD83D\uDE00"` in ordinary string literals. https://github.com/rescript-lang/rescript/pull/8603 - 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 - Add `@res.hoistedFunction` for emitting nested module functions as flat JavaScript exports. https://github.com/rescript-lang/rescript/pull/8402 - Add source map support with linked, inline, and hidden modes. https://github.com/rescript-lang/rescript/pull/8393 diff --git a/compiler/core/j.ml b/compiler/core/j.ml index 2a8a039fd4c..dfabdd1d120 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -72,7 +72,7 @@ and exception_ident = ident and for_ident = ident and for_direction = Js_op.direction_flag and property_map = (property_name * expression) list -and delim = External_arg_spec.delim = DNone | DStarJ | DNoQuotes | DBackQuotes +and delim = External_arg_spec.delim = DNone | DNoQuotes | DBackQuotes and record_rest_field = { record_rest_label: string; diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 0584e3852b2..d9b3ee1fc3c 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -771,7 +771,6 @@ and expression_desc cxt ~(level : int) f x : cxt = *) let () = match delim with - | DStarJ -> P.string f ("\"" ^ txt ^ "\"") | DNoQuotes -> P.string f txt | DNone -> Js_dump_string.pp_string f txt | DBackQuotes -> P.string f ("`" ^ txt ^ "`") diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index a10df47a627..f724b7efa2e 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -1353,7 +1353,7 @@ let rec float_equal ?comment (e0 : t) (e1 : t) : t = let int_equal = float_equal let tag_type = function - | Variant_runtime.String s -> str s ~delim:DStarJ + | Variant_runtime.String s -> str s | Int i -> small_int i | Float f -> float f | BigInt i -> diff --git a/compiler/frontend/ast_utf8_string_interp.ml b/compiler/frontend/ast_utf8_string_interp.ml index 517c340f06a..fd2404258f7 100644 --- a/compiler/frontend/ast_utf8_string_interp.ml +++ b/compiler/frontend/ast_utf8_string_interp.ml @@ -279,11 +279,16 @@ module Delim = struct | "js" -> if is_template then BackQuotes else Js | _ -> Unrecognized - let escaped_j_delimiter = "*j" (* not user level syntax allowed *) let some_escaped_back_quote_delimiter = Some "bq" - let some_escaped_j_delimiter = Some escaped_j_delimiter end +(* Scanner string payloads still contain JavaScript escape spelling. Decode an + ordinary string exactly once here, before it reaches typing and matching. *) +let semantic_string loc s = + match String_literal.decode_js_escapes s with + | Some decoded -> decoded + | None -> Location.raise_errorf ~loc "Invalid string escape sequence" + let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression = let is_template = Ext_list.exists e.pexp_attributes (fun ({txt}, _) -> @@ -293,12 +298,8 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression = in match Delim.parse_unprocessed is_template delim with | Js -> - let js_str = Ast_utf8_string.transform e.pexp_loc s in - { - e with - pexp_desc = - Pexp_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter)); - } + let semantic = semantic_string e.pexp_loc s in + {e with pexp_desc = Pexp_constant (Pconst_string (semantic, None))} | BackQuotes -> { e with @@ -311,16 +312,8 @@ let transform_exp (e : Parsetree.expression) s delim : Parsetree.expression = let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern = match Delim.parse_unprocessed false delim with | Js -> - let js_str = Ast_utf8_string.transform p.ppat_loc s in - (match String_literal.decode_js_escapes js_str with - | Some _ -> () - | None -> - Location.raise_errorf ~loc:p.ppat_loc "Invalid string escape sequence"); - { - p with - ppat_desc = - Ppat_constant (Pconst_string (js_str, Delim.some_escaped_j_delimiter)); - } + let semantic = semantic_string p.ppat_loc s in + {p with ppat_desc = Ppat_constant (Pconst_string (semantic, None))} | BackQuotes -> { p with @@ -328,6 +321,7 @@ let transform_pat (p : Parsetree.pattern) s delim : Parsetree.pattern = Ppat_constant (Pconst_string (s, Delim.some_escaped_back_quote_delimiter)); } + | Unrecognized when delim = "INTERNAL_RES_CHAR_CONTENTS" -> p | Unrecognized -> Location.raise_errorf ~loc:p.ppat_loc "Tagged template literals are not supported in patterns" diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 60e5215922b..2534cad0b4b 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -82,9 +82,23 @@ let map_tuple3 f1 f2 f3 (x, y, z) = (f1 x, f2 y, f3 z) let map_opt f = function | None -> None | Some x -> Some (f x) -let map_constant = function +let is_template attrs = + Ext_list.exists attrs (fun ({txt}, _) -> + match txt with + | "res.template" | "res.taggedTemplate" -> true + | _ -> false) + +let decode_js_string ~loc s = + match String_literal.decode_js_escapes s with + | Some s -> s + | None -> Location.raise_errorf ~loc "Invalid string escape sequence" + +let map_constant ~loc ~is_template = function | Pconst_integer (s, suffix) -> Pt.Pconst_integer (s, suffix) | Pconst_char c -> Pconst_char c + | Pconst_string (s, Some "js") when is_template -> Pconst_string (s, Some "bq") + | Pconst_string (s, Some ("js" | "*j")) -> + Pconst_string (decode_js_string ~loc s, None) | Pconst_string (s, q) -> Pconst_string (s, q) | Pconst_float (s, suffix) -> Pconst_float (s, suffix) @@ -511,7 +525,9 @@ module E = struct let inner = sub.expr sub {e with pexp_attributes = inner_attrs0} in await ~loc ~attrs:(sub.attributes sub await_attrs0) inner | Pexp_ident x -> ident ~loc ~attrs (map_loc sub x) - | Pexp_constant x -> constant ~loc ~attrs (map_constant x) + | Pexp_constant x -> + constant ~loc ~attrs + (map_constant ~loc ~is_template:(is_template attrs) x) | Pexp_let (r, vbs, e) -> let_ ~loc ~attrs r (List.map (sub.value_binding sub) vbs) (sub.expr sub e) | Pexp_fun (lab, def, p, e) -> @@ -879,9 +895,12 @@ module P = struct | Ppat_any -> any ~loc ~attrs () | Ppat_var s -> var ~loc ~attrs (map_loc sub s) | Ppat_alias (p, s) -> alias ~loc ~attrs (sub.pat sub p) (map_loc sub s) - | Ppat_constant c -> constant ~loc ~attrs (map_constant c) + | Ppat_constant c -> + constant ~loc ~attrs (map_constant ~loc ~is_template:false c) | Ppat_interval (c1, c2) -> - interval ~loc ~attrs (map_constant c1) (map_constant c2) + interval ~loc ~attrs + (map_constant ~loc ~is_template:false c1) + (map_constant ~loc ~is_template:false c2) | Ppat_tuple pl -> tuple ~loc ~attrs (List.map (sub.pat sub) pl) | Ppat_construct (l, p) -> construct ~loc ~attrs (map_loc sub l) (map_opt (sub.pat sub) p) diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index 0e975bcfb74..593b777300e 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -79,6 +79,9 @@ let map_opt f = function let map_constant = function | Pconst_integer (s, suffix) -> Pt.Pconst_integer (s, suffix) | Pconst_char c -> Pconst_char c + (* The PPX bridge uses parser-form ast0, where template segments are [js] + strings distinguished by a template attribute. *) + | Pconst_string (s, Some "bq") -> Pconst_string (s, Some "js") | Pconst_string (s, q) -> Pconst_string (s, q) | Pconst_float (s, suffix) -> Pconst_float (s, suffix) diff --git a/compiler/ml/external_arg_spec.ml b/compiler/ml/external_arg_spec.ml index d7238532397..ec5f8419d49 100644 --- a/compiler/ml/external_arg_spec.ml +++ b/compiler/ml/external_arg_spec.ml @@ -24,12 +24,11 @@ (** type definitions for arguments to a function declared external *) -type delim = DNone | DStarJ | DNoQuotes | DBackQuotes +type delim = DNone | DNoQuotes | DBackQuotes let parse_processed_delim = function | None -> Some DNone | Some "json" -> Some DNoQuotes - | Some "*j" -> Some DStarJ | Some "bq" -> Some DBackQuotes | _ -> None diff --git a/compiler/ml/external_arg_spec.mli b/compiler/ml/external_arg_spec.mli index ac99e6dea2a..29929ca0f9a 100644 --- a/compiler/ml/external_arg_spec.mli +++ b/compiler/ml/external_arg_spec.mli @@ -22,7 +22,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -type delim = DNone | DStarJ | DNoQuotes | DBackQuotes +type delim = DNone | DNoQuotes | DBackQuotes val parse_processed_delim : string option -> delim option diff --git a/compiler/ml/parmatch.ml b/compiler/ml/parmatch.ml index 74665a6b05c..a688e05e72f 100644 --- a/compiler/ml/parmatch.ml +++ b/compiler/ml/parmatch.ml @@ -269,8 +269,7 @@ let const_compare x y = compare (float_of_string f1) (float_of_string f2) | Const_bigint (s1, b1), Const_bigint (s2, b2) -> Bigint_utils.compare (s1, b1) (s2, b2) - | Const_string (s1, delim1), Const_string (s2, delim2) -> - String_literal.compare (s1, delim1) (s2, delim2) + | Const_string (s1, _), Const_string (s2, _) -> String.compare s1 s2 | _, _ -> compare x y let records_args l1 l2 = diff --git a/compiler/ml/string_literal.ml b/compiler/ml/string_literal.ml index 1b305a1b2bf..83368f27e57 100644 --- a/compiler/ml/string_literal.ml +++ b/compiler/ml/string_literal.ml @@ -108,14 +108,3 @@ let decode_js_escapes s = loop (index + 1) in loop 0 - -let runtime_value s delim = - match delim with - | Some ("*j" | "bq") -> ( - match decode_js_escapes s with - | Some decoded -> decoded - | None -> s) - | None | Some _ -> s - -let compare (s1, delim1) (s2, delim2) = - String.compare (runtime_value s1 delim1) (runtime_value s2 delim2) diff --git a/compiler/ml/string_literal.mli b/compiler/ml/string_literal.mli index 3617974587e..d730adce519 100644 --- a/compiler/ml/string_literal.mli +++ b/compiler/ml/string_literal.mli @@ -2,14 +2,3 @@ val decode_js_escapes : string -> string option (** Decode the escape sequences in a JavaScript string-literal body into its semantic UTF-8 value. Returns [None] for malformed input or unpaired UTF-16 surrogates. *) - -val runtime_value : string -> string option -> string -(** Return the runtime value represented by a typed string constant. - - Ordinary quoted literals and backquoted literals still contain JavaScript - escape sequences at this point in the pipeline. Other constants already - contain their semantic value. *) - -val compare : string * string option -> string * string option -> int -(** Compare typed string constants by runtime value rather than source - encoding. *) diff --git a/compiler/syntax/src/res_outcome_printer.ml b/compiler/syntax/src/res_outcome_printer.ml index 2638dd84a78..b12b258f13e 100644 --- a/compiler/syntax/src/res_outcome_printer.ml +++ b/compiler/syntax/src/res_outcome_printer.ml @@ -484,9 +484,7 @@ let print_string_literal_doc s = Doc.text ("\"" ^ String.escaped s ^ "\"") let print_inline_const_doc (c : External_ffi_types.inline_const) = match c with - | Const_str {s; delim = None | Some DNone | Some DStarJ} -> - (* DStarJ is the processed form of an ordinary double-quoted string *) - print_string_literal_doc s + | Const_str {s; delim = None | Some DNone} -> print_string_literal_doc s | Const_str {s; delim = Some DBackQuotes} -> Doc.text ("`" ^ s ^ "`") | Const_str {s; delim = Some DNoQuotes} -> Doc.text ("json`" ^ s ^ "`") | Const_bool b -> Doc.text (if b then "true" else "false") diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 3b44878362e..5da2c97db1b 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -2619,6 +2619,13 @@ and print_pattern ~state (p : Parsetree.pattern) cmt_tbl = | Ppat_constant c -> let template_literal = Parsetree_viewer.has_template_literal_attr p.ppat_attributes + || + match c with + | Pconst_string (_, Some ("js" | "*j" | "INTERNAL_RES_CHAR_CONTENTS")) + | Pconst_string (_, None) + | Pconst_integer _ | Pconst_char _ | Pconst_float _ -> + false + | Pconst_string (_, Some _) -> true in print_constant ~template_literal c | Ppat_tuple patterns -> diff --git a/compiler/syntax/src/res_scanner.ml b/compiler/syntax/src/res_scanner.ml index 4a58812315b..be643c180e9 100644 --- a/compiler/syntax/src/res_scanner.ml +++ b/compiler/syntax/src/res_scanner.ml @@ -413,7 +413,8 @@ let scan_string_escape_sequence ~start_pos scanner = next scanner; next scanner; let low = scan_digits ~n:4 ~base:16 in - if low < 0xDC00 || low > 0xDFFF then invalid_unicode_code_point ()) + if low >= 0 && (low < 0xDC00 || low > 0xDFFF) then + invalid_unicode_code_point ()) else invalid_unicode_code_point () else if high > Res_utf8.max || (0xDC00 <= high && high <= 0xDFFF) then invalid_unicode_code_point ()) diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 0c57b4748de..a8189c5b192 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -588,7 +588,8 @@ enabled. Fixtures use `-w +A` (everything on) so default-disabled warnings still fire. Fixtures follow the naming convention `warning__.res` -so coverage gaps stay greppable. +so coverage gaps stay greppable. Warning 11 (`Unused_match`) is covered by +`warning_11_equivalent_string_patterns.res`. ### Removed warnings diff --git a/tests/analysis_tests/tests/src/expected/DocComments.res.txt b/tests/analysis_tests/tests/src/expected/DocComments.res.txt index f9adbf2b358..e9641d78e17 100644 --- a/tests/analysis_tests/tests/src/expected/DocComments.res.txt +++ b/tests/analysis_tests/tests/src/expected/DocComments.res.txt @@ -2,7 +2,7 @@ Hover src/DocComments.res 9:9 { "contents": { "kind": "markdown", - "value": "```rescript\nint\n```\n---\n Doc comment with a triple-backquote example\\n \\n ```res example\\n let a = 10\\n /*\\n * stuff\\n */\\n ```\\n" + "value": "```rescript\nint\n```\n---\n Doc comment with a triple-backquote example\n \n ```res example\n let a = 10\n /*\n * stuff\n */\n ```\n" } } @@ -18,7 +18,7 @@ Hover src/DocComments.res 33:9 { "contents": { "kind": "markdown", - "value": "```rescript\nint\n```\n---\n Doc comment with a triple-backquote example\\n \\n ```res example\\n let a = 10\\n let b = 20\\n ```\\n" + "value": "```rescript\nint\n```\n---\n Doc comment with a triple-backquote example\n \n ```res example\n let a = 10\n let b = 20\n ```\n" } } diff --git a/tests/build_tests/super_errors/expected/tagged_template_pattern.res.expected b/tests/build_tests/super_errors/expected/tagged_template_pattern.res.expected new file mode 100644 index 00000000000..88053e1d556 --- /dev/null +++ b/tests/build_tests/super_errors/expected/tagged_template_pattern.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/tagged_template_pattern.res:3:5-14 + + 1 │ let classify = value => + 2 │ switch value { + 3 │ | json`\x61` => 1 + 4 │ | _ => 2 + 5 │ } + + Tagged template literals are not supported in patterns \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/tagged_template_pattern.res b/tests/build_tests/super_errors/fixtures/tagged_template_pattern.res new file mode 100644 index 00000000000..79f24d297fc --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/tagged_template_pattern.res @@ -0,0 +1,5 @@ +let classify = value => + switch value { + | json`\x61` => 1 + | _ => 2 + } diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml index f4fc2070edc..33d380419de 100644 --- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml +++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml @@ -180,6 +180,81 @@ let map_expr_to0 e = let attr_names attrs = List.map (fun ({Location.txt}, _) -> txt) attrs +let assert_string_expr ~expected ~delim expr = + match expr.Parsetree.pexp_desc with + | Pexp_constant (Pconst_string (actual, actual_delim)) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual; + OUnit.assert_equal ~printer:Ext_obj.dump delim actual_delim + | _ -> assert_failure "Expected a string expression" + +let assert_string_pat ~expected ~delim pat = + match pat.Parsetree.ppat_desc with + | Ppat_constant (Pconst_string (actual, actual_delim)) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual; + OUnit.assert_equal ~printer:Ext_obj.dump delim actual_delim + | _ -> assert_failure "Expected a string pattern" + +let test_ast0_strings_convert_to_internal_representation _ = + let encoded = {|a\n\uD83D\uDE00|} in + let expr0 = + Ast_helper0.Exp.constant ~loc + (Parsetree0.Pconst_string (encoded, Some "js")) + in + assert_string_expr ~expected:"a\n😀" ~delim:None (map_expr0 expr0); + let pat0 = + Ast_helper0.Pat.constant ~loc + (Parsetree0.Pconst_string (encoded, Some "js")) + in + assert_string_pat ~expected:"a\n😀" ~delim:None (map_pat0 pat0); + (* Older compiler-produced ast0 files can contain the processed [*j] + delimiter. Decode those directly instead of interpreting them as source + text again. *) + let legacy_expr0 = + Ast_helper0.Exp.constant ~loc (Parsetree0.Pconst_string ({|\"|}, Some "*j")) + in + assert_string_expr ~expected:"\"" ~delim:None (map_expr0 legacy_expr0); + let template_expr0 = + Ast_helper0.Exp.constant ~loc + ~attrs:[attr "res.template" (Parsetree0.PStr [])] + (Parsetree0.Pconst_string (encoded, Some "js")) + in + assert_string_expr ~expected:encoded ~delim:(Some "bq") + (map_expr0 template_expr0); + let invalid_expr0 = + Ast_helper0.Exp.constant ~loc + (Parsetree0.Pconst_string ({|\uD800|}, Some "js")) + in + match map_expr0 invalid_expr0 with + | _ -> assert_failure "Expected an invalid ast0 string escape" + | exception Location.Error _ -> () + +let test_string_literals_roundtrip_through_ast0 _ = + let semantic = "a\n😀" in + let expr = + Ast_helper.Exp.constant ~loc (Parsetree.Pconst_string (semantic, None)) + in + assert_string_expr ~expected:semantic ~delim:None + (map_expr0 (map_expr_to0 expr)); + let encoded = {|a\n\uD83D\uDE00|} in + let template_expr = + Ast_helper.Exp.constant ~loc + ~attrs:[attr "res.template" (Parsetree.PStr [])] + (Parsetree.Pconst_string (encoded, Some "bq")) + in + let template_expr0 = map_expr_to0 template_expr in + (match template_expr0.Parsetree0.pexp_desc with + | Pexp_constant (Pconst_string (actual, Some "js")) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") encoded actual + | _ -> assert_failure "Expected ast0's template string representation"); + assert_string_expr ~expected:encoded ~delim:(Some "bq") + (map_expr0 template_expr0); + let json_expr = + Ast_helper.Exp.constant ~loc + (Parsetree.Pconst_string ({|{"answer":42}|}, Some "json")) + in + assert_string_expr ~expected:{|{"answer":42}|} ~delim:(Some "json") + (map_expr0 (map_expr_to0 json_expr)) + (* Function-node attributes such as [@this] must stay node attributes across the v0 bridge: the built-in PPX reads decorators from [pexp_attributes], so a round trip that moves them into [p_attrs] silently disables them. *) @@ -235,6 +310,10 @@ let suites = >:: test_fun_node_attrs_roundtrip_through_ast0; "fun_param_attrs_roundtrip_through_ast0" >:: test_fun_param_attrs_roundtrip_through_ast0; + "ast0_strings_convert_to_internal_representation" + >:: test_ast0_strings_convert_to_internal_representation; + "string_literals_roundtrip_through_ast0" + >:: test_string_literals_roundtrip_through_ast0; "malformed_internal_record_rest_attr_fails" >:: test_malformed_internal_record_rest_attr_fails; "record_rest_roundtrips_through_ast0" diff --git a/tests/ounit_tests/ounit_lambda_constant_tests.ml b/tests/ounit_tests/ounit_lambda_constant_tests.ml index ef285a8a559..a6b31c34daf 100644 --- a/tests/ounit_tests/ounit_lambda_constant_tests.ml +++ b/tests/ounit_tests/ounit_lambda_constant_tests.ml @@ -12,7 +12,6 @@ let suites = ( "processed string delimiters" >:: fun _ -> assert_string_constant None (Some DNone); assert_string_constant (Some "json") (Some DNoQuotes); - assert_string_constant (Some "*j") (Some DStarJ); assert_string_constant (Some "bq") (Some DBackQuotes); assert_string_constant (Some "js") None ); ] diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index d562f25d076..3a40106fdee 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -1,11 +1,8 @@ let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) -let assert_runtime_value ?(delim = Some "*j") ~encoded ~expected () = - OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected - (String_literal.runtime_value encoded delim) - -let assert_same_runtime_value left right = - OUnit.assert_equal 0 (String_literal.compare left right) +let assert_decoded ~encoded ~expected = + OUnit.assert_equal ~printer:Ext_obj.dump (Some expected) + (String_literal.decode_js_escapes encoded) let assert_invalid_backquoted_pattern encoded = let template_attribute = @@ -27,42 +24,74 @@ let assert_invalid_tagged_pattern tag contents = | _ -> OUnit.assert_failure "expected a tagged pattern error" | exception Location.Error _ -> () +let assert_transformed_expression ?(delim = "js") ~encoded ~expected () = + let expression = + Ast_helper.Exp.constant (Parsetree.Pconst_string (encoded, Some delim)) + in + match + (Ast_utf8_string_interp.transform_exp expression encoded delim).pexp_desc + with + | Pexp_constant (Pconst_string (actual, None)) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual + | _ -> OUnit.assert_failure "expected a semantic string expression" + +let assert_transformed_pattern ?(delim = "js") ~encoded ~expected () = + let pattern = + Ast_helper.Pat.constant (Parsetree.Pconst_string (encoded, Some delim)) + in + match + (Ast_utf8_string_interp.transform_pat pattern encoded delim).ppat_desc + with + | Ppat_constant (Pconst_string (actual, None)) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual + | _ -> OUnit.assert_failure "expected a semantic string pattern" + let suites = __FILE__ >::: [ ( "plain text" >:: fun _ -> - assert_runtime_value ~encoded:"plain" ~expected:"plain" () ); + assert_decoded ~encoded:"plain" ~expected:"plain" ); ( "named escapes" >:: fun _ -> - assert_runtime_value ~encoded:{|\b\f\n\r\t\v\0|} - ~expected:"\b\012\n\r\t\011\000" () ); + assert_decoded ~encoded:{|\b\f\n\r\t\v\0|} + ~expected:"\b\012\n\r\t\011\000" ); ( "escaped punctuation and non-escapes" >:: fun _ -> - assert_runtime_value ~encoded:{|\\\"\'\ \$\`\a|} - ~expected:{|\"' $`a|} () ); + assert_decoded ~encoded:{|\\\"\'\ \$\`\a|} ~expected:{|\"' $`a|} ); ( "hex escapes" >:: fun _ -> - assert_runtime_value ~encoded:{|\x61\xE9|} ~expected:"aé" () ); + assert_decoded ~encoded:{|\x61\xE9|} ~expected:"aé" ); ( "unicode escapes" >:: fun _ -> - assert_runtime_value ~encoded:{|\u0061\u20AC|} ~expected:"a€" (); - assert_runtime_value ~encoded:{|\u{1f600}|} ~expected:"😀" (); - assert_runtime_value ~encoded:{|\uD83D\uDE00|} ~expected:"😀" () ); + assert_decoded ~encoded:{|\u0061\u20AC|} ~expected:"a€"; + assert_decoded ~encoded:{|\u{1f600}|} ~expected:"😀"; + assert_decoded ~encoded:{|\uD83D\uDE00|} ~expected:"😀" ); ( "line continuations" >:: fun _ -> - assert_runtime_value ~encoded:"a\\\nb" ~expected:"ab" (); - assert_runtime_value ~encoded:"a\\\rb" ~expected:"ab" (); - assert_runtime_value ~encoded:"a\\\r\nb" ~expected:"ab" () ); - ( "processed literal delimiters" >:: fun _ -> - assert_runtime_value ~delim:(Some "*j") ~encoded:{|\x61|} - ~expected:"a" (); - assert_runtime_value ~delim:(Some "bq") ~encoded:{|\x61|} - ~expected:"a" () ); - ( "semantic and unprocessed literals remain unchanged" >:: fun _ -> - assert_runtime_value ~delim:None ~encoded:{|\x61|} ~expected:{|\x61|} - (); - assert_runtime_value ~delim:(Some "json") ~encoded:{|\x61|} - ~expected:{|\x61|} (); - assert_runtime_value ~delim:(Some "unknown") ~encoded:{|\x61|} - ~expected:{|\x61|} () ); - ( "invalid encoded values remain unchanged" >:: fun _ -> + assert_decoded ~encoded:"a\\\nb" ~expected:"ab"; + assert_decoded ~encoded:"a\\\rb" ~expected:"ab"; + assert_decoded ~encoded:"a\\\r\nb" ~expected:"ab" ); + ( "ordinary literals become semantic strings" >:: fun _ -> + assert_transformed_expression ~encoded:{|\x61\n\uD83D\uDE00|} + ~expected:"a\n😀" (); + assert_transformed_pattern ~encoded:{|\x61\n\uD83D\uDE00|} + ~expected:"a\n😀" () ); + ( "template literals remain raw" >:: fun _ -> + let encoded = {|\x61|} in + let template_attribute = + (Location.mknoloc "res.template", Parsetree.PStr []) + in + let expression = + Ast_helper.Exp.constant ~attrs:[template_attribute] + (Parsetree.Pconst_string (encoded, Some "js")) + in + match + (Ast_utf8_string_interp.transform_exp expression encoded "js") + .pexp_desc + with + | Pexp_constant (Pconst_string (actual, Some "bq")) -> + OUnit.assert_equal ~printer:(Printf.sprintf "%S") encoded actual + | _ -> OUnit.assert_failure "expected a raw template segment" ); + ( "invalid encoded values are rejected" >:: fun _ -> List.iter - (fun encoded -> assert_runtime_value ~encoded ~expected:encoded ()) + (fun encoded -> + OUnit.assert_equal ~printer:Ext_obj.dump None + (String_literal.decode_js_escapes encoded)) [ {|trailing\|}; {|\x6|}; @@ -83,18 +112,15 @@ let suites = a string made json`\x61` collide with the ordinary "\\x61" pattern during string-switch sorting. *) assert_invalid_tagged_pattern "json" {|\x61|} ); - ( "comparison uses runtime values" >:: fun _ -> - assert_same_runtime_value ("a", Some "*j") ({|\x61|}, Some "*j"); - assert_same_runtime_value ("😀", None) ({|\u{1f600}|}, Some "*j"); - assert_same_runtime_value - ({|\uD83D\uDE00|}, Some "*j") - ({|\u{1f600}|}, Some "*j"); - assert_same_runtime_value ("a\nb", Some "*j") ({|a\x0ab|}, Some "*j"); - OUnit.assert_bool "comparison should use decoded ordering" - (String_literal.compare ({|\x62|}, Some "*j") ("a", Some "*j") > 0) - ); - ( "semantic backslashes remain distinct" >:: fun _ -> - OUnit.assert_bool "semantic backslash must remain distinct" - (String_literal.compare ({|\x61|}, None) ({|\x61|}, Some "*j") <> 0) - ); + ( "printer char patterns are not tagged templates" >:: fun _ -> + let pattern = + Ast_helper.Pat.constant + (Parsetree.Pconst_string ("a", Some "INTERNAL_RES_CHAR_CONTENTS")) + in + let transformed = + Ast_utf8_string_interp.transform_pat pattern "a" + "INTERNAL_RES_CHAR_CONTENTS" + in + OUnit.assert_equal ~printer:Ext_obj.dump pattern.ppat_desc + transformed.ppat_desc ); ] diff --git a/tests/syntax_tests/data/parsing/errors/scanner/expected/invalidSurrogatePair.res.txt b/tests/syntax_tests/data/parsing/errors/scanner/expected/invalidSurrogatePair.res.txt new file mode 100644 index 00000000000..700a45e92c0 --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/scanner/expected/invalidSurrogatePair.res.txt @@ -0,0 +1,12 @@ + + Syntax error! + syntax_tests/data/parsing/errors/scanner/invalidSurrogatePair.res:1:18-25 + + 1 │ let malformed = "\uD83D\uZZZZ" + 2 │ let after = 1 + 3 │ + + unknown escape sequence + +let malformed = {js|\uD83D\uZZZZ|js} +let after = 1 \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/errors/scanner/invalidSurrogatePair.res b/tests/syntax_tests/data/parsing/errors/scanner/invalidSurrogatePair.res new file mode 100644 index 00000000000..3ef11facb46 --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/scanner/invalidSurrogatePair.res @@ -0,0 +1,2 @@ +let malformed = "\uD83D\uZZZZ" +let after = 1 diff --git a/tests/tests/src/big_polyvar_test.mjs b/tests/tests/src/big_polyvar_test.mjs index 7a170c28ed1..1cfdd7542e2 100644 --- a/tests/tests/src/big_polyvar_test.mjs +++ b/tests/tests/src/big_polyvar_test.mjs @@ -23,3606 +23,6 @@ function eq(x, y) { } } -if ("variant0" !== "variant0") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 314, - 0 - ], - Error: new Error() - }; -} - -if ("variant1" !== "variant1") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 315, - 0 - ], - Error: new Error() - }; -} - -if ("variant2" !== "variant2") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 316, - 0 - ], - Error: new Error() - }; -} - -if ("variant3" !== "variant3") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 317, - 0 - ], - Error: new Error() - }; -} - -if ("variant4" !== "variant4") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 318, - 0 - ], - Error: new Error() - }; -} - -if ("variant5" !== "variant5") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 319, - 0 - ], - Error: new Error() - }; -} - -if ("variant6" !== "variant6") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 320, - 0 - ], - Error: new Error() - }; -} - -if ("variant7" !== "variant7") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 321, - 0 - ], - Error: new Error() - }; -} - -if ("variant8" !== "variant8") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 322, - 0 - ], - Error: new Error() - }; -} - -if ("variant9" !== "variant9") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 323, - 0 - ], - Error: new Error() - }; -} - -if ("variant10" !== "variant10") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 324, - 0 - ], - Error: new Error() - }; -} - -if ("variant11" !== "variant11") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 325, - 0 - ], - Error: new Error() - }; -} - -if ("variant12" !== "variant12") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 326, - 0 - ], - Error: new Error() - }; -} - -if ("variant13" !== "variant13") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 327, - 0 - ], - Error: new Error() - }; -} - -if ("variant14" !== "variant14") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 328, - 0 - ], - Error: new Error() - }; -} - -if ("variant15" !== "variant15") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 329, - 0 - ], - Error: new Error() - }; -} - -if ("variant16" !== "variant16") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 330, - 0 - ], - Error: new Error() - }; -} - -if ("variant17" !== "variant17") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 331, - 0 - ], - Error: new Error() - }; -} - -if ("variant18" !== "variant18") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 332, - 0 - ], - Error: new Error() - }; -} - -if ("variant19" !== "variant19") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 333, - 0 - ], - Error: new Error() - }; -} - -if ("variant20" !== "variant20") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 334, - 0 - ], - Error: new Error() - }; -} - -if ("variant21" !== "variant21") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 335, - 0 - ], - Error: new Error() - }; -} - -if ("variant22" !== "variant22") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 336, - 0 - ], - Error: new Error() - }; -} - -if ("variant23" !== "variant23") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 337, - 0 - ], - Error: new Error() - }; -} - -if ("variant24" !== "variant24") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 338, - 0 - ], - Error: new Error() - }; -} - -if ("variant25" !== "variant25") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 339, - 0 - ], - Error: new Error() - }; -} - -if ("variant26" !== "variant26") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 340, - 0 - ], - Error: new Error() - }; -} - -if ("variant27" !== "variant27") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 341, - 0 - ], - Error: new Error() - }; -} - -if ("variant28" !== "variant28") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 342, - 0 - ], - Error: new Error() - }; -} - -if ("variant29" !== "variant29") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 343, - 0 - ], - Error: new Error() - }; -} - -if ("variant30" !== "variant30") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 344, - 0 - ], - Error: new Error() - }; -} - -if ("variant31" !== "variant31") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 345, - 0 - ], - Error: new Error() - }; -} - -if ("variant32" !== "variant32") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 346, - 0 - ], - Error: new Error() - }; -} - -if ("variant33" !== "variant33") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 347, - 0 - ], - Error: new Error() - }; -} - -if ("variant34" !== "variant34") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 348, - 0 - ], - Error: new Error() - }; -} - -if ("variant35" !== "variant35") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 349, - 0 - ], - Error: new Error() - }; -} - -if ("variant36" !== "variant36") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 350, - 0 - ], - Error: new Error() - }; -} - -if ("variant37" !== "variant37") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 351, - 0 - ], - Error: new Error() - }; -} - -if ("variant38" !== "variant38") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 352, - 0 - ], - Error: new Error() - }; -} - -if ("variant39" !== "variant39") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 353, - 0 - ], - Error: new Error() - }; -} - -if ("variant40" !== "variant40") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 354, - 0 - ], - Error: new Error() - }; -} - -if ("variant41" !== "variant41") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 355, - 0 - ], - Error: new Error() - }; -} - -if ("variant42" !== "variant42") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 356, - 0 - ], - Error: new Error() - }; -} - -if ("variant43" !== "variant43") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 357, - 0 - ], - Error: new Error() - }; -} - -if ("variant44" !== "variant44") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 358, - 0 - ], - Error: new Error() - }; -} - -if ("variant45" !== "variant45") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 359, - 0 - ], - Error: new Error() - }; -} - -if ("variant46" !== "variant46") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 360, - 0 - ], - Error: new Error() - }; -} - -if ("variant47" !== "variant47") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 361, - 0 - ], - Error: new Error() - }; -} - -if ("variant48" !== "variant48") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 362, - 0 - ], - Error: new Error() - }; -} - -if ("variant49" !== "variant49") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 363, - 0 - ], - Error: new Error() - }; -} - -if ("variant50" !== "variant50") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 364, - 0 - ], - Error: new Error() - }; -} - -if ("variant51" !== "variant51") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 365, - 0 - ], - Error: new Error() - }; -} - -if ("variant52" !== "variant52") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 366, - 0 - ], - Error: new Error() - }; -} - -if ("variant53" !== "variant53") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 367, - 0 - ], - Error: new Error() - }; -} - -if ("variant54" !== "variant54") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 368, - 0 - ], - Error: new Error() - }; -} - -if ("variant55" !== "variant55") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 369, - 0 - ], - Error: new Error() - }; -} - -if ("variant56" !== "variant56") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 370, - 0 - ], - Error: new Error() - }; -} - -if ("variant57" !== "variant57") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 371, - 0 - ], - Error: new Error() - }; -} - -if ("variant58" !== "variant58") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 372, - 0 - ], - Error: new Error() - }; -} - -if ("variant59" !== "variant59") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 373, - 0 - ], - Error: new Error() - }; -} - -if ("variant60" !== "variant60") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 374, - 0 - ], - Error: new Error() - }; -} - -if ("variant61" !== "variant61") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 375, - 0 - ], - Error: new Error() - }; -} - -if ("variant62" !== "variant62") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 376, - 0 - ], - Error: new Error() - }; -} - -if ("variant63" !== "variant63") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 377, - 0 - ], - Error: new Error() - }; -} - -if ("variant64" !== "variant64") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 378, - 0 - ], - Error: new Error() - }; -} - -if ("variant65" !== "variant65") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 379, - 0 - ], - Error: new Error() - }; -} - -if ("variant66" !== "variant66") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 380, - 0 - ], - Error: new Error() - }; -} - -if ("variant67" !== "variant67") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 381, - 0 - ], - Error: new Error() - }; -} - -if ("variant68" !== "variant68") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 382, - 0 - ], - Error: new Error() - }; -} - -if ("variant69" !== "variant69") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 383, - 0 - ], - Error: new Error() - }; -} - -if ("variant70" !== "variant70") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 384, - 0 - ], - Error: new Error() - }; -} - -if ("variant71" !== "variant71") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 385, - 0 - ], - Error: new Error() - }; -} - -if ("variant72" !== "variant72") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 386, - 0 - ], - Error: new Error() - }; -} - -if ("variant73" !== "variant73") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 387, - 0 - ], - Error: new Error() - }; -} - -if ("variant74" !== "variant74") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 388, - 0 - ], - Error: new Error() - }; -} - -if ("variant75" !== "variant75") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 389, - 0 - ], - Error: new Error() - }; -} - -if ("variant76" !== "variant76") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 390, - 0 - ], - Error: new Error() - }; -} - -if ("variant77" !== "variant77") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 391, - 0 - ], - Error: new Error() - }; -} - -if ("variant78" !== "variant78") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 392, - 0 - ], - Error: new Error() - }; -} - -if ("variant79" !== "variant79") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 393, - 0 - ], - Error: new Error() - }; -} - -if ("variant80" !== "variant80") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 394, - 0 - ], - Error: new Error() - }; -} - -if ("variant81" !== "variant81") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 395, - 0 - ], - Error: new Error() - }; -} - -if ("variant82" !== "variant82") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 396, - 0 - ], - Error: new Error() - }; -} - -if ("variant83" !== "variant83") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 397, - 0 - ], - Error: new Error() - }; -} - -if ("variant84" !== "variant84") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 398, - 0 - ], - Error: new Error() - }; -} - -if ("variant85" !== "variant85") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 399, - 0 - ], - Error: new Error() - }; -} - -if ("variant86" !== "variant86") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 400, - 0 - ], - Error: new Error() - }; -} - -if ("variant87" !== "variant87") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 401, - 0 - ], - Error: new Error() - }; -} - -if ("variant88" !== "variant88") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 402, - 0 - ], - Error: new Error() - }; -} - -if ("variant89" !== "variant89") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 403, - 0 - ], - Error: new Error() - }; -} - -if ("variant90" !== "variant90") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 404, - 0 - ], - Error: new Error() - }; -} - -if ("variant91" !== "variant91") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 405, - 0 - ], - Error: new Error() - }; -} - -if ("variant92" !== "variant92") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 406, - 0 - ], - Error: new Error() - }; -} - -if ("variant93" !== "variant93") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 407, - 0 - ], - Error: new Error() - }; -} - -if ("variant94" !== "variant94") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 408, - 0 - ], - Error: new Error() - }; -} - -if ("variant95" !== "variant95") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 409, - 0 - ], - Error: new Error() - }; -} - -if ("variant96" !== "variant96") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 410, - 0 - ], - Error: new Error() - }; -} - -if ("variant97" !== "variant97") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 411, - 0 - ], - Error: new Error() - }; -} - -if ("variant98" !== "variant98") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 412, - 0 - ], - Error: new Error() - }; -} - -if ("variant99" !== "variant99") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 413, - 0 - ], - Error: new Error() - }; -} - -if ("variant100" !== "variant100") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 414, - 0 - ], - Error: new Error() - }; -} - -if ("variant101" !== "variant101") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 415, - 0 - ], - Error: new Error() - }; -} - -if ("variant102" !== "variant102") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 416, - 0 - ], - Error: new Error() - }; -} - -if ("variant103" !== "variant103") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 417, - 0 - ], - Error: new Error() - }; -} - -if ("variant104" !== "variant104") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 418, - 0 - ], - Error: new Error() - }; -} - -if ("variant105" !== "variant105") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 419, - 0 - ], - Error: new Error() - }; -} - -if ("variant106" !== "variant106") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 420, - 0 - ], - Error: new Error() - }; -} - -if ("variant107" !== "variant107") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 421, - 0 - ], - Error: new Error() - }; -} - -if ("variant108" !== "variant108") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 422, - 0 - ], - Error: new Error() - }; -} - -if ("variant109" !== "variant109") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 423, - 0 - ], - Error: new Error() - }; -} - -if ("variant110" !== "variant110") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 424, - 0 - ], - Error: new Error() - }; -} - -if ("variant111" !== "variant111") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 425, - 0 - ], - Error: new Error() - }; -} - -if ("variant112" !== "variant112") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 426, - 0 - ], - Error: new Error() - }; -} - -if ("variant113" !== "variant113") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 427, - 0 - ], - Error: new Error() - }; -} - -if ("variant114" !== "variant114") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 428, - 0 - ], - Error: new Error() - }; -} - -if ("variant115" !== "variant115") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 429, - 0 - ], - Error: new Error() - }; -} - -if ("variant116" !== "variant116") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 430, - 0 - ], - Error: new Error() - }; -} - -if ("variant117" !== "variant117") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 431, - 0 - ], - Error: new Error() - }; -} - -if ("variant118" !== "variant118") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 432, - 0 - ], - Error: new Error() - }; -} - -if ("variant119" !== "variant119") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 433, - 0 - ], - Error: new Error() - }; -} - -if ("variant120" !== "variant120") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 434, - 0 - ], - Error: new Error() - }; -} - -if ("variant121" !== "variant121") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 435, - 0 - ], - Error: new Error() - }; -} - -if ("variant122" !== "variant122") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 436, - 0 - ], - Error: new Error() - }; -} - -if ("variant123" !== "variant123") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 437, - 0 - ], - Error: new Error() - }; -} - -if ("variant124" !== "variant124") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 438, - 0 - ], - Error: new Error() - }; -} - -if ("variant125" !== "variant125") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 439, - 0 - ], - Error: new Error() - }; -} - -if ("variant126" !== "variant126") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 440, - 0 - ], - Error: new Error() - }; -} - -if ("variant127" !== "variant127") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 441, - 0 - ], - Error: new Error() - }; -} - -if ("variant128" !== "variant128") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 442, - 0 - ], - Error: new Error() - }; -} - -if ("variant129" !== "variant129") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 443, - 0 - ], - Error: new Error() - }; -} - -if ("variant130" !== "variant130") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 444, - 0 - ], - Error: new Error() - }; -} - -if ("variant131" !== "variant131") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 445, - 0 - ], - Error: new Error() - }; -} - -if ("variant132" !== "variant132") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 446, - 0 - ], - Error: new Error() - }; -} - -if ("variant133" !== "variant133") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 447, - 0 - ], - Error: new Error() - }; -} - -if ("variant134" !== "variant134") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 448, - 0 - ], - Error: new Error() - }; -} - -if ("variant135" !== "variant135") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 449, - 0 - ], - Error: new Error() - }; -} - -if ("variant136" !== "variant136") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 450, - 0 - ], - Error: new Error() - }; -} - -if ("variant137" !== "variant137") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 451, - 0 - ], - Error: new Error() - }; -} - -if ("variant138" !== "variant138") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 452, - 0 - ], - Error: new Error() - }; -} - -if ("variant139" !== "variant139") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 453, - 0 - ], - Error: new Error() - }; -} - -if ("variant140" !== "variant140") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 454, - 0 - ], - Error: new Error() - }; -} - -if ("variant141" !== "variant141") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 455, - 0 - ], - Error: new Error() - }; -} - -if ("variant142" !== "variant142") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 456, - 0 - ], - Error: new Error() - }; -} - -if ("variant143" !== "variant143") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 457, - 0 - ], - Error: new Error() - }; -} - -if ("variant144" !== "variant144") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 458, - 0 - ], - Error: new Error() - }; -} - -if ("variant145" !== "variant145") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 459, - 0 - ], - Error: new Error() - }; -} - -if ("variant146" !== "variant146") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 460, - 0 - ], - Error: new Error() - }; -} - -if ("variant147" !== "variant147") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 461, - 0 - ], - Error: new Error() - }; -} - -if ("variant148" !== "variant148") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 462, - 0 - ], - Error: new Error() - }; -} - -if ("variant149" !== "variant149") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 463, - 0 - ], - Error: new Error() - }; -} - -if ("variant150" !== "variant150") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 464, - 0 - ], - Error: new Error() - }; -} - -if ("variant151" !== "variant151") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 465, - 0 - ], - Error: new Error() - }; -} - -if ("variant152" !== "variant152") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 466, - 0 - ], - Error: new Error() - }; -} - -if ("variant153" !== "variant153") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 467, - 0 - ], - Error: new Error() - }; -} - -if ("variant154" !== "variant154") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 468, - 0 - ], - Error: new Error() - }; -} - -if ("variant155" !== "variant155") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 469, - 0 - ], - Error: new Error() - }; -} - -if ("variant156" !== "variant156") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 470, - 0 - ], - Error: new Error() - }; -} - -if ("variant157" !== "variant157") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 471, - 0 - ], - Error: new Error() - }; -} - -if ("variant158" !== "variant158") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 472, - 0 - ], - Error: new Error() - }; -} - -if ("variant159" !== "variant159") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 473, - 0 - ], - Error: new Error() - }; -} - -if ("variant160" !== "variant160") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 474, - 0 - ], - Error: new Error() - }; -} - -if ("variant161" !== "variant161") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 475, - 0 - ], - Error: new Error() - }; -} - -if ("variant162" !== "variant162") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 476, - 0 - ], - Error: new Error() - }; -} - -if ("variant163" !== "variant163") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 477, - 0 - ], - Error: new Error() - }; -} - -if ("variant164" !== "variant164") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 478, - 0 - ], - Error: new Error() - }; -} - -if ("variant165" !== "variant165") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 479, - 0 - ], - Error: new Error() - }; -} - -if ("variant166" !== "variant166") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 480, - 0 - ], - Error: new Error() - }; -} - -if ("variant167" !== "variant167") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 481, - 0 - ], - Error: new Error() - }; -} - -if ("variant168" !== "variant168") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 482, - 0 - ], - Error: new Error() - }; -} - -if ("variant169" !== "variant169") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 483, - 0 - ], - Error: new Error() - }; -} - -if ("variant170" !== "variant170") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 484, - 0 - ], - Error: new Error() - }; -} - -if ("variant171" !== "variant171") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 485, - 0 - ], - Error: new Error() - }; -} - -if ("variant172" !== "variant172") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 486, - 0 - ], - Error: new Error() - }; -} - -if ("variant173" !== "variant173") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 487, - 0 - ], - Error: new Error() - }; -} - -if ("variant174" !== "variant174") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 488, - 0 - ], - Error: new Error() - }; -} - -if ("variant175" !== "variant175") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 489, - 0 - ], - Error: new Error() - }; -} - -if ("variant176" !== "variant176") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 490, - 0 - ], - Error: new Error() - }; -} - -if ("variant177" !== "variant177") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 491, - 0 - ], - Error: new Error() - }; -} - -if ("variant178" !== "variant178") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 492, - 0 - ], - Error: new Error() - }; -} - -if ("variant179" !== "variant179") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 493, - 0 - ], - Error: new Error() - }; -} - -if ("variant180" !== "variant180") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 494, - 0 - ], - Error: new Error() - }; -} - -if ("variant181" !== "variant181") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 495, - 0 - ], - Error: new Error() - }; -} - -if ("variant182" !== "variant182") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 496, - 0 - ], - Error: new Error() - }; -} - -if ("variant183" !== "variant183") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 497, - 0 - ], - Error: new Error() - }; -} - -if ("variant184" !== "variant184") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 498, - 0 - ], - Error: new Error() - }; -} - -if ("variant185" !== "variant185") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 499, - 0 - ], - Error: new Error() - }; -} - -if ("variant186" !== "variant186") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 500, - 0 - ], - Error: new Error() - }; -} - -if ("variant187" !== "variant187") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 501, - 0 - ], - Error: new Error() - }; -} - -if ("variant188" !== "variant188") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 502, - 0 - ], - Error: new Error() - }; -} - -if ("variant189" !== "variant189") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 503, - 0 - ], - Error: new Error() - }; -} - -if ("variant190" !== "variant190") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 504, - 0 - ], - Error: new Error() - }; -} - -if ("variant191" !== "variant191") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 505, - 0 - ], - Error: new Error() - }; -} - -if ("variant192" !== "variant192") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 506, - 0 - ], - Error: new Error() - }; -} - -if ("variant193" !== "variant193") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 507, - 0 - ], - Error: new Error() - }; -} - -if ("variant194" !== "variant194") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 508, - 0 - ], - Error: new Error() - }; -} - -if ("variant195" !== "variant195") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 509, - 0 - ], - Error: new Error() - }; -} - -if ("variant196" !== "variant196") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 510, - 0 - ], - Error: new Error() - }; -} - -if ("variant197" !== "variant197") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 511, - 0 - ], - Error: new Error() - }; -} - -if ("variant198" !== "variant198") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 512, - 0 - ], - Error: new Error() - }; -} - -if ("variant199" !== "variant199") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 513, - 0 - ], - Error: new Error() - }; -} - -if ("variant200" !== "variant200") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 514, - 0 - ], - Error: new Error() - }; -} - -if ("variant201" !== "variant201") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 515, - 0 - ], - Error: new Error() - }; -} - -if ("variant202" !== "variant202") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 516, - 0 - ], - Error: new Error() - }; -} - -if ("variant203" !== "variant203") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 517, - 0 - ], - Error: new Error() - }; -} - -if ("variant204" !== "variant204") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 518, - 0 - ], - Error: new Error() - }; -} - -if ("variant205" !== "variant205") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 519, - 0 - ], - Error: new Error() - }; -} - -if ("variant206" !== "variant206") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 520, - 0 - ], - Error: new Error() - }; -} - -if ("variant207" !== "variant207") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 521, - 0 - ], - Error: new Error() - }; -} - -if ("variant208" !== "variant208") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 522, - 0 - ], - Error: new Error() - }; -} - -if ("variant209" !== "variant209") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 523, - 0 - ], - Error: new Error() - }; -} - -if ("variant210" !== "variant210") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 524, - 0 - ], - Error: new Error() - }; -} - -if ("variant211" !== "variant211") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 525, - 0 - ], - Error: new Error() - }; -} - -if ("variant212" !== "variant212") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 526, - 0 - ], - Error: new Error() - }; -} - -if ("variant213" !== "variant213") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 527, - 0 - ], - Error: new Error() - }; -} - -if ("variant214" !== "variant214") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 528, - 0 - ], - Error: new Error() - }; -} - -if ("variant215" !== "variant215") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 529, - 0 - ], - Error: new Error() - }; -} - -if ("variant216" !== "variant216") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 530, - 0 - ], - Error: new Error() - }; -} - -if ("variant217" !== "variant217") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 531, - 0 - ], - Error: new Error() - }; -} - -if ("variant218" !== "variant218") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 532, - 0 - ], - Error: new Error() - }; -} - -if ("variant219" !== "variant219") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 533, - 0 - ], - Error: new Error() - }; -} - -if ("variant220" !== "variant220") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 534, - 0 - ], - Error: new Error() - }; -} - -if ("variant221" !== "variant221") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 535, - 0 - ], - Error: new Error() - }; -} - -if ("variant222" !== "variant222") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 536, - 0 - ], - Error: new Error() - }; -} - -if ("variant223" !== "variant223") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 537, - 0 - ], - Error: new Error() - }; -} - -if ("variant224" !== "variant224") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 538, - 0 - ], - Error: new Error() - }; -} - -if ("variant225" !== "variant225") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 539, - 0 - ], - Error: new Error() - }; -} - -if ("variant226" !== "variant226") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 540, - 0 - ], - Error: new Error() - }; -} - -if ("variant227" !== "variant227") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 541, - 0 - ], - Error: new Error() - }; -} - -if ("variant228" !== "variant228") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 542, - 0 - ], - Error: new Error() - }; -} - -if ("variant229" !== "variant229") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 543, - 0 - ], - Error: new Error() - }; -} - -if ("variant230" !== "variant230") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 544, - 0 - ], - Error: new Error() - }; -} - -if ("variant231" !== "variant231") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 545, - 0 - ], - Error: new Error() - }; -} - -if ("variant232" !== "variant232") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 546, - 0 - ], - Error: new Error() - }; -} - -if ("variant233" !== "variant233") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 547, - 0 - ], - Error: new Error() - }; -} - -if ("variant234" !== "variant234") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 548, - 0 - ], - Error: new Error() - }; -} - -if ("variant235" !== "variant235") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 549, - 0 - ], - Error: new Error() - }; -} - -if ("variant236" !== "variant236") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 550, - 0 - ], - Error: new Error() - }; -} - -if ("variant237" !== "variant237") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 551, - 0 - ], - Error: new Error() - }; -} - -if ("variant238" !== "variant238") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 552, - 0 - ], - Error: new Error() - }; -} - -if ("variant239" !== "variant239") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 553, - 0 - ], - Error: new Error() - }; -} - -if ("variant240" !== "variant240") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 554, - 0 - ], - Error: new Error() - }; -} - -if ("variant241" !== "variant241") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 555, - 0 - ], - Error: new Error() - }; -} - -if ("variant242" !== "variant242") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 556, - 0 - ], - Error: new Error() - }; -} - -if ("variant243" !== "variant243") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 557, - 0 - ], - Error: new Error() - }; -} - -if ("variant244" !== "variant244") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 558, - 0 - ], - Error: new Error() - }; -} - -if ("variant245" !== "variant245") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 559, - 0 - ], - Error: new Error() - }; -} - -if ("variant246" !== "variant246") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 560, - 0 - ], - Error: new Error() - }; -} - -if ("variant247" !== "variant247") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 561, - 0 - ], - Error: new Error() - }; -} - -if ("variant248" !== "variant248") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 562, - 0 - ], - Error: new Error() - }; -} - -if ("variant249" !== "variant249") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 563, - 0 - ], - Error: new Error() - }; -} - -if ("variant250" !== "variant250") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 564, - 0 - ], - Error: new Error() - }; -} - -if ("variant251" !== "variant251") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 565, - 0 - ], - Error: new Error() - }; -} - -if ("variant252" !== "variant252") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 566, - 0 - ], - Error: new Error() - }; -} - -if ("variant253" !== "variant253") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 567, - 0 - ], - Error: new Error() - }; -} - -if ("variant254" !== "variant254") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 568, - 0 - ], - Error: new Error() - }; -} - -if ("variant255" !== "variant255") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 569, - 0 - ], - Error: new Error() - }; -} - -if ("variant256" !== "variant256") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 570, - 0 - ], - Error: new Error() - }; -} - -if ("variant257" !== "variant257") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 571, - 0 - ], - Error: new Error() - }; -} - -if ("variant258" !== "variant258") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 572, - 0 - ], - Error: new Error() - }; -} - -if ("variant259" !== "variant259") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 573, - 0 - ], - Error: new Error() - }; -} - -if ("variant260" !== "variant260") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 574, - 0 - ], - Error: new Error() - }; -} - -if ("variant261" !== "variant261") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 575, - 0 - ], - Error: new Error() - }; -} - -if ("variant262" !== "variant262") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 576, - 0 - ], - Error: new Error() - }; -} - -if ("variant263" !== "variant263") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 577, - 0 - ], - Error: new Error() - }; -} - -if ("variant264" !== "variant264") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 578, - 0 - ], - Error: new Error() - }; -} - -if ("variant265" !== "variant265") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 579, - 0 - ], - Error: new Error() - }; -} - -if ("variant266" !== "variant266") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 580, - 0 - ], - Error: new Error() - }; -} - -if ("variant267" !== "variant267") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 581, - 0 - ], - Error: new Error() - }; -} - -if ("variant268" !== "variant268") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 582, - 0 - ], - Error: new Error() - }; -} - -if ("variant269" !== "variant269") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 583, - 0 - ], - Error: new Error() - }; -} - -if ("variant270" !== "variant270") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 584, - 0 - ], - Error: new Error() - }; -} - -if ("variant271" !== "variant271") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 585, - 0 - ], - Error: new Error() - }; -} - -if ("variant272" !== "variant272") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 586, - 0 - ], - Error: new Error() - }; -} - -if ("variant273" !== "variant273") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 587, - 0 - ], - Error: new Error() - }; -} - -if ("variant274" !== "variant274") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 588, - 0 - ], - Error: new Error() - }; -} - -if ("variant275" !== "variant275") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 589, - 0 - ], - Error: new Error() - }; -} - -if ("variant276" !== "variant276") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 590, - 0 - ], - Error: new Error() - }; -} - -if ("variant277" !== "variant277") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 591, - 0 - ], - Error: new Error() - }; -} - -if ("variant278" !== "variant278") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 592, - 0 - ], - Error: new Error() - }; -} - -if ("variant279" !== "variant279") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 593, - 0 - ], - Error: new Error() - }; -} - -if ("variant280" !== "variant280") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 594, - 0 - ], - Error: new Error() - }; -} - -if ("variant281" !== "variant281") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 595, - 0 - ], - Error: new Error() - }; -} - -if ("variant282" !== "variant282") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 596, - 0 - ], - Error: new Error() - }; -} - -if ("variant283" !== "variant283") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 597, - 0 - ], - Error: new Error() - }; -} - -if ("variant284" !== "variant284") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 598, - 0 - ], - Error: new Error() - }; -} - -if ("variant285" !== "variant285") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 599, - 0 - ], - Error: new Error() - }; -} - -if ("variant286" !== "variant286") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 600, - 0 - ], - Error: new Error() - }; -} - -if ("variant287" !== "variant287") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 601, - 0 - ], - Error: new Error() - }; -} - -if ("variant288" !== "variant288") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 602, - 0 - ], - Error: new Error() - }; -} - -if ("variant289" !== "variant289") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 603, - 0 - ], - Error: new Error() - }; -} - -if ("variant290" !== "variant290") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 604, - 0 - ], - Error: new Error() - }; -} - -if ("variant291" !== "variant291") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 605, - 0 - ], - Error: new Error() - }; -} - -if ("variant292" !== "variant292") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 606, - 0 - ], - Error: new Error() - }; -} - -if ("variant293" !== "variant293") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 607, - 0 - ], - Error: new Error() - }; -} - -if ("variant294" !== "variant294") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 608, - 0 - ], - Error: new Error() - }; -} - -if ("variant295" !== "variant295") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 609, - 0 - ], - Error: new Error() - }; -} - -if ("variant296" !== "variant296") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 610, - 0 - ], - Error: new Error() - }; -} - -if ("variant297" !== "variant297") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 611, - 0 - ], - Error: new Error() - }; -} - -if ("variant298" !== "variant298") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 612, - 0 - ], - Error: new Error() - }; -} - -if ("variant299" !== "variant299") { - throw { - RE_EXN_ID: "Assert_failure", - _1: [ - "big_polyvar_test.res", - 613, - 0 - ], - Error: new Error() - }; -} - if (!eq(tFromJs("variant0"), "variant0")) { throw { RE_EXN_ID: "Assert_failure", diff --git a/tests/tests/src/external_ppx2.mjs b/tests/tests/src/external_ppx2.mjs index ab97a23df55..2750b0ae0d5 100644 --- a/tests/tests/src/external_ppx2.mjs +++ b/tests/tests/src/external_ppx2.mjs @@ -1,9 +1,9 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -f("\h\e\l\lo", 42); +f("hello", 42); -let x = "\h\e\l\lo"; +let x = "hello"; let y; diff --git a/tests/tests/src/string_constant_compare.mjs b/tests/tests/src/string_constant_compare.mjs index 8fe5706c0c5..a66584bcd1d 100644 --- a/tests/tests/src/string_constant_compare.mjs +++ b/tests/tests/src/string_constant_compare.mjs @@ -5,9 +5,9 @@ let a1 = true; let a2 = false; -let a3 = "'" === "\'"; +let a3 = true; -let a4 = "'" !== "\'"; +let a4 = false; export { a1, diff --git a/tests/tests/src/string_literal_normalization_test.mjs b/tests/tests/src/string_literal_normalization_test.mjs new file mode 100644 index 00000000000..fdd1cd64252 --- /dev/null +++ b/tests/tests/src/string_literal_normalization_test.mjs @@ -0,0 +1,35 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +let escaped = "abc"; + +let surrogatePair = "😀"; + +let concatenated = "ab"; + +let interpolated = `\x61` + "b" + `\u0063`; + +function constantSwitch() { + return 1; +} + +Mocha.describe("String_literal_normalization_test", () => { + Mocha.test("ordinary escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 20, characters 69-76", escaped, "abc")); + Mocha.test("surrogate-pair escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 23, characters 7-14", surrogatePair, "😀")); + Mocha.test("ordinary literals participate in constant folding", () => { + Test_utils.eq("File \"string_literal_normalization_test.res\", line 27, characters 7-14", concatenated, "ab"); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 28, characters 7-14", constantSwitch(), 1); + }); + Mocha.test("template segments survive the ast0 bridge", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 31, characters 61-68", interpolated, "abc")); +}); + +export { + escaped, + surrogatePair, + concatenated, + interpolated, + constantSwitch, +} +/* Not a pure module */ diff --git a/tests/tests/src/string_literal_normalization_test.res b/tests/tests/src/string_literal_normalization_test.res new file mode 100644 index 00000000000..706703a4235 --- /dev/null +++ b/tests/tests/src/string_literal_normalization_test.res @@ -0,0 +1,32 @@ +@@config({flags: ["-bs-test-ast-conversion"]}) + +open Mocha +open Test_utils + +let escaped = "\x61\u0062\u{63}" +let surrogatePair = "\uD83D\uDE00" +let concatenated = "\x61" ++ "\u0062" +let interpolated = `\x61${"b"}\u0063` + +let constantSwitch = () => + switch "a" { + | "\x61" => 1 + | "b" => 2 + | "c" => 3 + | _ => 4 + } + +describe(__MODULE__, () => { + test("ordinary escapes have one semantic representation", () => eq(__LOC__, escaped, "abc")) + + test("surrogate-pair escapes have one semantic representation", () => + eq(__LOC__, surrogatePair, "😀") + ) + + test("ordinary literals participate in constant folding", () => { + eq(__LOC__, concatenated, "ab") + eq(__LOC__, constantSwitch(), 1) + }) + + test("template segments survive the ast0 bridge", () => eq(__LOC__, interpolated, "abc")) +}) diff --git a/tests/tests/src/stringmatch_test.mjs b/tests/tests/src/stringmatch_test.mjs index a6df5db225b..53fe5a3951d 100644 --- a/tests/tests/src/stringmatch_test.mjs +++ b/tests/tests/src/stringmatch_test.mjs @@ -21,7 +21,7 @@ if (tst01("") !== 0) { }; } -if (tst01("\x00\x00\x00\x03") !== 1) { +if (tst01("\0\0\0\x03") !== 1) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -33,7 +33,7 @@ if (tst01("\x00\x00\x00\x03") !== 1) { }; } -if (tst01("\x00\x00\x00\x00\x00\x00\x00\x07") !== 1) { +if (tst01("\0\0\0\0\0\0\0\x07") !== 1) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -115,7 +115,7 @@ if (tst02("B") !== 3) { }; } -if (tst02("\x00\x00\x00\x00\x00\x00\x00\x07") !== 3) { +if (tst02("\0\0\0\0\0\0\0\x07") !== 3) { throw { RE_EXN_ID: "Assert_failure", _1: [ @@ -127,7 +127,7 @@ if (tst02("\x00\x00\x00\x00\x00\x00\x00\x07") !== 3) { }; } -if (tst02("\x00\x00\x00\x03") !== 3) { +if (tst02("\0\0\0\x03") !== 3) { throw { RE_EXN_ID: "Assert_failure", _1: [ diff --git a/tests/tests/src/tagged_template_test.mjs b/tests/tests/src/tagged_template_test.mjs index a843b4a5549..c2d1399d575 100644 --- a/tests/tests/src/tagged_template_test.mjs +++ b/tests/tests/src/tagged_template_test.mjs @@ -93,7 +93,7 @@ Mocha.describe("tagged templates", () => { ]); }); Mocha.test("with a ReScript tag lifted via TaggedTemplate.make, it should return the correct interpolation", () => Test_utils.eq("File \"tagged_template_test.res\", line 133, characters 13-20", greeting, "hello Ada you're 36 years old!")); - Mocha.test("a template literal tagged with json should generate a regular string interpolation for now", () => Test_utils.eq("File \"tagged_template_test.res\", line 138, characters 13-20", "some random " + "string", "some random string")); + Mocha.test("a template literal tagged with json should generate a regular string interpolation for now", () => Test_utils.eq("File \"tagged_template_test.res\", line 138, characters 13-20", "some random string", "some random string")); Mocha.test("a regular string interpolation should continue working", () => Test_utils.eq("File \"tagged_template_test.res\", line 142, characters 7-14", `some random ` + "string" + ` interpolation`, "some random string interpolation")); }); diff --git a/tests/tests/src/test_string_switch.mjs b/tests/tests/src/test_string_switch.mjs index 0c6349a97e0..4566a165128 100644 --- a/tests/tests/src/test_string_switch.mjs +++ b/tests/tests/src/test_string_switch.mjs @@ -18,7 +18,7 @@ switch (match) { } function classifyEquivalentEscape(value, selectedCase) { - if (value === "\x61") { + if (value === "a") { if (selectedCase === 0) { return 0; } else if (selectedCase === 1) { @@ -36,7 +36,7 @@ function classifyEquivalentEscape(value, selectedCase) { } function classifyEquivalentSurrogateEscape(value, selectedCase) { - if (value === "\u{1f600}") { + if (value === "😀") { if (selectedCase === 0) { return 0; } else if (selectedCase === 1) { From f74e760e71501bb3575a3bc2d68dcd2fc8e63fa5 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 15:27:19 +0200 Subject: [PATCH 07/10] Escape semantic @as values in GenType Signed-off-by: Christoph Knittel --- compiler/gentype/emit_text.ml | 20 +++++++++++++++++ compiler/gentype/import_path.ml | 22 ++++++++++++++++++- compiler/gentype/import_path.mli | 3 +++ compiler/gentype/translate_core_type.ml | 3 ++- .../gentype/translate_type_declarations.ml | 8 ++++--- .../src/EscapedNames.gen.tsx | 2 +- .../src/EscapedNames.res | 4 +++- .../src/ImportJsValue.gen.tsx | 16 ++++++++++++++ .../src/ImportJsValue.res | 8 +++++++ .../src/ImportJsValue.res.js | 20 +++++++++++++++++ .../typescript-react-example/src/MyMath.ts | 4 ++++ .../src/Records.gen.tsx | 2 +- .../src/Records.res.js | 2 +- tests/ounit_tests/dune | 2 +- tests/ounit_tests/ounit_gentype_tests.ml | 19 ++++++++++++++++ tests/ounit_tests/ounit_tests_main.ml | 1 + 16 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 tests/ounit_tests/ounit_gentype_tests.ml diff --git a/compiler/gentype/emit_text.ml b/compiler/gentype/emit_text.ml index 3cc68260c5c..2337764408d 100644 --- a/compiler/gentype/emit_text.ml +++ b/compiler/gentype/emit_text.ml @@ -8,6 +8,26 @@ let generics_string ~type_vars = | true -> "" | false -> "<" ^ String.concat "," type_vars ^ ">" +(* Escape a semantic string as JavaScript/TypeScript string-literal contents. + [String.escaped] cannot be used for this: its decimal byte escapes follow + OCaml syntax and change non-ASCII UTF-8 text in JavaScript. *) +let escape_string_contents x = + let buf = Buffer.create (String.length x) in + String.iter + (function + | '"' -> Buffer.add_string buf "\\\"" + | '\\' -> Buffer.add_string buf "\\\\" + | '\b' -> Buffer.add_string buf "\\b" + | '\012' -> Buffer.add_string buf "\\f" + | '\n' -> Buffer.add_string buf "\\n" + | '\r' -> Buffer.add_string buf "\\r" + | '\t' -> Buffer.add_string buf "\\t" + | c when Char.code c < 0x20 || Char.code c = 0x7f -> + Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c)) + | c -> Buffer.add_char buf c) + x; + Buffer.contents buf + let quotes x = "\"" ^ x ^ "\"" let field_access ~label value = value ^ "." ^ label diff --git a/compiler/gentype/import_path.ml b/compiler/gentype/import_path.ml index d9bd7b670c5..ebccd097538 100644 --- a/compiler/gentype/import_path.ml +++ b/compiler/gentype/import_path.ml @@ -29,4 +29,24 @@ let to_cmt ~(config : Config.t) ~output_file_relative (dir, s) = | Some name -> "-" ^ name) ^ ".cmt" -let emit (dir, s) = (dir, s) |> dump +(* Import paths are emitted inside single-quoted JavaScript/TypeScript string + literals. The AST stores their semantic value, so restore source escapes at + this final output boundary. *) +let escape_for_single_quotes s = + let buf = Buffer.create (String.length s) in + String.iter + (function + | '\'' -> Buffer.add_string buf "\\'" + | '\\' -> Buffer.add_string buf "\\\\" + | '\b' -> Buffer.add_string buf "\\b" + | '\012' -> Buffer.add_string buf "\\f" + | '\n' -> Buffer.add_string buf "\\n" + | '\r' -> Buffer.add_string buf "\\r" + | '\t' -> Buffer.add_string buf "\\t" + | c when Char.code c < 0x20 || Char.code c = 0x7f -> + Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c)) + | c -> Buffer.add_char buf c) + s; + Buffer.contents buf + +let emit path = path |> dump |> escape_for_single_quotes diff --git a/compiler/gentype/import_path.mli b/compiler/gentype/import_path.mli index 5bdaa18f04f..f9bd263441b 100644 --- a/compiler/gentype/import_path.mli +++ b/compiler/gentype/import_path.mli @@ -5,6 +5,9 @@ type t val bs_curry_path : config:Config.t -> t val chop_extension_safe : t -> t [@@live] val dump : t -> string + +(* Escape a semantic import path for a single-quoted JavaScript/TypeScript + string literal. The returned string does not include the quotes. *) val emit : t -> string val from_module : dir:string -> import_extension:string -> Module_name.t -> t val from_string_unsafe : string -> t diff --git a/compiler/gentype/translate_core_type.ml b/compiler/gentype/translate_core_type.ml index 92a5c1dbedc..514c069b3fb 100644 --- a/compiler/gentype/translate_core_type.ml +++ b/compiler/gentype/translate_core_type.ml @@ -183,7 +183,8 @@ and translateCoreType_ ~config ~type_vars_gen let label_js = if as_string then match attributes |> Annotation.get_as_string with - | Some label_renamed -> StringLabel label_renamed + | Some label_renamed -> + StringLabel (Emit_text.escape_string_contents label_renamed) | None -> if is_number label then IntLabel label else StringLabel label else if as_int then ( diff --git a/compiler/gentype/translate_type_declarations.ml b/compiler/gentype/translate_type_declarations.ml index f14fd9ac42c..d5d89ffdec9 100644 --- a/compiler/gentype/translate_type_declarations.ml +++ b/compiler/gentype/translate_type_declarations.ml @@ -35,12 +35,14 @@ let create_polyvariant_case (label, attributes) = | Some (_, BoolPayload b) -> BoolLabel b | Some (_, FloatPayload s) -> FloatLabel s | Some (_, IntPayload i) -> IntLabel i - | Some (_, StringPayload as_label) -> StringLabel as_label + | Some (_, StringPayload as_label) -> + StringLabel (Emit_text.escape_string_contents as_label) | _ -> if is_number label then IntLabel label else StringLabel label); } let create_variant_case label = function - | Some (Variant_runtime.String label) -> {label_js = StringLabel label} + | Some (Variant_runtime.String label) -> + {label_js = StringLabel (Emit_text.escape_string_contents label)} | Some (Variant_runtime.Int label) -> {label_js = IntLabel (string_of_int label)} | Some (Variant_runtime.Float label) -> {label_js = FloatLabel label} @@ -62,7 +64,7 @@ let create_variant_case label = function let rename_record_field ~attributes ~name = attributes |> Annotation.check_unsupported_gentype_as_renaming; match attributes |> Annotation.get_as_string with - | Some s -> s |> String.escaped + | Some s -> Emit_text.escape_string_contents s | None -> name |> Ext_ident.unwrap_uppercase_exotic let traslate_declaration_kind ~config ~loc ~output_file_relative ~resolver diff --git a/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx b/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx index 64081ba15ef..826ef75822c 100644 --- a/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx @@ -5,7 +5,7 @@ import * as EscapedNamesJS from './EscapedNames.res.js'; -export type variant = "Illegal\"Name"; +export type variant = "Illegal\"Name" | "café\npath\\name"; export type UppercaseVariant = "Illegal\"Name"; diff --git a/tests/gentype_tests/typescript-react-example/src/EscapedNames.res b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res index 2f6d2416644..fea9823548e 100644 --- a/tests/gentype_tests/typescript-react-example/src/EscapedNames.res +++ b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res @@ -1,5 +1,7 @@ @genType -type variant = | @as("Illegal\"Name") IllegalName +type variant = + | @as("Illegal\"Name") IllegalName + | @as("café\npath\\name") Utf8 @genType type \"UppercaseVariant" = | @as("Illegal\"Name") IllegalName diff --git a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.gen.tsx b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.gen.tsx index 96acb5ab481..3b1fe08a73b 100644 --- a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.gen.tsx @@ -13,6 +13,10 @@ import {returnMixedArray as returnMixedArrayNotChecked} from './MyMath'; import {useColor as useColorNotChecked} from './MyMath'; +import {useEscapedInlineVariant as useEscapedInlineVariantNotChecked} from './MyMath'; + +import {useUtf8InlineVariant as useUtf8InlineVariantNotChecked} from './MyMath'; + import {higherOrder as higherOrderNotChecked} from './MyMath'; import {convertVariant as convertVariantNotChecked} from './MyMath'; @@ -51,6 +55,18 @@ export const useColorTypeChecked: (_1:color) => number = useColorNotChecked as a // Export 'useColor' early to allow circular import from the '.bs.js' file. export const useColor: unknown = useColorTypeChecked as (_1:color) => number as any; +// In case of type error, check the type of 'useEscapedInlineVariant' in 'ImportJsValue.res' and './MyMath'. +export const useEscapedInlineVariantTypeChecked: (_1:"Illegal\"Name") => number = useEscapedInlineVariantNotChecked as any; + +// Export 'useEscapedInlineVariant' early to allow circular import from the '.bs.js' file. +export const useEscapedInlineVariant: unknown = useEscapedInlineVariantTypeChecked as (_1:"Illegal\"Name") => number as any; + +// In case of type error, check the type of 'useUtf8InlineVariant' in 'ImportJsValue.res' and './MyMath'. +export const useUtf8InlineVariantTypeChecked: (_1:"café\npath\\name") => number = useUtf8InlineVariantNotChecked as any; + +// Export 'useUtf8InlineVariant' early to allow circular import from the '.bs.js' file. +export const useUtf8InlineVariant: unknown = useUtf8InlineVariantTypeChecked as (_1:"café\npath\\name") => number as any; + // In case of type error, check the type of 'higherOrder' in 'ImportJsValue.res' and './MyMath'. export const higherOrderTypeChecked: (_1:((_1:number, _2:number) => number)) => number = higherOrderNotChecked as any; diff --git a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res index 29a82bc5bcc..bc02690f032 100644 --- a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res +++ b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res @@ -57,6 +57,14 @@ type stringFunction @genType.import("./MyMath") external useColor: color => int = "useColor" +@genType.import("./MyMath") +external useEscapedInlineVariant: @string [@as("Illegal\"Name") #illegalName] => int = + "useEscapedInlineVariant" + +@genType.import("./MyMath") +external useUtf8InlineVariant: @string [@as("café\npath\\name") #utf8] => int = + "useUtf8InlineVariant" + @genType.import("./MyMath") external higherOrder: ((int, int) => int) => int = "higherOrder" @genType let returnedFromHigherOrder = higherOrder(\"+") diff --git a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res.js b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res.js index 6e617f004d5..8a66e36eb2c 100644 --- a/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res.js +++ b/tests/gentype_tests/typescript-react-example/src/ImportJsValue.res.js @@ -47,6 +47,24 @@ function useColor(prim) { return ImportJsValueGen$1.useColor(prim); } +function useEscapedInlineVariant(prim) { + return ImportJsValueGen$1.useEscapedInlineVariant((() => { + switch (prim) { + case "illegalName" : + return "Illegal\"Name"; + } + })()); +} + +function useUtf8InlineVariant(prim) { + return ImportJsValueGen$1.useUtf8InlineVariant((() => { + switch (prim) { + case "utf8" : + return "café\npath\\name"; + } + })()); +} + function higherOrder(prim) { return ImportJsValueGen$1.higherOrder(prim); } @@ -74,6 +92,8 @@ export { useGetProp, useGetAbs, useColor, + useEscapedInlineVariant, + useUtf8InlineVariant, higherOrder, returnedFromHigherOrder, convertVariant, diff --git a/tests/gentype_tests/typescript-react-example/src/MyMath.ts b/tests/gentype_tests/typescript-react-example/src/MyMath.ts index 9c4db39420a..251290ef90c 100644 --- a/tests/gentype_tests/typescript-react-example/src/MyMath.ts +++ b/tests/gentype_tests/typescript-react-example/src/MyMath.ts @@ -23,6 +23,10 @@ export type stringFunction = (_: string) => string; export const useColor = (_x: "tomato" | "gray"): number => 0; +export const useEscapedInlineVariant = (_x: 'Illegal"Name'): number => 0; + +export const useUtf8InlineVariant = (_x: "café\npath\\name"): number => 0; + export const higherOrder = (foo: (_1: number, _2: number) => number) => foo(3, 4); diff --git a/tests/gentype_tests/typescript-react-example/src/Records.gen.tsx b/tests/gentype_tests/typescript-react-example/src/Records.gen.tsx index f605dc6ec05..b0a412eec10 100644 --- a/tests/gentype_tests/typescript-react-example/src/Records.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/Records.gen.tsx @@ -52,7 +52,7 @@ export type myRecBsAs = { readonly jsValid0: string; readonly type: string; readonly "the-key": string; - readonly "with\\\"dquote": string; + readonly "with\"dquote": string; readonly "with'squote": string; readonly "1number": string }; diff --git a/tests/gentype_tests/typescript-react-example/src/Records.res.js b/tests/gentype_tests/typescript-react-example/src/Records.res.js index cc58487d4db..64bf0923420 100644 --- a/tests/gentype_tests/typescript-react-example/src/Records.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Records.res.js @@ -108,7 +108,7 @@ function testMyRecBsAs(x) { x.jsValid0, x.type, x["the-key"], - x["with\\\"dquote"], + x["with\"dquote"], x["with'squote"], x["1number"] ]; diff --git a/tests/ounit_tests/dune b/tests/ounit_tests/dune index 73bd6f0ce25..01e508fe0a2 100644 --- a/tests/ounit_tests/dune +++ b/tests/ounit_tests/dune @@ -13,4 +13,4 @@ (backend bisect_ppx)) (flags (:standard -w +a-4-9-30-40-41-42-48-70)) - (libraries core ounit2 analysis)) + (libraries core gentype ounit2 analysis)) diff --git a/tests/ounit_tests/ounit_gentype_tests.ml b/tests/ounit_tests/ounit_gentype_tests.ml new file mode 100644 index 00000000000..476d04ab4b5 --- /dev/null +++ b/tests/ounit_tests/ounit_gentype_tests.ml @@ -0,0 +1,19 @@ +open OUnit + +let suites = + "gentype" + >::: [ + ( "escape semantic import paths" >:: fun _ -> + let emit path = + path |> Import_path.from_string_unsafe |> Import_path.emit + in + assert_equal "./foo\\\\bar" (emit "./foo\\bar"); + assert_equal "./foo\\'bar" (emit "./foo'bar"); + assert_equal "./foo\\nbar" (emit "./foo\nbar") ); + ( "escape semantic TypeScript strings" >:: fun _ -> + let escape = Emit_text.escape_string_contents in + assert_equal "é" (escape "é"); + assert_equal "a\\\"b\\\\c" (escape "a\"b\\c"); + assert_equal "\\b\\f\\n\\r\\t\\x0b\\x7f" + (escape "\b\012\n\r\t\011\127") ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index 9c78e5066f7..ab1711ea620 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -30,6 +30,7 @@ let suites = Ounit_analysis_config_tests.suites; Ounit_analysis_references_tests.suites; Ounit_ffi_inclusion_tests.suites; + Ounit_gentype_tests.suites; ] let _ = OUnit.run_test_tt_main suites From d71263518fb9c3249fede0b6b995171848a781e4 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 31 Aug 2026 15:41:19 +0200 Subject: [PATCH 08/10] Preserve raw extension payloads through ast0 Signed-off-by: Christoph Knittel --- compiler/ml/ast_mapper_from0.ml | 46 ++++++++++++++++++- tests/ounit_tests/ounit_ast_mapper0_tests.ml | 41 +++++++++++++++++ .../src/string_literal_normalization_test.mjs | 28 +++++++++-- .../src/string_literal_normalization_test.res | 15 ++++++ 4 files changed, 124 insertions(+), 6 deletions(-) diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 2534cad0b4b..9257ab643f7 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -102,6 +102,41 @@ let map_constant ~loc ~is_template = function | Pconst_string (s, q) -> Pconst_string (s, q) | Pconst_float (s, suffix) -> Pconst_float (s, suffix) +let is_raw_source_extension = function + | "raw" | "ffi" | "re" -> true + | _ -> false + +let map_raw_source_payload sub = function + | PStr + [ + { + pstr_desc = + Pstr_eval + ( { + pexp_desc = Pexp_constant (Pconst_string (s, delim)); + pexp_loc; + pexp_attributes; + }, + eval_attributes ); + pstr_loc; + }; + ] -> + let expression = + Ast_helper.Exp.constant + ~loc:(sub.location sub pexp_loc) + ~attrs:(sub.attributes sub pexp_attributes) + (Pt.Pconst_string (s, delim)) + in + Some + (Pt.PStr + [ + Ast_helper.Str.eval + ~loc:(sub.location sub pstr_loc) + ~attrs:(sub.attributes sub eval_attributes) + expression; + ]) + | _ -> None + let for_of_attr_name = "_res.for_of" let for_await_of_attr_name = "_res.for_await_of" @@ -1080,7 +1115,16 @@ let default_mapper = pc_rhs = this.expr this pc_rhs; }); location = (fun _this l -> l); - extension = (fun this (s, e) -> (map_loc this s, this.payload this e)); + extension = + (fun this (s, payload) -> + let payload = + if is_raw_source_extension s.txt then + match map_raw_source_payload this payload with + | Some payload -> payload + | None -> this.payload this payload + else this.payload this payload + in + (map_loc this s, payload)); attribute = (fun this (s, e) -> (map_loc this s, this.payload this e)); attributes = (fun this l -> List.map (this.attribute this) l); payload = diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml index 33d380419de..e48d2ecce6c 100644 --- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml +++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml @@ -255,6 +255,45 @@ let test_string_literals_roundtrip_through_ast0 _ = assert_string_expr ~expected:{|{"answer":42}|} ~delim:(Some "json") (map_expr0 (map_expr_to0 json_expr)) +let assert_raw_extension_payload ~name ~expected expression = + match expression.Parsetree.pexp_desc with + | Pexp_extension + ( {txt}, + PStr + [ + { + pstr_desc = + Pstr_eval + ( {pexp_desc = Pexp_constant (Pconst_string (actual, delim))}, + _ ); + }; + ] ) -> + OUnit.assert_equal name txt; + OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual; + OUnit.assert_equal ~printer:Ext_obj.dump (Some "js") delim + | _ -> assert_failure "Expected a raw extension string payload" + +let test_raw_extension_payloads_roundtrip_through_ast0 _ = + let encoded = {|'\\n'|} in + List.iter + (fun name -> + let payload = + Parsetree0.PStr + [ + Ast_helper0.Str.eval ~loc + (Ast_helper0.Exp.constant ~loc + (Parsetree0.Pconst_string (encoded, Some "js"))); + ] + in + let expression0 = + Ast_helper0.Exp.extension ~loc (Location.mknoloc name, payload) + in + let expression = map_expr0 expression0 in + assert_raw_extension_payload ~name ~expected:encoded expression; + assert_raw_extension_payload ~name ~expected:encoded + (map_expr0 (map_expr_to0 expression))) + ["raw"; "ffi"; "re"] + (* Function-node attributes such as [@this] must stay node attributes across the v0 bridge: the built-in PPX reads decorators from [pexp_attributes], so a round trip that moves them into [p_attrs] silently disables them. *) @@ -314,6 +353,8 @@ let suites = >:: test_ast0_strings_convert_to_internal_representation; "string_literals_roundtrip_through_ast0" >:: test_string_literals_roundtrip_through_ast0; + "raw_extension_payloads_roundtrip_through_ast0" + >:: test_raw_extension_payloads_roundtrip_through_ast0; "malformed_internal_record_rest_attr_fails" >:: test_malformed_internal_record_rest_attr_fails; "record_rest_roundtrips_through_ast0" diff --git a/tests/tests/src/string_literal_normalization_test.mjs b/tests/tests/src/string_literal_normalization_test.mjs index fdd1cd64252..a3b104d1593 100644 --- a/tests/tests/src/string_literal_normalization_test.mjs +++ b/tests/tests/src/string_literal_normalization_test.mjs @@ -15,14 +15,29 @@ function constantSwitch() { return 1; } +const rawBridgeProgramValue = '\\n'; +; + +let rawBridgeExpression = '\\n'; + +let rawBridgeFunction = (() => '\\n'); + +let rawBridgeRegex = /\\n/; + Mocha.describe("String_literal_normalization_test", () => { - Mocha.test("ordinary escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 20, characters 69-76", escaped, "abc")); - Mocha.test("surrogate-pair escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 23, characters 7-14", surrogatePair, "😀")); + Mocha.test("ordinary escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 28, characters 69-76", escaped, "abc")); + Mocha.test("surrogate-pair escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 31, characters 7-14", surrogatePair, "😀")); Mocha.test("ordinary literals participate in constant folding", () => { - Test_utils.eq("File \"string_literal_normalization_test.res\", line 27, characters 7-14", concatenated, "ab"); - Test_utils.eq("File \"string_literal_normalization_test.res\", line 28, characters 7-14", constantSwitch(), 1); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 35, characters 7-14", concatenated, "ab"); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 36, characters 7-14", constantSwitch(), 1); + }); + Mocha.test("template segments survive the ast0 bridge", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 39, characters 61-68", interpolated, "abc")); + Mocha.test("raw extension payloads preserve source spelling through ast0", () => { + Test_utils.eq("File \"string_literal_normalization_test.res\", line 42, characters 7-14", rawBridgeExpression, "\\n"); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 43, characters 7-14", rawBridgeFunction(), "\\n"); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 44, characters 7-14", rawBridgeProgramValue, "\\n"); + Test_utils.eq("File \"string_literal_normalization_test.res\", line 45, characters 7-14", rawBridgeRegex.test("\\n"), true); }); - Mocha.test("template segments survive the ast0 bridge", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 31, characters 61-68", interpolated, "abc")); }); export { @@ -31,5 +46,8 @@ export { concatenated, interpolated, constantSwitch, + rawBridgeExpression, + rawBridgeFunction, + rawBridgeRegex, } /* Not a pure module */ diff --git a/tests/tests/src/string_literal_normalization_test.res b/tests/tests/src/string_literal_normalization_test.res index 706703a4235..e8f5f53a202 100644 --- a/tests/tests/src/string_literal_normalization_test.res +++ b/tests/tests/src/string_literal_normalization_test.res @@ -16,6 +16,14 @@ let constantSwitch = () => | _ => 4 } +%%raw("const rawBridgeProgramValue = '\\n';") + +@val external rawBridgeProgramValue: string = "rawBridgeProgramValue" + +let rawBridgeExpression: string = %raw("'\\n'") +let rawBridgeFunction: unit => string = %ffi("() => '\\n'") +let rawBridgeRegex = /\\n/ + describe(__MODULE__, () => { test("ordinary escapes have one semantic representation", () => eq(__LOC__, escaped, "abc")) @@ -29,4 +37,11 @@ describe(__MODULE__, () => { }) test("template segments survive the ast0 bridge", () => eq(__LOC__, interpolated, "abc")) + + test("raw extension payloads preserve source spelling through ast0", () => { + eq(__LOC__, rawBridgeExpression, "\\n") + eq(__LOC__, rawBridgeFunction(), "\\n") + eq(__LOC__, rawBridgeProgramValue, "\\n") + eq(__LOC__, rawBridgeRegex->RegExp.test("\\n"), true) + }) }) From 760c85db194d245b8bae2aa89799b2c6c9b4b3ee Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Tue, 1 Sep 2026 06:46:51 +0200 Subject: [PATCH 09/10] Escape Unicode line separators in GenType paths Signed-off-by: Christoph Knittel --- compiler/gentype/import_path.ml | 47 ++++++++++++++++-------- tests/ounit_tests/ounit_gentype_tests.ml | 8 +++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/compiler/gentype/import_path.ml b/compiler/gentype/import_path.ml index ebccd097538..a2f469af2cf 100644 --- a/compiler/gentype/import_path.ml +++ b/compiler/gentype/import_path.ml @@ -30,23 +30,40 @@ let to_cmt ~(config : Config.t) ~output_file_relative (dir, s) = ^ ".cmt" (* Import paths are emitted inside single-quoted JavaScript/TypeScript string - literals. The AST stores their semantic value, so restore source escapes at - this final output boundary. *) + literals and also repeated in line comments. The AST stores their semantic + value, so restore source escapes at this final output boundary. Escaping the + Unicode line separators keeps them from terminating those comments. *) let escape_for_single_quotes s = let buf = Buffer.create (String.length s) in - String.iter - (function - | '\'' -> Buffer.add_string buf "\\'" - | '\\' -> Buffer.add_string buf "\\\\" - | '\b' -> Buffer.add_string buf "\\b" - | '\012' -> Buffer.add_string buf "\\f" - | '\n' -> Buffer.add_string buf "\\n" - | '\r' -> Buffer.add_string buf "\\r" - | '\t' -> Buffer.add_string buf "\\t" - | c when Char.code c < 0x20 || Char.code c = 0x7f -> - Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c)) - | c -> Buffer.add_char buf c) - s; + let len = String.length s in + let rec loop i = + if i < len then + (* The UTF-8 encodings of U+2028 and U+2029 differ only in their final + byte. Preserve all other UTF-8 text verbatim. *) + if + i + 2 < len + && s.[i] = '\226' + && s.[i + 1] = '\128' + && (s.[i + 2] = '\168' || s.[i + 2] = '\169') + then ( + Buffer.add_string buf + (if s.[i + 2] = '\168' then "\\u2028" else "\\u2029"); + loop (i + 3)) + else ( + (match s.[i] with + | '\'' -> Buffer.add_string buf "\\'" + | '\\' -> Buffer.add_string buf "\\\\" + | '\b' -> Buffer.add_string buf "\\b" + | '\012' -> Buffer.add_string buf "\\f" + | '\n' -> Buffer.add_string buf "\\n" + | '\r' -> Buffer.add_string buf "\\r" + | '\t' -> Buffer.add_string buf "\\t" + | c when Char.code c < 0x20 || Char.code c = 0x7f -> + Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c)) + | c -> Buffer.add_char buf c); + loop (i + 1)) + in + loop 0; Buffer.contents buf let emit path = path |> dump |> escape_for_single_quotes diff --git a/tests/ounit_tests/ounit_gentype_tests.ml b/tests/ounit_tests/ounit_gentype_tests.ml index 476d04ab4b5..d3afe68ec2c 100644 --- a/tests/ounit_tests/ounit_gentype_tests.ml +++ b/tests/ounit_tests/ounit_gentype_tests.ml @@ -9,7 +9,13 @@ let suites = in assert_equal "./foo\\\\bar" (emit "./foo\\bar"); assert_equal "./foo\\'bar" (emit "./foo'bar"); - assert_equal "./foo\\nbar" (emit "./foo\nbar") ); + assert_equal "./foo\\nbar" (emit "./foo\nbar"); + let line_separator = Ext_utf8.encode_codepoint 0x2028 in + let paragraph_separator = Ext_utf8.encode_codepoint 0x2029 in + assert_equal "./foo\\u2028bar\\u2029baz" + (emit + ("./foo" ^ line_separator ^ "bar" ^ paragraph_separator ^ "baz")) + ); ( "escape semantic TypeScript strings" >:: fun _ -> let escape = Emit_text.escape_string_contents in assert_equal "é" (escape "é"); From f400ea7e96b016a66ba108cd2ffc4d2260834e04 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Tue, 1 Sep 2026 07:06:55 +0200 Subject: [PATCH 10/10] Reject malformed UTF-8 in string literals Signed-off-by: Christoph Knittel --- compiler/ml/string_literal.ml | 27 ++++++++++++++----- .../ounit_tests/ounit_string_literal_tests.ml | 7 ++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/compiler/ml/string_literal.ml b/compiler/ml/string_literal.ml index 83368f27e57..da764144f99 100644 --- a/compiler/ml/string_literal.ml +++ b/compiler/ml/string_literal.ml @@ -44,6 +44,19 @@ let decode_js_escapes s = in loop start 0 false in + let copy_utf8 index = + match Ext_utf8.classify s.[index] with + | Single _ -> + Buffer.add_char buf s.[index]; + Some (index + 1) + | Leading (remaining, _) -> + let last = Ext_utf8.next s ~remaining index in + if last < 0 then None + else ( + Buffer.add_substring buf s index (last - index + 1); + Some (last + 1)) + | Cont _ | Invalid -> None + in let rec loop index = if index = len then Some (Buffer.contents buf) else @@ -97,14 +110,16 @@ let decode_js_escapes s = else None | Some codepoint when add_codepoint codepoint -> loop (index + 6) | Some _ | None -> None) - | c -> + | _ -> ( (* JavaScript non-escape characters, such as [\a], evaluate to the character following the backslash. This also handles escaped quotes, backslashes, dollars, backticks, and spaces. *) - Buffer.add_char buf c; - loop (index + 2)) - | c -> - Buffer.add_char buf c; - loop (index + 1) + match copy_utf8 (index + 1) with + | Some next -> loop next + | None -> None)) + | _ -> ( + match copy_utf8 index with + | Some next -> loop next + | None -> None) in loop 0 diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index 3a40106fdee..ad9b929463d 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -55,7 +55,8 @@ let suites = assert_decoded ~encoded:{|\b\f\n\r\t\v\0|} ~expected:"\b\012\n\r\t\011\000" ); ( "escaped punctuation and non-escapes" >:: fun _ -> - assert_decoded ~encoded:{|\\\"\'\ \$\`\a|} ~expected:{|\"' $`a|} ); + assert_decoded ~encoded:{|\\\"\'\ \$\`\a|} ~expected:{|\"' $`a|}; + assert_decoded ~encoded:"\\é" ~expected:"é" ); ( "hex escapes" >:: fun _ -> assert_decoded ~encoded:{|\x61\xE9|} ~expected:"aé" ); ( "unicode escapes" >:: fun _ -> @@ -103,6 +104,10 @@ let suites = {|\uDC00|}; {|\uD800\u0041|}; {|\uDC00\uD800|}; + "\128"; + "\195"; + "\195A"; + "\\\195"; ] ); ( "backquoted patterns reject lone surrogate escapes" >:: fun _ -> assert_invalid_backquoted_pattern {|\uD800|};