nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: Aleph-Alpha/ts-rs
Pull request #73 (2022-01-03 10:16:42)

export_to supports output to directories
Related to discussion in #71.

Adds support for output to directories when the export_to path contains a trailing `/` character.


Patch:
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 20081ad..9de50c8 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -47,10 +47,16 @@ impl DerivedTS {
     }
 
     fn into_impl(self, rust_ty: Ident, generics: Generics) -> TokenStream {
-        let export_to = self
-            .export_to
-            .clone()
-            .unwrap_or_else(|| format!("bindings/{}.ts", self.name));
+        let export_to = match &self.export_to {
+            Some(dirname) if dirname.ends_with('/') => {
+                format!("{}{}.ts", dirname, self.name)
+            }
+            Some(filename) => filename.clone(),
+            None => {
+                format!("bindings/{}.ts", self.name)
+            }
+        };
+
         let export = match self.export {
             true => Some(self.generate_export_test(&rust_ty, &generics)),
             false => None,


Test patch:
diff --git a/ts-rs/tests/export_manually.rs b/ts-rs/tests/export_manually.rs
index 0ce4c0c..0f75697 100644
--- a/ts-rs/tests/export_manually.rs
+++ b/ts-rs/tests/export_manually.rs
@@ -12,6 +12,14 @@ struct User {
     active: bool,
 }
 
+#[derive(TS)]
+#[ts(export_to = "export_here_dir_test/")]
+struct UserDir {
+    name: String,
+    age: i32,
+    active: bool,
+}
+
 #[test]
 fn export_manually() {
     User::export().unwrap();
@@ -32,3 +40,24 @@ fn export_manually() {
 
     assert_eq!(actual_content, expected_content);
 }
+
+#[test]
+fn export_manually_dir() {
+    UserDir::export().unwrap();
+
+    let expected_content = if cfg!(feature = "format") {
+        concat!(
+            "export interface UserDir {\n",
+            "  name: string;\n",
+            "  age: number;\n",
+            "  active: boolean;\n",
+            "}\n"
+        )
+    } else {
+        concat!("\nexport interface UserDir { name: string, age: number, active: boolean, }")
+    };
+
+    let actual_content = fs::read_to_string("export_here_dir_test/UserDir.ts").unwrap();
+
+    assert_eq!(actual_content, expected_content);
+}
\ No newline at end of file
Repository: Aleph-Alpha/ts-rs
Pull request #99 (2022-05-01 14:53:03)

Wrap invalid field names in quotes
If a struct field name would be an invalid Typescript ident,
the field name is wrapped in quotes.



Patch:
diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs
index 751ea91..c942aaa 100644
--- a/macros/src/types/named.rs
+++ b/macros/src/types/named.rs
@@ -6,7 +6,7 @@ use crate::{
     attr::{FieldAttr, Inflection, StructAttr},
     deps::Dependencies,
     types::generics::{format_generics, format_type},
-    utils::to_ts_ident,
+    utils::{raw_name_to_ts_field, to_ts_ident},
     DerivedTS,
 };
 
@@ -107,9 +107,10 @@ fn format_field(
         (None, Some(rn)) => rn.apply(&field_name),
         (None, None) => field_name,
     };
+    let valid_name = raw_name_to_ts_field(name);
 
     formatted_fields.push(quote! {
-        format!("{}{}: {},", #name, #optional_annotation, #formatted_ty)
+        format!("{}{}: {},", #valid_name, #optional_annotation, #formatted_ty)
     });
 
     Ok(())
diff --git a/macros/src/utils.rs b/macros/src/utils.rs
index 373db55..c02ba8f 100644
--- a/macros/src/utils.rs
+++ b/macros/src/utils.rs
@@ -55,6 +55,18 @@ pub fn to_ts_ident(ident: &Ident) -> String {
     }
 }
 
+/// Convert an arbitrary name to a valid Typescript field name.
+///
+/// If the name contains special characters it will be wrapped in quotes.
+pub fn raw_name_to_ts_field(value: String) -> String {
+    let has_invalid_chars = value.chars().any(|c| !c.is_alphanumeric());
+    if has_invalid_chars {
+        format!(r#""{value}""#)
+    } else {
+        value
+    }
+}
+
 /// Parse all `#[ts(..)]` attributes from the given slice.
 pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result<impl Iterator<Item = A>>
 where


Test patch:
diff --git a/ts-rs/tests/struct_rename.rs b/ts-rs/tests/struct_rename.rs
index 6c2059e..7b0be64 100644
--- a/ts-rs/tests/struct_rename.rs
+++ b/ts-rs/tests/struct_rename.rs
@@ -9,7 +9,14 @@ struct Rename {
     b: i32,
 }
 
+#[derive(serde::Serialize, TS)]
+struct RenameSerdeSpecialChar {
+    #[serde(rename = "a/b")]
+    b: i32,
+}
+
 #[test]
 fn test() {
-    assert_eq!(Rename::inline(), "{ A: number, B: number, }")
+    assert_eq!(Rename::inline(), "{ A: number, B: number, }");
+    assert_eq!(RenameSerdeSpecialChar::inline(), r#"{ "a/b": number, }"#);
 }
Repository: Aleph-Alpha/ts-rs
Pull request #114 (2022-08-18 13:01:17)

Implement `char` as a primitive
Not much to see here, just added `char` as a primitive binding to `string`.


Patch:
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index 32ea046..7429a06 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -575,7 +575,7 @@ impl_primitives! {
     u8, i8, u16, i16, u32, i32, f32, f64, usize, isize => "number",
     u64, i64, u128, i128 => "bigint",
     bool => "boolean",
-    Path, PathBuf, String, &'static str => "string",
+    char, Path, PathBuf, String, &'static str => "string",
     () => "null"
 }
 #[rustfmt::skip]


Test patch:
diff --git a/ts-rs/tests/simple.rs b/ts-rs/tests/simple.rs
index b1ac636..038b584 100644
--- a/ts-rs/tests/simple.rs
+++ b/ts-rs/tests/simple.rs
@@ -11,12 +11,14 @@ struct Simple {
     c: (i32, String, RefCell<i32>),
     d: Vec<String>,
     e: Option<String>,
+    f: char,
+    g: Option<char>,
 }
 
 #[test]
 fn test_def() {
     assert_eq!(
         Simple::inline(),
-        "{ a: number, b: string, c: [number, string, number], d: Array<string>, e: string | null, }"
+        "{ a: number, b: string, c: [number, string, number], d: Array<string>, e: string | null, f: string, g: string | null, }"
     )
 }
Repository: Aleph-Alpha/ts-rs
Pull request #120 (2022-09-30 09:43:50)

TypeGroup support in `extract_type_args`
The issue this PR fixes:

It turns out that field type is wrapped into a `Type::Group` in some cases (e.g when struct definition is generated via macro) and `extract_type_args` is unable to properly extract types.


Patch:
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index 8e715e0..3f90370 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -1,6 +1,8 @@
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
-use syn::{GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeTuple};
+use syn::{
+    GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup, TypeTuple,
+};
 
 use crate::{attr::StructAttr, deps::Dependencies};
 
@@ -96,6 +98,7 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
 
 fn extract_type_args(ty: &Type) -> Option<Vec<&Type>> {
     let last_segment = match ty {
+        Type::Group(TypeGroup { elem, .. }) => return extract_type_args(elem),
         Type::Path(type_path) => type_path.path.segments.last(),
         _ => None,
     }?;


Test patch:
diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs
index 93bd709..e9b46bb 100644
--- a/ts-rs/tests/generics.rs
+++ b/ts-rs/tests/generics.rs
@@ -39,8 +39,29 @@ struct Container {
     baz: Box<BTreeMap<String, Rc<Generic<String>>>>,
 }
 
+macro_rules! declare {
+    ($(#[$meta:meta])* $name:ident { $($fident:ident: $t:ty),+ $(,)? }) => {
+        $(#[$meta])*
+        struct $name {
+            $(pub $fident: $t),+
+        }
+    }
+}
+
+declare! {
+    #[derive(TS)]
+    TypeGroup {
+        foo: Vec<Container>,
+    }
+}
+
 #[test]
 fn test() {
+    assert_eq!(
+        TypeGroup::decl(),
+        "interface TypeGroup { foo: Array<Container>, }",
+    );
+
     assert_eq!(
         Generic::<()>::decl(),
         "interface Generic<T> { value: T, values: Array<T>, }"
Repository: Aleph-Alpha/ts-rs
Pull request #155 (2023-06-30 13:09:38)

Implement time Duration


Patch:
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index bec2c36..7c3ecdb 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -142,6 +142,7 @@ use std::{
     },
     ops::{Range, RangeInclusive},
     path::{Path, PathBuf},
+    time::Duration
 };
 
 pub use ts_rs_macros::TS;
@@ -576,6 +577,8 @@ mod bytes {
     impl_shadow!(as Vec<u8>: impl TS for bytes::BytesMut);
 }
 
+impl_primitives!(Duration => "string");
+
 impl_primitives! {
     u8, i8, NonZeroU8, NonZeroI8,
     u16, i16, NonZeroU16, NonZeroI16,


Test patch:
diff --git a/ts-rs/tests/time.rs b/ts-rs/tests/time.rs
new file mode 100644
index 0000000..85cb048
--- /dev/null
+++ b/ts-rs/tests/time.rs
@@ -0,0 +1,16 @@
+use ts_rs::TS;
+use std::time::Duration;
+
+#[test]
+fn time() {
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct Time {
+        duration: Duration
+    }
+
+    assert_eq!(
+        Time::decl(),
+        "interface Time { duration: string, }"
+    )
+}
\ No newline at end of file
Repository: Aleph-Alpha/ts-rs
Pull request #164 (2023-08-15 06:54:10)

Check for empty tuple in `format_type`
Empty tuple fields `()` are currently identified as `never[]` but are serialized into `null`. This PR fixes this by checking for an empty tuple at `format_type`. This can come up in generics where `T = ()`.


Patch:
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index 0396d92..290659e 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -71,6 +71,13 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
         // same goes for a tuple (`(A, B, C)`) - it doesn't have a type arg, so we handle it
         // explicitly here.
         Type::Tuple(tuple) => {
+            if tuple.elems.is_empty() {
+                // empty tuples `()` should be treated as `null`
+                return super::unit::null(&StructAttr::default(), "")
+                    .unwrap()
+                    .inline;
+            }
+
             // we convert the tuple field to a struct: `(A, B, C)` => `struct A(A, B, C)`
             let tuple_struct = super::type_def(
                 &StructAttr::default(),


Test patch:
diff --git a/ts-rs/tests/unit.rs b/ts-rs/tests/unit.rs
index db2cdba..c6fdcf5 100644
--- a/ts-rs/tests/unit.rs
+++ b/ts-rs/tests/unit.rs
@@ -14,9 +14,14 @@ struct Unit2 {}
 #[derive(TS)]
 struct Unit3();
 
+// serde_json serializes this to `null`, so it's TS type is `null` as well.
+#[derive(TS)]
+struct Unit4(());
+
 #[test]
 fn test() {
     assert_eq!("type Unit = null;", Unit::decl());
     assert_eq!("type Unit2 = Record<string, never>;", Unit2::decl());
     assert_eq!("type Unit3 = never[];", Unit3::decl());
+    assert_eq!("type Unit4 = null;", Unit4::decl());
 }
Repository: Aleph-Alpha/ts-rs
Pull request #181 (2023-12-10 13:17:11)

fix: Include borrowed types' type arguments in typescript
Borrowed types' type arguments weren't previously being included in the typescript export.
e.g.
```rust
struct A<'a> {
    b: &'a Vec<usize>,
}
```
Would be exported as: 
```typescript
interface A { b: Array, }
```


With this pr it's exported correctly as:
```typescript
interface A { b: Array<number>, }
```



Patch:
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index 290659e..cea3e3c 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -1,7 +1,8 @@
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 use syn::{
-    GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup, TypeTuple,
+    GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup,
+    TypeReference, TypeTuple,
 };
 
 use crate::{attr::StructAttr, deps::Dependencies};
@@ -109,7 +110,9 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
 
 fn extract_type_args(ty: &Type) -> Option<Vec<&Type>> {
     let last_segment = match ty {
-        Type::Group(TypeGroup { elem, .. }) => return extract_type_args(elem),
+        Type::Group(TypeGroup { elem, .. }) | Type::Reference(TypeReference { elem, .. }) => {
+            return extract_type_args(elem)
+        }
         Type::Path(type_path) => type_path.path.segments.last(),
         _ => None,
     }?;


Test patch:
diff --git a/ts-rs/tests/lifetimes.rs b/ts-rs/tests/lifetimes.rs
index 75bdb64..835610e 100644
--- a/ts-rs/tests/lifetimes.rs
+++ b/ts-rs/tests/lifetimes.rs
@@ -10,3 +10,25 @@ fn contains_borrow() {
 
     assert_eq!(S::decl(), "interface S { s: string, }")
 }
+
+#[test]
+fn contains_borrow_type_args() {
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct B<'a, T: 'a> {
+        a: &'a T,
+    }
+
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct A<'a> {
+        a: &'a &'a &'a Vec<u32>,                        //Multiple References
+        b: &'a Vec<B<'a, u32>>,                         //Nesting
+        c: &'a std::collections::HashMap<String, bool>, //Multiple type args
+    }
+
+    assert_eq!(
+        A::decl(),
+        "interface A { a: Array<number>, b: Array<B<number>>, c: Record<string, boolean>, }"
+    );
+}
Repository: Aleph-Alpha/ts-rs
Pull request #215 (2024-01-29 21:01:32)

Fix bug  in #212
`Vec<string>` was turning into `Array` using `inline` instead of `name` should resolve the issue


Patch:
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index beacc29..5bf26ff 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -64,7 +64,7 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
         }
 
         return quote!(
-            match <#generic_ident>::name().as_str() {
+            match <#generic_ident>::inline().as_str() {
                 // When exporting a generic, the default type used is `()`,
                 // which gives "null" when calling `.name()`. In this case, we
                 // want to preserve the type param's identifier as the name used


Test patch:
diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs
index 961fffc..703868e 100644
--- a/ts-rs/tests/generics.rs
+++ b/ts-rs/tests/generics.rs
@@ -147,6 +147,7 @@ fn generic_struct() {
 }
 
 #[test]
+// https://github.com/Aleph-Alpha/ts-rs/issues/56 TODO
 fn inline() {
     #[derive(TS)]
     struct Generic<T> {
@@ -159,13 +160,13 @@ fn inline() {
         #[ts(inline)]
         gi: Generic<String>,
         #[ts(flatten)]
-        t: Generic<String>,
+        t: Generic<Vec<String>>,
     }
 
     assert_eq!(Generic::<()>::decl(), "type Generic<T> = { t: T, }");
     assert_eq!(
         Container::decl(),
-        "type Container = { g: Generic<string>, gi: { t: string, }, t: string, }"
+        "type Container = { g: Generic<string>, gi: { t: string, }, t: Array<string>, }"
     );
 }
Repository: Aleph-Alpha/ts-rs
Pull request #216 (2024-01-30 18:07:34)

Inline generics with default types works, as long as there are no trait bounds
I found that `#[export]` doesn't magically figure out a type for trait bounds as I thought was happening (I saw the tests with trait bounds use `decl::<&'static str>` and had no idea how that was happening) and instead **always** uses `()`

This means that checking for `"null"` works with default types, and would work with bounds if we could implement foreign traits on `()`, which sadly we can't due to the orphan rule.

I opened a separate issue for this #214


Patch:
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index 5bf26ff..13de906 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -59,7 +59,7 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
         let generic_ident = generic.ident.clone();
         let generic_ident_str = generic_ident.to_string();
 
-        if !generic.bounds.is_empty() || generic.default.is_some() {
+        if !generic.bounds.is_empty() {
             return quote!(#generic_ident_str.to_owned());
         }
 


Test patch:
diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs
index 703868e..cba6ec7 100644
--- a/ts-rs/tests/generics.rs
+++ b/ts-rs/tests/generics.rs
@@ -147,7 +147,6 @@ fn generic_struct() {
 }
 
 #[test]
-// https://github.com/Aleph-Alpha/ts-rs/issues/56 TODO
 fn inline() {
     #[derive(TS)]
     struct Generic<T> {
@@ -173,6 +172,8 @@ fn inline() {
 #[test]
 #[ignore = "We haven't figured out how to inline generics with bounds yet"]
 fn inline_with_bounds() {
+    todo!("FIX ME: https://github.com/Aleph-Alpha/ts-rs/issues/214");
+
     #[derive(TS)]
     struct Generic<T: ToString> {
         t: T,
@@ -181,13 +182,16 @@ fn inline_with_bounds() {
     #[derive(TS)]
     struct Container {
         g: Generic<String>,
+
         #[ts(inline)]
         gi: Generic<String>,
+
         #[ts(flatten)]
         t: Generic<u32>,
     }
 
     assert_eq!(Generic::<&'static str>::decl(), "type Generic<T> = { t: T, }");
+    //                   ^^^^^^^^^^^^ Replace with something else
     assert_eq!(
         Container::decl(),
         "type Container = { g: Generic<string>, gi: { t: string, }, t: number, }"
@@ -196,7 +200,6 @@ fn inline_with_bounds() {
 }
 
 #[test]
-#[ignore = "We haven't figured out how to inline generics with defaults yet"]
 fn inline_with_default() {
     #[derive(TS)]
     struct Generic<T = String> {
@@ -206,17 +209,18 @@ fn inline_with_default() {
     #[derive(TS)]
     struct Container {
         g: Generic<String>,
+        
         #[ts(inline)]
         gi: Generic<String>,
+
         #[ts(flatten)]
         t: Generic<u32>,
     }
 
-    assert_eq!(Generic::<&'static str>::decl(), "type Generic<T = string> = { t: T, }");
+    assert_eq!(Generic::<()>::decl(), "type Generic<T = string> = { t: T, }");
     assert_eq!(
         Container::decl(),
         "type Container = { g: Generic<string>, gi: { t: string, }, t: number, }"
-        // Actual output: { g: Generic<string>, gi: { t: T, }, t: T, }
     );
 }
Repository: Aleph-Alpha/ts-rs
Pull request #244 (2024-02-29 17:42:14)

Parse `#[ts(tag = "...")]` on structs
This PR adds a `#[ts(...)]` equivalent to `#[serde(tag = "...")]` on structs


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e3844a..f0ad87c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@
 - Support `#[serde(untagged)]` on individual enum variants ([#226](https://github.com/Aleph-Alpha/ts-rs/pull/226))
 - Support for `#[serde(rename_all_fields = "...")]` ([#225](https://github.com/Aleph-Alpha/ts-rs/pull/225))
 - Export Rust doc comments/attributes on structs/enums as TSDoc strings ([#187](https://github.com/Aleph-Alpha/ts-rs/pull/187))
+- Implement `#[ts(...)]` equivalent for `#[serde(tag = "...")]` being used on a struct with named fields ([#244](https://github.com/Aleph-Alpha/ts-rs/pull/244))
 
 ### Fixes
 
diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs
index 1177aa9..0e37e1f 100644
--- a/macros/src/attr/struct.rs
+++ b/macros/src/attr/struct.rs
@@ -73,6 +73,7 @@ impl_parse! {
     StructAttr(input, out) {
         "rename" => out.rename = Some(parse_assign_str(input)?),
         "rename_all" => out.rename_all = Some(parse_assign_str(input).and_then(Inflection::try_from)?),
+        "tag" => out.tag = Some(parse_assign_str(input)?),
         "export" => out.export = true,
         "export_to" => out.export_to = Some(parse_assign_str(input)?)
     }


Test patch:
diff --git a/ts-rs/tests/struct_tag.rs b/ts-rs/tests/struct_tag.rs
index 356e3c2..752a325 100644
--- a/ts-rs/tests/struct_tag.rs
+++ b/ts-rs/tests/struct_tag.rs
@@ -1,17 +1,19 @@
 #![allow(dead_code)]
 
+#[cfg(feature = "serde-compat")]
 use serde::Serialize;
 use ts_rs::TS;
 
-#[derive(TS, Serialize)]
-#[serde(tag = "type")]
+#[derive(TS)]
+#[cfg_attr(feature = "serde-compat", derive(Serialize))]
+#[cfg_attr(feature = "serde-compat", serde(tag = "type"))]
+#[cfg_attr(not(feature = "serde-compat"), ts(tag = "type"))]
 struct TaggedType {
     a: i32,
     b: i32,
 }
 
 #[test]
-#[cfg(feature = "serde-compat")]
 fn test() {
     assert_eq!(
         TaggedType::inline(),
@@ -19,8 +21,3 @@ fn test() {
     )
 }
 
-#[test]
-#[cfg(not(feature = "serde-compat"))]
-fn test() {
-    assert_eq!(TaggedType::inline(), "{ a: number, b: number, }")
-}
Repository: Aleph-Alpha/ts-rs
Pull request #310 (2024-04-18 12:22:30)

Fix missing case in #299
## Goal

In #299, the `type_as_infer` (now called `replace_underscore`) function doesn't handle a type `Type::Path(TypePath { qself: Some(_), .. })` due to a match on `None` for the `qself` field, this PR is a quick fix for that

This allows the following use of `as`:
```rs
#[ts(as = "<_ as Bar>::Baz")]
foo: String
```

## Changes

Removed the match on `qself: None` and called `replace_underscore` on `qself.ty`

## Checklist

- [x] I have followed the steps listed in the [Contributing guide](https://github.com/Aleph-Alpha/ts-rs/blob/main/CONTRIBUTING.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 1417777..dd8552c 100644
--- a/macros/src/attr/field.rs
+++ b/macros/src/attr/field.rs
@@ -1,5 +1,5 @@
 use syn::{
-    AngleBracketedGenericArguments, Attribute, Field, GenericArgument, Ident, PathArguments,
+    AngleBracketedGenericArguments, Attribute, Field, GenericArgument, Ident, PathArguments, QSelf,
     Result, ReturnType, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, TypeReference,
     TypeSlice, TypeTuple,
 };
@@ -232,7 +232,11 @@ fn replace_underscore(ty: &mut Type, with: &Type) {
                 replace_underscore(elem, with);
             }
         }
-        Type::Path(TypePath { path, qself: None }) => {
+        Type::Path(TypePath { path, qself }) => {
+            if let Some(QSelf { ty, .. }) = qself {
+                replace_underscore(ty, with);
+            }
+
             for segment in &mut path.segments {
                 match &mut segment.arguments {
                     PathArguments::None => (),


Test patch:
diff --git a/ts-rs/tests/integration/infer_as.rs b/ts-rs/tests/integration/infer_as.rs
index 459aaac..09fa4c5 100644
--- a/ts-rs/tests/integration/infer_as.rs
+++ b/ts-rs/tests/integration/infer_as.rs
@@ -2,14 +2,28 @@
 
 use ts_rs::TS;
 
+trait Bar {
+    type Baz;
+}
+
+impl Bar for String {
+    type Baz = i32;
+}
+
 #[derive(TS)]
 #[ts(export)]
 struct Foo {
     #[ts(optional, as = "Option<_>")]
     my_optional_bool: bool,
+
+    #[ts(as = "<_ as Bar>::Baz")]
+    q_self: String,
 }
 
 #[test]
 fn test() {
-    assert_eq!(Foo::inline(), "{ my_optional_bool?: boolean, }");
+    assert_eq!(
+        Foo::inline(),
+        "{ my_optional_bool?: boolean, q_self: number, }"
+    );
 }