mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-21 09:34:12 +00:00
407 lines
9.3 KiB
Rust
407 lines
9.3 KiB
Rust
use crate::assist_context::{AssistContext, Assists};
|
|
use ide_db::assists::AssistId;
|
|
use syntax::{
|
|
ast::{
|
|
self,
|
|
edit_in_place::{HasVisibilityEdit, Indent},
|
|
make, HasGenericParams, HasName,
|
|
},
|
|
ted::{self, Position},
|
|
AstNode, SyntaxKind, T,
|
|
};
|
|
|
|
// NOTES :
|
|
// We generate erroneous code if a function is declared const (E0379)
|
|
// This is left to the user to correct as our only option is to remove the
|
|
// function completely which we should not be doing.
|
|
|
|
// Assist: generate_trait_from_impl
|
|
//
|
|
// Generate trait for an already defined inherent impl and convert impl to a trait impl.
|
|
//
|
|
// ```
|
|
// struct Foo<const N: usize>([i32; N]);
|
|
//
|
|
// macro_rules! const_maker {
|
|
// ($t:ty, $v:tt) => {
|
|
// const CONST: $t = $v;
|
|
// };
|
|
// }
|
|
//
|
|
// impl<const N: usize> Fo$0o<N> {
|
|
// // Used as an associated constant.
|
|
// const CONST_ASSOC: usize = N * 4;
|
|
//
|
|
// fn create() -> Option<()> {
|
|
// Some(())
|
|
// }
|
|
//
|
|
// const_maker! {i32, 7}
|
|
// }
|
|
// ```
|
|
// ->
|
|
// ```
|
|
// struct Foo<const N: usize>([i32; N]);
|
|
//
|
|
// macro_rules! const_maker {
|
|
// ($t:ty, $v:tt) => {
|
|
// const CONST: $t = $v;
|
|
// };
|
|
// }
|
|
//
|
|
// trait ${0:NewTrait}<const N: usize> {
|
|
// // Used as an associated constant.
|
|
// const CONST_ASSOC: usize = N * 4;
|
|
//
|
|
// fn create() -> Option<()>;
|
|
//
|
|
// const_maker! {i32, 7}
|
|
// }
|
|
//
|
|
// impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
|
// // Used as an associated constant.
|
|
// const CONST_ASSOC: usize = N * 4;
|
|
//
|
|
// fn create() -> Option<()> {
|
|
// Some(())
|
|
// }
|
|
//
|
|
// const_maker! {i32, 7}
|
|
// }
|
|
// ```
|
|
pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
|
// Get AST Node
|
|
let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
|
|
|
|
// Check if cursor is to the left of assoc item list's L_CURLY.
|
|
// if no L_CURLY then return.
|
|
let l_curly = impl_ast.assoc_item_list()?.l_curly_token()?;
|
|
|
|
let cursor_offset = ctx.offset();
|
|
let l_curly_offset = l_curly.text_range();
|
|
if cursor_offset >= l_curly_offset.start() {
|
|
return None;
|
|
}
|
|
|
|
// If impl is not inherent then we don't really need to go any further.
|
|
if impl_ast.for_token().is_some() {
|
|
return None;
|
|
}
|
|
|
|
let assoc_items = impl_ast.assoc_item_list()?;
|
|
let first_element = assoc_items.assoc_items().next();
|
|
first_element.as_ref()?;
|
|
|
|
let impl_name = impl_ast.self_ty()?;
|
|
|
|
acc.add(
|
|
AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate),
|
|
"Generate trait from impl",
|
|
impl_ast.syntax().text_range(),
|
|
|builder| {
|
|
let impl_ast = builder.make_mut(impl_ast);
|
|
let trait_items = assoc_items.clone_for_update();
|
|
let impl_items = builder.make_mut(assoc_items);
|
|
let impl_name = builder.make_mut(impl_name);
|
|
|
|
trait_items.assoc_items().for_each(|item| {
|
|
strip_body(&item);
|
|
remove_items_visibility(&item);
|
|
});
|
|
|
|
impl_items.assoc_items().for_each(|item| {
|
|
remove_items_visibility(&item);
|
|
});
|
|
|
|
let trait_ast = make::trait_(
|
|
false,
|
|
"NewTrait",
|
|
impl_ast.generic_param_list(),
|
|
impl_ast.where_clause(),
|
|
trait_items,
|
|
)
|
|
.clone_for_update();
|
|
|
|
let trait_name = trait_ast.name().expect("new trait should have a name");
|
|
let trait_name_ref = make::name_ref(&trait_name.to_string()).clone_for_update();
|
|
|
|
// Change `impl Foo` to `impl NewTrait for Foo`
|
|
let mut elements = vec![
|
|
trait_name_ref.syntax().clone().into(),
|
|
make::tokens::single_space().into(),
|
|
make::token(T![for]).into(),
|
|
];
|
|
|
|
if let Some(params) = impl_ast.generic_param_list() {
|
|
let gen_args = ¶ms.to_generic_args().clone_for_update();
|
|
elements.insert(1, gen_args.syntax().clone().into());
|
|
}
|
|
|
|
ted::insert_all(Position::before(impl_name.syntax()), elements);
|
|
|
|
// Insert trait before TraitImpl
|
|
ted::insert_all_raw(
|
|
Position::before(impl_ast.syntax()),
|
|
vec![
|
|
trait_ast.syntax().clone().into(),
|
|
make::tokens::whitespace(&format!("\n\n{}", impl_ast.indent_level())).into(),
|
|
],
|
|
);
|
|
|
|
// Link the trait name & trait ref names together as a placeholder snippet group
|
|
if let Some(cap) = ctx.config.snippet_cap {
|
|
builder.add_placeholder_snippet_group(
|
|
cap,
|
|
vec![trait_name.syntax().clone(), trait_name_ref.syntax().clone()],
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
Some(())
|
|
}
|
|
|
|
/// `E0449` Trait items always share the visibility of their trait
|
|
fn remove_items_visibility(item: &ast::AssocItem) {
|
|
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
|
|
has_vis.set_visibility(None);
|
|
}
|
|
}
|
|
|
|
fn strip_body(item: &ast::AssocItem) {
|
|
if let ast::AssocItem::Fn(f) = item {
|
|
if let Some(body) = f.body() {
|
|
// In contrast to function bodies, we want to see no ws before a semicolon.
|
|
// So let's remove them if we see any.
|
|
if let Some(prev) = body.syntax().prev_sibling_or_token() {
|
|
if prev.kind() == SyntaxKind::WHITESPACE {
|
|
ted::remove(prev);
|
|
}
|
|
}
|
|
|
|
ted::replace(body.syntax(), make::tokens::semicolon());
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable};
|
|
|
|
#[test]
|
|
fn test_trigger_when_cursor_on_header() {
|
|
check_assist_not_applicable(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo(f64);
|
|
|
|
impl Foo { $0
|
|
fn add(&mut self, x: f64) {
|
|
self.0 += x;
|
|
}
|
|
}"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_assoc_item_fn() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo(f64);
|
|
|
|
impl F$0oo {
|
|
fn add(&mut self, x: f64) {
|
|
self.0 += x;
|
|
}
|
|
}"#,
|
|
r#"
|
|
struct Foo(f64);
|
|
|
|
trait NewTrait {
|
|
fn add(&mut self, x: f64);
|
|
}
|
|
|
|
impl NewTrait for Foo {
|
|
fn add(&mut self, x: f64) {
|
|
self.0 += x;
|
|
}
|
|
}"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_assoc_item_macro() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo;
|
|
|
|
macro_rules! const_maker {
|
|
($t:ty, $v:tt) => {
|
|
const CONST: $t = $v;
|
|
};
|
|
}
|
|
|
|
impl F$0oo {
|
|
const_maker! {i32, 7}
|
|
}"#,
|
|
r#"
|
|
struct Foo;
|
|
|
|
macro_rules! const_maker {
|
|
($t:ty, $v:tt) => {
|
|
const CONST: $t = $v;
|
|
};
|
|
}
|
|
|
|
trait NewTrait {
|
|
const_maker! {i32, 7}
|
|
}
|
|
|
|
impl NewTrait for Foo {
|
|
const_maker! {i32, 7}
|
|
}"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_assoc_item_const() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo;
|
|
|
|
impl F$0oo {
|
|
const ABC: i32 = 3;
|
|
}"#,
|
|
r#"
|
|
struct Foo;
|
|
|
|
trait NewTrait {
|
|
const ABC: i32 = 3;
|
|
}
|
|
|
|
impl NewTrait for Foo {
|
|
const ABC: i32 = 3;
|
|
}"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_impl_with_generics() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo<const N: usize>([i32; N]);
|
|
|
|
impl<const N: usize> F$0oo<N> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
"#,
|
|
r#"
|
|
struct Foo<const N: usize>([i32; N]);
|
|
|
|
trait NewTrait<const N: usize> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
|
|
impl<const N: usize> NewTrait<N> for Foo<N> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_trait_items_should_not_have_vis() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo;
|
|
|
|
impl F$0oo {
|
|
pub fn a_func() -> Option<()> {
|
|
Some(())
|
|
}
|
|
}"#,
|
|
r#"
|
|
struct Foo;
|
|
|
|
trait NewTrait {
|
|
fn a_func() -> Option<()>;
|
|
}
|
|
|
|
impl NewTrait for Foo {
|
|
fn a_func() -> Option<()> {
|
|
Some(())
|
|
}
|
|
}"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_inherent_impl() {
|
|
check_assist_not_applicable(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
impl Emp$0tyImpl{}
|
|
"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_not_top_level_impl() {
|
|
check_assist_no_snippet_cap(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
mod a {
|
|
impl S$0 {
|
|
fn foo() {}
|
|
}
|
|
}"#,
|
|
r#"
|
|
mod a {
|
|
trait NewTrait {
|
|
fn foo();
|
|
}
|
|
|
|
impl NewTrait for S {
|
|
fn foo() {}
|
|
}
|
|
}"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_snippet_cap_is_some() {
|
|
check_assist(
|
|
generate_trait_from_impl,
|
|
r#"
|
|
struct Foo<const N: usize>([i32; N]);
|
|
|
|
impl<const N: usize> F$0oo<N> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
"#,
|
|
r#"
|
|
struct Foo<const N: usize>([i32; N]);
|
|
|
|
trait ${0:NewTrait}<const N: usize> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
|
|
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
|
// Used as an associated constant.
|
|
const CONST: usize = N * 4;
|
|
}
|
|
"#,
|
|
)
|
|
}
|
|
}
|