]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
Rollup merge of #99614 - RalfJung:transmute-is-not-memcpy, r=thomcc
[rust.git] / src / tools / rust-analyzer / crates / ide / src / goto_declaration.rs
1 use hir::Semantics;
2 use ide_db::{
3     defs::{Definition, NameClass, NameRefClass},
4     RootDatabase,
5 };
6 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
7
8 use crate::{FilePosition, NavigationTarget, RangeInfo};
9
10 // Feature: Go to Declaration
11 //
12 // Navigates to the declaration of an identifier.
13 //
14 // This is currently the same as `Go to Definition` with the exception of outline modules where it
15 // will navigate to the `mod name;` item declaration.
16 pub(crate) fn goto_declaration(
17     db: &RootDatabase,
18     position: FilePosition,
19 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
20     let sema = Semantics::new(db);
21     let file = sema.parse(position.file_id).syntax().clone();
22     let original_token = file
23         .token_at_offset(position.offset)
24         .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
25     let range = original_token.text_range();
26     let info: Vec<NavigationTarget> = sema
27         .descend_into_macros(original_token)
28         .iter()
29         .filter_map(|token| {
30             let parent = token.parent()?;
31             let def = match_ast! {
32                 match parent {
33                     ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
34                         NameRefClass::Definition(it) => Some(it),
35                         _ => None
36                     },
37                     ast::Name(name) => match NameClass::classify(&sema, &name)? {
38                         NameClass::Definition(it) => Some(it),
39                         _ => None
40                     },
41                     _ => None
42                 }
43             };
44             match def? {
45                 Definition::Module(module) => {
46                     Some(NavigationTarget::from_module_to_decl(db, module))
47                 }
48                 _ => None,
49             }
50         })
51         .collect();
52
53     Some(RangeInfo::new(range, info))
54 }
55
56 #[cfg(test)]
57 mod tests {
58     use ide_db::base_db::FileRange;
59     use itertools::Itertools;
60
61     use crate::fixture;
62
63     fn check(ra_fixture: &str) {
64         let (analysis, position, expected) = fixture::annotations(ra_fixture);
65         let navs = analysis
66             .goto_declaration(position)
67             .unwrap()
68             .expect("no declaration or definition found")
69             .info;
70         if navs.is_empty() {
71             panic!("unresolved reference")
72         }
73
74         let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
75         let navs = navs
76             .into_iter()
77             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
78             .sorted_by_key(cmp)
79             .collect::<Vec<_>>();
80         let expected = expected
81             .into_iter()
82             .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
83             .sorted_by_key(cmp)
84             .collect::<Vec<_>>();
85         assert_eq!(expected, navs);
86     }
87
88     #[test]
89     fn goto_decl_module_outline() {
90         check(
91             r#"
92 //- /main.rs
93 mod foo;
94  // ^^^
95 //- /foo.rs
96 use self$0;
97 "#,
98         )
99     }
100
101     #[test]
102     fn goto_decl_module_inline() {
103         check(
104             r#"
105 mod foo {
106  // ^^^
107     use self$0;
108 }
109 "#,
110         )
111     }
112 }