about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-19 04:54:04 +0000
committerGitHub <noreply@github.com>2021-04-19 04:54:04 +0000
commit3f432730dfc7df83fed2d78a97d3d0cf97ffc282 (patch)
tree6b284cf75eccc23a2dcaf398767bb939ea5f077e
parent7570212a544b8e973a7d57be3657aae6465028a7 (diff)
parent3d1ca786f6355041de9205cadd0a235581dd5af3 (diff)
downloadrust-3f432730dfc7df83fed2d78a97d3d0cf97ffc282.tar.gz
rust-3f432730dfc7df83fed2d78a97d3d0cf97ffc282.zip
Merge #8467
8467: Adds impl Deref assist r=jhgg a=jhgg

This PR adds a new `generate_deref` assist that automatically generates a deref impl for a given struct field.

Check out this gif:

![2021-04-11_00-33-33](https://user-images.githubusercontent.com/5489149/114296006-b38e1000-9a5d-11eb-9112-807c01b8fd0a.gif)

--

I have a few Q's:
 - [x] Should I write more tests, if so, what precisely should I test for?
 - [x] I have an inline question on line 65, can someone provide guidance? :) 
 - [x] I can implement this for `ast::TupleField` too. But should it be a separate assist fn, or should I try and jam both into the `generate_deref`?
 - [x] I want to follow this up with an assist on `impl $0Deref for T {` which would automatically generate a `DerefMut` impl that mirrors the Deref as well, however, I could probably use some pointers on how to do that, since I'll have to reach into the ast of `fn deref` to grab the field that it's referencing for the `DerefMut` impl. 

