fix(assist): derive source scope from syntax node to be transformed
This commit is contained in:
parent
68bdf609f3
commit
008f5065d1
@ -1,5 +1,4 @@
|
|||||||
use hir::HasSource;
|
use hir::HasSource;
|
||||||
use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into;
|
|
||||||
use syntax::ast::{self, make, AstNode};
|
use syntax::ast::{self, make, AstNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -129,20 +128,9 @@ fn add_missing_impl_members_inner(
|
|||||||
let target = impl_def.syntax().text_range();
|
let target = impl_def.syntax().text_range();
|
||||||
acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |edit| {
|
acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |edit| {
|
||||||
let new_impl_def = edit.make_mut(impl_def.clone());
|
let new_impl_def = edit.make_mut(impl_def.clone());
|
||||||
let missing_items = missing_items
|
|
||||||
.into_iter()
|
|
||||||
.map(|it| {
|
|
||||||
if ctx.sema.hir_file_for(it.syntax()).is_macro() {
|
|
||||||
if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) {
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it.clone_for_update()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let first_new_item = add_trait_assoc_items_to_impl(
|
let first_new_item = add_trait_assoc_items_to_impl(
|
||||||
&ctx.sema,
|
&ctx.sema,
|
||||||
missing_items,
|
&missing_items,
|
||||||
trait_,
|
trait_,
|
||||||
&new_impl_def,
|
&new_impl_def,
|
||||||
target_scope,
|
target_scope,
|
||||||
@ -1730,4 +1718,77 @@ impl m::Foo for S {
|
|||||||
}"#,
|
}"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_macro_should_not_cause_crash() {
|
||||||
|
check_assist(
|
||||||
|
add_missing_impl_members,
|
||||||
|
r#"
|
||||||
|
macro_rules! ty { () => { i32 } }
|
||||||
|
trait SomeTrait { type Output; }
|
||||||
|
impl SomeTrait for i32 { type Output = i64; }
|
||||||
|
macro_rules! define_method {
|
||||||
|
() => {
|
||||||
|
fn method(&mut self, params: <ty!() as SomeTrait>::Output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
trait AnotherTrait { define_method!(); }
|
||||||
|
impl $0AnotherTrait for () {
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
macro_rules! ty { () => { i32 } }
|
||||||
|
trait SomeTrait { type Output; }
|
||||||
|
impl SomeTrait for i32 { type Output = i64; }
|
||||||
|
macro_rules! define_method {
|
||||||
|
() => {
|
||||||
|
fn method(&mut self, params: <ty!() as SomeTrait>::Output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
trait AnotherTrait { define_method!(); }
|
||||||
|
impl AnotherTrait for () {
|
||||||
|
$0fn method(&mut self,params: <ty!()as SomeTrait>::Output) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: `T` in `ty!(T)` should be replaced by `PathTransform`.
|
||||||
|
#[test]
|
||||||
|
fn paths_in_nested_macro_should_get_transformed() {
|
||||||
|
check_assist(
|
||||||
|
add_missing_impl_members,
|
||||||
|
r#"
|
||||||
|
macro_rules! ty { ($me:ty) => { $me } }
|
||||||
|
trait SomeTrait { type Output; }
|
||||||
|
impl SomeTrait for i32 { type Output = i64; }
|
||||||
|
macro_rules! define_method {
|
||||||
|
($t:ty) => {
|
||||||
|
fn method(&mut self, params: <ty!($t) as SomeTrait>::Output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
trait AnotherTrait<T: SomeTrait> { define_method!(T); }
|
||||||
|
impl $0AnotherTrait<i32> for () {
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
macro_rules! ty { ($me:ty) => { $me } }
|
||||||
|
trait SomeTrait { type Output; }
|
||||||
|
impl SomeTrait for i32 { type Output = i64; }
|
||||||
|
macro_rules! define_method {
|
||||||
|
($t:ty) => {
|
||||||
|
fn method(&mut self, params: <ty!($t) as SomeTrait>::Output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
trait AnotherTrait<T: SomeTrait> { define_method!(T); }
|
||||||
|
impl AnotherTrait<i32> for () {
|
||||||
|
$0fn method(&mut self,params: <ty!(T)as SomeTrait>::Output) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
use hir::{InFile, ModuleDef};
|
use hir::{InFile, ModuleDef};
|
||||||
use ide_db::{
|
use ide_db::{helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator};
|
||||||
helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator,
|
|
||||||
syntax_helpers::insert_whitespace_into_node::insert_ws_into,
|
|
||||||
};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode, HasName},
|
ast::{self, AstNode, HasName},
|
||||||
@ -202,19 +199,24 @@ fn impl_def_from_trait(
|
|||||||
node
|
node
|
||||||
};
|
};
|
||||||
|
|
||||||
let trait_items = trait_items
|
// <<<<<<< HEAD
|
||||||
.into_iter()
|
// let trait_items = trait_items
|
||||||
.map(|it| {
|
// .into_iter()
|
||||||
if sema.hir_file_for(it.syntax()).is_macro() {
|
// .map(|it| {
|
||||||
if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) {
|
// if sema.hir_file_for(it.syntax()).is_macro() {
|
||||||
return it;
|
// if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) {
|
||||||
}
|
// return it;
|
||||||
}
|
// }
|
||||||
it.clone_for_update()
|
// }
|
||||||
})
|
// it.clone_for_update()
|
||||||
.collect();
|
// })
|
||||||
|
// .collect();
|
||||||
|
// let first_assoc_item =
|
||||||
|
// add_trait_assoc_items_to_impl(sema, trait_items, trait_, &impl_def, target_scope);
|
||||||
|
// =======
|
||||||
let first_assoc_item =
|
let first_assoc_item =
|
||||||
add_trait_assoc_items_to_impl(sema, trait_items, trait_, &impl_def, target_scope);
|
add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, target_scope);
|
||||||
|
// >>>>>>> fix(assist): derive source scope from syntax node to be transformed
|
||||||
|
|
||||||
// Generate a default `impl` function body for the derived trait.
|
// Generate a default `impl` function body for the derived trait.
|
||||||
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
|
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
|
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
|
||||||
use hir::{db::HirDatabase, HirDisplay, Semantics};
|
use hir::{db::HirDatabase, HirDisplay, InFile, Semantics};
|
||||||
use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap};
|
use ide_db::{
|
||||||
|
famous_defs::FamousDefs, path_transform::PathTransform,
|
||||||
|
syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, SnippetCap,
|
||||||
|
};
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{
|
ast::{
|
||||||
@ -91,30 +94,21 @@ pub fn filter_assoc_items(
|
|||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
items: &[hir::AssocItem],
|
items: &[hir::AssocItem],
|
||||||
default_methods: DefaultMethods,
|
default_methods: DefaultMethods,
|
||||||
) -> Vec<ast::AssocItem> {
|
) -> Vec<InFile<ast::AssocItem>> {
|
||||||
fn has_def_name(item: &ast::AssocItem) -> bool {
|
return items
|
||||||
match item {
|
|
||||||
ast::AssocItem::Fn(def) => def.name(),
|
|
||||||
ast::AssocItem::TypeAlias(def) => def.name(),
|
|
||||||
ast::AssocItem::Const(def) => def.name(),
|
|
||||||
ast::AssocItem::MacroCall(_) => None,
|
|
||||||
}
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
.iter()
|
.iter()
|
||||||
// Note: This throws away items with no source.
|
// Note: This throws away items with no source.
|
||||||
.filter_map(|&i| {
|
.copied()
|
||||||
let item = match i {
|
.filter_map(|assoc_item| {
|
||||||
hir::AssocItem::Function(i) => ast::AssocItem::Fn(sema.source(i)?.value),
|
let item = match assoc_item {
|
||||||
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(sema.source(i)?.value),
|
hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn),
|
||||||
hir::AssocItem::Const(i) => ast::AssocItem::Const(sema.source(i)?.value),
|
hir::AssocItem::TypeAlias(it) => sema.source(it)?.map(ast::AssocItem::TypeAlias),
|
||||||
|
hir::AssocItem::Const(it) => sema.source(it)?.map(ast::AssocItem::Const),
|
||||||
};
|
};
|
||||||
Some(item)
|
Some(item)
|
||||||
})
|
})
|
||||||
.filter(has_def_name)
|
.filter(has_def_name)
|
||||||
.filter(|it| match it {
|
.filter(|it| match &it.value {
|
||||||
ast::AssocItem::Fn(def) => matches!(
|
ast::AssocItem::Fn(def) => matches!(
|
||||||
(default_methods, def.body()),
|
(default_methods, def.body()),
|
||||||
(DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
|
(DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
|
||||||
@ -125,26 +119,55 @@ pub fn filter_assoc_items(
|
|||||||
),
|
),
|
||||||
_ => default_methods == DefaultMethods::No,
|
_ => default_methods == DefaultMethods::No,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect();
|
||||||
|
|
||||||
|
fn has_def_name(item: &InFile<ast::AssocItem>) -> bool {
|
||||||
|
match &item.value {
|
||||||
|
ast::AssocItem::Fn(def) => def.name(),
|
||||||
|
ast::AssocItem::TypeAlias(def) => def.name(),
|
||||||
|
ast::AssocItem::Const(def) => def.name(),
|
||||||
|
ast::AssocItem::MacroCall(_) => None,
|
||||||
|
}
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given `original_items` retrieved from the trait definition (usually by
|
||||||
|
/// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it,
|
||||||
|
/// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got
|
||||||
|
/// inserted.
|
||||||
pub fn add_trait_assoc_items_to_impl(
|
pub fn add_trait_assoc_items_to_impl(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
items: Vec<ast::AssocItem>,
|
original_items: &[InFile<ast::AssocItem>],
|
||||||
trait_: hir::Trait,
|
trait_: hir::Trait,
|
||||||
impl_: &ast::Impl,
|
impl_: &ast::Impl,
|
||||||
target_scope: hir::SemanticsScope<'_>,
|
target_scope: hir::SemanticsScope<'_>,
|
||||||
) -> ast::AssocItem {
|
) -> ast::AssocItem {
|
||||||
let source_scope = sema.scope_for_def(trait_);
|
|
||||||
|
|
||||||
let transform = PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone());
|
|
||||||
|
|
||||||
let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
|
let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
|
||||||
let items = items.into_iter().map(|assoc_item| {
|
let items = original_items.into_iter().map(|InFile { file_id, value: original_item }| {
|
||||||
transform.apply(assoc_item.syntax());
|
let cloned_item = {
|
||||||
assoc_item.remove_attrs_and_docs();
|
if file_id.is_macro() {
|
||||||
assoc_item.reindent_to(new_indent_level);
|
if let Some(formatted) =
|
||||||
assoc_item
|
ast::AssocItem::cast(insert_ws_into(original_item.syntax().clone()))
|
||||||
|
{
|
||||||
|
return formatted;
|
||||||
|
} else {
|
||||||
|
stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
original_item.clone_for_update()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(source_scope) = sema.scope(original_item.syntax()) {
|
||||||
|
// FIXME: Paths in nested macros are not handled well. See
|
||||||
|
// `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
|
||||||
|
let transform =
|
||||||
|
PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone());
|
||||||
|
transform.apply(cloned_item.syntax());
|
||||||
|
}
|
||||||
|
cloned_item.remove_attrs_and_docs();
|
||||||
|
cloned_item.reindent_to(new_indent_level);
|
||||||
|
cloned_item
|
||||||
});
|
});
|
||||||
|
|
||||||
let assoc_item_list = impl_.get_or_create_assoc_item_list();
|
let assoc_item_list = impl_.get_or_create_assoc_item_list();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user