about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs359
1 files changed, 337 insertions, 22 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
index 4837f92f934..6bb993df5b7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
@@ -35,10 +35,16 @@ use crate::{
 pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
 
-    // We want to only apply this to non-union structs with named fields
     let field_list = match strukt.kind() {
-        StructKind::Record(named) => named,
-        _ => return None,
+        StructKind::Record(named) => {
+            named.fields().filter_map(|f| Some((f.name()?, f.ty()?))).collect::<Vec<_>>()
+        }
+        StructKind::Tuple(tuple) => tuple
+            .fields()
+            .enumerate()
+            .filter_map(|(i, f)| Some((make::name(&format!("_{}", i)), f.ty()?)))
+            .collect::<Vec<_>>(),
+        StructKind::Unit => return None,
     };
 
     // Return early if we've found an existing new fn
@@ -50,11 +56,9 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
     let target = strukt.syntax().text_range();
     acc.add(AssistId::generate("generate_new"), "Generate `new`", target, |builder| {
         let trivial_constructors = field_list
-            .fields()
-            .map(|f| {
-                let name = f.name()?;
-
-                let ty = ctx.sema.resolve_type(&f.ty()?)?;
+            .iter()
+            .map(|(name, ty)| {
+                let ty = ctx.sema.resolve_type(ty)?;
 
                 let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
 
@@ -73,34 +77,44 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
                     edition,
                 )?;
 
-                Some(make::record_expr_field(make::name_ref(&name.text()), Some(expr)))
+                Some((make::name_ref(&name.text()), Some(expr)))
             })
             .collect::<Vec<_>>();
 
-        let params = field_list.fields().enumerate().filter_map(|(i, f)| {
+        let params = field_list.iter().enumerate().filter_map(|(i, (name, ty))| {
             if trivial_constructors[i].is_none() {
-                let name = f.name()?;
-                let ty = f.ty()?;
-
-                Some(make::param(make::ident_pat(false, false, name).into(), ty))
+                Some(make::param(make::ident_pat(false, false, name.clone()).into(), ty.clone()))
             } else {
                 None
             }
         });
         let params = make::param_list(None, params);
 
-        let fields = field_list.fields().enumerate().filter_map(|(i, f)| {
-            let constructor = trivial_constructors[i].clone();
-            if constructor.is_some() {
+        let fields = field_list.iter().enumerate().map(|(i, (name, _))| {
+            if let Some(constructor) = trivial_constructors[i].clone() {
                 constructor
             } else {
-                Some(make::record_expr_field(make::name_ref(&f.name()?.text()), None))
+                (make::name_ref(&name.text()), None)
             }
         });
-        let fields = make::record_expr_field_list(fields);
 
-        let record_expr = make::record_expr(make::ext::ident_path("Self"), fields);
-        let body = make::block_expr(None, Some(record_expr.into()));
+        let tail_expr: ast::Expr = match strukt.kind() {
+            StructKind::Record(_) => {
+                let fields = fields.map(|(name, expr)| make::record_expr_field(name, expr));
+                let fields = make::record_expr_field_list(fields);
+                make::record_expr(make::ext::ident_path("Self"), fields).into()
+            }
+            StructKind::Tuple(_) => {
+                let args = fields.map(|(arg, expr)| {
+                    let arg = || make::expr_path(make::path_unqualified(make::path_segment(arg)));
+                    expr.unwrap_or_else(arg)
+                });
+                let arg_list = make::arg_list(args);
+                make::expr_call(make::expr_path(make::ext::ident_path("Self")), arg_list).into()
+            }
+            StructKind::Unit => unreachable!(),
+        };
+        let body = make::block_expr(None, tail_expr.into());
 
         let ret_type = make::ret_type(make::ty_path(make::ext::ident_path("Self")));
 
@@ -157,7 +171,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
 }
 
 #[cfg(test)]
-mod tests {
+mod record_tests {
     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
 
     use super::*;
@@ -695,3 +709,304 @@ impl<T> Source<T> {
         );
     }
 }
+
+#[cfg(test)]
+mod tuple_tests {
+    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+    use super::*;
+
+    #[test]
+    fn test_generate_new_with_zst_fields() {
+        check_assist(
+            generate_new,
+            r#"
+struct Empty;
+
+struct Foo(Empty$0);
+"#,
+            r#"
+struct Empty;
+
+struct Foo(Empty);
+
+impl Foo {
+    fn $0new() -> Self {
+        Self(Empty)
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+struct Empty;
+
+struct Foo(String, Empty$0);
+"#,
+            r#"
+struct Empty;
+
+struct Foo(String, Empty);
+
+impl Foo {
+    fn $0new(_0: String) -> Self {
+        Self(_0, Empty)
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+enum Empty { Bar }
+
+struct Foo(Empty$0);
+"#,
+            r#"
+enum Empty { Bar }
+
+struct Foo(Empty);
+
+impl Foo {
+    fn $0new() -> Self {
+        Self(Empty::Bar)
+    }
+}
+"#,
+        );
+
+        // make sure the assist only works on unit variants
+        check_assist(
+            generate_new,
+            r#"
+struct Empty {}
+
+struct Foo(Empty$0);
+"#,
+            r#"
+struct Empty {}
+
+struct Foo(Empty);
+
+impl Foo {
+    fn $0new(_0: Empty) -> Self {
+        Self(_0)
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+enum Empty { Bar {} }
+
+struct Foo(Empty$0);
+"#,
+            r#"
+enum Empty { Bar {} }
+
+struct Foo(Empty);
+
+impl Foo {
+    fn $0new(_0: Empty) -> Self {
+        Self(_0)
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_generate_new() {
+        check_assist(
+            generate_new,
+            r#"
+struct Foo($0);
+"#,
+            r#"
+struct Foo();
+
+impl Foo {
+    fn $0new() -> Self {
+        Self()
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+struct Foo<T: Clone>($0);
+"#,
+            r#"
+struct Foo<T: Clone>();
+
+impl<T: Clone> Foo<T> {
+    fn $0new() -> Self {
+        Self()
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+struct Foo<'a, T: Foo<'a>>($0);
+"#,
+            r#"
+struct Foo<'a, T: Foo<'a>>();
+
+impl<'a, T: Foo<'a>> Foo<'a, T> {
+    fn $0new() -> Self {
+        Self()
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+struct Foo(String$0);
+"#,
+            r#"
+struct Foo(String);
+
+impl Foo {
+    fn $0new(_0: String) -> Self {
+        Self(_0)
+    }
+}
+"#,
+        );
+        check_assist(
+            generate_new,
+            r#"
+struct Foo(String, Vec<i32>$0);
+"#,
+            r#"
+struct Foo(String, Vec<i32>);
+
+impl Foo {
+    fn $0new(_0: String, _1: Vec<i32>) -> Self {
+        Self(_0, _1)
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn check_that_visibility_modifiers_dont_get_brought_in() {
+        check_assist(
+            generate_new,
+            r#"
+struct Foo(pub String, pub Vec<i32>$0);
+"#,
+            r#"
+struct Foo(pub String, pub Vec<i32>);
+
+impl Foo {
+    fn $0new(_0: String, _1: Vec<i32>) -> Self {
+        Self(_0, _1)
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn generate_new_not_applicable_if_fn_exists() {
+        check_assist_not_applicable(
+            generate_new,
+            r#"
+struct Foo($0);
+
+impl Foo {
+    fn new() -> Self {
+        Self
+    }
+}
+"#,
+        );
+
+        check_assist_not_applicable(
+            generate_new,
+            r#"
+struct Foo($0);
+
+impl Foo {
+    fn New() -> Self {
+        Self
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn generate_new_target() {
+        check_assist_target(
+            generate_new,
+            r#"
+struct SomeThingIrrelevant;
+/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>>($0);
+struct EvenMoreIrrelevant;
+"#,
+            "/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>>();",
+        );
+    }
+
+    #[test]
+    fn test_unrelated_new() {
+        check_assist(
+            generate_new,
+            r#"
+pub struct AstId<N: AstNode> {
+    file_id: HirFileId,
+    file_ast_id: FileAstId<N>,
+}
+
+impl<N: AstNode> AstId<N> {
+    pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
+        AstId { file_id, file_ast_id }
+    }
+}
+
+pub struct Source<T>(pub HirFileId,$0 pub T);
+
+impl<T> Source<T> {
+    pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
+        Source(self.file_id, f(self.ast))
+    }
+}
+"#,
+            r#"
+pub struct AstId<N: AstNode> {
+    file_id: HirFileId,
+    file_ast_id: FileAstId<N>,
+}
+
+impl<N: AstNode> AstId<N> {
+    pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
+        AstId { file_id, file_ast_id }
+    }
+}
+
+pub struct Source<T>(pub HirFileId, pub T);
+
+impl<T> Source<T> {
+    pub fn $0new(_0: HirFileId, _1: T) -> Self {
+        Self(_0, _1)
+    }
+
+    pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
+        Source(self.file_id, f(self.ast))
+    }
+}
+"#,
+        );
+    }
+}