@@ -93,10 +93,12 @@ struct GetterCtx {
9393}
9494
9595/// Emit getter methods for all fields of a type wrapper.
96+ #[ allow( clippy:: too_many_arguments) ]
9697pub ( super ) fn emit_getters (
9798 ty : & TypeDef ,
9899 type_paths : & HashMap < String , String > ,
99100 enum_names : & HashSet < & str > ,
101+ unit_enum_names : & HashSet < & str > ,
100102 no_serde_names : & HashSet < & str > ,
101103 exclude_fields : & HashSet < String > ,
102104 configured_features : & std:: collections:: HashSet < & str > ,
@@ -162,16 +164,19 @@ pub(super) fn emit_getters(
162164 } ,
163165 ) ) ;
164166 } else if is_enum_named ( & field. ty , enum_names) {
165- // Enum-typed Named field: return the serde-JSON encoding of the source enum
166- // value as a String. The opaque enum type is NOT declared in the extern block
167- // (see extern_block.rs), so we must not return the wrapper type here; and the
168- // discriminant-only wrapper would lose the variant payload, so serialize the
169- // original value instead. The Swift side JSON-decodes this back into its Codable
170- // enum (matching the bidirectional `*_from_json` representation).
171- emit_enum_string_getter ( field, & ctx, enum_names, out) ;
167+ // Enum-typed Named field: return the enum as a String. The opaque enum type is
168+ // NOT declared in the extern block (see extern_block.rs), so we must not return
169+ // the wrapper type here. Unit enums (all variants fieldless) serialize to their
170+ // bare serde raw value via the wrapper's `to_string()`, which Swift reconstructs
171+ // with `Type(rawValue:)`. Tagged enums (variants carry data) cannot use the
172+ // discriminant-only wrapper — it drops the payload — so the source value is
173+ // serialized with `serde_json::to_string`, which Swift decodes via `JSONDecoder`
174+ // (matching the bidirectional `*_from_json` representation).
175+ emit_enum_string_getter ( field, & ctx, enum_names, unit_enum_names, out) ;
172176 } else if is_vec_of_enum ( & field. ty , enum_names) {
173- // Vec<Named(enum)>: map each element to String via to_string().
174- emit_vec_enum_string_getter ( field, & ctx, enum_names, out) ;
177+ // Vec<Named(enum)>: map each element to a String (raw value for unit enums,
178+ // serde-JSON for tagged enums — see emit_enum_string_getter).
179+ emit_vec_enum_string_getter ( field, & ctx, enum_names, unit_enum_names, out) ;
175180 } else if let TypeRef :: Named ( wrapper) = & field. ty {
176181 emit_named_getter ( field, wrapper, & ctx, enum_names, out) ;
177182 } else if let TypeRef :: Vec ( inner) = & field. ty {
@@ -257,29 +262,42 @@ pub(super) fn emit_getters(
257262
258263/// Emit a `String`-returning getter for an enum-typed `Named` field.
259264///
260- /// Instead of returning the opaque enum wrapper (which would trigger swift-bridge's
261- /// `Vec<EnumType> Vectorizable` generation), this serializes the source enum value to
262- /// JSON via `serde_json::to_string`. The discriminant-only bridge wrapper drops the
263- /// variant payload and its `.to_string()` emits a bare variant name (invalid JSON), so
264- /// the original value must be serialized for the Swift `JSONDecoder` to reconstruct it.
265+ /// Returns the opaque enum as a `String` (avoids swift-bridge's `Vec<EnumType>
266+ /// Vectorizable` generation). The encoding depends on the enum kind:
267+ /// - Unit enums (all variants fieldless): the bridge wrapper's `to_string()` yields the
268+ /// bare serde raw value (e.g. `stop`), which Swift reconstructs via `Type(rawValue:)`.
269+ /// - Tagged enums (some variant carries data): the discriminant-only wrapper drops the
270+ /// payload, so the source value is serialized with `serde_json::to_string` and Swift
271+ /// decodes it via `JSONDecoder` (matching the bidirectional `*_from_json` representation).
265272fn emit_enum_string_getter (
266273 field : & crate :: core:: ir:: FieldDef ,
267274 ctx : & GetterCtx ,
268275 enum_names : & HashSet < & str > ,
276+ unit_enum_names : & HashSet < & str > ,
269277 out : & mut String ,
270278) {
271279 let TypeRef :: Named ( wrapper) = & field. ty else {
272280 return ;
273281 } ;
274282 let is_enum = enum_names. contains ( wrapper. as_str ( ) ) ;
275283 debug_assert ! ( is_enum, "emit_enum_string_getter called with non-enum Named type" ) ;
284+ let is_unit = unit_enum_names. contains ( wrapper. as_str ( ) ) ;
276285
277286 let name = & ctx. name ;
278287 let getter_name = & ctx. getter_name ;
279288
280289 if field. optional {
281- // Option<EnumType> → Option<String>. Boxed and Arc fields both deref to the enum via `&*w`.
282- let map_expr = if field. is_boxed || matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
290+ // Option<EnumType> → Option<String>.
291+ let map_expr = if is_unit {
292+ if field. is_boxed {
293+ format ! ( "self.0.{name}.clone().map(|w| {wrapper}::from(*w).to_string())" )
294+ } else if matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
295+ format ! ( "self.0.{name}.clone().map(|w| {wrapper}::from((*w).clone()).to_string())" )
296+ } else {
297+ format ! ( "self.0.{name}.clone().map(|w| {wrapper}::from(w).to_string())" )
298+ }
299+ } else if field. is_boxed || matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
300+ // Tagged enum: serialize the source value. Boxed and Arc both deref via `&*w`.
283301 format ! (
284302 "self.0.{name}.clone().map(|w| serde_json::to_string(&*w).unwrap_or_else(|_| \" null\" .to_string()))"
285303 )
@@ -294,8 +312,17 @@ fn emit_enum_string_getter(
294312 } ,
295313 ) ) ;
296314 } else {
297- // EnumType → String. Boxed and Arc fields both deref to the enum via `&*self.0.{name}`.
298- let expr = if field. is_boxed || matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
315+ // EnumType → String.
316+ let expr = if is_unit {
317+ if field. is_boxed {
318+ format ! ( "{wrapper}::from(*self.0.{name}.clone()).to_string()" )
319+ } else if matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
320+ format ! ( "{wrapper}::from((*self.0.{name}).clone()).to_string()" )
321+ } else {
322+ format ! ( "{wrapper}::from(self.0.{name}.clone()).to_string()" )
323+ }
324+ } else if field. is_boxed || matches ! ( field. core_wrapper, CoreWrapper :: Arc ) {
325+ // Tagged enum: serialize the source value. Boxed and Arc both deref via `&*self.0.{name}`.
299326 format ! ( "serde_json::to_string(&*self.0.{name}).unwrap_or_else(|_| \" null\" .to_string())" )
300327 } else {
301328 format ! ( "serde_json::to_string(&self.0.{name}).unwrap_or_else(|_| \" null\" .to_string())" )
@@ -312,11 +339,14 @@ fn emit_enum_string_getter(
312339
313340/// Emit a `Vec<String>`-returning getter for a `Vec<Named(enum)>` field.
314341///
315- /// Maps each enum element to its serde-JSON encoding via `serde_json::to_string`.
342+ /// Maps each enum element to a `String`: the bridge wrapper's `to_string()` raw value for
343+ /// unit enums, or `serde_json::to_string` of the source value for tagged enums (see
344+ /// `emit_enum_string_getter` for the encoding rationale).
316345fn emit_vec_enum_string_getter (
317346 field : & crate :: core:: ir:: FieldDef ,
318347 ctx : & GetterCtx ,
319348 enum_names : & HashSet < & str > ,
349+ unit_enum_names : & HashSet < & str > ,
320350 out : & mut String ,
321351) {
322352 let TypeRef :: Vec ( inner) = & field. ty else {
@@ -327,14 +357,22 @@ fn emit_vec_enum_string_getter(
327357 } ;
328358 let is_enum = enum_names. contains ( wrapper. as_str ( ) ) ;
329359 debug_assert ! ( is_enum, "emit_vec_enum_string_getter called with non-enum Vec<Named>" ) ;
360+ let is_unit = unit_enum_names. contains ( wrapper. as_str ( ) ) ;
330361
331362 let name = & ctx. name ;
332363 let getter_name = & ctx. getter_name ;
333364
334- // Build the per-element mapping expression based on wrapping strategy.
335- let elem_expr = match field. vec_inner_core_wrapper {
336- CoreWrapper :: Arc => "serde_json::to_string(&**elem).unwrap_or_else(|_| \" null\" .to_string())" . to_string ( ) ,
337- _ => "serde_json::to_string(elem).unwrap_or_else(|_| \" null\" .to_string())" . to_string ( ) ,
365+ // Build the per-element mapping expression based on enum kind and wrapping strategy.
366+ let elem_expr = if is_unit {
367+ match field. vec_inner_core_wrapper {
368+ CoreWrapper :: Arc => format ! ( "{wrapper}::from((**elem).clone()).to_string()" ) ,
369+ _ => format ! ( "{wrapper}::from(elem.clone()).to_string()" ) ,
370+ }
371+ } else {
372+ match field. vec_inner_core_wrapper {
373+ CoreWrapper :: Arc => "serde_json::to_string(&**elem).unwrap_or_else(|_| \" null\" .to_string())" . to_string ( ) ,
374+ _ => "serde_json::to_string(elem).unwrap_or_else(|_| \" null\" .to_string())" . to_string ( ) ,
375+ }
338376 } ;
339377
340378 if field. optional {
0 commit comments