Skip to content

Commit c2aad32

Browse files
committed
Preserve raw extension payloads through ast0
Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent 94ed3e3 commit c2aad32

4 files changed

Lines changed: 124 additions & 6 deletions

File tree

compiler/ml/ast_mapper_from0.ml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,41 @@ let map_constant ~loc ~is_template = function
102102
| Pconst_string (s, q) -> Pconst_string (s, q)
103103
| Pconst_float (s, suffix) -> Pconst_float (s, suffix)
104104

105+
let is_raw_source_extension = function
106+
| "raw" | "ffi" | "re" -> true
107+
| _ -> false
108+
109+
let map_raw_source_payload sub = function
110+
| PStr
111+
[
112+
{
113+
pstr_desc =
114+
Pstr_eval
115+
( {
116+
pexp_desc = Pexp_constant (Pconst_string (s, delim));
117+
pexp_loc;
118+
pexp_attributes;
119+
},
120+
eval_attributes );
121+
pstr_loc;
122+
};
123+
] ->
124+
let expression =
125+
Ast_helper.Exp.constant
126+
~loc:(sub.location sub pexp_loc)
127+
~attrs:(sub.attributes sub pexp_attributes)
128+
(Pt.Pconst_string (s, delim))
129+
in
130+
Some
131+
(Pt.PStr
132+
[
133+
Ast_helper.Str.eval
134+
~loc:(sub.location sub pstr_loc)
135+
~attrs:(sub.attributes sub eval_attributes)
136+
expression;
137+
])
138+
| _ -> None
139+
105140
let for_of_attr_name = "_res.for_of"
106141
let for_await_of_attr_name = "_res.for_await_of"
107142

@@ -1080,7 +1115,16 @@ let default_mapper =
10801115
pc_rhs = this.expr this pc_rhs;
10811116
});
10821117
location = (fun _this l -> l);
1083-
extension = (fun this (s, e) -> (map_loc this s, this.payload this e));
1118+
extension =
1119+
(fun this (s, payload) ->
1120+
let payload =
1121+
if is_raw_source_extension s.txt then
1122+
match map_raw_source_payload this payload with
1123+
| Some payload -> payload
1124+
| None -> this.payload this payload
1125+
else this.payload this payload
1126+
in
1127+
(map_loc this s, payload));
10841128
attribute = (fun this (s, e) -> (map_loc this s, this.payload this e));
10851129
attributes = (fun this l -> List.map (this.attribute this) l);
10861130
payload =

tests/ounit_tests/ounit_ast_mapper0_tests.ml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,45 @@ let test_string_literals_roundtrip_through_ast0 _ =
255255
assert_string_expr ~expected:{|{"answer":42}|} ~delim:(Some "json")
256256
(map_expr0 (map_expr_to0 json_expr))
257257

258+
let assert_raw_extension_payload ~name ~expected expression =
259+
match expression.Parsetree.pexp_desc with
260+
| Pexp_extension
261+
( {txt},
262+
PStr
263+
[
264+
{
265+
pstr_desc =
266+
Pstr_eval
267+
( {pexp_desc = Pexp_constant (Pconst_string (actual, delim))},
268+
_ );
269+
};
270+
] ) ->
271+
OUnit.assert_equal name txt;
272+
OUnit.assert_equal ~printer:(Printf.sprintf "%S") expected actual;
273+
OUnit.assert_equal ~printer:Ext_obj.dump (Some "js") delim
274+
| _ -> assert_failure "Expected a raw extension string payload"
275+
276+
let test_raw_extension_payloads_roundtrip_through_ast0 _ =
277+
let encoded = {|'\\n'|} in
278+
List.iter
279+
(fun name ->
280+
let payload =
281+
Parsetree0.PStr
282+
[
283+
Ast_helper0.Str.eval ~loc
284+
(Ast_helper0.Exp.constant ~loc
285+
(Parsetree0.Pconst_string (encoded, Some "js")));
286+
]
287+
in
288+
let expression0 =
289+
Ast_helper0.Exp.extension ~loc (Location.mknoloc name, payload)
290+
in
291+
let expression = map_expr0 expression0 in
292+
assert_raw_extension_payload ~name ~expected:encoded expression;
293+
assert_raw_extension_payload ~name ~expected:encoded
294+
(map_expr0 (map_expr_to0 expression)))
295+
["raw"; "ffi"; "re"]
296+
258297
(* Function-node attributes such as [@this] must stay node attributes across
259298
the v0 bridge: the built-in PPX reads decorators from [pexp_attributes],
260299
so a round trip that moves them into [p_attrs] silently disables them. *)
@@ -314,6 +353,8 @@ let suites =
314353
>:: test_ast0_strings_convert_to_internal_representation;
315354
"string_literals_roundtrip_through_ast0"
316355
>:: test_string_literals_roundtrip_through_ast0;
356+
"raw_extension_payloads_roundtrip_through_ast0"
357+
>:: test_raw_extension_payloads_roundtrip_through_ast0;
317358
"malformed_internal_record_rest_attr_fails"
318359
>:: test_malformed_internal_record_rest_attr_fails;
319360
"record_rest_roundtrips_through_ast0"

