Fix goto definition for record patterns

This commit is contained in:
Aleksey Kladov 2020-04-18 22:05:06 +02:00
parent ca61356b01
commit fa2ea8f494
7 changed files with 57 additions and 6 deletions

View file

@ -195,6 +195,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.analyze(field.syntax()).resolve_record_field(self.db, field) self.analyze(field.syntax()).resolve_record_field(self.db, field)
} }
pub fn resolve_record_field_pat(&self, field: &ast::RecordFieldPat) -> Option<StructField> {
self.analyze(field.syntax()).resolve_record_field_pat(self.db, field)
}
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
let sa = self.analyze(macro_call.syntax()); let sa = self.analyze(macro_call.syntax());
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call); let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);

View file

@ -95,6 +95,7 @@ impl SourceAnalyzer {
} }
fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> { fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
// FIXME: macros, see `expr_id`
let src = InFile { file_id: self.file_id, value: pat }; let src = InFile { file_id: self.file_id, value: pat };
self.body_source_map.as_ref()?.node_pat(src) self.body_source_map.as_ref()?.node_pat(src)
} }
@ -167,6 +168,16 @@ impl SourceAnalyzer {
Some((struct_field.into(), local)) Some((struct_field.into(), local))
} }
pub(crate) fn resolve_record_field_pat(
&self,
_db: &dyn HirDatabase,
field: &ast::RecordFieldPat,
) -> Option<StructField> {
let pat_id = self.pat_id(&field.pat()?)?;
let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?;
Some(struct_field.into())
}
pub(crate) fn resolve_macro_call( pub(crate) fn resolve_macro_call(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,

View file

@ -127,6 +127,7 @@ pub struct InferenceResult {
field_resolutions: FxHashMap<ExprId, StructFieldId>, field_resolutions: FxHashMap<ExprId, StructFieldId>,
/// For each field in record literal, records the field it resolves to. /// For each field in record literal, records the field it resolves to.
record_field_resolutions: FxHashMap<ExprId, StructFieldId>, record_field_resolutions: FxHashMap<ExprId, StructFieldId>,
record_field_pat_resolutions: FxHashMap<PatId, StructFieldId>,
/// For each struct literal, records the variant it resolves to. /// For each struct literal, records the variant it resolves to.
variant_resolutions: FxHashMap<ExprOrPatId, VariantId>, variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
/// For each associated item record what it resolves to /// For each associated item record what it resolves to
@ -147,6 +148,9 @@ impl InferenceResult {
pub fn record_field_resolution(&self, expr: ExprId) -> Option<StructFieldId> { pub fn record_field_resolution(&self, expr: ExprId) -> Option<StructFieldId> {
self.record_field_resolutions.get(&expr).copied() self.record_field_resolutions.get(&expr).copied()
} }
pub fn record_field_pat_resolution(&self, pat: PatId) -> Option<StructFieldId> {
self.record_field_pat_resolutions.get(&pat).copied()
}
pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> { pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> {
self.variant_resolutions.get(&id.into()).copied() self.variant_resolutions.get(&id.into()).copied()
} }

View file

@ -7,6 +7,7 @@ use hir_def::{
expr::{BindingAnnotation, Pat, PatId, RecordFieldPat}, expr::{BindingAnnotation, Pat, PatId, RecordFieldPat},
path::Path, path::Path,
type_ref::Mutability, type_ref::Mutability,
StructFieldId,
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
use test_utils::tested_by; use test_utils::tested_by;
@ -67,6 +68,11 @@ impl<'a> InferenceContext<'a> {
let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
for subpat in subpats { for subpat in subpats {
let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name));
if let Some(local_id) = matching_field {
let field_def = StructFieldId { parent: def.unwrap(), local_id };
self.result.record_field_pat_resolutions.insert(subpat.pat, field_def);
}
let expected_ty = let expected_ty =
matching_field.map_or(Ty::Unknown, |field| field_tys[field].clone().subst(&substs)); matching_field.map_or(Ty::Unknown, |field| field_tys[field].clone().subst(&substs));
let expected_ty = self.normalize_associated_types_in(expected_ty); let expected_ty = self.normalize_associated_types_in(expected_ty);

View file

@ -62,10 +62,9 @@ pub(crate) enum ReferenceResult {
impl ReferenceResult { impl ReferenceResult {
fn to_vec(self) -> Vec<NavigationTarget> { fn to_vec(self) -> Vec<NavigationTarget> {
use self::ReferenceResult::*;
match self { match self {
Exact(target) => vec![target], ReferenceResult::Exact(target) => vec![target],
Approximate(vec) => vec, ReferenceResult::Approximate(vec) => vec,
} }
} }
} }
@ -74,8 +73,6 @@ pub(crate) fn reference_definition(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
name_ref: &ast::NameRef, name_ref: &ast::NameRef,
) -> ReferenceResult { ) -> ReferenceResult {
use self::ReferenceResult::*;
let name_kind = classify_name_ref(sema, name_ref); let name_kind = classify_name_ref(sema, name_ref);
if let Some(def) = name_kind { if let Some(def) = name_kind {
let def = def.definition(); let def = def.definition();
@ -91,7 +88,7 @@ pub(crate) fn reference_definition(
.into_iter() .into_iter()
.map(|s| s.to_nav(sema.db)) .map(|s| s.to_nav(sema.db))
.collect(); .collect();
Approximate(navs) ReferenceResult::Approximate(navs)
} }
#[cfg(test)] #[cfg(test)]
@ -398,6 +395,25 @@ mod tests {
); );
} }
#[test]
fn goto_def_for_record_pat_fields() {
covers!(ra_ide_db::goto_def_for_record_field_pats);
check_goto(
r"
//- /lib.rs
struct Foo {
spam: u32,
}
fn bar(foo: Foo) -> Foo {
let Foo { spam<|>: _, } = foo
}
",
"spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)",
"spam: u32|spam",
);
}
#[test] #[test]
fn goto_def_for_record_fields_macros() { fn goto_def_for_record_fields_macros() {
check_goto( check_goto(

View file

@ -180,6 +180,7 @@ fn classify_name_inner(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Opti
} }
} }
#[derive(Debug)]
pub enum NameRefClass { pub enum NameRefClass {
Definition(Definition), Definition(Definition),
FieldShorthand { local: Local, field: Definition }, FieldShorthand { local: Local, field: Definition },
@ -229,6 +230,14 @@ pub fn classify_name_ref(
} }
} }
if let Some(record_field_pat) = ast::RecordFieldPat::cast(parent.clone()) {
tested_by!(goto_def_for_record_field_pats; force);
if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
let field = Definition::StructField(field);
return Some(NameRefClass::Definition(field));
}
}
if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
tested_by!(goto_def_for_macros; force); tested_by!(goto_def_for_macros; force);
if let Some(macro_def) = sema.resolve_macro_call(&macro_call) { if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {

View file

@ -6,5 +6,6 @@ test_utils::marks![
goto_def_for_fields goto_def_for_fields
goto_def_for_record_fields goto_def_for_record_fields
goto_def_for_field_init_shorthand goto_def_for_field_init_shorthand
goto_def_for_record_field_pats
search_filters_by_range search_filters_by_range
]; ];