Skip to content

Commit c292e07

Browse files
committed
Normalize string literal representation
Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent 59ed00e commit c292e07

214 files changed

Lines changed: 3417 additions & 5962 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

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

1515
#### :boom: Breaking Change
1616

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

2627
#### :rocket: New Feature
2728

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

3537
- 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
38+
- 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/8606
3639
- Fix signature inclusion rejecting equivalent object externals after type-alias expansion. https://github.com/rescript-lang/rescript/pull/8581
3740
- 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
3841
- 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

analysis/reanalyze/src/annotation.ml

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,38 @@ let tag_is_one_of_the_gentype_annotations s =
1717

1818
let rec get_attribute_payload check_text (attributes : Typedtree.attributes) =
1919
let rec from_expr (expr : Parsetree.expression) =
20-
match expr with
21-
| {pexp_desc = Pexp_constant (Pconst_string (s, _))} ->
22-
Some (StringPayload s)
23-
| {pexp_desc = Pexp_constant (Pconst_integer (n, _))} -> Some (IntPayload n)
24-
| {pexp_desc = Pexp_constant (Pconst_float (s, _))} -> Some (FloatPayload s)
25-
| {
26-
pexp_desc = Pexp_construct ({txt = Lident (("true" | "false") as s)}, _);
27-
_;
28-
} ->
29-
Some (BoolPayload (s = "true"))
30-
| {pexp_desc = Pexp_construct ({txt = Longident.Lident "[]"}, None)} -> None
31-
| {pexp_desc = Pexp_construct ({txt = Longident.Lident "::"}, Some e)} ->
32-
from_expr e
33-
| {pexp_desc = Pexp_construct ({txt}, _); _} ->
34-
Some (ConstructPayload (txt |> Longident.flatten |> String.concat "."))
35-
| {pexp_desc = Pexp_tuple exprs | Pexp_array exprs} ->
36-
let payloads =
37-
exprs |> List.rev
38-
|> List.fold_left
39-
(fun payloads expr ->
40-
match expr |> from_expr with
41-
| Some payload -> payload :: payloads
42-
| None -> payloads)
43-
[]
44-
in
45-
Some (TuplePayload payloads)
46-
| {pexp_desc = Pexp_ident {txt}} -> Some (IdentPayload txt)
47-
| _ -> None
20+
match Ast_payload.semantic_string_of_expression expr with
21+
| Some s -> Some (StringPayload s)
22+
| None -> (
23+
match expr with
24+
| {pexp_desc = Pexp_constant (Pconst_integer (n, _))} ->
25+
Some (IntPayload n)
26+
| {pexp_desc = Pexp_constant (Pconst_float (s, _))} ->
27+
Some (FloatPayload s)
28+
| {
29+
pexp_desc = Pexp_construct ({txt = Lident (("true" | "false") as s)}, _);
30+
_;
31+
} ->
32+
Some (BoolPayload (s = "true"))
33+
| {pexp_desc = Pexp_construct ({txt = Longident.Lident "[]"}, None)} ->
34+
None
35+
| {pexp_desc = Pexp_construct ({txt = Longident.Lident "::"}, Some e)} ->
36+
from_expr e
37+
| {pexp_desc = Pexp_construct ({txt}, _); _} ->
38+
Some (ConstructPayload (txt |> Longident.flatten |> String.concat "."))
39+
| {pexp_desc = Pexp_tuple exprs | Pexp_array exprs} ->
40+
let payloads =
41+
exprs |> List.rev
42+
|> List.fold_left
43+
(fun payloads expr ->
44+
match expr |> from_expr with
45+
| Some payload -> payload :: payloads
46+
| None -> payloads)
47+
[]
48+
in
49+
Some (TuplePayload payloads)
50+
| {pexp_desc = Pexp_ident {txt}} -> Some (IdentPayload txt)
51+
| _ -> None)
4852
in
4953
match attributes with
5054
| [] -> None

