Repository: Aleph-Alpha/ts-rs
Pull request #271 (2024-03-16 15:25:16)

`#[ts(as = "..")]` - Allow any type, not just path
## Goal

Allows the use of any type as override, e.g `#[ts(as = "(i32, SomeType, i32)")]`.  

## Checklist

- [x] I have followed the steps listed in the [Contributing guide](https://github.com/Aleph-Alpha/ts-rs/blob/main/CHANGELOG.md).
- [x] If necessary, I have added documentation related to the changes made.
- [x] I have added or updated the tests related to the changes made.



Patch:
diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs
index b4440a4..7345eb4 100644
--- a/macros/src/attr/field.rs
+++ b/macros/src/attr/field.rs
@@ -1,11 +1,11 @@
-use syn::{Attribute, Ident, Path, Result};
+use syn::{Attribute, Ident, Result, Type};
 
 use super::{parse_assign_from_str, parse_assign_str};
 use crate::utils::{parse_attrs, parse_docs};
 
 #[derive(Default)]
 pub struct FieldAttr {
-    pub type_as: Option<Path>,
+    pub type_as: Option<Type>,
     pub type_override: Option<String>,
     pub rename: Option<String>,
     pub inline: bool,
diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs
index c21c679..8635f6e 100644
--- a/macros/src/types/named.rs
+++ b/macros/src/types/named.rs
@@ -1,5 +1,5 @@
 use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
+use quote::{quote};
 use syn::{spanned::Spanned, Field, FieldsNamed, GenericArgument, PathArguments, Result, Type};
 
 use crate::{
@@ -43,6 +43,8 @@ pub(crate) fn named(attr: &StructAttr, name: &str, fields: &FieldsNamed) -> Resu
     };
 
     Ok(DerivedTS {
+        // the `replace` combines `{ ... } & { ... }` into just one `{ ... }`. Not necessary, but it
+        // results in simpler type definitions.
         inline: quote!(#inline.replace(" } & { ", " ")),
         inline_flattened: Some(quote!(format!("{{ {} }}", #fields))),
         docs: attr.docs.clone(),
@@ -54,7 +56,7 @@ pub(crate) fn named(attr: &StructAttr, name: &str, fields: &FieldsNamed) -> Resu
     })
 }
 
-// build an expresion which expands to a string, representing a single field of a struct.
+// build an expression which expands to a string, representing a single field of a struct.
 //
 // formatted_fields will contain all the fields that do not contain the flatten
 // attribute, in the format
@@ -90,11 +92,7 @@ fn format_field(
         syn_err_spanned!(field; "`type` is not compatible with `as`")
     }
 
-    let parsed_ty = if let Some(ref type_as) = type_as {
-        syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
-    } else {
-        field.ty.clone()
-    };
+    let parsed_ty = type_as.as_ref().unwrap_or(&field.ty).clone();
 
     let (ty, optional_annotation) = match optional {
         Optional {
diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs
index 4d29fbd..7ca1b3c 100644
--- a/macros/src/types/newtype.rs
+++ b/macros/src/types/newtype.rs
@@ -1,5 +1,5 @@
-use quote::{quote, ToTokens};
-use syn::{FieldsUnnamed, Result, Type};
+use quote::{quote};
+use syn::{FieldsUnnamed, Result};
 
 use crate::{
     attr::{FieldAttr, StructAttr},
@@ -42,11 +42,7 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) ->
         syn_err_spanned!(fields; "`type` is not compatible with `as`")
     }
 
-    let inner_ty = if let Some(ref type_as) = type_as {
-        syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
-    } else {
-        inner.ty.clone()
-    };
+    let inner_ty = type_as.as_ref().unwrap_or(&inner.ty).clone();
 
     let mut dependencies = Dependencies::default();
 
diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs
index b815b92..4272ca3 100644
--- a/macros/src/types/tuple.rs
+++ b/macros/src/types/tuple.rs
@@ -1,6 +1,6 @@
 use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
-use syn::{Field, FieldsUnnamed, Result, Type};
+use quote::{quote};
+use syn::{Field, FieldsUnnamed, Result};
 
 use crate::{
     attr::{FieldAttr, StructAttr},
@@ -59,11 +59,7 @@ fn format_field(
         return Ok(());
     }
 
-    let ty = if let Some(ref type_as) = type_as {
-        syn::parse_str::<Type>(&type_as.to_token_stream().to_string())?
-    } else {
-        field.ty.clone()
-    };
+    let ty = type_as.as_ref().unwrap_or(&field.ty).clone();
 
     if type_as.is_some() && type_override.is_some() {
         syn_err_spanned!(field; "`type` is not compatible with `as`")


Test patch:
diff --git a/ts-rs/tests/leading_colon.rs b/ts-rs/tests/leading_colon.rs
index 6ee31cd..cf0eabd 100644
--- a/ts-rs/tests/leading_colon.rs
+++ b/ts-rs/tests/leading_colon.rs
@@ -6,5 +6,5 @@ mod ts_rs {}
 
 #[derive(TS)]
 struct Foo {
-    x: u32
+    x: u32,
 }
diff --git a/ts-rs/tests/type_as.rs b/ts-rs/tests/type_as.rs
index 65e653c..4b55bb1 100644
--- a/ts-rs/tests/type_as.rs
+++ b/ts-rs/tests/type_as.rs
@@ -26,13 +26,20 @@ struct Override {
     // here, 'as' just behaves like 'type' (though it adds a dependency!)
     #[ts(as = "ExternalTypeDef")]
     y: Unsupported,
+    #[ts(as = "(i32, ExternalTypeDef, i32)")]
+    z: Unsupported,
 }
 
 #[test]
 fn struct_properties() {
     assert_eq!(
         Override::inline(),
-        "{ a: number, x: { a: number, b: number, c: number, }, y: ExternalTypeDef, }"
+        "{ \
+           a: number, \
+           x: { a: number, b: number, c: number, }, \
+           y: ExternalTypeDef, \
+           z: [number, ExternalTypeDef, number], \
+        }"
     );
     assert!(Override::dependencies()
         .iter()