From a624e2ea8d6fc97df03f663581e082541fd0348f Mon Sep 17 00:00:00 2001 From: jake Date: Sun, 11 Apr 2021 00:31:20 -0700 Subject: Adds impl Deref assist --- crates/ide_assists/src/handlers/generate_deref.rs | 143 ++++++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests.rs | 1 + crates/ide_assists/src/tests/generated.rs | 27 ++++ 4 files changed, 173 insertions(+) create mode 100644 crates/ide_assists/src/handlers/generate_deref.rs (limited to 'crates/ide_assists') 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 000000000..a8126e414 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_deref.rs @@ -0,0 +1,143 @@ +use ide_db::{helpers::FamousDefs, RootDatabase}; +use syntax::{ + ast::{self, NameOwner}, + AstNode, +}; + +use crate::{ + assist_context::{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<()> { + let strukt = ctx.find_node_at_offset::()?; + let field = ctx.find_node_at_offset::()?; + + if existing_deref_impl(&ctx.sema, &strukt).is_some() { + cov_mark::hit!(test_add_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| { + 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.syntax() + ); + let strukt_adt = ast::Adt::Struct(strukt); + // Q for reviewer: Is there a better way to specify the trait_text, e.g. + // - can I have it auto `use std::ops::Deref`, and then just use `Deref` as the trait text? + // Or is there a helper that might detect if `std::ops::Deref` has been used, and pick `Deref`, + // otherwise, pick `std::ops::Deref` for the trait_text. + 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_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 + } +}"#, + ); + } + + 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_deref_not_applicable_if_already_impl() { + cov_mark::check!(test_add_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 + } +}"#, + ) + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3e2c82dac..d6a083e1a 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -132,6 +132,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; @@ -199,6 +200,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 a7a923beb..6f25d3227 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs @@ -191,6 +191,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 27a22ca10..41559b43a 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -551,6 +551,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( -- cgit v1.2.3 From 3d1ca786f6355041de9205cadd0a235581dd5af3 Mon Sep 17 00:00:00 2001 From: jake Date: Sun, 18 Apr 2021 21:51:17 -0700 Subject: implement field stuff too --- crates/ide_assists/src/handlers/generate_deref.rs | 128 ++++++++++++++++++---- 1 file changed, 106 insertions(+), 22 deletions(-) (limited to 'crates/ide_assists') diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs index a8126e414..4998ff7a4 100644 --- a/crates/ide_assists/src/handlers/generate_deref.rs +++ b/crates/ide_assists/src/handlers/generate_deref.rs @@ -1,11 +1,13 @@ +use std::fmt::Display; + use ide_db::{helpers::FamousDefs, RootDatabase}; use syntax::{ ast::{self, NameOwner}, - AstNode, + AstNode, SyntaxNode, }; use crate::{ - assist_context::{AssistContext, Assists}, + assist_context::{AssistBuilder, AssistContext, Assists}, utils::generate_trait_impl_text, AssistId, AssistKind, }; @@ -36,11 +38,15 @@ use crate::{ // } // ``` 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::()?; let field = ctx.find_node_at_offset::()?; if existing_deref_impl(&ctx.sema, &strukt).is_some() { - cov_mark::hit!(test_add_deref_impl_already_exists); + cov_mark::hit!(test_add_record_deref_impl_already_exists); return None; } @@ -51,26 +57,50 @@ pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<( AssistId("generate_deref", AssistKind::Generate), format!("Generate `Deref` impl using `{}`", field_name), target, - |edit| { - let start_offset = strukt.syntax().text_range().end(); - let impl_code = format!( - r#" type Target = {0}; + |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::()?; + let field = ctx.find_node_at_offset::()?; + let field_list = ctx.find_node_at_offset::()?; + 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.syntax() - ); - let strukt_adt = ast::Adt::Struct(strukt); - // Q for reviewer: Is there a better way to specify the trait_text, e.g. - // - can I have it auto `use std::ops::Deref`, and then just use `Deref` as the trait text? - // Or is there a helper that might detect if `std::ops::Deref` has been used, and pick `Deref`, - // otherwise, pick `std::ops::Deref` for the trait_text. - let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code); - edit.insert(start_offset, deref_impl); - }, - ) + 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( @@ -97,7 +127,7 @@ mod tests { use super::*; #[test] - fn test_generate_deref() { + fn test_generate_record_deref() { check_assist( generate_deref, r#"struct A { } @@ -115,6 +145,43 @@ impl std::ops::Deref for B { ); } + #[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{}", @@ -125,8 +192,8 @@ impl std::ops::Deref for B { } #[test] - fn test_generate_deref_not_applicable_if_already_impl() { - cov_mark::check!(test_add_deref_impl_already_exists); + 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 } @@ -137,6 +204,23 @@ impl std::ops::Deref for B { 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 + } }"#, ) } -- cgit v1.2.3