analysis/reanalyze/src/arnold.ml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ module Find_functions_called = struct
535535
let super = Tast_mapper.default in
536536
let expr (self : Tast_mapper.mapper) (e : Typedtree.expression) =
537537
(match e.exp_desc with
538-
| Texp_apply {funct = {exp_desc = Texp_ident (callee, _, _)}} ->
538+
| Texp_apply {funct = {exp_desc = Texp_ident (callee, _, _)}}
539+
| Texp_tagged_template {tag = {exp_desc = Texp_ident (callee, _, _)}} ->
539540
let function_name = Path.name callee in
540541
callees := !callees |> String_set.add function_name
541542
| _ -> ());
@@ -867,6 +868,13 @@ module Compile = struct
867868
| None -> expr |> expression ~ctx |> eval_args ~args ~ctx)
868869
| Texp_apply {funct = expr; args} ->
869870
expr |> expression ~ctx |> eval_args ~args ~ctx
871+
| Texp_tagged_template {tag; values} ->
872+
let args =
873+
List.map (fun value -> (Asttypes.Nolabel, Some value)) values
874+
in
875+
tag |> expression ~ctx |> eval_args ~args ~ctx
876+
| Texp_template {values} ->
877+
values |> List.map (expression ~ctx) |> Command.sequence
870878
| Texp_let
871879
( Recursive,
872880
[{vb_pat = {pat_desc = Tpat_var (id, _); pat_loc}; vb_expr}],

analysis/reanalyze/src/side_effects.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ let rec expr_no_side_effects (expr : Typedtree.expression) =
6464
| Texp_for (_id, _pat, e1, e2, _dir, e3) ->
6565
e1 |> expr_no_side_effects && e2 |> expr_no_side_effects
6666
&& e3 |> expr_no_side_effects
67-
| Texp_for_of _ | Texp_for_await_of _ -> false
67+
| Texp_template {values} -> values |> List.for_all expr_no_side_effects
68+
| Texp_for_of _ | Texp_for_await_of _ | Texp_tagged_template _ -> false
6869
| Texp_object_literal fields ->
6970
fields |> List.for_all (fun (_name, e) -> e |> expr_no_side_effects)
7071
| Texp_object_get _ -> false

analysis/src/completion_front_end.ml

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,17 @@ let find_arg_completables ~(args : arg list) ~end_pos ~pos_before_cursor
209209
let rec expr_to_context_path_inner ~(in_jsx_context : bool)
210210
(e : Parsetree.expression) =
211211
match e.pexp_desc with
212-
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
212+
| Pexp_constant (Pconst_string _ | Pconst_json _ | Pconst_raw_source _) ->
213+
Some Completable.CPString
214+
| Pexp_template _ -> Some Completable.CPString
215+
| Pexp_tagged_template {tag} -> (
216+
match expr_to_context_path ~in_jsx_context tag with
217+
| Some context_path ->
218+
(* Tagged templates are typed like a call of the tag with the template
219+
strings and interpolation values. Preserve that application context
220+
now that the parser no longer represents it as [Pexp_apply]. *)
221+
Some (CPApply (context_path, [Nolabel; Nolabel]))
222+
| None -> None)
213223
| Pexp_constant (Pconst_integer _) -> Some CPInt
214224
| Pexp_constant (Pconst_float _) -> Some CPFloat
215225
| Pexp_construct ({txt = Lident ("true" | "false")}, None) -> Some CPBool
@@ -934,14 +944,22 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
934944
{
935945
pstr_desc =
936946
Pstr_eval
937-
( {pexp_loc; pexp_desc = Pexp_constant (Pconst_string (s, _))},
947+
( ({
948+
pexp_loc;
949+
pexp_desc =
950+
( Pexp_constant (Pconst_string _)
951+
| Pexp_template {source_segments = [_]; values = []} );
952+
} as expression),
938953
_ );
939954
};
940955
]
941-
when loc_has_cursor pexp_loc ->
942-
if Debug.verbose () then
943-
print_endline "[decoratorCompletion] Found @module";
944-
set_result (Completable.CdecoratorPayload (Module s))
956+
when loc_has_cursor pexp_loc -> (
957+
match Ast_payload.semantic_string_of_expression expression with
958+
| Some s ->
959+
if Debug.verbose () then
960+
print_endline "[decoratorCompletion] Found @module";
961+
set_result (Completable.CdecoratorPayload (Module s))
962+
| None -> ())
945963
| PStr
946964
[
947965
{
@@ -968,11 +986,14 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
968986
Completion_expressions.is_expr_hole from_expr,
969987
from_expr )
970988
with
971-
| true, _, _, {pexp_desc = Pexp_constant (Pconst_string (s, _))} ->
972-
if Debug.verbose () then
973-
print_endline
974-
"[decoratorCompletion] @module `from` payload was string";
975-
set_result (Completable.CdecoratorPayload (Module s))
989+
| true, _, _, from_expr -> (
990+
match Ast_payload.semantic_string_of_expression from_expr with
991+
| Some s ->
992+
if Debug.verbose () then
993+
print_endline
994+
"[decoratorCompletion] @module `from` payload was string";
995+
set_result (Completable.CdecoratorPayload (Module s))
996+
| None -> ())
976997
| false, true, true, _ ->
977998
if Debug.verbose () then
978999
print_endline
@@ -1176,7 +1197,7 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
11761197
args =
11771198
[
11781199
(* sh`echo "meh"` *)
1179-
(_, ({pexp_desc = Pexp_apply _} as inner_expr));
1200+
(_, ({pexp_desc = Pexp_tagged_template _} as inner_expr));
11801201
(* recovery inserted node *)
11811202
(_, {pexp_desc = Pexp_extension ({txt = "rescript.exprhole"}, _)});
11821203
];
@@ -1206,7 +1227,7 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
12061227
args =
12071228
[
12081229
(* sh`echo "meh"` *)
1209-
(_, ({pexp_desc = Pexp_apply _} as inner_expr));
1230+
(_, ({pexp_desc = Pexp_tagged_template _} as inner_expr));
12101231
(* foo *)
12111232
(_, {pexp_desc = Pexp_ident {txt = Lident field_name}});
12121233
];

analysis/src/completion_jsx.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ let is_regexp_jsx_heuristic_expr expr =
303303
{
304304
pstr_desc =
305305
Pstr_eval
306-
({pexp_desc = Pexp_constant (Pconst_string ("//", _))}, _);
306+
({pexp_desc = Pexp_constant (Pconst_raw_source "//")}, _);
307307
};
308308
] )
309309
when expr.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) ->

