Refactor reference searching to work with the ast

This commit is contained in:
Lukas Wirth 2021-02-07 18:38:12 +01:00
parent 43ccbf4360
commit d644728d82
8 changed files with 488 additions and 405 deletions

View file

@ -10,7 +10,9 @@ use base_db::{FileId, FileRange, SourceDatabaseExt};
use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility};
use once_cell::unsync::Lazy;
use rustc_hash::FxHashMap;
use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
use syntax::{
ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxNode, TextRange, TextSize,
};
use crate::defs::NameClass;
use crate::{
@ -18,6 +20,13 @@ use crate::{
RootDatabase,
};
#[derive(Debug, Clone)]
pub enum NameKind {
Name,
NameRef,
Lifetime,
}
#[derive(Debug, Default, Clone)]
pub struct UsageSearchResult {
pub references: FxHashMap<FileId, Vec<FileReference>>,
@ -52,23 +61,53 @@ impl IntoIterator for UsageSearchResult {
}
}
#[derive(Debug, Clone)]
pub enum NameLike {
NameRef(ast::NameRef),
Name(ast::Name),
Lifetime(ast::Lifetime),
}
mod __ {
use super::{
ast::{Lifetime, Name, NameRef},
NameLike,
};
stdx::impl_from!(NameRef, Name, Lifetime for NameLike);
}
#[derive(Debug, Clone)]
pub struct FileReference {
pub range: TextRange,
pub kind: ReferenceKind,
pub name: NameKind,
pub access: Option<ReferenceAccess>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ReferenceKind {
FieldShorthandForField,
FieldShorthandForLocal,
StructLiteral,
RecordFieldExprOrPat,
SelfParam,
EnumLiteral,
Lifetime,
Other,
impl FileReference {
pub fn name_from_syntax(&self, root: &SyntaxNode) -> Option<NameLike> {
let node = node_or_parent(root.covering_element(self.range));
match self.name {
NameKind::Name => ast::Name::cast(node).map(Into::into),
NameKind::NameRef => ast::NameRef::cast(node).map(Into::into),
NameKind::Lifetime => ast::Lifetime::cast(node).map(Into::into),
}
}
pub fn as_name_ref(&self, root: &SyntaxNode) -> Option<ast::NameRef> {
match self.name {
NameKind::NameRef => {
ast::NameRef::cast(node_or_parent(root.covering_element(self.range)))
}
_ => None,
}
}
}
fn node_or_parent(ele: SyntaxElement) -> SyntaxNode {
match ele {
NodeOrToken::Node(node) => node,
NodeOrToken::Token(token) => token.parent(),
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -369,8 +408,7 @@ impl<'a> FindUsages<'a> {
match NameRefClass::classify_lifetime(self.sema, lifetime) {
Some(NameRefClass::Definition(def)) if &def == self.def => {
let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
let reference =
FileReference { range, kind: ReferenceKind::Lifetime, access: None };
let reference = FileReference { range, name: NameKind::Lifetime, access: None };
sink(file_id, reference)
}
_ => false, // not a usage
@ -384,19 +422,12 @@ impl<'a> FindUsages<'a> {
) -> bool {
match NameRefClass::classify(self.sema, &name_ref) {
Some(NameRefClass::Definition(def)) if &def == self.def => {
let kind = if is_record_field_expr_or_pat(&name_ref) {
ReferenceKind::RecordFieldExprOrPat
} else if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) {
ReferenceKind::StructLiteral
} else if is_enum_lit_name_ref(&name_ref) {
ReferenceKind::EnumLiteral
} else {
ReferenceKind::Other
};
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let reference =
FileReference { range, kind, access: reference_access(&def, &name_ref) };
let reference = FileReference {
range,
name: NameKind::NameRef,
access: reference_access(&def, &name_ref),
};
sink(file_id, reference)
}
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
@ -404,12 +435,12 @@ impl<'a> FindUsages<'a> {
let reference = match self.def {
Definition::Field(_) if &field == self.def => FileReference {
range,
kind: ReferenceKind::FieldShorthandForField,
name: NameKind::NameRef,
access: reference_access(&field, &name_ref),
},
Definition::Local(l) if &local == l => FileReference {
range,
kind: ReferenceKind::FieldShorthandForLocal,
name: NameKind::NameRef,
access: reference_access(&Definition::Local(local), &name_ref),
},
_ => return false, // not a usage
@ -433,7 +464,7 @@ impl<'a> FindUsages<'a> {
let FileRange { file_id, range } = self.sema.original_range(name.syntax());
let reference = FileReference {
range,
kind: ReferenceKind::FieldShorthandForField,
name: NameKind::Name,
// FIXME: mutable patterns should have `Write` access
access: Some(ReferenceAccess::Read),
};
@ -473,54 +504,3 @@ fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<Referen
// 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::RecordExpr::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 is_record_field_expr_or_pat(name_ref: &ast::NameRef) -> bool {
if let Some(parent) = name_ref.syntax().parent() {
match_ast! {
match parent {
ast::RecordExprField(it) => true,
ast::RecordPatField(_it) => true,
_ => false,
}
}
} else {
false
}
}
fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool {
name_ref
.syntax()
.ancestors()
.find_map(ast::PathExpr::cast)
.and_then(|p| p.path())
.and_then(|p| p.qualifier())
.and_then(|p| p.segment())
.map(|p| p.name_ref().as_ref() == Some(name_ref))
.unwrap_or(false)
}