use ra_ap_hir::{
AsAssocItem, AssocItem, AssocItemContainer, Crate, Function, HasSource, Impl, InFile,
ModuleDef, Name, PathResolution, Trait,
};
use ra_ap_ide::{FileId, RootDatabase};
use ra_ap_syntax::ast::HasName;
use ra_ap_syntax::{AstNode, SyntaxKind, SyntaxNode, TextSize, ast};
use super::common::ParsedFile;
#[derive(Clone, Copy)]
pub(super) struct EdgeSite {
pub(super) krate: Crate,
pub(super) file: FileId,
pub(super) offset: TextSize,
}
pub(super) struct SemanticEdge {
pub(super) source: EdgeSite,
pub(super) target: EdgeSite,
pub(super) enumerated: bool,
}
pub(super) fn collect(pf: &ParsedFile<'_>) -> Box<[SemanticEdge]> {
let mut edges: Vec<SemanticEdge> = Vec::new();
for node in pf.tree.syntax().descendants() {
match node.kind() {
SyntaxKind::PATH => add_path(pf, &node, &mut edges),
SyntaxKind::METHOD_CALL_EXPR => add_method_call(pf, &node, &mut edges),
_ => {}
}
}
edges.into_boxed_slice()
}
fn add_path(pf: &ParsedFile<'_>, node: &SyntaxNode, edges: &mut Vec<SemanticEdge>) {
let Some(path) = ast::Path::cast(node.clone()).filter(|_| is_resolvable_path(node)) else {
return;
};
let (Some(source), Some(PathResolution::Def(def))) =
(source_site(pf, node), pf.sema.resolve_path(&path))
else {
return;
};
if let Some(target) = definition_site(pf.db, def) {
edges.push(SemanticEdge {
source,
target,
enumerated: false,
});
}
}
fn is_resolvable_path(node: &SyntaxNode) -> bool {
let outermost = node
.parent()
.is_none_or(|parent| parent.kind() != SyntaxKind::PATH);
outermost && node.ancestors().all(|it| it.kind() != SyntaxKind::USE_TREE)
}
fn add_method_call(pf: &ParsedFile<'_>, node: &SyntaxNode, edges: &mut Vec<SemanticEdge>) {
let Some(call) = ast::MethodCallExpr::cast(node.clone()) else {
return;
};
let (Some(name), Some(function)) = (call.name_ref(), pf.sema.resolve_method_call(&call)) else {
return;
};
let Some(krate) = resolved_crate(pf, node) else {
return;
};
let source = EdgeSite {
krate,
file: pf.file_id,
offset: name.syntax().text_range().start(),
};
let (targets, enumerated) = method_targets(pf.db, function);
edges.extend(targets.into_iter().map(|target| SemanticEdge {
source,
target,
enumerated,
}));
}
fn source_site(pf: &ParsedFile<'_>, node: &SyntaxNode) -> Option<EdgeSite> {
Some(EdgeSite {
krate: resolved_crate(pf, node)?,
file: pf.file_id,
offset: node.text_range().start(),
})
}
fn resolved_crate(pf: &ParsedFile<'_>, node: &SyntaxNode) -> Option<Crate> {
pf.sema.scope(node).map(|scope| scope.krate())
}
fn method_targets(db: &RootDatabase, function: Function) -> (Vec<EdgeSite>, bool) {
let container = function.as_assoc_item(db).map(|item| item.container(db));
match container {
Some(AssocItemContainer::Trait(declared)) => (trait_targets(db, declared, function), true),
_ => (
definition_site(db, ModuleDef::Function(function))
.into_iter()
.collect(),
false,
),
}
}
fn trait_targets(db: &RootDatabase, declared: Trait, function: Function) -> Vec<EdgeSite> {
let name = function.name(db);
let sites: Vec<EdgeSite> = Impl::all_for_trait(db, declared)
.into_iter()
.filter(|found| is_repository_impl(db, *found))
.flat_map(|found| found.items(db))
.filter_map(|item| implemented_function(item, db, &name))
.filter_map(|found| definition_site(db, ModuleDef::Function(found)))
.collect();
match sites.is_empty() {
true => definition_site(db, ModuleDef::Function(function))
.into_iter()
.collect(),
false => sites,
}
}
fn is_repository_impl(db: &RootDatabase, found: Impl) -> bool {
found.module(db).krate(db).origin(db).is_local()
}
fn implemented_function(item: AssocItem, db: &RootDatabase, name: &Name) -> Option<Function> {
match item {
AssocItem::Function(found) if found.name(db) == *name => Some(found),
_ => None,
}
}
fn definition_site(db: &RootDatabase, def: ModuleDef) -> Option<EdgeSite> {
let krate = def.module(db)?.krate(db);
let (file, offset) = match def {
ModuleDef::Function(it) => named_range(db, it.source(db)?),
ModuleDef::Adt(it) => named_range(db, it.source(db)?),
ModuleDef::EnumVariant(it) => named_range(db, it.source(db)?),
ModuleDef::Const(it) => named_range(db, it.source(db)?),
ModuleDef::Static(it) => named_range(db, it.source(db)?),
ModuleDef::Trait(it) => named_range(db, it.source(db)?),
ModuleDef::TypeAlias(it) => named_range(db, it.source(db)?),
ModuleDef::Module(_) | ModuleDef::Macro(_) | ModuleDef::BuiltinType(_) => None,
}?;
Some(EdgeSite {
krate,
file,
offset,
})
}
fn named_range<N: AstNode + HasName>(
db: &RootDatabase,
source: InFile<N>,
) -> Option<(FileId, TextSize)> {
let file = source.file_id.file_id()?.file_id(db);
let name = source.value.name()?;
Some((file, name.syntax().text_range().start()))
}