diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/handlers/add_function.rs | 174 |
1 files changed, 116 insertions, 58 deletions
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 6b5616aa9..95faf0f4f 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -43,16 +43,12 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
43 | return None; | 43 | return None; |
44 | } | 44 | } |
45 | 45 | ||
46 | let target_module = if let Some(qualifier) = path.qualifier() { | 46 | let target_module = match path.qualifier() { |
47 | if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = | 47 | Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { |
48 | ctx.sema.resolve_path(&qualifier) | 48 | Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), |
49 | { | 49 | _ => return None, |
50 | Some(module.definition_source(ctx.sema.db)) | 50 | }, |
51 | } else { | 51 | None => None, |
52 | return None; | ||
53 | } | ||
54 | } else { | ||
55 | None | ||
56 | }; | 52 | }; |
57 | 53 | ||
58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 54 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
@@ -83,25 +79,29 @@ struct FunctionBuilder { | |||
83 | } | 79 | } |
84 | 80 | ||
85 | impl FunctionBuilder { | 81 | impl FunctionBuilder { |
86 | /// Prepares a generated function that matches `call` in `generate_in` | 82 | /// Prepares a generated function that matches `call`. |
87 | /// (or as close to `call` as possible, if `generate_in` is `None`) | 83 | /// The function is generated in `target_module` or next to `call` |
88 | fn from_call( | 84 | fn from_call( |
89 | ctx: &AssistContext, | 85 | ctx: &AssistContext, |
90 | call: &ast::CallExpr, | 86 | call: &ast::CallExpr, |
91 | path: &ast::Path, | 87 | path: &ast::Path, |
92 | target_module: Option<hir::InFile<hir::ModuleSource>>, | 88 | target_module: Option<hir::Module>, |
93 | ) -> Option<Self> { | 89 | ) -> Option<Self> { |
94 | let needs_pub = target_module.is_some(); | ||
95 | let mut file = ctx.frange.file_id; | 90 | let mut file = ctx.frange.file_id; |
96 | let target = if let Some(target_module) = target_module { | 91 | let target = match &target_module { |
97 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; | 92 | Some(target_module) => { |
98 | file = in_file; | 93 | let module_source = target_module.definition_source(ctx.db); |
99 | target | 94 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; |
100 | } else { | 95 | file = in_file; |
101 | next_space_for_fn_after_call_site(&call)? | 96 | target |
97 | } | ||
98 | None => next_space_for_fn_after_call_site(&call)?, | ||
102 | }; | 99 | }; |
100 | let needs_pub = target_module.is_some(); | ||
101 | let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; | ||
103 | let fn_name = fn_name(&path)?; | 102 | let fn_name = fn_name(&path)?; |
104 | let (type_params, params) = fn_args(ctx, &call)?; | 103 | let (type_params, params) = fn_args(ctx, target_module, &call)?; |
104 | |||
105 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) | 105 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) |
106 | } | 106 | } |
107 | 107 | ||
@@ -144,6 +144,15 @@ enum GeneratedFunctionTarget { | |||
144 | InEmptyItemList(ast::ItemList), | 144 | InEmptyItemList(ast::ItemList), |
145 | } | 145 | } |
146 | 146 | ||
147 | impl GeneratedFunctionTarget { | ||
148 | fn syntax(&self) -> &SyntaxNode { | ||
149 | match self { | ||
150 | GeneratedFunctionTarget::BehindItem(it) => it, | ||
151 | GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
147 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | 156 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { |
148 | let name = call.segment()?.syntax().to_string(); | 157 | let name = call.segment()?.syntax().to_string(); |
149 | Some(ast::make::name(&name)) | 158 | Some(ast::make::name(&name)) |
@@ -152,17 +161,17 @@ fn fn_name(call: &ast::Path) -> Option<ast::Name> { | |||
152 | /// Computes the type variables and arguments required for the generated function | 161 | /// Computes the type variables and arguments required for the generated function |
153 | fn fn_args( | 162 | fn fn_args( |
154 | ctx: &AssistContext, | 163 | ctx: &AssistContext, |
164 | target_module: hir::Module, | ||
155 | call: &ast::CallExpr, | 165 | call: &ast::CallExpr, |
156 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { | 166 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
157 | let mut arg_names = Vec::new(); | 167 | let mut arg_names = Vec::new(); |
158 | let mut arg_types = Vec::new(); | 168 | let mut arg_types = Vec::new(); |
159 | for arg in call.arg_list()?.args() { | 169 | for arg in call.arg_list()?.args() { |
160 | let arg_name = match fn_arg_name(&arg) { | 170 | arg_names.push(match fn_arg_name(&arg) { |
161 | Some(name) => name, | 171 | Some(name) => name, |
162 | None => String::from("arg"), | 172 | None => String::from("arg"), |
163 | }; | 173 | }); |
164 | arg_names.push(arg_name); | 174 | 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, | 175 | Some(ty) => ty, |
167 | None => String::from("()"), | 176 | None => String::from("()"), |
168 | }); | 177 | }); |
@@ -218,12 +227,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> { | |||
218 | } | 227 | } |
219 | } | 228 | } |
220 | 229 | ||
221 | fn fn_arg_type(ctx: &AssistContext, fn_arg: &ast::Expr) -> Option<String> { | 230 | fn fn_arg_type( |
231 | ctx: &AssistContext, | ||
232 | target_module: hir::Module, | ||
233 | fn_arg: &ast::Expr, | ||
234 | ) -> Option<String> { | ||
222 | let ty = ctx.sema.type_of_expr(fn_arg)?; | 235 | let ty = ctx.sema.type_of_expr(fn_arg)?; |
223 | if ty.is_unknown() { | 236 | if ty.is_unknown() { |
224 | return None; | 237 | return None; |
225 | } | 238 | } |
226 | Some(ty.display(ctx.sema.db).to_string()) | 239 | |
240 | if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { | ||
241 | Some(rendered) | ||
242 | } else { | ||
243 | None | ||
244 | } | ||
227 | } | 245 | } |
228 | 246 | ||
229 | /// Returns the position inside the current mod or file | 247 | /// Returns the position inside the current mod or file |
@@ -252,10 +270,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu | |||
252 | 270 | ||
253 | fn next_space_for_fn_in_module( | 271 | fn next_space_for_fn_in_module( |
254 | db: &dyn hir::db::AstDatabase, | 272 | db: &dyn hir::db::AstDatabase, |
255 | module: hir::InFile<hir::ModuleSource>, | 273 | module_source: &hir::InFile<hir::ModuleSource>, |
256 | ) -> Option<(FileId, GeneratedFunctionTarget)> { | 274 | ) -> Option<(FileId, GeneratedFunctionTarget)> { |
257 | let file = module.file_id.original_file(db); | 275 | let file = module_source.file_id.original_file(db); |
258 | let assist_item = match module.value { | 276 | let assist_item = match &module_source.value { |
259 | hir::ModuleSource::SourceFile(it) => { | 277 | hir::ModuleSource::SourceFile(it) => { |
260 | if let Some(last_item) = it.items().last() { | 278 | if let Some(last_item) = it.items().last() { |
261 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | 279 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) |
@@ -599,8 +617,33 @@ fn bar(foo: impl Foo) { | |||
599 | } | 617 | } |
600 | 618 | ||
601 | #[test] | 619 | #[test] |
602 | #[ignore] | 620 | fn borrowed_arg() { |
603 | // FIXME print paths properly to make this test pass | 621 | check_assist( |
622 | add_function, | ||
623 | r" | ||
624 | struct Baz; | ||
625 | fn baz() -> Baz { todo!() } | ||
626 | |||
627 | fn foo() { | ||
628 | bar<|>(&baz()) | ||
629 | } | ||
630 | ", | ||
631 | r" | ||
632 | struct Baz; | ||
633 | fn baz() -> Baz { todo!() } | ||
634 | |||
635 | fn foo() { | ||
636 | bar(&baz()) | ||
637 | } | ||
638 | |||
639 | fn bar(baz: &Baz) { | ||
640 | <|>todo!() | ||
641 | } | ||
642 | ", | ||
643 | ) | ||
644 | } | ||
645 | |||
646 | #[test] | ||
604 | fn add_function_with_qualified_path_arg() { | 647 | fn add_function_with_qualified_path_arg() { |
605 | check_assist( | 648 | check_assist( |
606 | add_function, | 649 | add_function, |
@@ -609,10 +652,8 @@ mod Baz { | |||
609 | pub struct Bof; | 652 | pub struct Bof; |
610 | pub fn baz() -> Bof { Bof } | 653 | pub fn baz() -> Bof { Bof } |
611 | } | 654 | } |
612 | mod Foo { | 655 | fn foo() { |
613 | fn foo() { | 656 | <|>bar(Baz::baz()) |
614 | <|>bar(super::Baz::baz()) | ||
615 | } | ||
616 | } | 657 | } |
617 | ", | 658 | ", |
618 | r" | 659 | r" |
@@ -620,14 +661,12 @@ mod Baz { | |||
620 | pub struct Bof; | 661 | pub struct Bof; |
621 | pub fn baz() -> Bof { Bof } | 662 | pub fn baz() -> Bof { Bof } |
622 | } | 663 | } |
623 | mod Foo { | 664 | fn foo() { |
624 | fn foo() { | 665 | bar(Baz::baz()) |
625 | bar(super::Baz::baz()) | 666 | } |
626 | } | ||
627 | 667 | ||
628 | fn bar(baz: super::Baz::Bof) { | 668 | fn bar(baz: Baz::Bof) { |
629 | <|>todo!() | 669 | <|>todo!() |
630 | } | ||
631 | } | 670 | } |
632 | ", | 671 | ", |
633 | ) | 672 | ) |
@@ -809,6 +848,40 @@ fn foo() { | |||
809 | } | 848 | } |
810 | 849 | ||
811 | #[test] | 850 | #[test] |
851 | #[ignore] | ||
852 | // Ignored until local imports are supported. | ||
853 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 | ||
854 | fn qualified_path_uses_correct_scope() { | ||
855 | check_assist( | ||
856 | add_function, | ||
857 | " | ||
858 | mod foo { | ||
859 | pub struct Foo; | ||
860 | } | ||
861 | fn bar() { | ||
862 | use foo::Foo; | ||
863 | let foo = Foo; | ||
864 | baz<|>(foo) | ||
865 | } | ||
866 | ", | ||
867 | " | ||
868 | mod foo { | ||
869 | pub struct Foo; | ||
870 | } | ||
871 | fn bar() { | ||
872 | use foo::Foo; | ||
873 | let foo = Foo; | ||
874 | baz(foo) | ||
875 | } | ||
876 | |||
877 | fn baz(foo: foo::Foo) { | ||
878 | <|>todo!() | ||
879 | } | ||
880 | ", | ||
881 | ) | ||
882 | } | ||
883 | |||
884 | #[test] | ||
812 | fn add_function_in_module_containing_other_items() { | 885 | fn add_function_in_module_containing_other_items() { |
813 | check_assist( | 886 | check_assist( |
814 | add_function, | 887 | add_function, |
@@ -920,21 +993,6 @@ fn bar(baz: ()) {} | |||
920 | } | 993 | } |
921 | 994 | ||
922 | #[test] | 995 | #[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] | 996 | #[ignore] |
939 | fn create_method_with_no_args() { | 997 | fn create_method_with_no_args() { |
940 | check_assist( | 998 | check_assist( |