diff --git a/Cargo.lock b/Cargo.lock index 87c2883469..316dae053f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,7 +1039,6 @@ dependencies = [ "itertools", "join_to_string", "log", - "once_cell", "ra_assists", "ra_cfg", "ra_db", @@ -1060,6 +1059,7 @@ version = "0.1.0" dependencies = [ "fst", "log", + "once_cell", "ra_db", "ra_hir", "ra_prof", diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 7625fc8c82..410d8de624 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -19,7 +19,6 @@ join_to_string = "0.1.3" log = "0.4.8" rustc-hash = "1.1.0" rand = { version = "0.7.3", features = ["small_rng"] } -once_cell = "1.3.1" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index f61028f78c..5a41f702ef 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -68,9 +68,7 @@ pub use crate::{ folding_ranges::{Fold, FoldKind}, hover::HoverResult, inlay_hints::{InlayHint, InlayKind}, - references::{ - Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope, - }, + references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, runnables::{Runnable, RunnableKind, TestId}, source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, ssr::SsrError, @@ -88,6 +86,7 @@ pub use ra_ide_db::{ feature_flags::FeatureFlags, line_index::{LineCol, LineIndex}, line_index_utils::translate_offset_with_edit, + search::SearchScope, symbol_index::Query, RootDatabase, }; diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 95468d4343..6e2bf4ded9 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -10,28 +10,25 @@ //! resolved to the search element definition, we get a reference. mod rename; -mod search_scope; use hir::Semantics; -use once_cell::unsync::Lazy; -use ra_db::SourceDatabaseExt; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, + search::SearchScope, RootDatabase, }; use ra_prof::profile; use ra_syntax::{ algo::find_node_at_offset, ast::{self, NameOwner}, - match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, + AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset, }; -use test_utils::tested_by; use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; pub(crate) use self::rename::rename; -pub use self::search_scope::SearchScope; +pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; #[derive(Debug, Clone)] pub struct ReferenceSearchResult { @@ -46,25 +43,6 @@ pub struct Declaration { pub access: Option, } -#[derive(Debug, Clone)] -pub struct Reference { - pub file_range: FileRange, - pub kind: ReferenceKind, - pub access: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ReferenceKind { - StructLiteral, - Other, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum ReferenceAccess { - Read, - Write, -} - impl ReferenceSearchResult { pub fn declaration(&self) -> &Declaration { &self.declaration @@ -125,7 +103,8 @@ pub(crate) fn find_all_refs( let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; - let references = find_refs_to_def(db, &def, search_scope) + let references = def + .find_usages(db, search_scope) .into_iter() .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) .collect(); @@ -141,27 +120,6 @@ pub(crate) fn find_all_refs( Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) } -pub(crate) fn find_refs_to_def( - db: &RootDatabase, - def: &Definition, - search_scope: Option, -) -> Vec { - let search_scope = { - let base = SearchScope::for_def(&def, db); - match search_scope { - None => base, - Some(scope) => base.intersection(&scope), - } - }; - - let name = match def.name(db) { - None => return Vec::new(), - Some(it) => it.to_string(), - }; - - process_definition(db, def, name, search_scope) -} - fn find_name( sema: &Semantics, syntax: &SyntaxNode, @@ -179,72 +137,6 @@ fn find_name( Some(RangeInfo::new(range, def)) } -fn process_definition( - db: &RootDatabase, - def: &Definition, - name: String, - scope: SearchScope, -) -> Vec { - let _p = profile("process_definition"); - - let pat = name.as_str(); - let mut refs = vec![]; - - for (file_id, search_range) in scope { - let text = db.file_text(file_id); - let search_range = - search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text))); - - let sema = Semantics::new(db); - let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); - - for (idx, _) in text.match_indices(pat) { - let offset = TextUnit::from_usize(idx); - if !search_range.contains_inclusive(offset) { - tested_by!(search_filters_by_range); - continue; - } - - let name_ref = - if let Some(name_ref) = find_node_at_offset::(&tree, offset) { - name_ref - } else { - // Handle macro token cases - let token = match tree.token_at_offset(offset) { - TokenAtOffset::None => continue, - TokenAtOffset::Single(t) => t, - TokenAtOffset::Between(_, t) => t, - }; - let expanded = sema.descend_into_macros(token); - match ast::NameRef::cast(expanded.parent()) { - Some(name_ref) => name_ref, - _ => continue, - } - }; - - if let Some(d) = classify_name_ref(&sema, &name_ref) { - let d = d.definition(); - if &d == def { - let kind = - if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) { - ReferenceKind::StructLiteral - } else { - ReferenceKind::Other - }; - - let file_range = sema.original_range(name_ref.syntax()); - refs.push(Reference { - file_range, - kind, - access: reference_access(&d, &name_ref), - }); - } - } - } - } - refs -} - fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option { match def { Definition::Local(_) | Definition::StructField(_) => {} @@ -264,48 +156,6 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio None } -fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option { - // Only Locals and Fields have accesses for now. - match def { - Definition::Local(_) | Definition::StructField(_) => {} - _ => return None, - }; - - let mode = name_ref.syntax().ancestors().find_map(|node| { - match_ast! { - match (node) { - ast::BinExpr(expr) => { - if expr.op_kind()?.is_assignment() { - // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). - // FIXME: This is not terribly accurate. - if let Some(lhs) = expr.lhs() { - if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { - return Some(ReferenceAccess::Write); - } - } - } - Some(ReferenceAccess::Read) - }, - _ => {None} - } - } - }); - - // Default Locals and Fields to read - mode.or(Some(ReferenceAccess::Read)) -} - -fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { - name_ref - .syntax() - .ancestors() - .find_map(ast::RecordLit::cast) - .and_then(|l| l.path()) - .and_then(|p| p.segment()) - .map(|p| p.name_ref().as_ref() == Some(name_ref)) - .unwrap_or(false) -} - fn get_struct_def_name_for_struc_litetal_search( syntax: &SyntaxNode, position: FilePosition, @@ -324,20 +174,6 @@ fn get_struct_def_name_for_struc_litetal_search( None } -fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { - name_ref - .syntax() - .ancestors() - .find_map(ast::CallExpr::cast) - .and_then(|c| match c.expr()? { - ast::Expr::PathExpr(p) => { - Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) - } - _ => None, - }) - .unwrap_or(false) -} - #[cfg(test)] mod tests { use test_utils::covers; @@ -451,7 +287,7 @@ mod tests { #[test] fn search_filters_by_range() { - covers!(search_filters_by_range); + covers!(ra_ide_db::search_filters_by_range); let code = r#" fn foo() { let spam<|> = 92; @@ -767,7 +603,10 @@ mod tests { fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) { res.declaration().assert_match(expected_decl); assert_eq!(res.references.len(), expected_refs.len()); - res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i])); + res.references() + .iter() + .enumerate() + .for_each(|(i, r)| ref_assert_match(r, expected_refs[i])); } impl Declaration { @@ -785,21 +624,16 @@ mod tests { } } - impl Reference { - fn debug_render(&self) -> String { - let mut s = format!( - "{:?} {:?} {:?}", - self.file_range.file_id, self.file_range.range, self.kind - ); - if let Some(access) = self.access { - s.push_str(&format!(" {:?}", access)); - } - s + fn ref_debug_render(r: &Reference) -> String { + let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind); + if let Some(access) = r.access { + s.push_str(&format!(" {:?}", access)); } + s + } - fn assert_match(&self, expected: &str) { - let actual = self.debug_render(); - test_utils::assert_eq_text!(expected.trim(), actual.trim(),); - } + fn ref_assert_match(r: &Reference, expected: &str) { + let actual = ref_debug_render(r); + test_utils::assert_eq_text!(expected.trim(), actual.trim(),); } } diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs deleted file mode 100644 index d98c84d916..0000000000 --- a/crates/ra_ide/src/references/search_scope.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Generally, `search_scope` returns files that might contain references for the element. -//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. -//! In some cases, the location of the references is known to within a `TextRange`, -//! e.g. for things like local variables. -use std::mem; - -use hir::{DefWithBody, HasSource, ModuleSource}; -use ra_db::{FileId, SourceDatabaseExt}; -use ra_prof::profile; -use ra_syntax::{AstNode, TextRange}; -use rustc_hash::FxHashMap; - -use ra_ide_db::RootDatabase; - -use super::Definition; - -pub struct SearchScope { - entries: FxHashMap>, -} - -impl SearchScope { - fn empty() -> SearchScope { - SearchScope { entries: FxHashMap::default() } - } - - pub(crate) fn for_def(def: &Definition, db: &RootDatabase) -> SearchScope { - let _p = profile("search_scope"); - let module = match def.module(db) { - Some(it) => it, - None => return SearchScope::empty(), - }; - let module_src = module.definition_source(db); - let file_id = module_src.file_id.original_file(db); - - if let Definition::Local(var) = def { - let range = match var.parent(db) { - DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), - DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), - DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), - }; - let mut res = FxHashMap::default(); - res.insert(file_id, Some(range)); - return SearchScope::new(res); - } - - let vis = def.visibility(db).as_ref().map(|v| v.syntax().to_string()).unwrap_or_default(); - - if vis.as_str() == "pub(super)" { - if let Some(parent_module) = module.parent(db) { - let mut res = FxHashMap::default(); - let parent_src = parent_module.definition_source(db); - let file_id = parent_src.file_id.original_file(db); - - match parent_src.value { - ModuleSource::Module(m) => { - let range = Some(m.syntax().text_range()); - res.insert(file_id, range); - } - ModuleSource::SourceFile(_) => { - res.insert(file_id, None); - res.extend(parent_module.children(db).map(|m| { - let src = m.definition_source(db); - (src.file_id.original_file(db), None) - })); - } - } - return SearchScope::new(res); - } - } - - if vis.as_str() != "" { - let source_root_id = db.file_source_root(file_id); - let source_root = db.source_root(source_root_id); - let mut res = source_root.walk().map(|id| (id, None)).collect::>(); - - // FIXME: add "pub(in path)" - - if vis.as_str() == "pub(crate)" { - return SearchScope::new(res); - } - if vis.as_str() == "pub" { - let krate = module.krate(); - for rev_dep in krate.reverse_dependencies(db) { - let root_file = rev_dep.root_file(db); - let source_root_id = db.file_source_root(root_file); - let source_root = db.source_root(source_root_id); - res.extend(source_root.walk().map(|id| (id, None))); - } - return SearchScope::new(res); - } - } - - let mut res = FxHashMap::default(); - let range = match module_src.value { - ModuleSource::Module(m) => Some(m.syntax().text_range()), - ModuleSource::SourceFile(_) => None, - }; - res.insert(file_id, range); - SearchScope::new(res) - } - - fn new(entries: FxHashMap>) -> SearchScope { - SearchScope { entries } - } - pub fn single_file(file: FileId) -> SearchScope { - SearchScope::new(std::iter::once((file, None)).collect()) - } - pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope { - let (mut small, mut large) = (&self.entries, &other.entries); - if small.len() > large.len() { - mem::swap(&mut small, &mut large) - } - - let res = small - .iter() - .filter_map(|(file_id, r1)| { - let r2 = large.get(file_id)?; - let r = intersect_ranges(*r1, *r2)?; - Some((*file_id, r)) - }) - .collect(); - return SearchScope::new(res); - - fn intersect_ranges( - r1: Option, - r2: Option, - ) -> Option> { - match (r1, r2) { - (None, r) | (r, None) => Some(r), - (Some(r1), Some(r2)) => { - let r = r1.intersection(&r2)?; - Some(Some(r)) - } - } - } - } -} - -impl IntoIterator for SearchScope { - type Item = (FileId, Option); - type IntoIter = std::collections::hash_map::IntoIter>; - fn into_iter(self) -> Self::IntoIter { - self.entries.into_iter() - } -} diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml index 7ff1a536e3..52f0f23df5 100644 --- a/crates/ra_ide_db/Cargo.toml +++ b/crates/ra_ide_db/Cargo.toml @@ -16,6 +16,7 @@ rayon = "1.3.0" fst = { version = "0.3.5", default-features = false } rustc-hash = "1.1.0" superslice = "1.0.0" +once_cell = "1.3.1" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs index aa312c1403..79f48c9e32 100644 --- a/crates/ra_ide_db/src/lib.rs +++ b/crates/ra_ide_db/src/lib.rs @@ -9,6 +9,7 @@ pub mod feature_flags; pub mod symbol_index; pub mod change; pub mod defs; +pub mod search; pub mod imports_locator; mod wasm_shims; diff --git a/crates/ra_ide_db/src/marks.rs b/crates/ra_ide_db/src/marks.rs index d088fa2577..4f0a22af03 100644 --- a/crates/ra_ide_db/src/marks.rs +++ b/crates/ra_ide_db/src/marks.rs @@ -6,4 +6,5 @@ test_utils::marks![ goto_def_for_fields goto_def_for_record_fields goto_def_for_field_init_shorthand + search_filters_by_range ]; diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs new file mode 100644 index 0000000000..6f198df040 --- /dev/null +++ b/crates/ra_ide_db/src/search.rs @@ -0,0 +1,319 @@ +//! Implementation of find-usages functionality. +//! +//! It is based on the standard ide trick: first, we run a fast text search to +//! get a super-set of matches. Then, we we confirm each match using precise +//! name resolution. + +use std::mem; + +use hir::{DefWithBody, HasSource, ModuleSource, Semantics}; +use once_cell::unsync::Lazy; +use ra_db::{FileId, FileRange, SourceDatabaseExt}; +use ra_prof::profile; +use ra_syntax::{ + algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset, +}; +use rustc_hash::FxHashMap; +use test_utils::tested_by; + +use crate::{ + defs::{classify_name_ref, Definition}, + RootDatabase, +}; + +#[derive(Debug, Clone)] +pub struct Reference { + pub file_range: FileRange, + pub kind: ReferenceKind, + pub access: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ReferenceKind { + StructLiteral, + Other, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ReferenceAccess { + Read, + Write, +} + +/// Generally, `search_scope` returns files that might contain references for the element. +/// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. +/// In some cases, the location of the references is known to within a `TextRange`, +/// e.g. for things like local variables. +pub struct SearchScope { + entries: FxHashMap>, +} + +impl SearchScope { + fn new(entries: FxHashMap>) -> SearchScope { + SearchScope { entries } + } + + pub fn empty() -> SearchScope { + SearchScope::new(FxHashMap::default()) + } + + pub fn single_file(file: FileId) -> SearchScope { + SearchScope::new(std::iter::once((file, None)).collect()) + } + + pub fn intersection(&self, other: &SearchScope) -> SearchScope { + let (mut small, mut large) = (&self.entries, &other.entries); + if small.len() > large.len() { + mem::swap(&mut small, &mut large) + } + + let res = small + .iter() + .filter_map(|(file_id, r1)| { + let r2 = large.get(file_id)?; + let r = intersect_ranges(*r1, *r2)?; + Some((*file_id, r)) + }) + .collect(); + + return SearchScope::new(res); + + fn intersect_ranges( + r1: Option, + r2: Option, + ) -> Option> { + match (r1, r2) { + (None, r) | (r, None) => Some(r), + (Some(r1), Some(r2)) => { + let r = r1.intersection(&r2)?; + Some(Some(r)) + } + } + } + } +} + +impl IntoIterator for SearchScope { + type Item = (FileId, Option); + type IntoIter = std::collections::hash_map::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} + +impl Definition { + fn search_scope(&self, db: &RootDatabase) -> SearchScope { + let _p = profile("search_scope"); + let module = match self.module(db) { + Some(it) => it, + None => return SearchScope::empty(), + }; + let module_src = module.definition_source(db); + let file_id = module_src.file_id.original_file(db); + + if let Definition::Local(var) = self { + let range = match var.parent(db) { + DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), + DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), + DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), + }; + let mut res = FxHashMap::default(); + res.insert(file_id, Some(range)); + return SearchScope::new(res); + } + + let vis = self.visibility(db).as_ref().map(|v| v.syntax().to_string()).unwrap_or_default(); + + if vis.as_str() == "pub(super)" { + if let Some(parent_module) = module.parent(db) { + let mut res = FxHashMap::default(); + let parent_src = parent_module.definition_source(db); + let file_id = parent_src.file_id.original_file(db); + + match parent_src.value { + ModuleSource::Module(m) => { + let range = Some(m.syntax().text_range()); + res.insert(file_id, range); + } + ModuleSource::SourceFile(_) => { + res.insert(file_id, None); + res.extend(parent_module.children(db).map(|m| { + let src = m.definition_source(db); + (src.file_id.original_file(db), None) + })); + } + } + return SearchScope::new(res); + } + } + + if vis.as_str() != "" { + let source_root_id = db.file_source_root(file_id); + let source_root = db.source_root(source_root_id); + let mut res = source_root.walk().map(|id| (id, None)).collect::>(); + + // FIXME: add "pub(in path)" + + if vis.as_str() == "pub(crate)" { + return SearchScope::new(res); + } + if vis.as_str() == "pub" { + let krate = module.krate(); + for rev_dep in krate.reverse_dependencies(db) { + let root_file = rev_dep.root_file(db); + let source_root_id = db.file_source_root(root_file); + let source_root = db.source_root(source_root_id); + res.extend(source_root.walk().map(|id| (id, None))); + } + return SearchScope::new(res); + } + } + + let mut res = FxHashMap::default(); + let range = match module_src.value { + ModuleSource::Module(m) => Some(m.syntax().text_range()), + ModuleSource::SourceFile(_) => None, + }; + res.insert(file_id, range); + SearchScope::new(res) + } + + pub fn find_usages( + &self, + db: &RootDatabase, + search_scope: Option, + ) -> Vec { + let _p = profile("Definition::find_usages"); + + let search_scope = { + let base = self.search_scope(db); + match search_scope { + None => base, + Some(scope) => base.intersection(&scope), + } + }; + + let name = match self.name(db) { + None => return Vec::new(), + Some(it) => it.to_string(), + }; + + let pat = name.as_str(); + let mut refs = vec![]; + + for (file_id, search_range) in search_scope { + let text = db.file_text(file_id); + let search_range = + search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text))); + + let sema = Semantics::new(db); + let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); + + for (idx, _) in text.match_indices(pat) { + let offset = TextUnit::from_usize(idx); + if !search_range.contains_inclusive(offset) { + tested_by!(search_filters_by_range; force); + continue; + } + + let name_ref = + if let Some(name_ref) = find_node_at_offset::(&tree, offset) { + name_ref + } else { + // Handle macro token cases + let token = match tree.token_at_offset(offset) { + TokenAtOffset::None => continue, + TokenAtOffset::Single(t) => t, + TokenAtOffset::Between(_, t) => t, + }; + let expanded = sema.descend_into_macros(token); + match ast::NameRef::cast(expanded.parent()) { + Some(name_ref) => name_ref, + _ => continue, + } + }; + + // FIXME: reuse sb + // See https://github.com/rust-lang/rust/pull/68198#issuecomment-574269098 + + if let Some(d) = classify_name_ref(&sema, &name_ref) { + let d = d.definition(); + if &d == self { + let kind = if is_record_lit_name_ref(&name_ref) + || is_call_expr_name_ref(&name_ref) + { + ReferenceKind::StructLiteral + } else { + ReferenceKind::Other + }; + + let file_range = sema.original_range(name_ref.syntax()); + refs.push(Reference { + file_range, + kind, + access: reference_access(&d, &name_ref), + }); + } + } + } + } + refs + } +} + +fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option { + // Only Locals and Fields have accesses for now. + match def { + Definition::Local(_) | Definition::StructField(_) => {} + _ => return None, + }; + + let mode = name_ref.syntax().ancestors().find_map(|node| { + match_ast! { + match (node) { + ast::BinExpr(expr) => { + if expr.op_kind()?.is_assignment() { + // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). + // FIXME: This is not terribly accurate. + if let Some(lhs) = expr.lhs() { + if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { + return Some(ReferenceAccess::Write); + } + } + } + Some(ReferenceAccess::Read) + }, + _ => {None} + } + } + }); + + // Default Locals and Fields to read + mode.or(Some(ReferenceAccess::Read)) +} + +fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { + name_ref + .syntax() + .ancestors() + .find_map(ast::CallExpr::cast) + .and_then(|c| match c.expr()? { + ast::Expr::PathExpr(p) => { + Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) + } + _ => None, + }) + .unwrap_or(false) +} + +fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { + name_ref + .syntax() + .ancestors() + .find_map(ast::RecordLit::cast) + .and_then(|l| l.path()) + .and_then(|p| p.segment()) + .map(|p| p.name_ref().as_ref() == Some(name_ref)) + .unwrap_or(false) +}