aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src')
-rw-r--r--crates/ide_db/src/call_info.rs756
-rw-r--r--crates/ide_db/src/lib.rs6
-rw-r--r--crates/ide_db/src/traits.rs227
-rw-r--r--crates/ide_db/src/ty_filter.rs58
4 files changed, 1047 insertions, 0 deletions
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs
new file mode 100644
index 000000000..83a602b9a
--- /dev/null
+++ b/crates/ide_db/src/call_info.rs
@@ -0,0 +1,756 @@
1//! This crate provides primitives for tracking the information about a call site.
2use base_db::FilePosition;
3use either::Either;
4use hir::{HasAttrs, HirDisplay, Semantics, Type};
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::RootDatabase;
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 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 struct ActiveParameter {
145 pub ty: Type,
146 pub name: String,
147}
148
149impl ActiveParameter {
150 pub 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 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 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 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 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 crate::RootDatabase;
233 use base_db::{fixture::ChangeFixture, FilePosition};
234 use expect_test::{expect, Expect};
235 use test_utils::{mark, RangeOrOffset};
236
237 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
238 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
239 let change_fixture = ChangeFixture::parse(ra_fixture);
240 let mut database = RootDatabase::default();
241 database.apply_change(change_fixture.change);
242 let (file_id, range_or_offset) =
243 change_fixture.file_position.expect("expected a marker (<|>)");
244 let offset = match range_or_offset {
245 RangeOrOffset::Range(_) => panic!(),
246 RangeOrOffset::Offset(it) => it,
247 };
248 (database, FilePosition { file_id, offset })
249 }
250
251 fn check(ra_fixture: &str, expect: Expect) {
252 let (db, position) = position(ra_fixture);
253 let call_info = crate::call_info::call_info(&db, position);
254 let actual = match call_info {
255 Some(call_info) => {
256 let docs = match &call_info.doc {
257 None => "".to_string(),
258 Some(docs) => format!("{}\n------\n", docs.as_str()),
259 };
260 let params = call_info
261 .parameter_labels()
262 .enumerate()
263 .map(|(i, param)| {
264 if Some(i) == call_info.active_parameter {
265 format!("<{}>", param)
266 } else {
267 param.to_string()
268 }
269 })
270 .collect::<Vec<_>>()
271 .join(", ");
272 format!("{}{}\n({})\n", docs, call_info.signature, params)
273 }
274 None => String::new(),
275 };
276 expect.assert_eq(&actual);
277 }
278
279 #[test]
280 fn test_fn_signature_two_args() {
281 check(
282 r#"
283fn foo(x: u32, y: u32) -> u32 {x + y}
284fn bar() { foo(<|>3, ); }
285"#,
286 expect![[r#"
287 fn foo(x: u32, y: u32) -> u32
288 (<x: u32>, y: u32)
289 "#]],
290 );
291 check(
292 r#"
293fn foo(x: u32, y: u32) -> u32 {x + y}
294fn bar() { foo(3<|>, ); }
295"#,
296 expect![[r#"
297 fn foo(x: u32, y: u32) -> u32
298 (<x: u32>, y: u32)
299 "#]],
300 );
301 check(
302 r#"
303fn foo(x: u32, y: u32) -> u32 {x + y}
304fn bar() { foo(3,<|> ); }
305"#,
306 expect![[r#"
307 fn foo(x: u32, y: u32) -> u32
308 (x: u32, <y: u32>)
309 "#]],
310 );
311 check(
312 r#"
313fn foo(x: u32, y: u32) -> u32 {x + y}
314fn bar() { foo(3, <|>); }
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_empty() {
325 check(
326 r#"
327fn foo(x: u32, y: u32) -> u32 {x + y}
328fn bar() { foo(<|>); }
329"#,
330 expect![[r#"
331 fn foo(x: u32, y: u32) -> u32
332 (<x: u32>, y: u32)
333 "#]],
334 );
335 }
336
337 #[test]
338 fn test_fn_signature_two_args_first_generics() {
339 check(
340 r#"
341fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
342 where T: Copy + Display, U: Debug
343{ x + y }
344
345fn bar() { foo(<|>3, ); }
346"#,
347 expect![[r#"
348 fn foo(x: i32, y: {unknown}) -> u32
349 (<x: i32>, y: {unknown})
350 "#]],
351 );
352 }
353
354 #[test]
355 fn test_fn_signature_no_params() {
356 check(
357 r#"
358fn foo<T>() -> T where T: Copy + Display {}
359fn bar() { foo(<|>); }
360"#,
361 expect![[r#"
362 fn foo() -> {unknown}
363 ()
364 "#]],
365 );
366 }
367
368 #[test]
369 fn test_fn_signature_for_impl() {
370 check(
371 r#"
372struct F;
373impl F { pub fn new() { } }
374fn bar() {
375 let _ : F = F::new(<|>);
376}
377"#,
378 expect![[r#"
379 fn new()
380 ()
381 "#]],
382 );
383 }
384
385 #[test]
386 fn test_fn_signature_for_method_self() {
387 check(
388 r#"
389struct S;
390impl S { pub fn do_it(&self) {} }
391
392fn bar() {
393 let s: S = S;
394 s.do_it(<|>);
395}
396"#,
397 expect![[r#"
398 fn do_it(&self)
399 ()
400 "#]],
401 );
402 }
403
404 #[test]
405 fn test_fn_signature_for_method_with_arg() {
406 check(
407 r#"
408struct S;
409impl S {
410 fn foo(&self, x: i32) {}
411}
412
413fn main() { S.foo(<|>); }
414"#,
415 expect![[r#"
416 fn foo(&self, x: i32)
417 (<x: i32>)
418 "#]],
419 );
420 }
421
422 #[test]
423 fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
424 check(
425 r#"
426struct S;
427impl S {
428 fn foo(&self, x: i32) {}
429}
430
431fn main() { S::foo(<|>); }
432"#,
433 expect![[r#"
434 fn foo(self: &S, x: i32)
435 (<self: &S>, x: i32)
436 "#]],
437 );
438 }
439
440 #[test]
441 fn test_fn_signature_with_docs_simple() {
442 check(
443 r#"
444/// test
445// non-doc-comment
446fn foo(j: u32) -> u32 {
447 j
448}
449
450fn bar() {
451 let _ = foo(<|>);
452}
453"#,
454 expect![[r#"
455 test
456 ------
457 fn foo(j: u32) -> u32
458 (<j: u32>)
459 "#]],
460 );
461 }
462
463 #[test]
464 fn test_fn_signature_with_docs() {
465 check(
466 r#"
467/// Adds one to the number given.
468///
469/// # Examples
470///
471/// ```
472/// let five = 5;
473///
474/// assert_eq!(6, my_crate::add_one(5));
475/// ```
476pub fn add_one(x: i32) -> i32 {
477 x + 1
478}
479
480pub fn do() {
481 add_one(<|>
482}"#,
483 expect![[r##"
484 Adds one to the number given.
485
486 # Examples
487
488 ```
489 let five = 5;
490
491 assert_eq!(6, my_crate::add_one(5));
492 ```
493 ------
494 fn add_one(x: i32) -> i32
495 (<x: i32>)
496 "##]],
497 );
498 }
499
500 #[test]
501 fn test_fn_signature_with_docs_impl() {
502 check(
503 r#"
504struct addr;
505impl addr {
506 /// Adds one to the number given.
507 ///
508 /// # Examples
509 ///
510 /// ```
511 /// let five = 5;
512 ///
513 /// assert_eq!(6, my_crate::add_one(5));
514 /// ```
515 pub fn add_one(x: i32) -> i32 {
516 x + 1
517 }
518}
519
520pub fn do_it() {
521 addr {};
522 addr::add_one(<|>);
523}
524"#,
525 expect![[r##"
526 Adds one to the number given.
527
528 # Examples
529
530 ```
531 let five = 5;
532
533 assert_eq!(6, my_crate::add_one(5));
534 ```
535 ------
536 fn add_one(x: i32) -> i32
537 (<x: i32>)
538 "##]],
539 );
540 }
541
542 #[test]
543 fn test_fn_signature_with_docs_from_actix() {
544 check(
545 r#"
546struct WriteHandler<E>;
547
548impl<E> WriteHandler<E> {
549 /// Method is called when writer emits error.
550 ///
551 /// If this method returns `ErrorAction::Continue` writer processing
552 /// continues otherwise stream processing stops.
553 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
554 Running::Stop
555 }
556
557 /// Method is called when writer finishes.
558 ///
559 /// By default this method stops actor's `Context`.
560 fn finished(&mut self, ctx: &mut Self::Context) {
561 ctx.stop()
562 }
563}
564
565pub fn foo(mut r: WriteHandler<()>) {
566 r.finished(<|>);
567}
568"#,
569 expect![[r#"
570 Method is called when writer finishes.
571
572 By default this method stops actor's `Context`.
573 ------
574 fn finished(&mut self, ctx: &mut {unknown})
575 (<ctx: &mut {unknown}>)
576 "#]],
577 );
578 }
579
580 #[test]
581 fn call_info_bad_offset() {
582 mark::check!(call_info_bad_offset);
583 check(
584 r#"
585fn foo(x: u32, y: u32) -> u32 {x + y}
586fn bar() { foo <|> (3, ); }
587"#,
588 expect![[""]],
589 );
590 }
591
592 #[test]
593 fn test_nested_method_in_lambda() {
594 check(
595 r#"
596struct Foo;
597impl Foo { fn bar(&self, _: u32) { } }
598
599fn bar(_: u32) { }
600
601fn main() {
602 let foo = Foo;
603 std::thread::spawn(move || foo.bar(<|>));
604}
605"#,
606 expect![[r#"
607 fn bar(&self, _: u32)
608 (<_: u32>)
609 "#]],
610 );
611 }
612
613 #[test]
614 fn works_for_tuple_structs() {
615 check(
616 r#"
617/// A cool tuple struct
618struct S(u32, i32);
619fn main() {
620 let s = S(0, <|>);
621}
622"#,
623 expect![[r#"
624 A cool tuple struct
625 ------
626 struct S(u32, i32)
627 (u32, <i32>)
628 "#]],
629 );
630 }
631
632 #[test]
633 fn generic_struct() {
634 check(
635 r#"
636struct S<T>(T);
637fn main() {
638 let s = S(<|>);
639}
640"#,
641 expect![[r#"
642 struct S({unknown})
643 (<{unknown}>)
644 "#]],
645 );
646 }
647
648 #[test]
649 fn works_for_enum_variants() {
650 check(
651 r#"
652enum E {
653 /// A Variant
654 A(i32),
655 /// Another
656 B,
657 /// And C
658 C { a: i32, b: i32 }
659}
660
661fn main() {
662 let a = E::A(<|>);
663}
664"#,
665 expect![[r#"
666 A Variant
667 ------
668 enum E::A(i32)
669 (<i32>)
670 "#]],
671 );
672 }
673
674 #[test]
675 fn cant_call_struct_record() {
676 check(
677 r#"
678struct S { x: u32, y: i32 }
679fn main() {
680 let s = S(<|>);
681}
682"#,
683 expect![[""]],
684 );
685 }
686
687 #[test]
688 fn cant_call_enum_record() {
689 check(
690 r#"
691enum E {
692 /// A Variant
693 A(i32),
694 /// Another
695 B,
696 /// And C
697 C { a: i32, b: i32 }
698}
699
700fn main() {
701 let a = E::C(<|>);
702}
703"#,
704 expect![[""]],
705 );
706 }
707
708 #[test]
709 fn fn_signature_for_call_in_macro() {
710 check(
711 r#"
712macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
713fn foo() { }
714id! {
715 fn bar() { foo(<|>); }
716}
717"#,
718 expect![[r#"
719 fn foo()
720 ()
721 "#]],
722 );
723 }
724
725 #[test]
726 fn call_info_for_lambdas() {
727 check(
728 r#"
729struct S;
730fn foo(s: S) -> i32 { 92 }
731fn main() {
732 (|s| foo(s))(<|>)
733}
734 "#,
735 expect![[r#"
736 (S) -> i32
737 (<S>)
738 "#]],
739 )
740 }
741
742 #[test]
743 fn call_info_for_fn_ptr() {
744 check(
745 r#"
746fn main(f: fn(i32, f64) -> char) {
747 f(0, <|>)
748}
749 "#,
750 expect![[r#"
751 (i32, f64) -> char
752 (i32, <f64>)
753 "#]],
754 )
755 }
756}
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 7eff247c7..38ebdbf79 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -10,6 +10,9 @@ pub mod defs;
10pub mod search; 10pub mod search;
11pub mod imports_locator; 11pub mod imports_locator;
12pub mod source_change; 12pub mod source_change;
13pub mod ty_filter;
14pub mod traits;
15pub mod call_info;
13 16
14use std::{fmt, sync::Arc}; 17use std::{fmt, sync::Arc};
15 18
@@ -23,6 +26,9 @@ use rustc_hash::FxHashSet;
23 26
24use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase}; 27use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
25 28
29/// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience.
30pub use base_db;
31
26#[salsa::database( 32#[salsa::database(
27 base_db::SourceDatabaseStorage, 33 base_db::SourceDatabaseStorage,
28 base_db::SourceDatabaseExtStorage, 34 base_db::SourceDatabaseExtStorage,
diff --git a/crates/ide_db/src/traits.rs b/crates/ide_db/src/traits.rs
new file mode 100644
index 000000000..f57b6dd91
--- /dev/null
+++ b/crates/ide_db/src/traits.rs
@@ -0,0 +1,227 @@
1//! Functionality for obtaining data related to traits from the DB.
2
3use crate::RootDatabase;
4use hir::Semantics;
5use rustc_hash::FxHashSet;
6use syntax::{
7 ast::{self, NameOwner},
8 AstNode,
9};
10
11/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
12pub fn resolve_target_trait(
13 sema: &Semantics<RootDatabase>,
14 impl_def: &ast::Impl,
15) -> Option<hir::Trait> {
16 let ast_path =
17 impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
18
19 match sema.resolve_path(&ast_path) {
20 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
21 _ => None,
22 }
23}
24
25/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
26/// missing in this `impl` block.
27pub fn get_missing_assoc_items(
28 sema: &Semantics<RootDatabase>,
29 impl_def: &ast::Impl,
30) -> Vec<hir::AssocItem> {
31 // Names must be unique between constants and functions. However, type aliases
32 // may share the same name as a function or constant.
33 let mut impl_fns_consts = FxHashSet::default();
34 let mut impl_type = FxHashSet::default();
35
36 if let Some(item_list) = impl_def.assoc_item_list() {
37 for item in item_list.assoc_items() {
38 match item {
39 ast::AssocItem::Fn(f) => {
40 if let Some(n) = f.name() {
41 impl_fns_consts.insert(n.syntax().to_string());
42 }
43 }
44
45 ast::AssocItem::TypeAlias(t) => {
46 if let Some(n) = t.name() {
47 impl_type.insert(n.syntax().to_string());
48 }
49 }
50
51 ast::AssocItem::Const(c) => {
52 if let Some(n) = c.name() {
53 impl_fns_consts.insert(n.syntax().to_string());
54 }
55 }
56 ast::AssocItem::MacroCall(_) => (),
57 }
58 }
59 }
60
61 resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
62 target_trait
63 .items(sema.db)
64 .iter()
65 .filter(|i| match i {
66 hir::AssocItem::Function(f) => {
67 !impl_fns_consts.contains(&f.name(sema.db).to_string())
68 }
69 hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
70 hir::AssocItem::Const(c) => c
71 .name(sema.db)
72 .map(|n| !impl_fns_consts.contains(&n.to_string()))
73 .unwrap_or_default(),
74 })
75 .cloned()
76 .collect()
77 })
78}
79
80#[cfg(test)]
81mod tests {
82 use crate::RootDatabase;
83 use base_db::{fixture::ChangeFixture, FilePosition};
84 use expect_test::{expect, Expect};
85 use hir::Semantics;
86 use syntax::ast::{self, AstNode};
87 use test_utils::RangeOrOffset;
88
89 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
90 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
91 let change_fixture = ChangeFixture::parse(ra_fixture);
92 let mut database = RootDatabase::default();
93 database.apply_change(change_fixture.change);
94 let (file_id, range_or_offset) =
95 change_fixture.file_position.expect("expected a marker (<|>)");
96 let offset = match range_or_offset {
97 RangeOrOffset::Range(_) => panic!(),
98 RangeOrOffset::Offset(it) => it,
99 };
100 (database, FilePosition { file_id, offset })
101 }
102
103 fn check_trait(ra_fixture: &str, expect: Expect) {
104 let (db, position) = position(ra_fixture);
105 let sema = Semantics::new(&db);
106 let file = sema.parse(position.file_id);
107 let impl_block: ast::Impl =
108 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
109 let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
110 let actual = match trait_ {
111 Some(trait_) => trait_.name(&db).to_string(),
112 None => String::new(),
113 };
114 expect.assert_eq(&actual);
115 }
116
117 fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
118 let (db, position) = position(ra_fixture);
119 let sema = Semantics::new(&db);
120 let file = sema.parse(position.file_id);
121 let impl_block: ast::Impl =
122 sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
123 let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
124 let actual = items
125 .into_iter()
126 .map(|item| item.name(&db).unwrap().to_string())
127 .collect::<Vec<_>>()
128 .join("\n");
129 expect.assert_eq(&actual);
130 }
131
132 #[test]
133 fn resolve_trait() {
134 check_trait(
135 r#"
136pub trait Foo {
137 fn bar();
138}
139impl Foo for u8 {
140 <|>
141}
142 "#,
143 expect![["Foo"]],
144 );
145 check_trait(
146 r#"
147pub trait Foo {
148 fn bar();
149}
150impl Foo for u8 {
151 fn bar() {
152 fn baz() {
153 <|>
154 }
155 baz();
156 }
157}
158 "#,
159 expect![["Foo"]],
160 );
161 check_trait(
162 r#"
163pub trait Foo {
164 fn bar();
165}
166pub struct Bar;
167impl Bar {
168 <|>
169}
170 "#,
171 expect![[""]],
172 );
173 }
174
175 #[test]
176 fn missing_assoc_items() {
177 check_missing_assoc(
178 r#"
179pub trait Foo {
180 const FOO: u8;
181 fn bar();
182}
183impl Foo for u8 {
184 <|>
185}"#,
186 expect![[r#"
187 FOO
188 bar"#]],
189 );
190
191 check_missing_assoc(
192 r#"
193pub trait Foo {
194 const FOO: u8;
195 fn bar();
196}
197impl Foo for u8 {
198 const FOO: u8 = 10;
199 <|>
200}"#,
201 expect![[r#"
202 bar"#]],
203 );
204
205 check_missing_assoc(
206 r#"
207pub trait Foo {
208 const FOO: u8;
209 fn bar();
210}
211impl Foo for u8 {
212 const FOO: u8 = 10;
213 fn bar() {<|>}
214}"#,
215 expect![[r#""#]],
216 );
217
218 check_missing_assoc(
219 r#"
220pub struct Foo;
221impl Foo {
222 fn bar() {<|>}
223}"#,
224 expect![[r#""#]],
225 );
226 }
227}
diff --git a/crates/ide_db/src/ty_filter.rs b/crates/ide_db/src/ty_filter.rs
new file mode 100644
index 000000000..63a945282
--- /dev/null
+++ b/crates/ide_db/src/ty_filter.rs
@@ -0,0 +1,58 @@
1//! This module contains structures for filtering the expected types.
2//! Use case for structures in this module is, for example, situation when you need to process
3//! only certain `Enum`s.
4
5use crate::RootDatabase;
6use hir::{Adt, Semantics, Type};
7use std::iter;
8use syntax::ast::{self, make};
9
10/// Enum types that implement `std::ops::Try` trait.
11#[derive(Clone, Copy)]
12pub enum TryEnum {
13 Result,
14 Option,
15}
16
17impl TryEnum {
18 const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
19
20 /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
21 pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
22 let enum_ = match ty.as_adt() {
23 Some(Adt::Enum(it)) => it,
24 _ => return None,
25 };
26 TryEnum::ALL.iter().find_map(|&var| {
27 if &enum_.name(sema.db).to_string() == var.type_name() {
28 return Some(var);
29 }
30 None
31 })
32 }
33
34 pub fn happy_case(self) -> &'static str {
35 match self {
36 TryEnum::Result => "Ok",
37 TryEnum::Option => "Some",
38 }
39 }
40
41 pub fn sad_pattern(self) -> ast::Pat {
42 match self {
43 TryEnum::Result => make::tuple_struct_pat(
44 make::path_unqualified(make::path_segment(make::name_ref("Err"))),
45 iter::once(make::wildcard_pat().into()),
46 )
47 .into(),
48 TryEnum::Option => make::ident_pat(make::name("None")).into(),
49 }
50 }
51
52 fn type_name(self) -> &'static str {
53 match self {
54 TryEnum::Result => "Result",
55 TryEnum::Option => "Option",
56 }
57 }
58}