aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs27
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs788
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_syntax/src/ast/make.rs25
-rw-r--r--docs/user/assists.md26
5 files changed, 868 insertions, 0 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 0848ab6bc..64444ee3a 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -59,6 +59,33 @@ fn main() {
59} 59}
60 60
61#[test] 61#[test]
62fn doctest_add_function() {
63 check(
64 "add_function",
65 r#####"
66struct Baz;
67fn baz() -> Baz { Baz }
68fn foo() {
69 bar<|>("", baz());
70}
71
72"#####,
73 r#####"
74struct Baz;
75fn baz() -> Baz { Baz }
76fn foo() {
77 bar("", baz());
78}
79
80fn bar(arg: &str, baz: Baz) {
81 unimplemented!()
82}
83
84"#####,
85 )
86}
87
88#[test]
62fn doctest_add_hash() { 89fn doctest_add_hash() {
63 check( 90 check(
64 "add_hash", 91 "add_hash",
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
new file mode 100644
index 000000000..488bae08f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -0,0 +1,788 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 SyntaxKind, SyntaxNode, TextUnit,
4};
5
6use crate::{Assist, AssistCtx, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
8use hir::HirDisplay;
9use rustc_hash::{FxHashMap, FxHashSet};
10
11// Assist: add_function
12//
13// Adds a stub function with a signature matching the function under the cursor.
14//
15// ```
16// struct Baz;
17// fn baz() -> Baz { Baz }
18// fn foo() {
19// bar<|>("", baz());
20// }
21//
22// ```
23// ->
24// ```
25// struct Baz;
26// fn baz() -> Baz { Baz }
27// fn foo() {
28// bar("", baz());
29// }
30//
31// fn bar(arg: &str, baz: Baz) {
32// unimplemented!()
33// }
34//
35// ```
36pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
37 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
39 let path = path_expr.path()?;
40
41 if path.qualifier().is_some() {
42 return None;
43 }
44
45 if ctx.sema.resolve_path(&path).is_some() {
46 // The function call already resolves, no need to add a function
47 return None;
48 }
49
50 let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
51
52 ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
53 edit.target(call.syntax().text_range());
54
55 if let Some(function_template) = function_builder.render() {
56 edit.set_cursor(function_template.cursor_offset);
57 edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
58 }
59 })
60}
61
62struct FunctionTemplate {
63 insert_offset: TextUnit,
64 cursor_offset: TextUnit,
65 fn_def: ast::SourceFile,
66}
67
68struct FunctionBuilder {
69 append_fn_at: SyntaxNode,
70 fn_name: ast::Name,
71 type_params: Option<ast::TypeParamList>,
72 params: ast::ParamList,
73}
74
75impl FunctionBuilder {
76 fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
77 let append_fn_at = next_space_for_fn(&call)?;
78 let fn_name = fn_name(&call)?;
79 let (type_params, params) = fn_args(ctx, &call)?;
80 Some(Self { append_fn_at, fn_name, type_params, params })
81 }
82 fn render(self) -> Option<FunctionTemplate> {
83 let placeholder_expr = ast::make::expr_unimplemented();
84 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
85 let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
86 let fn_def = ast::make::add_newlines(2, fn_def);
87 let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
88 let insert_offset = self.append_fn_at.text_range().end();
89 let cursor_offset_from_fn_start = fn_def
90 .syntax()
91 .descendants()
92 .find_map(ast::MacroCall::cast)?
93 .syntax()
94 .text_range()
95 .start();
96 let cursor_offset = insert_offset + cursor_offset_from_fn_start;
97 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
98 }
99}
100
101fn fn_name(call: &CallExpr) -> Option<ast::Name> {
102 let name = call.expr()?.syntax().to_string();
103 Some(ast::make::name(&name))
104}
105
106/// Computes the type variables and arguments required for the generated function
107fn fn_args(
108 ctx: &AssistCtx,
109 call: &CallExpr,
110) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
111 let mut arg_names = Vec::new();
112 let mut arg_types = Vec::new();
113 for arg in call.arg_list()?.args() {
114 let arg_name = match fn_arg_name(&arg) {
115 Some(name) => name,
116 None => String::from("arg"),
117 };
118 arg_names.push(arg_name);
119 arg_types.push(match fn_arg_type(ctx, &arg) {
120 Some(ty) => ty,
121 None => String::from("()"),
122 });
123 }
124 deduplicate_arg_names(&mut arg_names);
125 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty));
126 Some((None, ast::make::param_list(params)))
127}
128
129/// Makes duplicate argument names unique by appending incrementing numbers.
130///
131/// ```
132/// let mut names: Vec<String> =
133/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
134/// deduplicate_arg_names(&mut names);
135/// let expected: Vec<String> =
136/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
137/// assert_eq!(names, expected);
138/// ```
139fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
140 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
141 *m.entry(name).or_insert(0) += 1;
142 m
143 });
144 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
145 .into_iter()
146 .filter(|(_, count)| *count >= 2)
147 .map(|(name, _)| name.clone())
148 .collect();
149
150 let mut counter_per_name = FxHashMap::default();
151 for arg_name in arg_names.iter_mut() {
152 if duplicate_arg_names.contains(arg_name) {
153 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
154 arg_name.push('_');
155 arg_name.push_str(&counter.to_string());
156 *counter += 1;
157 }
158 }
159}
160
161fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
162 match fn_arg {
163 Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
164 _ => Some(
165 fn_arg
166 .syntax()
167 .descendants()
168 .filter(|d| ast::NameRef::can_cast(d.kind()))
169 .last()?
170 .to_string(),
171 ),
172 }
173}
174
175fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
176 let ty = ctx.sema.type_of_expr(fn_arg)?;
177 if ty.is_unknown() {
178 return None;
179 }
180 Some(ty.display(ctx.sema.db).to_string())
181}
182
183/// Returns the position inside the current mod or file
184/// directly after the current block
185/// We want to write the generated function directly after
186/// fns, impls or macro calls, but inside mods
187fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
188 let mut ancestors = expr.syntax().ancestors().peekable();
189 let mut last_ancestor: Option<SyntaxNode> = None;
190 while let Some(next_ancestor) = ancestors.next() {
191 match next_ancestor.kind() {
192 SyntaxKind::SOURCE_FILE => {
193 break;
194 }
195 SyntaxKind::ITEM_LIST => {
196 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
197 break;
198 }
199 }
200 _ => {}
201 }
202 last_ancestor = Some(next_ancestor);
203 }
204 last_ancestor
205}
206
207#[cfg(test)]
208mod tests {
209 use crate::helpers::{check_assist, check_assist_not_applicable};
210
211 use super::*;
212
213 #[test]
214 fn add_function_with_no_args() {
215 check_assist(
216 add_function,
217 r"
218fn foo() {
219 bar<|>();
220}
221",
222 r"
223fn foo() {
224 bar();
225}
226
227fn bar() {
228 <|>unimplemented!()
229}
230",
231 )
232 }
233
234 #[test]
235 fn add_function_from_method() {
236 // This ensures that the function is correctly generated
237 // in the next outer mod or file
238 check_assist(
239 add_function,
240 r"
241impl Foo {
242 fn foo() {
243 bar<|>();
244 }
245}
246",
247 r"
248impl Foo {
249 fn foo() {
250 bar();
251 }
252}
253
254fn bar() {
255 <|>unimplemented!()
256}
257",
258 )
259 }
260
261 #[test]
262 fn add_function_directly_after_current_block() {
263 // The new fn should not be created at the end of the file or module
264 check_assist(
265 add_function,
266 r"
267fn foo1() {
268 bar<|>();
269}
270
271fn foo2() {}
272",
273 r"
274fn foo1() {
275 bar();
276}
277
278fn bar() {
279 <|>unimplemented!()
280}
281
282fn foo2() {}
283",
284 )
285 }
286
287 #[test]
288 fn add_function_with_no_args_in_same_module() {
289 check_assist(
290 add_function,
291 r"
292mod baz {
293 fn foo() {
294 bar<|>();
295 }
296}
297",
298 r"
299mod baz {
300 fn foo() {
301 bar();
302 }
303
304 fn bar() {
305 <|>unimplemented!()
306 }
307}
308",
309 )
310 }
311
312 #[test]
313 fn add_function_with_function_call_arg() {
314 check_assist(
315 add_function,
316 r"
317struct Baz;
318fn baz() -> Baz { unimplemented!() }
319fn foo() {
320 bar<|>(baz());
321}
322",
323 r"
324struct Baz;
325fn baz() -> Baz { unimplemented!() }
326fn foo() {
327 bar(baz());
328}
329
330fn bar(baz: Baz) {
331 <|>unimplemented!()
332}
333",
334 );
335 }
336
337 #[test]
338 fn add_function_with_method_call_arg() {
339 check_assist(
340 add_function,
341 r"
342struct Baz;
343impl Baz {
344 fn foo(&self) -> Baz {
345 ba<|>r(self.baz())
346 }
347 fn baz(&self) -> Baz {
348 Baz
349 }
350}
351",
352 r"
353struct Baz;
354impl Baz {
355 fn foo(&self) -> Baz {
356 bar(self.baz())
357 }
358 fn baz(&self) -> Baz {
359 Baz
360 }
361}
362
363fn bar(baz: Baz) {
364 <|>unimplemented!()
365}
366",
367 )
368 }
369
370 #[test]
371 fn add_function_with_string_literal_arg() {
372 check_assist(
373 add_function,
374 r#"
375fn foo() {
376 <|>bar("bar")
377}
378"#,
379 r#"
380fn foo() {
381 bar("bar")
382}
383
384fn bar(arg: &str) {
385 <|>unimplemented!()
386}
387"#,
388 )
389 }
390
391 #[test]
392 fn add_function_with_char_literal_arg() {
393 check_assist(
394 add_function,
395 r#"
396fn foo() {
397 <|>bar('x')
398}
399"#,
400 r#"
401fn foo() {
402 bar('x')
403}
404
405fn bar(arg: char) {
406 <|>unimplemented!()
407}
408"#,
409 )
410 }
411
412 #[test]
413 fn add_function_with_int_literal_arg() {
414 check_assist(
415 add_function,
416 r"
417fn foo() {
418 <|>bar(42)
419}
420",
421 r"
422fn foo() {
423 bar(42)
424}
425
426fn bar(arg: i32) {
427 <|>unimplemented!()
428}
429",
430 )
431 }
432
433 #[test]
434 fn add_function_with_cast_int_literal_arg() {
435 check_assist(
436 add_function,
437 r"
438fn foo() {
439 <|>bar(42 as u8)
440}
441",
442 r"
443fn foo() {
444 bar(42 as u8)
445}
446
447fn bar(arg: u8) {
448 <|>unimplemented!()
449}
450",
451 )
452 }
453
454 #[test]
455 fn name_of_cast_variable_is_used() {
456 // Ensures that the name of the cast type isn't used
457 // in the generated function signature.
458 check_assist(
459 add_function,
460 r"
461fn foo() {
462 let x = 42;
463 bar<|>(x as u8)
464}
465",
466 r"
467fn foo() {
468 let x = 42;
469 bar(x as u8)
470}
471
472fn bar(x: u8) {
473 <|>unimplemented!()
474}
475",
476 )
477 }
478
479 #[test]
480 fn add_function_with_variable_arg() {
481 check_assist(
482 add_function,
483 r"
484fn foo() {
485 let worble = ();
486 <|>bar(worble)
487}
488",
489 r"
490fn foo() {
491 let worble = ();
492 bar(worble)
493}
494
495fn bar(worble: ()) {
496 <|>unimplemented!()
497}
498",
499 )
500 }
501
502 #[test]
503 fn add_function_with_impl_trait_arg() {
504 check_assist(
505 add_function,
506 r"
507trait Foo {}
508fn foo() -> impl Foo {
509 unimplemented!()
510}
511fn baz() {
512 <|>bar(foo())
513}
514",
515 r"
516trait Foo {}
517fn foo() -> impl Foo {
518 unimplemented!()
519}
520fn baz() {
521 bar(foo())
522}
523
524fn bar(foo: impl Foo) {
525 <|>unimplemented!()
526}
527",
528 )
529 }
530
531 #[test]
532 #[ignore]
533 // FIXME print paths properly to make this test pass
534 fn add_function_with_qualified_path_arg() {
535 check_assist(
536 add_function,
537 r"
538mod Baz {
539 pub struct Bof;
540 pub fn baz() -> Bof { Bof }
541}
542mod Foo {
543 fn foo() {
544 <|>bar(super::Baz::baz())
545 }
546}
547",
548 r"
549mod Baz {
550 pub struct Bof;
551 pub fn baz() -> Bof { Bof }
552}
553mod Foo {
554 fn foo() {
555 bar(super::Baz::baz())
556 }
557
558 fn bar(baz: super::Baz::Bof) {
559 <|>unimplemented!()
560 }
561}
562",
563 )
564 }
565
566 #[test]
567 #[ignore]
568 // FIXME fix printing the generics of a `Ty` to make this test pass
569 fn add_function_with_generic_arg() {
570 check_assist(
571 add_function,
572 r"
573fn foo<T>(t: T) {
574 <|>bar(t)
575}
576",
577 r"
578fn foo<T>(t: T) {
579 bar(t)
580}
581
582fn bar<T>(t: T) {
583 <|>unimplemented!()
584}
585",
586 )
587 }
588
589 #[test]
590 #[ignore]
591 // FIXME Fix function type printing to make this test pass
592 fn add_function_with_fn_arg() {
593 check_assist(
594 add_function,
595 r"
596struct Baz;
597impl Baz {
598 fn new() -> Self { Baz }
599}
600fn foo() {
601 <|>bar(Baz::new);
602}
603",
604 r"
605struct Baz;
606impl Baz {
607 fn new() -> Self { Baz }
608}
609fn foo() {
610 bar(Baz::new);
611}
612
613fn bar(arg: fn() -> Baz) {
614 <|>unimplemented!()
615}
616",
617 )
618 }
619
620 #[test]
621 #[ignore]
622 // FIXME Fix closure type printing to make this test pass
623 fn add_function_with_closure_arg() {
624 check_assist(
625 add_function,
626 r"
627fn foo() {
628 let closure = |x: i64| x - 1;
629 <|>bar(closure)
630}
631",
632 r"
633fn foo() {
634 let closure = |x: i64| x - 1;
635 bar(closure)
636}
637
638fn bar(closure: impl Fn(i64) -> i64) {
639 <|>unimplemented!()
640}
641",
642 )
643 }
644
645 #[test]
646 fn unresolveable_types_default_to_unit() {
647 check_assist(
648 add_function,
649 r"
650fn foo() {
651 <|>bar(baz)
652}
653",
654 r"
655fn foo() {
656 bar(baz)
657}
658
659fn bar(baz: ()) {
660 <|>unimplemented!()
661}
662",
663 )
664 }
665
666 #[test]
667 fn arg_names_dont_overlap() {
668 check_assist(
669 add_function,
670 r"
671struct Baz;
672fn baz() -> Baz { Baz }
673fn foo() {
674 <|>bar(baz(), baz())
675}
676",
677 r"
678struct Baz;
679fn baz() -> Baz { Baz }
680fn foo() {
681 bar(baz(), baz())
682}
683
684fn bar(baz_1: Baz, baz_2: Baz) {
685 <|>unimplemented!()
686}
687",
688 )
689 }
690
691 #[test]
692 fn arg_name_counters_start_at_1_per_name() {
693 check_assist(
694 add_function,
695 r#"
696struct Baz;
697fn baz() -> Baz { Baz }
698fn foo() {
699 <|>bar(baz(), baz(), "foo", "bar")
700}
701"#,
702 r#"
703struct Baz;
704fn baz() -> Baz { Baz }
705fn foo() {
706 bar(baz(), baz(), "foo", "bar")
707}
708
709fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
710 <|>unimplemented!()
711}
712"#,
713 )
714 }
715
716 #[test]
717 fn add_function_not_applicable_if_function_already_exists() {
718 check_assist_not_applicable(
719 add_function,
720 r"
721fn foo() {
722 bar<|>();
723}
724
725fn bar() {}
726",
727 )
728 }
729
730 #[test]
731 fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
732 check_assist_not_applicable(
733 // bar is resolved, but baz isn't.
734 // The assist is only active if the cursor is on an unresolved path,
735 // but the assist should only be offered if the path is a function call.
736 add_function,
737 r"
738fn foo() {
739 bar(b<|>az);
740}
741
742fn bar(baz: ()) {}
743",
744 )
745 }
746
747 #[test]
748 fn add_function_not_applicable_if_function_path_not_singleton() {
749 // In the future this assist could be extended to generate functions
750 // if the path is in the same crate (or even the same workspace).
751 // For the beginning, I think this is fine.
752 check_assist_not_applicable(
753 add_function,
754 r"
755fn foo() {
756 other_crate::bar<|>();
757}
758 ",
759 )
760 }
761
762 #[test]
763 #[ignore]
764 fn create_method_with_no_args() {
765 check_assist(
766 add_function,
767 r"
768struct Foo;
769impl Foo {
770 fn foo(&self) {
771 self.bar()<|>;
772 }
773}
774 ",
775 r"
776struct Foo;
777impl Foo {
778 fn foo(&self) {
779 self.bar();
780 }
781 fn bar(&self) {
782 unimplemented!();
783 }
784}
785 ",
786 )
787 }
788}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 6b4c56dcd..fa3d3913f 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -96,6 +96,7 @@ mod handlers {
96 mod add_custom_impl; 96 mod add_custom_impl;
97 mod add_derive; 97 mod add_derive;
98 mod add_explicit_type; 98 mod add_explicit_type;
99 mod add_function;
99 mod add_impl; 100 mod add_impl;
100 mod add_missing_impl_members; 101 mod add_missing_impl_members;
101 mod add_new; 102 mod add_new;
@@ -129,6 +130,7 @@ mod handlers {
129 add_custom_impl::add_custom_impl, 130 add_custom_impl::add_custom_impl,
130 add_derive::add_derive, 131 add_derive::add_derive,
131 add_explicit_type::add_explicit_type, 132 add_explicit_type::add_explicit_type,
133 add_function::add_function,
132 add_impl::add_impl, 134 add_impl::add_impl,
133 add_missing_impl_members::add_missing_default_members, 135 add_missing_impl_members::add_missing_default_members,
134 add_missing_impl_members::add_missing_impl_members, 136 add_missing_impl_members::add_missing_impl_members,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index c49cf9a3b..f39559e9e 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -270,6 +270,31 @@ pub fn unreachable_macro_call() -> ast::MacroCall {
270 ast_from_text(&format!("unreachable!()")) 270 ast_from_text(&format!("unreachable!()"))
271} 271}
272 272
273pub fn param(name: String, ty: String) -> ast::Param {
274 ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty))
275}
276
277pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList {
278 let args = pats.into_iter().join(", ");
279 ast_from_text(&format!("fn f({}) {{ }}", args))
280}
281
282pub fn fn_def(
283 fn_name: ast::Name,
284 type_params: Option<ast::TypeParamList>,
285 params: ast::ParamList,
286 body: ast::BlockExpr,
287) -> ast::FnDef {
288 let type_params =
289 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
290 ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
291}
292
293pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
294 let newlines = "\n".repeat(amount_of_newlines);
295 ast_from_text(&format!("{}{}", newlines, t.syntax()))
296}
297
273fn ast_from_text<N: AstNode>(text: &str) -> N { 298fn ast_from_text<N: AstNode>(text: &str) -> N {
274 let parse = SourceFile::parse(text); 299 let parse = SourceFile::parse(text);
275 let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); 300 let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 94b5ef85d..754131f6f 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -56,6 +56,32 @@ fn main() {
56} 56}
57``` 57```
58 58
59## `add_function`
60
61Adds a stub function with a signature matching the function under the cursor.
62
63```rust
64// BEFORE
65struct Baz;
66fn baz() -> Baz { Baz }
67fn foo() {
68 bar┃("", baz());
69}
70
71
72// AFTER
73struct Baz;
74fn baz() -> Baz { Baz }
75fn foo() {
76 bar("", baz());
77}
78
79fn bar(arg: &str, baz: Baz) {
80 unimplemented!()
81}
82
83```
84
59## `add_hash` 85## `add_hash`
60 86
61Adds a hash to a raw string literal. 87Adds a hash to a raw string literal.