tests/tests/src/string_literal_normalization_test.mjs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,29 @@ function constantSwitch() {
1515
return 1;
1616
}
1717

18+
const rawBridgeProgramValue = '\\n';
19+
;
20+
21+
let rawBridgeExpression = '\\n';
22+
23+
let rawBridgeFunction = (() => '\\n');
24+
25+
let rawBridgeRegex = /\\n/;
26+
1827
Mocha.describe("String_literal_normalization_test", () => {
19-
Mocha.test("ordinary escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 20, characters 69-76", escaped, "abc"));
20-
Mocha.test("surrogate-pair escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 23, characters 7-14", surrogatePair, "😀"));
28+
Mocha.test("ordinary escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 28, characters 69-76", escaped, "abc"));
29+
Mocha.test("surrogate-pair escapes have one semantic representation", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 31, characters 7-14", surrogatePair, "😀"));
2130
Mocha.test("ordinary literals participate in constant folding", () => {
22-
Test_utils.eq("File \"string_literal_normalization_test.res\", line 27, characters 7-14", concatenated, "ab");
23-
Test_utils.eq("File \"string_literal_normalization_test.res\", line 28, characters 7-14", constantSwitch(), 1);
31+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 35, characters 7-14", concatenated, "ab");
32+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 36, characters 7-14", constantSwitch(), 1);
33+
});
34+
Mocha.test("template segments survive the ast0 bridge", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 39, characters 61-68", interpolated, "abc"));
35+
Mocha.test("raw extension payloads preserve source spelling through ast0", () => {
36+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 42, characters 7-14", rawBridgeExpression, "\\n");
37+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 43, characters 7-14", rawBridgeFunction(), "\\n");
38+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 44, characters 7-14", rawBridgeProgramValue, "\\n");
39+
Test_utils.eq("File \"string_literal_normalization_test.res\", line 45, characters 7-14", rawBridgeRegex.test("\\n"), true);
2440
});
25-
Mocha.test("template segments survive the ast0 bridge", () => Test_utils.eq("File \"string_literal_normalization_test.res\", line 31, characters 61-68", interpolated, "abc"));
2641
});
2742

2843
export {
@@ -31,5 +46,8 @@ export {
3146
concatenated,
3247
interpolated,
3348
constantSwitch,
49+
rawBridgeExpression,
50+
rawBridgeFunction,
51+
rawBridgeRegex,
3452
}
3553
/* Not a pure module */

tests/tests/src/string_literal_normalization_test.res

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ let constantSwitch = () =>
1616
| _ => 4
1717
}
1818

19+
%%raw("const rawBridgeProgramValue = '\\n';")
20+
21+
@val external rawBridgeProgramValue: string = "rawBridgeProgramValue"
22+
23+
let rawBridgeExpression: string = %raw("'\\n'")
24+
let rawBridgeFunction: unit => string = %ffi("() => '\\n'")
25+
let rawBridgeRegex = /\\n/
26+
1927
describe(__MODULE__, () => {
2028
test("ordinary escapes have one semantic representation", () => eq(__LOC__, escaped, "abc"))
2129

@@ -29,4 +37,11 @@ describe(__MODULE__, () => {
2937
})
3038

3139
test("template segments survive the ast0 bridge", () => eq(__LOC__, interpolated, "abc"))
40+
41+
test("raw extension payloads preserve source spelling through ast0", () => {
42+
eq(__LOC__, rawBridgeExpression, "\\n")
43+
eq(__LOC__, rawBridgeFunction(), "\\n")
44+
eq(__LOC__, rawBridgeProgramValue, "\\n")
45+
eq(__LOC__, rawBridgeRegex->RegExp.test("\\n"), true)
46+
})
3247
})

0 commit comments

Comments
 (0)