use std::sync::Arc;
use crate::base::FileId;
use crate::hir::{HirSymbol, SymbolIndex, SymbolKind, TypeRef};
#[derive(Clone, Debug)]
pub struct ReferenceResult {
pub references: Vec<Reference>,
pub include_declaration: bool,
}
impl ReferenceResult {
pub fn empty() -> Self {
Self {
references: Vec::new(),
include_declaration: false,
}
}
pub fn is_empty(&self) -> bool {
self.references.is_empty()
}
pub fn len(&self) -> usize {
self.references.len()
}
}
#[derive(Clone, Debug)]
pub struct Reference {
pub file: FileId,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub is_definition: bool,
pub kind: SymbolKind,
}
impl Reference {
pub fn from_symbol(symbol: &HirSymbol, is_definition: bool) -> Self {
Self {
file: symbol.file,
start_line: symbol.start_line,
start_col: symbol.start_col,
end_line: symbol.end_line,
end_col: symbol.end_col,
is_definition,
kind: symbol.kind,
}
}
pub fn from_type_ref(type_ref: &TypeRef, file: FileId) -> Self {
Self {
file,
start_line: type_ref.start_line,
start_col: type_ref.start_col,
end_line: type_ref.end_line,
end_col: type_ref.end_col,
is_definition: false,
kind: SymbolKind::Other, }
}
}
pub fn find_references(
index: &SymbolIndex,
file: FileId,
line: u32,
col: u32,
include_declaration: bool,
) -> ReferenceResult {
if let Some((target_name, _source_symbol)) = find_type_ref_at_position(index, file, line, col) {
return find_references_for_target(index, &target_name, include_declaration);
}
let symbol = match find_symbol_at_position(index, file, line, col) {
Some(s) => s,
None => return ReferenceResult::empty(),
};
let target_name = if symbol.kind.is_definition() {
symbol.qualified_name.clone()
} else {
if !symbol.supertypes.is_empty() {
symbol.supertypes[0].clone()
} else {
symbol.qualified_name.clone()
}
};
find_references_for_target(index, &target_name, include_declaration)
}
fn find_references_for_target(
index: &SymbolIndex,
target_name: &str,
include_declaration: bool,
) -> ReferenceResult {
let mut references = Vec::new();
if let Some(def) = find_definition(index, target_name) {
if include_declaration {
references.push(Reference::from_symbol(def, true));
}
}
let _simple_name = target_name.rsplit("::").next().unwrap_or(target_name);
let type_refs: Vec<_> = index
.all_symbols()
.flat_map(|sym| {
sym.type_refs
.iter()
.flat_map(|trk| trk.as_refs()) .filter(|tr| {
tr.effective_target().as_ref() == target_name
})
.map(move |tr| Reference::from_type_ref(tr, sym.file))
})
.collect();
references.extend(type_refs);
for sym in index.all_symbols() {
if sym.name.as_ref() == target_name && !sym.kind.is_definition() {
if !references.iter().any(|r| {
r.file == sym.file && r.start_line == sym.start_line && r.start_col == sym.start_col
}) {
references.push(Reference::from_symbol(sym, false));
}
}
}
ReferenceResult {
references,
include_declaration,
}
}
fn find_type_ref_at_position(
index: &SymbolIndex,
file: FileId,
line: u32,
col: u32,
) -> Option<(Arc<str>, &HirSymbol)> {
let symbols = index.symbols_in_file(file);
for symbol in symbols {
for type_ref_kind in &symbol.type_refs {
if type_ref_kind.contains(line, col) {
if let Some((_, tr)) = type_ref_kind.part_at(line, col) {
return Some((tr.effective_target().clone(), symbol));
}
}
}
}
None
}
fn find_definition<'a>(index: &'a SymbolIndex, name: &str) -> Option<&'a HirSymbol> {
if let Some(def) = index.lookup_definition(name) {
return Some(def);
}
let simple_name = name.rsplit("::").next().unwrap_or(name);
let simple_matches: Vec<_> = index
.lookup_simple(simple_name)
.into_iter()
.filter(|s| s.kind.is_definition())
.collect();
if simple_matches.len() == 1 {
return Some(simple_matches[0]);
}
if name.contains("::") {
let suffix = format!("::{}", name);
for def in index.all_definitions() {
if def.qualified_name.ends_with(&suffix) || def.qualified_name.as_ref() == name {
return Some(def);
}
}
}
None
}
fn find_symbol_at_position(
index: &SymbolIndex,
file: FileId,
line: u32,
col: u32,
) -> Option<&HirSymbol> {
let symbols = index.symbols_in_file(file);
let mut best: Option<&HirSymbol> = None;
for symbol in symbols {
if contains_position(symbol, line, col) {
match best {
None => best = Some(symbol),
Some(current) => {
if symbol_size(symbol) < symbol_size(current) {
best = Some(symbol);
}
}
}
}
}
best
}
fn contains_position(symbol: &HirSymbol, line: u32, col: u32) -> bool {
let after_start =
line > symbol.start_line || (line == symbol.start_line && col >= symbol.start_col);
let before_end = line < symbol.end_line || (line == symbol.end_line && col <= symbol.end_col);
after_start && before_end
}
fn symbol_size(symbol: &HirSymbol) -> u32 {
let line_diff = symbol.end_line.saturating_sub(symbol.start_line);
let col_diff = symbol.end_col.saturating_sub(symbol.start_col);
line_diff * 1000 + col_diff
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::{RefKind, new_element_id};
fn make_symbol(
name: &str,
qualified: &str,
kind: SymbolKind,
file: u32,
line: u32,
) -> HirSymbol {
HirSymbol {
name: Arc::from(name),
short_name: None,
qualified_name: Arc::from(qualified),
element_id: new_element_id(),
kind,
file: FileId::new(file),
start_line: line,
start_col: 0,
end_line: line,
end_col: 10,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs: Vec::new(),
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
direction: None,
multiplicity: None,
}
}
#[test]
fn test_find_references_from_definition() {
use crate::hir::TypeRefKind;
let mut index = SymbolIndex::new();
let engine_def = make_symbol("Engine", "Engine", SymbolKind::PartDefinition, 0, 1);
let mut engine_usage1 = make_symbol("engine", "Car::engine", SymbolKind::PartUsage, 0, 10);
engine_usage1.supertypes = vec![Arc::from("Engine")];
engine_usage1.type_refs = vec![TypeRefKind::Simple(TypeRef::new(
"Engine",
RefKind::TypedBy,
10,
15,
10,
21,
))];
let mut engine_usage2 = make_symbol("motor", "Truck::motor", SymbolKind::PartUsage, 1, 5);
engine_usage2.supertypes = vec![Arc::from("Engine")];
engine_usage2.type_refs = vec![TypeRefKind::Simple(TypeRef::new(
"Engine",
RefKind::TypedBy,
5,
12,
5,
18,
))];
index.add_file(FileId::new(0), vec![engine_def, engine_usage1]);
index.add_file(FileId::new(1), vec![engine_usage2]);
let result = find_references(&index, FileId::new(0), 1, 5, true);
assert_eq!(result.len(), 3);
assert!(result.references.iter().any(|r| r.is_definition));
}
#[test]
fn test_find_references_from_usage() {
let mut index = SymbolIndex::new();
let wheel_def = make_symbol("Wheel", "Wheel", SymbolKind::PartDefinition, 0, 1);
let mut wheel_usage = make_symbol(
"frontWheel",
"Car::frontWheel",
SymbolKind::PartUsage,
0,
10,
);
wheel_usage.supertypes = vec![Arc::from("Wheel")];
index.add_file(FileId::new(0), vec![wheel_def, wheel_usage]);
let result = find_references(&index, FileId::new(0), 10, 5, true);
assert!(!result.is_empty());
}
#[test]
fn test_find_references_exclude_declaration() {
let mut index = SymbolIndex::new();
let part_def = make_symbol("Part", "Part", SymbolKind::PartDefinition, 0, 1);
let mut usage = make_symbol("myPart", "myPart", SymbolKind::PartUsage, 0, 10);
usage.supertypes = vec![Arc::from("Part")];
index.add_file(FileId::new(0), vec![part_def, usage]);
let result = find_references(&index, FileId::new(0), 1, 5, false);
assert!(result.references.iter().all(|r| !r.is_definition));
}
#[test]
fn test_find_references_not_found() {
let index = SymbolIndex::new();
let result = find_references(&index, FileId::new(0), 0, 0, true);
assert!(result.is_empty());
}
}