1- use crate :: typescript:: convert_type;
2- use crate :: { utils, BuildState } ;
1+ use crate :: { typescript:: convert_type, utils, BuildState } ;
32use convert_case:: { Case , Casing } ;
43use syn:: __private:: ToTokens ;
54
@@ -9,26 +8,6 @@ use syn::__private::ToTokens;
98/// `rename_all` attributes for the name of the tag will also be adhered to.
109impl super :: ToTypescript for syn:: ItemEnum {
1110 fn convert_to_ts ( self , state : & mut BuildState , config : & crate :: BuildSettings ) {
12- // check we don't have any tuple structs that could mess things up.
13- // if we do ignore this struct
14- for variant in self . variants . iter ( ) {
15- // allow single-field tuple structs to pass through as newtype structs
16- let mut is_newtype = false ;
17- for f in variant. fields . iter ( ) {
18- if f. ident . is_none ( ) {
19- // If we already marked this variant as a newtype, we have a multi-field tuple struct
20- if is_newtype {
21- if crate :: DEBUG . try_get ( ) . is_some_and ( |d| * d) {
22- println ! ( "#[tsync] failed for enum {}" , self . ident) ;
23- }
24- return ;
25- } else {
26- is_newtype = true ;
27- }
28- }
29- }
30- }
31-
3211 state. types . push ( '\n' ) ;
3312
3413 let comments = utils:: get_comments ( self . clone ( ) . attrs ) ;
@@ -42,7 +21,15 @@ impl super::ToTypescript for syn::ItemEnum {
4221
4322 // always use output the internally_tagged representation if the tag is present
4423 if let Some ( tag_name) = utils:: get_attribute_arg ( "serde" , "tag" , & self . attrs ) {
45- add_internally_tagged_enum ( tag_name, self , state, casing, config. uses_type_interface )
24+ let content_name = utils:: get_attribute_arg ( "serde" , "content" , & self . attrs ) ;
25+ add_internally_tagged_enum (
26+ tag_name,
27+ content_name,
28+ self ,
29+ state,
30+ casing,
31+ config. uses_type_interface ,
32+ )
4633 } else if is_single {
4734 if utils:: has_attribute_arg ( "derive" , "Serialize_repr" , & self . attrs ) {
4835 add_numeric_enum ( self , state, casing, config)
@@ -208,63 +195,151 @@ fn add_numeric_enum(
208195/// ```
209196fn add_internally_tagged_enum (
210197 tag_name : String ,
198+ content_name : Option < String > ,
211199 exported_struct : syn:: ItemEnum ,
212200 state : & mut BuildState ,
213201 casing : Option < Case > ,
214202 uses_type_interface : bool ,
215203) {
216204 let export = if uses_type_interface { "" } else { "export " } ;
205+ let generics = utils:: extract_struct_generics ( exported_struct. generics . clone ( ) ) ;
217206 state. types . push_str ( & format ! (
218207 "{export}type {interface_name}{generics} =" ,
219208 interface_name = exported_struct. ident,
220- generics = utils:: extract_struct_generics ( exported_struct . generics. clone ( ) )
209+ generics = utils:: format_generics ( & generics)
221210 ) ) ;
222211
212+ // a list of the generics for each variant, so we don't need to recalculate them
213+ let mut variant_generics_list = Vec :: new ( ) ;
214+
223215 for variant in exported_struct. variants . iter ( ) {
216+ let variant_field_types = variant. fields . iter ( ) . map ( |f| f. ty . to_owned ( ) ) ;
217+ let variant_generics = generics
218+ . iter ( )
219+ . filter ( |gen| {
220+ variant_field_types
221+ . clone ( )
222+ . any ( |ty| utils:: type_contains_ident ( & ty, gen) )
223+ } )
224+ . cloned ( )
225+ . collect :: < Vec < _ > > ( ) ;
226+
224227 // Assumes that non-newtype tuple variants have already been filtered out
225- if variant. fields . iter ( ) . any ( |v| v. ident . is_none ( ) ) {
226- // TODO: Generate newtype structure
227- // This should contain the discriminant plus all fields of the inner structure as a flat structure
228- // TODO: Check for case where discriminant name matches an inner structure field name
229- // We should reject clashes
230- } else {
231- state. types . push ( '\n' ) ;
232- state. types . push_str ( & format ! (
233- " | {interface_name}__{variant_name}" ,
234- interface_name = exported_struct. ident,
235- variant_name = variant. ident,
236- ) )
228+ // TODO: Check for case where discriminant name matches an inner structure field name
229+ // We should reject clashes
230+ match & variant. fields {
231+ syn:: Fields :: Unnamed ( fields) if fields. unnamed . len ( ) > 1 && content_name. is_none ( ) => {
232+ continue ;
233+ }
234+ _ => {
235+ variant_generics_list. push ( variant_generics. clone ( ) ) ;
236+ state. types . push ( '\n' ) ;
237+ state. types . push_str ( & format ! (
238+ " | {interface_name}__{variant_name}{generics}" ,
239+ interface_name = exported_struct. ident,
240+ variant_name = variant. ident,
241+ generics = utils:: format_generics( & variant_generics)
242+ ) )
243+ }
237244 }
238245 }
239246
240247 state. types . push_str ( ";\n " ) ;
241248
242- for variant in exported_struct. variants {
243- // Assumes that non-newtype tuple variants have already been filtered out
244- if !variant. fields . iter ( ) . any ( |v| v. ident . is_none ( ) ) {
245- state. types . push ( '\n' ) ;
246- let comments = utils:: get_comments ( variant. attrs ) ;
247- state. write_comments ( & comments, 0 ) ;
248- state. types . push_str ( & format ! (
249- "type {interface_name}__{variant_name} = " ,
250- interface_name = exported_struct. ident,
251- variant_name = variant. ident,
252- ) ) ;
249+ for ( variant, generics) in exported_struct
250+ . variants
251+ . into_iter ( )
252+ . zip ( variant_generics_list)
253+ {
254+ let generics = utils:: format_generics ( & generics) ;
253255
254- let field_name = if let Some ( casing) = casing {
255- variant. ident . to_string ( ) . to_case ( casing)
256- } else {
257- variant. ident . to_string ( )
258- } ;
259- // add discriminant
260- state. types . push_str ( & format ! (
261- "{{\n {}{}: \" {}\" ;\n " ,
262- utils:: build_indentation( 2 ) ,
263- tag_name,
264- field_name,
265- ) ) ;
266- super :: structs:: process_fields ( variant. fields , state, 2 , casing) ;
267- state. types . push_str ( "};" ) ;
256+ match ( & variant. fields , content_name. as_ref ( ) ) {
257+ // adjacently tagged
258+ ( syn:: Fields :: Unnamed ( fields) , Some ( content_name) ) => {
259+ state. types . push ( '\n' ) ;
260+ let comments = utils:: get_comments ( variant. attrs ) ;
261+ state. write_comments ( & comments, 0 ) ;
262+ state. types . push_str ( & format ! (
263+ "type {interface_name}__{variant_name}{generics} = " ,
264+ interface_name = exported_struct. ident,
265+ variant_name = variant. ident,
266+ ) ) ;
267+ // add discriminant
268+ state. types . push_str ( & format ! (
269+ "{{\n {indent}\" {tag_name}\" : \" {}\" ;\n {indent}\" {content_name}\" : " ,
270+ variant. ident,
271+ indent = utils:: build_indentation( 2 ) ,
272+ ) ) ;
273+ super :: structs:: process_tuple_fields ( fields. clone ( ) , state) ;
274+ state. types . push_str ( ";\n };" ) ;
275+ }
276+ // missing content name, but is a newtype variant
277+ ( syn:: Fields :: Unnamed ( fields) , None ) if fields. unnamed . len ( ) <= 1 => {
278+ state. types . push ( '\n' ) ;
279+ let comments = utils:: get_comments ( variant. attrs ) ;
280+ state. write_comments ( & comments, 0 ) ;
281+ state. types . push_str ( & format ! (
282+ "type {interface_name}__{variant_name}{generics} = " ,
283+ interface_name = exported_struct. ident,
284+ variant_name = variant. ident,
285+ ) ) ;
286+
287+ let field_name = if let Some ( casing) = casing {
288+ variant. ident . to_string ( ) . to_case ( casing)
289+ } else {
290+ variant. ident . to_string ( )
291+ } ;
292+ // add discriminant
293+ state. types . push_str ( & format ! (
294+ "{{\n {}{}: \" {}\" }}" ,
295+ utils:: build_indentation( 2 ) ,
296+ tag_name,
297+ field_name,
298+ ) ) ;
299+
300+ // add the newtype field
301+ let newtype = convert_type ( & fields. unnamed . first ( ) . unwrap ( ) . ty ) ;
302+ state. types . push_str ( & format ! (
303+ " & {content_name}" ,
304+ content_name = newtype. ts_type
305+ ) ) ;
306+ }
307+ // missing content name, and is not a newtype, this is an error case
308+ ( syn:: Fields :: Unnamed ( _) , None ) => {
309+ if crate :: DEBUG . try_get ( ) . is_some_and ( |d : & bool | * d) {
310+ println ! (
311+ "#[tsync] failed for {} variant of enum {}, missing content attribute, skipping" ,
312+ variant. ident,
313+ exported_struct. ident
314+ ) ;
315+ }
316+ continue ;
317+ }
318+ _ => {
319+ state. types . push ( '\n' ) ;
320+ let comments = utils:: get_comments ( variant. attrs ) ;
321+ state. write_comments ( & comments, 0 ) ;
322+ state. types . push_str ( & format ! (
323+ "type {interface_name}__{variant_name}{generics} = " ,
324+ interface_name = exported_struct. ident,
325+ variant_name = variant. ident,
326+ ) ) ;
327+
328+ let field_name = if let Some ( casing) = casing {
329+ variant. ident . to_string ( ) . to_case ( casing)
330+ } else {
331+ variant. ident . to_string ( )
332+ } ;
333+ // add discriminant
334+ state. types . push_str ( & format ! (
335+ "{{\n {}{}: \" {}\" ;\n " ,
336+ utils:: build_indentation( 2 ) ,
337+ tag_name,
338+ field_name,
339+ ) ) ;
340+ super :: structs:: process_fields ( variant. fields , state, 2 , casing, false ) ;
341+ state. types . push_str ( "};" ) ;
342+ }
268343 }
269344 }
270345 state. types . push ( '\n' ) ;
@@ -278,10 +353,11 @@ fn add_externally_tagged_enum(
278353 uses_type_interface : bool ,
279354) {
280355 let export = if uses_type_interface { "" } else { "export " } ;
356+ let generics = utils:: extract_struct_generics ( exported_struct. generics . clone ( ) ) ;
281357 state. types . push_str ( & format ! (
282358 "{export}type {interface_name}{generics} =" ,
283359 interface_name = exported_struct. ident,
284- generics = utils:: extract_struct_generics ( exported_struct . generics. clone ( ) )
360+ generics = utils:: format_generics ( & generics)
285361 ) ) ;
286362
287363 for variant in exported_struct. variants {
@@ -293,17 +369,13 @@ fn add_externally_tagged_enum(
293369 } else {
294370 variant. ident . to_string ( )
295371 } ;
296- // Assumes that non-newtype tuple variants have already been filtered out
297- let is_newtype = variant. fields . iter ( ) . any ( |v| v. ident . is_none ( ) ) ;
298372
299- if is_newtype {
373+ if let syn :: Fields :: Unnamed ( fields ) = & variant . fields {
300374 // add discriminant
301- state. types . push_str ( & format ! ( " | {{ \" {}\" :" , field_name) ) ;
302- for field in variant. fields {
303- state
304- . types
305- . push_str ( & format ! ( " {}" , convert_type( & field. ty) . ts_type, ) ) ;
306- }
375+ state
376+ . types
377+ . push_str ( & format ! ( " | {{ \" {}\" : " , field_name) ) ;
378+ super :: structs:: process_tuple_fields ( fields. clone ( ) , state) ;
307379 state. types . push_str ( " }" ) ;
308380 } else {
309381 // add discriminant
@@ -313,13 +385,11 @@ fn add_externally_tagged_enum(
313385 field_name,
314386 ) ) ;
315387 let prepend;
316- if variant. fields . is_empty ( ) {
317- prepend = "" . into ( ) ;
318- } else {
319- prepend = utils:: build_indentation ( 6 ) ;
320- state. types . push ( '\n' ) ;
321- super :: structs:: process_fields ( variant. fields , state, 8 , casing) ;
322- }
388+
389+ prepend = utils:: build_indentation ( 6 ) ;
390+ state. types . push ( '\n' ) ;
391+ super :: structs:: process_fields ( variant. fields , state, 8 , casing, true ) ;
392+
323393 state
324394 . types
325395 . push_str ( & format ! ( "{}}}\n {}}}" , prepend, utils:: build_indentation( 4 ) ) ) ;
0 commit comments