rust-analyzer/crates/ra_ide_api/src/goto_definition.rs
Florian Diebold 85633656df Fix hover on the beginning of a nested expression
E.g. in
```
let foo = 1u32;
if true {
   <|>foo;
}
```
the hover shows `()`, the type of the whole if expression, instead of the more
sensible `u32`. The reason for this was that the search for an expression was
slightly left-biased: When on the edge between two tokens, it first looked at
all ancestors of the left token and then of the right token. Instead merge the
ancestors in ascending order, so that we get the smaller of the two possible
expressions.
2019-04-28 16:03:49 +02:00

468 lines
13 KiB
Rust

use ra_db::{FileId, SourceDatabase};
use ra_syntax::{
AstNode, ast,
algo::{find_node_at_offset, visit::{visitor, Visitor}},
SyntaxNode,
};
use test_utils::tested_by;
use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
pub(crate) fn goto_definition(
db: &RootDatabase,
position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
let file = db.parse(position.file_id);
let syntax = file.syntax();
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
let navs = reference_definition(db, position.file_id, name_ref).to_vec();
return Some(RangeInfo::new(name_ref.syntax().range(), navs.to_vec()));
}
if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
let navs = name_definition(db, position.file_id, name)?;
return Some(RangeInfo::new(name.syntax().range(), navs));
}
None
}
#[derive(Debug)]
pub(crate) enum ReferenceResult {
Exact(NavigationTarget),
Approximate(Vec<NavigationTarget>),
}
impl ReferenceResult {
fn to_vec(self) -> Vec<NavigationTarget> {
use self::ReferenceResult::*;
match self {
Exact(target) => vec![target],
Approximate(vec) => vec,
}
}
}
pub(crate) fn reference_definition(
db: &RootDatabase,
file_id: FileId,
name_ref: &ast::NameRef,
) -> ReferenceResult {
use self::ReferenceResult::*;
let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None);
// Special cases:
// Check if it is a method
if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) {
tested_by!(goto_definition_works_for_methods);
if let Some(func) = analyzer.resolve_method_call(method_call) {
return Exact(NavigationTarget::from_function(db, func));
}
}
// It could also be a field access
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
tested_by!(goto_definition_works_for_fields);
if let Some(field) = analyzer.resolve_field(field_expr) {
return Exact(NavigationTarget::from_field(db, field));
};
}
// It could also be a named field
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) {
tested_by!(goto_definition_works_for_named_fields);
let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast);
if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) {
if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() {
let hir_path = hir::Path::from_name_ref(name_ref);
let hir_name = hir_path.as_ident().unwrap();
if let Some(field) = s.field(db, hir_name) {
return Exact(NavigationTarget::from_field(db, field));
}
}
}
}
// General case, a path or a local:
if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
if let Some(resolved) = analyzer.resolve_path(db, path) {
match resolved {
hir::PathResolution::Def(def) => return Exact(NavigationTarget::from_def(db, def)),
hir::PathResolution::LocalBinding(pat) => {
let nav = NavigationTarget::from_pat(db, file_id, pat);
return Exact(nav);
}
hir::PathResolution::GenericParam(..) => {
// FIXME: go to the generic param def
}
hir::PathResolution::SelfType(impl_block) => {
let ty = impl_block.target_ty(db);
if let Some((def_id, _)) = ty.as_adt() {
return Exact(NavigationTarget::from_adt_def(db, def_id));
}
}
hir::PathResolution::AssocItem(assoc) => {
return Exact(NavigationTarget::from_impl_item(db, assoc));
}
}
}
}
// Fallback index based approach:
let navs = crate::symbol_index::index_resolve(db, name_ref)
.into_iter()
.map(NavigationTarget::from_symbol)
.collect();
Approximate(navs)
}
pub(crate) fn name_definition(
db: &RootDatabase,
file_id: FileId,
name: &ast::Name,
) -> Option<Vec<NavigationTarget>> {
let parent = name.syntax().parent()?;
if let Some(module) = ast::Module::cast(&parent) {
if module.has_semi() {
if let Some(child_module) =
hir::source_binder::module_from_declaration(db, file_id, module)
{
let nav = NavigationTarget::from_module(db, child_module);
return Some(vec![nav]);
}
}
}
if let Some(nav) = named_target(file_id, &parent) {
return Some(vec![nav]);
}
None
}
fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
visitor()
.visit(|node: &ast::StructDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::EnumDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::EnumVariant| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::FnDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::TypeAliasDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::ConstDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::StaticDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::TraitDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::NamedFieldDef| NavigationTarget::from_named(file_id, node))
.visit(|node: &ast::Module| NavigationTarget::from_named(file_id, node))
.accept(node)
}
#[cfg(test)]
mod tests {
use test_utils::covers;
use crate::mock_analysis::analysis_and_position;
fn check_goto(fixture: &str, expected: &str) {
let (analysis, pos) = analysis_and_position(fixture);
let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
assert_eq!(navs.len(), 1);
let nav = navs.pop().unwrap();
nav.assert_match(expected);
}
#[test]
fn goto_definition_works_in_items() {
check_goto(
"
//- /lib.rs
struct Foo;
enum E { X(Foo<|>) }
",
"Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
);
}
#[test]
fn goto_definition_resolves_correct_name() {
check_goto(
"
//- /lib.rs
use a::Foo;
mod a;
mod b;
enum E { X(Foo<|>) }
//- /a.rs
struct Foo;
//- /b.rs
struct Foo;
",
"Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
);
}
#[test]
fn goto_definition_works_for_module_declaration() {
check_goto(
"
//- /lib.rs
mod <|>foo;
//- /foo.rs
// empty
",
"foo SOURCE_FILE FileId(2) [0; 10)",
);
check_goto(
"
//- /lib.rs
mod <|>foo;
//- /foo/mod.rs
// empty
",
"foo SOURCE_FILE FileId(2) [0; 10)",
);
}
#[test]
fn goto_definition_works_for_methods() {
covers!(goto_definition_works_for_methods);
check_goto(
"
//- /lib.rs
struct Foo;
impl Foo {
fn frobnicate(&self) { }
}
fn bar(foo: &Foo) {
foo.frobnicate<|>();
}
",
"frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
);
}
#[test]
fn goto_definition_works_for_fields() {
covers!(goto_definition_works_for_fields);
check_goto(
"
//- /lib.rs
struct Foo {
spam: u32,
}
fn bar(foo: &Foo) {
foo.spam<|>;
}
",
"spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
);
}
#[test]
fn goto_definition_works_for_named_fields() {
covers!(goto_definition_works_for_named_fields);
check_goto(
"
//- /lib.rs
struct Foo {
spam: u32,
}
fn bar() -> Foo {
Foo {
spam<|>: 0,
}
}
",
"spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
);
}
#[test]
fn goto_definition_on_self() {
check_goto(
"
//- /lib.rs
struct Foo;
impl Foo {
pub fn new() -> Self {
Self<|> {}
}
}
",
"Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
);
check_goto(
"
//- /lib.rs
struct Foo;
impl Foo {
pub fn new() -> Self<|> {
Self {}
}
}
",
"Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
);
check_goto(
"
//- /lib.rs
enum Foo { A }
impl Foo {
pub fn new() -> Self<|> {
Foo::A
}
}
",
"Foo ENUM_DEF FileId(1) [0; 14) [5; 8)",
);
check_goto(
"
//- /lib.rs
enum Foo { A }
impl Foo {
pub fn thing(a: &Self<|>) {
}
}
",
"Foo ENUM_DEF FileId(1) [0; 14) [5; 8)",
);
}
#[test]
fn goto_definition_on_self_in_trait_impl() {
check_goto(
"
//- /lib.rs
struct Foo;
trait Make {
fn new() -> Self;
}
impl Make for Foo {
fn new() -> Self {
Self<|> {}
}
}
",
"Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
);
check_goto(
"
//- /lib.rs
struct Foo;
trait Make {
fn new() -> Self;
}
impl Make for Foo {
fn new() -> Self<|> {
Self{}
}
}
",
"Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
);
}
#[test]
fn goto_definition_works_when_used_on_definition_name_itself() {
check_goto(
"
//- /lib.rs
struct Foo<|> { value: u32 }
",
"Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)",
);
check_goto(
r#"
//- /lib.rs
struct Foo {
field<|>: string,
}
"#,
"field NAMED_FIELD_DEF FileId(1) [17; 30) [17; 22)",
);
check_goto(
"
//- /lib.rs
fn foo_test<|>() {
}
",
"foo_test FN_DEF FileId(1) [0; 17) [3; 11)",
);
check_goto(
"
//- /lib.rs
enum Foo<|> {
Variant,
}
",
"Foo ENUM_DEF FileId(1) [0; 25) [5; 8)",
);
check_goto(
"
//- /lib.rs
enum Foo {
Variant1,
Variant2<|>,
Variant3,
}
",
"Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)",
);
check_goto(
r#"
//- /lib.rs
static inner<|>: &str = "";
"#,
"inner STATIC_DEF FileId(1) [0; 24) [7; 12)",
);
check_goto(
r#"
//- /lib.rs
const inner<|>: &str = "";
"#,
"inner CONST_DEF FileId(1) [0; 23) [6; 11)",
);
check_goto(
r#"
//- /lib.rs
type Thing<|> = Option<()>;
"#,
"Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)",
);
check_goto(
r#"
//- /lib.rs
trait Foo<|> {
}
"#,
"Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)",
);
check_goto(
r#"
//- /lib.rs
mod bar<|> {
}
"#,
"bar MODULE FileId(1) [0; 11) [4; 7)",
);
}
}