mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
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:  -- 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>
This commit is contained in:
commit
3f432730df
227
crates/ide_assists/src/handlers/generate_deref.rs
Normal file
227
crates/ide_assists/src/handlers/generate_deref.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -134,6 +134,7 @@ mod handlers {
|
|||||||
mod generate_default_from_enum_variant;
|
mod generate_default_from_enum_variant;
|
||||||
mod generate_default_from_new;
|
mod generate_default_from_new;
|
||||||
mod generate_is_empty_from_len;
|
mod generate_is_empty_from_len;
|
||||||
|
mod generate_deref;
|
||||||
mod generate_derive;
|
mod generate_derive;
|
||||||
mod generate_enum_is_method;
|
mod generate_enum_is_method;
|
||||||
mod generate_enum_projection_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_enum_variant::generate_default_from_enum_variant,
|
||||||
generate_default_from_new::generate_default_from_new,
|
generate_default_from_new::generate_default_from_new,
|
||||||
generate_is_empty_from_len::generate_is_empty_from_len,
|
generate_is_empty_from_len::generate_is_empty_from_len,
|
||||||
|
generate_deref::generate_deref,
|
||||||
generate_derive::generate_derive,
|
generate_derive::generate_derive,
|
||||||
generate_enum_is_method::generate_enum_is_method,
|
generate_enum_is_method::generate_enum_is_method,
|
||||||
generate_enum_projection_method::generate_enum_as_method,
|
generate_enum_projection_method::generate_enum_as_method,
|
||||||
|
@ -193,6 +193,7 @@ fn assist_order_field_struct() {
|
|||||||
let mut assists = assists.iter();
|
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, "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 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 getter method");
|
||||||
assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
|
assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn doctest_generate_derive() {
|
fn doctest_generate_derive() {
|
||||||
check_doc_test(
|
check_doc_test(
|
||||||
|
@ -113,6 +113,10 @@ impl FamousDefs<'_, '_> {
|
|||||||
self.find_module("core:iter")
|
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> {
|
fn find_trait(&self, path: &str) -> Option<Trait> {
|
||||||
match self.find_def(path)? {
|
match self.find_def(path)? {
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
|
||||||
|
@ -112,6 +112,12 @@ pub mod ops {
|
|||||||
type Output;
|
type Output;
|
||||||
extern "rust-call" fn call_once(self, args: Args) -> Self::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 {
|
pub mod option {
|
||||||
@ -141,3 +147,5 @@ mod return_keyword {}
|
|||||||
|
|
||||||
/// Docs for prim_str
|
/// Docs for prim_str
|
||||||
mod prim_str {}
|
mod prim_str {}
|
||||||
|
|
||||||
|
pub use core::ops;
|
Loading…
x
Reference in New Issue
Block a user