1 use ra_db::{FileId, Cancelable, SyntaxDatabase};
4 algo::find_node_at_offset,
7 use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
9 pub(crate) fn goto_definition(
11 position: FilePosition,
12 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
13 let file = db.source_file(position.file_id);
14 let syntax = file.syntax();
15 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
16 let navs = reference_definition(db, position.file_id, name_ref)?.to_vec();
17 return Ok(Some(RangeInfo::new(
18 name_ref.syntax().range(),
22 if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
23 let navs = ctry!(name_definition(db, position.file_id, name)?);
24 return Ok(Some(RangeInfo::new(name.syntax().range(), navs)));
29 pub(crate) enum ReferenceResult {
30 Exact(NavigationTarget),
31 Approximate(Vec<NavigationTarget>),
34 impl ReferenceResult {
35 fn to_vec(self) -> Vec<NavigationTarget> {
36 use self::ReferenceResult::*;
38 Exact(target) => vec![target],
39 Approximate(vec) => vec,
44 pub(crate) fn reference_definition(
47 name_ref: &ast::NameRef,
48 ) -> Cancelable<ReferenceResult> {
49 use self::ReferenceResult::*;
50 if let Some(function) =
51 hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())
53 let scope = function.scopes(db)?;
54 // First try to resolve the symbol locally
55 if let Some(entry) = scope.resolve_local_name(name_ref) {
56 let nav = NavigationTarget::from_scope_entry(file_id, &entry);
57 return Ok(Exact(nav));
60 // Next check if it is a method
61 if let Some(method_call) = name_ref
64 .and_then(ast::MethodCallExpr::cast)
66 let infer_result = function.infer(db)?;
67 let syntax_mapping = function.body_syntax_mapping(db)?;
68 let expr = ast::Expr::cast(method_call.syntax()).unwrap();
69 if let Some(def_id) = syntax_mapping
71 .and_then(|it| infer_result.method_resolution(it))
73 if let Some(target) = NavigationTarget::from_def(db, def_id.resolve(db)?)? {
74 return Ok(Exact(target));
79 // Then try module name resolution
80 if let Some(module) = hir::source_binder::module_from_child_node(db, file_id, name_ref.syntax())
82 if let Some(path) = name_ref
85 .find_map(ast::Path::cast)
86 .and_then(hir::Path::from_ast)
88 let resolved = module.resolve_path(db, &path)?;
89 if let Some(def_id) = resolved.take_types().or(resolved.take_values()) {
90 if let Some(target) = NavigationTarget::from_def(db, def_id.resolve(db)?)? {
91 return Ok(Exact(target));
96 // If that fails try the index based approach.
98 .index_resolve(name_ref)?
100 .map(NavigationTarget::from_symbol)
102 Ok(Approximate(navs))
109 ) -> Cancelable<Option<Vec<NavigationTarget>>> {
110 if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
111 if module.has_semi() {
112 if let Some(child_module) =
113 hir::source_binder::module_from_declaration(db, file_id, module)
115 let nav = NavigationTarget::from_module(db, child_module)?;
116 return Ok(Some(vec![nav]));
125 use crate::mock_analysis::analysis_and_position;
127 fn check_goto(fixuture: &str, expected: &str) {
128 let (analysis, pos) = analysis_and_position(fixuture);
130 let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
131 assert_eq!(navs.len(), 1);
132 let nav = navs.pop().unwrap();
133 nav.assert_match(expected);
137 fn goto_definition_works_in_items() {
144 "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
149 fn goto_definition_resolves_correct_name() {
162 "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
167 fn goto_definition_works_for_module_declaration() {
175 "foo SOURCE_FILE FileId(2) [0; 10)",
185 "foo SOURCE_FILE FileId(2) [0; 10)",
190 fn goto_definition_works_for_methods() {
196 fn frobnicate(&self) { }
203 "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
213 "foo SOURCE_FILE FileId(2) [0; 10)",