//! `ra_hir_expand` deals with macro expansion. //! //! Specifically, it implements a concept of `MacroFile` -- a file whose syntax //! tree originates not from the text of some `FileId`, but from some macro //! expansion. pub mod db; pub mod ast_id_map; pub mod either; pub mod name; pub mod hygiene; pub mod diagnostics; use std::hash::{Hash, Hasher}; use ra_db::{salsa, CrateId, FileId}; use ra_syntax::{ ast::{self, AstNode}, SyntaxNode, TextRange, }; use crate::ast_id_map::FileAstId; /// Input to the analyzer is a set of files, where each file is identified by /// `FileId` and contains source code. However, another source of source code in /// Rust are macros: each macro can be thought of as producing a "temporary /// file". To assign an id to such a file, we use the id of the macro call that /// produced the file. So, a `HirFileId` is either a `FileId` (source code /// written by user), or a `MacroCallId` (source code produced by macro). /// /// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file /// containing the call plus the offset of the macro call in the file. Note that /// this is a recursive definition! However, the size_of of `HirFileId` is /// finite (because everything bottoms out at the real `FileId`) and small /// (`MacroCallId` uses the location interner). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct HirFileId(HirFileIdRepr); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum HirFileIdRepr { FileId(FileId), MacroFile(MacroFile), } impl From for HirFileId { fn from(id: FileId) -> Self { HirFileId(HirFileIdRepr::FileId(id)) } } impl From for HirFileId { fn from(id: MacroFile) -> Self { HirFileId(HirFileIdRepr::MacroFile(id)) } } impl HirFileId { /// For macro-expansion files, returns the file original source file the /// expansion originated from. pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId { match self.0 { HirFileIdRepr::FileId(file_id) => file_id, HirFileIdRepr::MacroFile(macro_file) => { let loc = db.lookup_intern_macro(macro_file.macro_call_id); loc.ast_id.file_id().original_file(db) } } } /// Return expansion information if it is a macro-expansion file pub fn parent_expansion(self, db: &dyn db::AstDatabase) -> Option { match self.0 { HirFileIdRepr::FileId(_) => None, HirFileIdRepr::MacroFile(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); let arg_range = loc.ast_id.to_node(db).token_tree()?.syntax().text_range(); let def_range = loc.def.ast_id.to_node(db).token_tree()?.syntax().text_range(); let macro_def = db.macro_def(loc.def)?; let shift = macro_def.0.shift(); let rev_map = db.parse_macro(macro_file)?.1; let arg_token_map = db.macro_arg(macro_file.macro_call_id)?.1; let def_token_map = macro_def.1; let arg_map = rev_map.map_ranges(&arg_token_map, arg_range, shift); let def_map = rev_map.map_ranges(&def_token_map, def_range, 0); let arg_file = loc.ast_id.file_id; let def_file = loc.def.ast_id.file_id; Some(ExpansionInfo { arg_file, def_file, arg_map, def_map }) } } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroFile { macro_call_id: MacroCallId, macro_file_kind: MacroFileKind, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MacroFileKind { Items, Expr, } /// `MacroCallId` identifies a particular macro invocation, like /// `println!("Hello, {}", world)`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroCallId(salsa::InternId); impl salsa::InternKey for MacroCallId { fn from_intern_id(v: salsa::InternId) -> Self { MacroCallId(v) } fn as_intern_id(&self) -> salsa::InternId { self.0 } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroDefId { pub krate: CrateId, pub ast_id: AstId, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroCallLoc { pub def: MacroDefId, pub ast_id: AstId, } impl MacroCallId { pub fn as_file(self, kind: MacroFileKind) -> HirFileId { let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind }; macro_file.into() } } #[derive(Debug, Clone, PartialEq, Eq)] /// ExpansionInfo mainly describes how to map text range between src and expanded macro pub struct ExpansionInfo { pub(crate) arg_file: HirFileId, pub(crate) def_file: HirFileId, pub(crate) arg_map: Vec<(TextRange, TextRange)>, pub(crate) def_map: Vec<(TextRange, TextRange)>, } impl ExpansionInfo { pub fn find_range(&self, from: TextRange) -> Option<(HirFileId, TextRange)> { for (src, dest) in &self.arg_map { if src.is_subrange(&from) { return Some((self.arg_file, *dest)); } } for (src, dest) in &self.def_map { if src.is_subrange(&from) { return Some((self.def_file, *dest)); } } None } } /// `AstId` points to an AST node in any file. /// /// It is stable across reparses, and can be used as salsa key/value. // FIXME: isn't this just a `Source>` ? #[derive(Debug)] pub 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 fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { AstId { file_id, file_ast_id } } pub fn file_id(&self) -> HirFileId { self.file_id } pub fn to_node(&self, db: &dyn db::AstDatabase) -> N { let root = db.parse_or_expand(self.file_id).unwrap(); db.ast_id_map(self.file_id).get(self.file_ast_id).to_node(&root) } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Source { pub file_id: HirFileId, pub ast: T, } impl Source { pub fn map U, U>(self, f: F) -> Source { Source { file_id: self.file_id, ast: f(self.ast) } } pub fn file_syntax(&self, db: &impl db::AstDatabase) -> SyntaxNode { db.parse_or_expand(self.file_id).expect("source created from invalid file") } }