use std::{marker::PhantomData, sync::Arc, hash::{Hash, Hasher}}; use ra_arena::{Arena, RawId, impl_arena_id}; use ra_syntax::{SyntaxNodePtr, TreeArc, SyntaxNode, SourceFile, AstNode, ast}; use crate::{HirFileId, DefDatabase}; #[derive(Debug)] pub(crate) struct AstId { file_id: HirFileId, file_ast_id: FileAstId, } impl Clone for AstId { fn clone(&self) -> AstId { *self } } impl Copy for AstId {} impl PartialEq for AstId { fn eq(&self, other: &Self) -> bool { (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id) } } impl Eq for AstId {} impl Hash for AstId { fn hash(&self, hasher: &mut H) { (self.file_id, self.file_ast_id).hash(hasher); } } impl AstId { pub(crate) fn file_id(&self) -> HirFileId { self.file_id } pub(crate) fn to_node(&self, db: &impl DefDatabase) -> TreeArc { let source_item_id = SourceItemId { file_id: self.file_id(), item_id: self.file_ast_id.raw }; let syntax_node = db.file_item(source_item_id); N::cast(&syntax_node).unwrap().to_owned() } } #[derive(Debug)] pub(crate) struct FileAstId { raw: SourceFileItemId, _ty: PhantomData, } impl Clone for FileAstId { fn clone(&self) -> FileAstId { *self } } impl Copy for FileAstId {} impl PartialEq for FileAstId { fn eq(&self, other: &Self) -> bool { self.raw == other.raw } } impl Eq for FileAstId {} impl Hash for FileAstId { fn hash(&self, hasher: &mut H) { self.raw.hash(hasher); } } impl FileAstId { pub(crate) fn with_file_id(self, file_id: HirFileId) -> AstId { AstId { file_id, file_ast_id: self } } } /// Identifier of item within a specific file. This is stable over reparses, so /// it's OK to use it as a salsa key/value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct SourceFileItemId(RawId); impl_arena_id!(SourceFileItemId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SourceItemId { file_id: HirFileId, item_id: SourceFileItemId, } /// Maps items' `SyntaxNode`s to `SourceFileItemId`s and back. #[derive(Debug, PartialEq, Eq)] pub struct SourceFileItems { file_id: HirFileId, arena: Arena, } impl SourceFileItems { pub(crate) fn file_items_query( db: &impl DefDatabase, file_id: HirFileId, ) -> Arc { let source_file = db.hir_parse(file_id); Arc::new(SourceFileItems::from_source_file(&source_file, file_id)) } pub(crate) fn file_item_query( db: &impl DefDatabase, source_item_id: SourceItemId, ) -> TreeArc { let source_file = db.hir_parse(source_item_id.file_id); db.file_items(source_item_id.file_id).arena[source_item_id.item_id] .to_node(&source_file) .to_owned() } pub(crate) fn ast_id(&self, item: &N) -> FileAstId { FileAstId { raw: self.id_of_unchecked(item.syntax()), _ty: PhantomData } } fn from_source_file(source_file: &SourceFile, file_id: HirFileId) -> SourceFileItems { let mut res = SourceFileItems { file_id, arena: Arena::default() }; // By walking the tree in bread-first order we make sure that parents // get lower ids then children. That is, adding a new child does not // change parent's id. This means that, say, adding a new function to a // trait does not change ids of top-level items, which helps caching. bfs(source_file.syntax(), |it| { if let Some(module_item) = ast::ModuleItem::cast(it) { res.alloc(module_item.syntax()); } else if let Some(macro_call) = ast::MacroCall::cast(it) { res.alloc(macro_call.syntax()); } }); res } fn alloc(&mut self, item: &SyntaxNode) -> SourceFileItemId { self.arena.alloc(SyntaxNodePtr::new(item)) } fn id_of_unchecked(&self, item: &SyntaxNode) -> SourceFileItemId { let ptr = SyntaxNodePtr::new(item); if let Some((id, _)) = self.arena.iter().find(|(_id, i)| **i == ptr) { return id; } panic!( "Can't find {:?} in SourceFileItems:\n{:?}", item, self.arena.iter().map(|(_id, i)| i).collect::>(), ); } } /// Walks the subtree in bfs order, calling `f` for each node. fn bfs(node: &SyntaxNode, mut f: impl FnMut(&SyntaxNode)) { let mut curr_layer = vec![node]; let mut next_layer = vec![]; while !curr_layer.is_empty() { curr_layer.drain(..).for_each(|node| { next_layer.extend(node.children()); f(node); }); std::mem::swap(&mut curr_layer, &mut next_layer); } }