mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 22:01:37 +00:00

There are few things to note in the implementation: First, this is a best-effort implementation. Mainly, type aliases may not be shown (due to their eager nature it's harder) and partial pathes (aka. hovering over `Struct` in `Struct::method`) are not supported at all. Second, we only need to show substitutions in expression and pattern position, because in type position all generic arguments always have to be written explicitly.
246 lines
5.9 KiB
Rust
246 lines
5.9 KiB
Rust
use hir::{AsAssocItem, Semantics};
|
|
use ide_db::{
|
|
defs::{Definition, NameClass, NameRefClass},
|
|
RootDatabase,
|
|
};
|
|
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
|
|
|
|
use crate::{
|
|
goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget,
|
|
RangeInfo,
|
|
};
|
|
|
|
// Feature: Go to Declaration
|
|
//
|
|
// Navigates to the declaration of an identifier.
|
|
//
|
|
// This is the same as `Go to Definition` with the following exceptions:
|
|
// - outline modules will navigate to the `mod name;` item declaration
|
|
// - trait assoc items will navigate to the assoc item of the trait declaration as opposed to the trait impl
|
|
// - fields in patterns will navigate to the field declaration of the struct, union or variant
|
|
pub(crate) fn goto_declaration(
|
|
db: &RootDatabase,
|
|
position @ FilePosition { file_id, offset }: FilePosition,
|
|
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
|
|
let sema = Semantics::new(db);
|
|
let file = sema.parse_guess_edition(file_id).syntax().clone();
|
|
let original_token = file
|
|
.token_at_offset(offset)
|
|
.find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
|
|
let range = original_token.text_range();
|
|
let info: Vec<NavigationTarget> = sema
|
|
.descend_into_macros_no_opaque(original_token)
|
|
.iter()
|
|
.filter_map(|token| {
|
|
let parent = token.parent()?;
|
|
let def = match_ast! {
|
|
match parent {
|
|
ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
|
|
NameRefClass::Definition(it, _) => Some(it),
|
|
NameRefClass::FieldShorthand { field_ref, .. } =>
|
|
return field_ref.try_to_nav(db),
|
|
NameRefClass::ExternCrateShorthand { decl, .. } =>
|
|
return decl.try_to_nav(db),
|
|
},
|
|
ast::Name(name) => match NameClass::classify(&sema, &name)? {
|
|
NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
|
|
NameClass::PatFieldShorthand { field_ref, .. } =>
|
|
return field_ref.try_to_nav(db),
|
|
},
|
|
_ => None
|
|
}
|
|
};
|
|
let assoc = match def? {
|
|
Definition::Module(module) => {
|
|
return Some(NavigationTarget::from_module_to_decl(db, module))
|
|
}
|
|
Definition::Const(c) => c.as_assoc_item(db),
|
|
Definition::TypeAlias(ta) => ta.as_assoc_item(db),
|
|
Definition::Function(f) => f.as_assoc_item(db),
|
|
Definition::ExternCrateDecl(it) => return it.try_to_nav(db),
|
|
_ => None,
|
|
}?;
|
|
|
|
let trait_ = assoc.implemented_trait(db)?;
|
|
let name = Some(assoc.name(db)?);
|
|
let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
|
|
item.try_to_nav(db)
|
|
})
|
|
.flatten()
|
|
.collect();
|
|
|
|
if info.is_empty() {
|
|
goto_definition(db, position)
|
|
} else {
|
|
Some(RangeInfo::new(range, info))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use ide_db::FileRange;
|
|
use itertools::Itertools;
|
|
|
|
use crate::fixture;
|
|
|
|
fn check(ra_fixture: &str) {
|
|
let (analysis, position, expected) = fixture::annotations(ra_fixture);
|
|
let navs = analysis
|
|
.goto_declaration(position)
|
|
.unwrap()
|
|
.expect("no declaration or definition found")
|
|
.info;
|
|
if navs.is_empty() {
|
|
panic!("unresolved reference")
|
|
}
|
|
|
|
let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
|
|
let navs = navs
|
|
.into_iter()
|
|
.map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
|
|
.sorted_by_key(cmp)
|
|
.collect::<Vec<_>>();
|
|
let expected = expected
|
|
.into_iter()
|
|
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
|
|
.sorted_by_key(cmp)
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(expected, navs);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_module_outline() {
|
|
check(
|
|
r#"
|
|
//- /main.rs
|
|
mod foo;
|
|
// ^^^
|
|
//- /foo.rs
|
|
use self$0;
|
|
"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_module_inline() {
|
|
check(
|
|
r#"
|
|
mod foo {
|
|
// ^^^
|
|
use self$0;
|
|
}
|
|
"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_goto_def_fallback() {
|
|
check(
|
|
r#"
|
|
struct Foo;
|
|
// ^^^
|
|
impl Foo$0 {}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_assoc_item_no_impl_item() {
|
|
check(
|
|
r#"
|
|
trait Trait {
|
|
const C: () = ();
|
|
// ^
|
|
}
|
|
impl Trait for () {}
|
|
|
|
fn main() {
|
|
<()>::C$0;
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_assoc_item() {
|
|
check(
|
|
r#"
|
|
trait Trait {
|
|
const C: () = ();
|
|
// ^
|
|
}
|
|
impl Trait for () {
|
|
const C: () = ();
|
|
}
|
|
|
|
fn main() {
|
|
<()>::C$0;
|
|
}
|
|
"#,
|
|
);
|
|
check(
|
|
r#"
|
|
trait Trait {
|
|
const C: () = ();
|
|
// ^
|
|
}
|
|
impl Trait for () {
|
|
const C$0: () = ();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_field_pat_shorthand() {
|
|
check(
|
|
r#"
|
|
struct Foo { field: u32 }
|
|
//^^^^^
|
|
fn main() {
|
|
let Foo { field$0 };
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_constructor_shorthand() {
|
|
check(
|
|
r#"
|
|
struct Foo { field: u32 }
|
|
//^^^^^
|
|
fn main() {
|
|
let field = 0;
|
|
Foo { field$0 };
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_for_extern_crate() {
|
|
check(
|
|
r#"
|
|
//- /main.rs crate:main deps:std
|
|
extern crate std$0;
|
|
/// ^^^
|
|
//- /std/lib.rs crate:std
|
|
// empty
|
|
"#,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn goto_decl_for_renamed_extern_crate() {
|
|
check(
|
|
r#"
|
|
//- /main.rs crate:main deps:std
|
|
extern crate std as abc$0;
|
|
/// ^^^
|
|
//- /std/lib.rs crate:std
|
|
// empty
|
|
"#,
|
|
)
|
|
}
|
|
}
|