Adds impl Deref assist

This commit is contained in:
jake 2021-04-11 00:31:20 -07:00
parent 5b40342d2d
commit a624e2ea8d
6 changed files with 185 additions and 0 deletions

View file

@ -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::<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_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
}
}"#,
)
}
}

View file

@ -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,

View file

@ -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");

View file

@ -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(

View file

@ -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),

View file

@ -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;