//! Things to wrap other things in file ids. use either::Either; use span::{ AstIdNode, ErasedFileAstId, FileAstId, FileId, FileRange, HirFileId, HirFileIdRepr, MacroFileId, SyntaxContextId, }; use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize}; use crate::{ db::{self, ExpandDatabase}, map_node_range_up, map_node_range_up_rooted, span_for_offset, MacroFileIdExt, }; /// `InFile` stores a value of `T` inside a particular file/syntax tree. /// /// Typical usages are: /// /// * `InFile` -- syntax node in a file /// * `InFile` -- ast node in a file /// * `InFile` -- offset in a file #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct InFileWrapper { pub file_id: FileKind, pub value: T, } pub type InFile = InFileWrapper; pub type InMacroFile = InFileWrapper; pub type InRealFile = InFileWrapper; /// `AstId` points to an AST node in any file. /// /// It is stable across reparses, and can be used as salsa key/value. pub type AstId = crate::InFile>; impl AstId { pub fn to_node(&self, db: &dyn ExpandDatabase) -> N { self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)) } pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange { self.to_ptr(db).text_range() } pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile { crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))) } pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr { db.ast_id_map(self.file_id).get(self.value) } } pub type ErasedAstId = crate::InFile; impl ErasedAstId { pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange { self.to_ptr(db).text_range() } pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr { db.ast_id_map(self.file_id).get_erased(self.value) } } impl InFileWrapper { pub fn new(file_id: FileKind, value: T) -> Self { Self { file_id, value } } pub fn map U, U>(self, f: F) -> InFileWrapper { InFileWrapper::new(self.file_id, f(self.value)) } } impl InFileWrapper { pub fn with_value(&self, value: U) -> InFileWrapper { InFileWrapper::new(self.file_id, value) } pub fn as_ref(&self) -> InFileWrapper { self.with_value(&self.value) } } impl InFileWrapper { pub fn cloned(&self) -> InFileWrapper { self.with_value(self.value.clone()) } } impl From> for InFile { fn from(InMacroFile { file_id, value }: InMacroFile) -> Self { InFile { file_id: file_id.into(), value } } } impl From> for InFile { fn from(InRealFile { file_id, value }: InRealFile) -> Self { InFile { file_id: file_id.into(), value } } } // region:transpose impls impl InFileWrapper> { pub fn transpose(self) -> Option> { Some(InFileWrapper::new(self.file_id, self.value?)) } } impl InFileWrapper> { pub fn transpose(self) -> Either, InFileWrapper> { match self.value { Either::Left(l) => Either::Left(InFileWrapper::new(self.file_id, l)), Either::Right(r) => Either::Right(InFileWrapper::new(self.file_id, r)), } } } // endregion:transpose impls trait FileIdToSyntax: Copy { fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode; } impl FileIdToSyntax for FileId { fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode { db.parse(self).syntax_node() } } impl FileIdToSyntax for MacroFileId { fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode { db.parse_macro_expansion(self).value.0.syntax_node() } } impl FileIdToSyntax for HirFileId { fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode { db.parse_or_expand(self) } } #[allow(private_bounds)] impl InFileWrapper { pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode { FileIdToSyntax::file_syntax(self.file_id, db) } } impl InFileWrapper { pub fn syntax(&self) -> InFileWrapper { self.with_value(self.value.syntax()) } } impl InFileWrapper { // unfortunately `syntax` collides with the impl above, because `&_` is fundamental pub fn syntax_ref(&self) -> InFileWrapper { self.with_value(self.value.syntax()) } } // region:specific impls impl InFile<&SyntaxNode> { pub fn parent_ancestors_with_macros( self, db: &dyn db::ExpandDatabase, ) -> impl Iterator> + '_ { let succ = move |node: &InFile| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), None => db .lookup_intern_macro_call(node.file_id.macro_file()?.macro_call_id) .to_node_item(db) .syntax() .cloned() .map(|node| node.parent()) .transpose(), }; std::iter::successors(succ(&self.cloned()), succ) } pub fn ancestors_with_macros( self, db: &dyn db::ExpandDatabase, ) -> impl Iterator> + '_ { let succ = move |node: &InFile| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), None => db .lookup_intern_macro_call(node.file_id.macro_file()?.macro_call_id) .to_node_item(db) .syntax() .cloned() .map(|node| node.parent()) .transpose(), }; std::iter::successors(Some(self.cloned()), succ) } /// Falls back to the macro call range if the node cannot be mapped up fully. /// /// For attributes and derives, this will point back to the attribute only. /// For the entire item use [`InFile::original_file_range_full`]. pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange { self.map(SyntaxNode::text_range).original_node_file_range_rooted(db) } /// Falls back to the macro call range if the node cannot be mapped up fully. pub fn original_file_range_with_macro_call_body( self, db: &dyn db::ExpandDatabase, ) -> FileRange { self.map(SyntaxNode::text_range).original_node_file_range_with_macro_call_body(db) } /// Attempts to map the syntax node back up its macro calls. pub fn original_file_range_opt( self, db: &dyn db::ExpandDatabase, ) -> Option<(FileRange, SyntaxContextId)> { self.map(SyntaxNode::text_range).original_node_file_range_opt(db) } pub fn original_syntax_node_rooted( self, db: &dyn db::ExpandDatabase, ) -> Option> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input let file_id = match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { return Some(InRealFile { file_id, value: self.value.clone() }) } HirFileIdRepr::MacroFile(m) if m.is_attr_macro(db) => m, _ => return None, }; let FileRange { file_id, range } = map_node_range_up_rooted(db, &db.expansion_span_map(file_id), self.value.text_range())?; let kind = self.value.kind(); let value = db .parse(file_id) .syntax_node() .covering_element(range) .ancestors() .take_while(|it| it.text_range() == range) .find(|it| it.kind() == kind)?; Some(InRealFile::new(file_id, value)) } } impl InMacroFile { pub fn upmap_once( self, db: &dyn db::ExpandDatabase, ) -> InFile> { self.file_id.expansion_info(db).map_range_up_once(db, self.value.text_range()) } } impl InFile { /// Falls back to the macro call range if the node cannot be mapped up fully. pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, HirFileIdRepr::MacroFile(mac_file) => { let (range, ctxt) = span_for_offset( db, &db.expansion_span_map(mac_file), self.value.text_range().start(), ); // FIXME: Figure out an API that makes proper use of ctx, this only exists to // keep pre-token map rewrite behaviour. if ctxt.is_root() { return range; } // Fall back to whole macro call. let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); loc.kind.original_call_range(db) } } } /// Attempts to map the syntax node back up its macro calls. pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { Some(FileRange { file_id, range: self.value.text_range() }) } HirFileIdRepr::MacroFile(mac_file) => { let (range, ctxt) = span_for_offset( db, &db.expansion_span_map(mac_file), self.value.text_range().start(), ); // FIXME: Figure out an API that makes proper use of ctx, this only exists to // keep pre-token map rewrite behaviour. if ctxt.is_root() { Some(range) } else { None } } } } } impl InMacroFile { pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> (FileRange, SyntaxContextId) { span_for_offset(db, &db.expansion_span_map(self.file_id), self.value) } } impl InFile { pub fn original_node_file_range( self, db: &dyn db::ExpandDatabase, ) -> (FileRange, SyntaxContextId) { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { (FileRange { file_id, range: self.value }, SyntaxContextId::ROOT) } HirFileIdRepr::MacroFile(mac_file) => { match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) { Some(it) => it, None => { let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); (loc.kind.original_call_range(db), SyntaxContextId::ROOT) } } } } } pub fn original_node_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value }, HirFileIdRepr::MacroFile(mac_file) => { match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) { Some(it) => it, _ => { let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); loc.kind.original_call_range(db) } } } } } pub fn original_node_file_range_with_macro_call_body( self, db: &dyn db::ExpandDatabase, ) -> FileRange { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value }, HirFileIdRepr::MacroFile(mac_file) => { match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) { Some(it) => it, _ => { let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); loc.kind.original_call_range_with_body(db) } } } } } pub fn original_node_file_range_opt( self, db: &dyn db::ExpandDatabase, ) -> Option<(FileRange, SyntaxContextId)> { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { Some((FileRange { file_id, range: self.value }, SyntaxContextId::ROOT)) } HirFileIdRepr::MacroFile(mac_file) => { map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) } } } } impl InFile { pub fn original_ast_node_rooted(self, db: &dyn db::ExpandDatabase) -> Option> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input let file_id = match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { return Some(InRealFile { file_id, value: self.value }) } HirFileIdRepr::MacroFile(m) => m, }; if !file_id.is_attr_macro(db) { return None; } let FileRange { file_id, range } = map_node_range_up_rooted( db, &db.expansion_span_map(file_id), self.value.syntax().text_range(), )?; // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes? let anc = db.parse(file_id).syntax_node().covering_element(range); let value = anc.ancestors().find_map(N::cast)?; Some(InRealFile::new(file_id, value)) } }