Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/generate_deref.rs')
-rw-r--r--crates/ide-assists/src/handlers/generate_deref.rs224
1 files changed, 224 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 0000000000..3cb8a68687
--- /dev/null
+++ b/crates/ide-assists/src/handlers/generate_deref.rs
@@ -0,0 +1,224 @@
+use std::fmt::Display;
+
+use ide_db::{famous_defs::FamousDefs, RootDatabase};
+use syntax::{
+ ast::{self, HasName},
+ 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, 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
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_record_deref_not_applicable_if_already_impl() {
+ cov_mark::check!(test_add_record_deref_impl_already_exists);
+ check_assist_not_applicable(
+ generate_deref,
+ r#"
+//- minicore: deref
+struct A { }
+struct B { $0a: A }
+
+impl core::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_assist_not_applicable(
+ generate_deref,
+ r#"
+//- minicore: deref
+struct A { }
+struct B($0A)
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}"#,
+ )
+ }
+}