Co-authored-by: jake <jh@discordapp.com>
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs227
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests.rs1
-rw-r--r--crates/ide_assists/src/tests/generated.rs27
-rw-r--r--crates/ide_db/src/helpers.rs4
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs8
6 files changed, 269 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs
new file mode 100644
index 00000000000..4998ff7a421
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,227 @@
+use std::fmt::Display;
+
+use ide_db::{helpers::FamousDefs, RootDatabase};
+use syntax::{
+    ast::{self, NameOwner},
+    AstNode, SyntaxNode,
+};
+
+use crate::{
+    assist_context::{AssistBuilder, AssistContext, Assists},
+    utils::generate_trait_impl_text,
+    AssistId, AssistKind,
+};
+
+// Assist: generate_deref
+//
+// Generate `Deref` impl using the given struct field.
+//
+// ```
+// struct A;
+// struct B {
+//    $0a: A
+// }
+// ```
+// ->
+// ```
+// struct A;
+// struct B {
+//    a: A
+// }
+//
+// impl std::ops::Deref for B {
+//     type Target = A;
+//
+//     fn deref(&self) -> &Self::Target {
+//         &self.a
+//     }
+// }
+// ```
+pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
+}
+
+fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+    let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+
+    if existing_deref_impl(&ctx.sema, &strukt).is_some() {
+        cov_mark::hit!(test_add_record_deref_impl_already_exists);
+        return None;
+    }
+
+    let field_type = field.ty()?;
+    let field_name = field.name()?;
+    let target = field.syntax().text_range();
+    acc.add(
+        AssistId("generate_deref", AssistKind::Generate),
+        format!("Generate `Deref` impl using `{}`", field_name),
+        target,
+        |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()),
+    )
+}
+
+fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+    let field = ctx.find_node_at_offset::<ast::TupleField>()?;
+    let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
+    let field_list_index =
+        field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?;
+
+    if existing_deref_impl(&ctx.sema, &strukt).is_some() {
+        cov_mark::hit!(test_add_field_deref_impl_already_exists);
+        return None;
+    }
+
+    let field_type = field.ty()?;
+    let target = field.syntax().text_range();
+    acc.add(
+        AssistId("generate_deref", AssistKind::Generate),
+        format!("Generate `Deref` impl using `{}`", field.syntax()),
+        target,
+        |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index),
+    )
+}
+
+fn generate_edit(
+    edit: &mut AssistBuilder,
+    strukt: ast::Struct,
+    field_type_syntax: &SyntaxNode,
+    field_name: impl Display,
+) {
+    let start_offset = strukt.syntax().text_range().end();
+    let impl_code = format!(
+        r#"    type Target = {0};
+
+    fn deref(&self) -> &Self::Target {{
+        &self.{1}
+    }}"#,
+        field_type_syntax, field_name
+    );
+    let strukt_adt = ast::Adt::Struct(strukt);
+    let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code);
+    edit.insert(start_offset, deref_impl);
+}
+
+fn existing_deref_impl(
+    sema: &'_ hir::Semantics<'_, RootDatabase>,
+    strukt: &ast::Struct,
+) -> Option<()> {
+    let strukt = sema.to_def(strukt)?;
+    let krate = strukt.module(sema.db).krate();
+
+    let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
+    let strukt_type = strukt.ty(sema.db);
+
+    if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
+        Some(())
+    } else {
+        None
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn test_generate_record_deref() {
+        check_assist(
+            generate_deref,
+            r#"struct A { }
+struct B { $0a: A }"#,
+            r#"struct A { }
+struct B { a: A }
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.a
+    }
+}"#,
+        );
+    }
+
+    #[test]
+    fn test_generate_field_deref_idx_0() {
+        check_assist(
+            generate_deref,
+            r#"struct A { }
+struct B($0A);"#,
+            r#"struct A { }
+struct B(A);
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}"#,
+        );
+    }
+    #[test]
+    fn test_generate_field_deref_idx_1() {
+        check_assist(
+            generate_deref,
+            r#"struct A { }
+struct B(u8, $0A);"#,
+            r#"struct A { }
+struct B(u8, A);
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.1
+    }
+}"#,
+        );
+    }
+
+    fn check_not_applicable(ra_fixture: &str) {
+        let fixture = format!(
+            "//- /main.rs crate:main deps:core,std\n{}\n{}",
+            ra_fixture,
+            FamousDefs::FIXTURE
+        );
+        check_assist_not_applicable(generate_deref, &fixture)
+    }
+
+    #[test]
+    fn test_generate_record_deref_not_applicable_if_already_impl() {
+        cov_mark::check!(test_add_record_deref_impl_already_exists);
+        check_not_applicable(
+            r#"struct A { }
+struct B { $0a: A }
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.a
+    }
+}"#,
+        )
+    }
+
+    #[test]
+    fn test_generate_field_deref_not_applicable_if_already_impl() {
+        cov_mark::check!(test_add_field_deref_impl_already_exists);
+        check_not_applicable(
+            r#"struct A { }
+struct B($0A)
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}"#,
+        )
+    }
+}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3694f468f0b..8996c1b615c 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -134,6 +134,7 @@ mod handlers {
     mod generate_default_from_enum_variant;
     mod generate_default_from_new;
     mod generate_is_empty_from_len;
+    mod generate_deref;
     mod generate_derive;
     mod generate_enum_is_method;
     mod generate_enum_projection_method;
@@ -201,6 +202,7 @@ mod handlers {
             generate_default_from_enum_variant::generate_default_from_enum_variant,
             generate_default_from_new::generate_default_from_new,
             generate_is_empty_from_len::generate_is_empty_from_len,
+            generate_deref::generate_deref,
             generate_derive::generate_derive,
             generate_enum_is_method::generate_enum_is_method,
             generate_enum_projection_method::generate_enum_as_method,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 60e3a1474b0..49533e7d2c5 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -193,6 +193,7 @@ fn assist_order_field_struct() {
     let mut assists = assists.iter();
 
     assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
+    assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`");
     assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
     assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
     assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 27a22ca10c1..41559b43ad7 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -552,6 +552,33 @@ impl Default for Example {
 }
 
 #[test]
+fn doctest_generate_deref() {
+    check_doc_test(
+        "generate_deref",
+        r#####"
+struct A;
+struct B {
+   $0a: A
+}
+"#####,
+        r#####"
+struct A;
+struct B {
+   a: A
+}
+
+impl std::ops::Deref for B {
+    type Target = A;
+
+    fn deref(&self) -> &Self::Target {
+        &self.a
+    }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_generate_derive() {
     check_doc_test(
         "generate_derive",
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 66798ea3a69..83a665b3760 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -113,6 +113,10 @@ impl FamousDefs<'_, '_> {
         self.find_module("core:iter")
     }
 
+    pub fn core_ops_Deref(&self) -> Option<Trait> {
+        self.find_trait("core:ops:Deref")
+    }
+
     fn find_trait(&self, path: &str) -> Option<Trait> {
         match self.find_def(path)? {
             hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs
index 4d79e064e08..29ae12dcf40 100644
--- a/crates/ide_db/src/helpers/famous_defs_fixture.rs
+++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs
@@ -112,6 +112,12 @@ pub mod ops {
         type Output;
         extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
     }
+
+    #[lang = "deref"]
+    pub trait Deref {
+        type Target: ?Sized;
+        fn deref(&self) -> &Self::Target;
+    }
 }
 
 pub mod option {
@@ -141,3 +147,5 @@ mod return_keyword {}
 
 /// Docs for prim_str
 mod prim_str {}
+
+pub use core::ops;
\ No newline at end of file