diff --git a/changes/printed-string-escaping.fix.md b/changes/printed-string-escaping.fix.md new file mode 100644 index 0000000000..09199f169f --- /dev/null +++ b/changes/printed-string-escaping.fix.md @@ -0,0 +1 @@ +- Print the `@as` names of fields and constructors and the strings of externals as they are written, so that non-ASCII letters are no longer turned into escapes that read back as other characters in error messages, hover and generated interfaces. diff --git a/compiler/ml/printtyp.ml b/compiler/ml/printtyp.ml index c9a19c4a15..8d3b29e866 100644 --- a/compiler/ml/printtyp.ml +++ b/compiler/ml/printtyp.ml @@ -945,7 +945,8 @@ and tree_of_constructor ?printing_context ~layout ~position cd = let as_annotation = function | Variant_runtime.Null -> "@as(null)" | Undefined -> "@as(undefined)" - | String s -> Printf.sprintf "@as(%S)" s + | String s -> + Printf.sprintf "@as(\"%s\")" (String_literal.encode_js_string s) | Int i -> Printf.sprintf "@as(%d)" i | Float f -> Printf.sprintf "@as(%s)" f | Bool b -> Printf.sprintf "@as(%b)" b diff --git a/compiler/syntax/src/res_outcome_printer.ml b/compiler/syntax/src/res_outcome_printer.ml index 831466e4d9..a473919c62 100644 --- a/compiler/syntax/src/res_outcome_printer.ml +++ b/compiler/syntax/src/res_outcome_printer.ml @@ -41,6 +41,9 @@ let collect_arrow_args (out_type : Outcometree.out_type) = | Otyp_arrow (args, ret) -> (args, ret) | _ -> ([], out_type) +let print_string_literal_doc s = + Doc.text ("\"" ^ String_literal.encode_js_string s ^ "\"") + let rec collect_functor_args (out_module_type : Outcometree.out_module_type) args = match out_module_type with @@ -398,7 +401,12 @@ and print_record_decl_row_doc (name, runtime_name, mut, opt, arg) = [ (match runtime_name with | Some runtime_name -> - Doc.text ("@as(\"" ^ String.escaped runtime_name ^ "\") ") + Doc.concat + [ + Doc.text "@as("; + print_string_literal_doc runtime_name; + Doc.text ") "; + ] | None -> Doc.nil); (if mut then Doc.text "mutable " else Doc.nil); Printer.print_ident_like ~allow_uident:false name; @@ -454,7 +462,6 @@ let print_type_parameter_doc (typ, (co, cn)) = (* Print a digested FFI declaration as the surface attributes the user wrote; the stored form is the declaration itself. *) -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 diff --git a/tests/build_tests/super_errors/expected/as_names_printed_as_written.res.expected b/tests/build_tests/super_errors/expected/as_names_printed_as_written.res.expected new file mode 100644 index 0000000000..6e4805b907 --- /dev/null +++ b/tests/build_tests/super_errors/expected/as_names_printed_as_written.res.expected @@ -0,0 +1,21 @@ + + We've found a bug for you! + /.../fixtures/as_names_printed_as_written.res:5:5-7:1 + + 3 │ module M: { + 4 │ type t = | @as("é") A({@as("prénom") name: string}) | @as("a\"b") B + 5 │ } = { + 6 │  type t = | @as("é") A({@as("prénom") name: int}) | @as("a\"b") B + 7 │ } + 8 │ + + Signature mismatch: + + Actual declaration (/.../fixtures/as_names_printed_as_written.res:6:3-66): + type t = @as("é") A({@as("prénom") name: int}) | @as("a\"b") B + + Expected declaration (/.../fixtures/as_names_printed_as_written.res:4:3-69): + type t = @as("é") A({@as("prénom") name: string}) | @as("a\"b") B + + The records of constructor A differ: + The types for field name are not equal. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/external_strings_printed_as_written.res.expected b/tests/build_tests/super_errors/expected/external_strings_printed_as_written.res.expected new file mode 100644 index 0000000000..4a733fe269 --- /dev/null +++ b/tests/build_tests/super_errors/expected/external_strings_printed_as_written.res.expected @@ -0,0 +1,19 @@ + + We've found a bug for you! + /.../fixtures/external_strings_printed_as_written.res:5:5-8:1 + + 3 │ @module("ünïcode") @scope("ëxtra") + 4 │ external f: int => int = "f" + 5 │ } = { + 6 │  @module("ünïcode") @scope("ëxtra") + 7 │  external f: string => int = "f" + 8 │ } + 9 │ + + Signature mismatch: + + Actual declaration (/.../fixtures/external_strings_printed_as_written.res:6:3-7:33): + @val @module("ünïcode") @scope("ëxtra") external f: string => int = "f" + + Expected declaration (/.../fixtures/external_strings_printed_as_written.res:3:3-4:30): + @val @module("ünïcode") @scope("ëxtra") external f: int => int = "f" \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/as_names_printed_as_written.res b/tests/build_tests/super_errors/fixtures/as_names_printed_as_written.res new file mode 100644 index 0000000000..f0535b9597 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/as_names_printed_as_written.res @@ -0,0 +1,7 @@ +// The @as names of constructors and fields print as they are written in +// source, non-ASCII letters and quotes included +module M: { + type t = | @as("é") A({@as("prénom") name: string}) | @as("a\"b") B +} = { + type t = | @as("é") A({@as("prénom") name: int}) | @as("a\"b") B +} diff --git a/tests/build_tests/super_errors/fixtures/external_strings_printed_as_written.res b/tests/build_tests/super_errors/fixtures/external_strings_printed_as_written.res new file mode 100644 index 0000000000..b0d75b8066 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/external_strings_printed_as_written.res @@ -0,0 +1,8 @@ +// The strings of a printed external print as they are written in source +module M: { + @module("ünïcode") @scope("ëxtra") + external f: int => int = "f" +} = { + @module("ünïcode") @scope("ëxtra") + external f: string => int = "f" +}