| //! `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. |
| #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] |
| |
| pub mod attrs; |
| pub mod builtin; |
| pub mod change; |
| pub mod db; |
| pub mod declarative; |
| pub mod eager; |
| pub mod files; |
| pub mod hygiene; |
| pub mod inert_attr_macro; |
| pub mod mod_path; |
| pub mod name; |
| pub mod proc_macro; |
| pub mod span_map; |
| |
| mod cfg_process; |
| mod fixup; |
| |
| use attrs::collect_attrs; |
| use rustc_hash::FxHashMap; |
| use triomphe::Arc; |
| |
| use std::hash::Hash; |
| |
| use base_db::{salsa::InternValueTrivial, CrateId}; |
| use either::Either; |
| use span::{ |
| Edition, EditionedFileId, ErasedFileAstId, FileAstId, HirFileIdRepr, Span, SpanAnchor, |
| SyntaxContextData, SyntaxContextId, |
| }; |
| use syntax::{ |
| ast::{self, AstNode}, |
| SyntaxNode, SyntaxToken, TextRange, TextSize, |
| }; |
| |
| use crate::{ |
| attrs::AttrId, |
| builtin::{ |
| include_input_to_file_id, BuiltinAttrExpander, BuiltinDeriveExpander, |
| BuiltinFnLikeExpander, EagerExpander, |
| }, |
| db::ExpandDatabase, |
| mod_path::ModPath, |
| proc_macro::{CustomProcMacroExpander, ProcMacroKind}, |
| span_map::{ExpansionSpanMap, SpanMap}, |
| }; |
| |
| pub use crate::files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile}; |
| |
| pub use mbe::{DeclarativeMacro, ValueResult}; |
| pub use span::{HirFileId, MacroCallId, MacroFileId}; |
| |
| pub mod tt { |
| pub use span::Span; |
| pub use tt::{token_to_literal, DelimiterKind, IdentIsRaw, LitKind, Spacing}; |
| |
| pub type Delimiter = ::tt::Delimiter<Span>; |
| pub type DelimSpan = ::tt::DelimSpan<Span>; |
| pub type Subtree = ::tt::Subtree<Span>; |
| pub type SubtreeBuilder = ::tt::SubtreeBuilder<Span>; |
| pub type Leaf = ::tt::Leaf<Span>; |
| pub type Literal = ::tt::Literal<Span>; |
| pub type Punct = ::tt::Punct<Span>; |
| pub type Ident = ::tt::Ident<Span>; |
| pub type TokenTree = ::tt::TokenTree<Span>; |
| } |
| |
| #[macro_export] |
| macro_rules! impl_intern_lookup { |
| ($db:ident, $id:ident, $loc:ident, $intern:ident, $lookup:ident) => { |
| impl $crate::Intern for $loc { |
| type Database<'db> = dyn $db + 'db; |
| type ID = $id; |
| fn intern(self, db: &Self::Database<'_>) -> $id { |
| db.$intern(self) |
| } |
| } |
| |
| impl $crate::Lookup for $id { |
| type Database<'db> = dyn $db + 'db; |
| type Data = $loc; |
| fn lookup(&self, db: &Self::Database<'_>) -> $loc { |
| db.$lookup(*self) |
| } |
| } |
| }; |
| } |
| |
| // ideally these would be defined in base-db, but the orphan rule doesn't let us |
| pub trait Intern { |
| type Database<'db>: ?Sized; |
| type ID; |
| fn intern(self, db: &Self::Database<'_>) -> Self::ID; |
| } |
| |
| pub trait Lookup { |
| type Database<'db>: ?Sized; |
| type Data; |
| fn lookup(&self, db: &Self::Database<'_>) -> Self::Data; |
| } |
| |
| impl_intern_lookup!( |
| ExpandDatabase, |
| MacroCallId, |
| MacroCallLoc, |
| intern_macro_call, |
| lookup_intern_macro_call |
| ); |
| |
| impl_intern_lookup!( |
| ExpandDatabase, |
| SyntaxContextId, |
| SyntaxContextData, |
| intern_syntax_context, |
| lookup_intern_syntax_context |
| ); |
| |
| pub type ExpandResult<T> = ValueResult<T, ExpandError>; |
| |
| #[derive(Debug, PartialEq, Eq, Clone, Hash)] |
| pub struct ExpandError { |
| inner: Arc<(ExpandErrorKind, Span)>, |
| } |
| |
| impl ExpandError { |
| pub fn new(span: Span, kind: ExpandErrorKind) -> Self { |
| ExpandError { inner: Arc::new((kind, span)) } |
| } |
| pub fn other(span: Span, msg: impl Into<Box<str>>) -> Self { |
| ExpandError { inner: Arc::new((ExpandErrorKind::Other(msg.into()), span)) } |
| } |
| pub fn kind(&self) -> &ExpandErrorKind { |
| &self.inner.0 |
| } |
| pub fn span(&self) -> Span { |
| self.inner.1 |
| } |
| } |
| |
| #[derive(Debug, PartialEq, Eq, Clone, Hash)] |
| pub enum ExpandErrorKind { |
| /// Attribute macro expansion is disabled. |
| ProcMacroAttrExpansionDisabled, |
| MissingProcMacroExpander(CrateId), |
| /// The macro for this call is disabled. |
| MacroDisabled, |
| /// The macro definition has errors. |
| MacroDefinition, |
| Mbe(mbe::ExpandErrorKind), |
| RecursionOverflow, |
| Other(Box<str>), |
| ProcMacroPanic(Box<str>), |
| } |
| |
| impl ExpandError { |
| pub fn render_to_string(&self, db: &dyn ExpandDatabase) -> (String, bool) { |
| self.inner.0.render_to_string(db) |
| } |
| } |
| |
| impl ExpandErrorKind { |
| pub fn render_to_string(&self, db: &dyn ExpandDatabase) -> (String, bool) { |
| match self { |
| ExpandErrorKind::ProcMacroAttrExpansionDisabled => { |
| ("procedural attribute macro expansion is disabled".to_owned(), false) |
| } |
| ExpandErrorKind::MacroDisabled => { |
| ("proc-macro is explicitly disabled".to_owned(), false) |
| } |
| &ExpandErrorKind::MissingProcMacroExpander(def_crate) => { |
| match db.proc_macros().get_error_for_crate(def_crate) { |
| Some((e, hard_err)) => (e.to_owned(), hard_err), |
| None => ( |
| format!( |
| "internal error: proc-macro map is missing error entry for crate {def_crate:?}" |
| ), |
| true, |
| ), |
| } |
| } |
| ExpandErrorKind::MacroDefinition => { |
| ("macro definition has parse errors".to_owned(), true) |
| } |
| ExpandErrorKind::Mbe(e) => (e.to_string(), true), |
| ExpandErrorKind::RecursionOverflow => { |
| ("overflow expanding the original macro".to_owned(), true) |
| } |
| ExpandErrorKind::Other(e) => ((**e).to_owned(), true), |
| ExpandErrorKind::ProcMacroPanic(e) => (format!("proc-macro panicked: {e}"), true), |
| } |
| } |
| } |
| |
| impl From<mbe::ExpandError> for ExpandError { |
| fn from(mbe: mbe::ExpandError) -> Self { |
| ExpandError { inner: Arc::new((ExpandErrorKind::Mbe(mbe.inner.1.clone()), mbe.inner.0)) } |
| } |
| } |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct MacroCallLoc { |
| pub def: MacroDefId, |
| pub krate: CrateId, |
| pub kind: MacroCallKind, |
| pub ctxt: SyntaxContextId, |
| } |
| impl InternValueTrivial for MacroCallLoc {} |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct MacroDefId { |
| pub krate: CrateId, |
| pub edition: Edition, |
| pub kind: MacroDefKind, |
| pub local_inner: bool, |
| pub allow_internal_unsafe: bool, |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum MacroDefKind { |
| Declarative(AstId<ast::Macro>), |
| BuiltIn(AstId<ast::Macro>, BuiltinFnLikeExpander), |
| BuiltInAttr(AstId<ast::Macro>, BuiltinAttrExpander), |
| BuiltInDerive(AstId<ast::Macro>, BuiltinDeriveExpander), |
| BuiltInEager(AstId<ast::Macro>, EagerExpander), |
| ProcMacro(AstId<ast::Fn>, CustomProcMacroExpander, ProcMacroKind), |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct EagerCallInfo { |
| /// The expanded argument of the eager macro. |
| arg: Arc<tt::Subtree>, |
| /// Call id of the eager macro's input file (this is the macro file for its fully expanded input). |
| arg_id: MacroCallId, |
| error: Option<ExpandError>, |
| /// The call site span of the eager macro |
| span: Span, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum MacroCallKind { |
| FnLike { |
| ast_id: AstId<ast::MacroCall>, |
| expand_to: ExpandTo, |
| /// Some if this is a macro call for an eager macro. Note that this is `None` |
| /// for the eager input macro file. |
| // FIXME: This is being interned, subtrees can vary quickly differing just slightly causing |
| // leakage problems here |
| eager: Option<Arc<EagerCallInfo>>, |
| }, |
| Derive { |
| ast_id: AstId<ast::Adt>, |
| /// Syntactical index of the invoking `#[derive]` attribute. |
| /// |
| /// Outer attributes are counted first, then inner attributes. This does not support |
| /// out-of-line modules, which may have attributes spread across 2 files! |
| derive_attr_index: AttrId, |
| /// Index of the derive macro in the derive attribute |
| derive_index: u32, |
| /// The "parent" macro call. |
| /// We will resolve the same token tree for all derive macros in the same derive attribute. |
| derive_macro_id: MacroCallId, |
| }, |
| Attr { |
| ast_id: AstId<ast::Item>, |
| // FIXME: This shouldn't be here, we can derive this from `invoc_attr_index` |
| // but we need to fix the `cfg_attr` handling first. |
| attr_args: Option<Arc<tt::Subtree>>, |
| /// Syntactical index of the invoking `#[attribute]`. |
| /// |
| /// Outer attributes are counted first, then inner attributes. This does not support |
| /// out-of-line modules, which may have attributes spread across 2 files! |
| invoc_attr_index: AttrId, |
| }, |
| } |
| |
| pub trait HirFileIdExt { |
| fn edition(self, db: &dyn ExpandDatabase) -> Edition; |
| /// Returns the original file of this macro call hierarchy. |
| fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId; |
| |
| /// Returns the original file of this macro call hierarchy while going into the included file if |
| /// one of the calls comes from an `include!``. |
| fn original_file_respecting_includes(self, db: &dyn ExpandDatabase) -> EditionedFileId; |
| |
| /// If this is a macro call, returns the syntax node of the very first macro call this file resides in. |
| fn original_call_node(self, db: &dyn ExpandDatabase) -> Option<InRealFile<SyntaxNode>>; |
| |
| fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option<InFile<ast::Attr>>; |
| } |
| |
| impl HirFileIdExt for HirFileId { |
| fn edition(self, db: &dyn ExpandDatabase) -> Edition { |
| match self.repr() { |
| HirFileIdRepr::FileId(file_id) => file_id.edition(), |
| HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition, |
| } |
| } |
| fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId { |
| let mut file_id = self; |
| loop { |
| match file_id.repr() { |
| HirFileIdRepr::FileId(id) => break id, |
| HirFileIdRepr::MacroFile(MacroFileId { macro_call_id }) => { |
| file_id = macro_call_id.lookup(db).kind.file_id(); |
| } |
| } |
| } |
| } |
| |
| fn original_file_respecting_includes(mut self, db: &dyn ExpandDatabase) -> EditionedFileId { |
| loop { |
| match self.repr() { |
| HirFileIdRepr::FileId(id) => break id, |
| HirFileIdRepr::MacroFile(file) => { |
| let loc = db.lookup_intern_macro_call(file.macro_call_id); |
| if loc.def.is_include() { |
| if let MacroCallKind::FnLike { eager: Some(eager), .. } = &loc.kind { |
| if let Ok(it) = |
| include_input_to_file_id(db, file.macro_call_id, &eager.arg) |
| { |
| break it; |
| } |
| } |
| } |
| self = loc.kind.file_id(); |
| } |
| } |
| } |
| } |
| |
| fn original_call_node(self, db: &dyn ExpandDatabase) -> Option<InRealFile<SyntaxNode>> { |
| let mut call = db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).to_node(db); |
| loop { |
| match call.file_id.repr() { |
| HirFileIdRepr::FileId(file_id) => { |
| break Some(InRealFile { file_id, value: call.value }) |
| } |
| HirFileIdRepr::MacroFile(MacroFileId { macro_call_id }) => { |
| call = db.lookup_intern_macro_call(macro_call_id).to_node(db); |
| } |
| } |
| } |
| } |
| |
| fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option<InFile<ast::Attr>> { |
| let macro_file = self.macro_file()?; |
| let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); |
| let attr = match loc.def.kind { |
| MacroDefKind::BuiltInDerive(..) => loc.to_node(db), |
| _ => return None, |
| }; |
| Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) |
| } |
| } |
| |
| pub trait MacroFileIdExt { |
| fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool; |
| fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool; |
| fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option<MacroCallId>; |
| fn expansion_level(self, db: &dyn ExpandDatabase) -> u32; |
| /// If this is a macro call, returns the syntax node of the call. |
| fn call_node(self, db: &dyn ExpandDatabase) -> InFile<SyntaxNode>; |
| fn parent(self, db: &dyn ExpandDatabase) -> HirFileId; |
| |
| fn expansion_info(self, db: &dyn ExpandDatabase) -> ExpansionInfo; |
| |
| fn is_builtin_derive(&self, db: &dyn ExpandDatabase) -> bool; |
| fn is_custom_derive(&self, db: &dyn ExpandDatabase) -> bool; |
| |
| /// Return whether this file is an include macro |
| fn is_include_macro(&self, db: &dyn ExpandDatabase) -> bool; |
| |
| fn is_eager(&self, db: &dyn ExpandDatabase) -> bool; |
| /// Return whether this file is an attr macro |
| fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool; |
| |
| /// Return whether this file is the pseudo expansion of the derive attribute. |
| /// See [`crate::builtin_attr_macro::derive_attr_expand`]. |
| fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool; |
| } |
| |
| impl MacroFileIdExt for MacroFileId { |
| fn call_node(self, db: &dyn ExpandDatabase) -> InFile<SyntaxNode> { |
| db.lookup_intern_macro_call(self.macro_call_id).to_node(db) |
| } |
| fn expansion_level(self, db: &dyn ExpandDatabase) -> u32 { |
| let mut level = 0; |
| let mut macro_file = self; |
| loop { |
| let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); |
| |
| level += 1; |
| macro_file = match loc.kind.file_id().repr() { |
| HirFileIdRepr::FileId(_) => break level, |
| HirFileIdRepr::MacroFile(it) => it, |
| }; |
| } |
| } |
| fn parent(self, db: &dyn ExpandDatabase) -> HirFileId { |
| self.macro_call_id.lookup(db).kind.file_id() |
| } |
| |
| /// Return expansion information if it is a macro-expansion file |
| fn expansion_info(self, db: &dyn ExpandDatabase) -> ExpansionInfo { |
| ExpansionInfo::new(db, self) |
| } |
| |
| fn is_custom_derive(&self, db: &dyn ExpandDatabase) -> bool { |
| matches!( |
| db.lookup_intern_macro_call(self.macro_call_id).def.kind, |
| MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) |
| ) |
| } |
| |
| fn is_builtin_derive(&self, db: &dyn ExpandDatabase) -> bool { |
| matches!( |
| db.lookup_intern_macro_call(self.macro_call_id).def.kind, |
| MacroDefKind::BuiltInDerive(..) |
| ) |
| } |
| |
| fn is_include_macro(&self, db: &dyn ExpandDatabase) -> bool { |
| db.lookup_intern_macro_call(self.macro_call_id).def.is_include() |
| } |
| |
| fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool { |
| db.lookup_intern_macro_call(self.macro_call_id).def.is_include_like() |
| } |
| |
| fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool { |
| db.lookup_intern_macro_call(self.macro_call_id).def.is_env_or_option_env() |
| } |
| |
| fn is_eager(&self, db: &dyn ExpandDatabase) -> bool { |
| let loc = db.lookup_intern_macro_call(self.macro_call_id); |
| matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) |
| } |
| |
| fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option<MacroCallId> { |
| let loc = db.lookup_intern_macro_call(self.macro_call_id); |
| match &loc.kind { |
| MacroCallKind::FnLike { eager, .. } => eager.as_ref().map(|it| it.arg_id), |
| _ => None, |
| } |
| } |
| |
| fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool { |
| matches!( |
| db.lookup_intern_macro_call(self.macro_call_id).def.kind, |
| MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) |
| ) |
| } |
| |
| fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool { |
| let loc = db.lookup_intern_macro_call(self.macro_call_id); |
| loc.def.is_attribute_derive() |
| } |
| } |
| |
| impl MacroDefId { |
| pub fn make_call( |
| self, |
| db: &dyn ExpandDatabase, |
| krate: CrateId, |
| kind: MacroCallKind, |
| ctxt: SyntaxContextId, |
| ) -> MacroCallId { |
| MacroCallLoc { def: self, krate, kind, ctxt }.intern(db) |
| } |
| |
| pub fn definition_range(&self, db: &dyn ExpandDatabase) -> InFile<TextRange> { |
| match self.kind { |
| MacroDefKind::Declarative(id) |
| | MacroDefKind::BuiltIn(id, _) |
| | MacroDefKind::BuiltInAttr(id, _) |
| | MacroDefKind::BuiltInDerive(id, _) |
| | MacroDefKind::BuiltInEager(id, _) => { |
| id.with_value(db.ast_id_map(id.file_id).get(id.value).text_range()) |
| } |
| MacroDefKind::ProcMacro(id, _, _) => { |
| id.with_value(db.ast_id_map(id.file_id).get(id.value).text_range()) |
| } |
| } |
| } |
| |
| pub fn ast_id(&self) -> Either<AstId<ast::Macro>, AstId<ast::Fn>> { |
| match self.kind { |
| MacroDefKind::ProcMacro(id, ..) => Either::Right(id), |
| MacroDefKind::Declarative(id) |
| | MacroDefKind::BuiltIn(id, _) |
| | MacroDefKind::BuiltInAttr(id, _) |
| | MacroDefKind::BuiltInDerive(id, _) |
| | MacroDefKind::BuiltInEager(id, _) => Either::Left(id), |
| } |
| } |
| |
| pub fn is_proc_macro(&self) -> bool { |
| matches!(self.kind, MacroDefKind::ProcMacro(..)) |
| } |
| |
| pub fn is_attribute(&self) -> bool { |
| matches!( |
| self.kind, |
| MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) |
| ) |
| } |
| |
| pub fn is_derive(&self) -> bool { |
| matches!( |
| self.kind, |
| MacroDefKind::BuiltInDerive(..) |
| | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) |
| ) |
| } |
| |
| pub fn is_fn_like(&self) -> bool { |
| matches!( |
| self.kind, |
| MacroDefKind::BuiltIn(..) |
| | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Bang) |
| | MacroDefKind::BuiltInEager(..) |
| | MacroDefKind::Declarative(..) |
| ) |
| } |
| |
| pub fn is_attribute_derive(&self) -> bool { |
| matches!(self.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_derive()) |
| } |
| |
| pub fn is_include(&self) -> bool { |
| matches!(self.kind, MacroDefKind::BuiltInEager(_, expander) if expander.is_include()) |
| } |
| |
| pub fn is_include_like(&self) -> bool { |
| matches!(self.kind, MacroDefKind::BuiltInEager(_, expander) if expander.is_include_like()) |
| } |
| |
| pub fn is_env_or_option_env(&self) -> bool { |
| matches!(self.kind, MacroDefKind::BuiltInEager(_, expander) if expander.is_env_or_option_env()) |
| } |
| } |
| |
| impl MacroCallLoc { |
| pub fn to_node(&self, db: &dyn ExpandDatabase) -> InFile<SyntaxNode> { |
| match self.kind { |
| MacroCallKind::FnLike { ast_id, .. } => { |
| ast_id.with_value(ast_id.to_node(db).syntax().clone()) |
| } |
| MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { |
| // FIXME: handle `cfg_attr` |
| ast_id.with_value(ast_id.to_node(db)).map(|it| { |
| collect_attrs(&it) |
| .nth(derive_attr_index.ast_index()) |
| .and_then(|it| match it.1 { |
| Either::Left(attr) => Some(attr.syntax().clone()), |
| Either::Right(_) => None, |
| }) |
| .unwrap_or_else(|| it.syntax().clone()) |
| }) |
| } |
| MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { |
| if self.def.is_attribute_derive() { |
| // FIXME: handle `cfg_attr` |
| ast_id.with_value(ast_id.to_node(db)).map(|it| { |
| collect_attrs(&it) |
| .nth(invoc_attr_index.ast_index()) |
| .and_then(|it| match it.1 { |
| Either::Left(attr) => Some(attr.syntax().clone()), |
| Either::Right(_) => None, |
| }) |
| .unwrap_or_else(|| it.syntax().clone()) |
| }) |
| } else { |
| ast_id.with_value(ast_id.to_node(db).syntax().clone()) |
| } |
| } |
| } |
| } |
| |
| pub fn to_node_item(&self, db: &dyn ExpandDatabase) -> InFile<ast::Item> { |
| match self.kind { |
| MacroCallKind::FnLike { ast_id, .. } => { |
| InFile::new(ast_id.file_id, ast_id.map(FileAstId::upcast).to_node(db)) |
| } |
| MacroCallKind::Derive { ast_id, .. } => { |
| InFile::new(ast_id.file_id, ast_id.map(FileAstId::upcast).to_node(db)) |
| } |
| MacroCallKind::Attr { ast_id, .. } => InFile::new(ast_id.file_id, ast_id.to_node(db)), |
| } |
| } |
| |
| fn expand_to(&self) -> ExpandTo { |
| match self.kind { |
| MacroCallKind::FnLike { expand_to, .. } => expand_to, |
| MacroCallKind::Derive { .. } => ExpandTo::Items, |
| MacroCallKind::Attr { .. } if self.def.is_attribute_derive() => ExpandTo::Items, |
| MacroCallKind::Attr { .. } => { |
| // FIXME(stmt_expr_attributes) |
| ExpandTo::Items |
| } |
| } |
| } |
| |
| pub fn include_file_id( |
| &self, |
| db: &dyn ExpandDatabase, |
| macro_call_id: MacroCallId, |
| ) -> Option<EditionedFileId> { |
| if self.def.is_include() { |
| if let MacroCallKind::FnLike { eager: Some(eager), .. } = &self.kind { |
| if let Ok(it) = include_input_to_file_id(db, macro_call_id, &eager.arg) { |
| return Some(it); |
| } |
| } |
| } |
| |
| None |
| } |
| } |
| |
| impl MacroCallKind { |
| fn descr(&self) -> &'static str { |
| match self { |
| MacroCallKind::FnLike { .. } => "macro call", |
| MacroCallKind::Derive { .. } => "derive macro", |
| MacroCallKind::Attr { .. } => "attribute macro", |
| } |
| } |
| |
| /// Returns the file containing the macro invocation. |
| pub fn file_id(&self) -> HirFileId { |
| match *self { |
| MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. } |
| | MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. } |
| | MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id, |
| } |
| } |
| |
| pub fn erased_ast_id(&self) -> ErasedFileAstId { |
| match *self { |
| MacroCallKind::FnLike { ast_id: InFile { value, .. }, .. } => value.erase(), |
| MacroCallKind::Derive { ast_id: InFile { value, .. }, .. } => value.erase(), |
| MacroCallKind::Attr { ast_id: InFile { value, .. }, .. } => value.erase(), |
| } |
| } |
| |
| /// Returns the original file range that best describes the location of this macro call. |
| /// |
| /// Unlike `MacroCallKind::original_call_range`, this also spans the item of attributes and derives. |
| pub fn original_call_range_with_body(self, db: &dyn ExpandDatabase) -> FileRange { |
| let mut kind = self; |
| let file_id = loop { |
| match kind.file_id().repr() { |
| HirFileIdRepr::MacroFile(file) => { |
| kind = db.lookup_intern_macro_call(file.macro_call_id).kind; |
| } |
| HirFileIdRepr::FileId(file_id) => break file_id, |
| } |
| }; |
| |
| let range = match kind { |
| MacroCallKind::FnLike { ast_id, .. } => ast_id.to_ptr(db).text_range(), |
| MacroCallKind::Derive { ast_id, .. } => ast_id.to_ptr(db).text_range(), |
| MacroCallKind::Attr { ast_id, .. } => ast_id.to_ptr(db).text_range(), |
| }; |
| |
| FileRange { range, file_id } |
| } |
| |
| /// Returns the original file range that best describes the location of this macro call. |
| /// |
| /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros |
| /// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives |
| /// get only the specific derive that is being referred to. |
| pub fn original_call_range(self, db: &dyn ExpandDatabase) -> FileRange { |
| let mut kind = self; |
| let file_id = loop { |
| match kind.file_id().repr() { |
| HirFileIdRepr::MacroFile(file) => { |
| kind = db.lookup_intern_macro_call(file.macro_call_id).kind; |
| } |
| HirFileIdRepr::FileId(file_id) => break file_id, |
| } |
| }; |
| |
| let range = match kind { |
| MacroCallKind::FnLike { ast_id, .. } => ast_id.to_ptr(db).text_range(), |
| MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { |
| // FIXME: should be the range of the macro name, not the whole derive |
| // FIXME: handle `cfg_attr` |
| collect_attrs(&ast_id.to_node(db)) |
| .nth(derive_attr_index.ast_index()) |
| .expect("missing derive") |
| .1 |
| .expect_left("derive is a doc comment?") |
| .syntax() |
| .text_range() |
| } |
| // FIXME: handle `cfg_attr` |
| MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { |
| collect_attrs(&ast_id.to_node(db)) |
| .nth(invoc_attr_index.ast_index()) |
| .expect("missing attribute") |
| .1 |
| .expect_left("attribute macro is a doc comment?") |
| .syntax() |
| .text_range() |
| } |
| }; |
| |
| FileRange { range, file_id } |
| } |
| |
| fn arg(&self, db: &dyn ExpandDatabase) -> InFile<Option<SyntaxNode>> { |
| match self { |
| MacroCallKind::FnLike { ast_id, .. } => { |
| ast_id.to_in_file_node(db).map(|it| Some(it.token_tree()?.syntax().clone())) |
| } |
| MacroCallKind::Derive { ast_id, .. } => { |
| ast_id.to_in_file_node(db).syntax().cloned().map(Some) |
| } |
| MacroCallKind::Attr { ast_id, .. } => { |
| ast_id.to_in_file_node(db).syntax().cloned().map(Some) |
| } |
| } |
| } |
| } |
| |
| /// ExpansionInfo mainly describes how to map text range between src and expanded macro |
| // FIXME: can be expensive to create, we should check the use sites and maybe replace them with |
| // simpler function calls if the map is only used once |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub struct ExpansionInfo { |
| expanded: InMacroFile<SyntaxNode>, |
| /// The argument TokenTree or item for attributes |
| arg: InFile<Option<SyntaxNode>>, |
| exp_map: Arc<ExpansionSpanMap>, |
| arg_map: SpanMap, |
| loc: MacroCallLoc, |
| } |
| |
| impl ExpansionInfo { |
| pub fn expanded(&self) -> InMacroFile<SyntaxNode> { |
| self.expanded.clone() |
| } |
| |
| pub fn arg(&self) -> InFile<Option<&SyntaxNode>> { |
| self.arg.as_ref().map(|it| it.as_ref()) |
| } |
| |
| pub fn call_file(&self) -> HirFileId { |
| self.arg.file_id |
| } |
| |
| pub fn is_attr(&self) -> bool { |
| matches!( |
| self.loc.def.kind, |
| MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) |
| ) |
| } |
| |
| /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. |
| /// |
| /// Note this does a linear search through the entire backing vector of the spanmap. |
| pub fn map_range_down_exact( |
| &self, |
| span: Span, |
| ) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> { |
| let tokens = self |
| .exp_map |
| .ranges_with_span_exact(span) |
| .flat_map(move |range| self.expanded.value.covering_element(range).into_token()); |
| |
| Some(InMacroFile::new(self.expanded.file_id, tokens)) |
| } |
| |
| /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. |
| /// Unlike [`map_range_down_exact`], this will consider spans that contain the given span. |
| /// |
| /// Note this does a linear search through the entire backing vector of the spanmap. |
| pub fn map_range_down( |
| &self, |
| span: Span, |
| ) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> { |
| let tokens = self |
| .exp_map |
| .ranges_with_span(span) |
| .flat_map(move |range| self.expanded.value.covering_element(range).into_token()); |
| |
| Some(InMacroFile::new(self.expanded.file_id, tokens)) |
| } |
| |
| /// Looks up the span at the given offset. |
| pub fn span_for_offset( |
| &self, |
| db: &dyn ExpandDatabase, |
| offset: TextSize, |
| ) -> (FileRange, SyntaxContextId) { |
| debug_assert!(self.expanded.value.text_range().contains(offset)); |
| span_for_offset(db, &self.exp_map, offset) |
| } |
| |
| /// Maps up the text range out of the expansion hierarchy back into the original file its from. |
| pub fn map_node_range_up( |
| &self, |
| db: &dyn ExpandDatabase, |
| range: TextRange, |
| ) -> Option<(FileRange, SyntaxContextId)> { |
| debug_assert!(self.expanded.value.text_range().contains_range(range)); |
| map_node_range_up(db, &self.exp_map, range) |
| } |
| |
| /// Maps up the text range out of the expansion into is macro call. |
| pub fn map_range_up_once( |
| &self, |
| db: &dyn ExpandDatabase, |
| token: TextRange, |
| ) -> InFile<smallvec::SmallVec<[TextRange; 1]>> { |
| debug_assert!(self.expanded.value.text_range().contains_range(token)); |
| let span = self.exp_map.span_at(token.start()); |
| match &self.arg_map { |
| SpanMap::RealSpanMap(_) => { |
| let file_id = span.anchor.file_id.into(); |
| let anchor_offset = |
| db.ast_id_map(file_id).get_erased(span.anchor.ast_id).text_range().start(); |
| InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] } |
| } |
| SpanMap::ExpansionSpanMap(arg_map) => { |
| let arg_range = self |
| .arg |
| .value |
| .as_ref() |
| .map_or_else(|| TextRange::empty(TextSize::from(0)), |it| it.text_range()); |
| InFile::new( |
| self.arg.file_id, |
| arg_map |
| .ranges_with_span_exact(span) |
| .filter(|range| range.intersect(arg_range).is_some()) |
| .collect(), |
| ) |
| } |
| } |
| } |
| |
| pub fn new(db: &dyn ExpandDatabase, macro_file: MacroFileId) -> ExpansionInfo { |
| let _p = tracing::info_span!("ExpansionInfo::new").entered(); |
| let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); |
| |
| let arg_tt = loc.kind.arg(db); |
| let arg_map = db.span_map(arg_tt.file_id); |
| |
| let (parse, exp_map) = db.parse_macro_expansion(macro_file).value; |
| let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() }; |
| |
| ExpansionInfo { expanded, loc, arg: arg_tt, exp_map, arg_map } |
| } |
| } |
| |
| /// Maps up the text range out of the expansion hierarchy back into the original file its from only |
| /// considering the root spans contained. |
| /// Unlike [`map_node_range_up`], this will not return `None` if any anchors or syntax contexts differ. |
| pub fn map_node_range_up_rooted( |
| db: &dyn ExpandDatabase, |
| exp_map: &ExpansionSpanMap, |
| range: TextRange, |
| ) -> Option<FileRange> { |
| let mut spans = exp_map.spans_for_range(range).filter(|span| span.ctx.is_root()); |
| let Span { range, anchor, ctx: _ } = spans.next()?; |
| let mut start = range.start(); |
| let mut end = range.end(); |
| |
| for span in spans { |
| if span.anchor != anchor { |
| return None; |
| } |
| start = start.min(span.range.start()); |
| end = end.max(span.range.end()); |
| } |
| let anchor_offset = |
| db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start(); |
| Some(FileRange { file_id: anchor.file_id, range: TextRange::new(start, end) + anchor_offset }) |
| } |
| |
| /// Maps up the text range out of the expansion hierarchy back into the original file its from. |
| /// |
| /// this will return `None` if any anchors or syntax contexts differ. |
| pub fn map_node_range_up( |
| db: &dyn ExpandDatabase, |
| exp_map: &ExpansionSpanMap, |
| range: TextRange, |
| ) -> Option<(FileRange, SyntaxContextId)> { |
| let mut spans = exp_map.spans_for_range(range); |
| let Span { range, anchor, ctx } = spans.next()?; |
| let mut start = range.start(); |
| let mut end = range.end(); |
| |
| for span in spans { |
| if span.anchor != anchor || span.ctx != ctx { |
| return None; |
| } |
| start = start.min(span.range.start()); |
| end = end.max(span.range.end()); |
| } |
| let anchor_offset = |
| db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start(); |
| Some(( |
| FileRange { file_id: anchor.file_id, range: TextRange::new(start, end) + anchor_offset }, |
| ctx, |
| )) |
| } |
| |
| /// Maps up the text range out of the expansion hierarchy back into the original file its from. |
| /// This version will aggregate the ranges of all spans with the same anchor and syntax context. |
| pub fn map_node_range_up_aggregated( |
| db: &dyn ExpandDatabase, |
| exp_map: &ExpansionSpanMap, |
| range: TextRange, |
| ) -> FxHashMap<(SpanAnchor, SyntaxContextId), TextRange> { |
| let mut map = FxHashMap::default(); |
| for span in exp_map.spans_for_range(range) { |
| let range = map.entry((span.anchor, span.ctx)).or_insert_with(|| span.range); |
| *range = TextRange::new( |
| range.start().min(span.range.start()), |
| range.end().max(span.range.end()), |
| ); |
| } |
| for ((anchor, _), range) in &mut map { |
| let anchor_offset = |
| db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start(); |
| *range += anchor_offset; |
| } |
| map |
| } |
| |
| /// Looks up the span at the given offset. |
| pub fn span_for_offset( |
| db: &dyn ExpandDatabase, |
| exp_map: &ExpansionSpanMap, |
| offset: TextSize, |
| ) -> (FileRange, SyntaxContextId) { |
| let span = exp_map.span_at(offset); |
| let anchor_offset = db |
| .ast_id_map(span.anchor.file_id.into()) |
| .get_erased(span.anchor.ast_id) |
| .text_range() |
| .start(); |
| (FileRange { file_id: span.anchor.file_id, range: span.range + anchor_offset }, span.ctx) |
| } |
| |
| /// In Rust, macros expand token trees to token trees. When we want to turn a |
| /// token tree into an AST node, we need to figure out what kind of AST node we |
| /// want: something like `foo` can be a type, an expression, or a pattern. |
| /// |
| /// Naively, one would think that "what this expands to" is a property of a |
| /// particular macro: macro `m1` returns an item, while macro `m2` returns an |
| /// expression, etc. That's not the case -- macros are polymorphic in the |
| /// result, and can expand to any type of the AST node. |
| /// |
| /// What defines the actual AST node is the syntactic context of the macro |
| /// invocation. As a contrived example, in `let T![*] = T![*];` the first `T` |
| /// expands to a pattern, while the second one expands to an expression. |
| /// |
| /// `ExpandTo` captures this bit of information about a particular macro call |
| /// site. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum ExpandTo { |
| Statements, |
| Items, |
| Pattern, |
| Type, |
| Expr, |
| } |
| |
| impl ExpandTo { |
| pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo { |
| use syntax::SyntaxKind::*; |
| |
| let syn = call.syntax(); |
| |
| let parent = match syn.parent() { |
| Some(it) => it, |
| None => return ExpandTo::Statements, |
| }; |
| |
| // FIXME: macros in statement position are treated as expression statements, they should |
| // probably be their own statement kind. The *grand*parent indicates what's valid. |
| if parent.kind() == MACRO_EXPR |
| && parent |
| .parent() |
| .map_or(false, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS)) |
| { |
| return ExpandTo::Statements; |
| } |
| |
| match parent.kind() { |
| MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => ExpandTo::Items, |
| MACRO_STMTS | EXPR_STMT | STMT_LIST => ExpandTo::Statements, |
| MACRO_PAT => ExpandTo::Pattern, |
| MACRO_TYPE => ExpandTo::Type, |
| |
| ARG_LIST | ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BREAK_EXPR | CALL_EXPR | CAST_EXPR |
| | CLOSURE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR | INDEX_EXPR | LET_EXPR |
| | MATCH_ARM | MATCH_EXPR | MATCH_GUARD | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR |
| | PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR_FIELD | REF_EXPR | RETURN_EXPR | TRY_EXPR |
| | TUPLE_EXPR | WHILE_EXPR | MACRO_EXPR => ExpandTo::Expr, |
| _ => { |
| // Unknown , Just guess it is `Items` |
| ExpandTo::Items |
| } |
| } |
| } |
| } |
| |
| intern::impl_internable!(ModPath, attrs::AttrInput); |