From 3de49cebd884d25ac18a15ccde3ec94d662cc17d Mon Sep 17 00:00:00 2001 From: AskSkivdal Date: Fri, 31 Oct 2025 09:44:27 +0100 Subject: [PATCH] #65 Support untagged enums --- src/to_typescript/enums.rs | 61 +++++++++++++++++++- test/issue-65-untagged-enums/rust.rs | 20 +++++++ test/issue-65-untagged-enums/tsync.sh | 8 +++ test/issue-65-untagged-enums/typescript.d.ts | 16 +++++ test/issue-65-untagged-enums/typescript.ts | 16 +++++ test/test_all.sh | 1 + 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 test/issue-65-untagged-enums/rust.rs create mode 100755 test/issue-65-untagged-enums/tsync.sh create mode 100644 test/issue-65-untagged-enums/typescript.d.ts create mode 100644 test/issue-65-untagged-enums/typescript.ts diff --git a/src/to_typescript/enums.rs b/src/to_typescript/enums.rs index 30e3f6c..3d72445 100644 --- a/src/to_typescript/enums.rs +++ b/src/to_typescript/enums.rs @@ -20,8 +20,12 @@ impl super::ToTypescript for syn::ItemEnum { let is_single = !self.variants.iter().any(|x| !x.fields.is_empty()); state.write_comments(&comments, 0); + // Handle untagged enum if serde has the tag untagged + if utils::get_attribute_arg("serde", "untagged", &self.attrs).is_some() { + add_untagged_tagged_enum(self, state, casing, config.uses_type_interface); + } // always use output the internally_tagged representation if the tag is present - if let Some(tag_name) = utils::get_attribute_arg("serde", "tag", &self.attrs) { + else if let Some(tag_name) = utils::get_attribute_arg("serde", "tag", &self.attrs) { let content_name = utils::get_attribute_arg("serde", "content", &self.attrs); add_internally_tagged_enum( tag_name, @@ -398,3 +402,58 @@ fn add_externally_tagged_enum( } state.types.push_str(";\n"); } + +fn add_untagged_tagged_enum( + exported_struct: syn::ItemEnum, + state: &mut BuildState, + casing: Option, + uses_type_interface: bool, +) { + let export = if uses_type_interface { "" } else { "export " }; + let generics = utils::extract_struct_generics(exported_struct.generics.clone()); + + // Write type name and generics + state.types.push_str(&format!( + "{export}type {interface_name}{generics} =", + interface_name = exported_struct.ident, + generics = utils::format_generics(&generics) + )); + + // Loop over each variant of the enum + for variant in exported_struct.variants { + state.types.push('\n'); + // Copy comments from rust + let comments = utils::get_comments(variant.attrs); + state.write_comments(&comments, 2); + + // Unnamed fields: + // ```rs + // enum Data { + // Value1(i32) + // } + // ``` + if let syn::Fields::Unnamed(fields) = &variant.fields { + // add discriminant + state.types.push_str(&format!(" | ")); + super::structs::process_tuple_fields(fields.clone(), state); + state.types.push_str(""); + } + // Named fields: + // ```rs + // enum Data { + // Value1 { v: i32 } + // } + // ``` + else { + // add discriminant + state.types.push_str(&format!(" | {{\n")); + + super::structs::process_fields(variant.fields, state, 6, casing, true); + + state + .types + .push_str(&format!("{}}}", utils::build_indentation(4))); + } + } + state.types.push_str(";\n"); +} diff --git a/test/issue-65-untagged-enums/rust.rs b/test/issue-65-untagged-enums/rust.rs new file mode 100644 index 0000000..820ad72 --- /dev/null +++ b/test/issue-65-untagged-enums/rust.rs @@ -0,0 +1,20 @@ +/// test/rust.rs +use tsync::tsync; + +#[derive(Serialize, Deserialize)] +#[tsync] +#[serde(untagged)] +enum Message { + ValueOne(i32, i32), + Value2(i32), +} + +#[derive(Serialize, Deserialize)] +#[tsync] +#[serde(untagged)] +enum Message2 { + ValueOne { a: V, b: G }, + Value2 { c: V }, + Value3(G), + Value3(Vec), +} diff --git a/test/issue-65-untagged-enums/tsync.sh b/test/issue-65-untagged-enums/tsync.sh new file mode 100755 index 0000000..1764bd6 --- /dev/null +++ b/test/issue-65-untagged-enums/tsync.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +cd $SCRIPT_DIR + +cargo run -- -i rust.rs -o typescript.d.ts +cargo run -- -i rust.rs -o typescript.ts \ No newline at end of file diff --git a/test/issue-65-untagged-enums/typescript.d.ts b/test/issue-65-untagged-enums/typescript.d.ts new file mode 100644 index 0000000..08b1f76 --- /dev/null +++ b/test/issue-65-untagged-enums/typescript.d.ts @@ -0,0 +1,16 @@ +/* This file is generated and managed by tsync */ + +type Message = + | [ number, number ] + | number; + +type Message2 = + | { + a: V; + b: G; + } + | { + c: V; + } + | G + | Array; diff --git a/test/issue-65-untagged-enums/typescript.ts b/test/issue-65-untagged-enums/typescript.ts new file mode 100644 index 0000000..f2b6155 --- /dev/null +++ b/test/issue-65-untagged-enums/typescript.ts @@ -0,0 +1,16 @@ +/* This file is generated and managed by tsync */ + +export type Message = + | [ number, number ] + | number; + +export type Message2 = + | { + a: V; + b: G; + } + | { + c: V; + } + | G + | Array; diff --git a/test/test_all.sh b/test/test_all.sh index 8235449..fa9feb8 100755 --- a/test/test_all.sh +++ b/test/test_all.sh @@ -17,4 +17,5 @@ cd $SCRIPT_DIR ./issue-43/tsync.sh ./issue-55/tsync.sh ./issue-58/tsync.sh +./issue-65-untagged-enums/tsync.sh ./raw_identifiers/tsync.sh