mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-14 01:25:54 +00:00
Merge #6458
6458: Qualify trait impl created by add_custom_impl assist r=matklad a=Veykril When we find at least one trait with the same name as the derive accessible from the current module we now generate a qualified path to that trait in the generated impl. If we don't find any we just do what was done before and emit the trait name in the generated impl. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
99a8e59f68
@ -1,13 +1,16 @@
|
|||||||
|
use ide_db::imports_locator;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode},
|
ast::{self, make, AstNode},
|
||||||
Direction, SmolStr,
|
Direction, SmolStr,
|
||||||
SyntaxKind::{IDENT, WHITESPACE},
|
SyntaxKind::{IDENT, WHITESPACE},
|
||||||
TextRange, TextSize,
|
TextRange, TextSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assist_context::{AssistContext, Assists},
|
assist_config::SnippetCap,
|
||||||
|
assist_context::{AssistBuilder, AssistContext, Assists},
|
||||||
|
utils::mod_path_to_ast,
|
||||||
AssistId, AssistKind,
|
AssistId, AssistKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,35 +33,96 @@ use crate::{
|
|||||||
// ```
|
// ```
|
||||||
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||||
let attr = ctx.find_node_at_offset::<ast::Attr>()?;
|
let attr = ctx.find_node_at_offset::<ast::Attr>()?;
|
||||||
let input = attr.token_tree()?;
|
|
||||||
|
|
||||||
let attr_name = attr
|
let attr_name = attr
|
||||||
.syntax()
|
.syntax()
|
||||||
.descendants_with_tokens()
|
.descendants_with_tokens()
|
||||||
.filter(|t| t.kind() == IDENT)
|
.filter(|t| t.kind() == IDENT)
|
||||||
.find_map(|i| i.into_token())
|
.find_map(syntax::NodeOrToken::into_token)
|
||||||
.filter(|t| *t.text() == "derive")?
|
.filter(|t| t.text() == "derive")?
|
||||||
.text()
|
.text()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let trait_token =
|
let trait_token =
|
||||||
ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
|
ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
|
||||||
|
let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
|
||||||
|
|
||||||
let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
|
let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
|
||||||
let annotated_name = annotated.syntax().text().to_string();
|
let annotated_name = annotated.syntax().text().to_string();
|
||||||
let start_offset = annotated.syntax().parent()?.text_range().end();
|
let insert_pos = annotated.syntax().parent()?.text_range().end();
|
||||||
|
|
||||||
let label =
|
let current_module = ctx.sema.scope(annotated.syntax()).module()?;
|
||||||
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
|
let current_crate = current_module.krate();
|
||||||
|
|
||||||
|
let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
|
||||||
|
either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flat_map(|trait_| {
|
||||||
|
current_module
|
||||||
|
.find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
|
||||||
|
.as_ref()
|
||||||
|
.map(mod_path_to_ast)
|
||||||
|
.zip(Some(trait_))
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut no_traits_found = true;
|
||||||
|
for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
|
||||||
|
add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
|
||||||
|
}
|
||||||
|
if no_traits_found {
|
||||||
|
add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_assist(
|
||||||
|
acc: &mut Assists,
|
||||||
|
snippet_cap: Option<SnippetCap>,
|
||||||
|
attr: &ast::Attr,
|
||||||
|
trait_path: &ast::Path,
|
||||||
|
annotated_name: &str,
|
||||||
|
insert_pos: TextSize,
|
||||||
|
) -> Option<()> {
|
||||||
let target = attr.syntax().text_range();
|
let target = attr.syntax().text_range();
|
||||||
|
let input = attr.token_tree()?;
|
||||||
|
let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
|
||||||
|
let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
|
||||||
|
|
||||||
acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
|
acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
|
||||||
|
update_attribute(builder, &input, &trait_name, &attr);
|
||||||
|
match snippet_cap {
|
||||||
|
Some(cap) => {
|
||||||
|
builder.insert_snippet(
|
||||||
|
cap,
|
||||||
|
insert_pos,
|
||||||
|
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
builder.insert(
|
||||||
|
insert_pos,
|
||||||
|
format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_attribute(
|
||||||
|
builder: &mut AssistBuilder,
|
||||||
|
input: &ast::TokenTree,
|
||||||
|
trait_name: &ast::NameRef,
|
||||||
|
attr: &ast::Attr,
|
||||||
|
) {
|
||||||
let new_attr_input = input
|
let new_attr_input = input
|
||||||
.syntax()
|
.syntax()
|
||||||
.descendants_with_tokens()
|
.descendants_with_tokens()
|
||||||
.filter(|t| t.kind() == IDENT)
|
.filter(|t| t.kind() == IDENT)
|
||||||
.filter_map(|t| t.into_token().map(|t| t.text().clone()))
|
.filter_map(|t| t.into_token().map(|t| t.text().clone()))
|
||||||
.filter(|t| t != trait_token.text())
|
.filter(|t| t != trait_name.text())
|
||||||
.collect::<Vec<SmolStr>>();
|
.collect::<Vec<SmolStr>>();
|
||||||
let has_more_derives = !new_attr_input.is_empty();
|
let has_more_derives = !new_attr_input.is_empty();
|
||||||
|
|
||||||
@ -77,23 +141,6 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
|
|||||||
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
|
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
|
||||||
builder.delete(line_break_range);
|
builder.delete(line_break_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
match ctx.config.snippet_cap {
|
|
||||||
Some(cap) => {
|
|
||||||
builder.insert_snippet(
|
|
||||||
cap,
|
|
||||||
start_offset,
|
|
||||||
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
builder.insert(
|
|
||||||
start_offset,
|
|
||||||
format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -102,6 +149,35 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_custom_impl_qualified() {
|
||||||
|
check_assist(
|
||||||
|
add_custom_impl,
|
||||||
|
"
|
||||||
|
mod fmt {
|
||||||
|
pub trait Debug {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debu<|>g)]
|
||||||
|
struct Foo {
|
||||||
|
bar: String,
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"
|
||||||
|
mod fmt {
|
||||||
|
pub trait Debug {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
bar: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Foo {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn add_custom_impl_for_unique_input() {
|
fn add_custom_impl_for_unique_input() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
Loading…
Reference in New Issue
Block a user