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