aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorIgor Aleksanov <[email protected]>2020-10-18 11:09:00 +0100
committerIgor Aleksanov <[email protected]>2020-10-18 11:09:00 +0100
commit9e7c952bbddc2e6763c49f0511a295362e9893d6 (patch)
tree5b144eabe1eaf62aa1ec5b804ee6fff00f5f84e9 /crates/ide
parent2067a410f31810f6e1941a86cdea0247c3b7d6f4 (diff)
Extract call_info and completion into separate crates
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/Cargo.toml2
-rw-r--r--crates/ide/src/call_hierarchy.rs4
-rw-r--r--crates/ide/src/call_info.rs742
-rw-r--r--crates/ide/src/completion.rs260
-rw-r--r--crates/ide/src/completion/complete_attribute.rs657
-rw-r--r--crates/ide/src/completion/complete_dot.rs431
-rw-r--r--crates/ide/src/completion/complete_fn_param.rs135
-rw-r--r--crates/ide/src/completion/complete_keyword.rs568
-rw-r--r--crates/ide/src/completion/complete_macro_in_item_position.rs41
-rw-r--r--crates/ide/src/completion/complete_mod.rs324
-rw-r--r--crates/ide/src/completion/complete_pattern.rs88
-rw-r--r--crates/ide/src/completion/complete_postfix.rs454
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs279
-rw-r--r--crates/ide/src/completion/complete_qualified_path.rs755
-rw-r--r--crates/ide/src/completion/complete_record.rs226
-rw-r--r--crates/ide/src/completion/complete_snippet.rs114
-rw-r--r--crates/ide/src/completion/complete_trait_impl.rs733
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs679
-rw-r--r--crates/ide/src/completion/completion_config.rs35
-rw-r--r--crates/ide/src/completion/completion_context.rs523
-rw-r--r--crates/ide/src/completion/completion_item.rs384
-rw-r--r--crates/ide/src/completion/generated_features.rs4
-rw-r--r--crates/ide/src/completion/patterns.rs249
-rw-r--r--crates/ide/src/completion/presentation.rs1346
-rw-r--r--crates/ide/src/completion/test_utils.rs125
-rw-r--r--crates/ide/src/display.rs81
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs6
28 files changed, 11 insertions, 9244 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index 29dc9a6a8..63299dc31 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -30,6 +30,8 @@ profile = { path = "../profile", version = "0.0.0" }
30test_utils = { path = "../test_utils", version = "0.0.0" } 30test_utils = { path = "../test_utils", version = "0.0.0" }
31assists = { path = "../assists", version = "0.0.0" } 31assists = { path = "../assists", version = "0.0.0" }
32ssr = { path = "../ssr", version = "0.0.0" } 32ssr = { path = "../ssr", version = "0.0.0" }
33call_info = { path = "../call_info", version = "0.0.0" }
34completion = { path = "../completion", version = "0.0.0" }
33 35
34# ide should depend only on the top-level `hir` package. if you need 36# ide should depend only on the top-level `hir` package. if you need
35# something from some `hir_xxx` subpackage, reexport the API via `hir`. 37# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index d2cf2cc7d..9d6433fe0 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -2,13 +2,13 @@
2 2
3use indexmap::IndexMap; 3use indexmap::IndexMap;
4 4
5use call_info::FnCallNode;
5use hir::Semantics; 6use hir::Semantics;
6use ide_db::RootDatabase; 7use ide_db::RootDatabase;
7use syntax::{ast, match_ast, AstNode, TextRange}; 8use syntax::{ast, match_ast, AstNode, TextRange};
8 9
9use crate::{ 10use crate::{
10 call_info::FnCallNode, display::ToNav, goto_definition, references, FilePosition, 11 display::ToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
11 NavigationTarget, RangeInfo,
12}; 12};
13 13
14#[derive(Debug, Clone)] 14#[derive(Debug, Clone)]
diff --git a/crates/ide/src/call_info.rs b/crates/ide/src/call_info.rs
deleted file mode 100644
index d7b2b926e..000000000
--- a/crates/ide/src/call_info.rs
+++ /dev/null
@@ -1,742 +0,0 @@
1//! FIXME: write short doc here
2use either::Either;
3use hir::{HasAttrs, HirDisplay, Semantics, Type};
4use ide_db::RootDatabase;
5use stdx::format_to;
6use syntax::{
7 ast::{self, ArgListOwner},
8 match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize,
9};
10use test_utils::mark;
11
12use crate::FilePosition;
13
14/// Contains information about a call site. Specifically the
15/// `FunctionSignature`and current parameter.
16#[derive(Debug)]
17pub struct CallInfo {
18 pub doc: Option<String>,
19 pub signature: String,
20 pub active_parameter: Option<usize>,
21 parameters: Vec<TextRange>,
22}
23
24impl CallInfo {
25 pub fn parameter_labels(&self) -> impl Iterator<Item = &str> + '_ {
26 self.parameters.iter().map(move |&it| &self.signature[it])
27 }
28 pub fn parameter_ranges(&self) -> &[TextRange] {
29 &self.parameters
30 }
31 fn push_param(&mut self, param: &str) {
32 if !self.signature.ends_with('(') {
33 self.signature.push_str(", ");
34 }
35 let start = TextSize::of(&self.signature);
36 self.signature.push_str(param);
37 let end = TextSize::of(&self.signature);
38 self.parameters.push(TextRange::new(start, end))
39 }
40}
41
42/// Computes parameter information for the given call expression.
43pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
44 let sema = Semantics::new(db);
45 let file = sema.parse(position.file_id);
46 let file = file.syntax();
47 let token = file.token_at_offset(position.offset).next()?;
48 let token = sema.descend_into_macros(token);
49
50 let (callable, active_parameter) = call_info_impl(&sema, token)?;
51
52 let mut res =
53 CallInfo { doc: None, signature: String::new(), parameters: vec![], active_parameter };
54
55 match callable.kind() {
56 hir::CallableKind::Function(func) => {
57 res.doc = func.docs(db).map(|it| it.as_str().to_string());
58 format_to!(res.signature, "fn {}", func.name(db));
59 }
60 hir::CallableKind::TupleStruct(strukt) => {
61 res.doc = strukt.docs(db).map(|it| it.as_str().to_string());
62 format_to!(res.signature, "struct {}", strukt.name(db));
63 }
64 hir::CallableKind::TupleEnumVariant(variant) => {
65 res.doc = variant.docs(db).map(|it| it.as_str().to_string());
66 format_to!(
67 res.signature,
68 "enum {}::{}",
69 variant.parent_enum(db).name(db),
70 variant.name(db)
71 );
72 }
73 hir::CallableKind::Closure => (),
74 }
75
76 res.signature.push('(');
77 {
78 if let Some(self_param) = callable.receiver_param(db) {
79 format_to!(res.signature, "{}", self_param)
80 }
81 let mut buf = String::new();
82 for (pat, ty) in callable.params(db) {
83 buf.clear();
84 if let Some(pat) = pat {
85 match pat {
86 Either::Left(_self) => format_to!(buf, "self: "),
87 Either::Right(pat) => format_to!(buf, "{}: ", pat),
88 }
89 }
90 format_to!(buf, "{}", ty.display(db));
91 res.push_param(&buf);
92 }
93 }
94 res.signature.push(')');
95
96 match callable.kind() {
97 hir::CallableKind::Function(_) | hir::CallableKind::Closure => {
98 let ret_type = callable.return_type();
99 if !ret_type.is_unit() {
100 format_to!(res.signature, " -> {}", ret_type.display(db));
101 }
102 }
103 hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
104 }
105 Some(res)
106}
107
108fn call_info_impl(
109 sema: &Semantics<RootDatabase>,
110 token: SyntaxToken,
111) -> Option<(hir::Callable, Option<usize>)> {
112 // Find the calling expression and it's NameRef
113 let calling_node = FnCallNode::with_node(&token.parent())?;
114
115 let callable = match &calling_node {
116 FnCallNode::CallExpr(call) => sema.type_of_expr(&call.expr()?)?.as_callable(sema.db)?,
117 FnCallNode::MethodCallExpr(call) => sema.resolve_method_call_as_callable(call)?,
118 };
119 let active_param = if let Some(arg_list) = calling_node.arg_list() {
120 // Number of arguments specified at the call site
121 let num_args_at_callsite = arg_list.args().count();
122
123 let arg_list_range = arg_list.syntax().text_range();
124 if !arg_list_range.contains_inclusive(token.text_range().start()) {
125 mark::hit!(call_info_bad_offset);
126 return None;
127 }
128 let param = std::cmp::min(
129 num_args_at_callsite,
130 arg_list
131 .args()
132 .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
133 .count(),
134 );
135
136 Some(param)
137 } else {
138 None
139 };
140 Some((callable, active_param))
141}
142
143#[derive(Debug)]
144pub(crate) struct ActiveParameter {
145 pub(crate) ty: Type,
146 pub(crate) name: String,
147}
148
149impl ActiveParameter {
150 pub(crate) fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
151 let sema = Semantics::new(db);
152 let file = sema.parse(position.file_id);
153 let file = file.syntax();
154 let token = file.token_at_offset(position.offset).next()?;
155 let token = sema.descend_into_macros(token);
156 Self::at_token(&sema, token)
157 }
158
159 pub(crate) fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
160 let (signature, active_parameter) = call_info_impl(&sema, token)?;
161
162 let idx = active_parameter?;
163 let mut params = signature.params(sema.db);
164 if !(idx < params.len()) {
165 mark::hit!(too_many_arguments);
166 return None;
167 }
168 let (pat, ty) = params.swap_remove(idx);
169 let name = pat?.to_string();
170 Some(ActiveParameter { ty, name })
171 }
172}
173
174#[derive(Debug)]
175pub(crate) enum FnCallNode {
176 CallExpr(ast::CallExpr),
177 MethodCallExpr(ast::MethodCallExpr),
178}
179
180impl FnCallNode {
181 fn with_node(syntax: &SyntaxNode) -> Option<FnCallNode> {
182 syntax.ancestors().find_map(|node| {
183 match_ast! {
184 match node {
185 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
186 ast::MethodCallExpr(it) => {
187 let arg_list = it.arg_list()?;
188 if !arg_list.syntax().text_range().contains_range(syntax.text_range()) {
189 return None;
190 }
191 Some(FnCallNode::MethodCallExpr(it))
192 },
193 _ => None,
194 }
195 }
196 })
197 }
198
199 pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
200 match_ast! {
201 match node {
202 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
203 ast::MethodCallExpr(it) => Some(FnCallNode::MethodCallExpr(it)),
204 _ => None,
205 }
206 }
207 }
208
209 pub(crate) fn name_ref(&self) -> Option<ast::NameRef> {
210 match self {
211 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
212 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
213 _ => return None,
214 }),
215
216 FnCallNode::MethodCallExpr(call_expr) => {
217 call_expr.syntax().children().filter_map(ast::NameRef::cast).next()
218 }
219 }
220 }
221
222 fn arg_list(&self) -> Option<ast::ArgList> {
223 match self {
224 FnCallNode::CallExpr(expr) => expr.arg_list(),
225 FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use expect_test::{expect, Expect};
233 use test_utils::mark;
234
235 use crate::fixture;
236
237 fn check(ra_fixture: &str, expect: Expect) {
238 let (analysis, position) = fixture::position(ra_fixture);
239 let call_info = analysis.call_info(position).unwrap();
240 let actual = match call_info {
241 Some(call_info) => {
242 let docs = match &call_info.doc {
243 None => "".to_string(),
244 Some(docs) => format!("{}\n------\n", docs.as_str()),
245 };
246 let params = call_info
247 .parameter_labels()
248 .enumerate()
249 .map(|(i, param)| {
250 if Some(i) == call_info.active_parameter {
251 format!("<{}>", param)
252 } else {
253 param.to_string()
254 }
255 })
256 .collect::<Vec<_>>()
257 .join(", ");
258 format!("{}{}\n({})\n", docs, call_info.signature, params)
259 }
260 None => String::new(),
261 };
262 expect.assert_eq(&actual);
263 }
264
265 #[test]
266 fn test_fn_signature_two_args() {
267 check(
268 r#"
269fn foo(x: u32, y: u32) -> u32 {x + y}
270fn bar() { foo(<|>3, ); }
271"#,
272 expect![[r#"
273 fn foo(x: u32, y: u32) -> u32
274 (<x: u32>, y: u32)
275 "#]],
276 );
277 check(
278 r#"
279fn foo(x: u32, y: u32) -> u32 {x + y}
280fn bar() { foo(3<|>, ); }
281"#,
282 expect![[r#"
283 fn foo(x: u32, y: u32) -> u32
284 (<x: u32>, y: u32)
285 "#]],
286 );
287 check(
288 r#"
289fn foo(x: u32, y: u32) -> u32 {x + y}
290fn bar() { foo(3,<|> ); }
291"#,
292 expect![[r#"
293 fn foo(x: u32, y: u32) -> u32
294 (x: u32, <y: u32>)
295 "#]],
296 );
297 check(
298 r#"
299fn foo(x: u32, y: u32) -> u32 {x + y}
300fn bar() { foo(3, <|>); }
301"#,
302 expect![[r#"
303 fn foo(x: u32, y: u32) -> u32
304 (x: u32, <y: u32>)
305 "#]],
306 );
307 }
308
309 #[test]
310 fn test_fn_signature_two_args_empty() {
311 check(
312 r#"
313fn foo(x: u32, y: u32) -> u32 {x + y}
314fn bar() { foo(<|>); }
315"#,
316 expect![[r#"
317 fn foo(x: u32, y: u32) -> u32
318 (<x: u32>, y: u32)
319 "#]],
320 );
321 }
322
323 #[test]
324 fn test_fn_signature_two_args_first_generics() {
325 check(
326 r#"
327fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
328 where T: Copy + Display, U: Debug
329{ x + y }
330
331fn bar() { foo(<|>3, ); }
332"#,
333 expect![[r#"
334 fn foo(x: i32, y: {unknown}) -> u32
335 (<x: i32>, y: {unknown})
336 "#]],
337 );
338 }
339
340 #[test]
341 fn test_fn_signature_no_params() {
342 check(
343 r#"
344fn foo<T>() -> T where T: Copy + Display {}
345fn bar() { foo(<|>); }
346"#,
347 expect![[r#"
348 fn foo() -> {unknown}
349 ()
350 "#]],
351 );
352 }
353
354 #[test]
355 fn test_fn_signature_for_impl() {
356 check(
357 r#"
358struct F;
359impl F { pub fn new() { } }
360fn bar() {
361 let _ : F = F::new(<|>);
362}
363"#,
364 expect![[r#"
365 fn new()
366 ()
367 "#]],
368 );
369 }
370
371 #[test]
372 fn test_fn_signature_for_method_self() {
373 check(
374 r#"
375struct S;
376impl S { pub fn do_it(&self) {} }
377
378fn bar() {
379 let s: S = S;
380 s.do_it(<|>);
381}
382"#,
383 expect![[r#"
384 fn do_it(&self)
385 ()
386 "#]],
387 );
388 }
389
390 #[test]
391 fn test_fn_signature_for_method_with_arg() {
392 check(
393 r#"
394struct S;
395impl S {
396 fn foo(&self, x: i32) {}
397}
398
399fn main() { S.foo(<|>); }
400"#,
401 expect![[r#"
402 fn foo(&self, x: i32)
403 (<x: i32>)
404 "#]],
405 );
406 }
407
408 #[test]
409 fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
410 check(
411 r#"
412struct S;
413impl S {
414 fn foo(&self, x: i32) {}
415}
416
417fn main() { S::foo(<|>); }
418"#,
419 expect![[r#"
420 fn foo(self: &S, x: i32)
421 (<self: &S>, x: i32)
422 "#]],
423 );
424 }
425
426 #[test]
427 fn test_fn_signature_with_docs_simple() {
428 check(
429 r#"
430/// test
431// non-doc-comment
432fn foo(j: u32) -> u32 {
433 j
434}
435
436fn bar() {
437 let _ = foo(<|>);
438}
439"#,
440 expect![[r#"
441 test
442 ------
443 fn foo(j: u32) -> u32
444 (<j: u32>)
445 "#]],
446 );
447 }
448
449 #[test]
450 fn test_fn_signature_with_docs() {
451 check(
452 r#"
453/// Adds one to the number given.
454///
455/// # Examples
456///
457/// ```
458/// let five = 5;
459///
460/// assert_eq!(6, my_crate::add_one(5));
461/// ```
462pub fn add_one(x: i32) -> i32 {
463 x + 1
464}
465
466pub fn do() {
467 add_one(<|>
468}"#,
469 expect![[r##"
470 Adds one to the number given.
471
472 # Examples
473
474 ```
475 let five = 5;
476
477 assert_eq!(6, my_crate::add_one(5));
478 ```
479 ------
480 fn add_one(x: i32) -> i32
481 (<x: i32>)
482 "##]],
483 );
484 }
485
486 #[test]
487 fn test_fn_signature_with_docs_impl() {
488 check(
489 r#"
490struct addr;
491impl addr {
492 /// Adds one to the number given.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// let five = 5;
498 ///
499 /// assert_eq!(6, my_crate::add_one(5));
500 /// ```
501 pub fn add_one(x: i32) -> i32 {
502 x + 1
503 }
504}
505
506pub fn do_it() {
507 addr {};
508 addr::add_one(<|>);
509}
510"#,
511 expect![[r##"
512 Adds one to the number given.
513
514 # Examples
515
516 ```
517 let five = 5;
518
519 assert_eq!(6, my_crate::add_one(5));
520 ```
521 ------
522 fn add_one(x: i32) -> i32
523 (<x: i32>)
524 "##]],
525 );
526 }
527
528 #[test]
529 fn test_fn_signature_with_docs_from_actix() {
530 check(
531 r#"
532struct WriteHandler<E>;
533
534impl<E> WriteHandler<E> {
535 /// Method is called when writer emits error.
536 ///
537 /// If this method returns `ErrorAction::Continue` writer processing
538 /// continues otherwise stream processing stops.
539 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
540 Running::Stop
541 }
542
543 /// Method is called when writer finishes.
544 ///
545 /// By default this method stops actor's `Context`.
546 fn finished(&mut self, ctx: &mut Self::Context) {
547 ctx.stop()
548 }
549}
550
551pub fn foo(mut r: WriteHandler<()>) {
552 r.finished(<|>);
553}
554"#,
555 expect![[r#"
556 Method is called when writer finishes.
557
558 By default this method stops actor's `Context`.
559 ------
560 fn finished(&mut self, ctx: &mut {unknown})
561 (<ctx: &mut {unknown}>)
562 "#]],
563 );
564 }
565
566 #[test]
567 fn call_info_bad_offset() {
568 mark::check!(call_info_bad_offset);
569 check(
570 r#"
571fn foo(x: u32, y: u32) -> u32 {x + y}
572fn bar() { foo <|> (3, ); }
573"#,
574 expect![[""]],
575 );
576 }
577
578 #[test]
579 fn test_nested_method_in_lambda() {
580 check(
581 r#"
582struct Foo;
583impl Foo { fn bar(&self, _: u32) { } }
584
585fn bar(_: u32) { }
586
587fn main() {
588 let foo = Foo;
589 std::thread::spawn(move || foo.bar(<|>));
590}
591"#,
592 expect![[r#"
593 fn bar(&self, _: u32)
594 (<_: u32>)
595 "#]],
596 );
597 }
598
599 #[test]
600 fn works_for_tuple_structs() {
601 check(
602 r#"
603/// A cool tuple struct
604struct S(u32, i32);
605fn main() {
606 let s = S(0, <|>);
607}
608"#,
609 expect![[r#"
610 A cool tuple struct
611 ------
612 struct S(u32, i32)
613 (u32, <i32>)
614 "#]],
615 );
616 }
617
618 #[test]
619 fn generic_struct() {
620 check(
621 r#"
622struct S<T>(T);
623fn main() {
624 let s = S(<|>);
625}
626"#,
627 expect![[r#"
628 struct S({unknown})
629 (<{unknown}>)
630 "#]],
631 );
632 }
633
634 #[test]
635 fn works_for_enum_variants() {
636 check(
637 r#"
638enum E {
639 /// A Variant
640 A(i32),
641 /// Another
642 B,
643 /// And C
644 C { a: i32, b: i32 }
645}
646
647fn main() {
648 let a = E::A(<|>);
649}
650"#,
651 expect![[r#"
652 A Variant
653 ------
654 enum E::A(i32)
655 (<i32>)
656 "#]],
657 );
658 }
659
660 #[test]
661 fn cant_call_struct_record() {
662 check(
663 r#"
664struct S { x: u32, y: i32 }
665fn main() {
666 let s = S(<|>);
667}
668"#,
669 expect![[""]],
670 );
671 }
672
673 #[test]
674 fn cant_call_enum_record() {
675 check(
676 r#"
677enum E {
678 /// A Variant
679 A(i32),
680 /// Another
681 B,
682 /// And C
683 C { a: i32, b: i32 }
684}
685
686fn main() {
687 let a = E::C(<|>);
688}
689"#,
690 expect![[""]],
691 );
692 }
693
694 #[test]
695 fn fn_signature_for_call_in_macro() {
696 check(
697 r#"
698macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
699fn foo() { }
700id! {
701 fn bar() { foo(<|>); }
702}
703"#,
704 expect![[r#"
705 fn foo()
706 ()
707 "#]],
708 );
709 }
710
711 #[test]
712 fn call_info_for_lambdas() {
713 check(
714 r#"
715struct S;
716fn foo(s: S) -> i32 { 92 }
717fn main() {
718 (|s| foo(s))(<|>)
719}
720 "#,
721 expect![[r#"
722 (S) -> i32
723 (<S>)
724 "#]],
725 )
726 }
727
728 #[test]
729 fn call_info_for_fn_ptr() {
730 check(
731 r#"
732fn main(f: fn(i32, f64) -> char) {
733 f(0, <|>)
734}
735 "#,
736 expect![[r#"
737 (i32, f64) -> char
738 (i32, <f64>)
739 "#]],
740 )
741 }
742}
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
deleted file mode 100644
index 69e875014..000000000
--- a/crates/ide/src/completion.rs
+++ /dev/null
@@ -1,260 +0,0 @@
1mod completion_config;
2mod completion_item;
3mod completion_context;
4mod presentation;
5mod patterns;
6mod generated_features;
7#[cfg(test)]
8mod test_utils;
9
10mod complete_attribute;
11mod complete_dot;
12mod complete_record;
13mod complete_pattern;
14mod complete_fn_param;
15mod complete_keyword;
16mod complete_snippet;
17mod complete_qualified_path;
18mod complete_unqualified_path;
19mod complete_postfix;
20mod complete_macro_in_item_position;
21mod complete_trait_impl;
22mod complete_mod;
23
24use ide_db::RootDatabase;
25
26use crate::{
27 completion::{
28 completion_context::CompletionContext,
29 completion_item::{CompletionKind, Completions},
30 },
31 FilePosition,
32};
33
34pub use crate::completion::{
35 completion_config::CompletionConfig,
36 completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
37};
38
39//FIXME: split the following feature into fine-grained features.
40
41// Feature: Magic Completions
42//
43// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
44// completions as well:
45//
46// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
47// is placed at the appropriate position. Even though `if` is easy to type, you
48// still want to complete it, to get ` { }` for free! `return` is inserted with a
49// space or `;` depending on the return type of the function.
50//
51// When completing a function call, `()` are automatically inserted. If a function
52// takes arguments, the cursor is positioned inside the parenthesis.
53//
54// There are postfix completions, which can be triggered by typing something like
55// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
56//
57// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
58// - `expr.match` -> `match expr {}`
59// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
60// - `expr.ref` -> `&expr`
61// - `expr.refm` -> `&mut expr`
62// - `expr.not` -> `!expr`
63// - `expr.dbg` -> `dbg!(expr)`
64// - `expr.dbgr` -> `dbg!(&expr)`
65// - `expr.call` -> `(expr)`
66//
67// There also snippet completions:
68//
69// .Expressions
70// - `pd` -> `eprintln!(" = {:?}", );`
71// - `ppd` -> `eprintln!(" = {:#?}", );`
72//
73// .Items
74// - `tfn` -> `#[test] fn feature(){}`
75// - `tmod` ->
76// ```rust
77// #[cfg(test)]
78// mod tests {
79// use super::*;
80//
81// #[test]
82// fn test_name() {}
83// }
84// ```
85
86/// Main entry point for completion. We run completion as a two-phase process.
87///
88/// First, we look at the position and collect a so-called `CompletionContext.
89/// This is a somewhat messy process, because, during completion, syntax tree is
90/// incomplete and can look really weird.
91///
92/// Once the context is collected, we run a series of completion routines which
93/// look at the context and produce completion items. One subtlety about this
94/// phase is that completion engine should not filter by the substring which is
95/// already present, it should give all possible variants for the identifier at
96/// the caret. In other words, for
97///
98/// ```no_run
99/// fn f() {
100/// let foo = 92;
101/// let _ = bar<|>
102/// }
103/// ```
104///
105/// `foo` *should* be present among the completion variants. Filtering by
106/// identifier prefix/fuzzy match should be done higher in the stack, together
107/// with ordering of completions (currently this is done by the client).
108pub(crate) fn completions(
109 db: &RootDatabase,
110 config: &CompletionConfig,
111 position: FilePosition,
112) -> Option<Completions> {
113 let ctx = CompletionContext::new(db, position, config)?;
114
115 if ctx.no_completion_required() {
116 // No work required here.
117 return None;
118 }
119
120 let mut acc = Completions::default();
121 complete_attribute::complete_attribute(&mut acc, &ctx);
122 complete_fn_param::complete_fn_param(&mut acc, &ctx);
123 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
124 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
125 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
126 complete_snippet::complete_item_snippet(&mut acc, &ctx);
127 complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
128 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
129 complete_dot::complete_dot(&mut acc, &ctx);
130 complete_record::complete_record(&mut acc, &ctx);
131 complete_pattern::complete_pattern(&mut acc, &ctx);
132 complete_postfix::complete_postfix(&mut acc, &ctx);
133 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
134 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
135 complete_mod::complete_mod(&mut acc, &ctx);
136
137 Some(acc)
138}
139
140#[cfg(test)]
141mod tests {
142 use crate::completion::completion_config::CompletionConfig;
143 use crate::fixture;
144
145 struct DetailAndDocumentation<'a> {
146 detail: &'a str,
147 documentation: &'a str,
148 }
149
150 fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
151 let (analysis, position) = fixture::position(ra_fixture);
152 let config = CompletionConfig::default();
153 let completions = analysis.completions(&config, position).unwrap().unwrap();
154 for item in completions {
155 if item.detail() == Some(expected.detail) {
156 let opt = item.documentation();
157 let doc = opt.as_ref().map(|it| it.as_str());
158 assert_eq!(doc, Some(expected.documentation));
159 return;
160 }
161 }
162 panic!("completion detail not found: {}", expected.detail)
163 }
164
165 fn check_no_completion(ra_fixture: &str) {
166 let (analysis, position) = fixture::position(ra_fixture);
167 let config = CompletionConfig::default();
168 analysis.completions(&config, position).unwrap();
169
170 let completions: Option<Vec<String>> = analysis
171 .completions(&config, position)
172 .unwrap()
173 .and_then(|completions| if completions.is_empty() { None } else { Some(completions) })
174 .map(|completions| {
175 completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
176 });
177
178 // `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
179 assert_eq!(completions, None, "Completions were generated, but weren't expected");
180 }
181
182 #[test]
183 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
184 check_detail_and_documentation(
185 r#"
186 //- /lib.rs
187 macro_rules! bar {
188 () => {
189 struct Bar;
190 impl Bar {
191 #[doc = "Do the foo"]
192 fn foo(&self) {}
193 }
194 }
195 }
196
197 bar!();
198
199 fn foo() {
200 let bar = Bar;
201 bar.fo<|>;
202 }
203 "#,
204 DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
205 );
206 }
207
208 #[test]
209 fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
210 check_detail_and_documentation(
211 r#"
212 //- /lib.rs
213 macro_rules! bar {
214 () => {
215 struct Bar;
216 impl Bar {
217 /// Do the foo
218 fn foo(&self) {}
219 }
220 }
221 }
222
223 bar!();
224
225 fn foo() {
226 let bar = Bar;
227 bar.fo<|>;
228 }
229 "#,
230 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
231 );
232 }
233
234 #[test]
235 fn test_no_completions_required() {
236 // There must be no hint for 'in' keyword.
237 check_no_completion(
238 r#"
239 fn foo() {
240 for i i<|>
241 }
242 "#,
243 );
244 // After 'in' keyword hints may be spawned.
245 check_detail_and_documentation(
246 r#"
247 /// Do the foo
248 fn foo() -> &'static str { "foo" }
249
250 fn bar() {
251 for c in fo<|>
252 }
253 "#,
254 DetailAndDocumentation {
255 detail: "fn foo() -> &'static str",
256 documentation: "Do the foo",
257 },
258 );
259 }
260}
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs
deleted file mode 100644
index f4a9864d1..000000000
--- a/crates/ide/src/completion/complete_attribute.rs
+++ /dev/null
@@ -1,657 +0,0 @@
1//! Completion for attributes
2//!
3//! This module uses a bit of static metadata to provide completions
4//! for built-in attributes.
5
6use rustc_hash::FxHashSet;
7use syntax::{ast, AstNode, SyntaxKind};
8
9use crate::completion::{
10 completion_context::CompletionContext,
11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
12 generated_features::FEATURES,
13};
14
15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16 if ctx.mod_declaration_under_caret.is_some() {
17 return None;
18 }
19
20 let attribute = ctx.attribute_under_caret.as_ref()?;
21 match (attribute.path(), attribute.token_tree()) {
22 (Some(path), Some(token_tree)) if path.to_string() == "derive" => {
23 complete_derive(acc, ctx, token_tree)
24 }
25 (Some(path), Some(token_tree)) if path.to_string() == "feature" => {
26 complete_lint(acc, ctx, token_tree, FEATURES)
27 }
28 (Some(path), Some(token_tree))
29 if ["allow", "warn", "deny", "forbid"]
30 .iter()
31 .any(|lint_level| lint_level == &path.to_string()) =>
32 {
33 complete_lint(acc, ctx, token_tree, DEFAULT_LINT_COMPLETIONS)
34 }
35 (_, Some(_token_tree)) => {}
36 _ => complete_attribute_start(acc, ctx, attribute),
37 }
38 Some(())
39}
40
41fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
42 for attr_completion in ATTRIBUTES {
43 let mut item = CompletionItem::new(
44 CompletionKind::Attribute,
45 ctx.source_range(),
46 attr_completion.label,
47 )
48 .kind(CompletionItemKind::Attribute);
49
50 if let Some(lookup) = attr_completion.lookup {
51 item = item.lookup_by(lookup);
52 }
53
54 match (attr_completion.snippet, ctx.config.snippet_cap) {
55 (Some(snippet), Some(cap)) => {
56 item = item.insert_snippet(cap, snippet);
57 }
58 _ => {}
59 }
60
61 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
62 acc.add(item);
63 }
64 }
65}
66
67struct AttrCompletion {
68 label: &'static str,
69 lookup: Option<&'static str>,
70 snippet: Option<&'static str>,
71 prefer_inner: bool,
72}
73
74impl AttrCompletion {
75 const fn prefer_inner(self) -> AttrCompletion {
76 AttrCompletion { prefer_inner: true, ..self }
77 }
78}
79
80const fn attr(
81 label: &'static str,
82 lookup: Option<&'static str>,
83 snippet: Option<&'static str>,
84) -> AttrCompletion {
85 AttrCompletion { label, lookup, snippet, prefer_inner: false }
86}
87
88const ATTRIBUTES: &[AttrCompletion] = &[
89 attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
90 attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
91 attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
92 attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
93 attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)),
94 attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
95 attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
96 attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
97 attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
98 // FIXME: resolve through macro resolution?
99 attr("global_allocator", None, None).prefer_inner(),
100 attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
101 attr("inline(…)", Some("inline"), Some("inline(${0:lint})")),
102 attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
103 attr("link", None, None),
104 attr("macro_export", None, None),
105 attr("macro_use", None, None),
106 attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)),
107 attr("no_mangle", None, None),
108 attr("no_std", None, None).prefer_inner(),
109 attr("non_exhaustive", None, None),
110 attr("panic_handler", None, None).prefer_inner(),
111 attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")),
112 attr("proc_macro", None, None),
113 attr("proc_macro_attribute", None, None),
114 attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
115 attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
116 .prefer_inner(),
117 attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
118 attr(
119 "should_panic(…)",
120 Some("should_panic"),
121 Some(r#"should_panic(expected = "${0:reason}")"#),
122 ),
123 attr(
124 r#"target_feature = "…""#,
125 Some("target_feature"),
126 Some("target_feature = \"${0:feature}\""),
127 ),
128 attr("test", None, None),
129 attr("used", None, None),
130 attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
131 attr(
132 r#"windows_subsystem = "…""#,
133 Some("windows_subsystem"),
134 Some(r#"windows_subsystem = "${0:subsystem}""#),
135 )
136 .prefer_inner(),
137];
138
139fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
140 if let Ok(existing_derives) = parse_comma_sep_input(derive_input) {
141 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
142 .into_iter()
143 .filter(|completion| !existing_derives.contains(completion.label))
144 {
145 let mut label = derive_completion.label.to_owned();
146 for dependency in derive_completion
147 .dependencies
148 .into_iter()
149 .filter(|&&dependency| !existing_derives.contains(dependency))
150 {
151 label.push_str(", ");
152 label.push_str(dependency);
153 }
154 acc.add(
155 CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
156 .kind(CompletionItemKind::Attribute),
157 );
158 }
159
160 for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
161 acc.add(
162 CompletionItem::new(
163 CompletionKind::Attribute,
164 ctx.source_range(),
165 custom_derive_name,
166 )
167 .kind(CompletionItemKind::Attribute),
168 );
169 }
170 }
171}
172
173fn complete_lint(
174 acc: &mut Completions,
175 ctx: &CompletionContext,
176 derive_input: ast::TokenTree,
177 lints_completions: &[LintCompletion],
178) {
179 if let Ok(existing_lints) = parse_comma_sep_input(derive_input) {
180 for lint_completion in lints_completions
181 .into_iter()
182 .filter(|completion| !existing_lints.contains(completion.label))
183 {
184 acc.add(
185 CompletionItem::new(
186 CompletionKind::Attribute,
187 ctx.source_range(),
188 lint_completion.label,
189 )
190 .kind(CompletionItemKind::Attribute)
191 .detail(lint_completion.description),
192 );
193 }
194 }
195}
196
197fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
198 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
199 (Some(left_paren), Some(right_paren))
200 if left_paren.kind() == SyntaxKind::L_PAREN
201 && right_paren.kind() == SyntaxKind::R_PAREN =>
202 {
203 let mut input_derives = FxHashSet::default();
204 let mut current_derive = String::new();
205 for token in derive_input
206 .syntax()
207 .children_with_tokens()
208 .filter_map(|token| token.into_token())
209 .skip_while(|token| token != &left_paren)
210 .skip(1)
211 .take_while(|token| token != &right_paren)
212 {
213 if SyntaxKind::COMMA == token.kind() {
214 if !current_derive.is_empty() {
215 input_derives.insert(current_derive);
216 current_derive = String::new();
217 }
218 } else {
219 current_derive.push_str(token.to_string().trim());
220 }
221 }
222
223 if !current_derive.is_empty() {
224 input_derives.insert(current_derive);
225 }
226 Ok(input_derives)
227 }
228 _ => Err(()),
229 }
230}
231
232fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
233 let mut result = FxHashSet::default();
234 ctx.scope.process_all_names(&mut |name, scope_def| {
235 if let hir::ScopeDef::MacroDef(mac) = scope_def {
236 if mac.is_derive_macro() {
237 result.insert(name.to_string());
238 }
239 }
240 });
241 result
242}
243
244struct DeriveCompletion {
245 label: &'static str,
246 dependencies: &'static [&'static str],
247}
248
249/// Standard Rust derives and the information about their dependencies
250/// (the dependencies are needed so that the main derive don't break the compilation when added)
251#[rustfmt::skip]
252const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
253 DeriveCompletion { label: "Clone", dependencies: &[] },
254 DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
255 DeriveCompletion { label: "Debug", dependencies: &[] },
256 DeriveCompletion { label: "Default", dependencies: &[] },
257 DeriveCompletion { label: "Hash", dependencies: &[] },
258 DeriveCompletion { label: "PartialEq", dependencies: &[] },
259 DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
260 DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
261 DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
262];
263
264pub(super) struct LintCompletion {
265 pub(super) label: &'static str,
266 pub(super) description: &'static str,
267}
268
269#[rustfmt::skip]
270const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[
271 LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# },
272 LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# },
273 LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# },
274 LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# },
275 LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# },
276 LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# },
277 LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# },
278 LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# },
279 LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# },
280 LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# },
281 LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# },
282 LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# },
283 LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# },
284 LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# },
285 LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# },
286 LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# },
287 LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# },
288 LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# },
289 LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# },
290 LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# },
291 LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# },
292 LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# },
293 LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# },
294 LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# },
295 LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# },
296 LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# },
297 LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# },
298 LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# },
299 LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# },
300 LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# },
301 LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# },
302 LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# },
303 LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# },
304 LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# },
305 LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# },
306 LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# },
307 LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# },
308 LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# },
309 LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# },
310 LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# },
311 LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# },
312 LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# },
313 LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# },
314 LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# },
315 LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# },
316 LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# },
317 LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# },
318 LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# },
319 LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# },
320 LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# },
321 LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# },
322 LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# },
323 LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# },
324 LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# },
325 LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# },
326 LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# },
327 LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# },
328 LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# },
329 LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# },
330 LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# },
331 LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# },
332 LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# },
333 LintCompletion { label: "path_statements", description: r#"path statements with no effect"# },
334 LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# },
335 LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# },
336 LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# },
337 LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# },
338 LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# },
339 LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# },
340 LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# },
341 LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# },
342 LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# },
343 LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# },
344 LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# },
345 LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# },
346 LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# },
347 LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# },
348 LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# },
349 LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# },
350 LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# },
351 LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# },
352 LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# },
353 LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# },
354 LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# },
355 LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# },
356 LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# },
357 LintCompletion { label: "unused_imports", description: r#"imports that are never used"# },
358 LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# },
359 LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# },
360 LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# },
361 LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# },
362 LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# },
363 LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# },
364 LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# },
365 LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# },
366 LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# },
367 LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# },
368 LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# },
369 LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# },
370 LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# },
371 LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# },
372 LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# },
373 LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# },
374 LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# },
375 LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# },
376 LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# },
377 LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# },
378 LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# },
379 LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# },
380 LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# },
381 LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# },
382 LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# },
383 LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# },
384 LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# },
385 LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# },
386];
387
388#[cfg(test)]
389mod tests {
390 use expect_test::{expect, Expect};
391
392 use crate::completion::{test_utils::completion_list, CompletionKind};
393
394 fn check(ra_fixture: &str, expect: Expect) {
395 let actual = completion_list(ra_fixture, CompletionKind::Attribute);
396 expect.assert_eq(&actual);
397 }
398
399 #[test]
400 fn empty_derive_completion() {
401 check(
402 r#"
403#[derive(<|>)]
404struct Test {}
405 "#,
406 expect![[r#"
407 at Clone
408 at Copy, Clone
409 at Debug
410 at Default
411 at Eq, PartialEq
412 at Hash
413 at Ord, PartialOrd, Eq, PartialEq
414 at PartialEq
415 at PartialOrd, PartialEq
416 "#]],
417 );
418 }
419
420 #[test]
421 fn empty_lint_completion() {
422 check(
423 r#"#[allow(<|>)]"#,
424 expect![[r#"
425 at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name
426 at ambiguous_associated_items ambiguous associated items
427 at anonymous_parameters detects anonymous parameters
428 at arithmetic_overflow arithmetic operation overflows
429 at array_into_iter detects calling `into_iter` on arrays
430 at asm_sub_register using only a subset of a register for inline asm inputs
431 at bare_trait_objects suggest using `dyn Trait` for trait objects
432 at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants
433 at box_pointers use of owned (Box type) heap memory
434 at cenum_impl_drop_cast a C-like enum implementing Drop is cast
435 at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types
436 at coherence_leak_check distinct impls distinguished only by the leak-check code
437 at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice
438 at confusable_idents detects visually confusable pairs between identifiers
439 at const_err constant evaluation detected erroneous expression
440 at dead_code detect unused, unexported items
441 at deprecated detects use of deprecated items
442 at deprecated_in_future detects use of items that will be deprecated in a future version
443 at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated
444 at ellipsis_inclusive_range_patterns `...` range patterns are deprecated
445 at explicit_outlives_requirements outlives requirements can be inferred
446 at exported_private_dependencies public interface leaks type from a private dependency
447 at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice
448 at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns
449 at improper_ctypes proper use of libc types in foreign modules
450 at improper_ctypes_definitions proper use of libc types in foreign item definitions
451 at incomplete_features incomplete features that may function improperly in some or all cases
452 at incomplete_include trailing content in included file
453 at indirect_structural_match pattern with const indirectly referencing non-structural-match type
454 at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`
455 at intra_doc_link_resolution_failure failures in resolving intra-doc link targets
456 at invalid_codeblock_attributes codeblock attribute looks a lot like a known one
457 at invalid_type_param_default type parameter default erroneously allowed in invalid location
458 at invalid_value an invalid value is being created (such as a NULL reference)
459 at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements
460 at keyword_idents detects edition keywords being used as an identifier
461 at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters
462 at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
463 at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system
464 at meta_variable_misuse possible meta-variable misuse at macro definition
465 at missing_copy_implementations detects potentially-forgotten implementations of `Copy`
466 at missing_crate_level_docs detects crates with no crate-level documentation
467 at missing_debug_implementations detects missing implementations of Debug
468 at missing_doc_code_examples detects publicly-exported items without code samples in their documentation
469 at missing_docs detects missing documentation for public members
470 at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns
471 at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used
472 at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows
473 at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior
474 at no_mangle_const_items const items will not have their symbols exported
475 at no_mangle_generic_items generic items must be mangled
476 at non_ascii_idents detects non-ASCII identifiers
477 at non_camel_case_types types, variants, traits and type parameters should have camel case names
478 at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern
479 at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names
480 at non_upper_case_globals static constants should have uppercase identifiers
481 at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order
482 at overflowing_literals literal out of range for its type
483 at overlapping_patterns detects overlapping patterns
484 at path_statements path statements with no effect
485 at patterns_in_fns_without_body patterns in functions without body were erroneously allowed
486 at private_doc_tests detects code samples in docs of private items not documented by rustdoc
487 at private_in_public detect private items in public interfaces not caught by the old implementation
488 at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules
489 at pub_use_of_private_extern_crate detect public re-exports of private extern crates
490 at redundant_semicolons detects unnecessary trailing semicolons
491 at renamed_and_removed_lints lints that have been renamed or removed
492 at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed
493 at single_use_lifetimes detects lifetime parameters that are only used once
494 at soft_unstable a feature gate that doesn't break dependent crates
495 at stable_features stable features found in `#[feature]` directive
496 at trivial_bounds these bounds don't depend on an type parameters
497 at trivial_casts detects trivial casts which could be removed
498 at trivial_numeric_casts detects trivial casts of numeric types which could be removed
499 at type_alias_bounds bounds in type aliases are not enforced
500 at tyvar_behind_raw_pointer raw pointer to an inference variable
501 at unaligned_references detects unaligned references to fields of packed structs
502 at uncommon_codepoints detects uncommon Unicode codepoints in identifiers
503 at unconditional_panic operation will cause a panic at runtime
504 at unconditional_recursion functions that cannot return without calling themselves
505 at unknown_crate_types unknown crate type found in `#[crate_type]` directive
506 at unknown_lints unrecognized lint attribute
507 at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]`
508 at unreachable_code detects unreachable code paths
509 at unreachable_patterns detects unreachable patterns
510 at unreachable_pub `pub` items not reachable from crate root
511 at unsafe_code usage of `unsafe` code
512 at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated
513 at unstable_features enabling unstable features (deprecated. do not use)
514 at unstable_name_collisions detects name collision with an existing but unstable method
515 at unused_allocation detects unnecessary allocations that can be eliminated
516 at unused_assignments detect assignments that will never be read
517 at unused_attributes detects attributes that were not used by the compiler
518 at unused_braces unnecessary braces around an expression
519 at unused_comparisons comparisons made useless by limits of the types involved
520 at unused_crate_dependencies crate dependencies that are never used
521 at unused_doc_comments detects doc comments that aren't used by rustdoc
522 at unused_extern_crates extern crates that are never used
523 at unused_features unused features found in crate-level `#[feature]` directives
524 at unused_import_braces unnecessary braces around an imported item
525 at unused_imports imports that are never used
526 at unused_labels detects labels that are never used
527 at unused_lifetimes detects lifetime parameters that are never used
528 at unused_macros detects macros that were not used
529 at unused_must_use unused result of a type flagged as `#[must_use]`
530 at unused_mut detect mut variables which don't need to be mutable
531 at unused_parens `if`, `match`, `while` and `return` do not need parentheses
532 at unused_qualifications detects unnecessarily qualified names
533 at unused_results unused result of an expression in a statement
534 at unused_unsafe unnecessary use of an `unsafe` block
535 at unused_variables detect variables which are not used in any way
536 at variant_size_differences detects enums with widely varying variant sizes
537 at warnings mass-change the level for lints which produce warnings
538 at where_clauses_object_safety checks the object safety of where clauses
539 at while_true suggest using `loop { }` instead of `while true { }`
540 "#]],
541 )
542 }
543
544 #[test]
545 fn no_completion_for_incorrect_derive() {
546 check(
547 r#"
548#[derive{<|>)]
549struct Test {}
550"#,
551 expect![[r#""#]],
552 )
553 }
554
555 #[test]
556 fn derive_with_input_completion() {
557 check(
558 r#"
559#[derive(serde::Serialize, PartialEq, <|>)]
560struct Test {}
561"#,
562 expect![[r#"
563 at Clone
564 at Copy, Clone
565 at Debug
566 at Default
567 at Eq
568 at Hash
569 at Ord, PartialOrd, Eq
570 at PartialOrd
571 "#]],
572 )
573 }
574
575 #[test]
576 fn test_attribute_completion() {
577 check(
578 r#"#[<|>]"#,
579 expect![[r#"
580 at allow(…)
581 at cfg(…)
582 at cfg_attr(…)
583 at deny(…)
584 at deprecated = "…"
585 at derive(…)
586 at doc = "…"
587 at forbid(…)
588 at ignore = "…"
589 at inline(…)
590 at link
591 at link_name = "…"
592 at macro_export
593 at macro_use
594 at must_use = "…"
595 at no_mangle
596 at non_exhaustive
597 at path = "…"
598 at proc_macro
599 at proc_macro_attribute
600 at proc_macro_derive(…)
601 at repr(…)
602 at should_panic(…)
603 at target_feature = "…"
604 at test
605 at used
606 at warn(…)
607 "#]],
608 )
609 }
610
611 #[test]
612 fn test_attribute_completion_inside_nested_attr() {
613 check(r#"#[cfg(<|>)]"#, expect![[]])
614 }
615
616 #[test]
617 fn test_inner_attribute_completion() {
618 check(
619 r"#![<|>]",
620 expect![[r#"
621 at allow(…)
622 at cfg(…)
623 at cfg_attr(…)
624 at deny(…)
625 at deprecated = "…"
626 at derive(…)
627 at doc = "…"
628 at feature(…)
629 at forbid(…)
630 at global_allocator
631 at ignore = "…"
632 at inline(…)
633 at link
634 at link_name = "…"
635 at macro_export
636 at macro_use
637 at must_use = "…"
638 at no_mangle
639 at no_std
640 at non_exhaustive
641 at panic_handler
642 at path = "…"
643 at proc_macro
644 at proc_macro_attribute
645 at proc_macro_derive(…)
646 at recursion_limit = …
647 at repr(…)
648 at should_panic(…)
649 at target_feature = "…"
650 at test
651 at used
652 at warn(…)
653 at windows_subsystem = "…"
654 "#]],
655 );
656 }
657}
diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/ide/src/completion/complete_dot.rs
deleted file mode 100644
index f0f9a7f1d..000000000
--- a/crates/ide/src/completion/complete_dot.rs
+++ /dev/null
@@ -1,431 +0,0 @@
1//! Completes references after dot (fields and method calls).
2
3use hir::{HasVisibility, Type};
4use rustc_hash::FxHashSet;
5use test_utils::mark;
6
7use crate::completion::{completion_context::CompletionContext, completion_item::Completions};
8
9/// Complete dot accesses, i.e. fields or methods.
10pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
11 let dot_receiver = match &ctx.dot_receiver {
12 Some(expr) => expr,
13 _ => return,
14 };
15
16 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
17 Some(ty) => ty,
18 _ => return,
19 };
20
21 if ctx.is_call {
22 mark::hit!(test_no_struct_field_completion_for_method_call);
23 } else {
24 complete_fields(acc, ctx, &receiver_ty);
25 }
26 complete_methods(acc, ctx, &receiver_ty);
27}
28
29fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
30 for receiver in receiver.autoderef(ctx.db) {
31 for (field, ty) in receiver.fields(ctx.db) {
32 if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
33 // Skip private field. FIXME: If the definition location of the
34 // field is editable, we should show the completion
35 continue;
36 }
37 acc.add_field(ctx, field, &ty);
38 }
39 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
40 // FIXME: Handle visibility
41 acc.add_tuple_field(ctx, i, &ty);
42 }
43 }
44}
45
46fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
47 if let Some(krate) = ctx.krate {
48 let mut seen_methods = FxHashSet::default();
49 let traits_in_scope = ctx.scope.traits_in_scope();
50 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
51 if func.self_param(ctx.db).is_some()
52 && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m))
53 && seen_methods.insert(func.name(ctx.db))
54 {
55 acc.add_function(ctx, func, None);
56 }
57 None::<()>
58 });
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use expect_test::{expect, Expect};
65 use test_utils::mark;
66
67 use crate::completion::{test_utils::completion_list, CompletionKind};
68
69 fn check(ra_fixture: &str, expect: Expect) {
70 let actual = completion_list(ra_fixture, CompletionKind::Reference);
71 expect.assert_eq(&actual);
72 }
73
74 #[test]
75 fn test_struct_field_and_method_completion() {
76 check(
77 r#"
78struct S { foo: u32 }
79impl S {
80 fn bar(&self) {}
81}
82fn foo(s: S) { s.<|> }
83"#,
84 expect![[r#"
85 me bar() fn bar(&self)
86 fd foo u32
87 "#]],
88 );
89 }
90
91 #[test]
92 fn test_struct_field_completion_self() {
93 check(
94 r#"
95struct S { the_field: (u32,) }
96impl S {
97 fn foo(self) { self.<|> }
98}
99"#,
100 expect![[r#"
101 me foo() fn foo(self)
102 fd the_field (u32,)
103 "#]],
104 )
105 }
106
107 #[test]
108 fn test_struct_field_completion_autoderef() {
109 check(
110 r#"
111struct A { the_field: (u32, i32) }
112impl A {
113 fn foo(&self) { self.<|> }
114}
115"#,
116 expect![[r#"
117 me foo() fn foo(&self)
118 fd the_field (u32, i32)
119 "#]],
120 )
121 }
122
123 #[test]
124 fn test_no_struct_field_completion_for_method_call() {
125 mark::check!(test_no_struct_field_completion_for_method_call);
126 check(
127 r#"
128struct A { the_field: u32 }
129fn foo(a: A) { a.<|>() }
130"#,
131 expect![[""]],
132 );
133 }
134
135 #[test]
136 fn test_visibility_filtering() {
137 check(
138 r#"
139mod inner {
140 pub struct A {
141 private_field: u32,
142 pub pub_field: u32,
143 pub(crate) crate_field: u32,
144 pub(super) super_field: u32,
145 }
146}
147fn foo(a: inner::A) { a.<|> }
148"#,
149 expect![[r#"
150 fd crate_field u32
151 fd pub_field u32
152 fd super_field u32
153 "#]],
154 );
155
156 check(
157 r#"
158struct A {}
159mod m {
160 impl super::A {
161 fn private_method(&self) {}
162 pub(super) fn the_method(&self) {}
163 }
164}
165fn foo(a: A) { a.<|> }
166"#,
167 expect![[r#"
168 me the_method() pub(super) fn the_method(&self)
169 "#]],
170 );
171 }
172
173 #[test]
174 fn test_union_field_completion() {
175 check(
176 r#"
177union U { field: u8, other: u16 }
178fn foo(u: U) { u.<|> }
179"#,
180 expect![[r#"
181 fd field u8
182 fd other u16
183 "#]],
184 );
185 }
186
187 #[test]
188 fn test_method_completion_only_fitting_impls() {
189 check(
190 r#"
191struct A<T> {}
192impl A<u32> {
193 fn the_method(&self) {}
194}
195impl A<i32> {
196 fn the_other_method(&self) {}
197}
198fn foo(a: A<u32>) { a.<|> }
199"#,
200 expect![[r#"
201 me the_method() fn the_method(&self)
202 "#]],
203 )
204 }
205
206 #[test]
207 fn test_trait_method_completion() {
208 check(
209 r#"
210struct A {}
211trait Trait { fn the_method(&self); }
212impl Trait for A {}
213fn foo(a: A) { a.<|> }
214"#,
215 expect![[r#"
216 me the_method() fn the_method(&self)
217 "#]],
218 );
219 }
220
221 #[test]
222 fn test_trait_method_completion_deduplicated() {
223 check(
224 r"
225struct A {}
226trait Trait { fn the_method(&self); }
227impl<T> Trait for T {}
228fn foo(a: &A) { a.<|> }
229",
230 expect![[r#"
231 me the_method() fn the_method(&self)
232 "#]],
233 );
234 }
235
236 #[test]
237 fn completes_trait_method_from_other_module() {
238 check(
239 r"
240struct A {}
241mod m {
242 pub trait Trait { fn the_method(&self); }
243}
244use m::Trait;
245impl Trait for A {}
246fn foo(a: A) { a.<|> }
247",
248 expect![[r#"
249 me the_method() fn the_method(&self)
250 "#]],
251 );
252 }
253
254 #[test]
255 fn test_no_non_self_method() {
256 check(
257 r#"
258struct A {}
259impl A {
260 fn the_method() {}
261}
262fn foo(a: A) {
263 a.<|>
264}
265"#,
266 expect![[""]],
267 );
268 }
269
270 #[test]
271 fn test_tuple_field_completion() {
272 check(
273 r#"
274fn foo() {
275 let b = (0, 3.14);
276 b.<|>
277}
278"#,
279 expect![[r#"
280 fd 0 i32
281 fd 1 f64
282 "#]],
283 )
284 }
285
286 #[test]
287 fn test_tuple_field_inference() {
288 check(
289 r#"
290pub struct S;
291impl S { pub fn blah(&self) {} }
292
293struct T(S);
294
295impl T {
296 fn foo(&self) {
297 // FIXME: This doesn't work without the trailing `a` as `0.` is a float
298 self.0.a<|>
299 }
300}
301"#,
302 expect![[r#"
303 me blah() pub fn blah(&self)
304 "#]],
305 );
306 }
307
308 #[test]
309 fn test_completion_works_in_consts() {
310 check(
311 r#"
312struct A { the_field: u32 }
313const X: u32 = {
314 A { the_field: 92 }.<|>
315};
316"#,
317 expect![[r#"
318 fd the_field u32
319 "#]],
320 );
321 }
322
323 #[test]
324 fn works_in_simple_macro_1() {
325 check(
326 r#"
327macro_rules! m { ($e:expr) => { $e } }
328struct A { the_field: u32 }
329fn foo(a: A) {
330 m!(a.x<|>)
331}
332"#,
333 expect![[r#"
334 fd the_field u32
335 "#]],
336 );
337 }
338
339 #[test]
340 fn works_in_simple_macro_2() {
341 // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
342 check(
343 r#"
344macro_rules! m { ($e:expr) => { $e } }
345struct A { the_field: u32 }
346fn foo(a: A) {
347 m!(a.<|>)
348}
349"#,
350 expect![[r#"
351 fd the_field u32
352 "#]],
353 );
354 }
355
356 #[test]
357 fn works_in_simple_macro_recursive_1() {
358 check(
359 r#"
360macro_rules! m { ($e:expr) => { $e } }
361struct A { the_field: u32 }
362fn foo(a: A) {
363 m!(m!(m!(a.x<|>)))
364}
365"#,
366 expect![[r#"
367 fd the_field u32
368 "#]],
369 );
370 }
371
372 #[test]
373 fn macro_expansion_resilient() {
374 check(
375 r#"
376macro_rules! dbg {
377 () => {};
378 ($val:expr) => {
379 match $val { tmp => { tmp } }
380 };
381 // Trailing comma with single argument is ignored
382 ($val:expr,) => { $crate::dbg!($val) };
383 ($($val:expr),+ $(,)?) => {
384 ($($crate::dbg!($val)),+,)
385 };
386}
387struct A { the_field: u32 }
388fn foo(a: A) {
389 dbg!(a.<|>)
390}
391"#,
392 expect![[r#"
393 fd the_field u32
394 "#]],
395 );
396 }
397
398 #[test]
399 fn test_method_completion_issue_3547() {
400 check(
401 r#"
402struct HashSet<T> {}
403impl<T> HashSet<T> {
404 pub fn the_method(&self) {}
405}
406fn foo() {
407 let s: HashSet<_>;
408 s.<|>
409}
410"#,
411 expect![[r#"
412 me the_method() pub fn the_method(&self)
413 "#]],
414 );
415 }
416
417 #[test]
418 fn completes_method_call_when_receiver_is_a_macro_call() {
419 check(
420 r#"
421struct S;
422impl S { fn foo(&self) {} }
423macro_rules! make_s { () => { S }; }
424fn main() { make_s!().f<|>; }
425"#,
426 expect![[r#"
427 me foo() fn foo(&self)
428 "#]],
429 )
430 }
431}
diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/ide/src/completion/complete_fn_param.rs
deleted file mode 100644
index 9efe25461..000000000
--- a/crates/ide/src/completion/complete_fn_param.rs
+++ /dev/null
@@ -1,135 +0,0 @@
1//! See `complete_fn_param`.
2
3use rustc_hash::FxHashMap;
4use syntax::{
5 ast::{self, ModuleItemOwner},
6 match_ast, AstNode,
7};
8
9use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
10
11/// Complete repeated parameters, both name and type. For example, if all
12/// functions in a file have a `spam: &mut Spam` parameter, a completion with
13/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
14/// suggested.
15pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
16 if !ctx.is_param {
17 return;
18 }
19
20 let mut params = FxHashMap::default();
21
22 let me = ctx.token.ancestors().find_map(ast::Fn::cast);
23 let mut process_fn = |func: ast::Fn| {
24 if Some(&func) == me.as_ref() {
25 return;
26 }
27 func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
28 let text = param.syntax().text().to_string();
29 params.entry(text).or_insert(param);
30 })
31 };
32
33 for node in ctx.token.parent().ancestors() {
34 match_ast! {
35 match node {
36 ast::SourceFile(it) => it.items().filter_map(|item| match item {
37 ast::Item::Fn(it) => Some(it),
38 _ => None,
39 }).for_each(&mut process_fn),
40 ast::ItemList(it) => it.items().filter_map(|item| match item {
41 ast::Item::Fn(it) => Some(it),
42 _ => None,
43 }).for_each(&mut process_fn),
44 ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
45 ast::AssocItem::Fn(it) => Some(it),
46 _ => None,
47 }).for_each(&mut process_fn),
48 _ => continue,
49 }
50 };
51 }
52
53 params
54 .into_iter()
55 .filter_map(|(label, param)| {
56 let lookup = param.pat()?.syntax().text().to_string();
57 Some((label, lookup))
58 })
59 .for_each(|(label, lookup)| {
60 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
61 .kind(crate::CompletionItemKind::Binding)
62 .lookup_by(lookup)
63 .add_to(acc)
64 });
65}
66
67#[cfg(test)]
68mod tests {
69 use expect_test::{expect, Expect};
70
71 use crate::completion::{test_utils::completion_list, CompletionKind};
72
73 fn check(ra_fixture: &str, expect: Expect) {
74 let actual = completion_list(ra_fixture, CompletionKind::Magic);
75 expect.assert_eq(&actual);
76 }
77
78 #[test]
79 fn test_param_completion_last_param() {
80 check(
81 r#"
82fn foo(file_id: FileId) {}
83fn bar(file_id: FileId) {}
84fn baz(file<|>) {}
85"#,
86 expect![[r#"
87 bn file_id: FileId
88 "#]],
89 );
90 }
91
92 #[test]
93 fn test_param_completion_nth_param() {
94 check(
95 r#"
96fn foo(file_id: FileId) {}
97fn baz(file<|>, x: i32) {}
98"#,
99 expect![[r#"
100 bn file_id: FileId
101 "#]],
102 );
103 }
104
105 #[test]
106 fn test_param_completion_trait_param() {
107 check(
108 r#"
109pub(crate) trait SourceRoot {
110 pub fn contains(&self, file_id: FileId) -> bool;
111 pub fn module_map(&self) -> &ModuleMap;
112 pub fn lines(&self, file_id: FileId) -> &LineIndex;
113 pub fn syntax(&self, file<|>)
114}
115"#,
116 expect![[r#"
117 bn file_id: FileId
118 "#]],
119 );
120 }
121
122 #[test]
123 fn completes_param_in_inner_function() {
124 check(
125 r#"
126fn outer(text: String) {
127 fn inner(<|>)
128}
129"#,
130 expect![[r#"
131 bn text: String
132 "#]],
133 )
134 }
135}
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs
deleted file mode 100644
index e59747095..000000000
--- a/crates/ide/src/completion/complete_keyword.rs
+++ /dev/null
@@ -1,568 +0,0 @@
1//! FIXME: write short doc here
2
3use syntax::{ast, SyntaxKind};
4use test_utils::mark;
5
6use crate::completion::{
7 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
8};
9
10pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
11 // complete keyword "crate" in use stmt
12 let source_range = ctx.source_range();
13
14 if ctx.use_item_syntax.is_some() {
15 if ctx.path_qual.is_none() {
16 CompletionItem::new(CompletionKind::Keyword, source_range, "crate::")
17 .kind(CompletionItemKind::Keyword)
18 .insert_text("crate::")
19 .add_to(acc);
20 }
21 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
22 .kind(CompletionItemKind::Keyword)
23 .add_to(acc);
24 CompletionItem::new(CompletionKind::Keyword, source_range, "super::")
25 .kind(CompletionItemKind::Keyword)
26 .insert_text("super::")
27 .add_to(acc);
28 }
29
30 // Suggest .await syntax for types that implement Future trait
31 if let Some(receiver) = &ctx.dot_receiver {
32 if let Some(ty) = ctx.sema.type_of_expr(receiver) {
33 if ty.impls_future(ctx.db) {
34 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await")
35 .kind(CompletionItemKind::Keyword)
36 .detail("expr.await")
37 .insert_text("await")
38 .add_to(acc);
39 }
40 };
41 }
42}
43
44pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
45 if ctx.token.kind() == SyntaxKind::COMMENT {
46 mark::hit!(no_keyword_completion_in_comments);
47 return;
48 }
49
50 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
51 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
52 add_keyword(ctx, acc, "where", "where ");
53 return;
54 }
55 if ctx.unsafe_is_prev {
56 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
57 add_keyword(ctx, acc, "fn", "fn $0() {}")
58 }
59
60 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
61 add_keyword(ctx, acc, "trait", "trait $0 {}");
62 add_keyword(ctx, acc, "impl", "impl $0 {}");
63 }
64
65 return;
66 }
67 if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
68 {
69 add_keyword(ctx, acc, "fn", "fn $0() {}");
70 }
71 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
72 add_keyword(ctx, acc, "use", "use ");
73 add_keyword(ctx, acc, "impl", "impl $0 {}");
74 add_keyword(ctx, acc, "trait", "trait $0 {}");
75 }
76
77 if ctx.has_item_list_or_source_file_parent {
78 add_keyword(ctx, acc, "enum", "enum $0 {}");
79 add_keyword(ctx, acc, "struct", "struct $0");
80 add_keyword(ctx, acc, "union", "union $0 {}");
81 }
82
83 if ctx.is_expr {
84 add_keyword(ctx, acc, "match", "match $0 {}");
85 add_keyword(ctx, acc, "while", "while $0 {}");
86 add_keyword(ctx, acc, "loop", "loop {$0}");
87 add_keyword(ctx, acc, "if", "if ");
88 add_keyword(ctx, acc, "if let", "if let ");
89 }
90
91 if ctx.if_is_prev || ctx.block_expr_parent {
92 add_keyword(ctx, acc, "let", "let ");
93 }
94
95 if ctx.after_if {
96 add_keyword(ctx, acc, "else", "else {$0}");
97 add_keyword(ctx, acc, "else if", "else if $0 {}");
98 }
99 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
100 add_keyword(ctx, acc, "mod", "mod $0 {}");
101 }
102 if ctx.bind_pat_parent || ctx.ref_pat_parent {
103 add_keyword(ctx, acc, "mut", "mut ");
104 }
105 if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
106 {
107 add_keyword(ctx, acc, "const", "const ");
108 add_keyword(ctx, acc, "type", "type ");
109 }
110 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
111 add_keyword(ctx, acc, "static", "static ");
112 };
113 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
114 add_keyword(ctx, acc, "extern", "extern ");
115 }
116 if ctx.has_item_list_or_source_file_parent
117 || has_trait_or_impl_parent
118 || ctx.block_expr_parent
119 || ctx.is_match_arm
120 {
121 add_keyword(ctx, acc, "unsafe", "unsafe ");
122 }
123 if ctx.in_loop_body {
124 if ctx.can_be_stmt {
125 add_keyword(ctx, acc, "continue", "continue;");
126 add_keyword(ctx, acc, "break", "break;");
127 } else {
128 add_keyword(ctx, acc, "continue", "continue");
129 add_keyword(ctx, acc, "break", "break");
130 }
131 }
132 if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent | ctx.has_field_list_parent {
133 add_keyword(ctx, acc, "pub(crate)", "pub(crate) ");
134 add_keyword(ctx, acc, "pub", "pub ");
135 }
136
137 if !ctx.is_trivial_path {
138 return;
139 }
140 let fn_def = match &ctx.function_syntax {
141 Some(it) => it,
142 None => return,
143 };
144 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
145}
146
147fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
148 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
149 .kind(CompletionItemKind::Keyword);
150
151 match ctx.config.snippet_cap {
152 Some(cap) => res.insert_snippet(cap, snippet),
153 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
154 }
155 .build()
156}
157
158fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
159 acc.add(keyword(ctx, kw, snippet));
160}
161
162fn complete_return(
163 ctx: &CompletionContext,
164 fn_def: &ast::Fn,
165 can_be_stmt: bool,
166) -> Option<CompletionItem> {
167 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
168 (true, true) => "return $0;",
169 (true, false) => "return;",
170 (false, true) => "return $0",
171 (false, false) => "return",
172 };
173 Some(keyword(ctx, "return", snip))
174}
175
176#[cfg(test)]
177mod tests {
178 use expect_test::{expect, Expect};
179
180 use crate::completion::{
181 test_utils::{check_edit, completion_list},
182 CompletionKind,
183 };
184 use test_utils::mark;
185
186 fn check(ra_fixture: &str, expect: Expect) {
187 let actual = completion_list(ra_fixture, CompletionKind::Keyword);
188 expect.assert_eq(&actual)
189 }
190
191 #[test]
192 fn test_keywords_in_use_stmt() {
193 check(
194 r"use <|>",
195 expect![[r#"
196 kw crate::
197 kw self
198 kw super::
199 "#]],
200 );
201
202 check(
203 r"use a::<|>",
204 expect![[r#"
205 kw self
206 kw super::
207 "#]],
208 );
209
210 check(
211 r"use a::{b, <|>}",
212 expect![[r#"
213 kw self
214 kw super::
215 "#]],
216 );
217 }
218
219 #[test]
220 fn test_keywords_at_source_file_level() {
221 check(
222 r"m<|>",
223 expect![[r#"
224 kw const
225 kw enum
226 kw extern
227 kw fn
228 kw impl
229 kw mod
230 kw pub
231 kw pub(crate)
232 kw static
233 kw struct
234 kw trait
235 kw type
236 kw union
237 kw unsafe
238 kw use
239 "#]],
240 );
241 }
242
243 #[test]
244 fn test_keywords_in_function() {
245 check(
246 r"fn quux() { <|> }",
247 expect![[r#"
248 kw const
249 kw extern
250 kw fn
251 kw if
252 kw if let
253 kw impl
254 kw let
255 kw loop
256 kw match
257 kw mod
258 kw return
259 kw static
260 kw trait
261 kw type
262 kw unsafe
263 kw use
264 kw while
265 "#]],
266 );
267 }
268
269 #[test]
270 fn test_keywords_inside_block() {
271 check(
272 r"fn quux() { if true { <|> } }",
273 expect![[r#"
274 kw const
275 kw extern
276 kw fn
277 kw if
278 kw if let
279 kw impl
280 kw let
281 kw loop
282 kw match
283 kw mod
284 kw return
285 kw static
286 kw trait
287 kw type
288 kw unsafe
289 kw use
290 kw while
291 "#]],
292 );
293 }
294
295 #[test]
296 fn test_keywords_after_if() {
297 check(
298 r#"fn quux() { if true { () } <|> }"#,
299 expect![[r#"
300 kw const
301 kw else
302 kw else if
303 kw extern
304 kw fn
305 kw if
306 kw if let
307 kw impl
308 kw let
309 kw loop
310 kw match
311 kw mod
312 kw return
313 kw static
314 kw trait
315 kw type
316 kw unsafe
317 kw use
318 kw while
319 "#]],
320 );
321 check_edit(
322 "else",
323 r#"fn quux() { if true { () } <|> }"#,
324 r#"fn quux() { if true { () } else {$0} }"#,
325 );
326 }
327
328 #[test]
329 fn test_keywords_in_match_arm() {
330 check(
331 r#"
332fn quux() -> i32 {
333 match () { () => <|> }
334}
335"#,
336 expect![[r#"
337 kw if
338 kw if let
339 kw loop
340 kw match
341 kw return
342 kw unsafe
343 kw while
344 "#]],
345 );
346 }
347
348 #[test]
349 fn test_keywords_in_trait_def() {
350 check(
351 r"trait My { <|> }",
352 expect![[r#"
353 kw const
354 kw fn
355 kw type
356 kw unsafe
357 "#]],
358 );
359 }
360
361 #[test]
362 fn test_keywords_in_impl_def() {
363 check(
364 r"impl My { <|> }",
365 expect![[r#"
366 kw const
367 kw fn
368 kw pub
369 kw pub(crate)
370 kw type
371 kw unsafe
372 "#]],
373 );
374 }
375
376 #[test]
377 fn test_keywords_in_loop() {
378 check(
379 r"fn my() { loop { <|> } }",
380 expect![[r#"
381 kw break
382 kw const
383 kw continue
384 kw extern
385 kw fn
386 kw if
387 kw if let
388 kw impl
389 kw let
390 kw loop
391 kw match
392 kw mod
393 kw return
394 kw static
395 kw trait
396 kw type
397 kw unsafe
398 kw use
399 kw while
400 "#]],
401 );
402 }
403
404 #[test]
405 fn test_keywords_after_unsafe_in_item_list() {
406 check(
407 r"unsafe <|>",
408 expect![[r#"
409 kw fn
410 kw impl
411 kw trait
412 "#]],
413 );
414 }
415
416 #[test]
417 fn test_keywords_after_unsafe_in_block_expr() {
418 check(
419 r"fn my_fn() { unsafe <|> }",
420 expect![[r#"
421 kw fn
422 kw impl
423 kw trait
424 "#]],
425 );
426 }
427
428 #[test]
429 fn test_mut_in_ref_and_in_fn_parameters_list() {
430 check(
431 r"fn my_fn(&<|>) {}",
432 expect![[r#"
433 kw mut
434 "#]],
435 );
436 check(
437 r"fn my_fn(<|>) {}",
438 expect![[r#"
439 kw mut
440 "#]],
441 );
442 check(
443 r"fn my_fn() { let &<|> }",
444 expect![[r#"
445 kw mut
446 "#]],
447 );
448 }
449
450 #[test]
451 fn test_where_keyword() {
452 check(
453 r"trait A <|>",
454 expect![[r#"
455 kw where
456 "#]],
457 );
458 check(
459 r"impl A <|>",
460 expect![[r#"
461 kw where
462 "#]],
463 );
464 }
465
466 #[test]
467 fn no_keyword_completion_in_comments() {
468 mark::check!(no_keyword_completion_in_comments);
469 check(
470 r#"
471fn test() {
472 let x = 2; // A comment<|>
473}
474"#,
475 expect![[""]],
476 );
477 check(
478 r#"
479/*
480Some multi-line comment<|>
481*/
482"#,
483 expect![[""]],
484 );
485 check(
486 r#"
487/// Some doc comment
488/// let test<|> = 1
489"#,
490 expect![[""]],
491 );
492 }
493
494 #[test]
495 fn test_completion_await_impls_future() {
496 check(
497 r#"
498//- /main.rs crate:main deps:std
499use std::future::*;
500struct A {}
501impl Future for A {}
502fn foo(a: A) { a.<|> }
503
504//- /std/lib.rs crate:std
505pub mod future {
506 #[lang = "future_trait"]
507 pub trait Future {}
508}
509"#,
510 expect![[r#"
511 kw await expr.await
512 "#]],
513 );
514
515 check(
516 r#"
517//- /main.rs crate:main deps:std
518use std::future::*;
519fn foo() {
520 let a = async {};
521 a.<|>
522}
523
524//- /std/lib.rs crate:std
525pub mod future {
526 #[lang = "future_trait"]
527 pub trait Future {
528 type Output;
529 }
530}
531"#,
532 expect![[r#"
533 kw await expr.await
534 "#]],
535 )
536 }
537
538 #[test]
539 fn after_let() {
540 check(
541 r#"fn main() { let _ = <|> }"#,
542 expect![[r#"
543 kw if
544 kw if let
545 kw loop
546 kw match
547 kw return
548 kw while
549 "#]],
550 )
551 }
552
553 #[test]
554 fn before_field() {
555 check(
556 r#"
557struct Foo {
558 <|>
559 pub f: i32,
560}
561"#,
562 expect![[r#"
563 kw pub
564 kw pub(crate)
565 "#]],
566 )
567 }
568}
diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/ide/src/completion/complete_macro_in_item_position.rs
deleted file mode 100644
index fc8625d8e..000000000
--- a/crates/ide/src/completion/complete_macro_in_item_position.rs
+++ /dev/null
@@ -1,41 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
6 // Show only macros in top level.
7 if ctx.is_new_item {
8 ctx.scope.process_all_names(&mut |name, res| {
9 if let hir::ScopeDef::MacroDef(mac) = res {
10 acc.add_macro(ctx, Some(name.to_string()), mac);
11 }
12 })
13 }
14}
15
16#[cfg(test)]
17mod tests {
18 use expect_test::{expect, Expect};
19
20 use crate::completion::{test_utils::completion_list, CompletionKind};
21
22 fn check(ra_fixture: &str, expect: Expect) {
23 let actual = completion_list(ra_fixture, CompletionKind::Reference);
24 expect.assert_eq(&actual)
25 }
26
27 #[test]
28 fn completes_macros_as_item() {
29 check(
30 r#"
31macro_rules! foo { () => {} }
32fn foo() {}
33
34<|>
35"#,
36 expect![[r#"
37 ma foo!(…) macro_rules! foo
38 "#]],
39 )
40 }
41}
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/ide/src/completion/complete_mod.rs
deleted file mode 100644
index c7a99bdc3..000000000
--- a/crates/ide/src/completion/complete_mod.rs
+++ /dev/null
@@ -1,324 +0,0 @@
1//! Completes mod declarations.
2
3use base_db::{SourceDatabaseExt, VfsPath};
4use hir::{Module, ModuleSource};
5use ide_db::RootDatabase;
6use rustc_hash::FxHashSet;
7
8use crate::{CompletionItem, CompletionItemKind};
9
10use super::{
11 completion_context::CompletionContext, completion_item::CompletionKind,
12 completion_item::Completions,
13};
14
15/// Complete mod declaration, i.e. `mod <|> ;`
16pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
17 let mod_under_caret = match &ctx.mod_declaration_under_caret {
18 Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
19 Some(mod_under_caret) => mod_under_caret,
20 None => return None,
21 };
22
23 let _p = profile::span("completion::complete_mod");
24
25 let current_module = ctx.scope.module()?;
26
27 let module_definition_file =
28 current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
29 let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
30 let directory_to_look_for_submodules = directory_to_look_for_submodules(
31 current_module,
32 ctx.db,
33 source_root.path_for_file(&module_definition_file)?,
34 )?;
35
36 let existing_mod_declarations = current_module
37 .children(ctx.db)
38 .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
39 .collect::<FxHashSet<_>>();
40
41 let module_declaration_file =
42 current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
43 module_declaration_source_file.file_id.original_file(ctx.db)
44 });
45
46 source_root
47 .iter()
48 .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
49 .filter(|submodule_candidate_file| {
50 Some(submodule_candidate_file) != module_declaration_file.as_ref()
51 })
52 .filter_map(|submodule_file| {
53 let submodule_path = source_root.path_for_file(&submodule_file)?;
54 let directory_with_submodule = submodule_path.parent()?;
55 match submodule_path.name_and_extension()? {
56 ("lib", Some("rs")) | ("main", Some("rs")) => None,
57 ("mod", Some("rs")) => {
58 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
59 match directory_with_submodule.name_and_extension()? {
60 (directory_name, None) => Some(directory_name.to_owned()),
61 _ => None,
62 }
63 } else {
64 None
65 }
66 }
67 (file_name, Some("rs"))
68 if directory_with_submodule == directory_to_look_for_submodules =>
69 {
70 Some(file_name.to_owned())
71 }
72 _ => None,
73 }
74 })
75 .filter(|name| !existing_mod_declarations.contains(name))
76 .for_each(|submodule_name| {
77 let mut label = submodule_name;
78 if mod_under_caret.semicolon_token().is_none() {
79 label.push(';')
80 }
81 acc.add(
82 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
83 .kind(CompletionItemKind::Module),
84 )
85 });
86
87 Some(())
88}
89
90fn directory_to_look_for_submodules(
91 module: Module,
92 db: &RootDatabase,
93 module_file_path: &VfsPath,
94) -> Option<VfsPath> {
95 let directory_with_module_path = module_file_path.parent()?;
96 let base_directory = match module_file_path.name_and_extension()? {
97 ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => {
98 Some(directory_with_module_path)
99 }
100 (regular_rust_file_name, Some("rs")) => {
101 if matches!(
102 (
103 directory_with_module_path
104 .parent()
105 .as_ref()
106 .and_then(|path| path.name_and_extension()),
107 directory_with_module_path.name_and_extension(),
108 ),
109 (Some(("src", None)), Some(("bin", None)))
110 ) {
111 // files in /src/bin/ can import each other directly
112 Some(directory_with_module_path)
113 } else {
114 directory_with_module_path.join(regular_rust_file_name)
115 }
116 }
117 _ => None,
118 }?;
119
120 let mut resulting_path = base_directory;
121 for module in module_chain_to_containing_module_file(module, db) {
122 if let Some(name) = module.name(db) {
123 resulting_path = resulting_path.join(&name.to_string())?;
124 }
125 }
126
127 Some(resulting_path)
128}
129
130fn module_chain_to_containing_module_file(
131 current_module: Module,
132 db: &RootDatabase,
133) -> Vec<Module> {
134 let mut path = Vec::new();
135
136 let mut current_module = Some(current_module);
137 while let Some(ModuleSource::Module(_)) =
138 current_module.map(|module| module.definition_source(db).value)
139 {
140 if let Some(module) = current_module {
141 path.insert(0, module);
142 current_module = module.parent(db);
143 } else {
144 current_module = None;
145 }
146 }
147
148 path
149}
150
151#[cfg(test)]
152mod tests {
153 use crate::completion::{test_utils::completion_list, CompletionKind};
154 use expect_test::{expect, Expect};
155
156 fn check(ra_fixture: &str, expect: Expect) {
157 let actual = completion_list(ra_fixture, CompletionKind::Magic);
158 expect.assert_eq(&actual);
159 }
160
161 #[test]
162 fn lib_module_completion() {
163 check(
164 r#"
165 //- /lib.rs
166 mod <|>
167 //- /foo.rs
168 fn foo() {}
169 //- /foo/ignored_foo.rs
170 fn ignored_foo() {}
171 //- /bar/mod.rs
172 fn bar() {}
173 //- /bar/ignored_bar.rs
174 fn ignored_bar() {}
175 "#,
176 expect![[r#"
177 md bar;
178 md foo;
179 "#]],
180 );
181 }
182
183 #[test]
184 fn no_module_completion_with_module_body() {
185 check(
186 r#"
187 //- /lib.rs
188 mod <|> {
189
190 }
191 //- /foo.rs
192 fn foo() {}
193 "#,
194 expect![[r#""#]],
195 );
196 }
197
198 #[test]
199 fn main_module_completion() {
200 check(
201 r#"
202 //- /main.rs
203 mod <|>
204 //- /foo.rs
205 fn foo() {}
206 //- /foo/ignored_foo.rs
207 fn ignored_foo() {}
208 //- /bar/mod.rs
209 fn bar() {}
210 //- /bar/ignored_bar.rs
211 fn ignored_bar() {}
212 "#,
213 expect![[r#"
214 md bar;
215 md foo;
216 "#]],
217 );
218 }
219
220 #[test]
221 fn main_test_module_completion() {
222 check(
223 r#"
224 //- /main.rs
225 mod tests {
226 mod <|>;
227 }
228 //- /tests/foo.rs
229 fn foo() {}
230 "#,
231 expect![[r#"
232 md foo
233 "#]],
234 );
235 }
236
237 #[test]
238 fn directly_nested_module_completion() {
239 check(
240 r#"
241 //- /lib.rs
242 mod foo;
243 //- /foo.rs
244 mod <|>;
245 //- /foo/bar.rs
246 fn bar() {}
247 //- /foo/bar/ignored_bar.rs
248 fn ignored_bar() {}
249 //- /foo/baz/mod.rs
250 fn baz() {}
251 //- /foo/moar/ignored_moar.rs
252 fn ignored_moar() {}
253 "#,
254 expect![[r#"
255 md bar
256 md baz
257 "#]],
258 );
259 }
260
261 #[test]
262 fn nested_in_source_module_completion() {
263 check(
264 r#"
265 //- /lib.rs
266 mod foo;
267 //- /foo.rs
268 mod bar {
269 mod <|>
270 }
271 //- /foo/bar/baz.rs
272 fn baz() {}
273 "#,
274 expect![[r#"
275 md baz;
276 "#]],
277 );
278 }
279
280 // FIXME binary modules are not supported in tests properly
281 // Binary modules are a bit special, they allow importing the modules from `/src/bin`
282 // and that's why are good to test two things:
283 // * no cycles are allowed in mod declarations
284 // * no modules from the parent directory are proposed
285 // Unfortunately, binary modules support is in cargo not rustc,
286 // hence the test does not work now
287 //
288 // #[test]
289 // fn regular_bin_module_completion() {
290 // check(
291 // r#"
292 // //- /src/bin.rs
293 // fn main() {}
294 // //- /src/bin/foo.rs
295 // mod <|>
296 // //- /src/bin/bar.rs
297 // fn bar() {}
298 // //- /src/bin/bar/bar_ignored.rs
299 // fn bar_ignored() {}
300 // "#,
301 // expect![[r#"
302 // md bar;
303 // "#]],foo
304 // );
305 // }
306
307 #[test]
308 fn already_declared_bin_module_completion_omitted() {
309 check(
310 r#"
311 //- /src/bin.rs crate:main
312 fn main() {}
313 //- /src/bin/foo.rs
314 mod <|>
315 //- /src/bin/bar.rs
316 mod foo;
317 fn bar() {}
318 //- /src/bin/bar/bar_ignored.rs
319 fn bar_ignored() {}
320 "#,
321 expect![[r#""#]],
322 );
323 }
324}
diff --git a/crates/ide/src/completion/complete_pattern.rs b/crates/ide/src/completion/complete_pattern.rs
deleted file mode 100644
index 5a13574d4..000000000
--- a/crates/ide/src/completion/complete_pattern.rs
+++ /dev/null
@@ -1,88 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Completes constats and paths in patterns.
6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
7 if !ctx.is_pat_binding_or_const {
8 return;
9 }
10 if ctx.record_pat_syntax.is_some() {
11 return;
12 }
13
14 // FIXME: ideally, we should look at the type we are matching against and
15 // suggest variants + auto-imports
16 ctx.scope.process_all_names(&mut |name, res| {
17 match &res {
18 hir::ScopeDef::ModuleDef(def) => match def {
19 hir::ModuleDef::Adt(hir::Adt::Enum(..))
20 | hir::ModuleDef::Adt(hir::Adt::Struct(..))
21 | hir::ModuleDef::EnumVariant(..)
22 | hir::ModuleDef::Const(..)
23 | hir::ModuleDef::Module(..) => (),
24 _ => return,
25 },
26 hir::ScopeDef::MacroDef(_) => (),
27 _ => return,
28 };
29
30 acc.add_resolution(ctx, name.to_string(), &res)
31 });
32}
33
34#[cfg(test)]
35mod tests {
36 use expect_test::{expect, Expect};
37
38 use crate::completion::{test_utils::completion_list, CompletionKind};
39
40 fn check(ra_fixture: &str, expect: Expect) {
41 let actual = completion_list(ra_fixture, CompletionKind::Reference);
42 expect.assert_eq(&actual)
43 }
44
45 #[test]
46 fn completes_enum_variants_and_modules() {
47 check(
48 r#"
49enum E { X }
50use self::E::X;
51const Z: E = E::X;
52mod m {}
53
54static FOO: E = E::X;
55struct Bar { f: u32 }
56
57fn foo() {
58 match E::X { <|> }
59}
60"#,
61 expect![[r#"
62 st Bar
63 en E
64 ev X ()
65 ct Z
66 md m
67 "#]],
68 );
69 }
70
71 #[test]
72 fn completes_in_simple_macro_call() {
73 check(
74 r#"
75macro_rules! m { ($e:expr) => { $e } }
76enum E { X }
77
78fn foo() {
79 m!(match E::X { <|> })
80}
81"#,
82 expect![[r#"
83 en E
84 ma m!(…) macro_rules! m
85 "#]],
86 );
87 }
88}
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs
deleted file mode 100644
index db5319618..000000000
--- a/crates/ide/src/completion/complete_postfix.rs
+++ /dev/null
@@ -1,454 +0,0 @@
1//! FIXME: write short doc here
2
3mod format_like;
4
5use assists::utils::TryEnum;
6use syntax::{
7 ast::{self, AstNode, AstToken},
8 TextRange, TextSize,
9};
10use text_edit::TextEdit;
11
12use self::format_like::add_format_like_completions;
13use crate::{
14 completion::{
15 completion_config::SnippetCap,
16 completion_context::CompletionContext,
17 completion_item::{Builder, CompletionKind, Completions},
18 },
19 CompletionItem, CompletionItemKind,
20};
21
22pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
23 if !ctx.config.enable_postfix_completions {
24 return;
25 }
26
27 let dot_receiver = match &ctx.dot_receiver {
28 Some(it) => it,
29 None => return,
30 };
31
32 let receiver_text =
33 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
34
35 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
36 Some(it) => it,
37 None => return,
38 };
39
40 let cap = match ctx.config.snippet_cap {
41 Some(it) => it,
42 None => return,
43 };
44 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty);
45 if let Some(try_enum) = &try_enum {
46 match try_enum {
47 TryEnum::Result => {
48 postfix_snippet(
49 ctx,
50 cap,
51 &dot_receiver,
52 "ifl",
53 "if let Ok {}",
54 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
55 )
56 .add_to(acc);
57
58 postfix_snippet(
59 ctx,
60 cap,
61 &dot_receiver,
62 "while",
63 "while let Ok {}",
64 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
65 )
66 .add_to(acc);
67 }
68 TryEnum::Option => {
69 postfix_snippet(
70 ctx,
71 cap,
72 &dot_receiver,
73 "ifl",
74 "if let Some {}",
75 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
76 )
77 .add_to(acc);
78
79 postfix_snippet(
80 ctx,
81 cap,
82 &dot_receiver,
83 "while",
84 "while let Some {}",
85 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
86 )
87 .add_to(acc);
88 }
89 }
90 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
91 postfix_snippet(
92 ctx,
93 cap,
94 &dot_receiver,
95 "if",
96 "if expr {}",
97 &format!("if {} {{\n $0\n}}", receiver_text),
98 )
99 .add_to(acc);
100 postfix_snippet(
101 ctx,
102 cap,
103 &dot_receiver,
104 "while",
105 "while expr {}",
106 &format!("while {} {{\n $0\n}}", receiver_text),
107 )
108 .add_to(acc);
109 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
110 .add_to(acc);
111 }
112
113 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
114 .add_to(acc);
115 postfix_snippet(
116 ctx,
117 cap,
118 &dot_receiver,
119 "refm",
120 "&mut expr",
121 &format!("&mut {}", receiver_text),
122 )
123 .add_to(acc);
124
125 // The rest of the postfix completions create an expression that moves an argument,
126 // so it's better to consider references now to avoid breaking the compilation
127 let dot_receiver = include_references(dot_receiver);
128 let receiver_text =
129 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
130
131 match try_enum {
132 Some(try_enum) => match try_enum {
133 TryEnum::Result => {
134 postfix_snippet(
135 ctx,
136 cap,
137 &dot_receiver,
138 "match",
139 "match expr {}",
140 &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
141 )
142 .add_to(acc);
143 }
144 TryEnum::Option => {
145 postfix_snippet(
146 ctx,
147 cap,
148 &dot_receiver,
149 "match",
150 "match expr {}",
151 &format!(
152 "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
153 receiver_text
154 ),
155 )
156 .add_to(acc);
157 }
158 },
159 None => {
160 postfix_snippet(
161 ctx,
162 cap,
163 &dot_receiver,
164 "match",
165 "match expr {}",
166 &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
167 )
168 .add_to(acc);
169 }
170 }
171
172 postfix_snippet(
173 ctx,
174 cap,
175 &dot_receiver,
176 "box",
177 "Box::new(expr)",
178 &format!("Box::new({})", receiver_text),
179 )
180 .add_to(acc);
181
182 postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text))
183 .add_to(acc);
184
185 postfix_snippet(
186 ctx,
187 cap,
188 &dot_receiver,
189 "dbg",
190 "dbg!(expr)",
191 &format!("dbg!({})", receiver_text),
192 )
193 .add_to(acc);
194
195 postfix_snippet(
196 ctx,
197 cap,
198 &dot_receiver,
199 "dbgr",
200 "dbg!(&expr)",
201 &format!("dbg!(&{})", receiver_text),
202 )
203 .add_to(acc);
204
205 postfix_snippet(
206 ctx,
207 cap,
208 &dot_receiver,
209 "call",
210 "function(expr)",
211 &format!("${{1}}({})", receiver_text),
212 )
213 .add_to(acc);
214
215 if let ast::Expr::Literal(literal) = dot_receiver.clone() {
216 if let Some(literal_text) = ast::String::cast(literal.token()) {
217 add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
218 }
219 }
220}
221
222fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
223 if receiver_is_ambiguous_float_literal {
224 let text = receiver.syntax().text();
225 let without_dot = ..text.len() - TextSize::of('.');
226 text.slice(without_dot).to_string()
227 } else {
228 receiver.to_string()
229 }
230}
231
232fn include_references(initial_element: &ast::Expr) -> ast::Expr {
233 let mut resulting_element = initial_element.clone();
234 while let Some(parent_ref_element) =
235 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
236 {
237 resulting_element = ast::Expr::from(parent_ref_element);
238 }
239 resulting_element
240}
241
242fn postfix_snippet(
243 ctx: &CompletionContext,
244 cap: SnippetCap,
245 receiver: &ast::Expr,
246 label: &str,
247 detail: &str,
248 snippet: &str,
249) -> Builder {
250 let edit = {
251 let receiver_syntax = receiver.syntax();
252 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
253 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
254 TextEdit::replace(delete_range, snippet.to_string())
255 };
256 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
257 .detail(detail)
258 .kind(CompletionItemKind::Snippet)
259 .snippet_edit(cap, edit)
260}
261
262#[cfg(test)]
263mod tests {
264 use expect_test::{expect, Expect};
265
266 use crate::completion::{
267 test_utils::{check_edit, completion_list},
268 CompletionKind,
269 };
270
271 fn check(ra_fixture: &str, expect: Expect) {
272 let actual = completion_list(ra_fixture, CompletionKind::Postfix);
273 expect.assert_eq(&actual)
274 }
275
276 #[test]
277 fn postfix_completion_works_for_trivial_path_expression() {
278 check(
279 r#"
280fn main() {
281 let bar = true;
282 bar.<|>
283}
284"#,
285 expect![[r#"
286 sn box Box::new(expr)
287 sn call function(expr)
288 sn dbg dbg!(expr)
289 sn dbgr dbg!(&expr)
290 sn if if expr {}
291 sn match match expr {}
292 sn not !expr
293 sn ok Ok(expr)
294 sn ref &expr
295 sn refm &mut expr
296 sn while while expr {}
297 "#]],
298 );
299 }
300
301 #[test]
302 fn postfix_type_filtering() {
303 check(
304 r#"
305fn main() {
306 let bar: u8 = 12;
307 bar.<|>
308}
309"#,
310 expect![[r#"
311 sn box Box::new(expr)
312 sn call function(expr)
313 sn dbg dbg!(expr)
314 sn dbgr dbg!(&expr)
315 sn match match expr {}
316 sn ok Ok(expr)
317 sn ref &expr
318 sn refm &mut expr
319 "#]],
320 )
321 }
322
323 #[test]
324 fn option_iflet() {
325 check_edit(
326 "ifl",
327 r#"
328enum Option<T> { Some(T), None }
329
330fn main() {
331 let bar = Option::Some(true);
332 bar.<|>
333}
334"#,
335 r#"
336enum Option<T> { Some(T), None }
337
338fn main() {
339 let bar = Option::Some(true);
340 if let Some($1) = bar {
341 $0
342}
343}
344"#,
345 );
346 }
347
348 #[test]
349 fn result_match() {
350 check_edit(
351 "match",
352 r#"
353enum Result<T, E> { Ok(T), Err(E) }
354
355fn main() {
356 let bar = Result::Ok(true);
357 bar.<|>
358}
359"#,
360 r#"
361enum Result<T, E> { Ok(T), Err(E) }
362
363fn main() {
364 let bar = Result::Ok(true);
365 match bar {
366 Ok(${1:_}) => {$2},
367 Err(${3:_}) => {$0},
368}
369}
370"#,
371 );
372 }
373
374 #[test]
375 fn postfix_completion_works_for_ambiguous_float_literal() {
376 check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#)
377 }
378
379 #[test]
380 fn works_in_simple_macro() {
381 check_edit(
382 "dbg",
383 r#"
384macro_rules! m { ($e:expr) => { $e } }
385fn main() {
386 let bar: u8 = 12;
387 m!(bar.d<|>)
388}
389"#,
390 r#"
391macro_rules! m { ($e:expr) => { $e } }
392fn main() {
393 let bar: u8 = 12;
394 m!(dbg!(bar))
395}
396"#,
397 );
398 }
399
400 #[test]
401 fn postfix_completion_for_references() {
402 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#);
403 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#);
404 }
405
406 #[test]
407 fn postfix_completion_for_format_like_strings() {
408 check_edit(
409 "fmt",
410 r#"fn main() { "{some_var:?}".<|> }"#,
411 r#"fn main() { format!("{:?}", some_var) }"#,
412 );
413 check_edit(
414 "panic",
415 r#"fn main() { "Panic with {a}".<|> }"#,
416 r#"fn main() { panic!("Panic with {}", a) }"#,
417 );
418 check_edit(
419 "println",
420 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".<|> }"#,
421 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
422 );
423 check_edit(
424 "loge",
425 r#"fn main() { "{2+2}".<|> }"#,
426 r#"fn main() { log::error!("{}", 2+2) }"#,
427 );
428 check_edit(
429 "logt",
430 r#"fn main() { "{2+2}".<|> }"#,
431 r#"fn main() { log::trace!("{}", 2+2) }"#,
432 );
433 check_edit(
434 "logd",
435 r#"fn main() { "{2+2}".<|> }"#,
436 r#"fn main() { log::debug!("{}", 2+2) }"#,
437 );
438 check_edit(
439 "logi",
440 r#"fn main() { "{2+2}".<|> }"#,
441 r#"fn main() { log::info!("{}", 2+2) }"#,
442 );
443 check_edit(
444 "logw",
445 r#"fn main() { "{2+2}".<|> }"#,
446 r#"fn main() { log::warn!("{}", 2+2) }"#,
447 );
448 check_edit(
449 "loge",
450 r#"fn main() { "{2+2}".<|> }"#,
451 r#"fn main() { log::error!("{}", 2+2) }"#,
452 );
453 }
454}
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
deleted file mode 100644
index 50d1e5c81..000000000
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ /dev/null
@@ -1,279 +0,0 @@
1// Feature: Format String Completion.
2//
3// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
4//
5// The following postfix snippets are available:
6//
7// - `format` -> `format!(...)`
8// - `panic` -> `panic!(...)`
9// - `println` -> `println!(...)`
10// - `log`:
11// + `logd` -> `log::debug!(...)`
12// + `logt` -> `log::trace!(...)`
13// + `logi` -> `log::info!(...)`
14// + `logw` -> `log::warn!(...)`
15// + `loge` -> `log::error!(...)`
16
17use crate::completion::{
18 complete_postfix::postfix_snippet, completion_config::SnippetCap,
19 completion_context::CompletionContext, completion_item::Completions,
20};
21use syntax::ast::{self, AstToken};
22
23/// Mapping ("postfix completion item" => "macro to use")
24static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"),
26 ("panic", "panic!"),
27 ("println", "println!"),
28 ("eprintln", "eprintln!"),
29 ("logd", "log::debug!"),
30 ("logt", "log::trace!"),
31 ("logi", "log::info!"),
32 ("logw", "log::warn!"),
33 ("loge", "log::error!"),
34];
35
36pub(super) fn add_format_like_completions(
37 acc: &mut Completions,
38 ctx: &CompletionContext,
39 dot_receiver: &ast::Expr,
40 cap: SnippetCap,
41 receiver_text: &ast::String,
42) {
43 let input = match string_literal_contents(receiver_text) {
44 // It's not a string literal, do not parse input.
45 Some(input) => input,
46 None => return,
47 };
48
49 let mut parser = FormatStrParser::new(input);
50
51 if parser.parse().is_ok() {
52 for (label, macro_name) in KINDS {
53 let snippet = parser.into_suggestion(macro_name);
54
55 postfix_snippet(ctx, cap, &dot_receiver, label, macro_name, &snippet).add_to(acc);
56 }
57 }
58}
59
60/// Checks whether provided item is a string literal.
61fn string_literal_contents(item: &ast::String) -> Option<String> {
62 let item = item.text();
63 if item.len() >= 2 && item.starts_with("\"") && item.ends_with("\"") {
64 return Some(item[1..item.len() - 1].to_owned());
65 }
66
67 None
68}
69
70/// Parser for a format-like string. It is more allowing in terms of string contents,
71/// as we expect variable placeholders to be filled with expressions.
72#[derive(Debug)]
73pub struct FormatStrParser {
74 input: String,
75 output: String,
76 extracted_expressions: Vec<String>,
77 state: State,
78 parsed: bool,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq)]
82enum State {
83 NotExpr,
84 MaybeExpr,
85 Expr,
86 MaybeIncorrect,
87 FormatOpts,
88}
89
90impl FormatStrParser {
91 pub fn new(input: String) -> Self {
92 Self {
93 input: input.into(),
94 output: String::new(),
95 extracted_expressions: Vec::new(),
96 state: State::NotExpr,
97 parsed: false,
98 }
99 }
100
101 pub fn parse(&mut self) -> Result<(), ()> {
102 let mut current_expr = String::new();
103
104 let mut placeholder_id = 1;
105
106 // Count of open braces inside of an expression.
107 // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
108 // "{MyStruct { val_a: 0, val_b: 1 }}".
109 let mut inexpr_open_count = 0;
110
111 for chr in self.input.chars() {
112 match (self.state, chr) {
113 (State::NotExpr, '{') => {
114 self.output.push(chr);
115 self.state = State::MaybeExpr;
116 }
117 (State::NotExpr, '}') => {
118 self.output.push(chr);
119 self.state = State::MaybeIncorrect;
120 }
121 (State::NotExpr, _) => {
122 self.output.push(chr);
123 }
124 (State::MaybeIncorrect, '}') => {
125 // It's okay, we met "}}".
126 self.output.push(chr);
127 self.state = State::NotExpr;
128 }
129 (State::MaybeIncorrect, _) => {
130 // Error in the string.
131 return Err(());
132 }
133 (State::MaybeExpr, '{') => {
134 self.output.push(chr);
135 self.state = State::NotExpr;
136 }
137 (State::MaybeExpr, '}') => {
138 // This is an empty sequence '{}'. Replace it with placeholder.
139 self.output.push(chr);
140 self.extracted_expressions.push(format!("${}", placeholder_id));
141 placeholder_id += 1;
142 self.state = State::NotExpr;
143 }
144 (State::MaybeExpr, _) => {
145 current_expr.push(chr);
146 self.state = State::Expr;
147 }
148 (State::Expr, '}') => {
149 if inexpr_open_count == 0 {
150 self.output.push(chr);
151 self.extracted_expressions.push(current_expr.trim().into());
152 current_expr = String::new();
153 self.state = State::NotExpr;
154 } else {
155 // We're closing one brace met before inside of the expression.
156 current_expr.push(chr);
157 inexpr_open_count -= 1;
158 }
159 }
160 (State::Expr, ':') => {
161 if inexpr_open_count == 0 {
162 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
163 self.output.push(chr);
164 self.extracted_expressions.push(current_expr.trim().into());
165 current_expr = String::new();
166 self.state = State::FormatOpts;
167 } else {
168 // We're inside of braced expression, assume that it's a struct field name/value delimeter.
169 current_expr.push(chr);
170 }
171 }
172 (State::Expr, '{') => {
173 current_expr.push(chr);
174 inexpr_open_count += 1;
175 }
176 (State::Expr, _) => {
177 current_expr.push(chr);
178 }
179 (State::FormatOpts, '}') => {
180 self.output.push(chr);
181 self.state = State::NotExpr;
182 }
183 (State::FormatOpts, _) => {
184 self.output.push(chr);
185 }
186 }
187 }
188
189 if self.state != State::NotExpr {
190 return Err(());
191 }
192
193 self.parsed = true;
194 Ok(())
195 }
196
197 pub fn into_suggestion(&self, macro_name: &str) -> String {
198 assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
199
200 let expressions_as_string = self.extracted_expressions.join(", ");
201 format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use expect_test::{expect, Expect};
209
210 fn check(input: &str, expect: &Expect) {
211 let mut parser = FormatStrParser::new((*input).to_owned());
212 let outcome_repr = if parser.parse().is_ok() {
213 // Parsing should be OK, expected repr is "string; expr_1, expr_2".
214 if parser.extracted_expressions.is_empty() {
215 parser.output
216 } else {
217 format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
218 }
219 } else {
220 // Parsing should fail, expected repr is "-".
221 "-".to_owned()
222 };
223
224 expect.assert_eq(&outcome_repr);
225 }
226
227 #[test]
228 fn format_str_parser() {
229 let test_vector = &[
230 ("no expressions", expect![["no expressions"]]),
231 ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
232 ("{expr:?}", expect![["{:?}; expr"]]),
233 ("{malformed", expect![["-"]]),
234 ("malformed}", expect![["-"]]),
235 ("{{correct", expect![["{{correct"]]),
236 ("correct}}", expect![["correct}}"]]),
237 ("{correct}}}", expect![["{}}}; correct"]]),
238 ("{correct}}}}}", expect![["{}}}}}; correct"]]),
239 ("{incorrect}}", expect![["-"]]),
240 ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
241 ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
242 (
243 "{SomeStruct { val_a: 0, val_b: 1 }}",
244 expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
245 ),
246 ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
247 (
248 "{SomeStruct { val_a: 0, val_b: 1 }:?}",
249 expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
250 ),
251 ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
252 ];
253
254 for (input, output) in test_vector {
255 check(input, output)
256 }
257 }
258
259 #[test]
260 fn test_into_suggestion() {
261 let test_vector = &[
262 ("println!", "{}", r#"println!("{}", $1)"#),
263 ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
264 (
265 "log::info!",
266 "{} {expr} {} {2 + 2}",
267 r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
268 ),
269 ("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
270 ];
271
272 for (kind, input, output) in test_vector {
273 let mut parser = FormatStrParser::new((*input).to_owned());
274 parser.parse().expect("Parsing must succeed");
275
276 assert_eq!(&parser.into_suggestion(*kind), output);
277 }
278 }
279}
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs
deleted file mode 100644
index 2fafedd47..000000000
--- a/crates/ide/src/completion/complete_qualified_path.rs
+++ /dev/null
@@ -1,755 +0,0 @@
1//! Completion of paths, i.e. `some::prefix::<|>`.
2