mod source_to_def;
use crate::{
ids::{DefWithBodyId, ItemDefinitionId},
resolve,
resolve::HasResolver,
semantics::source_to_def::{SourceToDefCache, SourceToDefContainer, SourceToDefContext},
source_analyzer::SourceAnalyzer,
FileId, HirDatabase, InFile, ModuleDef, Name, PatId, PerNs, Resolver, Ty, Visibility,
};
use mun_syntax::{ast, AstNode, SyntaxNode, TextSize};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use std::cell::RefCell;
pub struct Semantics<'db> {
pub db: &'db dyn HirDatabase,
source_file_to_file: RefCell<FxHashMap<SyntaxNode, FileId>>,
source_to_definition_cache: RefCell<SourceToDefCache>,
}
impl<'db> Semantics<'db> {
pub fn new(db: &'db dyn HirDatabase) -> Self {
Self {
db,
source_file_to_file: Default::default(),
source_to_definition_cache: Default::default(),
}
}
pub fn parse(&self, file_id: FileId) -> ast::SourceFile {
let tree = self.db.parse(file_id).tree();
let mut cache = self.source_file_to_file.borrow_mut();
cache.insert(tree.syntax().clone(), file_id);
tree
}
pub fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> SemanticsScope<'db> {
let analyzer = self.analyze_with_offset(node, offset);
SemanticsScope {
db: self.db,
file_id: analyzer.file_id,
resolver: analyzer.resolver,
}
}
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Ty> {
self.analyze(expr.syntax()).type_of_expr(self.db, expr)
}
fn analyze(&self, node: &SyntaxNode) -> SourceAnalyzer {
self.build_analyzer(node, None)
}
fn analyze_with_offset(&self, node: &SyntaxNode, offset: TextSize) -> SourceAnalyzer {
self.build_analyzer(node, Some(offset))
}
fn build_analyzer(&self, node: &SyntaxNode, offset: Option<TextSize>) -> SourceAnalyzer {
let node = self.find_file(node.clone());
let node = node.as_ref();
let container = match self.with_source_to_def_context(|ctx| ctx.find_container(node)) {
Some(it) => it,
None => return SourceAnalyzer::new_for_resolver(Resolver::default(), node),
};
let resolver = match container {
SourceToDefContainer::DefWithBodyId(def) => {
return SourceAnalyzer::new_for_body(self.db, def, node, offset)
}
SourceToDefContainer::ModuleId(id) => id.resolver(self.db.upcast()),
};
SourceAnalyzer::new_for_resolver(resolver, node)
}
fn with_source_to_def_context<F: FnOnce(&mut SourceToDefContext) -> T, T>(&self, f: F) -> T {
let mut cache = self.source_to_definition_cache.borrow_mut();
let mut context = SourceToDefContext {
db: self.db,
cache: &mut cache,
};
f(&mut context)
}
fn lookup_file(&self, root_node: &SyntaxNode) -> Option<FileId> {
let cache = self.source_file_to_file.borrow();
cache.get(root_node).copied()
}
fn find_file(&self, node: SyntaxNode) -> InFile<SyntaxNode> {
let root_node = find_root(&node);
let file_id = self.lookup_file(&root_node).unwrap_or_else(|| {
panic!(
"\n\nFailed to lookup {:?} in this Semantics.\n\
Make sure to use only query nodes, derived from this instance of Semantics.\n\
root node: {:?}\n\
known nodes: {}\n\n",
node,
root_node,
self.source_file_to_file
.borrow()
.keys()
.map(|it| format!("{:?}", it))
.collect::<Vec<_>>()
.join(", ")
)
});
InFile::new(file_id, node)
}
}
fn find_root(node: &SyntaxNode) -> SyntaxNode {
node.ancestors().last().unwrap()
}
pub struct SemanticsScope<'a> {
pub db: &'a dyn HirDatabase,
file_id: FileId,
resolver: Resolver,
}
pub enum ScopeDef {
ModuleDef(ModuleDef),
Local(Local),
Unknown,
}
impl ScopeDef {
pub fn all_items(def: PerNs<(ItemDefinitionId, Visibility)>) -> SmallVec<[Self; 2]> {
let mut items = SmallVec::new();
match (def.take_types(), def.take_values()) {
(Some(ty), None) => items.push(ScopeDef::ModuleDef(ty.0.into())),
(None, Some(val)) => items.push(ScopeDef::ModuleDef(val.0.into())),
(Some(ty), Some(val)) => {
items.push(ScopeDef::ModuleDef(ty.0.into()));
if ty != val {
items.push(ScopeDef::ModuleDef(val.0.into()))
}
}
(None, None) => {}
};
if items.is_empty() {
items.push(ScopeDef::Unknown)
}
items
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Local {
pub(crate) parent: DefWithBodyId,
pub(crate) pat_id: PatId,
}
impl Local {
pub fn ty(self, db: &dyn HirDatabase) -> Ty {
let infer = db.infer(self.parent);
infer[self.pat_id].clone()
}
}
impl<'a> SemanticsScope<'a> {
pub fn visit_all_names(&self, visit: &mut dyn FnMut(Name, ScopeDef)) {
let resolver = &self.resolver;
resolver.visit_all_names(self.db.upcast(), &mut |name, def| {
let def = match def {
resolve::ScopeDef::PerNs(it) => {
let items = ScopeDef::all_items(it);
for item in items {
visit(name.clone(), item);
}
return;
}
resolve::ScopeDef::Local(pat_id) => {
let parent = resolver
.body_owner()
.expect("found a local outside of a body");
ScopeDef::Local(Local { parent, pat_id })
}
};
visit(name, def)
})
}
}