analysis/src/document_symbol.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ let get_symbols ~source ~kind_file =
1919
match exp.pexp_desc with
2020
| Pexp_fun _ -> Lsp.Types.SymbolKind.Function
2121
| Pexp_constraint (e, _) -> expr_kind e
22-
| Pexp_constant (Pconst_string _) -> Lsp.Types.SymbolKind.String
22+
| Pexp_constant (Pconst_string _ | Pconst_json _ | Pconst_raw_source _) ->
23+
Lsp.Types.SymbolKind.String
24+
| Pexp_template _ -> Lsp.Types.SymbolKind.String
2325
| Pexp_constant (Pconst_float _ | Pconst_integer _) ->
2426
Lsp.Types.SymbolKind.Number
2527
| Pexp_constant _ -> Lsp.Types.SymbolKind.Constant

analysis/src/dump_ast.ml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,13 @@ let print_attributes attributes =
4242
let print_constant const =
4343
match const with
4444
| Parsetree.Pconst_integer (s, _) -> "Pconst_integer(" ^ s ^ ")"
45-
| Pconst_char c -> "Pconst_char(" ^ String.make 1 (Char.chr c) ^ ")"
46-
| Pconst_string (s, delim) ->
47-
let delim =
48-
match delim with
49-
| None -> ""
50-
| Some delim -> delim ^ " "
51-
in
52-
"Pconst_string(" ^ delim ^ s ^ delim ^ ")"
45+
| Pconst_char {source; semantic} ->
46+
"Pconst_char(source=" ^ source ^ ", semantic=" ^ string_of_int semantic
47+
^ ")"
48+
| Pconst_string {source; semantic} ->
49+
"Pconst_string(source=" ^ source ^ ", semantic=" ^ semantic ^ ")"
50+
| Pconst_json source -> "Pconst_json(" ^ source ^ ")"
51+
| Pconst_raw_source source -> "Pconst_raw_source(" ^ source ^ ")"
5352
| Pconst_float (s, _) -> "Pconst_float(" ^ s ^ ")"
5453

