mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 14:13:58 +00:00
Merge #906
906: Add support for goto definition and hover for struct fields r=matklad a=vipentti This works partially towards fixing #512 Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>
This commit is contained in:
commit
84e47113e0
3 changed files with 124 additions and 3 deletions
|
@ -74,6 +74,30 @@ pub(crate) fn reference_definition(
|
||||||
return Exact(NavigationTarget::from_field(db, field));
|
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 infer_result = function.infer(db);
|
||||||
|
let syntax_mapping = function.body_syntax_mapping(db);
|
||||||
|
|
||||||
|
let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast);
|
||||||
|
|
||||||
|
if let Some(expr) = struct_lit.and_then(|lit| syntax_mapping.node_expr(lit.into())) {
|
||||||
|
let ty = infer_result[expr].clone();
|
||||||
|
if let hir::Ty::Adt { def_id, .. } = ty {
|
||||||
|
if let hir::AdtDef::Struct(s) = def_id {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Try name resolution
|
// Try name resolution
|
||||||
let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax());
|
let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax());
|
||||||
|
@ -255,6 +279,26 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn goto_definition_works_when_used_on_definition_name_itself() {
|
fn goto_definition_works_when_used_on_definition_name_itself() {
|
||||||
check_goto(
|
check_goto(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ra_db::SourceDatabase;
|
use ra_db::SourceDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner},
|
AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner},
|
||||||
algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}},
|
algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -179,6 +179,7 @@ impl NavigationTarget {
|
||||||
.visit(doc_comments::<ast::TypeAliasDef>)
|
.visit(doc_comments::<ast::TypeAliasDef>)
|
||||||
.visit(doc_comments::<ast::ConstDef>)
|
.visit(doc_comments::<ast::ConstDef>)
|
||||||
.visit(doc_comments::<ast::StaticDef>)
|
.visit(doc_comments::<ast::StaticDef>)
|
||||||
|
.visit(doc_comments::<ast::NamedFieldDef>)
|
||||||
.accept(&node)?
|
.accept(&node)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +190,20 @@ impl NavigationTarget {
|
||||||
// TODO: After type inference is done, add type information to improve the output
|
// TODO: After type inference is done, add type information to improve the output
|
||||||
let node = self.node(db)?;
|
let node = self.node(db)?;
|
||||||
|
|
||||||
|
fn visit_ascribed_node<T>(node: &T, prefix: &str) -> Option<String>
|
||||||
|
where
|
||||||
|
T: NameOwner + VisibilityOwner + TypeAscriptionOwner,
|
||||||
|
{
|
||||||
|
let mut string = visit_node(node, prefix)?;
|
||||||
|
|
||||||
|
if let Some(type_ref) = node.ascribed_type() {
|
||||||
|
string.push_str(": ");
|
||||||
|
type_ref.syntax().text().push_to(&mut string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(string)
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_node<T>(node: &T, label: &str) -> Option<String>
|
fn visit_node<T>(node: &T, label: &str) -> Option<String>
|
||||||
where
|
where
|
||||||
T: NameOwner + VisibilityOwner,
|
T: NameOwner + VisibilityOwner,
|
||||||
|
@ -207,8 +222,9 @@ impl NavigationTarget {
|
||||||
.visit(|node: &ast::TraitDef| visit_node(node, "trait "))
|
.visit(|node: &ast::TraitDef| visit_node(node, "trait "))
|
||||||
.visit(|node: &ast::Module| visit_node(node, "mod "))
|
.visit(|node: &ast::Module| visit_node(node, "mod "))
|
||||||
.visit(|node: &ast::TypeAliasDef| visit_node(node, "type "))
|
.visit(|node: &ast::TypeAliasDef| visit_node(node, "type "))
|
||||||
.visit(|node: &ast::ConstDef| visit_node(node, "const "))
|
.visit(|node: &ast::ConstDef| visit_ascribed_node(node, "const "))
|
||||||
.visit(|node: &ast::StaticDef| visit_node(node, "static "))
|
.visit(|node: &ast::StaticDef| visit_ascribed_node(node, "static "))
|
||||||
|
.visit(|node: &ast::NamedFieldDef| visit_ascribed_node(node, ""))
|
||||||
.accept(&node)?
|
.accept(&node)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,6 +336,66 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_shows_struct_field_info() {
|
||||||
|
// Hovering over the field when instantiating
|
||||||
|
check_hover_result(
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
struct Foo {
|
||||||
|
field_a: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let foo = Foo {
|
||||||
|
field_a<|>: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
&["field_a: u32"],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hovering over the field in the definition
|
||||||
|
check_hover_result(
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
struct Foo {
|
||||||
|
field_a<|>: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let foo = Foo {
|
||||||
|
field_a: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
&["field_a: u32"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_const_static() {
|
||||||
|
check_hover_result(
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
fn main() {
|
||||||
|
const foo<|>: u32 = 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
&["const foo: u32"],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_result(
|
||||||
|
r#"
|
||||||
|
//- /main.rs
|
||||||
|
fn main() {
|
||||||
|
static foo<|>: u32 = 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
&["static foo: u32"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_some() {
|
fn hover_some() {
|
||||||
let (analysis, position) = single_file_with_position(
|
let (analysis, position) = single_file_with_position(
|
||||||
|
|
|
@ -2,6 +2,7 @@ test_utils::marks!(
|
||||||
inserts_parens_for_function_calls
|
inserts_parens_for_function_calls
|
||||||
goto_definition_works_for_methods
|
goto_definition_works_for_methods
|
||||||
goto_definition_works_for_fields
|
goto_definition_works_for_fields
|
||||||
|
goto_definition_works_for_named_fields
|
||||||
call_info_bad_offset
|
call_info_bad_offset
|
||||||
dont_complete_current_use
|
dont_complete_current_use
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue