Repository: BrynCooke/buildstructor (language: rust)

Issue:
Support returning Self
Currently the fully qualified type name is expected.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46d9634..439f534 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,15 +4,18 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## 0.1.4 - unreleased
+## 0.1.5 - unreleased
 ### Added
 [#9](https://github.com/BrynCooke/buildstructor/issues/9) Add `*_new` support
 Any method named `new` or has a suffix `_new` will create a builder.
 Builders methods are named appropriately. e.g. `try_new` -> `try_build`.
 ### Fixed
-[#11](https://github.com/BrynCooke/buildstructor/issues/1) Fix multiple builders in the same module.
+[#11](https://github.com/BrynCooke/buildstructor/issues/11) Fix multiple builders in the same module.
 Removes the use of wildcard imports to builder modules to fix name clashes. 
 
+[#8](https://github.com/BrynCooke/buildstructor/issues/8) Fix constructors that return `Self`
+`Self` on builders needed to be converted to the target type. 
+
 ## 0.1.4 - 2022-03-30
 ### Fixed
 [#6](https://github.com/BrynCooke/buildstructor/issues/6) Fix generics on collections.
diff --git a/examples/returns_self.rs b/examples/returns_self.rs
new file mode 100644
index 0000000..f5154f3
--- /dev/null
+++ b/examples/returns_self.rs
@@ -0,0 +1,17 @@
+use buildstructor::builder;
+
+pub struct ReturnsSelf {
+    simple: usize,
+}
+
+#[builder]
+impl ReturnsSelf {
+    fn new(simple: usize) -> Self {
+        Self { simple }
+    }
+}
+
+fn main() {
+    let generic = ReturnsSelf::builder().simple(2).build();
+    assert_eq!(generic.simple, 2);
+}
diff --git a/src/buildstructor/codegen.rs b/src/buildstructor/codegen.rs
index 3d24b49..1fd8f58 100644
--- a/src/buildstructor/codegen.rs
+++ b/src/buildstructor/codegen.rs
@@ -386,4 +386,9 @@ mod tests {
     fn collection_option_test() {
         assert_codegen!(collections_option_test_case());
     }
+
+    #[test]
+    fn returns_self_test() {
+        assert_codegen!(returns_self_test_case());
+    }
 }
diff --git a/src/buildstructor/lower.rs b/src/buildstructor/lower.rs
index 0e7a7b8..ea38baa 100644
--- a/src/buildstructor/lower.rs
+++ b/src/buildstructor/lower.rs
@@ -46,63 +46,19 @@ pub enum FieldType {
 
 pub fn lower(model: ConstrutorModel) -> Result<Ir> {
     Ok(Ir {
-        vis: model.vis,
+        vis: model.vis.clone(),
         module_name: format_ident!(
             "__{}_{}_builder",
             model.ident.to_string().to_lowercase(),
             model.constructor_name.to_string().to_lowercase()
         ),
         target_name: model.ident.clone(),
-        builder_name: format_ident!("__{}Builder", model.ident),
+        builder_name: format_ident!("__{}Builder", model.ident.clone()),
         constructor_method_name: model.constructor_name.clone(),
-        builder_method_name: format_ident!(
-            "{}builder",
-            model
-                .constructor_name
-                .to_string()
-                .strip_suffix("new")
-                .expect("already checked that the method ends with new, qed")
-        ),
-        builder_fields: model
-            .args
-            .iter()
-            .filter_map(|f| match f {
-                FnArg::Typed(t) => {
-                    let ident = try_match!(&*t.pat, Pat::Ident(x)=>x).ok()?;
-                    let field_type = field_type(&*t.ty);
-                    let args = t.ty.generic_args();
-                    let (key_type, value_type, collection_type) = match (
-                        &field_type,
-                        args.and_then(|args| args.iter().next()),
-                        args.and_then(|args| args.iter().nth(1)),
-                    ) {
-                        (
-                            FieldType::Vec | FieldType::Set,
-                            Some(GenericArgument::Type(collection_type)),
-                            None,
-                        ) => (None, None, Some(collection_type.clone())),
-                        (
-                            FieldType::Map,
-                            Some(GenericArgument::Type(key_type)),
-                            Some(GenericArgument::Type(value_type)),
-                        ) => (Some(key_type.clone()), Some(value_type.clone()), None),
-                        _ => (None, None, None),
-                    };
-
-                    Some(BuilderField {
-                        ty: *t.ty.clone(),
-                        name: ident.ident.clone(),
-                        field_type,
-                        key_type,
-                        value_type,
-                        collection_type,
-                    })
-                }
-                FnArg::Receiver(_) => None,
-            })
-            .collect(),
+        builder_method_name: builder_method_name(&model),
+        builder_fields: builder_fields(&model),
         constructor_name: format_ident!("{}Constructor", model.ident.to_string()),
-        return_type: model.output,
+        return_type: builder_return_type(model.output, model.ident),
         is_async: model.is_async,
         generics: model.generics,
         builder_generics: Ir::builder_generics(),
@@ -110,6 +66,68 @@ pub fn lower(model: ConstrutorModel) -> Result<Ir> {
     })
 }
 
+fn builder_return_type(mut return_type: ReturnType, target: Ident) -> ReturnType {
+    if let ReturnType::Type(_, ty) = &mut return_type {
+        let self_type = Box::new(Type::Path(format_ident!("Self").to_type_path()));
+        if ty == &self_type {
+            *ty = Box::new(Type::Path(target.to_type_path()));
+        }
+    }
+    return_type
+}
+
+fn builder_fields(model: &ConstrutorModel) -> Vec<BuilderField> {
+    model
+        .args
+        .iter()
+        .filter_map(|f| match f {
+            FnArg::Typed(t) => {
+                let ident = try_match!(&*t.pat, Pat::Ident(x)=>x).ok()?;
+                let field_type = field_type(&*t.ty);
+                let args = t.ty.generic_args();
+                let (key_type, value_type, collection_type) = match (
+                    &field_type,
+                    args.and_then(|args| args.iter().next()),
+                    args.and_then(|args| args.iter().nth(1)),
+                ) {
+                    (
+                        FieldType::Vec | FieldType::Set,
+                        Some(GenericArgument::Type(collection_type)),
+                        None,
+                    ) => (None, None, Some(collection_type.clone())),
+                    (
+                        FieldType::Map,
+                        Some(GenericArgument::Type(key_type)),
+                        Some(GenericArgument::Type(value_type)),
+                    ) => (Some(key_type.clone()), Some(value_type.clone()), None),
+                    _ => (None, None, None),
+                };
+
+                Some(BuilderField {
+                    ty: *t.ty.clone(),
+                    name: ident.ident.clone(),
+                    field_type,
+                    key_type,
+                    value_type,
+                    collection_type,
+                })
+            }
+            FnArg::Receiver(_) => None,
+        })
+        .collect()
+}
+
+fn builder_method_name(model: &ConstrutorModel) -> Ident {
+    format_ident!(
+        "{}builder",
+        model
+            .constructor_name
+            .to_string()
+            .strip_suffix("new")
+            .expect("already checked that the method ends with new, qed")
+    )
+}
+
 fn field_type(ty: &Type) -> FieldType {
     match ty.raw_ident() {
         Some(f) if f == format_ident!("Option") => FieldType::Optional,
diff --git a/src/buildstructor/mod.rs b/src/buildstructor/mod.rs
index 9bc69f4..4182fe1 100644
--- a/src/buildstructor/mod.rs
+++ b/src/buildstructor/mod.rs
@@ -171,4 +171,15 @@ mod tests {
             }
         )
     }
+
+    pub fn returns_self_test_case() -> Ast {
+        parse_quote!(
+            #[builder]
+            impl Foo {
+                fn new(simple: usize) -> Self {
+                    Self { simple }
+                }
+            }
+        )
+    }
 }


Test patch:
diff --git a/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__returns_self_test.snap b/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__returns_self_test.snap
new file mode 100644
index 0000000..549ffd3
--- /dev/null
+++ b/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__returns_self_test.snap
@@ -0,0 +1,66 @@
+---
+source: src/buildstructor/codegen.rs
+assertion_line: 392
+expression: output
+---
+impl Foo {
+    fn builder() -> __foo_new_builder::__FooBuilder<
+            (__foo_new_builder::__Required<usize>,),
+        > {
+        __foo_new_builder::new()
+    }
+}
+mod __foo_new_builder {
+    use super::*;
+    pub fn new() -> __FooBuilder<(__foo_new_builder::__Required<usize>,)> {
+        __FooBuilder {
+            fields: (__required(),),
+            phantom: core::default::Default::default(),
+        }
+    }
+    pub struct __Required<T> {
+        phantom: std::marker::PhantomData<T>,
+    }
+    pub struct __Optional<T> {
+        lazy: Option<T>,
+    }
+    pub struct __Set<T> {
+        value: T,
+    }
+    fn __set<T>(value: T) -> __Set<T> {
+        __Set { value }
+    }
+    fn __required<T>() -> __Required<T> {
+        __Required::<T> {
+            phantom: core::default::Default::default(),
+        }
+    }
+    fn __optional<T>() -> __Optional<T> {
+        __Optional::<T> { lazy: None }
+    }
+    impl<T: Default> From<__Optional<T>> for __Set<T> {
+        fn from(o: __Optional<T>) -> Self {
+            __Set {
+                value: o.lazy.unwrap_or_default(),
+            }
+        }
+    }
+    pub struct __FooBuilder<__P> {
+        fields: __P,
+        phantom: (),
+    }
+    impl __FooBuilder<(__Required<usize>,)> {
+        pub fn simple(self, simple: usize) -> __FooBuilder<(__Set<usize>,)> {
+            __FooBuilder {
+                fields: (__set(simple),),
+                phantom: core::default::Default::default(),
+            }
+        }
+    }
+    impl<__P0: Into<__Set<usize>>> __FooBuilder<(__P0,)> {
+        pub fn build(self) -> Foo {
+            Foo::new(self.fields.0.into().value)
+        }
+    }
+}
+
diff --git a/tests/buildstructor/pass/returns_self.rs b/tests/buildstructor/pass/returns_self.rs
new file mode 100644
index 0000000..4cfbe71
--- /dev/null
+++ b/tests/buildstructor/pass/returns_self.rs
@@ -0,0 +1,15 @@
+use buildstructor::builder;
+pub struct Foo {
+    simple: usize,
+}
+
+#[builder]
+impl Foo {
+    fn new(simple: usize) -> Self {
+        Self { simple }
+    }
+}
+
+fn main() {
+    let _: Foo = Foo::builder().simple(3).build();
+}