From 8586107993ffa5fafd6b8e9af0e5c227bfd3f9ea Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 11 Dec 2025 22:58:51 -0500 Subject: [PATCH] fix: record field names incorrectly emitted by genType I managed to find the source of this with Claude's help, though I don't have enough experience with ocaml to know for sure if this is an idiomatic fix. resolves #8086 Signed-off-by: Rob --- CHANGELOG.md | 1 + compiler/gentype/GenTypeCommon.ml | 2 +- compiler/gentype/TranslateTypeDeclarations.ml | 9 +++++-- .../src/EscapedNames.gen.tsx | 23 ++++++++++++++++ .../src/EscapedNames.res | 26 +++++++++++++++++++ .../src/EscapedNames.res.js | 14 ++++++++++ 6 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx create mode 100644 tests/gentype_tests/typescript-react-example/src/EscapedNames.res create mode 100644 tests/gentype_tests/typescript-react-example/src/EscapedNames.res.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a395c8a21..7b2b2e80aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Fix `@val` shadowing (rewrite using `globalThis`). https://github.com/rescript-lang/rescript/pull/8098 - Fix `@scope` shadowing (rewrite using `globalThis`). https://github.com/rescript-lang/rescript/pull/8100 - Fix rewatch panic on duplicate module name. https://github.com/rescript-lang/rescript/pull/8102 +- Fix gentype generating invalid syntax for exotic / escaped record field names and type names. https://github.com/rescript-lang/rescript/pull/8087 #### :memo: Documentation diff --git a/compiler/gentype/GenTypeCommon.ml b/compiler/gentype/GenTypeCommon.ml index 548289d922..9923927fda 100644 --- a/compiler/gentype/GenTypeCommon.ml +++ b/compiler/gentype/GenTypeCommon.ml @@ -180,7 +180,7 @@ let ident ?(builtin = true) ?(type_args = []) name = Ident {builtin; name; type_args} let sanitize_type_name name = - name + name |> Ext_ident.unwrap_uppercase_exotic |> String.map (function | '\'' -> '_' | c -> c) diff --git a/compiler/gentype/TranslateTypeDeclarations.ml b/compiler/gentype/TranslateTypeDeclarations.ml index 2768bdf343..9d12689871 100644 --- a/compiler/gentype/TranslateTypeDeclarations.ml +++ b/compiler/gentype/TranslateTypeDeclarations.ml @@ -41,12 +41,15 @@ let create_case (label, attributes) ~poly = * If @genType.as is used, perform renaming conversion. * If @as is used (with records-as-objects active), escape and quote if * the identifier contains characters which are invalid as JS property names. + * For escaped identifiers like \"foo-bar", strip the surrounding \"..." + * since they are part of the ReScript syntax, not the actual field name. + * The resulting name will be quoted later in EmitType if needed. *) 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 - | None -> name + | None -> name |> Ext_ident.unwrap_uppercase_exotic let traslate_declaration_kind ~config ~loc ~output_file_relative ~resolver ~type_attributes ~type_env ~type_name ~type_vars declaration_kind : @@ -290,7 +293,9 @@ let traslate_declaration_kind ~config ~loc ~output_file_relative ~resolver create_variant ~inherits:[] ~no_payloads ~payloads ~polymorphic:false ~tag:tag_annotation ~unboxed:unboxed_annotation in - let resolved_type_name = type_name |> TypeEnv.add_module_path ~type_env in + let resolved_type_name = + type_name |> sanitize_type_name |> TypeEnv.add_module_path ~type_env + in let export_from_type_declaration = { CodeItem.export_type = diff --git a/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx b/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx new file mode 100644 index 0000000000..e4ade9ad94 --- /dev/null +++ b/tests/gentype_tests/typescript-react-example/src/EscapedNames.gen.tsx @@ -0,0 +1,23 @@ +/* TypeScript file generated from EscapedNames.res by genType. */ + +/* eslint-disable */ +/* tslint:disable */ + +import * as EscapedNamesJS from './EscapedNames.res.js'; + +export type variant = "Illegal\"Name"; + +export type UppercaseVariant = "Illegal\"Name"; + +export type polymorphicVariant = "Illegal\"Name"; + +export type object_ = { readonly normalField: number; readonly "escape\"me": number }; + +export type record = { + readonly normalField: variant; + readonly "Renamed'Field": number; + readonly "Illegal-field name": number; + readonly UPPERCASE: number +}; + +export const myRecord: record = EscapedNamesJS.myRecord as any; diff --git a/tests/gentype_tests/typescript-react-example/src/EscapedNames.res b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res new file mode 100644 index 0000000000..9093f9ef49 --- /dev/null +++ b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res @@ -0,0 +1,26 @@ +@genType +type variant = | @as("Illegal\"Name") IllegalName + +@genType +type \"UppercaseVariant" = | @as("Illegal\"Name") IllegalName + +@genType +type polymorphicVariant = [#"Illegal\"Name"] + +@genType +type object_ = {"normalField": int, "escape\"me": int} + +@genType +type record = { + normalField: variant, + @as("Renamed'Field") renamedField: int, + \"Illegal-field name": int, + \"UPPERCASE": int, +} +@genType +let myRecord = { + normalField: IllegalName, + renamedField: 42, + \"Illegal-field name": 7, + \"UPPERCASE": 100, +} diff --git a/tests/gentype_tests/typescript-react-example/src/EscapedNames.res.js b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res.js new file mode 100644 index 0000000000..791308c9bd --- /dev/null +++ b/tests/gentype_tests/typescript-react-example/src/EscapedNames.res.js @@ -0,0 +1,14 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +let myRecord = { + normalField: "Illegal\"Name", + "Renamed'Field": 42, + "Illegal-field name": 7, + UPPERCASE: 100 +}; + +export { + myRecord, +} +/* No side effect */