5554
let print_core_type typ ~pos =
@@ -264,6 +263,21 @@ and print_expr_item expr ~pos ~indentation =
264263
^ ")"
265264
| Pexp_extension (({txt} as loc), _) ->
266265
"Pexp_extension(%" ^ (loc |> print_loc_denominator_loc ~pos) ^ txt ^ ")"
266+
| Pexp_template {source_segments; values} ->
267+
"Pexp_template(source_segments=["
268+
^ String.concat ", " source_segments
269+
^ "], values=["
270+
^ String.concat ", "
271+
(List.map (fun value -> print_expr_item value ~pos ~indentation) values)
272+
^ "])"
273+
| Pexp_tagged_template {tag; raw_sources; values} ->
274+
"Pexp_tagged_template(tag="
275+
^ print_expr_item tag ~pos ~indentation
276+
^ ", sources="
277+
^ string_of_int (List.length raw_sources)
278+
^ ", values="
279+
^ string_of_int (List.length values)
280+
^ ")"
267281
| Pexp_assert expr ->
268282
"Pexp_assert(" ^ print_expr_item expr ~pos ~indentation ^ ")"
269283
| Pexp_field (exp, loc) ->

analysis/src/process_attributes.ml

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,38 @@ open Shared_types
22

33
(* TODO should I hang on to location? *)
44
let rec find_doc_attribute attributes =
5-
let open Parsetree in
65
match attributes with
76
| [] -> None
8-
| ( {Asttypes.txt = "ocaml.doc" | "ocaml.text" | "ns.doc" | "res.doc"},
9-
PStr
10-
[
11-
{
12-
pstr_desc =
13-
Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (doc, _))}, _);
14-
};
15-
] )
16-
:: _ ->
17-
Some doc
7+
| ({Asttypes.txt = "ocaml.doc" | "ocaml.text" | "ns.doc" | "res.doc"}, payload)
8+
:: rest -> (
9+
match Ast_payload.semantic_string_of_payload payload with
10+
| Some doc -> Some doc
11+
| None -> find_doc_attribute rest)
1812
| _ :: rest -> find_doc_attribute rest
1913

2014
let rec find_deprecated_attribute attributes =
2115
let open Parsetree in
2216
match attributes with
2317
| [] -> None
24-
| ( {Asttypes.txt = "deprecated"},
25-
PStr [{pstr_desc = Pstr_eval ({pexp_desc = expr}, _)}] )
18+
| ({Asttypes.txt = "deprecated"}, PStr [{pstr_desc = Pstr_eval (expr, _)}])
2619
:: _ -> (
27-
match expr with
28-
(* Simple deprecated attr @deprecated("message") *)
29-
| Pexp_constant (Pconst_string (_msg, _)) -> Some _msg
30-
(* deprecated attr with record *)
31-
| Pexp_record (fields, _) ->
32-
let reason = ref "" in
33-
34-
fields
35-
|> List.iter (fun {lid = {txt}; x} ->
36-
match (txt, x) with
37-
| ( Lident "reason",
38-
{pexp_desc = Pexp_constant (Pconst_string (msg, _))} ) ->
39-
reason := msg
40-
| _ -> ());
41-
42-
Some !reason
43-
| _ -> None)
20+
match Ast_payload.semantic_string_of_expression expr with
21+
| Some msg -> Some msg
22+
| None -> (
23+
match expr.pexp_desc with
24+
(* deprecated attr with record *)
25+
| Pexp_record (fields, _) ->
26+
let reason = ref "" in
27+
fields
28+
|> List.iter (fun {lid = {txt}; x} ->
29+
match txt with
30+
| Lident "reason" -> (
31+
match Ast_payload.semantic_string_of_expression x with
32+
| Some msg -> reason := msg
33+
| None -> ())
34+
| _ -> ());
35+
Some !reason
36+
| _ -> None))
4437
| ({Asttypes.txt = "deprecated"}, _) :: _ -> Some ""
4538
| _ :: rest -> find_deprecated_attribute rest
4639

analysis/src/type_utils.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ module Codegen = struct
10091009
let mk_fail_with_exp () =
10101010
Ast_helper.Exp.apply
10111011
(Ast_helper.Exp.ident {txt = Lident "failwith"; loc = Location.none})
1012-
[(Nolabel, Ast_helper.Exp.constant (Pconst_string ("TODO", None)))]
1012+
[(Nolabel, Ast_helper.Exp.constant (Ast_helper.Const.string "TODO"))]
10131013

10141014
let mk_construct_pat ?payload name =
10151015
Ast_helper.Pat.construct

0 commit comments

Comments
 (0)