diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/add_function.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/add_function.rs | 187 |
1 files changed, 124 insertions, 63 deletions
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 6b5616aa9..de016ae4e 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -1,7 +1,11 @@ | |||
1 | use hir::HirDisplay; | 1 | use hir::HirDisplay; |
2 | use ra_db::FileId; | 2 | use ra_db::FileId; |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, edit::IndentLevel, ArgListOwner, AstNode, ModuleItemOwner}, | 4 | ast::{ |
5 | self, | ||
6 | edit::{AstNodeEdit, IndentLevel}, | ||
7 | ArgListOwner, AstNode, ModuleItemOwner, | ||
8 | }, | ||
5 | SyntaxKind, SyntaxNode, TextSize, | 9 | SyntaxKind, SyntaxNode, TextSize, |
6 | }; | 10 | }; |
7 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
@@ -43,16 +47,12 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
43 | return None; | 47 | return None; |
44 | } | 48 | } |
45 | 49 | ||
46 | let target_module = if let Some(qualifier) = path.qualifier() { | 50 | let target_module = match path.qualifier() { |
47 | if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = | 51 | Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { |
48 | ctx.sema.resolve_path(&qualifier) | 52 | Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), |
49 | { | 53 | _ => return None, |
50 | Some(module.definition_source(ctx.sema.db)) | 54 | }, |
51 | } else { | 55 | None => None, |
52 | return None; | ||
53 | } | ||
54 | } else { | ||
55 | None | ||
56 | }; | 56 | }; |
57 | 57 | ||
58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
@@ -83,25 +83,29 @@ struct FunctionBuilder { | |||
83 | } | 83 | } |
84 | 84 | ||
85 | impl FunctionBuilder { | 85 | impl FunctionBuilder { |
86 | /// Prepares a generated function that matches `call` in `generate_in` | 86 | /// Prepares a generated function that matches `call`. |
87 | /// (or as close to `call` as possible, if `generate_in` is `None`) | 87 | /// The function is generated in `target_module` or next to `call` |
88 | fn from_call( | 88 | fn from_call( |
89 | ctx: &AssistContext, | 89 | ctx: &AssistContext, |
90 | call: &ast::CallExpr, | 90 | call: &ast::CallExpr, |
91 | path: &ast::Path, | 91 | path: &ast::Path, |
92 | target_module: Option<hir::InFile<hir::ModuleSource>>, | 92 | target_module: Option<hir::Module>, |
93 | ) -> Option<Self> { | 93 | ) -> Option<Self> { |
94 | let needs_pub = target_module.is_some(); | ||
95 | let mut file = ctx.frange.file_id; | 94 | let mut file = ctx.frange.file_id; |
96 | let target = if let Some(target_module) = target_module { | 95 | let target = match &target_module { |
97 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; | 96 | Some(target_module) => { |
98 | file = in_file; | 97 | let module_source = target_module.definition_source(ctx.db); |
99 | target | 98 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; |
100 | } else { | 99 | file = in_file; |
101 | next_space_for_fn_after_call_site(&call)? | 100 | target |
101 | } | ||
102 | None => next_space_for_fn_after_call_site(&call)?, | ||
102 | }; | 103 | }; |
104 | let needs_pub = target_module.is_some(); | ||
105 | let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; | ||
103 | let fn_name = fn_name(&path)?; | 106 | let fn_name = fn_name(&path)?; |
104 | let (type_params, params) = fn_args(ctx, &call)?; | 107 | let (type_params, params) = fn_args(ctx, target_module, &call)?; |
108 | |||
105 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) | 109 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) |
106 | } | 110 | } |
107 | 111 | ||
@@ -116,17 +120,16 @@ impl FunctionBuilder { | |||
116 | let (fn_def, insert_offset) = match self.target { | 120 | let (fn_def, insert_offset) = match self.target { |
117 | GeneratedFunctionTarget::BehindItem(it) => { | 121 | GeneratedFunctionTarget::BehindItem(it) => { |
118 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); | 122 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); |
119 | let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); | 123 | let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); |
120 | (indented, it.text_range().end()) | 124 | (indented, it.text_range().end()) |
121 | } | 125 | } |
122 | GeneratedFunctionTarget::InEmptyItemList(it) => { | 126 | GeneratedFunctionTarget::InEmptyItemList(it) => { |
123 | let indent_once = IndentLevel(1); | 127 | let indent_once = IndentLevel(1); |
124 | let indent = IndentLevel::from_node(it.syntax()); | 128 | let indent = IndentLevel::from_node(it.syntax()); |
125 | |||
126 | let fn_def = ast::make::add_leading_newlines(1, fn_def); | 129 | let fn_def = ast::make::add_leading_newlines(1, fn_def); |
127 | let fn_def = indent_once.increase_indent(fn_def); | 130 | let fn_def = fn_def.indent(indent_once); |
128 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); | 131 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); |
129 | let fn_def = indent.increase_indent(fn_def); | 132 | let fn_def = fn_def.indent(indent); |
130 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) | 133 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) |
131 | } | 134 | } |
132 | }; | 135 | }; |
@@ -144,6 +147,15 @@ enum GeneratedFunctionTarget { | |||
144 | InEmptyItemList(ast::ItemList), | 147 | InEmptyItemList(ast::ItemList), |
145 | } | 148 | } |
146 | 149 | ||
150 | impl GeneratedFunctionTarget { | ||
151 | fn syntax(&self) -> &SyntaxNode { | ||
152 | match self { | ||
153 | GeneratedFunctionTarget::BehindItem(it) => it, | ||
154 | GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
147 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | 159 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { |
148 | let name = call.segment()?.syntax().to_string(); | 160 | let name = call.segment()?.syntax().to_string(); |
149 | Some(ast::make::name(&name)) | 161 | Some(ast::make::name(&name)) |
@@ -152,17 +164,17 @@ fn fn_name(call: &ast::Path) -> Option<ast::Name> { | |||
152 | /// Computes the type variables and arguments required for the generated function | 164 | /// Computes the type variables and arguments required for the generated function |
153 | fn fn_args( | 165 | fn fn_args( |
154 | ctx: &AssistContext, | 166 | ctx: &AssistContext, |
167 | target_module: hir::Module, | ||
155 | call: &ast::CallExpr, | 168 | call: &ast::CallExpr, |
156 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { | 169 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
157 | let mut arg_names = Vec::new(); | 170 | let mut arg_names = Vec::new(); |
158 | let mut arg_types = Vec::new(); | 171 | let mut arg_types = Vec::new(); |
159 | for arg in call.arg_list()?.args() { | 172 | for arg in call.arg_list()?.args() { |
160 | let arg_name = match fn_arg_name(&arg) { | 173 | arg_names.push(match fn_arg_name(&arg) { |
161 | Some(name) => name, | 174 | Some(name) => name, |
162 | None => String::from("arg"), | 175 | None => String::from("arg"), |
163 | }; | 176 | }); |
164 | arg_names.push(arg_name); | 177 | arg_types.push(match fn_arg_type(ctx, target_module, &arg) { |
165 | arg_types.push(match fn_arg_type(ctx, &arg) { | ||
166 | Some(ty) => ty, | 178 | Some(ty) => ty, |
167 | None => String::from("()"), | 179 | None => String::from("()"), |
168 | }); | 180 | }); |
@@ -218,12 +230,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> { | |||
218 | } | 230 | } |
219 | } | 231 | } |
220 | 232 | ||
221 | fn fn_arg_type(ctx: &AssistContext, fn_arg: &ast::Expr) -> Option<String> { | 233 | fn fn_arg_type( |
234 | ctx: &AssistContext, | ||
235 | target_module: hir::Module, | ||
236 | fn_arg: &ast::Expr, | ||
237 | ) -> Option<String> { | ||
222 | let ty = ctx.sema.type_of_expr(fn_arg)?; | 238 | let ty = ctx.sema.type_of_expr(fn_arg)?; |
223 | if ty.is_unknown() { | 239 | if ty.is_unknown() { |
224 | return None; | 240 | return None; |
225 | } | 241 | } |
226 | Some(ty.display(ctx.sema.db).to_string()) | 242 | |
243 | if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { | ||
244 | Some(rendered) | ||
245 | } else { | ||
246 | None | ||
247 | } | ||
227 | } | 248 | } |
228 | 249 | ||
229 | /// Returns the position inside the current mod or file | 250 | /// Returns the position inside the current mod or file |
@@ -252,10 +273,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu | |||
252 | 273 | ||
253 | fn next_space_for_fn_in_module( | 274 | fn next_space_for_fn_in_module( |
254 | db: &dyn hir::db::AstDatabase, | 275 | db: &dyn hir::db::AstDatabase, |
255 | module: hir::InFile<hir::ModuleSource>, | 276 | module_source: &hir::InFile<hir::ModuleSource>, |
256 | ) -> Option<(FileId, GeneratedFunctionTarget)> { | 277 | ) -> Option<(FileId, GeneratedFunctionTarget)> { |
257 | let file = module.file_id.original_file(db); | 278 | let file = module_source.file_id.original_file(db); |
258 | let assist_item = match module.value { | 279 | let assist_item = match &module_source.value { |
259 | hir::ModuleSource::SourceFile(it) => { | 280 | hir::ModuleSource::SourceFile(it) => { |
260 | if let Some(last_item) = it.items().last() { | 281 | if let Some(last_item) = it.items().last() { |
261 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | 282 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) |
@@ -599,8 +620,33 @@ fn bar(foo: impl Foo) { | |||
599 | } | 620 | } |
600 | 621 | ||
601 | #[test] | 622 | #[test] |
602 | #[ignore] | 623 | fn borrowed_arg() { |
603 | // FIXME print paths properly to make this test pass | 624 | check_assist( |
625 | add_function, | ||
626 | r" | ||
627 | struct Baz; | ||
628 | fn baz() -> Baz { todo!() } | ||
629 | |||
630 | fn foo() { | ||
631 | bar<|>(&baz()) | ||
632 | } | ||
633 | ", | ||
634 | r" | ||
635 | struct Baz; | ||
636 | fn baz() -> Baz { todo!() } | ||
637 | |||
638 | fn foo() { | ||
639 | bar(&baz()) | ||
640 | } | ||
641 | |||
642 | fn bar(baz: &Baz) { | ||
643 | <|>todo!() | ||
644 | } | ||
645 | ", | ||
646 | ) | ||
647 | } | ||
648 | |||
649 | #[test] | ||
604 | fn add_function_with_qualified_path_arg() { | 650 | fn add_function_with_qualified_path_arg() { |
605 | check_assist( | 651 | check_assist( |
606 | add_function, | 652 | add_function, |
@@ -609,10 +655,8 @@ mod Baz { | |||
609 | pub struct Bof; | 655 | pub struct Bof; |
610 | pub fn baz() -> Bof { Bof } | 656 | pub fn baz() -> Bof { Bof } |
611 | } | 657 | } |
612 | mod Foo { | 658 | fn foo() { |
613 | fn foo() { | 659 | <|>bar(Baz::baz()) |
614 | <|>bar(super::Baz::baz()) | ||
615 | } | ||
616 | } | 660 | } |
617 | ", | 661 | ", |
618 | r" | 662 | r" |
@@ -620,14 +664,12 @@ mod Baz { | |||
620 | pub struct Bof; | 664 | pub struct Bof; |
621 | pub fn baz() -> Bof { Bof } | 665 | pub fn baz() -> Bof { Bof } |
622 | } | 666 | } |
623 | mod Foo { | 667 | fn foo() { |
624 | fn foo() { | 668 | bar(Baz::baz()) |
625 | bar(super::Baz::baz()) | 669 | } |
626 | } | ||
627 | 670 | ||
628 | fn bar(baz: super::Baz::Bof) { | 671 | fn bar(baz: Baz::Bof) { |
629 | <|>todo!() | 672 | <|>todo!() |
630 | } | ||
631 | } | 673 | } |
632 | ", | 674 | ", |
633 | ) | 675 | ) |
@@ -809,6 +851,40 @@ fn foo() { | |||
809 | } | 851 | } |
810 | 852 | ||
811 | #[test] | 853 | #[test] |
854 | #[ignore] | ||
855 | // Ignored until local imports are supported. | ||
856 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 | ||
857 | fn qualified_path_uses_correct_scope() { | ||
858 | check_assist( | ||
859 | add_function, | ||
860 | " | ||
861 | mod foo { | ||
862 | pub struct Foo; | ||
863 | } | ||
864 | fn bar() { | ||
865 | use foo::Foo; | ||
866 | let foo = Foo; | ||
867 | baz<|>(foo) | ||
868 | } | ||
869 | ", | ||
870 | " | ||
871 | mod foo { | ||
872 | pub struct Foo; | ||
873 | } | ||
874 | fn bar() { | ||
875 | use foo::Foo; | ||
876 | let foo = Foo; | ||
877 | baz(foo) | ||
878 | } | ||
879 | |||
880 | fn baz(foo: foo::Foo) { | ||
881 | <|>todo!() | ||
882 | } | ||
883 | ", | ||
884 | ) | ||
885 | } | ||
886 | |||
887 | #[test] | ||
812 | fn add_function_in_module_containing_other_items() { | 888 | fn add_function_in_module_containing_other_items() { |
813 | check_assist( | 889 | check_assist( |
814 | add_function, | 890 | add_function, |
@@ -920,21 +996,6 @@ fn bar(baz: ()) {} | |||
920 | } | 996 | } |
921 | 997 | ||
922 | #[test] | 998 | #[test] |
923 | fn add_function_not_applicable_if_function_path_not_singleton() { | ||
924 | // In the future this assist could be extended to generate functions | ||
925 | // if the path is in the same crate (or even the same workspace). | ||
926 | // For the beginning, I think this is fine. | ||
927 | check_assist_not_applicable( | ||
928 | add_function, | ||
929 | r" | ||
930 | fn foo() { | ||
931 | other_crate::bar<|>(); | ||
932 | } | ||
933 | ", | ||
934 | ) | ||
935 | } | ||
936 | |||
937 | #[test] | ||
938 | #[ignore] | 999 | #[ignore] |
939 | fn create_method_with_no_args() { | 1000 | fn create_method_with_no_args() { |
940 | check_assist( | 1001 | check_assist( |