| //! `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. |
| |
| #![warn(rust_2018_idioms, unused_lifetimes)] |
| |
| pub mod attrs; |
| pub mod builtin_attr_macro; |
| pub mod builtin_derive_macro; |
| pub mod builtin_fn_macro; |
| pub mod change; |
| pub mod db; |
| pub mod declarative; |
| pub mod eager; |
| pub mod files; |
| pub mod hygiene; |
| pub mod mod_path; |
| pub mod name; |
| pub mod proc_macro; |
| pub mod quote; |
| pub mod span_map; |
| |
| mod fixup; |
| |
| use attrs::collect_attrs; |
| use triomphe::Arc; |
| |
| use std::{fmt, hash::Hash}; |
| |
| use base_db::{salsa::impl_intern_value_trivial, CrateId, Edition, FileId}; |
| use either::Either; |
| use span::{ErasedFileAstId, FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId}; |
| use syntax::{ |
| ast::{self, AstNode}, |
| SyntaxNode, SyntaxToken, TextRange, TextSize, |
| }; |
| |
| use crate::{ |
| attrs::AttrId, |
| builtin_attr_macro::BuiltinAttrExpander, |
| builtin_derive_macro::BuiltinDeriveExpander, |
| builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, |
| db::{ExpandDatabase, TokenExpander}, |
| mod_path::ModPath, |
| proc_macro::{CustomProcMacroExpander, ProcMacroKind}, |
| span_map::{ExpansionSpanMap, SpanMap}, |
| }; |
| |
| pub use crate::files::{AstId, ErasedAstId, InFile, InMacroFile, InRealFile}; |
| |
| pub use mbe::ValueResult; |
| pub use span::{HirFileId, MacroCallId, MacroFileId}; |
| |
| pub type DeclarativeMacro = ::mbe::DeclarativeMacro<tt::Span>; |
| |
| pub mod tt { |
| pub use span::Span; |
| pub use tt::{DelimiterKind, 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 enum ExpandError { |
| UnresolvedProcMacro(CrateId), |
| /// The macro expansion is disabled. |
| MacroDisabled, |
| MacroDefinition, |
| Mbe(mbe::ExpandError), |
| RecursionOverflow, |
| Other(Box<Box<str>>), |
| ProcMacroPanic(Box<Box<str>>), |
| } |
| |
| impl ExpandError { |
| pub fn other(msg: impl Into<Box<str>>) -> Self { |
| ExpandError::Other(Box::new(msg.into())) |
| } |
| } |
| |
| impl From<mbe::ExpandError> for ExpandError { |
| fn from(mbe: mbe::ExpandError) -> Self { |
| Self::Mbe(mbe) |
| } |
| } |
| |
| impl fmt::Display for ExpandError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), |
| ExpandError::Mbe(it) => it.fmt(f), |
| ExpandError::RecursionOverflow => f.write_str("overflow expanding the original macro"), |
| ExpandError::ProcMacroPanic(it) => { |
| f.write_str("proc-macro panicked: ")?; |
| f.write_str(it) |
| } |
| ExpandError::Other(it) => f.write_str(it), |
| ExpandError::MacroDisabled => f.write_str("macro disabled"), |
| ExpandError::MacroDefinition => f.write_str("macro definition has parse errors"), |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct MacroCallLoc { |
| pub def: MacroDefId, |
| pub krate: CrateId, |
| /// 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 differ just slightly causing |
| // leakage problems here |
| eager: Option<Arc<EagerCallInfo>>, |
| pub kind: MacroCallKind, |
| pub call_site: Span, |
| } |
| impl_intern_value_trivial!(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, |
| pub span: Span, |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum MacroDefKind { |
| Declarative(AstId<ast::Macro>), |
| BuiltIn(BuiltinFnLikeExpander, AstId<ast::Macro>), |
| BuiltInAttr(BuiltinAttrExpander, AstId<ast::Macro>), |
| BuiltInDerive(BuiltinDeriveExpander, AstId<ast::Macro>), |
| BuiltInEager(EagerExpander, AstId<ast::Macro>), |
| ProcMacro(CustomProcMacroExpander, ProcMacroKind, AstId<ast::Fn>), |
| } |
| |
| #[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>, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum MacroCallKind { |
| FnLike { |
| ast_id: AstId<ast::MacroCall>, |
| expand_to: ExpandTo, |
| }, |
| 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, |
| }, |
| 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 { |
| /// Returns the original file of this macro call hierarchy. |
| fn original_file(self, db: &dyn ExpandDatabase) -> FileId; |
| |
| /// 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) -> FileId; |
| |
| /// 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>>; |
| |
| /// Return expansion information if it is a macro-expansion file |
| fn expansion_info(self, db: &dyn ExpandDatabase) -> Option<ExpansionInfo>; |
| |
| fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option<InFile<ast::Attr>>; |
| } |
| |
| impl HirFileIdExt for HirFileId { |
| fn original_file(self, db: &dyn ExpandDatabase) -> FileId { |
| 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) -> FileId { |
| 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 Some(eager) = &loc.eager { |
| if let Ok(it) = builtin_fn_macro::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); |
| } |
| } |
| } |
| } |
| |
| /// Return expansion information if it is a macro-expansion file |
| fn expansion_info(self, db: &dyn ExpandDatabase) -> Option<ExpansionInfo> { |
| Some(ExpansionInfo::new(db, self.macro_file()?)) |
| } |
| |
| 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 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_eager(&self, db: &dyn ExpandDatabase) -> bool { |
| let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); |
| matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) |
| } |
| |
| fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool { |
| let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); |
| matches!(loc.kind, MacroCallKind::Attr { .. }) |
| } |
| |
| fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool { |
| let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); |
| loc.def.is_attribute_derive() |
| } |
| } |
| |
| impl MacroDefId { |
| pub fn as_lazy_macro( |
| self, |
| db: &dyn ExpandDatabase, |
| krate: CrateId, |
| kind: MacroCallKind, |
| call_site: Span, |
| ) -> MacroCallId { |
| MacroCallLoc { def: self, krate, eager: None, kind, call_site }.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::FuncLike, _) |
| | 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()) |
| } |
| } |
| |
| 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()) |
| } |
| } |
| } |
| } |
| |
| 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<FileId> { |
| if self.def.is_include() { |
| if let Some(eager) = &self.eager { |
| if let Ok(it) = |
| builtin_fn_macro::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(Debug, Clone, PartialEq, Eq)] |
| pub struct ExpansionInfo { |
| pub expanded: InMacroFile<SyntaxNode>, |
| /// The argument TokenTree or item for attributes |
| arg: InFile<Option<SyntaxNode>>, |
| /// The `macro_rules!` or attribute input. |
| attr_input_or_mac_def: Option<InFile<ast::TokenTree>>, |
| |
| macro_def: TokenExpander, |
| macro_arg: Arc<tt::Subtree>, |
| pub exp_map: Arc<ExpansionSpanMap>, |
| arg_map: SpanMap, |
| } |
| |
| impl ExpansionInfo { |
| pub fn expanded(&self) -> InMacroFile<SyntaxNode> { |
| self.expanded.clone() |
| } |
| |
| pub fn call_node(&self) -> Option<InFile<SyntaxNode>> { |
| Some(self.arg.with_value(self.arg.value.as_ref()?.parent()?)) |
| } |
| |
| pub fn call_file(&self) -> HirFileId { |
| self.arg.file_id |
| } |
| |
| /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. |
| 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(span) |
| .filter(|range| range.intersect(arg_range).is_some()) |
| .collect(), |
| ) |
| } |
| } |
| } |
| |
| pub fn new(db: &dyn ExpandDatabase, macro_file: MacroFileId) -> ExpansionInfo { |
| let loc: MacroCallLoc = 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 macro_def = db.macro_expander(loc.def); |
| let (parse, exp_map) = db.parse_macro_expansion(macro_file).value; |
| let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() }; |
| |
| let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value; |
| |
| let def = loc.def.ast_id().left().and_then(|id| { |
| let def_tt = match id.to_node(db) { |
| ast::Macro::MacroRules(mac) => mac.token_tree()?, |
| ast::Macro::MacroDef(_) if matches!(macro_def, TokenExpander::BuiltInAttr(_)) => { |
| return None |
| } |
| ast::Macro::MacroDef(mac) => mac.body()?, |
| }; |
| Some(InFile::new(id.file_id, def_tt)) |
| }); |
| let attr_input_or_mac_def = def.or_else(|| match loc.kind { |
| MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { |
| // FIXME: handle `cfg_attr` |
| let tt = collect_attrs(&ast_id.to_node(db)) |
| .nth(invoc_attr_index.ast_index()) |
| .and_then(|x| Either::left(x.1))? |
| .token_tree()?; |
| Some(InFile::new(ast_id.file_id, tt)) |
| } |
| _ => None, |
| }); |
| |
| ExpansionInfo { |
| expanded, |
| arg: arg_tt, |
| attr_input_or_mac_def, |
| macro_arg, |
| macro_def, |
| exp_map, |
| arg_map, |
| } |
| } |
| } |
| |
| /// Maps up the text range out of the expansion hierarchy back into the original file its from. |
| 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, |
| )) |
| } |
| |
| /// 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); |