From 66b6a433c6243b8be72bbd04a40d0a38cedb11b4 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 3 Jul 2020 18:15:03 +0200 Subject: Unify naming of generating assists --- crates/ra_assists/src/handlers/add_derive.rs | 127 --- .../src/handlers/add_from_impl_for_enum.rs | 200 ---- crates/ra_assists/src/handlers/add_function.rs | 1053 ------------------- crates/ra_assists/src/handlers/add_impl.rs | 105 -- crates/ra_assists/src/handlers/add_new.rs | 424 -------- crates/ra_assists/src/handlers/generate_derive.rs | 127 +++ .../src/handlers/generate_from_impl_for_enum.rs | 200 ++++ .../ra_assists/src/handlers/generate_function.rs | 1058 ++++++++++++++++++++ crates/ra_assists/src/handlers/generate_impl.rs | 109 ++ crates/ra_assists/src/handlers/generate_new.rs | 424 ++++++++ crates/ra_assists/src/lib.rs | 20 +- crates/ra_assists/src/tests/generated.rs | 218 ++-- xtask/tests/tidy.rs | 2 +- 13 files changed, 2038 insertions(+), 2029 deletions(-) delete mode 100644 crates/ra_assists/src/handlers/add_derive.rs delete mode 100644 crates/ra_assists/src/handlers/add_from_impl_for_enum.rs delete mode 100644 crates/ra_assists/src/handlers/add_function.rs delete mode 100644 crates/ra_assists/src/handlers/add_impl.rs delete mode 100644 crates/ra_assists/src/handlers/add_new.rs create mode 100644 crates/ra_assists/src/handlers/generate_derive.rs create mode 100644 crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs create mode 100644 crates/ra_assists/src/handlers/generate_function.rs create mode 100644 crates/ra_assists/src/handlers/generate_impl.rs create mode 100644 crates/ra_assists/src/handlers/generate_new.rs diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs deleted file mode 100644 index e2b94e7f8..000000000 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ /dev/null @@ -1,127 +0,0 @@ -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner}, - SyntaxKind::{COMMENT, WHITESPACE}, - TextSize, -}; - -use crate::{AssistContext, AssistId, AssistKind, Assists}; - -// Assist: add_derive -// -// Adds a new `#[derive()]` clause to a struct or enum. -// -// ``` -// struct Point { -// x: u32, -// y: u32,<|> -// } -// ``` -// -> -// ``` -// #[derive($0)] -// struct Point { -// x: u32, -// y: u32, -// } -// ``` -pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let cap = ctx.config.snippet_cap?; - let nominal = ctx.find_node_at_offset::()?; - let node_start = derive_insertion_offset(&nominal)?; - let target = nominal.syntax().text_range(); - acc.add(AssistId("add_derive", AssistKind::None), "Add `#[derive]`", target, |builder| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_simple_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - match derive_attr { - None => { - builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); - } - Some(tt) => { - // Just move the cursor. - builder.insert_snippet( - cap, - tt.syntax().text_range().end() - TextSize::of(')'), - "$0", - ) - } - }; - }) -} - -// Insert `derive` after doc comments. -fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { - let non_ws_child = nominal - .syntax() - .children_with_tokens() - .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; - Some(non_ws_child.text_range().start()) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_target}; - - use super::*; - - #[test] - fn add_derive_new() { - check_assist( - add_derive, - "struct Foo { a: i32, <|>}", - "#[derive($0)]\nstruct Foo { a: i32, }", - ); - check_assist( - add_derive, - "struct Foo { <|> a: i32, }", - "#[derive($0)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_existing() { - check_assist( - add_derive, - "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone$0)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_new_with_doc_comment() { - check_assist( - add_derive, - " -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } - ", - " -/// `Foo` is a pretty important struct. -/// It does stuff. -#[derive($0)] -struct Foo { a: i32, } - ", - ); - } - - #[test] - fn add_derive_target() { - check_assist_target( - add_derive, - " -struct SomeThingIrrelevant; -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } -struct EvenMoreIrrelevant; - ", - "/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32, }", - ); - } -} diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs deleted file mode 100644 index a324670ed..000000000 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ /dev/null @@ -1,200 +0,0 @@ -use ra_ide_db::RootDatabase; -use ra_syntax::ast::{self, AstNode, NameOwner}; -use test_utils::mark; - -use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists}; - -// Assist: add_from_impl_for_enum -// -// Adds a From impl for an enum variant with one tuple field. -// -// ``` -// enum A { <|>One(u32) } -// ``` -// -> -// ``` -// enum A { One(u32) } -// -// impl From for A { -// fn from(v: u32) -> Self { -// A::One(v) -// } -// } -// ``` -pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let variant = ctx.find_node_at_offset::()?; - let variant_name = variant.name()?; - let enum_name = variant.parent_enum().name()?; - let field_list = match variant.kind() { - ast::StructKind::Tuple(field_list) => field_list, - _ => return None, - }; - if field_list.fields().count() != 1 { - return None; - } - let field_type = field_list.fields().next()?.type_ref()?; - let path = match field_type { - ast::TypeRef::PathType(it) => it, - _ => return None, - }; - - if existing_from_impl(&ctx.sema, &variant).is_some() { - mark::hit!(test_add_from_impl_already_exists); - return None; - } - - let target = variant.syntax().text_range(); - acc.add( - AssistId("add_from_impl_for_enum", AssistKind::Refactor), - "Add From impl for this enum variant", - target, - |edit| { - let start_offset = variant.parent_enum().syntax().text_range().end(); - let buf = format!( - r#" - -impl From<{0}> for {1} {{ - fn from(v: {0}) -> Self {{ - {1}::{2}(v) - }} -}}"#, - path.syntax(), - enum_name, - variant_name - ); - edit.insert(start_offset, buf); - }, - ) -} - -fn existing_from_impl( - sema: &'_ hir::Semantics<'_, RootDatabase>, - variant: &ast::EnumVariant, -) -> Option<()> { - let variant = sema.to_def(variant)?; - let enum_ = variant.parent_enum(sema.db); - let krate = enum_.module(sema.db).krate(); - - let from_trait = FamousDefs(sema, krate).core_convert_From()?; - - let enum_type = enum_.ty(sema.db); - - let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db); - - if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) { - Some(()) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use test_utils::mark; - - use crate::tests::{check_assist, check_assist_not_applicable}; - - use super::*; - - #[test] - fn test_add_from_impl_for_enum() { - check_assist( - add_from_impl_for_enum, - "enum A { <|>One(u32) }", - r#"enum A { One(u32) } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -}"#, - ); - } - - #[test] - fn test_add_from_impl_for_enum_complicated_path() { - check_assist( - add_from_impl_for_enum, - r#"enum A { <|>One(foo::bar::baz::Boo) }"#, - r#"enum A { One(foo::bar::baz::Boo) } - -impl From for A { - fn from(v: foo::bar::baz::Boo) -> Self { - A::One(v) - } -}"#, - ); - } - - fn check_not_applicable(ra_fixture: &str) { - let fixture = - format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); - check_assist_not_applicable(add_from_impl_for_enum, &fixture) - } - - #[test] - fn test_add_from_impl_no_element() { - check_not_applicable("enum A { <|>One }"); - } - - #[test] - fn test_add_from_impl_more_than_one_element_in_tuple() { - check_not_applicable("enum A { <|>One(u32, String) }"); - } - - #[test] - fn test_add_from_impl_struct_variant() { - check_not_applicable("enum A { <|>One { x: u32 } }"); - } - - #[test] - fn test_add_from_impl_already_exists() { - mark::check!(test_add_from_impl_already_exists); - check_not_applicable( - r#" -enum A { <|>One(u32), } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -} -"#, - ); - } - - #[test] - fn test_add_from_impl_different_variant_impl_exists() { - check_assist( - add_from_impl_for_enum, - r#"enum A { <|>One(u32), Two(String), } - -impl From for A { - fn from(v: String) -> Self { - A::Two(v) - } -} - -pub trait From { - fn from(T) -> Self; -}"#, - r#"enum A { One(u32), Two(String), } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -} - -impl From for A { - fn from(v: String) -> Self { - A::Two(v) - } -} - -pub trait From { - fn from(T) -> Self; -}"#, - ); - } -} diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs deleted file mode 100644 index 7150eb53a..000000000 --- a/crates/ra_assists/src/handlers/add_function.rs +++ /dev/null @@ -1,1053 +0,0 @@ -use hir::HirDisplay; -use ra_db::FileId; -use ra_syntax::{ - ast::{ - self, - edit::{AstNodeEdit, IndentLevel}, - make, ArgListOwner, AstNode, ModuleItemOwner, - }, - SyntaxKind, SyntaxNode, TextSize, -}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use crate::{ - assist_config::SnippetCap, - utils::{render_snippet, Cursor}, - AssistContext, AssistId, AssistKind, Assists, -}; - -// Assist: add_function -// -// Adds a stub function with a signature matching the function under the cursor. -// -// ``` -// struct Baz; -// fn baz() -> Baz { Baz } -// fn foo() { -// bar<|>("", baz()); -// } -// -// ``` -// -> -// ``` -// struct Baz; -// fn baz() -> Baz { Baz } -// fn foo() { -// bar("", baz()); -// } -// -// fn bar(arg: &str, baz: Baz) { -// ${0:todo!()} -// } -// -// ``` -pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; - let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; - let path = path_expr.path()?; - - if ctx.sema.resolve_path(&path).is_some() { - // The function call already resolves, no need to add a function - return None; - } - - let target_module = match path.qualifier() { - Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { - Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), - _ => return None, - }, - None => None, - }; - - let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; - - let target = call.syntax().text_range(); - acc.add(AssistId("add_function", AssistKind::None), "Add function", target, |builder| { - let function_template = function_builder.render(); - builder.edit_file(function_template.file); - let new_fn = function_template.to_string(ctx.config.snippet_cap); - match ctx.config.snippet_cap { - Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), - None => builder.insert(function_template.insert_offset, new_fn), - } - }) -} - -struct FunctionTemplate { - insert_offset: TextSize, - placeholder_expr: ast::MacroCall, - leading_ws: String, - fn_def: ast::FnDef, - trailing_ws: String, - file: FileId, -} - -impl FunctionTemplate { - fn to_string(&self, cap: Option) -> String { - let f = match cap { - Some(cap) => render_snippet( - cap, - self.fn_def.syntax(), - Cursor::Replace(self.placeholder_expr.syntax()), - ), - None => self.fn_def.to_string(), - }; - format!("{}{}{}", self.leading_ws, f, self.trailing_ws) - } -} - -struct FunctionBuilder { - target: GeneratedFunctionTarget, - fn_name: ast::Name, - type_params: Option, - params: ast::ParamList, - file: FileId, - needs_pub: bool, -} - -impl FunctionBuilder { - /// Prepares a generated function that matches `call`. - /// The function is generated in `target_module` or next to `call` - fn from_call( - ctx: &AssistContext, - call: &ast::CallExpr, - path: &ast::Path, - target_module: Option, - ) -> Option { - let mut file = ctx.frange.file_id; - let target = match &target_module { - Some(target_module) => { - let module_source = target_module.definition_source(ctx.db()); - let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; - file = in_file; - target - } - None => next_space_for_fn_after_call_site(&call)?, - }; - let needs_pub = target_module.is_some(); - let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; - let fn_name = fn_name(&path)?; - let (type_params, params) = fn_args(ctx, target_module, &call)?; - - Some(Self { target, fn_name, type_params, params, file, needs_pub }) - } - - fn render(self) -> FunctionTemplate { - let placeholder_expr = make::expr_todo(); - let fn_body = make::block_expr(vec![], Some(placeholder_expr)); - let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; - let mut fn_def = - make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); - let leading_ws; - let trailing_ws; - - let insert_offset = match self.target { - GeneratedFunctionTarget::BehindItem(it) => { - let indent = IndentLevel::from_node(&it); - leading_ws = format!("\n\n{}", indent); - fn_def = fn_def.indent(indent); - trailing_ws = String::new(); - it.text_range().end() - } - GeneratedFunctionTarget::InEmptyItemList(it) => { - let indent = IndentLevel::from_node(it.syntax()); - leading_ws = format!("\n{}", indent + 1); - fn_def = fn_def.indent(indent + 1); - trailing_ws = format!("\n{}", indent); - it.syntax().text_range().start() + TextSize::of('{') - } - }; - - let placeholder_expr = - fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); - FunctionTemplate { - insert_offset, - placeholder_expr, - leading_ws, - fn_def, - trailing_ws, - file: self.file, - } - } -} - -enum GeneratedFunctionTarget { - BehindItem(SyntaxNode), - InEmptyItemList(ast::ItemList), -} - -impl GeneratedFunctionTarget { - fn syntax(&self) -> &SyntaxNode { - match self { - GeneratedFunctionTarget::BehindItem(it) => it, - GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), - } - } -} - -fn fn_name(call: &ast::Path) -> Option { - let name = call.segment()?.syntax().to_string(); - Some(make::name(&name)) -} - -/// Computes the type variables and arguments required for the generated function -fn fn_args( - ctx: &AssistContext, - target_module: hir::Module, - call: &ast::CallExpr, -) -> Option<(Option, ast::ParamList)> { - let mut arg_names = Vec::new(); - let mut arg_types = Vec::new(); - for arg in call.arg_list()?.args() { - arg_names.push(match fn_arg_name(&arg) { - Some(name) => name, - None => String::from("arg"), - }); - arg_types.push(match fn_arg_type(ctx, target_module, &arg) { - Some(ty) => ty, - None => String::from("()"), - }); - } - deduplicate_arg_names(&mut arg_names); - let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); - Some((None, make::param_list(params))) -} - -/// Makes duplicate argument names unique by appending incrementing numbers. -/// -/// ``` -/// let mut names: Vec = -/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()]; -/// deduplicate_arg_names(&mut names); -/// let expected: Vec = -/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()]; -/// assert_eq!(names, expected); -/// ``` -fn deduplicate_arg_names(arg_names: &mut Vec) { - let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| { - *m.entry(name).or_insert(0) += 1; - m - }); - let duplicate_arg_names: FxHashSet = arg_name_counts - .into_iter() - .filter(|(_, count)| *count >= 2) - .map(|(name, _)| name.clone()) - .collect(); - - let mut counter_per_name = FxHashMap::default(); - for arg_name in arg_names.iter_mut() { - if duplicate_arg_names.contains(arg_name) { - let counter = counter_per_name.entry(arg_name.clone()).or_insert(1); - arg_name.push('_'); - arg_name.push_str(&counter.to_string()); - *counter += 1; - } - } -} - -fn fn_arg_name(fn_arg: &ast::Expr) -> Option { - match fn_arg { - ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), - _ => Some( - fn_arg - .syntax() - .descendants() - .filter(|d| ast::NameRef::can_cast(d.kind())) - .last()? - .to_string(), - ), - } -} - -fn fn_arg_type( - ctx: &AssistContext, - target_module: hir::Module, - fn_arg: &ast::Expr, -) -> Option { - let ty = ctx.sema.type_of_expr(fn_arg)?; - if ty.is_unknown() { - return None; - } - - if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) { - Some(rendered) - } else { - None - } -} - -/// Returns the position inside the current mod or file -/// directly after the current block -/// We want to write the generated function directly after -/// fns, impls or macro calls, but inside mods -fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option { - let mut ancestors = expr.syntax().ancestors().peekable(); - let mut last_ancestor: Option = None; - while let Some(next_ancestor) = ancestors.next() { - match next_ancestor.kind() { - SyntaxKind::SOURCE_FILE => { - break; - } - SyntaxKind::ITEM_LIST => { - if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) { - break; - } - } - _ => {} - } - last_ancestor = Some(next_ancestor); - } - last_ancestor.map(GeneratedFunctionTarget::BehindItem) -} - -fn next_space_for_fn_in_module( - db: &dyn hir::db::AstDatabase, - module_source: &hir::InFile, -) -> Option<(FileId, GeneratedFunctionTarget)> { - let file = module_source.file_id.original_file(db); - let assist_item = match &module_source.value { - hir::ModuleSource::SourceFile(it) => { - if let Some(last_item) = it.items().last() { - GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) - } else { - GeneratedFunctionTarget::BehindItem(it.syntax().clone()) - } - } - hir::ModuleSource::Module(it) => { - if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) { - GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) - } else { - GeneratedFunctionTarget::InEmptyItemList(it.item_list()?) - } - } - }; - Some((file, assist_item)) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - - use super::*; - - #[test] - fn add_function_with_no_args() { - check_assist( - add_function, - r" -fn foo() { - bar<|>(); -} -", - r" -fn foo() { - bar(); -} - -fn bar() { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_from_method() { - // This ensures that the function is correctly generated - // in the next outer mod or file - check_assist( - add_function, - r" -impl Foo { - fn foo() { - bar<|>(); - } -} -", - r" -impl Foo { - fn foo() { - bar(); - } -} - -fn bar() { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_directly_after_current_block() { - // The new fn should not be created at the end of the file or module - check_assist( - add_function, - r" -fn foo1() { - bar<|>(); -} - -fn foo2() {} -", - r" -fn foo1() { - bar(); -} - -fn bar() { - ${0:todo!()} -} - -fn foo2() {} -", - ) - } - - #[test] - fn add_function_with_no_args_in_same_module() { - check_assist( - add_function, - r" -mod baz { - fn foo() { - bar<|>(); - } -} -", - r" -mod baz { - fn foo() { - bar(); - } - - fn bar() { - ${0:todo!()} - } -} -", - ) - } - - #[test] - fn add_function_with_function_call_arg() { - check_assist( - add_function, - r" -struct Baz; -fn baz() -> Baz { todo!() } -fn foo() { - bar<|>(baz()); -} -", - r" -struct Baz; -fn baz() -> Baz { todo!() } -fn foo() { - bar(baz()); -} - -fn bar(baz: Baz) { - ${0:todo!()} -} -", - ); - } - - #[test] - fn add_function_with_method_call_arg() { - check_assist( - add_function, - r" -struct Baz; -impl Baz { - fn foo(&self) -> Baz { - ba<|>r(self.baz()) - } - fn baz(&self) -> Baz { - Baz - } -} -", - r" -struct Baz; -impl Baz { - fn foo(&self) -> Baz { - bar(self.baz()) - } - fn baz(&self) -> Baz { - Baz - } -} - -fn bar(baz: Baz) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_with_string_literal_arg() { - check_assist( - add_function, - r#" -fn foo() { - <|>bar("bar") -} -"#, - r#" -fn foo() { - bar("bar") -} - -fn bar(arg: &str) { - ${0:todo!()} -} -"#, - ) - } - - #[test] - fn add_function_with_char_literal_arg() { - check_assist( - add_function, - r#" -fn foo() { - <|>bar('x') -} -"#, - r#" -fn foo() { - bar('x') -} - -fn bar(arg: char) { - ${0:todo!()} -} -"#, - ) - } - - #[test] - fn add_function_with_int_literal_arg() { - check_assist( - add_function, - r" -fn foo() { - <|>bar(42) -} -", - r" -fn foo() { - bar(42) -} - -fn bar(arg: i32) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_with_cast_int_literal_arg() { - check_assist( - add_function, - r" -fn foo() { - <|>bar(42 as u8) -} -", - r" -fn foo() { - bar(42 as u8) -} - -fn bar(arg: u8) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn name_of_cast_variable_is_used() { - // Ensures that the name of the cast type isn't used - // in the generated function signature. - check_assist( - add_function, - r" -fn foo() { - let x = 42; - bar<|>(x as u8) -} -", - r" -fn foo() { - let x = 42; - bar(x as u8) -} - -fn bar(x: u8) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_with_variable_arg() { - check_assist( - add_function, - r" -fn foo() { - let worble = (); - <|>bar(worble) -} -", - r" -fn foo() { - let worble = (); - bar(worble) -} - -fn bar(worble: ()) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_with_impl_trait_arg() { - check_assist( - add_function, - r" -trait Foo {} -fn foo() -> impl Foo { - todo!() -} -fn baz() { - <|>bar(foo()) -} -", - r" -trait Foo {} -fn foo() -> impl Foo { - todo!() -} -fn baz() { - bar(foo()) -} - -fn bar(foo: impl Foo) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn borrowed_arg() { - check_assist( - add_function, - r" -struct Baz; -fn baz() -> Baz { todo!() } - -fn foo() { - bar<|>(&baz()) -} -", - r" -struct Baz; -fn baz() -> Baz { todo!() } - -fn foo() { - bar(&baz()) -} - -fn bar(baz: &Baz) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_with_qualified_path_arg() { - check_assist( - add_function, - r" -mod Baz { - pub struct Bof; - pub fn baz() -> Bof { Bof } -} -fn foo() { - <|>bar(Baz::baz()) -} -", - r" -mod Baz { - pub struct Bof; - pub fn baz() -> Bof { Bof } -} -fn foo() { - bar(Baz::baz()) -} - -fn bar(baz: Baz::Bof) { - ${0:todo!()} -} -", - ) - } - - #[test] - #[ignore] - // FIXME fix printing the generics of a `Ty` to make this test pass - fn add_function_with_generic_arg() { - check_assist( - add_function, - r" -fn foo(t: T) { - <|>bar(t) -} -", - r" -fn foo(t: T) { - bar(t) -} - -fn bar(t: T) { - ${0:todo!()} -} -", - ) - } - - #[test] - #[ignore] - // FIXME Fix function type printing to make this test pass - fn add_function_with_fn_arg() { - check_assist( - add_function, - r" -struct Baz; -impl Baz { - fn new() -> Self { Baz } -} -fn foo() { - <|>bar(Baz::new); -} -", - r" -struct Baz; -impl Baz { - fn new() -> Self { Baz } -} -fn foo() { - bar(Baz::new); -} - -fn bar(arg: fn() -> Baz) { - ${0:todo!()} -} -", - ) - } - - #[test] - #[ignore] - // FIXME Fix closure type printing to make this test pass - fn add_function_with_closure_arg() { - check_assist( - add_function, - r" -fn foo() { - let closure = |x: i64| x - 1; - <|>bar(closure) -} -", - r" -fn foo() { - let closure = |x: i64| x - 1; - bar(closure) -} - -fn bar(closure: impl Fn(i64) -> i64) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn unresolveable_types_default_to_unit() { - check_assist( - add_function, - r" -fn foo() { - <|>bar(baz) -} -", - r" -fn foo() { - bar(baz) -} - -fn bar(baz: ()) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn arg_names_dont_overlap() { - check_assist( - add_function, - r" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - <|>bar(baz(), baz()) -} -", - r" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar(baz(), baz()) -} - -fn bar(baz_1: Baz, baz_2: Baz) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn arg_name_counters_start_at_1_per_name() { - check_assist( - add_function, - r#" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - <|>bar(baz(), baz(), "foo", "bar") -} -"#, - r#" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar(baz(), baz(), "foo", "bar") -} - -fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { - ${0:todo!()} -} -"#, - ) - } - - #[test] - fn add_function_in_module() { - check_assist( - add_function, - r" -mod bar {} - -fn foo() { - bar::my_fn<|>() -} -", - r" -mod bar { - pub(crate) fn my_fn() { - ${0:todo!()} - } -} - -fn foo() { - bar::my_fn() -} -", - ) - } - - #[test] - #[ignore] - // Ignored until local imports are supported. - // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 - fn qualified_path_uses_correct_scope() { - check_assist( - add_function, - " -mod foo { - pub struct Foo; -} -fn bar() { - use foo::Foo; - let foo = Foo; - baz<|>(foo) -} -", - " -mod foo { - pub struct Foo; -} -fn bar() { - use foo::Foo; - let foo = Foo; - baz(foo) -} - -fn baz(foo: foo::Foo) { - ${0:todo!()} -} -", - ) - } - - #[test] - fn add_function_in_module_containing_other_items() { - check_assist( - add_function, - r" -mod bar { - fn something_else() {} -} - -fn foo() { - bar::my_fn<|>() -} -", - r" -mod bar { - fn something_else() {} - - pub(crate) fn my_fn() { - ${0:todo!()} - } -} - -fn foo() { - bar::my_fn() -} -", - ) - } - - #[test] - fn add_function_in_nested_module() { - check_assist( - add_function, - r" -mod bar { - mod baz {} -} - -fn foo() { - bar::baz::my_fn<|>() -} -", - r" -mod bar { - mod baz { - pub(crate) fn my_fn() { - ${0:todo!()} - } - } -} - -fn foo() { - bar::baz::my_fn() -} -", - ) - } - - #[test] - fn add_function_in_another_file() { - check_assist( - add_function, - r" -//- /main.rs -mod foo; - -fn main() { - foo::bar<|>() -} -//- /foo.rs -", - r" - - -pub(crate) fn bar() { - ${0:todo!()} -}", - ) - } - - #[test] - fn add_function_not_applicable_if_function_already_exists() { - check_assist_not_applicable( - add_function, - r" -fn foo() { - bar<|>(); -} - -fn bar() {} -", - ) - } - - #[test] - fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() { - check_assist_not_applicable( - // bar is resolved, but baz isn't. - // The assist is only active if the cursor is on an unresolved path, - // but the assist should only be offered if the path is a function call. - add_function, - r" -fn foo() { - bar(b<|>az); -} - -fn bar(baz: ()) {} -", - ) - } - - #[test] - #[ignore] - fn create_method_with_no_args() { - check_assist( - add_function, - r" -struct Foo; -impl Foo { - fn foo(&self) { - self.bar()<|>; - } -} - ", - r" -struct Foo; -impl Foo { - fn foo(&self) { - self.bar(); - } - fn bar(&self) { - todo!(); - } -} - ", - ) - } -} diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs deleted file mode 100644 index 2f603ef9c..000000000 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ /dev/null @@ -1,105 +0,0 @@ -use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; -use stdx::{format_to, SepBy}; - -use crate::{AssistContext, AssistId, AssistKind, Assists}; - -// Assist: add_impl -// -// Adds a new inherent impl for a type. -// -// ``` -// struct Ctx { -// data: T,<|> -// } -// ``` -// -> -// ``` -// struct Ctx { -// data: T, -// } -// -// impl Ctx { -// $0 -// } -// ``` -pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let nominal = ctx.find_node_at_offset::()?; - let name = nominal.name()?; - let target = nominal.syntax().text_range(); - acc.add( - AssistId("add_impl", AssistKind::Refactor), - format!("Implement {}", name.text().as_str()), - target, - |edit| { - let type_params = nominal.type_param_list(); - let start_offset = nominal.syntax().text_range().end(); - let mut buf = String::new(); - buf.push_str("\n\nimpl"); - if let Some(type_params) = &type_params { - format_to!(buf, "{}", type_params.syntax()); - } - buf.push_str(" "); - buf.push_str(name.text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime_token()) - .map(|it| it.text().clone()); - let type_params = type_params - .type_params() - .filter_map(|it| it.name()) - .map(|it| it.text().clone()); - - let generic_params = lifetime_params.chain(type_params).sep_by(", "); - format_to!(buf, "<{}>", generic_params) - } - match ctx.config.snippet_cap { - Some(cap) => { - buf.push_str(" {\n $0\n}"); - edit.insert_snippet(cap, start_offset, buf); - } - None => { - buf.push_str(" {\n}"); - edit.insert(start_offset, buf); - } - } - }, - ) -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_target}; - - use super::*; - - #[test] - fn test_add_impl() { - check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); - check_assist( - add_impl, - "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n $0\n}", - ); - check_assist( - add_impl, - "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", - ); - } - - #[test] - fn add_impl_target() { - check_assist_target( - add_impl, - " -struct SomeThingIrrelevant; -/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {<|>} -struct EvenMoreIrrelevant; -", - "/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {}", - ); - } -} diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs deleted file mode 100644 index 0b3d29c7c..000000000 --- a/crates/ra_assists/src/handlers/add_new.rs +++ /dev/null @@ -1,424 +0,0 @@ -use hir::Adt; -use ra_syntax::{ - ast::{ - self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, - }, - T, -}; -use stdx::{format_to, SepBy}; - -use crate::{AssistContext, AssistId, AssistKind, Assists}; - -// Assist: add_new -// -// Adds a new inherent impl for a type. -// -// ``` -// struct Ctx { -// data: T,<|> -// } -// ``` -// -> -// ``` -// struct Ctx { -// data: T, -// } -// -// impl Ctx { -// fn $0new(data: T) -> Self { Self { data } } -// } -// -// ``` -pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let strukt = ctx.find_node_at_offset::()?; - - // We want to only apply this to non-union structs with named fields - let field_list = match strukt.kind() { - StructKind::Record(named) => named, - _ => return None, - }; - - // Return early if we've found an existing new fn - let impl_def = find_struct_impl(&ctx, &strukt)?; - - let target = strukt.syntax().text_range(); - acc.add(AssistId("add_new", AssistKind::None), "Add default constructor", target, |builder| { - let mut buf = String::with_capacity(512); - - if impl_def.is_some() { - buf.push('\n'); - } - - let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); - - let params = field_list - .fields() - .filter_map(|f| { - Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax())) - }) - .sep_by(", "); - let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); - - format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); - - let start_offset = impl_def - .and_then(|impl_def| { - buf.push('\n'); - let start = impl_def - .syntax() - .descendants_with_tokens() - .find(|t| t.kind() == T!['{'])? - .text_range() - .end(); - - Some(start) - }) - .unwrap_or_else(|| { - buf = generate_impl_text(&strukt, &buf); - strukt.syntax().text_range().end() - }); - - match ctx.config.snippet_cap { - None => builder.insert(start_offset, buf), - Some(cap) => { - buf = buf.replace("fn new", "fn $0new"); - builder.insert_snippet(cap, start_offset, buf); - } - } - }) -} - -// Generates the surrounding `impl Type { }` including type and lifetime -// parameters -fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { - let type_params = strukt.type_param_list(); - let mut buf = String::with_capacity(code.len()); - buf.push_str("\n\nimpl"); - if let Some(type_params) = &type_params { - format_to!(buf, "{}", type_params.syntax()); - } - buf.push_str(" "); - buf.push_str(strukt.name().unwrap().text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime_token()) - .map(|it| it.text().clone()); - let type_params = - type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); - format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", ")) - } - - format_to!(buf, " {{\n{}\n}}\n", code); - - buf -} - -// Uses a syntax-driven approach to find any impl blocks for the struct that -// exist within the module/file -// -// Returns `None` if we've found an existing `new` fn -// -// FIXME: change the new fn checking to a more semantic approach when that's more -// viable (e.g. we process proc macros, etc) -fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option> { - let db = ctx.db(); - let module = strukt.syntax().ancestors().find(|node| { - ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) - })?; - - let struct_def = ctx.sema.to_def(strukt)?; - - let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| { - let blk = ctx.sema.to_def(&impl_blk)?; - - // FIXME: handle e.g. `struct S; impl S {}` - // (we currently use the wrong type parameter) - // also we wouldn't want to use e.g. `impl S` - let same_ty = match blk.target_ty(db).as_adt() { - Some(def) => def == Adt::Struct(struct_def), - None => false, - }; - let not_trait_impl = blk.target_trait(db).is_none(); - - if !(same_ty && not_trait_impl) { - None - } else { - Some(impl_blk) - } - }); - - if let Some(ref impl_blk) = block { - if has_new_fn(impl_blk) { - return None; - } - } - - Some(block) -} - -fn has_new_fn(imp: &ast::ImplDef) -> bool { - if let Some(il) = imp.item_list() { - for item in il.assoc_items() { - if let ast::AssocItem::FnDef(f) = item { - if let Some(name) = f.name() { - if name.text().eq_ignore_ascii_case("new") { - return true; - } - } - } - } - } - - false -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; - - use super::*; - - #[test] - #[rustfmt::skip] - fn test_add_new() { - // Check output of generation - check_assist( - add_new, -"struct Foo {<|>}", -"struct Foo {} - -impl Foo { - fn $0new() -> Self { Self { } } -} -", - ); - check_assist( - add_new, -"struct Foo {<|>}", -"struct Foo {} - -impl Foo { - fn $0new() -> Self { Self { } } -} -", - ); - check_assist( - add_new, -"struct Foo<'a, T: Foo<'a>> {<|>}", -"struct Foo<'a, T: Foo<'a>> {} - -impl<'a, T: Foo<'a>> Foo<'a, T> { - fn $0new() -> Self { Self { } } -} -", - ); - check_assist( - add_new, -"struct Foo { baz: String <|>}", -"struct Foo { baz: String } - -impl Foo { - fn $0new(baz: String) -> Self { Self { baz } } -} -", - ); - check_assist( - add_new, -"struct Foo { baz: String, qux: Vec <|>}", -"struct Foo { baz: String, qux: Vec } - -impl Foo { - fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } -} -", - ); - - // Check that visibility modifiers don't get brought in for fields - check_assist( - add_new, -"struct Foo { pub baz: String, pub qux: Vec <|>}", -"struct Foo { pub baz: String, pub qux: Vec } - -impl Foo { - fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } -} -", - ); - - // Check that it reuses existing impls - check_assist( - add_new, -"struct Foo {<|>} - -impl Foo {} -", -"struct Foo {} - -impl Foo { - fn $0new() -> Self { Self { } } -} -", - ); - check_assist( - add_new, -"struct Foo {<|>} - -impl Foo { - fn qux(&self) {} -} -", -"struct Foo {} - -impl Foo { - fn $0new() -> Self { Self { } } - - fn qux(&self) {} -} -", - ); - - check_assist( - add_new, -"struct Foo {<|>} - -impl Foo { - fn qux(&self) {} - fn baz() -> i32 { - 5 - } -} -", -"struct Foo {} - -impl Foo { - fn $0new() -> Self { Self { } } - - fn qux(&self) {} - fn baz() -> i32 { - 5 - } -} -", - ); - - // Check visibility of new fn based on struct - check_assist( - add_new, -"pub struct Foo {<|>}", -"pub struct Foo {} - -impl Foo { - pub fn $0new() -> Self { Self { } } -} -", - ); - check_assist( - add_new, -"pub(crate) struct Foo {<|>}", -"pub(crate) struct Foo {} - -impl Foo { - pub(crate) fn $0new() -> Self { Self { } } -} -", - ); - } - - #[test] - fn add_new_not_applicable_if_fn_exists() { - check_assist_not_applicable( - add_new, - " -struct Foo {<|>} - -impl Foo { - fn new() -> Self { - Self - } -}", - ); - - check_assist_not_applicable( - add_new, - " -struct Foo {<|>} - -impl Foo { - fn New() -> Self { - Self - } -}", - ); - } - - #[test] - fn add_new_target() { - check_assist_target( - add_new, - " -struct SomeThingIrrelevant; -/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {<|>} -struct EvenMoreIrrelevant; -", - "/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {}", - ); - } - - #[test] - fn test_unrelated_new() { - check_assist( - add_new, - r##" -pub struct AstId { - file_id: HirFileId, - file_ast_id: FileAstId, -} - -impl AstId { - pub fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { - AstId { file_id, file_ast_id } - } -} - -pub struct Source { - pub file_id: HirFileId,<|> - pub ast: T, -} - -impl Source { - pub fn map U, U>(self, f: F) -> Source { - Source { file_id: self.file_id, ast: f(self.ast) } - } -} -"##, - r##" -pub struct AstId { - file_id: HirFileId, - file_ast_id: FileAstId, -} - -impl AstId { - pub fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { - AstId { file_id, file_ast_id } - } -} - -pub struct Source { - pub file_id: HirFileId, - pub ast: T, -} - -impl Source { - pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } - - pub fn map U, U>(self, f: F) -> Source { - Source { file_id: self.file_id, ast: f(self.ast) } - } -} -"##, - ); - } -} diff --git a/crates/ra_assists/src/handlers/generate_derive.rs b/crates/ra_assists/src/handlers/generate_derive.rs new file mode 100644 index 000000000..8fc522eef --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_derive.rs @@ -0,0 +1,127 @@ +use ra_syntax::{ + ast::{self, AstNode, AttrsOwner}, + SyntaxKind::{COMMENT, WHITESPACE}, + TextSize, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: generate_derive +// +// Adds a new `#[derive()]` clause to a struct or enum. +// +// ``` +// struct Point { +// x: u32, +// y: u32,<|> +// } +// ``` +// -> +// ``` +// #[derive($0)] +// struct Point { +// x: u32, +// y: u32, +// } +// ``` +pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let cap = ctx.config.snippet_cap?; + let nominal = ctx.find_node_at_offset::()?; + let node_start = derive_insertion_offset(&nominal)?; + let target = nominal.syntax().text_range(); + acc.add(AssistId("generate_derive", AssistKind::None), "Add `#[derive]`", target, |builder| { + let derive_attr = nominal + .attrs() + .filter_map(|x| x.as_simple_call()) + .filter(|(name, _arg)| name == "derive") + .map(|(_name, arg)| arg) + .next(); + match derive_attr { + None => { + builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); + } + Some(tt) => { + // Just move the cursor. + builder.insert_snippet( + cap, + tt.syntax().text_range().end() - TextSize::of(')'), + "$0", + ) + } + }; + }) +} + +// Insert `derive` after doc comments. +fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { + let non_ws_child = nominal + .syntax() + .children_with_tokens() + .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; + Some(non_ws_child.text_range().start()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_target}; + + use super::*; + + #[test] + fn add_derive_new() { + check_assist( + generate_derive, + "struct Foo { a: i32, <|>}", + "#[derive($0)]\nstruct Foo { a: i32, }", + ); + check_assist( + generate_derive, + "struct Foo { <|> a: i32, }", + "#[derive($0)]\nstruct Foo { a: i32, }", + ); + } + + #[test] + fn add_derive_existing() { + check_assist( + generate_derive, + "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", + "#[derive(Clone$0)]\nstruct Foo { a: i32, }", + ); + } + + #[test] + fn add_derive_new_with_doc_comment() { + check_assist( + generate_derive, + " +/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32<|>, } + ", + " +/// `Foo` is a pretty important struct. +/// It does stuff. +#[derive($0)] +struct Foo { a: i32, } + ", + ); + } + + #[test] + fn add_derive_target() { + check_assist_target( + generate_derive, + " +struct SomeThingIrrelevant; +/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32<|>, } +struct EvenMoreIrrelevant; + ", + "/// `Foo` is a pretty important struct. +/// It does stuff. +struct Foo { a: i32, }", + ); + } +} diff --git a/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs new file mode 100644 index 000000000..e781be61e --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs @@ -0,0 +1,200 @@ +use ra_ide_db::RootDatabase; +use ra_syntax::ast::{self, AstNode, NameOwner}; +use test_utils::mark; + +use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists}; + +// Assist: generate_from_impl_for_enum +// +// Adds a From impl for an enum variant with one tuple field. +// +// ``` +// enum A { <|>One(u32) } +// ``` +// -> +// ``` +// enum A { One(u32) } +// +// impl From for A { +// fn from(v: u32) -> Self { +// A::One(v) +// } +// } +// ``` +pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let variant = ctx.find_node_at_offset::()?; + let variant_name = variant.name()?; + let enum_name = variant.parent_enum().name()?; + let field_list = match variant.kind() { + ast::StructKind::Tuple(field_list) => field_list, + _ => return None, + }; + if field_list.fields().count() != 1 { + return None; + } + let field_type = field_list.fields().next()?.type_ref()?; + let path = match field_type { + ast::TypeRef::PathType(it) => it, + _ => return None, + }; + + if existing_from_impl(&ctx.sema, &variant).is_some() { + mark::hit!(test_add_from_impl_already_exists); + return None; + } + + let target = variant.syntax().text_range(); + acc.add( + AssistId("generate_from_impl_for_enum", AssistKind::Refactor), + "Generate `From` impl for this enum variant", + target, + |edit| { + let start_offset = variant.parent_enum().syntax().text_range().end(); + let buf = format!( + r#" + +impl From<{0}> for {1} {{ + fn from(v: {0}) -> Self {{ + {1}::{2}(v) + }} +}}"#, + path.syntax(), + enum_name, + variant_name + ); + edit.insert(start_offset, buf); + }, + ) +} + +fn existing_from_impl( + sema: &'_ hir::Semantics<'_, RootDatabase>, + variant: &ast::EnumVariant, +) -> Option<()> { + let variant = sema.to_def(variant)?; + let enum_ = variant.parent_enum(sema.db); + let krate = enum_.module(sema.db).krate(); + + let from_trait = FamousDefs(sema, krate).core_convert_From()?; + + let enum_type = enum_.ty(sema.db); + + let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db); + + if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) { + Some(()) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use test_utils::mark; + + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_generate_from_impl_for_enum() { + check_assist( + generate_from_impl_for_enum, + "enum A { <|>One(u32) }", + r#"enum A { One(u32) } + +impl From for A { + fn from(v: u32) -> Self { + A::One(v) + } +}"#, + ); + } + + #[test] + fn test_generate_from_impl_for_enum_complicated_path() { + check_assist( + generate_from_impl_for_enum, + r#"enum A { <|>One(foo::bar::baz::Boo) }"#, + r#"enum A { One(foo::bar::baz::Boo) } + +impl From for A { + fn from(v: foo::bar::baz::Boo) -> Self { + A::One(v) + } +}"#, + ); + } + + fn check_not_applicable(ra_fixture: &str) { + let fixture = + format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); + check_assist_not_applicable(generate_from_impl_for_enum, &fixture) + } + + #[test] + fn test_add_from_impl_no_element() { + check_not_applicable("enum A { <|>One }"); + } + + #[test] + fn test_add_from_impl_more_than_one_element_in_tuple() { + check_not_applicable("enum A { <|>One(u32, String) }"); + } + + #[test] + fn test_add_from_impl_struct_variant() { + check_not_applicable("enum A { <|>One { x: u32 } }"); + } + + #[test] + fn test_add_from_impl_already_exists() { + mark::check!(test_add_from_impl_already_exists); + check_not_applicable( + r#" +enum A { <|>One(u32), } + +impl From for A { + fn from(v: u32) -> Self { + A::One(v) + } +} +"#, + ); + } + + #[test] + fn test_add_from_impl_different_variant_impl_exists() { + check_assist( + generate_from_impl_for_enum, + r#"enum A { <|>One(u32), Two(String), } + +impl From for A { + fn from(v: String) -> Self { + A::Two(v) + } +} + +pub trait From { + fn from(T) -> Self; +}"#, + r#"enum A { One(u32), Two(String), } + +impl From for A { + fn from(v: u32) -> Self { + A::One(v) + } +} + +impl From for A { + fn from(v: String) -> Self { + A::Two(v) + } +} + +pub trait From { + fn from(T) -> Self; +}"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/generate_function.rs b/crates/ra_assists/src/handlers/generate_function.rs new file mode 100644 index 000000000..35162791e --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_function.rs @@ -0,0 +1,1058 @@ +use hir::HirDisplay; +use ra_db::FileId; +use ra_syntax::{ + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, ArgListOwner, AstNode, ModuleItemOwner, + }, + SyntaxKind, SyntaxNode, TextSize, +}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ + assist_config::SnippetCap, + utils::{render_snippet, Cursor}, + AssistContext, AssistId, AssistKind, Assists, +}; + +// Assist: generate_function +// +// Adds a stub function with a signature matching the function under the cursor. +// +// ``` +// struct Baz; +// fn baz() -> Baz { Baz } +// fn foo() { +// bar<|>("", baz()); +// } +// +// ``` +// -> +// ``` +// struct Baz; +// fn baz() -> Baz { Baz } +// fn foo() { +// bar("", baz()); +// } +// +// fn bar(arg: &str, baz: Baz) { +// ${0:todo!()} +// } +// +// ``` +pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; + let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; + let path = path_expr.path()?; + + if ctx.sema.resolve_path(&path).is_some() { + // The function call already resolves, no need to add a function + return None; + } + + let target_module = match path.qualifier() { + Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { + Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), + _ => return None, + }, + None => None, + }; + + let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; + + let target = call.syntax().text_range(); + acc.add( + AssistId("generate_function", AssistKind::None), + format!("Generate `{}` function", function_builder.fn_name), + target, + |builder| { + let function_template = function_builder.render(); + builder.edit_file(function_template.file); + let new_fn = function_template.to_string(ctx.config.snippet_cap); + match ctx.config.snippet_cap { + Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), + None => builder.insert(function_template.insert_offset, new_fn), + } + }, + ) +} + +struct FunctionTemplate { + insert_offset: TextSize, + placeholder_expr: ast::MacroCall, + leading_ws: String, + fn_def: ast::FnDef, + trailing_ws: String, + file: FileId, +} + +impl FunctionTemplate { + fn to_string(&self, cap: Option) -> String { + let f = match cap { + Some(cap) => render_snippet( + cap, + self.fn_def.syntax(), + Cursor::Replace(self.placeholder_expr.syntax()), + ), + None => self.fn_def.to_string(), + }; + format!("{}{}{}", self.leading_ws, f, self.trailing_ws) + } +} + +struct FunctionBuilder { + target: GeneratedFunctionTarget, + fn_name: ast::Name, + type_params: Option, + params: ast::ParamList, + file: FileId, + needs_pub: bool, +} + +impl FunctionBuilder { + /// Prepares a generated function that matches `call`. + /// The function is generated in `target_module` or next to `call` + fn from_call( + ctx: &AssistContext, + call: &ast::CallExpr, + path: &ast::Path, + target_module: Option, + ) -> Option { + let mut file = ctx.frange.file_id; + let target = match &target_module { + Some(target_module) => { + let module_source = target_module.definition_source(ctx.db()); + let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; + file = in_file; + target + } + None => next_space_for_fn_after_call_site(&call)?, + }; + let needs_pub = target_module.is_some(); + let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; + let fn_name = fn_name(&path)?; + let (type_params, params) = fn_args(ctx, target_module, &call)?; + + Some(Self { target, fn_name, type_params, params, file, needs_pub }) + } + + fn render(self) -> FunctionTemplate { + let placeholder_expr = make::expr_todo(); + let fn_body = make::block_expr(vec![], Some(placeholder_expr)); + let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; + let mut fn_def = + make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); + let leading_ws; + let trailing_ws; + + let insert_offset = match self.target { + GeneratedFunctionTarget::BehindItem(it) => { + let indent = IndentLevel::from_node(&it); + leading_ws = format!("\n\n{}", indent); + fn_def = fn_def.indent(indent); + trailing_ws = String::new(); + it.text_range().end() + } + GeneratedFunctionTarget::InEmptyItemList(it) => { + let indent = IndentLevel::from_node(it.syntax()); + leading_ws = format!("\n{}", indent + 1); + fn_def = fn_def.indent(indent + 1); + trailing_ws = format!("\n{}", indent); + it.syntax().text_range().start() + TextSize::of('{') + } + }; + + let placeholder_expr = + fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); + FunctionTemplate { + insert_offset, + placeholder_expr, + leading_ws, + fn_def, + trailing_ws, + file: self.file, + } + } +} + +enum GeneratedFunctionTarget { + BehindItem(SyntaxNode), + InEmptyItemList(ast::ItemList), +} + +impl GeneratedFunctionTarget { + fn syntax(&self) -> &SyntaxNode { + match self { + GeneratedFunctionTarget::BehindItem(it) => it, + GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), + } + } +} + +fn fn_name(call: &ast::Path) -> Option { + let name = call.segment()?.syntax().to_string(); + Some(make::name(&name)) +} + +/// Computes the type variables and arguments required for the generated function +fn fn_args( + ctx: &AssistContext, + target_module: hir::Module, + call: &ast::CallExpr, +) -> Option<(Option, ast::ParamList)> { + let mut arg_names = Vec::new(); + let mut arg_types = Vec::new(); + for arg in call.arg_list()?.args() { + arg_names.push(match fn_arg_name(&arg) { + Some(name) => name, + None => String::from("arg"), + }); + arg_types.push(match fn_arg_type(ctx, target_module, &arg) { + Some(ty) => ty, + None => String::from("()"), + }); + } + deduplicate_arg_names(&mut arg_names); + let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); + Some((None, make::param_list(params))) +} + +/// Makes duplicate argument names unique by appending incrementing numbers. +/// +/// ``` +/// let mut names: Vec = +/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()]; +/// deduplicate_arg_names(&mut names); +/// let expected: Vec = +/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()]; +/// assert_eq!(names, expected); +/// ``` +fn deduplicate_arg_names(arg_names: &mut Vec) { + let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| { + *m.entry(name).or_insert(0) += 1; + m + }); + let duplicate_arg_names: FxHashSet = arg_name_counts + .into_iter() + .filter(|(_, count)| *count >= 2) + .map(|(name, _)| name.clone()) + .collect(); + + let mut counter_per_name = FxHashMap::default(); + for arg_name in arg_names.iter_mut() { + if duplicate_arg_names.contains(arg_name) { + let counter = counter_per_name.entry(arg_name.clone()).or_insert(1); + arg_name.push('_'); + arg_name.push_str(&counter.to_string()); + *counter += 1; + } + } +} + +fn fn_arg_name(fn_arg: &ast::Expr) -> Option { + match fn_arg { + ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), + _ => Some( + fn_arg + .syntax() + .descendants() + .filter(|d| ast::NameRef::can_cast(d.kind())) + .last()? + .to_string(), + ), + } +} + +fn fn_arg_type( + ctx: &AssistContext, + target_module: hir::Module, + fn_arg: &ast::Expr, +) -> Option { + let ty = ctx.sema.type_of_expr(fn_arg)?; + if ty.is_unknown() { + return None; + } + + if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) { + Some(rendered) + } else { + None + } +} + +/// Returns the position inside the current mod or file +/// directly after the current block +/// We want to write the generated function directly after +/// fns, impls or macro calls, but inside mods +fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option { + let mut ancestors = expr.syntax().ancestors().peekable(); + let mut last_ancestor: Option = None; + while let Some(next_ancestor) = ancestors.next() { + match next_ancestor.kind() { + SyntaxKind::SOURCE_FILE => { + break; + } + SyntaxKind::ITEM_LIST => { + if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) { + break; + } + } + _ => {} + } + last_ancestor = Some(next_ancestor); + } + last_ancestor.map(GeneratedFunctionTarget::BehindItem) +} + +fn next_space_for_fn_in_module( + db: &dyn hir::db::AstDatabase, + module_source: &hir::InFile, +) -> Option<(FileId, GeneratedFunctionTarget)> { + let file = module_source.file_id.original_file(db); + let assist_item = match &module_source.value { + hir::ModuleSource::SourceFile(it) => { + if let Some(last_item) = it.items().last() { + GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) + } else { + GeneratedFunctionTarget::BehindItem(it.syntax().clone()) + } + } + hir::ModuleSource::Module(it) => { + if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) { + GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) + } else { + GeneratedFunctionTarget::InEmptyItemList(it.item_list()?) + } + } + }; + Some((file, assist_item)) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn add_function_with_no_args() { + check_assist( + generate_function, + r" +fn foo() { + bar<|>(); +} +", + r" +fn foo() { + bar(); +} + +fn bar() { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_from_method() { + // This ensures that the function is correctly generated + // in the next outer mod or file + check_assist( + generate_function, + r" +impl Foo { + fn foo() { + bar<|>(); + } +} +", + r" +impl Foo { + fn foo() { + bar(); + } +} + +fn bar() { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_directly_after_current_block() { + // The new fn should not be created at the end of the file or module + check_assist( + generate_function, + r" +fn foo1() { + bar<|>(); +} + +fn foo2() {} +", + r" +fn foo1() { + bar(); +} + +fn bar() { + ${0:todo!()} +} + +fn foo2() {} +", + ) + } + + #[test] + fn add_function_with_no_args_in_same_module() { + check_assist( + generate_function, + r" +mod baz { + fn foo() { + bar<|>(); + } +} +", + r" +mod baz { + fn foo() { + bar(); + } + + fn bar() { + ${0:todo!()} + } +} +", + ) + } + + #[test] + fn add_function_with_function_call_arg() { + check_assist( + generate_function, + r" +struct Baz; +fn baz() -> Baz { todo!() } +fn foo() { + bar<|>(baz()); +} +", + r" +struct Baz; +fn baz() -> Baz { todo!() } +fn foo() { + bar(baz()); +} + +fn bar(baz: Baz) { + ${0:todo!()} +} +", + ); + } + + #[test] + fn add_function_with_method_call_arg() { + check_assist( + generate_function, + r" +struct Baz; +impl Baz { + fn foo(&self) -> Baz { + ba<|>r(self.baz()) + } + fn baz(&self) -> Baz { + Baz + } +} +", + r" +struct Baz; +impl Baz { + fn foo(&self) -> Baz { + bar(self.baz()) + } + fn baz(&self) -> Baz { + Baz + } +} + +fn bar(baz: Baz) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_with_string_literal_arg() { + check_assist( + generate_function, + r#" +fn foo() { + <|>bar("bar") +} +"#, + r#" +fn foo() { + bar("bar") +} + +fn bar(arg: &str) { + ${0:todo!()} +} +"#, + ) + } + + #[test] + fn add_function_with_char_literal_arg() { + check_assist( + generate_function, + r#" +fn foo() { + <|>bar('x') +} +"#, + r#" +fn foo() { + bar('x') +} + +fn bar(arg: char) { + ${0:todo!()} +} +"#, + ) + } + + #[test] + fn add_function_with_int_literal_arg() { + check_assist( + generate_function, + r" +fn foo() { + <|>bar(42) +} +", + r" +fn foo() { + bar(42) +} + +fn bar(arg: i32) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_with_cast_int_literal_arg() { + check_assist( + generate_function, + r" +fn foo() { + <|>bar(42 as u8) +} +", + r" +fn foo() { + bar(42 as u8) +} + +fn bar(arg: u8) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn name_of_cast_variable_is_used() { + // Ensures that the name of the cast type isn't used + // in the generated function signature. + check_assist( + generate_function, + r" +fn foo() { + let x = 42; + bar<|>(x as u8) +} +", + r" +fn foo() { + let x = 42; + bar(x as u8) +} + +fn bar(x: u8) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_with_variable_arg() { + check_assist( + generate_function, + r" +fn foo() { + let worble = (); + <|>bar(worble) +} +", + r" +fn foo() { + let worble = (); + bar(worble) +} + +fn bar(worble: ()) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_with_impl_trait_arg() { + check_assist( + generate_function, + r" +trait Foo {} +fn foo() -> impl Foo { + todo!() +} +fn baz() { + <|>bar(foo()) +} +", + r" +trait Foo {} +fn foo() -> impl Foo { + todo!() +} +fn baz() { + bar(foo()) +} + +fn bar(foo: impl Foo) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn borrowed_arg() { + check_assist( + generate_function, + r" +struct Baz; +fn baz() -> Baz { todo!() } + +fn foo() { + bar<|>(&baz()) +} +", + r" +struct Baz; +fn baz() -> Baz { todo!() } + +fn foo() { + bar(&baz()) +} + +fn bar(baz: &Baz) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_with_qualified_path_arg() { + check_assist( + generate_function, + r" +mod Baz { + pub struct Bof; + pub fn baz() -> Bof { Bof } +} +fn foo() { + <|>bar(Baz::baz()) +} +", + r" +mod Baz { + pub struct Bof; + pub fn baz() -> Bof { Bof } +} +fn foo() { + bar(Baz::baz()) +} + +fn bar(baz: Baz::Bof) { + ${0:todo!()} +} +", + ) + } + + #[test] + #[ignore] + // FIXME fix printing the generics of a `Ty` to make this test pass + fn add_function_with_generic_arg() { + check_assist( + generate_function, + r" +fn foo(t: T) { + <|>bar(t) +} +", + r" +fn foo(t: T) { + bar(t) +} + +fn bar(t: T) { + ${0:todo!()} +} +", + ) + } + + #[test] + #[ignore] + // FIXME Fix function type printing to make this test pass + fn add_function_with_fn_arg() { + check_assist( + generate_function, + r" +struct Baz; +impl Baz { + fn new() -> Self { Baz } +} +fn foo() { + <|>bar(Baz::new); +} +", + r" +struct Baz; +impl Baz { + fn new() -> Self { Baz } +} +fn foo() { + bar(Baz::new); +} + +fn bar(arg: fn() -> Baz) { + ${0:todo!()} +} +", + ) + } + + #[test] + #[ignore] + // FIXME Fix closure type printing to make this test pass + fn add_function_with_closure_arg() { + check_assist( + generate_function, + r" +fn foo() { + let closure = |x: i64| x - 1; + <|>bar(closure) +} +", + r" +fn foo() { + let closure = |x: i64| x - 1; + bar(closure) +} + +fn bar(closure: impl Fn(i64) -> i64) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn unresolveable_types_default_to_unit() { + check_assist( + generate_function, + r" +fn foo() { + <|>bar(baz) +} +", + r" +fn foo() { + bar(baz) +} + +fn bar(baz: ()) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn arg_names_dont_overlap() { + check_assist( + generate_function, + r" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + <|>bar(baz(), baz()) +} +", + r" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + bar(baz(), baz()) +} + +fn bar(baz_1: Baz, baz_2: Baz) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn arg_name_counters_start_at_1_per_name() { + check_assist( + generate_function, + r#" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + <|>bar(baz(), baz(), "foo", "bar") +} +"#, + r#" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + bar(baz(), baz(), "foo", "bar") +} + +fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { + ${0:todo!()} +} +"#, + ) + } + + #[test] + fn add_function_in_module() { + check_assist( + generate_function, + r" +mod bar {} + +fn foo() { + bar::my_fn<|>() +} +", + r" +mod bar { + pub(crate) fn my_fn() { + ${0:todo!()} + } +} + +fn foo() { + bar::my_fn() +} +", + ) + } + + #[test] + #[ignore] + // Ignored until local imports are supported. + // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 + fn qualified_path_uses_correct_scope() { + check_assist( + generate_function, + " +mod foo { + pub struct Foo; +} +fn bar() { + use foo::Foo; + let foo = Foo; + baz<|>(foo) +} +", + " +mod foo { + pub struct Foo; +} +fn bar() { + use foo::Foo; + let foo = Foo; + baz(foo) +} + +fn baz(foo: foo::Foo) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn add_function_in_module_containing_other_items() { + check_assist( + generate_function, + r" +mod bar { + fn something_else() {} +} + +fn foo() { + bar::my_fn<|>() +} +", + r" +mod bar { + fn something_else() {} + + pub(crate) fn my_fn() { + ${0:todo!()} + } +} + +fn foo() { + bar::my_fn() +} +", + ) + } + + #[test] + fn add_function_in_nested_module() { + check_assist( + generate_function, + r" +mod bar { + mod baz {} +} + +fn foo() { + bar::baz::my_fn<|>() +} +", + r" +mod bar { + mod baz { + pub(crate) fn my_fn() { + ${0:todo!()} + } + } +} + +fn foo() { + bar::baz::my_fn() +} +", + ) + } + + #[test] + fn add_function_in_another_file() { + check_assist( + generate_function, + r" +//- /main.rs +mod foo; + +fn main() { + foo::bar<|>() +} +//- /foo.rs +", + r" + + +pub(crate) fn bar() { + ${0:todo!()} +}", + ) + } + + #[test] + fn add_function_not_applicable_if_function_already_exists() { + check_assist_not_applicable( + generate_function, + r" +fn foo() { + bar<|>(); +} + +fn bar() {} +", + ) + } + + #[test] + fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() { + check_assist_not_applicable( + // bar is resolved, but baz isn't. + // The assist is only active if the cursor is on an unresolved path, + // but the assist should only be offered if the path is a function call. + generate_function, + r" +fn foo() { + bar(b<|>az); +} + +fn bar(baz: ()) {} +", + ) + } + + #[test] + #[ignore] + fn create_method_with_no_args() { + check_assist( + generate_function, + r" +struct Foo; +impl Foo { + fn foo(&self) { + self.bar()<|>; + } +} + ", + r" +struct Foo; +impl Foo { + fn foo(&self) { + self.bar(); + } + fn bar(&self) { + todo!(); + } +} + ", + ) + } +} diff --git a/crates/ra_assists/src/handlers/generate_impl.rs b/crates/ra_assists/src/handlers/generate_impl.rs new file mode 100644 index 000000000..5f287afb6 --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_impl.rs @@ -0,0 +1,109 @@ +use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; +use stdx::{format_to, SepBy}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: generate_impl +// +// Adds a new inherent impl for a type. +// +// ``` +// struct Ctx { +// data: T,<|> +// } +// ``` +// -> +// ``` +// struct Ctx { +// data: T, +// } +// +// impl Ctx { +// $0 +// } +// ``` +pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let nominal = ctx.find_node_at_offset::()?; + let name = nominal.name()?; + let target = nominal.syntax().text_range(); + acc.add( + AssistId("generate_impl", AssistKind::Refactor), + format!("Generate impl for `{}`", name), + target, + |edit| { + let type_params = nominal.type_param_list(); + let start_offset = nominal.syntax().text_range().end(); + let mut buf = String::new(); + buf.push_str("\n\nimpl"); + if let Some(type_params) = &type_params { + format_to!(buf, "{}", type_params.syntax()); + } + buf.push_str(" "); + buf.push_str(name.text().as_str()); + if let Some(type_params) = type_params { + let lifetime_params = type_params + .lifetime_params() + .filter_map(|it| it.lifetime_token()) + .map(|it| it.text().clone()); + let type_params = type_params + .type_params() + .filter_map(|it| it.name()) + .map(|it| it.text().clone()); + + let generic_params = lifetime_params.chain(type_params).sep_by(", "); + format_to!(buf, "<{}>", generic_params) + } + match ctx.config.snippet_cap { + Some(cap) => { + buf.push_str(" {\n $0\n}"); + edit.insert_snippet(cap, start_offset, buf); + } + None => { + buf.push_str(" {\n}"); + edit.insert(start_offset, buf); + } + } + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_target}; + + use super::*; + + #[test] + fn test_add_impl() { + check_assist( + generate_impl, + "struct Foo {<|>}\n", + "struct Foo {}\n\nimpl Foo {\n $0\n}\n", + ); + check_assist( + generate_impl, + "struct Foo {<|>}", + "struct Foo {}\n\nimpl Foo {\n $0\n}", + ); + check_assist( + generate_impl, + "struct Foo<'a, T: Foo<'a>> {<|>}", + "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", + ); + } + + #[test] + fn add_impl_target() { + check_assist_target( + generate_impl, + " +struct SomeThingIrrelevant; +/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {<|>} +struct EvenMoreIrrelevant; +", + "/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {}", + ); + } +} diff --git a/crates/ra_assists/src/handlers/generate_new.rs b/crates/ra_assists/src/handlers/generate_new.rs new file mode 100644 index 000000000..b37bba761 --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_new.rs @@ -0,0 +1,424 @@ +use hir::Adt; +use ra_syntax::{ + ast::{ + self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, + }, + T, +}; +use stdx::{format_to, SepBy}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: generate_new +// +// Adds a new inherent impl for a type. +// +// ``` +// struct Ctx { +// data: T,<|> +// } +// ``` +// -> +// ``` +// struct Ctx { +// data: T, +// } +// +// impl Ctx { +// fn $0new(data: T) -> Self { Self { data } } +// } +// +// ``` +pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let strukt = ctx.find_node_at_offset::()?; + + // We want to only apply this to non-union structs with named fields + let field_list = match strukt.kind() { + StructKind::Record(named) => named, + _ => return None, + }; + + // Return early if we've found an existing new fn + let impl_def = find_struct_impl(&ctx, &strukt)?; + + let target = strukt.syntax().text_range(); + acc.add(AssistId("generate_new", AssistKind::None), "Generate `new`", target, |builder| { + let mut buf = String::with_capacity(512); + + if impl_def.is_some() { + buf.push('\n'); + } + + let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); + + let params = field_list + .fields() + .filter_map(|f| { + Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax())) + }) + .sep_by(", "); + let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); + + format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); + + let start_offset = impl_def + .and_then(|impl_def| { + buf.push('\n'); + let start = impl_def + .syntax() + .descendants_with_tokens() + .find(|t| t.kind() == T!['{'])? + .text_range() + .end(); + + Some(start) + }) + .unwrap_or_else(|| { + buf = generate_impl_text(&strukt, &buf); + strukt.syntax().text_range().end() + }); + + match ctx.config.snippet_cap { + None => builder.insert(start_offset, buf), + Some(cap) => { + buf = buf.replace("fn new", "fn $0new"); + builder.insert_snippet(cap, start_offset, buf); + } + } + }) +} + +// Generates the surrounding `impl Type { }` including type and lifetime +// parameters +fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { + let type_params = strukt.type_param_list(); + let mut buf = String::with_capacity(code.len()); + buf.push_str("\n\nimpl"); + if let Some(type_params) = &type_params { + format_to!(buf, "{}", type_params.syntax()); + } + buf.push_str(" "); + buf.push_str(strukt.name().unwrap().text().as_str()); + if let Some(type_params) = type_params { + let lifetime_params = type_params + .lifetime_params() + .filter_map(|it| it.lifetime_token()) + .map(|it| it.text().clone()); + let type_params = + type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); + format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", ")) + } + + format_to!(buf, " {{\n{}\n}}\n", code); + + buf +} + +// Uses a syntax-driven approach to find any impl blocks for the struct that +// exist within the module/file +// +// Returns `None` if we've found an existing `new` fn +// +// FIXME: change the new fn checking to a more semantic approach when that's more +// viable (e.g. we process proc macros, etc) +fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option> { + let db = ctx.db(); + let module = strukt.syntax().ancestors().find(|node| { + ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) + })?; + + let struct_def = ctx.sema.to_def(strukt)?; + + let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| { + let blk = ctx.sema.to_def(&impl_blk)?; + + // FIXME: handle e.g. `struct S; impl S {}` + // (we currently use the wrong type parameter) + // also we wouldn't want to use e.g. `impl S` + let same_ty = match blk.target_ty(db).as_adt() { + Some(def) => def == Adt::Struct(struct_def), + None => false, + }; + let not_trait_impl = blk.target_trait(db).is_none(); + + if !(same_ty && not_trait_impl) { + None + } else { + Some(impl_blk) + } + }); + + if let Some(ref impl_blk) = block { + if has_new_fn(impl_blk) { + return None; + } + } + + Some(block) +} + +fn has_new_fn(imp: &ast::ImplDef) -> bool { + if let Some(il) = imp.item_list() { + for item in il.assoc_items() { + if let ast::AssocItem::FnDef(f) = item { + if let Some(name) = f.name() { + if name.text().eq_ignore_ascii_case("new") { + return true; + } + } + } + } + } + + false +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; + + use super::*; + + #[test] + #[rustfmt::skip] + fn test_generate_new() { + // Check output of generation + check_assist( + generate_new, +"struct Foo {<|>}", +"struct Foo {} + +impl Foo { + fn $0new() -> Self { Self { } } +} +", + ); + check_assist( + generate_new, +"struct Foo {<|>}", +"struct Foo {} + +impl Foo { + fn $0new() -> Self { Self { } } +} +", + ); + check_assist( + generate_new, +"struct Foo<'a, T: Foo<'a>> {<|>}", +"struct Foo<'a, T: Foo<'a>> {} + +impl<'a, T: Foo<'a>> Foo<'a, T> { + fn $0new() -> Self { Self { } } +} +", + ); + check_assist( + generate_new, +"struct Foo { baz: String <|>}", +"struct Foo { baz: String } + +impl Foo { + fn $0new(baz: String) -> Self { Self { baz } } +} +", + ); + check_assist( + generate_new, +"struct Foo { baz: String, qux: Vec <|>}", +"struct Foo { baz: String, qux: Vec } + +impl Foo { + fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } +} +", + ); + + // Check that visibility modifiers don't get brought in for fields + check_assist( + generate_new, +"struct Foo { pub baz: String, pub qux: Vec <|>}", +"struct Foo { pub baz: String, pub qux: Vec } + +impl Foo { + fn $0new(baz: String, qux: Vec) -> Self { Self { baz, qux } } +} +", + ); + + // Check that it reuses existing impls + check_assist( + generate_new, +"struct Foo {<|>} + +impl Foo {} +", +"struct Foo {} + +impl Foo { + fn $0new() -> Self { Self { } } +} +", + ); + check_assist( + generate_new, +"struct Foo {<|>} + +impl Foo { + fn qux(&self) {} +} +", +"struct Foo {} + +impl Foo { + fn $0new() -> Self { Self { } } + + fn qux(&self) {} +} +", + ); + + check_assist( + generate_new, +"struct Foo {<|>} + +impl Foo { + fn qux(&self) {} + fn baz() -> i32 { + 5 + } +} +", +"struct Foo {} + +impl Foo { + fn $0new() -> Self { Self { } } + + fn qux(&self) {} + fn baz() -> i32 { + 5 + } +} +", + ); + + // Check visibility of new fn based on struct + check_assist( + generate_new, +"pub struct Foo {<|>}", +"pub struct Foo {} + +impl Foo { + pub fn $0new() -> Self { Self { } } +} +", + ); + check_assist( + generate_new, +"pub(crate) struct Foo {<|>}", +"pub(crate) struct Foo {} + +impl Foo { + pub(crate) fn $0new() -> Self { Self { } } +} +", + ); + } + + #[test] + fn generate_new_not_applicable_if_fn_exists() { + check_assist_not_applicable( + generate_new, + " +struct Foo {<|>} + +impl Foo { + fn new() -> Self { + Self + } +}", + ); + + check_assist_not_applicable( + generate_new, + " +struct Foo {<|>} + +impl Foo { + fn New() -> Self { + Self + } +}", + ); + } + + #[test] + fn generate_new_target() { + check_assist_target( + generate_new, + " +struct SomeThingIrrelevant; +/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {<|>} +struct EvenMoreIrrelevant; +", + "/// Has a lifetime parameter +struct Foo<'a, T: Foo<'a>> {}", + ); + } + + #[test] + fn test_unrelated_new() { + check_assist( + generate_new, + r##" +pub struct AstId { + file_id: HirFileId, + file_ast_id: FileAstId, +} + +impl AstId { + pub fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { + AstId { file_id, file_ast_id } + } +} + +pub struct Source { + pub file_id: HirFileId,<|> + pub ast: T, +} + +impl Source { + pub fn map U, U>(self, f: F) -> Source { + Source { file_id: self.file_id, ast: f(self.ast) } + } +} +"##, + r##" +pub struct AstId { + file_id: HirFileId, + file_ast_id: FileAstId, +} + +impl AstId { + pub fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { + AstId { file_id, file_ast_id } + } +} + +pub struct Source { + pub file_id: HirFileId, + pub ast: T, +} + +impl Source { + pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } + + pub fn map U, U>(self, f: F) -> Source { + Source { file_id: self.file_id, ast: f(self.ast) } + } +} +"##, + ); + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 65cda95ee..5b9def052 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -112,13 +112,8 @@ mod handlers { pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; mod add_custom_impl; - mod add_derive; mod add_explicit_type; - mod add_from_impl_for_enum; - mod add_function; - mod add_impl; mod add_missing_impl_members; - mod add_new; mod add_turbo_fish; mod apply_demorgan; mod auto_import; @@ -132,6 +127,11 @@ mod handlers { mod flip_binexpr; mod flip_comma; mod flip_trait_bound; + mod generate_derive; + mod generate_from_impl_for_enum; + mod generate_function; + mod generate_impl; + mod generate_new; mod inline_local_variable; mod introduce_named_lifetime; mod invert_if; @@ -154,12 +154,7 @@ mod handlers { &[ // These are alphabetic for the foolish consistency add_custom_impl::add_custom_impl, - add_derive::add_derive, add_explicit_type::add_explicit_type, - add_from_impl_for_enum::add_from_impl_for_enum, - add_function::add_function, - add_impl::add_impl, - add_new::add_new, add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, @@ -173,6 +168,11 @@ mod handlers { flip_binexpr::flip_binexpr, flip_comma::flip_comma, flip_trait_bound::flip_trait_bound, + generate_derive::generate_derive, + generate_from_impl_for_enum::generate_from_impl_for_enum, + generate_function::generate_function, + generate_impl::generate_impl, + generate_new::generate_new, inline_local_variable::inline_local_variable, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 31ea888c5..eff7feded 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -21,26 +21,6 @@ impl Debug for S { ) } -#[test] -fn doctest_add_derive() { - check_doc_test( - "add_derive", - r#####" -struct Point { - x: u32, - y: u32,<|> -} -"#####, - r#####" -#[derive($0)] -struct Point { - x: u32, - y: u32, -} -"#####, - ) -} - #[test] fn doctest_add_explicit_type() { check_doc_test( @@ -58,52 +38,6 @@ fn main() { ) } -#[test] -fn doctest_add_from_impl_for_enum() { - check_doc_test( - "add_from_impl_for_enum", - r#####" -enum A { <|>One(u32) } -"#####, - r#####" -enum A { One(u32) } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -} -"#####, - ) -} - -#[test] -fn doctest_add_function() { - check_doc_test( - "add_function", - r#####" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar<|>("", baz()); -} - -"#####, - r#####" -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar("", baz()); -} - -fn bar(arg: &str, baz: Baz) { - ${0:todo!()} -} - -"#####, - ) -} - #[test] fn doctest_add_hash() { check_doc_test( @@ -121,27 +55,6 @@ fn main() { ) } -#[test] -fn doctest_add_impl() { - check_doc_test( - "add_impl", - r#####" -struct Ctx { - data: T,<|> -} -"#####, - r#####" -struct Ctx { - data: T, -} - -impl Ctx { - $0 -} -"#####, - ) -} - #[test] fn doctest_add_impl_default_members() { check_doc_test( @@ -208,28 +121,6 @@ impl Trait for () { ) } -#[test] -fn doctest_add_new() { - check_doc_test( - "add_new", - r#####" -struct Ctx { - data: T,<|> -} -"#####, - r#####" -struct Ctx { - data: T, -} - -impl Ctx { - fn $0new(data: T) -> Self { Self { data } } -} - -"#####, - ) -} - #[test] fn doctest_add_turbo_fish() { check_doc_test( @@ -466,6 +357,115 @@ fn foo() { } ) } +#[test] +fn doctest_generate_derive() { + check_doc_test( + "generate_derive", + r#####" +struct Point { + x: u32, + y: u32,<|> +} +"#####, + r#####" +#[derive($0)] +struct Point { + x: u32, + y: u32, +} +"#####, + ) +} + +#[test] +fn doctest_generate_from_impl_for_enum() { + check_doc_test( + "generate_from_impl_for_enum", + r#####" +enum A { <|>One(u32) } +"#####, + r#####" +enum A { One(u32) } + +impl From for A { + fn from(v: u32) -> Self { + A::One(v) + } +} +"#####, + ) +} + +#[test] +fn doctest_generate_function() { + check_doc_test( + "generate_function", + r#####" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + bar<|>("", baz()); +} + +"#####, + r#####" +struct Baz; +fn baz() -> Baz { Baz } +fn foo() { + bar("", baz()); +} + +fn bar(arg: &str, baz: Baz) { + ${0:todo!()} +} + +"#####, + ) +} + +#[test] +fn doctest_generate_impl() { + check_doc_test( + "generate_impl", + r#####" +struct Ctx { + data: T,<|> +} +"#####, + r#####" +struct Ctx { + data: T, +} + +impl Ctx { + $0 +} +"#####, + ) +} + +#[test] +fn doctest_generate_new() { + check_doc_test( + "generate_new", + r#####" +struct Ctx { + data: T,<|> +} +"#####, + r#####" +struct Ctx { + data: T, +} + +impl Ctx { + fn $0new(data: T) -> Self { Self { data } } +} + +"#####, + ) +} + #[test] fn doctest_inline_local_variable() { check_doc_test( diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index d38ac7f17..f99935170 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -56,8 +56,8 @@ fn check_todo(path: &Path, text: &str) { // Some of our assists generate `todo!()` so those files are whitelisted. "tests/generated.rs", "handlers/add_missing_impl_members.rs", - "handlers/add_function.rs", "handlers/add_turbo_fish.rs", + "handlers/generate_function.rs", // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make. "ast/make.rs", ]; -- cgit v1.2.3 From d09f6923009943cee1b24571c2905c3c0bfb49c7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 3 Jul 2020 19:14:42 +0200 Subject: Add AssistKind::Generate --- crates/ra_assists/src/handlers/generate_derive.rs | 47 ++++++++++++---------- .../src/handlers/generate_from_impl_for_enum.rs | 2 +- .../ra_assists/src/handlers/generate_function.rs | 2 +- crates/ra_assists/src/handlers/generate_impl.rs | 2 +- crates/ra_assists/src/handlers/generate_new.rs | 2 +- crates/ra_assists/src/lib.rs | 1 + crates/rust-analyzer/src/to_proto.rs | 2 +- 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/crates/ra_assists/src/handlers/generate_derive.rs b/crates/ra_assists/src/handlers/generate_derive.rs index 8fc522eef..6ccf39900 100644 --- a/crates/ra_assists/src/handlers/generate_derive.rs +++ b/crates/ra_assists/src/handlers/generate_derive.rs @@ -29,27 +29,32 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option< let nominal = ctx.find_node_at_offset::()?; let node_start = derive_insertion_offset(&nominal)?; let target = nominal.syntax().text_range(); - acc.add(AssistId("generate_derive", AssistKind::None), "Add `#[derive]`", target, |builder| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_simple_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - match derive_attr { - None => { - builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); - } - Some(tt) => { - // Just move the cursor. - builder.insert_snippet( - cap, - tt.syntax().text_range().end() - TextSize::of(')'), - "$0", - ) - } - }; - }) + acc.add( + AssistId("generate_derive", AssistKind::Generate), + "Add `#[derive]`", + target, + |builder| { + let derive_attr = nominal + .attrs() + .filter_map(|x| x.as_simple_call()) + .filter(|(name, _arg)| name == "derive") + .map(|(_name, arg)| arg) + .next(); + match derive_attr { + None => { + builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); + } + Some(tt) => { + // Just move the cursor. + builder.insert_snippet( + cap, + tt.syntax().text_range().end() - TextSize::of(')'), + "$0", + ) + } + }; + }, + ) } // Insert `derive` after doc comments. diff --git a/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs index e781be61e..a347e3c2e 100644 --- a/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs @@ -45,7 +45,7 @@ pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext let target = variant.syntax().text_range(); acc.add( - AssistId("generate_from_impl_for_enum", AssistKind::Refactor), + AssistId("generate_from_impl_for_enum", AssistKind::Generate), "Generate `From` impl for this enum variant", target, |edit| { diff --git a/crates/ra_assists/src/handlers/generate_function.rs b/crates/ra_assists/src/handlers/generate_function.rs index 35162791e..b721b96bb 100644 --- a/crates/ra_assists/src/handlers/generate_function.rs +++ b/crates/ra_assists/src/handlers/generate_function.rs @@ -63,7 +63,7 @@ pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Optio let target = call.syntax().text_range(); acc.add( - AssistId("generate_function", AssistKind::None), + AssistId("generate_function", AssistKind::Generate), format!("Generate `{}` function", function_builder.fn_name), target, |builder| { diff --git a/crates/ra_assists/src/handlers/generate_impl.rs b/crates/ra_assists/src/handlers/generate_impl.rs index 5f287afb6..cbbac1d7f 100644 --- a/crates/ra_assists/src/handlers/generate_impl.rs +++ b/crates/ra_assists/src/handlers/generate_impl.rs @@ -27,7 +27,7 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<() let name = nominal.name()?; let target = nominal.syntax().text_range(); acc.add( - AssistId("generate_impl", AssistKind::Refactor), + AssistId("generate_impl", AssistKind::Generate), format!("Generate impl for `{}`", name), target, |edit| { diff --git a/crates/ra_assists/src/handlers/generate_new.rs b/crates/ra_assists/src/handlers/generate_new.rs index b37bba761..e27def1d8 100644 --- a/crates/ra_assists/src/handlers/generate_new.rs +++ b/crates/ra_assists/src/handlers/generate_new.rs @@ -42,7 +42,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let impl_def = find_struct_impl(&ctx, &strukt)?; let target = strukt.syntax().text_range(); - acc.add(AssistId("generate_new", AssistKind::None), "Generate `new`", target, |builder| { + acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { let mut buf = String::with_capacity(512); if impl_def.is_some() { diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 5b9def052..3d61fbded 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -30,6 +30,7 @@ pub use assist_config::AssistConfig; pub enum AssistKind { None, QuickFix, + Generate, Refactor, RefactorExtract, RefactorInline, diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 2312a6e4d..83c9ac35c 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -629,7 +629,7 @@ pub(crate) fn call_hierarchy_item( pub(crate) fn code_action_kind(kind: AssistKind) -> String { match kind { - AssistKind::None => lsp_types::code_action_kind::EMPTY, + AssistKind::None | AssistKind::Generate => lsp_types::code_action_kind::EMPTY, AssistKind::QuickFix => lsp_types::code_action_kind::QUICKFIX, AssistKind::Refactor => lsp_types::code_action_kind::REFACTOR, AssistKind::RefactorExtract => lsp_types::code_action_kind::REFACTOR_EXTRACT, -- cgit v1.2.3