use std::{marker::PhantomData, sync::Arc, hash::{Hash, Hasher}}; use ra_arena::{Arena, RawId, impl_arena_id}; use ra_syntax::{SyntaxNodePtr, TreeArc, SyntaxNode, AstNode, ast}; use crate::{HirFileId, DefDatabase}; /// `AstId` points to an AST node in any file. /// /// It is stable across reparses, and can be used as salsa key/value. #[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 syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.raw); N::cast(&syntax_node).unwrap().to_owned() } } /// `AstId` points to an AST node in a specific file. #[derive(Debug)] pub(crate) struct FileAstId { raw: ErasedFileAstId, _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 } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ErasedFileAstId(RawId); impl_arena_id!(ErasedFileAstId); /// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back. #[derive(Debug, PartialEq, Eq, Default)] pub struct AstIdMap { arena: Arena, } impl AstIdMap { pub(crate) fn ast_id_map_query(db: &impl DefDatabase, file_id: HirFileId) -> Arc { let map = if let Some(node) = db.parse_or_expand(file_id) { AstIdMap::from_source(&*node) } else { AstIdMap::default() }; Arc::new(map) } pub(crate) fn file_item_query( db: &impl DefDatabase, file_id: HirFileId, ast_id: ErasedFileAstId, ) -> TreeArc { let node = db.parse_or_expand(file_id).unwrap(); db.ast_id_map(file_id).arena[ast_id].to_node(&*node).to_owned() } pub(crate) fn ast_id(&self, item: &N) -> FileAstId { let ptr = SyntaxNodePtr::new(item.syntax()); let raw = match self.arena.iter().find(|(_id, i)| **i == ptr) { Some((it, _)) => it, None => panic!( "Can't find {:?} in AstIdMap:\n{:?}", item.syntax(), self.arena.iter().map(|(_id, i)| i).collect::>(), ), }; FileAstId { raw, _ty: PhantomData } } fn from_source(node: &SyntaxNode) -> AstIdMap { assert!(node.parent().is_none()); let mut res = AstIdMap { 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(node, |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) -> ErasedFileAstId { self.arena.alloc(SyntaxNodePtr::new(item)) } } /// 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); } }